高可用是一個(gè)健壯的系統(tǒng)必備的特性,實(shí)現(xiàn)高可用的方法有很多灾部,這篇文章簡要介紹了實(shí)現(xiàn)高可用的方法,適合瀏覽一遍熟悉高可用的概況惯退。
我的這篇文章的主要目的是介紹如何用zookeeper構(gòu)建一個(gè)高可用的Spring服務(wù)赌髓,項(xiàng)目的代碼也放在了我的github上。
Spring服務(wù)的高可用
我了解的Spring服務(wù)實(shí)現(xiàn)高可用的方式有負(fù)載均衡和主從模式催跪。
利用負(fù)載均衡機(jī)制將請(qǐng)求發(fā)送到不同的微服務(wù)實(shí)例上锁蠕,這種機(jī)制要求各個(gè)服務(wù)必須是無狀態(tài)的,這樣當(dāng)一個(gè)請(qǐng)求過來的時(shí)候才能保證無論發(fā)到哪個(gè)微服務(wù)實(shí)例上都能得到一致的結(jié)果懊蒸。
但是如果一個(gè)服務(wù)的業(yè)務(wù)要求它是有狀態(tài)的(比如在服務(wù)中配置了定時(shí)器區(qū)完成定時(shí)任務(wù))荣倾,這時(shí)利用負(fù)載均衡就不能實(shí)現(xiàn)高可用的要求,比如在我們部署了兩個(gè)微服務(wù)實(shí)例骑丸,兩個(gè)服務(wù)中接收請(qǐng)求舌仍、記錄請(qǐng)求到數(shù)據(jù)庫并啟動(dòng)定時(shí)器任務(wù),這時(shí)如果一個(gè)服務(wù)崩潰了通危,它上面的定時(shí)任務(wù)也隨之消失铸豁,這時(shí)我們希望復(fù)原這些定時(shí)任務(wù),但是當(dāng)讀取數(shù)據(jù)庫時(shí)菊碟,我們并不知道原來崩潰的服務(wù)上有哪些定時(shí)任務(wù)节芥。(當(dāng)然原則上如果在數(shù)據(jù)庫中增加一個(gè)記錄服務(wù)實(shí)例的字段,是可以實(shí)現(xiàn)的逆害,但是這樣會(huì)讓結(jié)構(gòu)很復(fù)雜)
如果采用主從模式的高可用头镊,將所有的定時(shí)任務(wù)都放在一個(gè)服務(wù)上,這樣當(dāng)一個(gè)服務(wù)崩潰時(shí)魄幕,另一個(gè)服務(wù)監(jiān)聽到這個(gè)消息相艇,就會(huì)從數(shù)據(jù)庫中讀取所有的任務(wù)記錄并啟動(dòng)定時(shí)任務(wù)。這樣的模式結(jié)構(gòu)更簡單清晰梅垄。
Zookeeper簡介
在Zookeeper的官網(wǎng)上有這么一句話:ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.
這大概描述了Zookeeper主要可以干哪些事情:配置管理厂捞、名字服務(wù)输玷、分布式同步以及集群管理。比較具體的介紹請(qǐng)參見ZooKeeper簡介(淺入)靡馁,我們這里配置服務(wù)的主從主要是利用了其group services
的功能欲鹏。
如果沒有使用過的同學(xué)想實(shí)際體驗(yàn)一下,推薦按照ZooKeeper Getting Started Guide官網(wǎng)上的demo操作一遍臭墨,5min左右的時(shí)間赔嚎,可以對(duì)zookeeper的基本用法有個(gè)了解。
代碼實(shí)現(xiàn)
完整的代碼實(shí)現(xiàn)可以到我的github上查看胧弛,我這里介紹實(shí)現(xiàn)高可用主從模式的幾個(gè)關(guān)鍵地方尤误。
Curator是Netflix公司開源的一套zookeeper客戶端框架,解決了很多Zookeeper客戶端非常底層的細(xì)節(jié)開發(fā)工作结缚,包括連接重連损晤、反復(fù)注冊(cè)Watcher和NodeExistsException異常等等。具體的使用方法這里不做展開红竭,有興趣的可以參考Zookeeper客戶端Curator使用詳解尤勋。
我們可以使用如下curatorFramework()
的方法便捷的建立服務(wù)與zookeeper的連接。
@Bean(destroyMethod = "close")
public CuratorFramework curatorFramework() {
return CuratorFrameworkFactory.builder().connectString(zkUrl).retryPolicy(new ExponentialBackoffRetry(1000, 3)).sessionTimeoutMs(5000).connectionTimeoutMs(5000).build();
}
@Bean(destroyMethod = "stop")
public LeaderInitiatorFactoryBean leaderInitiator() {
LeaderInitiatorFactoryBean bean = new LeaderInitiatorFactoryBean();
bean.setClient(curatorFramework()).setPath(path).setRole(role).setApplicationEventPublisher(applicationEventPublisher);
return bean;
}
@Bean(destroyMethod = "stop")
public ZookeeperMetadataStore zookeeperMetadataStore() throws Exception {
ZookeeperMetadataStore metadataStore = new ZookeeperMetadataStore(curatorFramework());
metadataStore.setRoot(path);
return metadataStore;
}
同時(shí)為了實(shí)現(xiàn)服務(wù)的主從機(jī)制茵宪,引入spring-integration-zookeeper依賴最冰,用leaderInitiator()
方法將服務(wù)配置成主從模式,當(dāng)主服務(wù)發(fā)生問題時(shí)稀火,spring會(huì)通過自己的事件廣播器將這個(gè)消息傳遞給從服務(wù)暖哨,從服務(wù)在接收到事件之后會(huì)執(zhí)行一些方法,將自己的狀態(tài)更新成主服務(wù)凰狞。監(jiān)聽器的實(shí)現(xiàn)如下:
@EventListener
public void processEvent(AbstractLeaderEvent event) {
if (event instanceof OnGrantedEvent) {
logger.info("on grant:" + event);
haService.leaderGranted();
logger.info(" leader grant process succeed");
} else if (event instanceof OnRevokedEvent) {
logger.info("on revoke:" + event);
haService.leaderRevoked();
logger.info(" leader revoke process succeed");
}
}
以上是利用zookeeper構(gòu)建高可用的Spring服務(wù)的一個(gè)簡單demo篇裁,完整代碼可以到我的github上查看,如果對(duì)你有幫助希望你能在博客和github上幫我點(diǎn)贊服球!