高并发系统设计 -- 缓存与数据库一致性问题详细讲解

news2024/11/23 8:42:32

CAP理论

CAP

首先来谈一下数据的一致性这个话题,所谓的一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中的数据的值是一致的。

  • 强一致性:这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大;
  • 弱一致性:这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态;
  • 最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型。

​ CAP理论,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。分布式系统要么满足CA,要么CP,要么AP,无法同时满足CAP。

– 《维基百科 – CAP定理》

因此使用缓存提高性能,也就是可用性,那么就必定会有数据更新的延迟,也就是一致性不满足了,使用缓存满足的是可用性和分区容错型,抛弃的是一致性。是AP。

但是我们可以通过一些方案保证服务的最终一致性,也就是弱一致性。

常见的方案有:

  • 延迟双删
  • 重试机制删除缓存
  • 读取binlog异步删除缓存

延迟双删

  • 先删除缓存
  • 再更新数据库
  • 休眠一会儿(读业务逻辑数据的耗时+几百毫秒)
  • 再次删除缓存
  • 待后续请求落到DB后,将查询数据更新到缓存,确保后续请求一直落在缓存上

第二次删除的目的是确保读请求结束,写请求可以删除读请求可能带来的缓存脏数据,因为删除缓存和更新DB之间会有时间差。

我个人觉得这个解决方法是有点儿问题的,首先等待多少时间是一个玄学,这个其实都是小问题,最关键的问题是,如果我再次删除的这一步出错了怎么办,这个时候再用消息队列就鸡肋了,这样一整套操作下来可能要花费0.5秒,这0.5秒可能会有100个用户请求,MySQL直接被打宕机了。

写请求串行化

  1. 写请求更新之前先获取分布式锁,获得之后才能去数据库更新这个数据,获取不到就进行等待,超时后就返回更新失败。
  2. 更新完之后去刷新缓存,如果刷新失败就放到消息队列中进行重试,重试时取数据库最新数据更新缓存。

读请求发现缓存中没有数据时,直接去数据库读,读完更新缓存。

这个方案其实还可以,但是这是对写业务进行了串行化处理,导致吞吐量变低了。银行这样业务是完全可行的。

重试机制删除缓存

image.png

1.先更新数据库

2.异步删除缓存(如果数据库是读写分离的,那么删除缓存时需要延迟删除,否则可能会在删除缓存时,从库还没有收到更新后的数据,其他读请求就去从库读到旧数据然后设置到缓存中。)

3.删除缓存失败时,将删除的key放到内存队列或者是消息队列中进行异步重试

在更新完数据库后,我们为什么不直接更新,而是采用删除缓存呢?

这是因为直接更新缓存的话,在高并发场景下,有多个更新请求时,难以保证后更新数据库的请求会后更新缓存,也就是上面的高并发写问题。如果采用删除缓存,可以让下次读时读取数据库,更新缓存,保证一致性。

业务项目更新数据库,其他项目订阅binlog更新

image.png

1.业务项目直接更新数据库。

2.cannal项目会读取数据库的binlog,然后解析后发消息到kafka。

3.然后缓存更新项目订阅topic,从kafka接收到更新数据库操作的消息后,更新缓存,更新缓存失败时,新建异步线程去重试或者将操作发到消息队列,后续再进行处理。

你可能还是不懂

上面说的其实很概括,很抽象。

但很多人对这个问题,依旧有很多疑惑:

  • 到底是更新缓存还是删缓存?
  • 到底选择先更新数据库,再删除缓存,还是先删除缓存,再更新数据库?
  • 为什么要引入消息队列保证一致性?
  • 延迟双删会有什么问题?到底要不要用?

这篇文章,我们就来把这些问题讲清楚。

这篇文章干货很多,希望你可以耐心读完。

img

引入缓存提高性能

我们从最简单的场景开始讲起。

如果你的业务处于起步阶段,流量非常小,那无论是读请求还是写请求,直接操作数据库即可,这时你的架构模型是这样的:

img

但随着业务量的增长,你的项目请求量越来越大,这时如果每次都从数据库中读数据,那肯定会有性能问题。

这个阶段通常的做法是,引入「缓存」来提高读性能,架构模型就变成了这样:

