cris's blog


  • 首页

  • 标签

  • 分类

  • 归档

谈谈rabbitmq中如何保证消息的可靠传输

发表于 2020-04-17 | 分类于 rabbitmq

前言

我们经常会在项目里面有用到消息队列的场景,比如贷款场景,用户之前在我们这里借了一笔钱,今天把钱还清了,假设这个时候需要发送一个含有推广链接的短信,或者这个用户还钱的时候正好触发了某个规则,可以对这个用户进行额度提升的操作,都可以用消息队列来处理,那么问题来了,

  1. 消息是如何投递的?
  2. 消息是否成功的投递到了消息队列中?
  3. 消息投递过程中是否会有丢失的问题?消息是否被正确消费了呢?
    带着上述的几个问题并以rabbitmq为例开始我们今天的主题

消息可靠性

在计算机网络中,一个可靠的通讯协议可以通知发送者它发送的数据是否成功到达消息接收方,那么在rabbitmq中消息是怎样投递的呢?

rabbitmq 消息投递过程

  • 生产者生产消息
  • 生产者建立与broker的连接
  • 生产者发送消息到broker
  • broker收到消息,并通过消息中的exchange,routing-key将其路由到指定队列
  • consumer监听到指定队列的消息并消费

那么这里我们可以提出几个问题

生产者是否成功地将消息投递到broker?

在默认情况下,rabbitmq 生产者发送完消息后,是不会给rabbitmq返回任何结果的,那很有可能消息发送到一半因为网络或其他原因,消息没有到达broker,消息直接丢失,这种情况是我们无法忍受的,当然rabbitmq也提供了两种方式解决这种问题,供我们自行选择。

  • 事务消息模式
  • 发送方确认模式

生产者的消息到达broker后,在broker中是否会丢失?

消息到达broker后,很有可能因为一些原因导致broker重启或者退出,因为rabbitmq的消息默认都是在内存里面的,发生重启会导致消息丢失,这也是我们无法忍受的,这里用到了rabbitmq中的持久化机制,需要我们同时保证3个条件,才能保证消息的持久化

  • 消息投递时开启了持久化
  • 目标交换机开启了持久化
  • 目标队列开启了持久化
  • 消息是否被消费者成功消费呢?

正常来讲如果消息投递成功了,并且在broker开启了持久化是不是就能保证消息可靠传输了呢?答案是否定的,消费者消费也可能出现问题,比如消费失败,或者消息压根就没有到达消费者(丢了),这个时候又该如何处理呢?RabbitMQ这里提供了一个消费确认的机制,这种机制分为手动确认和自动确认

  • manual ack 需要业务方根据消费情况手动确认消息消费情况
  • auto ack 消息一经broker发出,就认为已经投递成功了

很显然 autoack这种是有消息丢失风险的,我们很难保证业务方正确消费的情况,所以一般来说我们想要保证消费的可靠性需要开启手动ack模式,确保业务方正确消费后,再进行确认,下面就说一下手动确认在标准AMQP协议中定义的手动ack的接口方法

  • basic.ack 如果业务方正确消费了消息,那么业务方需要给broker发送一个ack消息,代表消息已经正确处理, broker可以将这条消息删除了

  • basic.reject 如果业务方发生了异常或者因为某些原因导致无法消费消息,可以给broker发送拒绝消息,让这条消息重新入队,以便让别的消费者可以消费这条消息 对应标准AMQP0.9.1.1协议中的 basic.reject方法

  • basic.nack 在basic.reject的基础上提供批量拒绝消息的方法

当消息消费失败时候怎么处理?

在原有队列进行几次重试操作,排除网络抖动原因后,将失败消息从原有队列上删除,并加入一个专门处理异常的重试队列以重试的方式进行补偿,必要的时候进行人工处理(若是消息发送方因为某些原因导致数据回滚了,当前消息一定会处理失败,所以需要将对应的消息进行删除,若是因为下游服务不可用导致的,则需要跟业务方进行沟通,如果是业务代码发生了bug,修代码吧兄弟,总之及时看生产的监控消息)

参考文章

rabbitmq官网#Consumer Acknowledgements and Publisher Confirms

谈谈如何保障redis的高可用?

发表于 2020-04-16 | 分类于 redis

前言

我们都知道,系统中每引入一个新的中间件,都会造成可用性的降低,举个例子,本来我只需要保障应用本身高可用即可,现在又引入了中间件A,如果它宕机了怎么办?所以,伴随着这个话题,我们今天就来聊聊如果保证redis的高可用

高可用方案

为什么要用保证redis的高可用?redis单点会带来哪些问题呢?

  • 单点故障问题
    如果目前只有一台redis服务器在运行,发生机器故障,服务直接不可用,如果故障比较严重,磁盘损坏了,数据就损坏了,单台机器时没有办法保证数据的安全性的
  • 容量瓶颈
    redis是内存存储的,单机很容易受到内存容量的限制

主从复制模式

主从复制,顾名思义,分为主节点和从节点,主节点负责写,从节点负责扩展主节点的读能力,并且可以将主节点的数据同步给从节点,一旦发现主节点发生故障,从节点可以随时顶上来

主从复制模式面临的问题

  • 并没有解决单点的写压力
  • 主节点发生故障的时候,需要手动将从节点晋升为主节点,同时需要通知应用放更改主节点地址并重启,并且需要命令其他从节点复制新的主节点,整个过程需要人工干预
  • 主节点的存储能力受到单机的限制

redis 主从复制和sentinel架构示意图如下

redis sentinel 哨兵模式

哨兵模式实际上就是在主从复制的基础上,加上了一层监控的哨兵节点。Redis-sentinel也是官方提供的高可用方案,哨兵模式是在2.6以后开始提供的,如果需要在生产上使用,尽量使用2.8之后的版本,比较稳定。哨兵模式下在主节点发生故障时,可以实现自动的主备切换,并且可以监控多个主从集群,哨兵节点本身也支持集群,毕竟哨兵只有单个节点也无法支持哨兵节点的高可用,并且如果唯一的哨兵如果宕机了,哨兵模式又变回主从复制模式了,又将无法自动的进行主备切换了。
客户端连接的是哨兵节点,只要连接任意一个哨兵节点,就可以获得redis主从集群中的信息。


图片引用自Redis哨兵(Sentinel)模式快速入门

哨兵节点的作用

监控

sentinel 会不间断的检查主服务器和从服务器是否正常运行

通知

当被监控的某个redis服务器如果出现问题,sentinel通过api脚本向管理员或者其他应用程序发送通知

