手把手实现RPC框架--简易版Dubbo构造(十一)服务注册中心Nacos

本节commit地址: 8136649(由于引入Nacos,项目结构和代码调整较大,头莫被搞昏了,但理解起来简单)

为什么需要Nacos?

我们现在的RPC框架其实只有一个服务提供者,客户端也是通过固定的一个服务端地址进行访问的,这会存在极大的隐患,如果这个服务提供者挂了或者换了地址,那客户端就没法访问了。
在分布式架构中,有一个重要的组件,就是服务注册中心,它用于保存多个服务提供者的信息,每个服务提供者在启动时都需要向注册中心注册自己所拥有的服务。这样客户端在发起远程调用的时候,就可以直接向注册中心请求服务提供者的信息,如果拿来的这个挂了,还可以重新请求,并且在这种情况下可以很方便地实现负载均衡。
常见的注册中心有Eureka、Zookeeper和Nacos。

安装使用Nacos

见本人另一篇:Nacos部署详细步骤(注意下载1.3.0版本)

引入nacos-client依赖

<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.3.0</version>
</dependency>

       首先把整个逻辑先顺一遍,引入Nacos服务注册中心之后,启动服务端,服务端会把服务名(即接口名)和服务端地址注册到Nacos,然后启动客户端,客户端发出调用请求时,会向Nacos请求一个可以提供对应服务的服务端地址,拿到地址后就可以和服务端进行通信了。其次修正一个概念,在前面服务端本地保存的服务类名是叫ServiceRegistry,但现在引入了服务注册中心,为了保证命名与传达的意思保持一致,所以现在将服务端的本地服务类名改为ServiceProvider,而ServiceRegistry则作为Nacos服务注册中心的远程注册表的名字。

RPC框架思路

实现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<Instance> 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个。

服务端注册服务到Nacos

修改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 <T> void publishService(Object service, Class<T> 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<Object> result = new AtomicReference<>(null);
        try {
            //从Nacos获取提供对应服务的服务端地址
            InetSocketAddress inetSocketAddress = serviceRegistry.lookupService(rpcRequest.getInterfaceName());
            //创建Netty通道连接
            Channel channel = ChannelProvider.get(inetSocketAddress, serializer);
……

测试

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……

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
NacosDubbo都是阿里巴巴开源的项目,Nacos是一款基于云原生架构的动态服务发现、配置管理和服务管理平台,而Dubbo是一款高性能的分布式服务框架。 在使用NacosDubbo进行服务调用时,需要使用Nacos提供的服务发现功能来获取可用的Dubbo服务,然后使用Dubbo提供的RPC框架进行远程调用。 下面是一个使用NacosDubbo进行服务调用的客户端实现示例: 1. 添加依赖 在项目的pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>${nacos.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>${dubbo.version}</version> </dependency> ``` 其中,${nacos.version}和${dubbo.version}需要根据您使用的版本进行替换。 2. 配置Nacos客户端 在使用Nacos之前,需要先配置Nacos客户端。可以在application.properties文件中添加以下配置: ```properties # Nacos配置 spring.cloud.nacos.config.server-addr=localhost:8848 spring.cloud.nacos.config.namespace= spring.cloud.nacos.config.file-extension=properties # Dubbo配置 dubbo.registry.address=nacos://localhost:8848 ``` 其中,spring.cloud.nacos.config.server-addr为Nacos服务器地址,dubbo.registry.address为Dubbo注册中心地址。 3. 获取Dubbo服务 使用Nacos提供的服务发现功能,获取可用的Dubbo服务。可以在代码中添加以下方法: ```java public List<Invoker<?>> getDubboService(String serviceName) throws NacosException { // 创建Nacos服务发现客户端 NamingService namingService = NacosFactory.createNamingService(nacosProperties.getConfigServerAddr()); // 获取可用的Dubbo服务 List<Instance> instances = namingService.getAllInstances(serviceName); if (instances == null || instances.isEmpty()) { throw new RuntimeException("No available Dubbo service"); } // 将Dubbo服务转换为Invoker List<Invoker<?>> invokers = new ArrayList<>(); for (Instance instance : instances) { URL url = new URL("dubbo", instance.getIp(), instance.getPort(), serviceName); Invoker<?> invoker = new DubboInvoker<Object>(Object.class, url, new RpcClientWrapper()); invokers.add(invoker); } return invokers; } ``` 其中,serviceName为Dubbo服务名称。 4. 远程调用Dubbo服务 获取到可用的Dubbo服务之后,就可以使用Dubbo提供的RPC框架进行远程调用。可以在代码中添加以下方法: ```java public Object invokeDubboService(List<Invoker<?>> invokers, String methodName, Object... args) throws RpcException { // 创建Dubbo调用上下文 RpcContext rpcContext = RpcContext.getContext(); // 随机选择一个Dubbo服务 Invoker<?> invoker = invokers.get(new Random().nextInt(invokers.size())); // 设置Dubbo调用上下文 rpcContext.setInvoker(invoker); rpcContext.setMethodName(methodName); rpcContext.setArguments(args); // 远程调用Dubbo服务 Result result = invoker.invoke(new RpcInvocation(methodName, new Class<?>[0], args)); if (result.hasException()) { throw result.getException(); } return result.getValue(); } ``` 其中,invokers为获取到的Dubbo服务列表,methodName为Dubbo服务方法名,args为Dubbo服务方法参数。 使用以上方法,就可以在NacosDubbo的帮助下,轻松实现服务调用客户端。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

”PANDA

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值