消息队列
消息队列的作用
- 把数据放到消息队列叫做生产者
- 从消息队列里边取数据叫做消费者
主要作用有两个:
- 1、解耦: 添加消息队列,让消费者需要数据时,不再强依赖于生产者。
- 2、异步: 消费者直接发送请求然后进行排队即可,不必阻塞等待.
队列的两种模式
- 点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除)
- 发布/订阅模式(一对多,消费者消费数据之后不会清除消息)
消息生产者(发布)将消息发布到 topic 中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到 topic 的消息会被所有订阅者消费。
削峰和限流
我们再来一个场景,现在我们每个月要搞一次大促,大促期间的并发可能会很高的,比如每秒3000个请求。假设我们现在有两台机器处理请求,并且每台机器只能每次处理1000个请求。
那多出来的 1000 个请求,可能就把我们整个系统给搞崩了…所以,有一种办法,我们可以写到消息队列中:
JDK中的队列没有考虑到的问题
高可用
- 无论是我们使用消息队列来做解耦、异步还是削峰,消息队列肯定不能是单机的。
- 试着想一下,如果是单机的消息队列,万一这台机器挂了,那我们整个系统几乎就是不可用了。
- 所以,当我们项目中使用消息队列,都是得集群/分布式的。要做集群/分布式就必然希望该消息队列能够提供现成的支持,而不是自己写代码手动去实现。
数据丢失问题
我们将数据写到消息队列上,系统 B 和 C 还没来得及取消息队列的数据,就挂掉了。如果没有做任何的措施,我们的数据就丢了。
因此需要将数据进行持久化操作,进而减少数据的丢失。持久化可以存在:
- 磁盘
- 数据库
- Redis
- 分布式文件系统
- 同步存储还是异步存储
获得数据队列的方法
- push: 生产者将数据放到消息队列中,消息队列有数据了,主动叫消费者
- pull: 消费者不断去轮训消息队列,看看有没有新的数据,如果有就消费
Zookeeper
简介
- ZooKeeper主要服务于分布式系统,可以用ZooKeeper来做:统一配置管理、统一命名服务、分布式锁、集群管理。
- 使用分布式系统就无法避免对节点管理的问题 (需要实时感知节点的状态、对节点进行统一管理等等) ,而由于这些问题处理起来可能相对麻烦和提高了系统的复杂性,ZooKeeper作为一个能够通用解决这些问题的中间件就应运而生了
zookeeper的内部结构
ZooKeeper的数据结构,跟Unix文件系统非常类似,可以看做是一颗树,每个节点叫做 ZNode。每一个节点可以通过路径来标识,结构图如下:
ZooKeeper的节点我们称之为Znode,Znode分为两种类型:
- 持久节点: 默认的节点类型。创建节点的客户端与zookeeper断开连接后,该节点依旧存在 。
- 持久节点顺序节点: 所谓顺序节点,就是在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号:
- 临时节点: 和持久节点相反,当创建节点的客户端与zookeeper断开连接后,临时节点会被删除:
- 临时顺序节点: 结合和临时节点和顺序节点的特点:在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号;当创建节点的客户端与zookeeper断开连接后,临时节点会被删除。
利用 zookeeper 实现分布锁
获取锁
- 在 Zookeeper 当中创建一个持久节,当第一个客户端 Client1 想要获得锁时,需要在这个节点下面创建一个临时顺序节点。
- Client1查找持久节点下面所有的临时顺序节点并排序,判断自己所创建的节点是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。
- 如果再有一个客户端 Client2 前来获取锁,则在持久节点下面再创建一个临时顺序节点Lock2。
- Client2查找持久节点下面所有的临时顺序节点并排序,判断自己所创建的节点Lock2是不是顺序最靠前的一个,结果发现节点Lock2并不是最小的。于是,Client2向排序仅比它靠前的节点Lock1注册Watcher,用于监听Lock1节点是否存在。这意味着Client2抢锁失败,进入了等待状态。
- 如果又有一个客户端Client3前来获取锁,则在持久节点下载再创建一个临时顺序节点Lock3。
- Client3查找持久节点下面所有的临时顺序节点并排序,判断自己所创建的节点Lock3是不是顺序最靠前的一个,结果同样发现节点Lock3并不是最小的。于是,Client3向排序仅比它靠前的节点Lock2注册Watcher,用于监听Lock2节点是否存在。这意味着Client3同样抢锁失败,进入了等待状态。
释放锁
释放锁就比较简单了,因为前面创建的临时顺序节点,所以在出现下面两种情况时,都会自动释放锁:
- 任务完成后,Client 会释放锁。
- 任务没完成,Client 就崩溃了,也会自动释放锁。
监听器 watcher
常见的监听场景有以下两项:
-
监听Znode节点的数据变化
-
监听子节点的增减变化
zookeeper 实现统一配置管理
比如我们现在有三个系统 A、B、C,他们有三份配置,分别是ASystem.yml
、BSystem.yml
、CSystem.yml
,然后,这三份配置又非常类似,很多的配置项几乎都一样。
- 此时,如果我们要改变其中一份配置项的信息,很可能其他两份都要改。并且,改变了配置项的信息很可能就要重启系统
于是,我们希望把ASystem.yml
、BSystem.yml
、CSystem.yml
相同的配置项抽取出来成一份公用的配置common.yml
,并且即便common.yml
改了,也不需要系统A、B、C重启
我们可以将common.yml这份配置放在ZooKeeper的Znode节点中,系统A、B、C监听着这个Znode节点有无变更,如果变更了,及时响应。
zookeeper 实现统一的命名服务
集群状态
以我们三个系统A、B、C为例,在 ZooKeeper 中创建临时节点:
- 只要系统 A挂了,那
/groupMember/A
这个节点就会删除,通过监听groupMember
下的子节点,系统B和C就能够感知到系统A已经挂了。 - 除了能够感知节点的上下线变化,ZooKeeper 还可以实现动态选举Master的功能。(如果集群是主从架构模式下)
- 原理也很简单,如果想要实现动态选举Master的功能,Znode节点的类型是带顺序号的临时节点(EPHEMERAL_SEQUENTIAL)就好了。
- Zookeeper会每次选举最小编号的作为Master,如果Master挂了,自然对应的Znode节点就会删除。然后让新的最小编号作为Master,这样就可以实现动态选举的功能了