img

当下优秀的缓存中间件,当属 Redis 莫属,它不仅性能非常高,还提供了很多友好的数据类型,可以很好地满足我们的业务需求。

但引入缓存之后,你就会面临一个问题:之前数据只存在数据库中,现在要放到缓存中读取,具体要怎么存呢?

最简单直接的方案是「全量数据刷到缓存中」:

  • 数据库的数据,全量刷入缓存(不设置失效时间)
  • 写请求只更新数据库,不更新缓存
  • 启动一个定时任务,定时把数据库的数据,更新到缓存中

img

这个方案的优点是,所有读请求都可以直接「命中」缓存,不需要再查数据库,性能非常高。

但缺点也很明显,有 2 个问题:

  1. 缓存利用率低:不经常访问的数据,还一直留在缓存中
  2. 数据不一致:因为是「定时」刷新缓存,缓存和数据库存在不一致(取决于定时任务的执行频率)

所以,这种方案一般更适合业务「体量小」,且对数据一致性要求不高的业务场景。

那如果我们的业务体量很大,怎么解决这 2 个问题呢?

缓存利用率和一致性问题

先来看第一个问题,如何提高缓存利用率?

想要缓存利用率「最大化」,我们很容易想到的方案是,缓存中只保留最近访问的「热数据」。但具体要怎么做呢?

我们可以这样优化:

  • 写请求依旧只写数据库
  • 读请求先读缓存,如果缓存不存在,则从数据库读取,并重建缓存
  • 同时,写入缓存中的数据,都设置失效时间

img

这样一来,缓存中不经常访问的数据,随着时间的推移,都会逐渐「过期」淘汰掉,最终缓存中保留的,都是经常被访问的「热数据」,缓存利用率得以最大化。

再来看数据一致性问题。

要想保证缓存和数据库「实时」一致,那就不能再用定时任务刷新缓存了。

所以,当数据发生更新时,我们不仅要操作数据库,还要一并操作缓存。具体操作就是,修改一条数据时,不仅要更新数据库,也要连带缓存一起更新。

但数据库和缓存都更新,又存在先后问题,那对应的方案就有 2 个:

  1. 先更新缓存,后更新数据库
  2. 先更新数据库,后更新缓存

哪个方案更好呢?

先不考虑并发问题,正常情况下,无论谁先谁后,都可以让两者保持一致,但现在我们需要重点考虑「异常」情况。

因为操作分为两步,那么就很有可能存在「第一步成功、第二步失败」的情况发生。

这 2 种方案我们一个个来分析。

1) 先更新缓存,后更新数据库

如果缓存更新成功了,但数据库更新失败,那么此时缓存中是最新值,但数据库中是「旧值」。

虽然此时读请求可以命中缓存,拿到正确的值,但是,一旦缓存「失效」,就会从数据库中读取到「旧值」,重建缓存也是这个旧值。

这时用户会发现自己之前修改的数据又「变回去」了,对业务造成影响。

2) 先更新数据库,后更新缓存

如果数据库更新成功了,但缓存更新失败,那么此时数据库中是最新值,缓存中是「旧值」。

之后的读请求读到的都是旧数据,只有当缓存「失效」后,才能从数据库中得到正确的值。

这时用户会发现,自己刚刚修改了数据,但却看不到变更,一段时间过后,数据才变更过来,对业务也会有影响。

可见,无论谁先谁后,但凡后者发生异常,就会对业务造成影响。那怎么解决这个问题呢?

别急,后面我会详细给出对应的解决方案。

我们继续分析,除了操作失败问题,还有什么场景会影响数据一致性?

这里我们还需要重点关注:并发问题

并发引发的一致性问题

假设我们采用「先更新数据库,再更新缓存」的方案,并且两步都可以「成功执行」的前提下,如果存在并发,情况会是怎样的呢?

有线程 A 和线程 B 两个线程,需要更新「同一条」数据,会发生这样的场景:

  1. 线程 A 更新数据库(X = 1)
  2. 线程 B 更新数据库(X = 2)
  3. 线程 B 更新缓存(X = 2)
  4. 线程 A 更新缓存(X = 1)

