摘要
爲了高可用通產我們一個服務會部署多節點。但是有時我們希望對某些操作要求單線程處理,此時可以通過主備服務形式實現。正常情況下主節點服務處理,當主節點宕機後備用節點相關服務繼續處理。
需求
資源中心會將資源文件相關操作分發至各個項目節點,資源的操作包括添加、分享、刪除、和諧、恢復。資源中心向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 - 節點服務啓動完成!