RPC Version 5
引入了 zookeeper 作为服务注册中心,修改掉以前本地使用Map存储服务的方式。
下载zookeeper
需要修改 conf 下的 zoo_sample.cfg,复制一份改名为zoo.cfg,修改里面的 dataDir 路径,改为一个真实存在的路径,如果是windows复制的全路径名 要把 " \ " 改为 " / " ,然后保存。
接下来在项目引入 Curator 依赖 和 Log4j 依赖 :
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
还是需要先了解一下zookeeper 和 使用 curator 搭建zookeeper客户端:
Curator 学习
zookeeper 学习
漫画版了解zookeeper
开始新增一个注册中心接口:
注册方法 和 服务发现
public interface ServiceRegister {
void register(String serviceName, InetSocketAddress serverAddress);
InetSocketAddress serviceDiscovery(String serviceName);
}
实现zookeeper注册中心:
public class ZKServiceRegister implements ServiceRegister{
// curator 提供的zookeeper客户端
private CuratorFramework client;
// zookeeper根路径节点
private static final String ROOT_PATH = "MyRPC";
// zookeeper客户端初始化,与zookeeper服务端建立连接
public ZKServiceRegister(){
// 指数时间重试
RetryPolicy policy = new ExponentialBackoffRetry(1000, 3);
// zookeeper的地址固定,不管是服务提供者还是,消费者都要与之建立连接
// sessionTimeoutMs 与 zoo.cfg中的tickTime 有关系,
// zk还会根据minSessionTimeout与maxSessionTimeout两个参数重新调整最后的超时值。默认分别为tickTime 的2倍和20倍
// 使用心跳监听状态
this.client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181")
.sessionTimeoutMs(40000).retryPolicy(policy).namespace(ROOT_PATH).build();
this.client.start();
System.out.println("zookeeper 连接成功!");
}
@Override
public void register(String serviceName, InetSocketAddress serverAddress) {
try {
// serviceName创建成永久节点,服务提供者下线时,不删服务名,只删地址
if(client.checkExists().forPath("/" + serviceName) == null){
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/" + serviceName);
}
// 路径地址,一个 / 代表一个节点
String path = "/" + serviceName + "/" + getServiceAddress(serverAddress);
// 临时节点,服务器下线就删除节点
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path);
} catch (Exception e) {
System.out.println("此服务已存在");
}
}
// 根据服务名返回地址
@Override
public InetSocketAddress serviceDiscovery(String serviceName) {
List<String> strings = null;
try {
strings = client.getChildren().forPath("/" + serviceName);
// 默认用第一个
String s = strings.get(0);
return parseAddress(s);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 地址转字符串
private String getServiceAddress(InetSocketAddress serverAddress){
return serverAddress.getHostName() + ":" + serverAddress.getPort();
}
// 字符串解析为地址
private InetSocketAddress parseAddress(String address){
String[] res = address.split(":");
return new InetSocketAddress(res[0],Integer.parseInt(res[1]));
}
}
同时需要改一下 client的构造方法,使其在 sendRequest() 中注册服务:
NettyClient:
// 新增属性
private ServiceRegister serviceRegister;
// 初始化注册中心
public NettyRPCClient(){
this.serviceRegister = new ZKServiceRegister();
}
sendRequest 方法添加:
// 从注册中心获取host port
InetSocketAddress address = serviceRegister.serviceDiscovery(request.getInterfaceName());
host = address.getHostName();
port = address.getPort();
SimpleRPCClient 同样方式修改。
需要修改一下服务提供方法 ServiceProvider:
public class ServiceProvider {
private Map<String, Object> interfaceProvider;
private ServiceRegister serviceRegister;
private String host;
private int port;
public ServiceProvider(String host,int port) {
this.host = host;
this.port = port;
this.interfaceProvider = new HashMap<>();
this.serviceRegister = new ZKServiceRegister();
}
public Map<String, Object> getProvide() {
return this.interfaceProvider;
}
public void provideServiceInterface(Object service) {
Class<?>[] interfaces = service.getClass().getInterfaces();
for (Class clazz : interfaces) {
//本机的映射表map
interfaceProvider.put(clazz.getName(), service);
// 在注册中心注册服务
serviceRegister.register(clazz.getName(),new InetSocketAddress(host,port));
}
}
public Object getService(String interfaceName) {
return interfaceProvider.get(interfaceName);
}
}
大功告成!
下篇使用负载均衡。