解耦,未解耦的区别_幂等与时间解耦之旅

2023-05-16

解耦,未解耦的区别

HTTP中的幂等性意味着相同的请求可以执行多次,效果与仅执行一次一样。 如果用新资源替换某个资源的当前状态,则无论您执行多少次,最终状态都将与您仅执行一次相同。 举一个更具体的例子:删除用户是幂等的,因为无论您通过唯一标识符删除给定用户多少次,最终该用户都会被删除。 另一方面,创建新用户不是幂等的,因为两次请求该操作将创建两个用户。 用HTTP术语来说是RFC 2616:9.1.2等幂方法必须说的:

9.1.2等幂方法

方法还可以具有“ 幂等 ”的特性,因为[…] N> 0个相同请求的副作用与单个请求的副作用相同。 GET,HEAD,PUT和DELETE方法共享此属性。 同样,方法OPTIONS和TRACE不应有副作用,因此本质上是幂等的。

时间耦合是系统的不良特性,其中正确的行为隐含地取决于时间维度。 用简单的英语来说,这可能意味着例如系统仅在所有组件同时存在时才起作用。 阻塞请求-响应通信(ReST,SOAP或任何其他形式的RPC)要求客户端和服务器同时可用,这就是这种效果的一个示例。

基本了解这些概念的含义后,我们来看一个简单的案例研究- 大型多人在线角色扮演游戏 。 我们的人工用例如下:玩家发送优质短信,以在游戏内购买虚拟剑。 交付SMS时将调用我们的HTTP网关,我们需要通知部署在另一台计算机上的InventoryService 。 当前的API涉及ReST,其外观如下:

@Slf4j
@RestController
class SmsController {
 
    private final RestOperations restOperations;
 
    @Autowired
    public SmsController(RestOperations restOperations) {
        this.restOperations = restOperations;
    }
 
    @RequestMapping(value = "/sms/{phoneNumber}", method = POST)
    public void handleSms(@PathVariable String phoneNumber) {
        Optional<Player> maybePlayer = phoneNumberToPlayer(phoneNumber);
        maybePlayer
                .map(Player::getId)
                .map(this::purchaseSword)
                .orElseThrow(() -> new IllegalArgumentException("Unknown player for phone number " + phoneNumber));
    }
 
    private long purchaseSword(long playerId) {
        Sword sword = new Sword();
        HttpEntity<String> entity = new HttpEntity<>(sword.toJson(), jsonHeaders());
        restOperations.postForObject(
            "http://inventory:8080/player/{playerId}/inventory",
            entity, Object.class, playerId);
        return playerId;
    }
 
    private HttpHeaders jsonHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return headers;
    }
 
    private Optional<Player> phoneNumberToPlayer(String phoneNumber) {
        //...
    }
}

依次产生类似于以下内容的请求:

> POST /player/123123/inventory HTTP/1.1
> Host: inventory:8080
> Content-type: application/json
>
> {"type": "sword", "strength": 100, ...}
 
< HTTP/1.1 201 Created
< Content-Length: 75
< Content-Type: application/json;charset=UTF-8
< Location: http://inventory:8080/player/123123/inventory/1

这很简单。 SmsController只需通过发布购买的剑SmsController适当的数据转发到SmsController inventory:8080服务。 该服务立即或201 Created返回201 Created HTTP响应,确认操作成功。 此外,还会创建并返回到资源的链接,因此您可以对其进行查询。 有人会说:ReST是最新技术。 但是,如果您至少关心客户的钱并了解什么是ACID(比特币交易所还必须学习的东西:请参阅[1] , [2] , [3]和[4] )–该API也是易碎,容易出错。 想象所有这些类型的错误:

  1. 您的请求从未到达inventory服务器
  2. 您的请求已到达服务器,但被拒绝
  3. 服务器接受连接,但无法读取请求
  4. 服务器读取请求但挂起
  5. 服务器处理了请求,但发送响应失败
  6. 服务器发送了200 OK响应,但丢失了,您再也没有收到
  7. 收到服务器的响应,但客户端无法处理它
  8. 服务器的响应已发送,但客户端更早超时

