在实际场景中,一般都是集群协作,同一个服务程序会部署在很多台服务器中,然后通过负载均衡服务器去调度。
问题
这样就会产生一个问题,如果集群中某个服务挂了,Nginx并不能很快的感知到,如果还把客户端的请求分配给挂掉的服务器上就会出问题。
我们可以通过zookeeper来解决这个问题。
解决方案
集群服务器做如下操作:
- 各个服务连接zookeeper注册中心
- 创建一个目录节点(临时的顺序节点),因为是临时节点,所以当服务挂掉后对应的节点会删除,当存放的节点名称相同时,顺序节点会保证新添加的节点依次添加到目录节点中。
创建的节点存放服务器的IP地址和端口号,其实不用顺序节点也行,节点的名称直接是服务的IP地址和端口(如:120.53.119.107:8080)更加方便,值也不用存储。
/servers/nginx0001
Nginx服务做如下操作:
连接zookeeper注册中心
- 获取指定目录下的子节点列表(/servers)
- 对指定的目录节点(/servers)添加监听事件(持久性事件,能够多次监听,如果不是持久性事件,只会监听到一次)
- 当节点(/servers)下面的子节点发生改变,Nginx服务就会立即被通知到
- 把获取到的数据(如IP地址)缓存到本地列表

代码
代码部分比较简单,这里直接使用官方提供的zookeeper依赖,项目中也可以直接使用Apache开源的Curator库的API进行开发,都差不多。
<!-- 官方自带的客户端操作,这个版本要和服务端安装的版本相同 --><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.6.3</version><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></exclusion></exclusions></dependency>
集群代码
import org.apache.zookeeper.*;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.io.IOException;@Configurationpublic class ClusterServer {//zookeeper集群,zookeeper的ip与端口,这里测试一台机器开启三个zookeeperprivate String ZK_SERVER_LIST = "120.53.119.107:2181,120.53.119.107:2182,120.53.119.107:2183";//设置超时时间,当主节点3s后没反应就认该节点已经挂了private static final int sessionTimeout = 3000;//所有的注册节点信息当道/servers下面,方便管理private String SERVER_DIR = "/servers";//host和port可以从配置文件里面读取private String host = "127.0.0.1";private String port = "8080";private ZooKeeper zk;@Beanpublic ZooKeeper zooKeeper() throws IOException {//创建zookeeper对象并监听zk = new ZooKeeper(ZK_SERVER_LIST, sessionTimeout, new Watcher() {@Overridepublic void process(WatchedEvent watchedEvent) {System.out.println("event = " + watchedEvent);//链接成功if(watchedEvent.getState()== Event.KeeperState.SyncConnected){System.out.println("zookeeper客户端连接成功");//注册对应的信息,CreateMode.EPHEMERAL临时节点,ZooDefs.Ids.OPEN_ACL_UNSAFE不需要权限try {//节点的名称就是服务IP地址和端口String result = zk.create(SERVER_DIR+"/"+host+":"+port, null,ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);System.out.println(result);} catch (Exception e) {e.printStackTrace();}}}});return zk;}}
客户端代码(Nginx)
import org.apache.zookeeper.*;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.io.IOException;import java.util.List;@Configurationpublic class NginxClient {//zookeeper集群,zookeeper的ip与端口,这里测试一台机器开启三个zookeeperprivate String ZK_SERVER_LIST = "120.53.119.107:2181,120.53.119.107:2182,120.53.119.107:2183";//设置超时时间,当主节点3s后没反应就认该节点已经挂了private static final int sessionTimeout = 3000;//所有的注册节点信息当道/servers下面,方便管理private String SERVER_DIR = "/servers";//获取服务器列表private volatile List<String> serverList;private ZooKeeper zk;@Beanpublic ZooKeeper zooKeeper() throws IOException {//创建zookeeper对象并监听zk = new ZooKeeper(ZK_SERVER_LIST, sessionTimeout, new Watcher() {@Overridepublic void process(WatchedEvent watchedEvent) {System.out.println("event = " + watchedEvent);//链接成功if(watchedEvent.getState()== Event.KeeperState.SyncConnected){System.out.println("zookeeper客户端连接成功");//获取SERVER_DIR节点对应的IP地址try {// 获取服务器子节点信息,并且对父节点进行监听List<String> ips = zk.getChildren(SERVER_DIR,true);serverList = ips;System.out.println(serverList);} catch (Exception e) {e.printStackTrace();}}}});return zk;}}
