我以前在一片文章 用Nginx+Redis實現session共享的均衡負載 中作了一個負載均衡的實驗,其主要架構以下:html
把debian1做爲調度服務器承擔請求分發的任務,即用戶訪問的是debian1,而後debain1把請求按照必定的策略發送給應用服務器:debian2或者debain3,甚至更多的debain四、五、6......java
狀態和數據能夠放在外部的分佈式緩存服務和分佈式數據庫服務中,這樣應用服務自己就是無狀態的,因此機器增減都是很容易的,應用的高可用是有保證的(對於有狀態的高可用不只要注意機器增減與切換、還要注意備份冗餘、數據一致性等問題)。可是當時忽略了一個地方,那就是調度服務器debian1自己的高可用性沒有考慮到,存在單點問題。linux
高可用的首要想法就是雙機熱備,故障時自動切換,因此咱們要給debian1加一個備機debain1'。我如今按照本身的知識粗淺的把解決方案分爲兩類:客戶端有感知的高可用、對客戶端透明的高可用,並分別挑選一個示例作一下實驗。git
注:下面實現高可用都用的是雙機熱備,爲了方便,把調度服務器debian1簡稱爲主機,把調度服務器debian1的備機debian1'簡稱爲備機。github
客戶端有感知的高可用,也就是須要客戶端的配合,客戶端本身去確認服務器的變動並切換訪問的目標。好比說咱們的主機、備機都在ZooKeeper(或者其餘相似的註冊中心好比redis)中進行註冊,客戶端監聽ZooKeeper中服務器的信息,發現主機下線本身就切換訪問備機便可。redis
首先在本機搭建包含3個節點的ZooKeeper僞集羣。在官網下載版本3.5.4-beta
,解壓,而後複製3份,每一份都要作以下操做:算法
進入conf文件夾 建立一個配置文件zoo.cfg。代碼以下:數據庫
initLimit=10
syncLimit=5
clientPort=2181(每一個節點不一樣:2181,3181,4181)
tickTime=2000
dataDir=E:/zookeeper-3.5.4-1/data(每一個節點不一樣:3.5.4-2,3.5.4-3)
dataLogDir=E:/zookeeper-3.5.4-1/datalog(每一個節點不一樣,同上)
server.1=192.168.*.*::2888:3888(實驗機器的局域網IP或者直接localhost)
server.2=192.168.*.*::4888:5888
server.3=192.168.*.*::6888:7888
複製代碼
建立上面的dataDir
和dataLogDir
,並在dataDir
目錄下必須建立myid
文件,寫入不一樣的整數ID,也就是上面的server.x的x
,好比1apache
分別進入bin目錄,在zkServer.cmd
中call
以前加入set ZOOCFG=../conf/zoo.cfg
並用其啓動。後端
順帶一提,代碼開發我就使用我以前的項目CHKV了,由於這個項目中的NameNode
或者DataNode
也能夠用ZooKeeper實現高可用,歡迎和我一塊兒完善這個項目,一塊進步。
調度服務器主要向ZooKeeper註冊本身,並向客戶端提供服務。咱們使用curator框架來和ZooKeeper交互,特別要注意版本問題。
主要代碼以下:
public static void main(String... arg) throws Exception {
thisNode = ManagementFactory.getRuntimeMXBean().getName();
logger.debug("my pid: {}",thisNode);
// 構造鏈接
CuratorFramework curator = CuratorFrameworkFactory
.builder()
.connectString(CONNECT_ADDR)
.connectionTimeoutMs(CONNECTION_TIMEOUT)//鏈接建立超時時間
.sessionTimeoutMs(SESSION_TIMEOUT)//會話超時時間
.retryPolicy(policy)
.build();
curator.start();
// 建立節點也就是成爲master,阻塞等待
boolean result = becomeMaster(curator);
if (result){
logger.info("Successfully Became Master");
}else {
logger.info("Failed to Became Master");
}
// 監聽
NodeCache cache = new NodeCache(curator, MASTER_NODE_PATH,false);
cache.getListenable().addListener(()->{
ChildData data = cache.getCurrentData();
if (data != null){
String path = data.getPath();
Stat stat = data.getStat();
String dataString = new String(data.getData());
logger.debug("masterNode info, path:{},data:{},stat,{}",path,dataString,stat);
}else {
logger.info("masterNode is down, try to become Master");
if (becomeMaster(curator)){
logger.info("Successfully tried to Became Master");
}else {
logger.info("Failed to try to Became Master");
}
}
});
cache.start(true);
}
// 確認master
private static boolean confirm(CuratorFramework curator) throws Exception {
masterNode = new String(curator.getData().forPath(MASTER_NODE_PATH));
logger.info("masterNode: {}",masterNode);
return thisNode.equals(masterNode);
}
// 成爲master
private static boolean becomeMaster(CuratorFramework curator) throws Exception {
String path= "";
try {
path = curator.create()
.creatingParentContainersIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath(MASTER_NODE_PATH,thisNode.getBytes());
logger.debug(path);
}catch (Exception e){
logger.error(e.getMessage());
}
return MASTER_NODE_PATH.equals(path);
}
複製代碼
完整代碼在GitHub上。
客戶端主要向ZooKeeper監聽調度服務器變動事件,並向其發起應用請求。實際上應用服務器也可使用這部分代碼來監聽調度服務器的變化。
主要代碼以下:
public static void main(String... arg) throws Exception {
CuratorFramework curator = CuratorFrameworkFactory
.builder()
.connectString(CONNECT_ADDR)
.connectionTimeoutMs(CONNECTION_TIMEOUT)
.sessionTimeoutMs(SESSION_TIMEOUT)
.retryPolicy(policy)
.build();
curator.start();
NodeCache cache = new NodeCache(curator, MASTER_NODE_PATH,false);
cache.getListenable().addListener(()->{
ChildData data = cache.getCurrentData();
if (data != null){
String path = data.getPath();
Stat stat = data.getStat();
String dataString = new String(data.getData());
logger.debug("masterNode info, path:{},data:{},stat,{}",path,dataString,stat);
masterInfo = dataString;
}else {
logger.info("masterNode is down, waiting");
}
});
cache.start(true);
// 得到主機,阻塞等待
try {
masterInfo = new String(curator.getData().forPath(MASTER_NODE_PATH));
}catch (Exception e){
logger.error("no masterInfo");
masterInfo = null;
}
while (masterInfo==null);
logger.info("masterInfo:{}",masterInfo);
}
複製代碼
完整代碼在GitHub上。
對客戶端透明的高可用,也就是客戶端不須要作什麼工做,服務器切換不切換客戶端根本不知道也不關心。主要實現方式有兩種,一種是客戶端經過域名訪問主機,那麼監控主機下線後就把域名從新分配給備機,固然這個切換會有時間成本,視定義的DNS緩存時間而定;第二種就是客戶端經過IP訪問主機,監控到主機下線後就經過IP漂移技術把對外的IP(或者說虛擬IP)分配給備機,這樣就能作到及時的切換。
實際環境中經常使用keepalived來實現IP漂移。
搭建過程參考了The keepalived solution for LVS和官網文檔
首先主機、備機都要安裝keepalived,而後配置主機/etc/keepalived/keepalived.conf
:
vrrp_instance VI_1 {
state MASTER # MASTER表示此實例是主機,BACKUP表示此實例是備機
interface eth0 # 網卡名稱,亦即網絡接口
virtual_router_id 51
priority 100
advert_int 1 # 心跳檢查時間間隔,單位秒
authentication { # 認證方式 是 密碼的方式
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {# 虛擬IP地址,也就是對外開放的IP
10.23.8.80
}
}
virtual_server 10.23.8.80 80 { # 虛擬服務器,也就是對外開放的IP與端口
delay_loop 6
lb_algo wlc # 負載均衡調度算法 此處是 加權最少鏈接
lb_kind NAT # 有 DR,NAT,TUN三種
persistence_timeout 600
protocol TCP
real_server 172.18.1.11 80 {# 後端的 應用服務器
weight 100 # 節點的權重
TCP_CHECK {
connect_timeout 3 # 3秒超時
}
}
real_server 172.18.1.12 80 {# 後端的 應用服務器
weight 100
TCP_CHECK {
connect_timeout 3
}
}
real_server 172.18.1.13 80 {# 後端的 應用服務器
weight 100
TCP_CHECK {
connect_timeout 3
}
}
}
複製代碼
配置備機/etc/keepalived/keepalived.conf
,與主機相似,可是state是backup,且權重較低便可:
vrrp_instance VI_1 {
state BACKUP
interface eth1
virtual_router_id 51
priority 90
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
10.23.8.80
}
}
複製代碼
說白了,這兩種高可用的實現方式前者是在應用層實現的,然後者是在傳輸層實現的,那麼咱們就能夠想到,計算機網絡的每一層其實都是能夠作負載均衡和高可用的。
查看原文,來自MageekChiu