在所有这些情况下,您仅在客户端获得一个异常,而您不知道服务器的状态是什么。 从技术上讲,您应该重试失败的请求,但是由于POST不具有幂等性,因此您最终可能会用一把以上的剑来奖励玩家(在5-8情况下)。 但是,如果不重试,您可能会失去游戏玩家的金钱而又不给他他宝贵的神器。 肯定有更好的办法。

将POST转换为幂等PUT

在某些情况下,通过将ID生成基本上从服务器转移到客户端,从POST转换为幂等PUT会非常简单。 使用POST的是服务器生成剑的ID,并将其发送到Location标头中的客户端。 事实证明,在客户端急切地生成UUID并稍稍更改语义加上在服务器端强制执行一些约束就足够了:

private long purchaseSword(long playerId) {
    Sword sword = new Sword();
    UUID uuid = sword.getUuid();
    HttpEntity<String> entity = new HttpEntity<>(sword.toJson(), jsonHeaders());
    asyncRetryExecutor
            .withMaxRetries(10)
            .withExponentialBackoff(100, 2.0)
            .doWithRetry(ctx ->
                    restOperations.put(
                            "http://inventory:8080/player/{playerId}/inventory/{uuid}",
                            entity, playerId, uuid));
    return playerId;
}

该API如下所示:

> PUT /player/123123/inventory/45e74f80-b2fb-11e4-ab27-0800200c9a66 HTTP/1.1
> Host: inventory:8080
> Content-type: application/json;charset=UTF-8
>
> {"type": "sword", "strength": 100, ...}
 
< HTTP/1.1 201 Created
< Content-Length: 75
< Content-Type: application/json;charset=UTF-8
< Location: http://inventory:8080/player/123123/inventory/45e74f80-b2fb-11e4-ab27-0800200c9a66

为什么这么大? 简单地说(不需要双关语),客户端现在可以根据需要重试PUT请求多次。 服务器首次收到PUT时,会将剑以客户端生成的UUID( 45e74f80-b2fb-11e4-ab27-0800200c9a66 )作为主键45e74f80-b2fb-11e4-ab27-0800200c9a66在数据库中。 在第二次尝试PUT的情况下,我们可以更新或拒绝该请求。 使用POST不可能,因为每个请求都被视为购买新剑–现在我们可以跟踪是否已经有这样的PUT。 我们只需要记住,后续的PUT并不是错误,而是更新请求:

@RestController
@Slf4j
public class InventoryController {
 
    private final PlayerRepository playerRepository;
 
    @Autowired
    public InventoryController(PlayerRepository playerRepository) {
        this.playerRepository = playerRepository;
    }
 
    @RequestMapping(value = "/player/{playerId}/inventory/{invId}", method = PUT)
    @Transactional
    public void addSword(@PathVariable UUID playerId, @PathVariable UUID invId) {
        playerRepository.findOne(playerId).addSwordWithId(invId);
    }
 
}
 
interface PlayerRepository extends JpaRepository<Player, UUID> {}
 
@lombok.Data
@lombok.AllArgsConstructor
@lombok.NoArgsConstructor
@Entity
class Sword {
 
    @Id
    @Convert(converter = UuidConverter.class)
    UUID id;
    int strength;
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Sword)) return false;
        Sword sword = (Sword) o;
        return id.equals(sword.id);
 
    }
 
    @Override
    public int hashCode() {
        return id.hashCode();
    }
}
 
@Data
@Entity
class Player {
 
    @Id
    @Convert(converter = UuidConverter.class)
    UUID id = UUID.randomUUID();
 
    @OneToMany(cascade = ALL, fetch = EAGER)
    @JoinColumn(name="player_id")
    Set<Sword> swords = new HashSet<>();
 
    public Player addSwordWithId(UUID id) {
        swords.add(new Sword(id, 100));
        return this;
    }
 
}