最终 X 的值在缓存中是 1,在数据库中是 2,发生不一致。

也就是说,A 虽然先于 B 发生,但 B 操作数据库和缓存的时间,却要比 A 的时间短,执行时序发生「错乱」,最终这条数据结果是不符合预期的。

同样地,采用「先更新缓存,再更新数据库」的方案,也会有类似问题,这里不再详述。

那怎么解决这个问题呢?这里通常的解决方案是,加「分布式锁」。

两个线程要修改「同一条」数据,每个线程在改之前,先去申请分布式锁,拿到锁的线程才允许更新数据库和缓存,拿不到锁的线程,返回失败,等待下次重试。

这么做的目的,就是为了只允许一个线程去操作数据和缓存,避免并发问题。

除此之外,我们从「缓存利用率」的角度来评估这个方案,也是不太推荐的。

这是因为每次数据发生变更,都「无脑」更新缓存,但是缓存中的数据不一定会被「马上读取」,这就会导致缓存中可能存放了很多不常访问的数据,浪费缓存资源。

而且很多情况下,写到缓存中的值,并不是与数据库中的值一一对应的,很有可能是先查询数据库,再经过一系列「计算」得出一个值,才把这个值才写到缓存中。

由此可见,这种「更新数据库 + 更新缓存」的方案,不仅缓存利用率不高,还会造成机器性能的浪费。

所以此时我们需要考虑另外一种方案:删除缓存

删除缓存可以保证一致性吗?

删除缓存对应的方案也有 2 种:

  1. 先删除缓存,后更新数据库
  2. 先更新数据库,后删除缓存

同样地,先来看「第二步」操作失败的情况。

先删除缓存,后更新数据库,第二步操作失败,数据库没有更新成功,那下次读缓存发现不存在,则从数据库中读取,并重建缓存,此时数据库和缓存依旧保持一致。

但如果是先更新数据库,后删除缓存,第二步操作失败,数据库是最新值,缓存中是旧值,发生不一致。所以,这个方案依旧存在问题。

总之,和前面提到的问题类似,第二步失败依旧有不一致的风险。

好,我们再来看「并发」问题,这个问题是我们需要关注的「重点」。

1) 先删除缓存,后更新数据库

如果有 2 个线程要并发「读写」数据,可能会发生以下场景:

  1. 线程 A 要更新 X = 2(原值 X = 1)
  2. 线程 A 先删除缓存
  3. 线程 B 读缓存,发现不存在,从数据库中读取到旧值(X = 1)
  4. 线程 A 将新值写入数据库(X = 2)
  5. 线程 B 将旧值写入缓存(X = 1)

最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),发生不一致。

可见,先删除缓存,后更新数据库,当发生「读+写」并发时,还是存在数据不一致的情况。

2) 先更新数据库,后删除缓存

依旧是 2 个线程并发「读写」数据:

  1. 缓存中 X 不存在(数据库 X = 1)
  2. 线程 A 读取数据库,得到旧值(X = 1)
  3. 线程 B 更新数据库(X = 2)
  4. 线程 B 删除缓存
  5. 线程 A 将旧值写入缓存(X = 1)

最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),也发生不一致。

这种情况「理论」来说是可能发生的,但实际真的有可能发生吗?

其实概率「很低」,这是因为它必须满足 3 个条件:

  1. 缓存刚好已失效
  2. 读请求 + 写请求并发
  3. 更新数据库 + 删除缓存的时间(步骤 3-4),要比读数据库 + 写缓存时间短(步骤 2 和 5)

仔细想一下,条件 3 发生的概率其实是非常低的。

因为写数据库一般会先「加锁」,所以写数据库,通常是要比读数据库的时间更长的。

这么来看,「先更新数据库 + 再删除缓存」的方案,是可以保证数据一致性的。

所以,我们应该采用这种方案,来操作数据库和缓存。

好,解决了并发问题,我们继续来看前面遗留的,第二步执行「失败」导致数据不一致的问题

如何保证两步都执行成功?

前面我们分析到,无论是更新缓存还是删除缓存,只要第二步发生失败,那么就会导致数据库和缓存不一致。

保证第二步成功执行,就是解决问题的关键