自动故障迁移

当主节点不能正常工作时,sentinel会开始一次自动的故障迁移,它将会与失效主节点是主从关系的其中一个主节点的其中一个从节点升级为新的主节点,并且将其他的从节点指向新的主节点。

配置提供者

在redis sentinel模式下,客户端在初始化连接的是sentinel的集合,从中获取主节点的信息。

主观下线(SDOWN)

主观下线其实是指单个sentinel实例对节点做出的下线判断,默认情况下,每个sentinel会以每秒一次的频率对redis节点和其他sentinel节点发送PING命令,并通过节点的回复来判断是否在线。

主观下线适用于所有主节点和从节点。如果在down-after-milliseconds毫秒内,sentinel没有收到目标节点的有效回复(+PONG,-LOADING,-MASTERDOWN),则会判定为该节点为主观下线

客观下线(ODOWN)

客观下线是指多个sentinel实例对同一个实例进行主观下线判断。

客观下线只适用于主节点,如果哨兵节点发现主节点主观下线后,会通过is-master-down-by-addr命令向其他sentinel节点询问该节点的状态,若果超过个数的节点认为主节点不可达(主节点已经主观下线),则sentinel节点会判定主节点为客观下线,只有发现主节点客观下线后,才会开启自动的故障迁移

自动故障迁移(automatic-failover)过程

  1. sentinel发现主节点主观下线,修改其状态为SDOWN
  2. sentinel和其他sentinel节点确认master是否down掉(SENTINEL is-master-down-by-addr),确认其状态为客观下线(ODOWN),
  3. sentinel间进行leader选举,由被选出的leader sentinel节点来进行后续的故障切换工作,选举基于Raft协议
  4. sentinel节点开始进行故障切换,并选出合适的从节点作为主节点
  5. 对选出的从节点执行 slaveof no one将其晋升为新的主节点
  6. 对其余的从节点发送命令,使其变为新的主节点的从节点,并且从新的主节点复制数据
  7. leader sentinel继续监控已下线主节点,一旦其重新上线,就把他降级为新的主节点的从节点,并且从新的主节点中复制数据

leader哨兵节点选举

  1. 某个sentinel发现master节点主观下线后,执行以下操作

    • 如果该哨兵节点没有投过票,它就成为candidate
    • 如果该哨兵节点已经投过票,则在2倍的故障时间内就不会成为leader,也就是相当于一个follower
  2. sentinel节点成为candidate后执行以下操作

    • 更新故障转移状态为start
    • 令当前epoch + 1,即发起新一轮的选举,在sentinel中epoch相当于Raft协议中的term
    • 向其他节点发送SENTINEL is-master-down-by-addr指令,该命令包含自己的epoch
    • 投自己一票,投票的方式是将自己master结构中的leader和leader_epoch改成投给的sentinel和它的epoch
  3. 其他节点收到Candidate的SENTINEL is-master-down-by-addr命令,如果收到命令的sentinel判断发现当前epoch和通过命令收到的epoch一样,证明它已经投过票了,当前epoch内该sentinel就只能成为follower

  4. candidate会不断统计自己的票数,直到他发现认同他成为leader超过一半且超过它配置的quorum,则该candidate成为leader哨兵节点

  5. 在一个选举时间内,如果一个candidate没有获得超过一半的票数且超过quorum,则该次选举失败

  6. 如果在一个epoch内,没有一个candidate获得更多的票数,则2倍故障转移的时间后,candidate增加epoch并重新投票

master节点选举

从已经宕机的主节点的从节点中挑选一个节点作为主节点,这个主节点是按照以下规则选出来的

  • slave优先级(手动配置的) 如果有配置最高的slave节点,则返回,没有则继续
  • 复制偏移量最大的(对主节点数据复制的最完整的从节点),如果有则返回,没有则继续
  • 启动最早的slave节点(run_id最小)

redis cluster

来聊redis cluster之前先来聊聊几个问题

水平扩展性

水平扩展性或者说给系统中添加节点的能力,对于redis来说特别重要,redis只能进行垂直扩展(给予redis进程更多的CPU或者内存),不过垂直扩展的特点也很明显,很快就会变得非常昂贵并且变得无法管理。

我们只能通过把redis中的数据进行分区,将不同分区的数据存储到不同的redis实例中,很多公司针对redis已经实现了自己的分区方案,不过大部分方案都是在客户端进行分区,也就是,数据在哪个节点进行读写是客户端来决定的,这个方法的缺点也很明显,所有的客户端都要实现这个相同的分区策略,并且连接到同一个redis实例的不同的redis客户端将会紧紧的耦合在一起。

另一种分区策略对客户端进行了解耦,就是用代理进行协助分区,Twitter就采用了这种分区策略,在这种方式下,
proxy就像一个在客户端和redis实例之间的一个单独的节点负责处理数据分区,虽然这种方式给客户端解耦,但是又引入了proxy单点故障的问题,又需要考虑proxy的高可用问题,还有一个问题就是如果将来需要添加或者删除节点的时候,又会带来数据的重新分区,如果只是用redis做缓存的话,这个问题没什么大不了的,但是如果用来做数据存储,只是改变分区策略可能会引起数据的“丢失”(原来的key值可能会被分区到别的redis实例上面去了),并且这个新的redis实例上还没有数据,调整了分区策略后必须要进行数据迁移

高可用

当在一个大型企业级应用中依赖一个数据库的时候,是不允许数据库有停机时间的,现代的数据库必须要尽可能地有容灾和容错能力,并且不需要人为干预。尽管redis实例很少发生宕机,但是也有一些异常情况会导致redis实例不可用,由于没有好的redis高可用方案,一些公司开始自行研发自己的redis高可用方案,但是大部分用户都没有高可用方案,并且一些stackoverflow上面的问答都太专业化了,普通用户根本就被抛弃了,于是redis团队立刻研发了一套高可用方案redis sentinel,这是一个官方的高可用方案,但是引入了额外的复杂度,并且这种方案并不提供主节点的水平扩展,在redis实例外还需要3台额外的机器(存放哨兵节点)。

为了解决上述问题,redis 3.0.0发布了redis cluster ,redis cluster是一个多主多从并且去中心的架构,redis cluster简而言之就是一个数据分片策略,它可以在集群运行的时候将数据重新分片到其他节点上面,并且提供了故障切换的方法可以确保系统可以应对各种各样的故障。redis cluster使用hash分区将key分配到16,384个hash槽内,集群的每一个主节点负责一部分hash槽,集群中的每一个从节点负责复制指定主节点的数据,并且可以重新分配给别的主节点或者是被选举为新的主节点,主节点接收来自客户端的读和写请求,从节点不与客户端做任何交互,只做主节点的数据备份。