上面的代码片段中很少有快捷方式,例如直接将存储库注入到控制器,以及使用@Transactional注释。 但是你明白了。 还要注意,假设没有完全同时插入两个具有相同UUID的剑,此代码相当乐观。 否则将发生约束违例异常。

旁注1:我在控制器和JPA模型中都使用UUID类型。 开箱即用不支持它们,对于JPA,您需要自定义转换器:

public class UuidConverter implements AttributeConverter<UUID, String> {
    @Override
    public String convertToDatabaseColumn(UUID attribute) {
        return attribute.toString();
    }
 
    @Override
    public UUID convertToEntityAttribute(String dbData) {
        return UUID.fromString(dbData);
    }
}

对于Spring MVC同样(仅单向):

@Bean
GenericConverter uuidConverter() {
    return new GenericConverter() {
        @Override
        public Set<ConvertiblePair> getConvertibleTypes() {
            return Collections.singleton(new ConvertiblePair(String.class, UUID.class));
        }
 
        @Override
        public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
            return UUID.fromString(source.toString());
        }
    };
}

附注2:如果无法更改客户端,则可以通过将每个请求的哈希存储在服务器端来跟踪重复项。 这样,当多次发送同一请求(客户端重试)时,它将被忽略。 但是有时我们可能会有合法的用例,可以两次发送完全相同的请求(例如,在短时间内购买两把剑)。

时间耦合–客户不可用

您认为自己很聪明,但是仅重试就不够了。 首先,客户端可以在重新尝试失败的请求时死亡。 如果服务器严重损坏或关闭,重试可能要花费几分钟甚至几小时。 您不能仅仅因为下游依赖项之一关闭而就阻止了传入的HTTP请求-如果可能,您必须在后台异步处理此类请求。 但是,延长重试时间会增加客户端死亡或重新启动的可能性,这可能会使我们的请求松动。 想象一下,我们收到了优质的SMS,但是InventoryService目前处于关闭状态。 我们可以在第二,第二,第四等之后重试,但是如果InventoryService停机了几个小时又碰巧我们的服务也重新启动了怎么办? 我们只是失去了短信和剑从未被赋予玩家的机会。

解决此问题的方法是先保留未决请求,然后在后台处理它。 收到SMS消息后,我们几乎没有将玩家ID存储在名为“ pending_purchases数据库表中。 后台调度程序或事件唤醒异步线程,该线程将收集所有未完成的购买并将尝试将其发送到InventoryService (甚至可能以批处理方式?)每隔一分钟甚至一秒钟运行一次的周期性批处理线程,并收集所有未完成的请求将不可避免地导致延迟和不必要数据库流量。 因此,我打算使用Quartz调度程序,它将为每个待处理的请求调度重试作业:

@Slf4j
@RestController
class SmsController {
 
    private Scheduler scheduler;
 
    @Autowired
    public SmsController(Scheduler scheduler) {
        this.scheduler = scheduler;
    }
 
    @RequestMapping(value = "/sms/{phoneNumber}", method = POST)
    public void handleSms(@PathVariable String phoneNumber) {
        phoneNumberToPlayer(phoneNumber)
                .map(Player::getId)
                .map(this::purchaseSword)
                .orElseThrow(() -> new IllegalArgumentException("Unknown player for phone number " + phoneNumber));
    }
 
    private UUID purchaseSword(UUID playerId) {
        UUID swordId = UUID.randomUUID();
        InventoryAddJob.scheduleOn(scheduler, Duration.ZERO, playerId, swordId);
        return swordId;
    }
 
    //...
 
}

和工作本身:

@Slf4j
public class InventoryAddJob implements Job {
 