想一下,程序在执行过程中发生异常,最简单的解决办法是什么?

答案是:重试

是的,其实这里我们也可以这样做。

无论是先操作缓存,还是先操作数据库,但凡后者执行失败了,我们就可以发起重试,尽可能地去做「补偿」。

那这是不是意味着,只要执行失败,我们「无脑重试」就可以了呢?

答案是否定的。现实情况往往没有想的这么简单,失败后立即重试的问题在于:

  • 立即重试很大概率「还会失败」
  • 「重试次数」设置多少才合理?
  • 重试会一直「占用」这个线程资源,无法服务其它客户端请求

看到了么,虽然我们想通过重试的方式解决问题,但这种「同步」重试的方案依旧不严谨。

那更好的方案应该怎么做?

答案是:异步重试。什么是异步重试?

其实就是把重试请求写到「消息队列」中,然后由专门的消费者来重试,直到成功。

或者更直接的做法,为了避免第二步执行失败,我们可以把操作缓存这一步,直接放到消息队列中,由消费者来操作缓存。

到这里你可能会问,写消息队列也有可能会失败啊?而且,引入消息队列,这又增加了更多的维护成本,这样做值得吗?

这个问题很好,但我们思考这样一个问题:如果在执行失败的线程中一直重试,还没等执行成功,此时如果项目「重启」了,那这次重试请求也就「丢失」了,那这条数据就一直不一致了。

所以,这里我们必须把重试消息或第二步操作放到另一个「服务」中,这个服务用「消息队列」最为合适。这是因为消息队列的特性,正好符合我们的需求:

  • 消息队列保证可靠性:写到队列中的消息,成功消费之前不会丢失(重启项目也不担心)
  • 消息队列保证消息成功投递:下游从队列拉取消息,成功消费后才会删除消息,否则还会继续投递消息给消费者(符合我们重试的需求)

至于写队列失败和消息队列的维护成本问题:

  • 写队列失败:操作缓存和写消息队列,「同时失败」的概率其实是很小的
  • 维护成本:我们项目中一般都会用到消息队列,维护成本并没有新增很多

所以,引入消息队列来解决这个问题,是比较合适的。这时架构模型就变成了这样:

img

那如果你确实不想在应用中去写消息队列,是否有更简单的方案,同时又可以保证一致性呢?

方案还是有的,这就是近几年比较流行的解决方案:订阅数据库变更日志,再操作缓存

具体来讲就是,我们的业务应用在修改数据时,「只需」修改数据库,无需操作缓存。

那什么时候操作缓存呢?这就和数据库的「变更日志」有关了。

拿 MySQL 举例,当一条数据发生修改时,MySQL 就会产生一条变更日志(Binlog),我们可以订阅这个日志,拿到具体操作的数据,然后再根据这条数据,去删除对应的缓存。

img

订阅变更日志,目前也有了比较成熟的开源中间件,例如阿里的 canal,使用这种方案的优点在于:

  • 无需考虑写消息队列失败情况:只要写 MySQL 成功,Binlog 肯定会有
  • 自动投递到下游队列:canal 自动把数据库变更日志「投递」给下游的消息队列

当然,与此同时,我们需要投入精力去维护 canal 的高可用和稳定性。

如果你有留意观察很多数据库的特性,就会发现其实很多数据库都逐渐开始提供「订阅变更日志」的功能了,相信不远的将来,我们就不用通过中间件来拉取日志,自己写程序就可以订阅变更日志了,这样可以进一步简化流程。

至此,我们可以得出结论,想要保证数据库和缓存一致性,推荐采用「先更新数据库,再删除缓存」方案,并配合「消息队列」或「订阅变更日志」的方式来做

主从库延迟和延迟双删问题

到这里,还有 2 个问题,是我们没有重点分析过的。

第一个问题,还记得前面讲到的「先删除缓存,再更新数据库」导致不一致的场景么?

这里我再把例子拿过来让你复习一下:

2 个线程要并发「读写」数据,可能会发生以下场景:

  1. 线程 A 要更新 X = 2(原值 X = 1)
  2. 线程 A 先删除缓存
  3. 线程 B 读缓存,发现不存在,从数据库中读取到旧值(X = 1)
  4. 线程 A 将新值写入数据库(X = 2)
  5. 线程 B 将旧值写入缓存(X = 1)

