本节commit地址: 8136649(由于引入Nacos,项目结构和代码调整较大,头莫被搞昏了,但理解起来简单)
为什么需要Nacos?
我们现在的RPC框架其实只有一个服务提供者,客户端也是通过固定的一个服务端地址进行访问的,这会存在极大的隐患,如果这个服务提供者挂了或者换了地址,那客户端就没法访问了。
在分布式架构中,有一个重要的组件,就是服务注册中心,它用于保存多个服务提供者的信息,每个服务提供者在启动时都需要向注册中心注册自己所拥有的服务。这样客户端在发起远程调用的时候,就可以直接向注册中心请求服务提供者的信息,如果拿来的这个挂了,还可以重新请求,并且在这种情况下可以很方便地实现负载均衡。
常见的注册中心有Eureka、Zookeeper和Nacos。
见本人另一篇:Nacos部署详细步骤(注意下载1.3.0版本)
com.alibaba.nacos
nacos-client
1.3.0
首先把整个逻辑先顺一遍,引入Nacos服务注册中心之后,启动服务端,服务端会把服务名(即接口名)和服务端地址注册到Nacos,然后启动客户端,客户端发出调用请求时,会向Nacos请求一个可以提供对应服务的服务端地址,拿到地址后就可以和服务端进行通信了。其次修正一个概念,在前面服务端本地保存的服务类名是叫ServiceRegistry,但现在引入了服务注册中心,为了保证命名与传达的意思保持一致,所以现在将服务端的本地服务类名改为ServiceProvider,而ServiceRegistry则作为Nacos服务注册中心的远程注册表的名字。
首先定义ServiceRegistry接口:
public interface ServiceRegistry {
/**
* @description 将一个服务注册到注册表
* @param [ServiceName, inetSocketAddress] 服务名称,提供服务的地址
* @return [void]
* @date [2021-03-13 14:44]
*/
void register(String serviceName, InetSocketAddress inetSocketAddress);
/**
* @description 根据服务名查找服务端地址
* @param [serviceName]
* @return [java.net.InetSocketAddress] 服务端地址
* @date [2021-03-13 14:45]
*/
InetSocketAddress lookupService(String serviceName);
}
实现Nacos服务注册中心的实现类:NacosServiceRegistry,我们也可以使用ZooKeeper作为注册中心,实现接口就可以
public class NacosServiceRegistry implements ServiceRegistry{
private static final Logger logger = LoggerFactory.getLogger(NacosServiceRegistry.class);
private static final String SERVER_ADDR = "127.0.0.1:8848";
private static final NamingService namingService;
static {
try {
//连接Nacos创建命名服务
namingService = NamingFactory.createNamingService(SERVER_ADDR);
}catch (NacosException e){
logger.error("连接Nacos时有错误发生:" + e);
throw new RpcException(RpcError.FAILED_TO_CONNECT_TO_SERVICE_REGISTRY);
}
}
@Override
public void register(String serviceName, InetSocketAddress inetSocketAddress) {
try {
//向Nacos注册服务
namingService.registerInstance(serviceName, inetSocketAddress.getHostName(), inetSocketAddress.getPort());
}catch (NacosException e) {
logger.error("注册服务时有错误发生" + e);
throw new RpcException(RpcError.REGISTER_SERVICE_FAILED);
}
}
@Override
public InetSocketAddress lookupService(String serviceName) {
try {
//利用列表获取某个服务的所有提供者
List instances = namingService.getAllInstances(serviceName);
Instance instance = instances.get(0);
return new InetSocketAddress(instance.getIp(), instance.getPort());
}catch (NacosException e) {
logger.error("获取服务时有错误发生" + e);
}
return null;
}
}
可以发现Nacos的使用还是比较简单,在lookupService()方法中,通过getAllInstance()获取到某个服务的所有提供者,然后需要选择一个,这里就涉及到负载均衡策略,后面再讲,这里我们先选择第0个。
修改RpcServer接口,新增一个方法publishService(),用于向Nacos注册服务,接着实现这个方法就可以了,以NettyServer的实现为例,首先在构造方法中创建一个ServiceRegistry:
public NettyServer(String host, int port) {
this.host = host;
this.port = port;
serviceRegistry = new NacosServiceRegistry();
serviceProvider = new ServiceProviderImpl();
}
然后实现publishService()方法,这里的实现是注册完一个服务后直接调用start()方法,这是个不太好的实现……导致一个服务端只能注册一个服务,之后会进行调整。
/**
* @description 将服务保存到服务端本地注册表,同时注册到Nacos注册中心
*/
@Override
public void publishService(Object service, Class serviceClass) {
if (serializer == null) {
logger.error("未设置序列化器");
throw new RpcException(RpcError.SERIALIZER_NOT_FOUND);
}
serviceProvider.addServiceProvider(service);
serviceRegistry.register(serviceClass.getCanonicalName(), new InetSocketAddress(host, port));
start();
}
以NettyClient为例,之前创建NettyClient时,需要传入host和port,现在这个host和port是从Nacos服务注册中心中获取的,sendRequest修改如下(重点在最后两行代码):
public Object sendRequest(RpcRequest rpcRequest) {
if (serializer == null) {
logger.error("未设置序列化器");
throw new RpcException(RpcError.SERIALIZER_NOT_FOUND);
}
//保证自定义实体类变量的原子性和共享性的线程安全,此处应用于rpcResponse
AtomicReference
NettyTestClient如下,构造RpcClient时不再需要传入地址和端口:
public class NettyTestClient {
public static void main(String[] args) {
RpcClient client = new NettyClient();
client.setSerializer(new ProtostuffSerializer());
RpcClientProxy rpcClientProxy = new RpcClientProxy(client);
HelloService helloService = rpcClientProxy.getProxy(HelloService.class);
HelloObject object = new HelloObject(12, "this is netty style");
String res = helloService.hello(object);
System.out.println(res);
}
}
NettyTestServer如下,这里把之前的start()写在了publishService中,实际应当分离,否则只能注册一个服务:
public class NettyTestServer {
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
NettyServer server = new NettyServer("127.0.0.1", 9999);
server.setSerializer(new ProtostuffSerializer());
server.publishService(helloService, HelloService.class);
}
}
startup.cmd启动Nacos,然后启动服务端和客户端,会得到和之前一样的结果。
这里如果通过设置不同的端口号,启动两个服务端的话,会看到即使客户端多次调用,也只是由同一个服务端提供服务,这是因为在NacosServiceRegistry中,我们直接选择了服务列表的第0个,后面实现负载均衡的时候会作出修改。
本节over……