    @Autowired private RestOperations restOperations;
    @lombok.Setter private UUID invId;
    @lombok.Setter private UUID playerId;
 
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            tryPurchase();
        } catch (Exception e) {
            Duration delay = Duration.ofSeconds(5);
            log.error("Can't add to inventory, will retry in {}", delay, e);
            scheduleOn(context.getScheduler(), delay, playerId, invId);
        }
    }
 
    private void tryPurchase() {
        restOperations.put(/*...*/);
    }
 
    public static void scheduleOn(Scheduler scheduler, Duration delay, UUID playerId, UUID invId) {
        try {
            JobDetail job = newJob()
                    .ofType(InventoryAddJob.class)
                    .usingJobData("playerId", playerId.toString())
                    .usingJobData("invId", invId.toString())
                    .build();
            Date runTimestamp = Date.from(Instant.now().plus(delay));
            Trigger trigger = newTrigger().startAt(runTimestamp).build();
            scheduler.scheduleJob(job, trigger);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
 
}

每当我们收到优质的SMS时,我们都会安排异步作业立即执行。 Quartz将负责持久性(如果应用程序关闭,则在重新启动后将尽快执行作业)。 而且,如果该特定实例出现故障,则另一个可以承担这项工作–或我们可以形成集群并在它们之间进行负载平衡请求:一个实例接收SMS,另一个实例在InventoryService请求剑。 显然,如果HTTP调用失败,则稍后重新安排重试时间,一切都是事务性的且具有故障保护功能。 在实际代码中,您可能会添加最大重试限制以及指数延迟,但是您了解了。

时间耦合–客户端和服务器无法满足

我们为正确执行重试所做的努力是客户端和服务器之间模糊的时间耦合的标志-它们必须同时生活在一起。 从技术上讲,这不是必需的。 想象玩家在48小时内向客户服务发送一封包含订单的电子邮件,他们手动更改了库存。 同样的情况也适用于我们的情况,但是用某种消息代理(例如JMS)替换电子邮件服务器:

@Bean
ActiveMQConnectionFactory activeMQConnectionFactory() {
    return new ActiveMQConnectionFactory("tcp://localhost:61616");
}
 
@Bean
JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
    return new JmsTemplate(connectionFactory);
}

建立ActiveMQ连接后,我们可以简单地将购买请求发送给经纪人:

private UUID purchaseSword(UUID playerId) {
    final Sword sword = new Sword(playerId);
    jmsTemplate.send("purchases", session -> {
        TextMessage textMessage = session.createTextMessage();
        textMessage.setText(sword.toJson());
        return textMessage;
    });
    return sword.getUuid();
}

通过用JMS主题上的消息传递完全替换同步请求-响应协议,我们暂时将客户端与服务器分离。 他们不再需要同时生活。 此外,不止一个生产者和消费者可以相互交流。 例如,您可以有多个购买渠道,更重要的是:多个利益相关方,而不仅仅是InventoryService 。 更好的是,如果您使用像Kafka这样的专用消息传递系统, 则从技术上讲,您可以保留数天(数月)的消息而不会降低性能。 好处是,如果将另一个购买事件的使用者添加到InventoryService旁边的系统,它将立即收到许多历史数据。 而且,现在您的应用程序在时间上与代理耦合,因此,由于Kafka是分布式和复制的,因此在这种情况下它可以更好地工作。

异步消息传递的缺点

在ReST,SOAP或任何形式的RPC中使用的同步数据交换很容易理解和实现。 从延迟的角度来看,谁在乎这种抽象会疯狂地泄漏(本地方法调用通常比远程方法快几个数量级,更不用说它可能因本地未知的众多原因而失败),因此开发起来很快。 消息传递的一个真正警告是反馈渠道。 因为没有响应管道,所以您可以不再只是“ 发送 ”(“ return ”)消息而已。 您要么需要带有一些相关性ID的响应队列,要么需要每个请求临时的一次性响应队列。 我们还撒谎了一点,声称在两个系统之间放置消息代理可修复时间耦合。 确实如此,但是现在我们耦合到了消息传递总线,它也可能会崩溃,特别是因为它通常处于高负载下,有时无法正确复制。

本文展示了在分布式系统中提供保证的一些挑战和部分解决方案。 但是,归根结底,请记住,“ 仅一次 ”语义几乎不可能轻松实现,因此仔细检查您确实需要它们。

翻译自: https://www.javacodegeeks.com/2015/02/journey-to-idempotency-and-temporal-decoupling.html

解耦,未解耦的区别

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