图片引用自Redis Cluster集群使用与原理

参考文章

Redis哨兵(Sentinel)模式快速入门
Redis 知识汇总
An Introduction to Redis Cluster

关于Raft协议,这个网站上面有动画演示,非常容易理解,可以去了解下

聊聊中间件之redis

发表于 2020-04-16 | 分类于 redis

前言

不知不觉4月份已经过去大半了,转眼间自己已经失业半个月了,感觉还有好多东西没有复习,不管怎么样,不能让自己闲下来,今天就来整理一下redis相关的内容

about redis

1
Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。  --- wikipedia

redis是一款基于内存的数据库,即NOSQL,采用单线程模型,能支持10W的并发
常用的数据结构 String List Set ZSet Hash
持久化方式:RDB(快照),AOF(日志追加)

redis为什么高性能?

  • 单线程模型避免线程上下文切换问题
  • 纯内存操作
  • 核心基于非阻塞的IO多路复用

redis中的数据结构

string字符串 Simple Dynamic String(动态字符串)

String是redis里面最简单的key-value类型了,它的value不仅可以是String还可以是数字,它也是Memcached中的唯一的数据类型,redis String底层使用SDS(Simple Dynamic String)实现,常见的用法就是缓存、计数等功能,常用指令 set get decr(-1) incr(+1) mget(获取一个或多个给定key的值)

list 列表

list就是列表,在redis底层是用链表实现的,通过列表可以轻松实现最新消息列表等与插入序有关的业务。
常用指令 lpush(链表头插入一个元素),lpop(链表头删除一个元素),rpush,rpop,lrange(获取指定范围内的元素)

set 集合

set就是一个集合,一堆不重复值得集合,利用redis提供的set可以存储一些集合性的数据,比如微博中一个人关注的人,粉丝等等,redis还对set提供了一系列的函数可以实现对集合的并、交、差,这就可以非常方便的实现共同好友、共同关注、二度好友等功能

常用指令 sadd(向一个集合中添加一个或多个元素) spop(移除一个随机元素) srem(移除一个或多个指定元素) smembers(获取集合所有成员) sunion(求并集)

zset有序集合 内部实现为skip list跳跃表

zset(又称sorted set),有序集合,比set增加了一个score(权重)的概念,使得集合中的元素可以按照score进行排序,这一特性特别适合做排行榜

hash 字典

hash几乎可以等价于java中的hashmap,底层采用数组+链表的结构,常用于存储对象(因为)
常用指令 hget,hset,hgetall

redis常见的使用场景

  • 缓存
  • 单线程模型可做分布式锁
  • 排行榜 有序集合可以实现
  • 计数器 商品浏览量 视频播放数 redis 提供的incr 内存操作 性能非常好
  • 分布式会话
  • 社交网络 共同关注/共同好友 redis提供的hash和集合(交并差)

redis中的内存淘汰策略

redis中数据的存取是纯内存操作的,内存是很昂贵的资源,而redis在64位操作系统时是不做内存容量限制的,那么当数据量很大的时候,可想而知,如果不对redis中的数据进行淘汰,很有可能导致操作系统因为内存耗尽而进入假死状态,一般而言 我们会用两种机制(过期策略+内存淘汰机制)配合从而对一些不需要的数据进行删除用来节约空间。

过期策略

通常情况下,我们设置一个key的时候,可以对这个key的过期时间进行设置,当key过期后,redis通过

定时删除

定时删除是指每隔一段时间,随机抽取一些设置了过期时间的key,检测是否过期,过期了就将其删掉

key可能很多,扫描所有的key值可能会很消耗性能,这样的话有一些key可能会删不掉,这时候就需要惰性删除的配合进行删除。

惰性删除

当redis中的key过期后,redis并不会主动去删除它,而是等到客户端访问某个key的时候,判断key是否过期,如果没过期则直接返回,如果过期,redis会将这个key删除并且不会返回给客户端,这样的话可以解决一些已经过期了,但没有被定期删除扫到的key,但是又引入了另一个问题,已经过期的key并且永远不会访问到,这样既不会被定时删除所处理,也不会被惰性删除所处理,这时就要引入内存淘汰的机制了

内存淘汰机制

redis在使用内存超过某个阈值(通过配置文件进行配置 maxmemory)的时候,会触发内存淘汰机制,选取一些key进行删除

1
2
maxmemory
maxmemory-policy

配置文件这里的maxmemory-policy 就是可以配置的策略,其中可配置的策略有以下几种

  • noeviction 默认策略 不淘汰,内存不足时候,新的写入会报错
  • allkeys-lru 当内存不足的时候,在所有的键空间中移除最近最少使用的key
  • allkeys-random 在所有键中,随机移除key
  • volatile-lru 在设置了过期时间的键空间中,找最近最少使用的key进行删除
  • volatile-random 在设置了过期时间的键空间中,随机移除key
  • volatile-ttl 在设置了过期时间的键空间中,有更早过期时间的key将会被删除

一般来说 如果只是用来缓存数据的话,用allkeys-lru就可以了,如果既需要缓存又要保证数据安全性进行持久化的话,出于性能考虑推荐用volatile-lru

redis 分布式锁

单节点分布式锁

单节点的分布式锁(只有一个master节点)往往是不可靠的,虽然实现起来很简单,但是如果主节点宕机或者没有来得及同步数据,给从节点导致从节点选举为master时候锁丢失,这种方案很明显是不可靠的

setnx + del

执行业务前通过 setnx 获取锁,执行完业务删除锁,表面上看起来没什么问题,但是如果业务的执行出现了异常,再或者服务宕机没有来得及删除该锁,导致这个业务再次执行的时候再也获取不到这个锁,从而导致死锁,这样是行不通的

setnx + expire

业务先通过set nx获取锁,然后再通过expire命令为锁设置过期时间,这种情况实际上跟先加锁后删除的效果是一样的,如果业务执行出现了异常,expire没有被执行,业务还是无法获取这个锁,出现这样的问题的根本原因实际上就是因为 setnx + del || expire是两条指令,并非一个原子性操作,两个指令如果能同时执行就可以避免这样的问题

lua脚本 setnx + expire

setnx + expire 本身不是原子性的,但是redis提供了单线程模型执行lua脚本,保证脚本在运行过程中不会被任意其他请求打断,通过lua脚本执行相同的命令即可保证这两条命令的原子性