最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),发生不一致。

第二个问题:是关于「读写分离 + 主从复制延迟」情况下,缓存和数据库一致性的问题。

如果使用「先更新数据库,再删除缓存」方案,其实也发生不一致:

  1. 线程 A 更新主库 X = 2(原值 X = 1)
  2. 线程 A 删除缓存
  3. 线程 B 查询缓存,没有命中,查询「从库」得到旧值(从库 X = 1)
  4. 从库「同步」完成(主从库 X = 2)
  5. 线程 B 将「旧值」写入缓存(X = 1)

最终 X 的值在缓存中是 1(旧值),在主从库中是 2(新值),也发生不一致。

看到了么?这 2 个问题的核心在于:缓存都被回种了「旧值」

那怎么解决这类问题呢?

最有效的办法就是,把缓存删掉

但是,不能立即删,而是需要「延迟删」,这就是业界给出的方案:缓存延迟双删策略

按照延时双删策略,这 2 个问题的解决方案是这样的:

解决第一个问题:在线程 A 删除缓存、更新完数据库之后,先「休眠一会」,再「删除」一次缓存。

解决第二个问题:线程 A 可以生成一条「延时消息」,写到消息队列中,消费者延时「删除」缓存。

这两个方案的目的,都是为了把缓存清掉,这样一来,下次就可以从数据库读取到最新值,写入缓存。

但问题来了,这个「延迟删除」缓存,延迟时间到底设置要多久呢?

  • 问题1:延迟时间要大于「主从复制」的延迟时间
  • 问题2:延迟时间要大于线程 B 读取数据库 + 写入缓存的时间

但是,这个时间在分布式和高并发场景下,其实是很难评估的。

很多时候,我们都是凭借经验大致估算这个延迟时间,例如延迟 1-5s,只能尽可能地降低不一致的概率。

所以你看,采用这种方案,也只是尽可能保证一致性而已,极端情况下,还是有可能发生不一致。

所以实际使用中,我还是建议你采用「先更新数据库,再删除缓存」的方案,同时,要尽可能地保证「主从复制」不要有太大延迟,降低出问题的概率。

可以做到强一致吗?

看到这里你可能会想,这些方案还是不够完美,我就想让缓存和数据库「强一致」,到底能不能做到呢?

其实很难。

要想做到强一致,最常见的方案是 2PC、3PC、Paxos、Raft 这类一致性协议,但它们的性能往往比较差,而且这些方案也比较复杂,还要考虑各种容错问题。

相反,这时我们换个角度思考一下,我们引入缓存的目的是什么?

没错,性能

一旦我们决定使用缓存,那必然要面临一致性问题。性能和一致性就像天平的两端,无法做到都满足要求。

而且,就拿我们前面讲到的方案来说,当操作数据库和缓存完成之前,只要有其它请求可以进来,都有可能查到「中间状态」的数据。

所以如果非要追求强一致,那必须要求所有更新操作完成之前期间,不能有「任何请求」进来。

虽然我们可以通过加「分布锁」的方式来实现,但我们也要付出相应的代价,甚至很可能会超过引入缓存带来的性能提升。

所以,既然决定使用缓存,就必须容忍「一致性」问题,我们只能尽可能地去降低问题出现的概率。

同时我们也要知道,缓存都是有「失效时间」的,就算在这期间存在短期不一致,我们依旧有失效时间来兜底,这样也能达到最终一致。

总结

好了,总结一下这篇文章的重点。

1、想要提高应用的性能,可以引入「缓存」来解决

2、引入缓存后,需要考虑缓存和数据库一致性问题,可选的方案有:「更新数据库 + 更新缓存」、「更新数据库 + 删除缓存」

3、更新数据库 + 更新缓存方案,在「并发」场景下无法保证缓存和数据一致性,解决方案是加「分布锁」,但这种方案存在「缓存资源浪费」和「机器性能浪费」的情况

4、采用「先删除缓存,再更新数据库」方案,在「并发」场景下依旧有不一致问题,解决方案是「延迟双删」,但这个延迟时间很难评估