解耦,未解耦的区别_幂等与时间解耦之旅 的相关文章

  • 将Linux系统下交叉编译的依赖库推到ARM平台下无法建立以来关系解决

    问题 xff1a 平常把opencv库使用源码交叉编译好之后 xff0c 从windows推到飞机里发现 xff0c 该库的依赖关系都没有了 xff0c 导致程序运行的时候报该库大小有问题 解决办法 xff1a 1 将linux下的库推到A
  • git clone submodule全是空白的解决办法

    git clone recursive git xxxx 64 xxx git com 结果submodule 文件夹时空白的 通过查看服务器上的代码 xff0c 发现submodule时存在的 xff08 主分支时空白的 xff0c 只有
  • 进程编译连接动态库,需要将动态库改为lib***.so

    1 本身该库可能编译成npuDetect so 但是需要改其名字为libnpuDetect so CMakelists才能找到该库 2 进程中连接动态库 xff0c 如果该库还依赖别的动态库 xff0c 则需要继续把其他的库也要连接进来
  • git 简单操作流程图

  • 关于for循环中的变量int i 如果跳出了这个for循环后,i的值是继续保留还是被释放掉了

    include lt iostream gt using namespace std int main char a 10 定义一个一维数组用来存放字符串 int i j 定义变量 cout lt lt 34 请输入字符 xff1a for
  • 将数组作为参数,调用该函数时候给的是数组地址还是整个数组

    1 在实际的应用中 xff0c 数组经常作为函数参数 xff0c 将数组中的数据传递到另外一个函数中 xff0c 一般来说 xff0c 传递可以采用两种方法 xff1a 1 gt 数组元素作为函数的实参时 xff0c 用法跟普通变量作参数相
  • c++中指针箭头的用法

    1 c 43 43 中指针用箭头来引用类或者结构体的成员 xff0c 箭头操作符 gt 用来引用指针对象 这是是用于类 xff0c 或者是结构体的指针变量用的 如struct Point int x y Point pt 61 new Po
  • string类的各种函数用法

    标准c 43 43 中string类函数介绍 注意不是CString 之所以抛弃char 的字符串而选用C 43 43 标准程序库中的string类 xff0c 是因为他和前者比较起来 xff0c 不必 担心内存是否足够 字符串长度等等 x
  • 中国做图像处理的公司

    xff08 1 xff09 北京北方猎波科技有限公司 xff1a http www northwh com beifangliebo main1 html 红外探测成像产品 xff08 2 xff09 深圳超多维光电子有限公司北京分公司 x
  • 【2015-2016,我在路上】

    前言 xff1a 每天 xff0c 每时 xff0c 每分 xff0c 时光的步伐永远不会停止 xff0c 当我提起笔 xff0c 写下的这一瞬间 xff0c 时间又是一年 xff0c 一年的时光 xff0c 在没逝去时 xff0c 感觉很
  • stm32+djyos下串口缓冲区配置

    就这行简单代码 xff1a write UartFd DataBuf 100 执行时间有时候长 xff0c 有时候短 xff0c 直接影响了后续代码的执行 xff0c why xff1f 进一步了解到 xff0c 用户发送数据 xff0c
  • 远程 sshd提示:Server unexpectedly closed network connection

    root 64 xx vim etc ssh sshd config 修改端口为3330 root 64 xx iptables I INPUT p tcp dport 3330 j ACCEPT 添加防火墙3330端口 允许 root 6
  • 解决git-gui文件数量的上限的问题

    原文链接 xff1a https blog csdn net u014221090 article details 55505228 遇到的情况 对于git的使用 xff0c 有一部分人是使用git bash xff08 命令行 xff09
  • 快速复制论文中的公式

    设备情况 MacOSOffice xff08 WPS不行 xff09 Mathpix Snipping Toolhttps www latex4technics com xff08 需要科技才能访问 0点云 xff09 步骤 xff1a 打
  • k8s官方文档学习

    文章目录 节点1 Addresses xff1a 2 conditions 块描述了所有 Running 节点的状态 xff1a 3 Capacity 块描述节点上的可用资源 xff1a CPU 内存 xff08 memory xff09
  • 一种简单有效的锂电池充电均衡电路

    这个均衡电路用的是三个一模一样的并联稳压电路组成的 xff0c 每个电池上并一个 电路原理图如下 xff1a 每个稳压电源都调节到4 2V 均衡的原理是 xff0c 当电池电压都小于4 2V时 xff0c 并联稳压电路不起作用 xff0c
  • 基于opencv对图片的二进制流进行编解码

    span class token keyword import span cv2 span class token keyword import span numpy span class token keyword as span np
  • STM32与传感器串口通讯问题

    前言 STM32F407ZGT6使用串口通信发送指令给传感器 xff0c 让传感器返回测得的数据 1 过程 大家看我这个程序 xff0c 在main函数前面我定义了一个read instruction数组 xff0c 存放读取传感器的指令
  • 检测到"_ITERATOR_DEBUG_LEVEL"的不匹配项

    最近在项目中遇到了问题 xff0c 编译器提示 检测到 34 ITERATOR DEBUG LEVEL 34 的不匹配项 xff0c 上网查找后发现是编译Release版本用到了DEBUG库的原因 xff0c 其中也提供了在预编译中加入 3
  • C语言实现单链表的逆置

    单链表的逆置是一个非常经典的问题 xff0c 这里利用两个思想进行解决 首先 xff0c 我们需要看下原理图 xff0c 其实两个思想都是一样的 xff0c 都是使后一个的节点的 next 指针指向前一个节点 xff0c 依次递推 xff0