set nx ex

redis 2.6.12开始,set命令开始支持EX、NX、PX、XX等选项,可以完全替代setnx等操作,而且还是原子性的,可以执行通过set实现单节点的分布式锁。

多节点redis分布式锁

RedLock(真正意义上的分布式锁)

假设当前有5个独立的redis master节点

  • 获取当前unix时间戳 以毫秒为单位
  • 依次从5个实例尝试,使用相同的key和value获取锁,客户端在请求锁的时候,应该设置一个超时时间,防止在请求锁的时候避免由于服务端已经挂掉的时候,客户端还在死等响应结果。如果服务端没有在规定的超时时间内响应,客户端应该尽快从另外一个redis实例中获取锁
  • 客户端使用当前时间减去开始获取锁的时间,就得到锁的使用时间,并且当且仅当从大多数redis节点都获取到锁的时候,并且锁使用的时间小于锁失败时间时,锁才获取成功
  • 如果获取到了锁,锁的真正有效时间等于有效时间减去第三步获取锁所使用的时间
  • 如果获取锁失败,客户端应该在所有的redis实例上进行解锁,防止某些节点获取到锁,但是客户端没有得到响应,从而导致接下来的一端时间里客户端不能重新获取锁

但是这种分布式锁真的可靠吗? 业务时间超过了分布式锁的可用时间,这个锁还有意义吗?

谈谈mysql中的锁

发表于 2020-04-14 | 分类于 mysql

前言

今天又来写博客了,没错今天又被面试官锤了,这次被锤的问题是关于mysql中关于锁的问题,特此整理下有关锁的问题,避免今后对这种问题再次犯同样的错误(꒦_꒦)

什么是锁?为什么要加锁

1
2
3
4
5
锁是计算机协调多个进程或纯线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU,RAM,I/O)  
的争用外,数据也是一种供许多用户共享的资源。如果保证数据并发访问的一致性、有效性是所有数据库必须解决
的一个问题,锁冲突也是影响数据库并发访问性能的一个因素,
从这个角度来看,锁对数据库而言显得尤为重要,也更加复杂。
锁机制用于管理对共享资源的并发访问

锁分类


图片引用自参考文章MySQL InnoDB锁机制全面解析分享

按照加锁机制进行分类

  • 乐观锁 乐观锁乐观地假定大概率不会发生更新冲突,访问、处理数据的时候不加锁,只在更新数据的时候查看版本号是否有冲突,有则处理、无则提交事务
  • 悲观锁 悲观锁悲观地假定大概率会发生更新冲突,访问或者处理数据前加排他锁,在整个数据处理过程中锁定数据,事务提交或者回滚后才释放锁

按照兼容性进行分类

  • 共享锁 也就是读锁,读操作之间互相不冲突,但是读操作跟写操作互相冲突,也就是会阻塞写请求
  • 排他锁 也就是写锁, 写操作之间互相冲突,写操作跟读操作互相冲突

按照锁的粒度进行分类

  • 表级锁 mysql中锁定粒度最大的一种锁,对整张表进行加锁,实现简单,资源消耗较少,加锁快,不会出现死锁。由于锁定粒度最大,触发锁冲突的概率最高,并发度最低,myisam和innodb引擎都支持表级锁
  • 页级锁 开销和加锁时间介于表锁和行锁之间,会出现死锁,并发程度一般
  • 行级锁 开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发程度也最高

按照锁模式进行分类

  • record lock 记录锁,对符合条件的行进行加锁,锁住索引项
  • gap lock 间隙锁,对符合条件的记录行之间的间隙进行加锁,不包含符合条件的索引项本身,只是锁定记录的范围,其他事务不能在锁范围内插入数据,这样就防止了别的数据新增幻影行(幻读)
  • next-key lock 锁定索引项本身和索引范围,即record lock + gap lock的结合 可解决幻读问题
  • 意向锁 为了允许表锁和行锁共存,实现多粒度锁机制,innodb还有两种内部使用的意向锁(表级锁)分为
    • 意向共享锁(IS) 事务打算给数据行加共享锁,必须先取得该表的IS锁
    • 意向排他锁(IX) 事务打算给数据行加排他锁,必须先取得该表的IX锁
      意向锁是在给某一行进行加锁的时候,mysql会自动为这一行所处的表进行加意向锁,无需用户任何处理,意向锁的作用我们可以看下面一个例子
时间 事务1 事务2
t1 更新table a中某一行数据 do nothing
t2 do nothing 更新table a全表数据,需要获取表锁
t3 提交 —
t4 — 提交

在上述例子中,事务1中在t1时刻对表中数据进行数据更新时,自动加该表的意向排他锁,这时事务2获取该表的写锁时会先判断是否和意向锁兼容,发现该表存在意向排他锁,锁类型不兼容,需要等待事务1提交后才能进行操作。
这样如果某张表存在行级锁,获取表锁的时候就不必遍历整张表判断是否存在行级锁,直接判断是否与表级的意向锁是否兼容就可以了,下面给出表级的意向锁和读写锁的兼容情况

—- IS IX S X
IS 兼容 兼容 兼容 冲突
IX 兼容 兼容 冲突 冲突
S 兼容 冲突 兼容 冲突
X 冲突 冲突 冲突 冲突
  • 插入意向锁 插入意向锁是gap锁的一种,这种锁会在记录插入前设置。这种锁表示了多个事务在插入到相同的索引间隙的时候,只要他们不是插入到相同的位置上就不必彼此互相等待。在获取插入的排它锁之前,需要先获取插入意向锁

insert-intention-locks

谈到这里其实可以聊聊今天被锤的一个面试问题,有关SQL优化相关的问题

面试中被面试官锤的问题

表级锁了解吗?什么时候会触发表级锁?update语句什么情况下会触发表级锁?

现在mysql默认存储引擎都是innodb,那么innodb存储引擎下什么情况会进行锁表呢?这就要从mysql的索引讲起了 innodb中默认使用行级锁,这个行级锁锁的是索引记录,换句话说,如果你的SQL语句中如果没有索引,这时innodb会锁住谁呢?毫无疑问这个时候没有索引就会触发了表级锁,实际使用中我们要特别注意innodb行级锁的这个特性,不然会引起大量的锁冲突,从而影响并发性能, update语句中什么情况会触发表级锁可以看下面哪些情况会锁表

都有哪些情况innoDB会锁表呢?

