在实际场景中,一般都是集群协作,同一个服务程序会部署在很多台服务器中,然后通过负载均衡服务器去调度。
问题
这样就会产生一个问题,如果集群中某个服务挂了,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;
@Configuration
public class ClusterServer {
//zookeeper集群,zookeeper的ip与端口,这里测试一台机器开启三个zookeeper
private 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;
@Bean
public ZooKeeper zooKeeper() throws IOException {
//创建zookeeper对象并监听
zk = new ZooKeeper(ZK_SERVER_LIST, sessionTimeout, new Watcher() {
@Override
public 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;
@Configuration
public class NginxClient {
//zookeeper集群,zookeeper的ip与端口,这里测试一台机器开启三个zookeeper
private 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;
@Bean
public ZooKeeper zooKeeper() throws IOException {
//创建zookeeper对象并监听
zk = new ZooKeeper(ZK_SERVER_LIST, sessionTimeout, new Watcher() {
@Override
public 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;
}
}