随机推荐

  • UNIX下C语言的图形编程-curses.h函数库

    相信您在网路上一定用过如 tin elm 等工具 这些软体有项共同的特色 即他们能利用上下左右等方向键来控制游标的位置 除此之外 这些程式 的画面也较为美观 对 Programming 有兴趣的朋友一定对此感到好奇 也 许他能在 PC 上用
  • 如何同时启动多个Tomcat服务器

    这篇文章转载自 如何同时启动多个Tomcat服务器 conf子目录中打开server xml文件 xff0c 查找以下三处 xff1a 1 修改http访问端口 xff08 默认为8080端口 xff09 span class hljs t
  • 找到合适的方案记录服务端日志

    做过服务端开发的同学都清楚日志是多么的重要 你要分析应用当天的 PV UV 你需要对日志进行统计分析 你需要排查程序 BUG 你需要寻找日志中的异常信息等等 所以 建立一套合适的日志体系是非常有必要的 日志体系一般都会遵循这么几个原则 根据
  • doPDF——word转为PDF软件的使用方法

    doPDF软件下载链接 xff0c 版本doPDF 7 3 379 点击打开链接 1 下载好doPDF软件我们就可以动手安装了 安装到选择打印机的界面 软件使用方法如图所示 xff08 备注我用的是word2007 xff0c 可能与200
  • 过去的 2017 年

    过去的 2017 年分为两个部分 xff0c 前半部分偏忙碌 xff0c 个人时间较少 xff0c 但是收获甚微 xff1b 后半部分进入了一个学习的环境 xff0c 最主要的就是个人可自由支配的时间多了 xff0c 留给了我很多思考的时间
  • Android四大组件详解

    注 xff1a 本文主要来自网易的一个博主的文章 xff0c 经过阅读 xff0c 总结 xff0c 故留下文章在此 Android四大基本组件介绍与生命周期 Android四大基本组件分别是Activity xff0c Service服务
  • Socket通信原理和实践

    我们深谙信息交流的价值 xff0c 那网络中进程之间如何通信 xff0c 如我们每天打开浏览器浏览网页时 xff0c 浏览器的进程怎么与web服务器通信的 xff1f 当你用QQ聊天时 xff0c QQ进程怎么与服务器或你好友所在的QQ进程
  • linux下查看和添加PATH环境变量

    linux下查看和添加PATH环境变量 PATH xff1a 决定了shell将到哪些目录中寻找命令或程序 xff0c PATH的值是一系列目录 xff0c 当您运行一个程序时 xff0c Linux在这些目录下进行搜寻编译链接 编辑你的
  • Linux 内存映射函数 mmap()函数详解

    一 概述 内存映射 xff0c 简而言之就是将用户空间的一段内存区域映射到内核空间 xff0c 映射成功后 xff0c 用户对这段内存区域的修改可以直接反映到内核空间 xff0c 同样 xff0c 内核空间对这段区域的修改也直接反映用户空间
  • Cygwin获取root权限

    1 找到cygwin 的etc目录中有一个名为passwd的文件 2 用写字板打开passwd 这个文件 xff0c 找到以下部分 xff0c 把其中的windows用户名换成root xff08 共3处都改过来 xff09 Adminis
  • Linux Shell 只列出目录的方法

    在实际应用中 xff0c 我们有时需要仅列出目录 xff0c 下面是 4 种不同的方法 1 利用 ls 命令的 d 选项 xff1a ls d Desktop pic shell src 2 利用 ls 命令的 F 选项 xff1a ls
  • 读《Linux内核设计与实现》我想到了这些书

    从题目中可以看到 xff0c 这篇文章是以我读 Linux内核设计与实现 而想到的其他我读过的书 xff0c 所以 xff0c 这篇文章的主要支撑点是 Linux内核 开始读这本书已经是很久以前的事了 xff0c 不过 xff0c 由于时间
  • 计算机保研复习

    操作系统 1 进程间的通信方式 无名管道pipe xff1a 管道是一种半双工的通信方式 xff0c 数据只能单向流动 xff0c 而且只能在具有亲缘关系的进程间使用 进程的亲缘关系通常是指父子进程关系 命名管道FIFO xff1a 有名管
  • Gyro陀螺仪 > MPU 6000 vs ICM 20689

    目录 参考 MPU 6000和ICM 20689对比 陀螺仪的选择 xff1a 采样率与噪声 参考 Gyro MPU 6000 vs ICM 20689 IntoFPV Forum FPV Drone Flight Controller E
  • PV操作与信号灯及例子+三大操作系统共同点的线程通信

    看待技术问题要瞄准其本质 xff0c 不管是Linux VxWorks还是WIN32 xff0c 其涉及到多线程的部分都是那些内容 xff0c 无非就是线程控制和线程通信 xff0c 它们的许多函数只是名称不同 xff0c 其实质含义是等价
  • docker内使用apt-get update时报Temporary failure resolving ‘security.ubuntu.com错的解决方法

    经常构建docker 镜像的时候 xff0c 在镜像Build时需要经常软件的安装或更新时 xff0c 运行apt get update 或者apt get install 时出现Temporary failure resolving 39
  • ffmpeg 之 RTMP 一

    1 RTMP 介绍 RTMP Real Time Messaging Control 是Adobe 公司flash 播放器和服务器之间的音视 xff0c 视频以及数据传输的流媒体协议 该协议是个协议族 xff0c 包括多种协议 xff0c
  • 移植FreeModbus到FreeRTOS系统上

    实测连接西门子的组态屏运行正常 1 串口驱动移植 portserial c BOOL xMBPortSerialInit UCHAR ucPORT ULONG ulBaudRate UCHAR ucDataBits eMBParity eP
  • 地平线 momenta 华为 百度 毫末 上汽大通等大厂算法机会都有.有考虑的吗?

    base xff1a 北京 上海 苏州 南京 融合算法工程师 视觉算法工程师 规划控制 slam算法 嵌入式应用软件工程师 感知 控制 决策 自动驾驶行业或者机器人行业的1年以上都可以联系 我的微信 xff1a Dmyutmost
  • 解耦,未解耦的区别_幂等与时间解耦之旅

    解耦 未解耦的区别 HTTP中的幂等性意味着相同的请求可以执行多次 xff0c 效果与仅执行一次一样 如果用新资源替换某个资源的当前状态 xff0c 则无论您执行多少次 xff0c 最终状态都将与您仅执行一次相同 举一个更具体的例子 xff