其实不管是读操作(select)还是写操作(update,delete,insert),只要涉及到带有筛选条件的语句,如果筛选条件中没有用到索引,就会触发全表扫描,区别是读操作可能加读锁(也有可能不加锁,mvcc中的快照读是通过版本号实现的,不加读锁,当前读需要加读锁),写操作默认需要对影响的数据集隐式加写锁,那么如果发现影响的数据集没有用到索引或者是索引效果不好(区分度不够高,导致需要扫描表中大部分数据)再或者全表扫描的时候,就会锁住整张表,导致默认的行级锁升级为表级锁,因此我们总结下以下情况会导致锁表

  • 全表更新 事务需要更新大部分数据或全部数据,如果使用行级锁,会导致事务执行效率低,从而导致其他事务长时间等待锁和更多的锁冲突
  • 多表级联 事务涉及多张表,比较复杂的关联查询,很可能造成死锁,这种情况若能一次性锁住事务涉及的表,从而避免死锁,减少数据库事务回滚所带来的开销
  • 本应部分更新,但是因为筛选条件中未用到索引或者索引区分度程度不高(innodb认为全表扫描比走索引效率更高导致索引失效的情况),导致全表扫描,这个时候就要通过explain去查看下查询计划,看下查询语句是否真的用到了索引

锁优化部分

  • 尽量让数据检索都通过索引来完成,避免无索引或者索引失效导致行级锁升级为表级锁
  • 合理设计索引,以缩小加锁范围,避免间隙锁造成不该锁定的键值被锁定
  • 尽量控制事务的大小,因为行级锁的复杂性会加大资源使用量以及锁定时间

这里面实际上第三种情况是可以避免的,在做业务时,需要谨慎的加索引,在合适的列上创建索引,索引列区分程度是否高(主键索引和唯一索引不用说区分度百分百,如果能用到主键索引或者唯一索引就尽可能的使用这两种索引,如果不能使用,确保索引列区分程度够高)

参考文章

MySQL InnoDB锁机制全面解析分享
MySQL锁总结
mysql官方文档#insert-intention-locks

消息队列之RabbitMQ

发表于 2020-04-13 | 分类于 rabbitmq

前言

今天继续整理下跟RabbitMQ有关的内容,加深一下理解,还是避免被面试官锤。

什么是消息队列,为什么要用消息队列

这里的内容在上一篇(谈谈消息队列)内容中已经讲过了,这里就不再赘述了

消息队列之RabbitMQ

RabbitMQ是一款消息队列软件,也被成为消息代理(Message Broker)或队列管理器(Queue manager),他是一个由ErLang语言开发的AMQP的开源实现

AMQP(Adavanced message queue),高级消息队列协议,它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可以传递消息,并且不受产品、开发语言等条件的限制。

这里引用了RabbitMQ官网的图片,AMQP的消息模型

消息代理(messaging broker)从publisher(消息生产者)接收消息,并且将消息路由给消费者,由于AMQP是个网络协议,生产者、消息代理和消费者可以位于不同的机器上

RabbitMQ中的基本概念

  • Exchange 从生产者接收消息,并且根据exchange的类型将消息推送到对应的队列中
  • Binding queue和exchange的绑定,通过这个绑定rabbitmq可以知道如何正确地将消息路由到指定的queue上了
  • Routing key 生产者将消息发送给exchange的时候,rabbitmq 需要指定一个routing key来确定这个消息的路由规则,这个路由规则将决定消息如何路由到queue中
  • Users
  • Vhost,virtual host 是一种可以在同一个rabbitMQ实例中隔离多个不同的应用,不同的用户可以拥有不同virtual host的权限,每一个virtual host都可以拥有不同的queues和exchanges
  • Producer 应用中消息的生产者
  • Consumer 应用中消息的消费者
  • Queue 一个存储消息的buffer
  • Message 生产者通过rabbitMQ投递给消费者的消息
  • Connection 客户端应用程序和RabbitMQ之间的TCP连接,和生产者与RabbitMQ之间都是通过TCP连接建立的。
  • Channel 在Connection中的一个虚拟连接,队列中的消息的发布和消费

由Exchange、Queue、RoutingKey三个才能决定一个从Exchange到Queue的唯一的线路

exchange和exchange类型

Exchanges在AMQP模型中是消息被发送到的实体。Exchanges将消息路由到0个或更多个queue上面去,路由算法取决于exchange的类型和绑定规则,AMQP提供以下exchange类型

类型 默认预定义的名字
Direct Exchange 空串或者 amqp.direct
Fanout Exchange amq.fanout
Topic Exchange amq.topic
Headers Exchange amq.match

此外 AMQP中还定义了几个很重要的属性

  • name
  • durability(broker重启后exchange是否还存在)
  • Auto-delete(当exchange上面的最后一个queue解绑后是否自动删除)
  • Arguments(可选参数,插件或者broker的特定功能会用到)

下面的内容全部引用自rabbitmq官网,提供了AMQP协议的概述

Default Exchanges

默认的exchange是一个broker预定义的没有名字的direct exchange,它有一个特殊的性质:所有新建的queue都会绑定到默认的exchange上面,并且routing-key和queue名相同

Direct Exchange

direct exchange根据消息所带的routing-key将消息投递给对应的队列。direct exchange倾向于用作消息的单播路由(unicast routing),尽管它也可以用作多播路由(multicast routing),工作原理:

  • 一个queue通过一个routing-key K绑定到一个exchange上
  • 当一条routing-key为R的消息到达direct-exchange时,direct exchange会把消息投递给routing-key跟R相等的queue上面去

direct exchange通常用于循环分配任务给多个工作者,需要注意的是,消息的负载均衡是发生在consumer之间的,并不是发生在queue之间,举个例子(一个direct exchange 通过一个routing-key绑定了多个queue,消息会发到多个queue中,一个queue中消息的消费并不影响另一个queue的消费,也就是不会发生抢消息的行为)


图片引用自rabbitmq官网

Fanout Exchange 扇形交换机

扇形交换机会把他收到的消息发送到所有绑定到它的queue中,并且这里的routing-key是被忽略的。如果有n个queue绑定到了扇形交换机上面,当一条新消息发布到交换机中时,消息会复制n份投递到所有的n个queue中,扇形交换机特别适合做广播消息

图片引用自rabbitmq官网

因为扇形交换机会把消息广播到所有的queue中,他的使用场景非常相似:

  • 很多大型多人在线游戏会用它处理排行榜更新或其他全局事件
  • 体育新闻网站可以准实时地将比分发送给移动客户端
  • 分布式系统可以将不同的状态和配置更新进行广播
  • 可以通过扇形交换机来实现分发消息给群聊中 的用户