5、采用「先更新数据库,再删除缓存」方案,为了保证两步都成功执行,需配合「消息队列」或「订阅变更日志」的方案来做,本质是通过「重试」的方式保证数据最终一致

6、采用「先更新数据库,再删除缓存」方案,「读写分离 + 主从库延迟」也会导致缓存和数据库不一致,缓解此问题的方案是「延迟双删」,凭借经验发送「延迟消息」到队列中,延迟删除缓存,同时也要控制主从库延迟,尽可能降低不一致发生的概率

后记

本以为这个老生常谈的话题,写起来很好写,没想到在写的过程中,还是挖到了很多之前没有深度思考过的细节。

在这里我也分享 4 点心得给你:

1、性能和一致性不能同时满足,为了性能考虑,通常会采用「最终一致性」的方案

2、掌握缓存和数据库一致性问题,核心问题有 3 点:缓存利用率、并发、缓存 + 数据库一起成功问题

3、失败场景下要保证一致性,常见手段就是「重试」,同步重试会影响吞吐量,所以通常会采用异步重试的方案

4、订阅变更日志的思想,本质是把权威数据源(例如 MySQL)当做 leader 副本,让其它异质系统(例如 Redis / Elasticsearch)成为它的 follower 副本,通过同步变更日志的方式,保证 leader 和 follower 之间保持一致

很多一致性问题,都会采用这些方案来解决,希望我的这些心得对你有所启发。

部分搬运来自http://kaito-kidd.com/2021/09/08/how-to-keep-cache-and-consistency-of-db/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/165607.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

steam搬砖项目,信息差赚钱,内含全部讲解

