1、架构
SpringBoot+zookeeper
2、实现思路
- 服务注册:服务启动后,向zk中注册一个临时有序节点(如:/server/order/child000000001),存储服务相关信息(如:服务的ip地址、端口号等)
- 服务发现:服务启动后,根据所需监听服务信息,对应设置Watch进行节点持续监听,获取服务信息列表缓存本地,后续可采用一系列算法(如:本地轮询)进行服务调用,监听节点发生变化,则更新本地服务信息列表缓存
- 服务注册需采用临时有序节点,服务宕机或者zk会话断开可自动删除坏节点,实现自动感知服务下线功能,有序则是为了方便后续实现负载均衡
3、实现步骤
/**
* @author: Lanrriet
* @date: 2020/7/23 6:11 下午
* @description: 服务注册与发现
*/
public class ZkDiscovery {
private static final String BASE_SERVICE = "/server";
private ZooKeeper zooKeeper;
private String monitorServer;
@Value("${server.port}")
private String port;
@Autowired
private ZookeeperConfig zkConfig;
@PostConstruct
public void init() {
try {
// 1.创建zk连接
zooKeeper = new ZooKeeper(zkConfig.getAddress(), zkConfig.getTimeout(), getWatcher());
// 2.服务注册
if (!StringUtils.isEmpty(zkConfig.getAppName())) {
//服务节点路径
String serverPath = BASE_SERVICE + "/" + zkConfig.getAppName();
if (zooKeeper.exists(serverPath, false) == null) {
zooKeeper.create(serverPath, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
//服务节点信息
String serverData = ServerUtil.getServerIp() + ":" + port;
zooKeeper.create(serverPath + "/child", serverData.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
} else {
throw new RuntimeException("服务注册失败,请检查配置信息");
}
} catch (Exception e) {
e.printStackTrace();
}
// 3.监听服务不为空时,更新服务信息列表
monitorServer = zkConfig.getMonitorServer();
if (!StringUtils.isEmpty(monitorServer)) {
for (String serverName : monitorServer.split(",")) {
updateServerList(serverName);
}
}
}
// 根据配置生成节点监听器
private Watcher getWatcher() {
Watcher watcher = null;
if (!StringUtils.isEmpty(zkConfig.getMonitorServer())) {
String monitorServer = zkConfig.getMonitorServer();
watcher = new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
for (String serverName : monitorServer.split(",")) {
String serverPath = BASE_SERVICE + "/" + serverName;
if (watchedEvent.getType() == Watcher.Event.EventType.NodeChildrenChanged && watchedEvent.getPath().equals(serverPath)) {
updateServerList(serverName);
}
//持续节点监听
try {
zooKeeper.exists(serverPath, getWatcher());
zooKeeper.getChildren(serverPath, getWatcher());
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
}
return watcher;
}
// 更新服务信息列表
private void updateServerList(String serverName) {
String serverPath = BASE_SERVICE + "/" + serverName;
try {
if (zooKeeper.exists(serverPath, false) != null) {
List<String> urlList = new ArrayList<>();
List<String> children = zooKeeper.getChildren(serverPath, true);
for (String subNode : children) {
byte[] data = zooKeeper.getData(serverPath + "/" + subNode, false, null);
String host = new String(data, "utf-8");
System.out.println("host:" + host);
urlList.add(host);
}
ServerData sd = new ServerData();
sd.setAppName(serverName);
sd.setBalance(0);
sd.setUrlList(urlList);
if (LoadBalanse.SERVER_LIST == null) {
LoadBalanse.SERVER_LIST = new ArrayList<ServerData>();
}
List<ServerData> sList = LoadBalanse.SERVER_LIST.stream().filter(s -> serverName.equals(s.getAppName())).collect(Collectors.toList());
if (sList.size() > 0) {
ServerData preSd = sList.get(0);
preSd.setBalance(sd.getBalance());
preSd.setUrlList(sd.getUrlList());
} else {
LoadBalanse.SERVER_LIST.add(sd);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public abstract class LoadBalanse {
/**
* 监听服务列表
*/
public volatile static List<ServerData> SERVER_LIST;
/**
* 获取指定服务信息
*
* @param appName
* @return
*/
public abstract String getServerUrl(String appName);
}
/**
* 本地轮询
*/
public class RandomLoadBalance extends LoadBalanse {
@Override
public String getServerUrl(String appName) {
String result = "";
if (!CollectionUtils.isEmpty(SERVER_LIST)) {
ServerData serverData = SERVER_LIST.stream().filter(sd -> appName.equals(sd.getAppName())).collect(Collectors.toList()).get(0);
List<String> urlList = serverData.getUrlList();
int balance = serverData.getBalance();
int instanceSize = urlList.size();
int serviceIndex = balance % instanceSize;
serverData.setBalance(++balance);
String server = urlList.get(serviceIndex);
result = server;
System.out.println("发现可用的" + appName + "服务:" + server);
}
return result;
}
}
/**
* @author: Lanrriet
* @date: 2020/7/23 6:11 下午
* @description: 自动装配,方便后续使用
*/
@Data
@ConfigurationProperties(prefix = "zookeeper")
public class ZookeeperConfig {
/**
* zk服务连接地址
*/
private String address = "127.0.0.1:2181";
/**
* 连接超时
*/
private int timeout = 5000;
/**
* 注册服务名称
*/
private String appName;
/**
* 监控服务名称
*/
private String monitorServer;
}
@Configuration
@EnableConfigurationProperties({ZookeeperConfig.class})
public class ZookeeperConfiguration {
@Bean
public ZkDiscovery zkDiscovery() {
return new ZkDiscovery();
}
}
4、具体使用
添加maven依赖
<dependency>
<groupId>com.demo</groupId>
<artifactId>zookeeper-discovery</artifactId>
<version>1.0</version>
</dependency>
application.properties配置信息
#zk
zookeeper.address=127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
zookeeper.appname=user # 注册节点名称
zookeeper.monitor-server=order,test # 监听服务名称
服务调用
@RequestMapping("/getOrder")
public Object getProduct(@RequestBody Map entity) {
String host = loadBalanse.getServerUrl("order");
Map res = restTemplate.postForObject("http://" + host + "/order/getProduct", entity, Map.class);
res.put("host", host);
return res;
}