topic exchange

topic exchange通过routing-key通配的方式将消息路由到一个或多个队列上面,正因如此,topic exchange一般用于实现各种发布/订阅的变种。topic exchange也常用来做消息的广播,topic exchange有很广泛的用途,当涉及到多个消费者可以选择性的接收它们所关注类型的消息的时候,就可以考虑topic exchange的使用了

使用场景

  • 将数据按照地理位置的相关性做分发,比如销售点
  • 多个执行者处理一个后台任务,每一个执行者处理特定的一块任务
  • 股票价格更新(或者其他金融数据的更新)
  • 新闻更新(包括分类、标签的更新)
  • 云上不同类型服务的服务编排
  • 分布式(架构/系统)软件构建或者打包时,每个builder只能处理一种架构或者系统

spring rabbitmq启动流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
通过SpringFactoriesLoader机制,扫描META-INF/Spring.factories
发现需要加载org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
加载RabbitAutoConfiguration后发现需要按条件进行装配,RabbitTemplate类加载后,则进行自动装配
加载配置文件RabbitProperties(application.yml或application.properties等配置文件中spring.rabbitmq.下的内容),装配CachingConnectionFactory

在Spring容器初始化完毕,并初始化对应listener bean时

RabbitListenerAnnotationBeanPostProcessor#postProcessAfterInitialization()
//
processAmqpListener()
processListener()
RabbitListenerEndpointRegistrar#registerEndpoint()
//用AmqpListenerEndpointDescriptor包装endPoint和containerfactory
//如果是立刻启动,则调用
RabbitListenerEndpointRegistry#registerListenerContainer()

通过RabbitListenerAnnotationBeanPostProcessor注册被rabbitListener注解的方法


未完待续

参考文章

for RabbitMQ beginners- what is RabbitMQ?

谈谈数据库中的索引

发表于 2020-04-10 | 分类于 mysql

前言

今天来聊聊数据库当中的索引,想整理一下关于索引的基础内容,不仅是因为我最近比较闲,而且因为我今天面试中聊到索引的内容,感觉自己回答的特别水,特此整理一下相关的内容加深一下记忆,以便于下回面试的时候不至于被面试官锤(这么简单的内容你特么都答不出来,辣鸡),本文要讲的索引全部是在mysql数据库中的,因此后续再说索引,说的就是mysql数据库中的索引,不再赘述。


索引的定义

在开始看mysql中的索引之前,我们先了解下在DMBS系统中索引的定义是怎样的。

1
2
数据库索引,是数据库管理系统的一个排序的数据结构,
以协助快速查询、更新表中数据. ------ 维基百科

索引的分类

那么索引有哪些类别呢?这里从几个不同的角度来分析有哪些类别的索引

物理存储角度

  • 聚集索引(又称聚簇索引)
  • 非聚集索引(又称非聚簇索引)

聚集索引的特点是,数据库中表数据的物理顺序和索引顺序相同,而非聚集索引在数据库中的索引的逻辑顺序和表中数据的物理顺序并不相同。所以说聚集索引和非聚集索引是从物理存储角度来划分的,此外,因为数据表的结构只能有一个,所以一张表的聚集索引只能有一个。

逻辑角度:

  1. 主键索引 一种特殊的唯一索引,不允许有空值
  2. 普通索引或者单列索引 最基本的索引,没有任何限制
  3. 联合索引或者多列索引 建立在多个列上的索引,被称为联合索引,遵循最左前缀原则
  4. 唯一索引 与普通索引类似,不同的是唯一索引要求索引的值必须唯一,但允许有空值

在这里顺便介绍下 上述的索引的创建和删除,水一下

  • 普通索引

创建

1
ALTER TABLE `table_name` ADD INDEX `index_name`(`column_name`);

删除

1
ALTER TABLE `table_name` DROP INDEX `index_name`;
  • 联合索引

创建

1
ALTER TABLE `table_name` ADD INDEX `index_name`(`column_name_1`,`column_name_2`);

删除

1
ALTER TABLE `table_name` DROP INDEX `index_name`;
  • 主键索引

创建

1
2
#可以在建表时指定 
ALTER TABLE `table_name` add PRIMARY KEY(id);

删除(如果主键索引是自增列,那么删除主键将不满足自增列必须是索引列的定义,所以需要额外做一些手脚才能删除)

1
ALTER TABLE `table_name` DROP PRIMARY KEY;
  • 唯一索引

创建

1
ALTER TABLE `table_name` ADD UNIQUE INDEX `index_name`(`column_name`);

删除

1
ALTER TABLE `table_name` DROP INDEX `index_name`;

数据结构角度

  1. B-Tree索引,在mysql中B-Tree索引实际上是由B+树结构实现的,更适合于范围查询
  2. hash 索引 等值比较 效率非常高,通常用于精确内容查找(etc: in(),=, <=>,这里的“<=>”等价于=),但是mysql的InnoDB存储引擎并不支持hash索引
  3. full-text索引(全文索引)

索引的优点

  • 索引大大减少了服务器需要扫描的数据量
  • 索引可以帮助服务器避免排序和临时表
  • 索引可以将随机I/O变为顺序I/O

摘自《高性能mysql》第5.2节

索引的缺点

  • 创建索引和维护索引需要花费时间,并且随着数据量的增加所耗费的时间也会增加
  • 虽然索引大大提高了查询速度,但是同时会降低更新表(insert,update,delete)的速度,mysql不仅要保存数据,还要保存索引文件
  • 建立索引会建立占用磁盘空间的索引文件,一般情况下这种问题不大,但是如果一张大表上建立多种组合索引,索引文件会膨胀的很快。
  • 如果某个列数据上包含许多重复的内容,那么在这列上建立索引就没有太大的效果(索引列区分程度不高,计算公式select distinct(col) from table/select count(*) from table)
  • 非常小的表,大部分情况下简单的全表扫描更高效

索引为什么能加速数据查询?

