使用ZooKeeper实现Redis订阅消息的排他式处理

来源:互联网 发布:小学生机器人编程 编辑:程序博客网 时间:2024/06/03 22:59

前面咱们讨论过Redis Sentinel环境下Key过期事件消息订阅的问题,在演示程序中,由KeyExpiresMessageListener监听Redis发布出来的Key过期消息。

在生产环境下,这样的处理机制通常需要由一组独立部署的KeyExpiresMessageListener来构成高可用,但是当订阅消息发布出来时,所有的KeyExpiresMessageListener都能接收到,这样就会造成业务逻辑的重复处理,甚至可能导致系统逻辑错误,这就需要对各KeyExpiresMessageListener的工作进行全局的排他式控制,利用ZooKeeper我们可以很好地解决这个问题。

ZooKeeper是近几年广为应用的分布式协调框架,它使用类似Paxos协议的ZAB协议(ZooKeeper Atomic Broadcast)完成分布式系统一致性保障。分布式应用程序可以基于ZooKeeper实现数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁、分布式队列等功能。

ZooKeeper提供以下分布式一致性特性:

  • 顺序一致性
从同一个客户端发起的事务请求,最终将会严格地按照其发起顺序被应用到ZooKeeper中。
  • 原子性
所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的。
  • 单一视图
无论客户端连接的是哪个ZooKeeper服务器,其看到的服务端数据模型都是一致的。
  • 可靠性
一旦服务端成功地应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会被一直被保留下来,除非有另一个事务又对其进行了变更。
  • 实时性
ZooKeeper保证在一定的时间段内,客户端最终一定能够从服务端上读取到最新的数据状态。

下面我们来部署由三个节点192.168.112.81、192.168.112.82、192.168.112.83组成的ZooKeeper集群,部署版本是3.4.8。
首先分别在三台服务器上解压zookeeper-3.4.8.tar.gz并设置好环境变量ZOOKEEPER_HOME,然后把conf/zoo_sample.cfg复制一份为zoo.cfg,这是ZooKeeper默认读取的配置文件。在安装目录下新建data子目录,并在三个节点上分别用echo x > data/myid(x=1/2/3)创建节点标识文件。然后打开zoo.cfg,修改如下:
然后进入安装目录,在三个节点分别执行以下命令启动ZooKeeper:

这样,ZooKeeper集群就准备好了。

如前文所述,ZooKeeper可以提供的功能之一是分布式锁。分布式锁是控制分布式系统之间同步访问共享资源的一种方式,当不同系统或者同一个系统之内的不同节点访问同一个或一组资源的时候,需要通过互斥手段来防止相互之间的干扰,以保证一致性。

分布式锁的其中一种是排他锁或独占锁,当事务A对资源R加上了排它锁,那么在加锁期间,一致性系统将只允许事务A对资源R进行读取和更新操作,其他事务都不能对资源R进行任何类型的操作,直到事务A释放了资源R。我们的应用实践正是基于这种原理。

在ZooKeeper中,可以用一个数据节点来表示一个锁,比如:/a/lock,这是一个临时节点。ZooKeeper定义了三个大类的四种节点,以满足不同的需要:
  • 持久节点(PERSISTENT)
被创建后就一直存在于ZooKeeper中,直到有操作主动删除它。
  • 持久顺序节点(PERSISTENT_SEQUENTIAL)
持久性与持久节点一致,此外ZooKeeper会自动为该类型的节点名后面加上一个顺序数字后缀。
  • 临时节点(EPHEMERAL)
临时节点的生命周期相对短暂,它的存在与客户端的会话绑定在一起,一旦会话失效,则临时节点会被自动清除。
  • 临时顺序节点(EPHEMERAL_SEQUENTIAL)

也就是会被添加顺序数字后缀的临时节点。

会使临时节点被清除的会话时效,包括两种形式:

  1. ZooKeeper系统重启
  2. 客户端主动关闭会话

此外,客户端也可以主动删除临时节点来清除它。

我们利用临时节点来实现一个各应用节点处理接收到的订阅消息的排它锁,机制是:当所有应用节点上的KeyExpiresMessageListener同时收到Key过期消息的时候,这些应用节点会随即尝试在ZooKeeper中创建一个锁节点,基于ZooKeeper良好的一致性保证,确保只有一个应用节点会创建锁节点成功,那么,后续的事务处理就只由这个幸运的应用节点来继续完成。

假设我们的业务是,接收Redis中订单数据Key过期的消息,并根据过期数据Key名字中包含的订单号来进行后续处理,订单数据Key形如EK_UPBO_15001。下面我们来看一下用于测试的程序。

首先是之前见过的AppConfig.java,定义了用来接收订阅消息的各种Bean:
然后是加入了ZooKeeper元素的改进版的KeyExpiresMessageListener.java,使用ApacheCurator-2.10.0作为访问ZooKeeper的客户端:

还有工作线程类ZkThread.java:
最后,为了模拟多个应用节点,我们同时启动两个主程序,Main1和Main2:
好了,一切准备就绪,让我们启动两个主程序:

日志输出显示,程序已经连接上ZooKeeper。

现在在Redis里设置一个过期时间为5秒的Key:

5秒之后,Main1和Main2的Console输出分别为:

可以看到,本次竞争,Main2成功创建了排他锁,胜出:)

再观察一下ZooKeeper中的排他锁节点路径情况:

可以看到,由于我们在程序里设置了5秒的模拟业务处理耗时,所以临时节点/sasp/order/UPBO/15001也只短短存在了5秒钟就被删除掉了。

ZooKeeper是一个非常有用的框架,是Hadoop、HBase、Storm、Solr、Dubbo等众多大型分布式系统的核心组件,用于进行分布式协调。除此之外,ZooKeeper还有很多用途,本文只是抛砖引玉,更多的有趣实践请朋友们自己去探索吧!
0 0
原创粉丝点击