Steam平台就是一个全球的游戏平台,搬砖主要是搬的一款火遍全球的游戏CSGO的装备和饰品。CS听说过吧,这款游戏就是CS的一个系列。(通俗易懂的理解就是,从国外steam游戏平台购买装备,再挂到国内网易buff平台上进行售卖。…

【TypeScript】TS进阶-装饰器(九)

🐱个人主页:不叫猫先生 🙋‍♂️作者简介:前端领域新星创作者、阿里云专家博主,专注于前端各领域技术,共同学习共同进步,一起加油呀! 💫系列专栏:vue3从入门到…

ADI demo PL工程的编译-以adrv9371x_zc706为例子之使用Cygwin

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 ADI demo PL工程的编译-以adrv9371x_zc706为例子之使用Cygwin前言Cygwin 安装工程编译总结前言 之前一篇ADI demo工程的编译是ADI不推荐的方法,而且确实在后面NO…

【Django项目开发】django的信号机制(八)

文章目录1、什么是信号2、代码实现3、要想上面定义的信号机制生效,需要在apps.py中加载信号机制1、什么是信号 通俗来说,信号就是通信双方约定的一种信息通知方式,双方通过信号来确定发生了什么事情,然后决定自己应该做什么。 Django 中的信号用于在框架执行操作时解耦。当某…

2.1总线概述

文章目录一、总线简图二、概念与分类1.总线定义2.总线特点(1)分时(2)共享3.总线特性4.总线的分类(1)按数据传输格式①串行总线②并行总线(2)按总线功能①片内总线②系统总线③通信总…

【原创】linux实时应用如何printf输出不影响实时性?

linux实时应用printf输出 文章目录linux实时应用printf输出1. 前言2. linux终端输出3. 常见的NRT IO输出方案3.1 一种实现方式3.3 改进3. Xenomai3 printf()接口3.1 应用运行前环境初始化1. GCC特定语法2. libcobalt printf初始化流程3.2 libcobalt printf内存管理1. print_buf…

我的周刊(第074期)

我的信息周刊,记录这周我看到的有价值的信息,主要针对计算机领域,内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。🎯 项目code-server[1]这是一款开源的基于 VSCode 的在线编…

【Linux系统】第四篇:Linux中编辑器vim的使用

文章目录一、vim的介绍1、vim的基本模式2、vim的使用二、命令模式1、光标移动操作2、文本复制、粘贴、剪切、撤销操作3、文本编辑相关操作三、插入模式四、底行模式底行模式命令集五、vim的配置原理六、sudo无法提权问题一、vim的介绍 vim是Linux下的一款多模式编辑器。 注意…

【已解决】右键以某应用打开xx文件时,没有“始终”选项怎么办

问题解决方案简单来说详细操作解释问题 右键以某应用打开xx文件时,没有“始终”选项 解决方案 简单来说 在注册表:计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\找到要打开的文件后缀名,删除…

2022尚硅谷SSM框架跟学(五)Spring基础二

2022尚硅谷SSM框架跟学 五Spring基础二3.AOP3.1场景模拟3.1.1声明接口3.1.2创建实现类3.1.3创建带日志功能的实现类3.1.4提出问题(1)现有代码缺陷(2)解决思路(3)困难3.2代理模式3.2.1概念(1)介绍(2)生活中的代理(3)相关术语3.2.2静态代理3.2.3动态代理3.2.4测试3.3AOP概念及相关…

视频 | 生信 linux 实战题目讲解03

点击阅读原文跳转完整教案。1 Linux初探,打开新世界的大门1.1 Linux系统简介和目录理解1.1.1 为什么要用Linux系统1.1.2 Linux系统无处不在1.1.3 免费的Linux系统来一套1.1.4 Linux系统登录-联系远方的她1.1.5 初识Linux系统 - 黑夜中的闪烁是你的落脚点1.1.6 我的电…

使用混沌和非线性控制参数来提高哈里斯鹰优化算法的优化性能,解决车联网相关的路由问题(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

华为路由汇总实验

目录 OSPF路由聚合实验 在ABR上做路由聚合 在ASBR上做路由聚合 BGP路由聚合实验 在AR4-1上做静态聚合 在AR6-3上做手动聚合 ISIS路由聚合实验 R1配置路由聚合 OSPF路由聚合实验 OSPF——基本概念5(汇总、更新、认证)_静下心来敲木鱼的博客-CSDN博…

华为MPLS单域实验配置

目录 配置AS内的MPLS LDP协议 配置PE-PE之间的MP-BGP协议 在PE上配置VPN实例 在CE侧配置PE-CE的路由协议 在PE侧配置PE-CE的路由协议 在PE侧将CE的路由重发布进MP-BGP中 在CE侧将MP-BGP的路由重发布进CE中 MPLS隧道——单域基础理论讲解_静下心来敲木鱼的博客-CSDN博客h…

SPSS常用的10种统计分析

目录 实验一 地理数据的统计处理 一、实验目的 二、实验内容 三、实验步骤 实验二 双变量相关分析 一、实验目的 二、实验内容 三、实验步骤 实验三 主成分分析 一、实验目的 二、实验内容 三、实验步骤 实验四 因子分析 一、实验目的 二、实验内容 三、实…

【Linux】缓冲区 进度条小程序

目录 一、\r && \n 二、缓冲区的概念 三、小程序编写 1、倒数小程序 2、进度条小程序 一、\r && \n C语言中有很多字符,但是宏观上可以分成两类:可显字符、控制字符。 可显字符包括我们见到的 1、2、3....,a、b、c....…

历史最全事件抽取任务分类、经典论文、模型及数据集整理分享

事件抽取技术是从非结构化信息中抽取出用户感兴趣的事件,并以结构化呈现给用户。事件抽取任务可分解为4个子任务: 触发词识别、事件类型分类、论元识别和角色分类任务。其中,触发词识别和事件类型分类可合并成事件识别任务。事件识别判断句子中的每个单词…

Linux面试题

Linux 概述 什么是Linux Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和Unix的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的Unix工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想…

Java使用Zxing二维码生成

目录 1、二维码简介 二维码纠错级别 2、ZXing简介 3、示例 3.1 搭建一个maven项目,引入Zxing依赖包 3.2 创建QrCodeUtil.java 类 1、二维码简介 二维条形码是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录…

C++ 001:C++ 基础语法

1. 开始之前 1.1 学习路线 这次我是下定决心要学 C 了,而且是系统地,不半途而废地学习 C 了~ 有这个新专栏为证~ 由于某次偶然的机会,我看见了一张 C 竞赛的学习路线表(这里由于表格内容太多就不贴出来)&#xff0c…