負載均衡的高可用
最近在工作中遇到了hiveserver2需要部署高可用的場景,去網(wǎng)上搜索了解過后,用了絕大多數(shù)人推薦的共同方法:
Property_name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Property_value? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Description
hive.server2.support.dynamic.service.discovery? ? ? ? ? ? ? ? ? ? ? ? true(默認false)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?使hiveserver2服務可被動態(tài)發(fā)現(xiàn)
hive.server2.zookeeper.namespace? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?hiveserver2(默認值)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?hiveserver2實例在zk中注冊的znode名
hive.zookeeper.client.port? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?2181(默認值)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? zk端口
hive.zookeeper.quorum? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? zk1:2181,zk2:2181,zk3:2181(默認空)? ? zk集群連接方式
當如上配置后,啟動的hiveserver2實例都會注冊到zk的/hiveserver2節(jié)點下,如下所示

get一下其中一個實例,就會發(fā)現(xiàn)它包含了這個hs2的uri、端口號等連接配置,如下所示

此時用客戶端(如beeline)連接hs2時,url需使用
jdbc:hive2://zk1:2181,zk2:2181,zk3:2181/;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2
此時zk會隨機從多個實例中隨機拿一個實例供連接使用,過程在代碼中體現(xiàn)如下:

先去指定的znode(/hiveserver2)下拿到所有實例,再通過Random隨機拿其中一個去連接;也正因為這個隨機的過程,此種方式的hs2高可用一定程度上實現(xiàn)了hiveserver2的負載均衡。
問題
但這種方式的高可用在使用中存在一個問題,即當你同時開啟了鑒權的服務(如ranger),hs2在啟動時不僅會注冊hs2的實例,還會注冊一個leader節(jié)點,如下所示

從圖中可以看出,leader節(jié)點下有兩個子節(jié)點,而每個子節(jié)點實際沒有任何內(nèi)容,因為這個leader節(jié)點在此處沒有用處。
但因為leader節(jié)點的存在,使上面講的隨機拿的過程中就可能會拿到這個leader節(jié)點,而該節(jié)點實際不是有效的hs2實例,故此時連接會報錯“Unable to read HiveServer2 configs from ZooKeeper”.
那么Leader節(jié)點為什么會產(chǎn)生呢?我們看下源碼里對應的部分

而目前還沒有發(fā)現(xiàn)leader節(jié)點的具體作用,只會在從zk拿實例時徒增報錯。
為了解決此問題,我重新編譯了hive-jdbc的源碼,在從zk拿實例的過程中過濾掉了leader節(jié)點。
能解決此問題的,還有另一種方式,即配置ActivePassiveHA。
ActivePassiveHA
探究源碼過程中,發(fā)現(xiàn)hive還提供了另一種高可用方案,即ActivePassiveHA,開啟需如下配置:
Property_name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Property_value? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Description
hive.server2.support.dynamic.service.discovery? ? ? ? ?true(默認false)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?使hiveserver2服務可被動態(tài)發(fā)現(xiàn)
hive.server2.active.passive.ha.enable? ? ? ? ? ? ? ? ? ? ? ? ?true(默認false)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ActivePassiveHA啟用
hive.server2.active.passive.ha.registry.namespace? ? ?hs2ActivePassiveHA(默認值)? ? ? ? ? ? ? ? ? ? hiveserver2實例及l(fā)eader在zk中注冊的znode名
hive.zookeeper.quorum? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? zk1:2181,zk2:2181,zk3:2181(默認空)? ?zk集群連接方式
hive.zookeeper.client.port? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?2181(默認值)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?zk端口
當如上配置后,啟動的hiveserver2實例都會注冊到zk的/hs2ActivePassiveHA節(jié)點下,如下所示

由圖可見,其本質還是和上面類似的注冊實例的過程相似,但注冊的實例統(tǒng)一放在了instances下面,且注冊時信息更詳細;而單獨產(chǎn)生的_LEADER節(jié)點則將兩個實例中的registry.unique.id拿出單獨放置。(hs2ActivePassiveHA后的unsecure或secure是根據(jù)是否開啟身份驗證或鑒權后自動添加的)
而ActivePassiveHA和上述高可用方案最大的區(qū)別,就是通過_LEADER節(jié)點分配可連接實例中的"leader"和"worker",當leader沒有掛掉的時候,所有通過zk連接到hs2的連接都會指向leader節(jié)點,而不會連接到其他節(jié)點,與上述高可用方案的隨機方式有一定區(qū)別。
此時,連接的url需使用
jdbc:hive2://zk1:2181,zk2:2181,zk3:2181/;serviceDiscoveryMode=zooKeeperHA;zooKeeperNamespace=hs2ActivePassiveHA
這種高可用方案同時解決了另一個問題:java.lang.NoClassDefFoundError: org/apache/tez/dag/api/TezConfiguration,如下所示

這問題出現(xiàn)在hs2啟動過程中,因為對hive3來說mr引擎已經(jīng)是過時的,所以無論你的hive執(zhí)行引擎選的是什么,都會在啟動hs2時自動啟動一個TezSession,但如果你在hive-env.sh中沒有配置tez包的位置,這里就會報ClassNotFound,并且強制等待60s后重試,重試后不管成功還是失敗都會繼續(xù)向下執(zhí)行,不影響正常啟動,但拖慢了hs2啟動的時間。本來十幾秒就能解決的事非要等待一分多鐘才行。
但當開啟了ActicePassiveHA后,這個啟動TezSession的過程就會變成此處暫不啟動,后續(xù)由leader自行啟動,代碼如下:

問題
此種高可用方案在hive官網(wǎng)和網(wǎng)絡上的帖子中幾乎沒有被提及,是在hs2啟動時跟源碼發(fā)現(xiàn)的,目前發(fā)現(xiàn)存在一些問題:
此種方式的高可用在leader節(jié)點沒有掛掉的情況下會始終連接leader節(jié)點,只有在leader不可用時才會自動切換,類似hadoop的Actice/Standby方案,但這樣只能做到故障切換,沒有做到負載均衡(這種方式在代碼中是否有另外一套負載均衡的機制還有待探究)
url中serviceDiscoveryMode=zooKeeperHA的連接方式不被一些鑒權服務所支持(如ranger)