在数据库系统中,索引就像书中的目录,想象一下,看书的时候没有目录,那我们想要找到我们想要看到的精彩内容就需要一页一页的找,同样地,在没有索引的情况下,在数据库中查询数据是需要进行全表扫描的,如果一张表只有几十条数据,这个时候不加索引是完全没有问题的,但是如果数据量很大呢?我们都知道数据库中的数据是存在磁盘上的,取数据的时候是把需要的数据块读取到内存中,然而内存是有限的,并不能把所有的数据一次性全部取出,所以我们拿到想要的数据可能会发生很多次磁盘IO(磁盘IO是很重的操作,速率远小于从内存中直接操作,所以提升效率的关键是尽量减少磁盘IO次数),如果是全表扫描可能发生很多次磁盘IO所以会很慢,以mysql为例,mysql的innodb引擎是以B+树作为索引树,1个3层的B+数大概可以存储2000W数据,也就是说通常情况下,我们对表中数据(不超过3层B+树最大容量情况)的查询一般需要3次磁盘IO就能拿到磁盘数据了,如果全表扫描这种效率是不堪设想的,而为什么索引不用二叉树(有序时退化为单链表,退化为线性查找)、平衡二叉树(一个节点可存储的子节点数量太少,数据量较大时候,树的平均高度过高)、B树(非叶子节点既存数据元素,又存储指针,可存放的数据量更少)可以自行了解一下,这里不过多解释了,这也就是为什么索引能加速数据查询

索引原理?

未完待续

参考文章

MySQL有哪些索引类型 ?

谈谈数据库事务

发表于 2019-12-03 | 分类于 mysql

什么是事务

事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

概述

当事务被提交给了DBMS(数据库管理系统),则DBMS需要保证该事务的所有操作成功完成,并且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则数据库中的所有操作都需要回滚,回到事务之前的状态,同时该事务对数据库其他事务的执行无影响,所有事务好像都在独立的运行。

事务的ACID特性

Atomicity 原子性

1
事务作为一个整体被执行,要么全部执行,要么全部不执行(由undo-log保证,事务某处发生错误的时候通过undo-log进行回滚,保证了原子性)

Consistency 一致性()

1
事务应确保数据库的状态从一个一致状态转变为另一个一致状态,一致状态的含义是数据库内的数据应满足完整性约束.

Isolation 隔离性

1
多个事务并发执行时,一个事务的执行应该不影响其他事务的执行。(由MVCC||加锁保证)

Durability 持久性

1
已经提交的事务对数据库的修改应该永久保存在数据库当中。(由redo-log保证,数据库保存数据的时候会先写入)

事务的隔离级别

READ-UNCOMMITED(未提交读)

该隔离级别下的事务会读到其他未提交的事务的数据,这种现象也被称之为脏读

READ-COMMITED(提交读)

一个事务可以读取另一个已经提交的事务,多次读取会产生不一样的结果,此现象被称为不可重复读

REPEATABLE-READ(可重复读)

同一个事务内,同样的查询语句多次执行所获取的结果是一致的,但是可能会出现幻读现象

SERIALIZABLE(序列化)

该隔离级别下,所有事务都是串行执行的。此级别是最高隔离级别,同时性能也是最差的

事务的并发带来的问题

脏读

当一个事务正在访问数据,并且对数据进行了修改,但是该事务还未提交,这时另一个事务访问到了该事务中未提交的数据,产生了脏读

时间 事务1 事务2
t1 开启事务 开启事务
t2 — update table set property = ‘newValue’ where id = 1;
t3 select * from table where id = 1 —
t4 commit commit

假设事务2未更新前是’oldValue’,事务2更改后修改还没有提交,事务1读到了事务2未提交的’newValue’,这时,事务2进行了回滚,导致事务1拿到的数据变为无效数据,因为事务2对该数据的修改还是未提交的,那么事务1此时读到的数据就是脏数据,对脏数据进行的操作可能是不正确的。

幻读

多次读取一定范围内的记录,发现结果不一致(一般指记录增多或记录减少,下述操作在mysql8.0.0版本未能出现,mysql innodb 引擎部分解决了幻读问题,可以尝试在其他数据库执行该操作)

时间 事务1 事务2
t1 开启事务 开启事务
t2 查询所有年龄为20的person,结果为20 —
t3 — 新增一个年龄为20的person并commit
t4 查询所有年龄为20的person,结果为21 —
t5 commit commit

假设事务2插入前,年龄为20的人有20条记录, 事务1第一次查询的时候发现有20个年龄为20的人,第二次查询却发现年龄为20的人多了一个,好像产生了幻觉

不可重复读

多次读取同一条记录,发现该记录列值被修改过

时间 事务1 事务2
t1 开启事务 开启事务
t2 查询张三的年龄,结果为20 —
t3 — 更新张三年龄为21
t4 查询张三的年龄,结果为21 —
t5 commit commit

假设张三原来的年龄为20,发现张三第一次查询的结果20和第二次查询的结果21不一致,这就是不可重复读

各事务隔离级别对应解决的问题

隔离级别 脏读 不可重复读 幻读
READ-UNCOMMITED √ √ √
READ-COMMITED × √ √
REPEATABLE-READ × × √
SERIALIZABLE × × ×

mysql的InnoDB存储引擎解决了RR模式下部分幻读的问题,但是并不能完全避免幻读,解释见下文快照读和当前读部分
github上面有关于这个问题的讨论


并发事务解决方案

锁机制

解决写-写冲突问题,效率很差

  • 悲观锁 依靠数据库提供的锁机制实现 当一个线程需要对共享资源进行操作的时候,首先对其进行加锁,当该线程持有该资源的时候,其他线程对该资源操作的时候就会阻塞

  • 乐观锁 基于版本号实现
    当一个线程需要操作共享资源的时候,不进行加锁,而是在操作之后,通过一个版本号进行控制,如果在该线程执行操作完成后通过版本号进行判断,如果发现已经被其他线程操作了,则通知该线程操作失败。

MVCC(多版本并发控制)

解决读写冲突问题,通过一种快照机制生成一个数据请求时间节点的数据快照,通过这个快照来进行数据的读取,这样,因为不需要加锁,读写操作都不会相互阻塞(仅作用于RR和RC)。

Mysql InnoDB 引擎在每一行数据都都加了几个隐藏列(参考自mysql官方文档)

1
2
3
4
5
6
7
Internally, InnoDB adds three fields to each row stored in the database. A 6-byte  DB_TRX_ID field indicates the transaction identifier for the last transaction that inserted or updated the row.  
Also, a deletion is treated internally as an
update where a special bit in the row is set to mark it as deleted.
Each row also contains a 7-byte DB_ROLL_PTR field called the roll pointer. The roll pointer points to an undo log record written to the rollback segment. If the row was updated, the
undo log record contains the information necessary to rebuild the content of the row before it was updated.
A 6-byte DB_ROW_ID field contains a row ID that increases monotonically as new rows are inserted.
If InnoDB generates a clustered index automatically, the index contains row ID values. Otherwise, the DB_ROW_ID column does not appear in any index.
  • DB_TRX_ID 6字节的事务id,代表最近一次插入或更新该行记录的事务id
    在inno db存储引擎下,delete操作被视为update操作,做法就是在记录行中有一个特殊的标记位标记该行是否被删除,而并非真删除

  • DB_ROLL_PTR:7字节回滚指针,每次对一行数据进行修改的时候,会生成一条undo log,保留数据修改前的状态,回滚指针即指向该undo log记录

  • DB_ROW_ID: 隐藏的自增id,当建表时候没有指定主键的时候,就会使用该rowId创建聚簇索引,否则聚簇索引中将不包含该DB_ROW_ID内容

