在实际场景中,一般都是集群协作,同一个服务程序会部署在很多台服务器中,然后通过负载均衡服务器去调度。
Zookeeper服务的动态感知 - 图1

问题

这样就会产生一个问题,如果集群中某个服务挂了,Nginx并不能很快的感知到,如果还把客户端的请求分配给挂掉的服务器上就会出问题。
我们可以通过zookeeper来解决这个问题。

解决方案

集群服务器做如下操作:

  1. 各个服务连接zookeeper注册中心
  2. 创建一个目录节点(临时的顺序节点),因为是临时节点,所以当服务挂掉后对应的节点会删除,当存放的节点名称相同时,顺序节点会保证新添加的节点依次添加到目录节点中。
  3. 创建的节点存放服务器的IP地址和端口号,其实不用顺序节点也行,节点的名称直接是服务的IP地址和端口(如:120.53.119.107:8080)更加方便,值也不用存储。

    1. /servers/nginx0001

    Nginx服务做如下操作:

  4. 连接zookeeper注册中心

  5. 获取指定目录下的子节点列表(/servers)
  6. 对指定的目录节点(/servers)添加监听事件(持久性事件,能够多次监听,如果不是持久性事件,只会监听到一次)
  7. 当节点(/servers)下面的子节点发生改变,Nginx服务就会立即被通知到
  8. 把获取到的数据(如IP地址)缓存到本地列表

Zookeeper服务的动态感知 - 图2

代码

代码部分比较简单,这里直接使用官方提供的zookeeper依赖,项目中也可以直接使用Apache开源的Curator库的API进行开发,都差不多。

  1. <!-- 官方自带的客户端操作,这个版本要和服务端安装的版本相同 -->
  2. <dependency>
  3. <groupId>org.apache.zookeeper</groupId>
  4. <artifactId>zookeeper</artifactId>
  5. <version>3.6.3</version>
  6. <exclusions>
  7. <exclusion>
  8. <groupId>org.slf4j</groupId>
  9. <artifactId>slf4j-log4j12</artifactId>
  10. </exclusion>
  11. </exclusions>
  12. </dependency>

集群代码

  1. import org.apache.zookeeper.*;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import java.io.IOException;
  5. @Configuration
  6. public class ClusterServer {
  7. //zookeeper集群,zookeeper的ip与端口,这里测试一台机器开启三个zookeeper
  8. private String ZK_SERVER_LIST = "120.53.119.107:2181,120.53.119.107:2182,120.53.119.107:2183";
  9. //设置超时时间,当主节点3s后没反应就认该节点已经挂了
  10. private static final int sessionTimeout = 3000;
  11. //所有的注册节点信息当道/servers下面,方便管理
  12. private String SERVER_DIR = "/servers";
  13. //host和port可以从配置文件里面读取
  14. private String host = "127.0.0.1";
  15. private String port = "8080";
  16. private ZooKeeper zk;
  17. @Bean
  18. public ZooKeeper zooKeeper() throws IOException {
  19. //创建zookeeper对象并监听
  20. zk = new ZooKeeper(ZK_SERVER_LIST, sessionTimeout, new Watcher() {
  21. @Override
  22. public void process(WatchedEvent watchedEvent) {
  23. System.out.println("event = " + watchedEvent);
  24. //链接成功
  25. if(watchedEvent.getState()== Event.KeeperState.SyncConnected){
  26. System.out.println("zookeeper客户端连接成功");
  27. //注册对应的信息,CreateMode.EPHEMERAL临时节点,ZooDefs.Ids.OPEN_ACL_UNSAFE不需要权限
  28. try {
  29. //节点的名称就是服务IP地址和端口
  30. String result = zk.create(SERVER_DIR+"/"+host+":"+port, null,
  31. ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
  32. System.out.println(result);
  33. } catch (Exception e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. }
  38. });
  39. return zk;
  40. }
  41. }

客户端代码(Nginx)

  1. import org.apache.zookeeper.*;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import java.io.IOException;
  5. import java.util.List;
  6. @Configuration
  7. public class NginxClient {
  8. //zookeeper集群,zookeeper的ip与端口,这里测试一台机器开启三个zookeeper
  9. private String ZK_SERVER_LIST = "120.53.119.107:2181,120.53.119.107:2182,120.53.119.107:2183";
  10. //设置超时时间,当主节点3s后没反应就认该节点已经挂了
  11. private static final int sessionTimeout = 3000;
  12. //所有的注册节点信息当道/servers下面,方便管理
  13. private String SERVER_DIR = "/servers";
  14. //获取服务器列表
  15. private volatile List<String> serverList;
  16. private ZooKeeper zk;
  17. @Bean
  18. public ZooKeeper zooKeeper() throws IOException {
  19. //创建zookeeper对象并监听
  20. zk = new ZooKeeper(ZK_SERVER_LIST, sessionTimeout, new Watcher() {
  21. @Override
  22. public void process(WatchedEvent watchedEvent) {
  23. System.out.println("event = " + watchedEvent);
  24. //链接成功
  25. if(watchedEvent.getState()== Event.KeeperState.SyncConnected){
  26. System.out.println("zookeeper客户端连接成功");
  27. //获取SERVER_DIR节点对应的IP地址
  28. try {
  29. // 获取服务器子节点信息,并且对父节点进行监听
  30. List<String> ips = zk.getChildren(SERVER_DIR,true);
  31. serverList = ips;
  32. System.out.println(serverList);
  33. } catch (Exception e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. }
  38. });
  39. return zk;
  40. }
  41. }