我以前在一片文章 用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份,每一份都要作以下操做:算法
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
,好比1zkServer.cmd
中call
以前加入set ZOOCFG=../conf/zoo.cfg
并用其启动。顺带一提,代码开发我就使用我以前的项目CHKV了,由于这个项目中的NameNode
或者DataNode
也能够用ZooKeeper实现高可用,欢迎和我一块儿完善这个项目,一块进步。数据库
调度服务器主要向ZooKeeper注册本身,并向客户端提供服务。咱们使用curator框架来和ZooKeeper交互,特别要注意版本问题。apache
主要代码以下:后端
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