read-view(又称快照snapshot)

事务的快照主要是用来存储数据库的事务运行情况,通常用来进行可见性判断。

  • 查看当前所有未提交的活跃事务,存储在数组中记作m_ids
    RR是在第一次快照读操作前生成readView,之后的查询操作复用该readView
    RC是在每一次快照读操作前都生成一个readView

可见性比较算法

  • 如果 DB_TRX_ID < m_ids中最小事务id,则代表被访问的数据版本在生成快照之前就已经提交,当前事务可以获得该条记录的稳定版本

  • 如果 DB_TRX_ID < m_ids中最大事务id,则代表生成该事务的版本在生成快照之后,所以该版本对当前事务是不可见的

  • 如果DB_TRX_ID在最大事务id和最小事务id之间,则需要判断下DB_TRX_ID是否在m_ids当中,如果存在,则说明事务还是活跃的,该版本数据不可访问,如果不存在,说明该事务在生成快照之后已经提交,该数据版本可以被该事务访问

当前读和快照读

  • 快照读 普通的select操作(不包含 select … for update, select … lock in share mode)
  • 当前读
    • select … in share mode
    • select … for update
    • insert
    • update
    • delete
时间 事务1 事务2
t1 开启事务 开启事务
t2 查询年龄为20的人,结果为20 —
t3 — 新增一个人李四,id为31
t4 — commit
t5 更新李四的年龄为31,可以更新成功,此时再次查询年龄为20的人,结果为21,出现幻读 —
t6 commit —

inno db引擎只能部分避免幻读,这个部分指的是只能避免当前读的幻读,而对于上表中快照读的情况却不能避免

版本链

每次对记录进行改动,都会记录一条undo-log,每条undo-log上面也会有一个roll-ptr属性,指向历史版本的undo-log,可以将这些undo-log串联起来,形成一个版本链,如下图(图片引用自参考文章中《MySQL事务隔离级别和MVCC》
)

MVCC总结

所谓的MVCC,就是指RR,RC这两个事务隔离级别的事务在执行快照读操作时访问版本链的过程,使得不同事务的读-读,读-写操作并发执行,从而提升系统性能

参考文章

MySQL-InnoDB-MVCC多版本并发控制

深入学习MySQL事务:ACID特性的实现原理

mysql官方文档

MySQL事务隔离级别和MVCC

谈谈消息队列

发表于 2019-08-11 | 分类于 消息队列

什么是消息队列?

1
在计算机科学中,消息队列是一种进程间通信或同一进程不同线程间的通信方式,软件的贮列同来处理一系列的输入,通常是来自用户。消息队列提供了异步的通信方式,每一个贮列中的详细记录包含详细说明的数据,包括发生的时间,输入设备的种类以及特定的输入参数,也就是说消息的发送者和接收者不需要同时与消息队列交互。消息会保存在队列中,直到接收者取回它。   ——————摘自wikipedia

为什么要引入消息队列?能解决什么问题?

消息队列是很重要的分布式中间件,能够应用于流量削峰填谷、应用间解耦、异步处理、消息通讯场景.

流量削峰

很多电商网站都会搞整点秒杀的优惠活动,如果按照传统实现方案,通过传统方式进行秒杀下单,所有的查询创建在秒杀活动开始的时候一瞬间全都打到db server上面去,流量暴增,数据库立马就跪了,服务全跪。
显然传统方式是无法应对这种瞬时激增的流量的,那么这时候就要引入流量削峰的方案了,引入消息队列,所有的下单信息发送到消息队列中,并给定一个当前系统能承受的最大请求数量,当超过这个最大数量,多余的流量被丢弃,将被削掉流量的用户引导至错误页面,削峰完成,虽然丢失了一部分流量,但是保证了流量超出系统承受能力的时候,系统不至于崩溃.

应用间解耦

案例:系统A提供了一些数据,此时系统B对这些数据比较关注,此时的技术方案是A系统直接将数据通过接口调用发送给系统B,这时问题解决了,A系统成功为B系统提供了数据。


存在的问题:

系统A和系统B都存在宕机的可能性,服务的高可用性没有办法保证,通过接口调用的方式存在失败的可能性.

如果这时又来了系统C,D,E,这时按照刚才的方案,系统A又需要调用系统C,D,E,这时候系统A的开发人员肯定要疯了,同样的逻辑要写3,4次.

解决方案:
这时候如果使用消息队列,系统A的开发人员就解脱了,系统A只需要应用消息队列中的发布订阅模式,将数据简单地丢到消息队列中,对此数据关心的系统直接从消息队列中取出这份数据即可.

异步处理

案例:假设现在有一个简单的登录场景,假设进行登录的时候需要执行3个操作,调用user服务的查询用户接口A,记录访问日志存库操作B,发送推送消息操作C,这三个操作依次耗时20ms,30ms,50ms
如果同步处理的话那么在后端服务内的耗时至少是三个操作的总和,返给前端的时候还要计算上TCP连接建立的时间,实际上这个操作中,有一些操作是对于前端交互是毫无关系的,比如操作B和操作C,用户点击登录时
用户所关心的是立即登录,并不关心记录日志,发送推送消息这些操作,这些操作完全可以异步处理,非核心的业务逻辑或不要求立刻返回结果的任务完全可以异步执行,而这时就可以使用消息队列来完成这个操作。

消息通讯

消息队列还可以应用于线程间通信或者进程间通信,或者应用于纯消息通讯,比如点对点的聊天,或者聊天室这种发布订阅模式。

test comment

发表于 2019-08-04 | 分类于 其他

用来测试评论的test post

hello github pages

发表于 2019-08-04

hello,github pages~ 由于heroku的app挪作他用了,偶尔用用github pages也不错。

123
mr_cris

mr_cris

30 日志
5 分类
20 标签
© 2022 mr_cris
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
访问人数 总访问量 次