本章实现了引入注册中心对于客户端以及服务端服务交互的一个管理,commit的版本号为0286460,并在注册中心对于服务端服务暴露中引入了负载均衡策略。
注册中心
回顾我们第一章所提到的关于RPC框架的一个整体框图,为了在大量分布式的模块中进行各个模块的通信,我们需要引入一个注册中心实现对于各个模块之间的管理,来保证各个模块之间通信的可靠。
目前比较主流的注册中心有Nacos,Zookeeper以及Eureka,以下列举了几种主流注册中心的比较:
由于nacos易用,有稳定的控制台,且数据实时更新,因此本文选择的注册中心为nacos。nacos主要提供两个
作用,服务发现以及服务注册,因此我们编写一个工具类,实现服务注册以及服务发现的方法:
public class NacosUtils {
private static final String SERVER_ADDRESS = "127.0.0.1:8848";
private static final Logger logger = LoggerFactory.getLogger(NacosUtils.class);
private static final NamingService namingService;
static {
namingService = NacosUtils.getNamingService();
}
public static NamingService getNamingService(){
try{
return NacosFactory.createNamingService(SERVER_ADDRESS);
} catch (NacosException e) {
logger.error("连接到nacos失败:{}",e);
throw new RpcException(RpcError.CONNECT_REGISTRATION_FAILURE);
}
}
public static void register( String serviceName, InetSocketAddress inetSocketAddress) throws NacosException {
namingService.registerInstance(serviceName,inetSocketAddress.getHostName(),inetSocketAddress.getPort());
}
public static List<Instance> getAllInstance(String serviceName) throws NacosException {
return namingService.getAllInstances(serviceName);
}
}
服务端
在服务端我们需要的就是利用工具类将服务注册到注册中心,因此需要实现注册服务的接口:
public class NacosRegisterService implements RegisterService {
private static final Logger logger = LoggerFactory.getLogger(NacosRegisterService.class);
@Override
public void registry(String serviceName, InetSocketAddress inetSocketAddress) {
try {
NacosUtils.register(serviceName,inetSocketAddress);
} catch (NacosException e) {
logger.error("服务注册到注册中心时出现错误:{}",e);
throw new RpcException(RpcError.REGISTERED_SERVICE_FAILURE);
}
}
}
在服务端启动之前将我们需要的服务手动注册到注册中心以及本地服务缓存中:
@Override
public void publishService(List<Object> services) {
for(Object service : services){
serverPublisher.addService(service);
Class<?>[] interfaces = service.getClass().getInterfaces();
for (Class<?> anInterface : interfaces) {
registerService.registry(anInterface.getCanonicalName(),new InetSocketAddress(host,port));
}
}
start();
}
客户端
客户端需要的就是根据接口名字,从注册中心中拿到所需要的服务,以及对应提供服务的服务端inetSocketAddress,之后的通信过程就与前边无异:
public class NacosRegisterDiscovery implements RegisterDiscovery {
private final LoadBalance loadBalance;
private static final Logger logger = LoggerFactory.getLogger(NacosRegisterDiscovery.class);
public NacosRegisterDiscovery(LoadBalance loadBalance){
this.loadBalance = loadBalance;
}
@Override
public InetSocketAddress lookupService(String serviceName) {
try {
List<Instance> allInstances = NacosUtils.getAllInstance(serviceName);
Instance instance = loadBalance.getInstance(allInstances);
return new InetSocketAddress(instance.getIp(),instance.getPort());
} catch (NacosException e) {
logger.error("获取服务注册中心的服务失败:{}",e);
throw new RpcException(RpcError.SERVICE_NOT_FOUND);
}
}
}
负载均衡策略
在获取注册到注册中心的服务时,为了保证在大量请求的情况下服务器不会因为多次请求同一台服务器导致宕机,因此我们需要引入负载均衡策略:
public interface LoadBalance {
<T> T getInstance(List<T> list);
}
常见的负载均衡策略有轮询,加权,随机,哈希等,本文采用的是简单轮询负载均衡算法,如下:
public class RoundRobinLoadBalance implements LoadBalance{
private AtomicInteger atomicInteger = new AtomicInteger(0);
private final int getAndIncrement(){
int current;
int next;
do{
current = this.atomicInteger.get();
next = current >= Integer.MAX_VALUE ? 0 : current + 1;
}while (!this.atomicInteger.compareAndSet(current,next));
return next;
}
@Override
public <T> T getInstance(List<T> list) {
int index = getAndIncrement() % list.size();
return list.get(index);
}
}
测试
服务端
[main] INFO com.alibaba.nacos.client.naming - initializer namespace from System Property :null
[main] INFO com.alibaba.nacos.client.utils.ParamUtil - [settings] [req-serv] nacos-server port:8848
[main] INFO com.alibaba.nacos.client.utils.ParamUtil - [settings] [http-client] connect timeout:1000
[main] INFO com.alibaba.nacos.client.utils.ParamUtil - NACOS_CLIENT_VERSION: ${project.version}
[main] INFO com.alibaba.nacos.client.utils.ParamUtil - PER_TASK_CONFIG_SIZE: 3000.0
[main] INFO com.alibaba.nacos.client.naming - [BEAT] adding beat: BeatInfo{port=9999, ip='activate.navicat.com', weight=1.0, serviceName='DEFAULT_GROUP@@cn.fzzfrjf.entity.HelloService', cluster='DEFAULT', metadata={}, scheduled=false, period=5000, stopped=false} to beat map.
[main] INFO com.alibaba.nacos.client.naming - [REGISTER-SERVICE] public registering service DEFAULT_GROUP@@cn.fzzfrjf.entity.HelloService with instance: Instance{instanceId='null', ip='activate.navicat.com', port=9999, weight=1.0, healthy=true, enabled=true, ephemeral=true, clusterName='DEFAULT', serviceName='null', metadata={}}
[main] INFO com.alibaba.nacos.client.identify.CredentialWatcher - null No credential found
[main] INFO cn.fzzfrjf.core.DefaultServerPublisher - 向接口:interface cn.fzzfrjf.entity.ByeService注册服务:cn.fzzfrjf.service.ByeServiceImpl
[main] INFO com.alibaba.nacos.client.naming - [BEAT] adding beat: BeatInfo{port=9999, ip='activate.navicat.com', weight=1.0, serviceName='DEFAULT_GROUP@@cn.fzzfrjf.entity.ByeService', cluster='DEFAULT', metadata={}, scheduled=false, period=5000, stopped=false} to beat map.
[main] INFO com.alibaba.nacos.client.naming - [REGISTER-SERVICE] public registering service DEFAULT_GROUP@@cn.fzzfrjf.entity.ByeService with instance: Instance{instanceId='null', ip='activate.navicat.com', port=9999, weight=1.0, healthy=true, enabled=true, ephemeral=true, clusterName='DEFAULT', serviceName='null', metadata={}}
[main] WARN io.netty.bootstrap.ServerBootstrap - Unknown channel option 'SO_KEEPALIVE' for channel '[id: 0xa5aaf6b3]'
[nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xa5aaf6b3] REGISTERED
[nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xa5aaf6b3] BIND: /127.0.0.1:9999
[nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xa5aaf6b3, L:/127.0.0.1:9999] ACTIVE
[nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xa5aaf6b3, L:/127.0.0.1:9999] READ: [id: 0xea264de9, L:/127.0.0.1:9999 - R:/127.0.0.1:53190]
[nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xa5aaf6b3, L:/127.0.0.1:9999] READ COMPLETE
[nioEventLoopGroup-3-1] INFO cn.fzzfrjf.core.NettyServerHandler - 服务器接收到请求:cn.fzzfrjf.entity.RpcRequest@3bb68152
[nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xa5aaf6b3, L:/127.0.0.1:9999] READ: [id: 0xa0b5cc17, L:/127.0.0.1:9999 - R:/127.0.0.1:53191]
[nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0xa5aaf6b3, L:/127.0.0.1:9999] READ COMPLETE
[nioEventLoopGroup-3-2] INFO cn.fzzfrjf.core.NettyServerHandler - 服务器接收到请求:cn.fzzfrjf.entity.RpcRequest@1e724045
客户端
[main] WARN com.alibaba.nacos.client.utils.LogUtils - Load Log4j Configuration of Nacos fail, message: org/apache/logging/log4j/LogManager
[main] INFO com.alibaba.nacos.client.naming - initializer namespace from System Property :null
[main] INFO com.alibaba.nacos.client.naming - initializer namespace from System Environment :null
[main] INFO com.alibaba.nacos.client.naming - initializer namespace from System Property :null
[main] INFO com.alibaba.nacos.client.utils.ParamUtil - [settings] [req-serv] nacos-server port:8848
[main] INFO com.alibaba.nacos.client.utils.ParamUtil - [settings] [http-client] connect timeout:1000
[main] INFO com.alibaba.nacos.client.utils.ParamUtil - NACOS_CLIENT_VERSION: ${project.version}
[main] INFO com.alibaba.nacos.client.utils.ParamUtil - PER_TASK_CONFIG_SIZE: 3000.0
[main] INFO com.alibaba.nacos.client.identify.CredentialWatcher - null No credential found
[main] INFO com.alibaba.nacos.client.naming - new ips(1) service: DEFAULT_GROUP@@cn.fzzfrjf.entity.HelloService -> [{"instanceId":"activate.navicat.com#9999#DEFAULT#DEFAULT_GROUP@@cn.fzzfrjf.entity.HelloService","ip":"activate.navicat.com","port":9999,"weight":1.0,"healthy":true,"enabled":true,"ephemeral":true,"clusterName":"DEFAULT","serviceName":"DEFAULT_GROUP@@cn.fzzfrjf.entity.HelloService","metadata":{},"instanceHeartBeatInterval":5000,"instanceIdGenerator":"simple","ipDeleteTimeout":30000,"instanceHeartBeatTimeOut":15000}]
[main] INFO com.alibaba.nacos.client.naming - current ips:(1) service: DEFAULT_GROUP@@cn.fzzfrjf.entity.HelloService -> [{"instanceId":"activate.navicat.com#9999#DEFAULT#DEFAULT_GROUP@@cn.fzzfrjf.entity.HelloService","ip":"activate.navicat.com","port":9999,"weight":1.0,"healthy":true,"enabled":true,"ephemeral":true,"clusterName":"DEFAULT","serviceName":"DEFAULT_GROUP@@cn.fzzfrjf.entity.HelloService","metadata":{},"instanceHeartBeatInterval":5000,"instanceIdGenerator":"simple","ipDeleteTimeout":30000,"instanceHeartBeatTimeOut":15000}]
[nioEventLoopGroup-2-1] INFO cn.fzzfrjf.core.ChannelProvider - 获取channel连接成功,连接到服务器activate.navicat.com:9999
[nioEventLoopGroup-2-1] INFO cn.fzzfrjf.core.NettyClient - 成功向服务器发送请求:cn.fzzfrjf.entity.RpcRequest@358615bf
[nioEventLoopGroup-2-1] INFO cn.fzzfrjf.core.NettyClientHandler - 客户端获取到服务端返回的信息:RpcResponse(code=200, requestId=1c4437f4-44cd-4605-bea9-ddafd8cb7622, data=这是id为:2发送的:This is NettyClient!)
[main] INFO com.alibaba.nacos.client.naming - new ips(1) service: DEFAULT_GROUP@@cn.fzzfrjf.entity.ByeService -> [{"instanceId":"activate.navicat.com#9999#DEFAULT#DEFAULT_GROUP@@cn.fzzfrjf.entity.ByeService","ip":"activate.navicat.com","port":9999,"weight":1.0,"healthy":true,"enabled":true,"ephemeral":true,"clusterName":"DEFAULT","serviceName":"DEFAULT_GROUP@@cn.fzzfrjf.entity.ByeService","metadata":{},"instanceHeartBeatInterval":5000,"instanceIdGenerator":"simple","ipDeleteTimeout":30000,"instanceHeartBeatTimeOut":15000}]
[main] INFO com.alibaba.nacos.client.naming - current ips:(1) service: DEFAULT_GROUP@@cn.fzzfrjf.entity.ByeService -> [{"instanceId":"activate.navicat.com#9999#DEFAULT#DEFAULT_GROUP@@cn.fzzfrjf.entity.ByeService","ip":"activate.navicat.com","port":9999,"weight":1.0,"healthy":true,"enabled":true,"ephemeral":true,"clusterName":"DEFAULT","serviceName":"DEFAULT_GROUP@@cn.fzzfrjf.entity.ByeService","metadata":{},"instanceHeartBeatInterval":5000,"instanceIdGenerator":"simple","ipDeleteTimeout":30000,"instanceHeartBeatTimeOut":15000}]
[nioEventLoopGroup-2-2] INFO cn.fzzfrjf.core.ChannelProvider - 获取channel连接成功,连接到服务器activate.navicat.com:9999
[nioEventLoopGroup-2-2] INFO cn.fzzfrjf.core.NettyClient - 成功向服务器发送请求:cn.fzzfrjf.entity.RpcRequest@76e83d7e
[nioEventLoopGroup-2-2] INFO cn.fzzfrjf.core.NettyClientHandler - 客户端获取到服务端返回的信息:RpcResponse(code=200, requestId=52f2a26a-5d96-4461-8af6-66f89b327eb4, data=(This is NettyClient!),bye!)
这是id为:2发送的:This is NettyClient!
(This is NettyClient!),bye!
最终完成了通过注册中心来实现服务的注册以及发现功能。
未完。。。