服务注册中心
之所以需要访问注册和服务发现是因为分布式系统中,服务之间需要相互调用,但若每个服务自己维护一份依赖的服务信息的话,就显得很麻烦,且自身维护的数据无法保证其实时性,当依赖的服务信息发生变更时,无法及时获取更新,解决方案就是引入一个注册中心,服务提供方将自己的信息写入到注册中心,服务使用方从注册中心来获取服务信息; 如下图:
client表示服务使用方,server表示服务提供方。
客户端可自动发现服务信息,当服务状态发生变化时(上线,下线,更换地址),客户端可以及时响应变化。
实现
- 首先保证Zookeeper以安装启动,且可以正常访问
- 创建Maven项目并添加Zookeeper的Java客户端依赖(注意版本号需>3.6)
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.1</version>
</dependency>
3.编写服务提供方
package com.lbb.zoo.servers;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* 用户服务
*/
public class UserService {
//端口号
private int port = 8899;
//节点
private String node = "/userService";
//socket服务端
private ServerSocket socket;
//获取本机ip地址
public String getLocalHost() throws SocketException {
//获取本机ip地址 可能有多个网卡信息,所以需要遍历筛选
String ip = null;
//获取全部网络接口
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
//获取网络接口
NetworkInterface ni = (NetworkInterface) networkInterfaces.nextElement();
//获取InetAddress
Enumeration<InetAddress> nias = ni.getInetAddresses();
while (nias.hasMoreElements()) {
InetAddress ia = (InetAddress) nias.nextElement();
//获取需要的ip
if (!ia.isLinkLocalAddress() && !ia.isLoopbackAddress() && ia instanceof Inet4Address) {
ip = ia.getHostAddress();
}
}
}
return ip;
}
//注册服务
public void serverRegister(String ip) throws IOException, KeeperException, InterruptedException {
//注册成服务信息到zookeeper(服务注册)
//参数 1 ip:port(集群用,隔开) 2 超时时间 3 监视器
//如果出现错误可以把超时时间延长试试
ZooKeeper zooKeeper = new ZooKeeper("192.168.74.129:2181,192.168.74.130:2181,192.168.74.131:2181", 3000, null);
System.out.println("ZooKeeper连接成功....");
//创建节点 将自身的服务信息写入到zookeeper
//参数 1.节点 2.节点值 3.访问控制列表,管理访问控制权限 4.创建模式:持久和临时 ,下面是临时的
//访问控制列表,管理访问控制权限
ArrayList<ACL> acls = new ArrayList<>();
//所有都可以访问
ACL acl = new ACL(31, ZooDefs.Ids.ANYONE_ID_UNSAFE);
acls.add(acl);
zooKeeper.create(node, (ip + ":" + port).getBytes(), acls, CreateMode.EPHEMERAL);
System.out.println("服务发布成功....");
}
//处理客户端连接
private void clientHandler() throws IOException {
//时时监听
while (true) {
//获取客户端Socket
Socket client_socket = this.socket.accept();
//接收字符流
InputStream inputStream = client_socket.getInputStream();
byte[] barr = new byte[1024];
//时时接收信息
while (true) {
//byte承载
int size = inputStream.read(barr);
if (size == -1){
// System.out.println("客户端下线了....");
client_socket.close();
break;
}
//读取数据
String data = new String(barr, 0, size);
System.out.println(client_socket.getInetAddress().getHostAddress() + ":" + data);
}
}
}
//启动服务并监听客户端请求
public void start() throws Exception {
//获取本机ip地址
String ip = getLocalHost();
//启动socket服务端
//创建socket 默认就是本机地址,只需要给端口号
socket = new ServerSocket(port);
System.out.println("服务器已启动,正在监测中....");
//注册服务
serverRegister(ip);
//监听socket处理客户端请求
clientHandler();
}
public static void main(String[] args) throws Exception {
//调用服务
new UserService().start();
}
}
4.客户端
package com.lbb.zoo.client;
import org.apache.zookeeper.*;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端
*/
public class UserClient implements Watcher {
//节点
private String node = "/userService";
//server_ip
private String server_ip;
//server_port
private int server_port;
//ZooKeeper
private ZooKeeper zooKeeper;
public void run() throws Exception {
//连接zookeeper
zooKeeper = new ZooKeeper("192.168.74.129:2181,192.168.74.130:2181,192.168.74.131:2181", 3000, null);
System.out.println("Zookeeper连接成功!");
//尝试获取服务信息
getServerInfo();
//监听节点变化
//参数 1.节点 2.监视器,出现变动时回调 3.模式:永久和临时
zooKeeper.addWatch(node, this, AddWatchMode.PERSISTENT);
}
//连接服务器,发送数据
public void sendMessage() throws IOException {
//接受用户输入
System.out.println("请输入要发送的信息,输入q退出:");
while (true) {
Scanner in = new Scanner(System.in);
String message = in.next();
if (message.equals("q")) {
System.out.println();
System.out.println("再见!!!");
System.exit(0);
}
//连接服务器
if (server_ip != null) {
Socket socket = new Socket();
socket.connect(new InetSocketAddress(server_ip, server_port));
//发送数据
OutputStream outputStream = socket.getOutputStream();
outputStream.write(message.getBytes());
socket.close();
System.out.println("发送成功!");
} else {
System.err.println("当前服务不可用....");
}
}
}
//获取服务信息
public void getServerInfo() {
try {
//获取节点数据
//参数 1.节点 2.监视对象,不需要就填false,需要就天wactcher 4.状态
byte[] data = zooKeeper.getData(node, false, null);
//读取信息
String[] infos = new String(data).split(":");
//更新服务器信息
server_ip = infos[0];
server_port = Integer.parseInt(infos[1]);
System.out.println("获取服务信息成功!");
} catch (KeeperException e) {
System.err.println("服务信息不存在! 等待服务上线........");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//wactcher接口的方法实现,处理事件
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeCreated){
getServerInfo();
System.out.println("服务上线了");
}else if (event.getType() == Event.EventType.NodeDataChanged){
getServerInfo();
System.out.println("服务更新了....");
}else if (event.getType() == Event.EventType.NodeDeleted){
server_ip = null;
System.out.println("服务下线了....");
}
}
public static void main(String[] args) throws Exception {
UserClient userClient = new UserClient();
//连接zookeeper,获取状态信息
userClient.run();
//发送数据
userClient.sendMessage();
}
}
5.打包服务端代码,下面步骤可忽略,仅为了测试客户端正确性, 为了在打包时附带其全部依赖,此处借助Spring的打包插件,在pom中添加以下内容:
<!-- 为了方便运行,我们要将packaging 改为jar-->
<groupId>com.lbb</groupId>
<artifactId>zookeeperdemo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 打包插件,该插件可以将jar包一起打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.5.6.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
注意:Spring-boot打包插件会自动获取项目中的主函数,必须保证主函数只有一个,所以需要暂时注释客户端的主函数,最后执行maven的package,得到jar包。
6.将jar上传至虚拟机并运行
在target里面会有打好的包zookeeperdemo.jar -> 点击idea下面terminal进入操作界面,进入target目录 -> 将zookeeperdemo.jar直接拖到terminal操作界面->输入下面命令执行jar文件:
java -jar zookeeperdemo.jar
若没有其他问题则客户端依然可以正常连接服务器发送消息;
7.将jar复制到虚拟机里,还是在terminal操作界面 -> 将zookeeperdemo.jar直接拖到terminal操作界面,进入target目录 -> 输入下面命令:
scp zookeeperdemo.jar root@192.168.74.129:/root
中间会让你输入yes和密码,都是不显示的打完直接回车即可。
8.在lunix系统根目录输入命令,执行jar包:
java -jar zookeeperdemo.jar
发布成功后客户端依然可以正常连接服务器发送消息;
以上便是使用Zookeeper实现服务注册和服务发现的具体步骤,在实际开发中,我们会将提供的服务部署为集群,这时可将集群中的各个服务信息作为子节点注册到指定节点下,客户端监听该节点变化,获取子节点列表从而获取到服务列表,还可以在此基础上加上负载均衡算法实现对服务列表的合理访问;