摘要
为了高可用通产我们一个服务会部署多节点。但是有时我们希望对某些操作要求单线程处理,此时可以通过主备服务形式实现。正常情况下主节点服务处理,当主节点宕机后备用节点相关服务继续处理。
需求
资源中心会将资源文件相关操作分发至各个项目节点,资源的操作包括添加、分享、删除、和谐、恢复。资源中心向mq发布消息,各个项目节点通过订阅mq消息处理对应资源。各项目节点HA部署双节点。此时有可能出现其中一个节点正在处理资源的上传操作,由于涉及到文件IO,此操作比较慢,同时另一个节点接收到了删除的mq消息,执行对应的资源删除操作。为了避免这种情况,要求对消息的操作必须是顺序的。
解决方案
项目的两个节点在启动完成后抢注zookeeper同时订阅zk的节点disconnect事件,抢注成功的节点成为主节点通知执行启动操作,抢注失败的节点为从节点不做操作。当主节点宕机后,从节点会接收到disconnect消息,从而执行启动操作。
实现
- 服务启动后抢注zk
@Override public void run(ApplicationArguments args) throws Exception { if(enable){ zookeeperWatcher.connect(); if(!zookeeperWatcher.exists()){ zookeeperWatcher.create(); }else{ log.info("节点已被抢注,不再发布节点抢注成功事件!!!"); } }else{ log.info("不启用,默认发布抢注成功事件"); SpringContextUtil.getApplicationContext().publishEvent(new MainStandbyRunnerEvent(true)); } }
- 通过publishEvent+listener将主备启动与主备解耦
抢注zk节点成功发布启动事件
各个业务方通过添加listener实现启动逻辑SpringContextUtil.getApplicationContext().publishEvent(new MainStandbyRunnerEvent(true));
@Override public void onApplicationEvent(MainStandbyRunnerEvent event) { log.info("执行节点服务启动时间!"); //TODO 启动逻辑 log.info("节点服务启动完成!"); }
- 监听zk节点断开事件并注册节点发布注册成功事件
@Override public void process(WatchedEvent event){ if(path.equals(event.getPath()) && event.getType() == Event.EventType.NodeDeleted){ log.info("节点断开连接"); //抢注节点 create(); } } /** * 创建节点并发布事件 */ public void create(){ try { byte[] bytes = value.getBytes(CHARSET); this.zookeeper.create(path, bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); log.info("发布节点抢注成功事件"); SpringContextUtil.getApplicationContext().publishEvent(new MainStandbyRunnerEvent(true)); } catch (Exception e) { log.error(e.getMessage()); } }
效果
主节点启动
2020-01-13 16:24:02.196 [main] INFO c.i.s.common.mainstandby.config.MainStandbyProperties - 主备启动参数:[enable=true,host=127.0.0.1,path=/MainStandbyStart,value=MainNodeStart]
2020-01-13 16:26:36.127 [main] INFO c.i.sclass.common.mainstandby.watcher.ZookeeperWatcher - 发布节点抢注成功事件
2020-01-13 16:26:36.127 [main] INFO c.i.sclass.baseauth.listener.MainStandbyRunnerListener - 执行节点服务启动事件!
2020-01-13 16:26:36.127 [main] INFO c.i.sclass.baseauth.listener.MainStandbyRunnerListener - 节点服务启动完成!
从节点启动
2020-01-13 16:27:46.292 [main] INFO c.i.s.common.mainstandby.config.MainStandbyProperties - 主备启动参数:[enable=true,host=127.0.0.1,path=/MainStandbyStart,value=StandbyNodeStart]
2020-01-13 16:27:46.762 [main] INFO c.i.s.common.mainstandby.component.MainStandbyRunner - 节点已被抢注,不再发布节点抢注成功事件!!!
主节点宕机
2020-01-13 16:28:39.509 [main-EventThread] INFO c.i.sclass.common.mainstandby.watcher.ZookeeperWatcher - 节点断开连接
2020-01-13 16:28:39.516 [main-EventThread] INFO c.i.sclass.common.mainstandby.watcher.ZookeeperWatcher - 发布节点抢注成功事件
2020-01-13 16:28:39.516 [main-EventThread] INFO c.i.s.baseauthtest.listener.MainStandbyRunnerListener - 执行节点服务启动事件!
2020-01-13 16:28:39.517 [main-EventThread] INFO c.i.s.baseauthtest.listener.MainStandbyRunnerListener - 节点服务启动完成!