分布式服务框架-底层通信(终)

239 阅读3分钟

这是我参与8月更文挑战的第24天,活动详情查看: 8月更文挑战

本文使用源码地址:simple-rpc

分布式服务框架-底层通信(3)中我们解决了Netty客户端调用的三个问题:

  1. 选择合适的序列化协议,解决Netty传输过程中出现的半包/粘包问题。
  2. 发挥长连接的优势,对Netty的Channel通道进行复用。
  3. Netty是异步框架,客户端发起服务调用后同步等待获取调用结果。

接下来我们要真正发起调用了。我们在服务引入时已经创建了服务调用方的动态代理类,通过实现InvocationHandler接口,将复杂的远程调用通信逻辑封装在invoke(Object proxy, Method method, Object[] args)方法中。

动态代理

@Override
public Object invoke(Object proxy, Method method, Object[] args) {

    // 服务接口名称
    String serviceItfName = targetItf.getName();
    IRegisterCenter registerCenter = RegisterCenterImpl.getInstance();
    // 获取服务列表
    List<Provider> providerList = registerCenter.getServiceMetadata().get(serviceItfName);

    if (CollectionUtils.isEmpty(providerList)) {
        log.debug("can't find provider service={}, maybe need to reconnect zk server", serviceItfName);
    } else {
        // 根据负载均衡策略获取对应的服务提供者
        LoadBalanceStrategy loadBalance = LoadBalanceStrategyEngine.getLoadBalanceStrategy(this.loadBalanceStrategy);
        Provider provider = loadBalance.select(providerList);

        Provider providerCopy = Provider.copy(provider);
        providerCopy.setServiceMethod(method);
        providerCopy.setServiceItf(targetItf);

        Request request = Request.builder().args(args)
                .invokeMethodName(method.getName())
                .invokeTimeout(timeout)
                .provider(providerCopy)
                .uniqueKey(UUID.randomUUID().toString() + "-" + Thread.currentThread().getId())
                .build();
        try {
            InetSocketAddress socketAddress = new InetSocketAddress(providerCopy.getServerIp(), providerCopy.getServerPort());

            // 发起异步调用请求
            Future<Response> responseFuture = executorService.submit(RevokerServiceCallable.of(socketAddress, request));
            Response response = responseFuture.get(timeout, TimeUnit.MILLISECONDS);
            if (response != null) {
                return response.getResult();
            }
        } catch (Exception e) {
            log.error("RevokerProxyBeanFactory invoke error, request={}", request, e);
            throw new SRpcException("RevokerProxyBeanFactory invoke error", e);
        }
    }
    return null;
}

public Object getProxy() {
    return Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(), 
            new Class[]{targetItf}, this);
}

先说一下getProxy()方法,通过Proxy.newProxyInstance()获取代理对象。接着我们看一下invoke()到底做了什么:

  1. 从服务注册中心获取服务提供者列表;
  2. 实现软负载(后面会说),根据服务调用端配置的负载策略参数loadBalanceStrategy,获取具体的负载类,选择合适的服务提供者;
  3. 组装服务调用请求Request
  4. 异步提交请求RevokerServiceCallable
  5. 通过阻塞队列机制同步等待请求结果。

可以看出来,除了第4步,其他的都是为了服务调用做准备的。那就很明白了,客户端发起服务调用的核心逻辑一定在RevokerServiceCallable类中。

客户端异步服务调用

RevokerServiceCallable类实现Callable<Response>,实现了call()方法。如果有不熟悉的可以自己复习一下。

@Override
public Response call() {
    
    RevokerResponseHolder.initResponseData(request.getUniqueKey());
    ArrayBlockingQueue<Channel> blockingQueue = NettyChannelPoolFactory.getInstance().acquire(inetSocketAddress);
    try {
        if (channel == null) {
            channel = blockingQueue.poll(request.getInvokeTimeout(), TimeUnit.MILLISECONDS);
        }
        if (channel == null) {
            log.error("can't find channel to resolve this request");
            throw new SRpcException("can't find channel to resolve this request");
        } else {
            while (!channel.isOpen() || !channel.isActive() || !channel.isWritable()) {
                log.warn("retry get new channel");
                channel = blockingQueue.poll(request.getInvokeTimeout(), TimeUnit.MILLISECONDS);
                if (channel == null) {
                    channel = NettyChannelPoolFactory.getInstance().registerChannel(inetSocketAddress);
                }
            }
            ChannelFuture channelFuture = channel.writeAndFlush(request);
            channelFuture.syncUninterruptibly();
            long invokeTimeout = request.getInvokeTimeout();
            return RevokerResponseHolder.getValue(request.getUniqueKey(), invokeTimeout);
        }
    } catch (Exception e) {
        log.error("service invoke error", e);
        throw new SRpcException("service invoke error", e);
    } finally {
        NettyChannelPoolFactory.getInstance().release(blockingQueue, channel, inetSocketAddress);
    }
}

整个过程步骤如下:

  1. 初始化返回结果容器,将本次调用的唯一标识作为Key存入返回结果的Map
  2. 根据本地调用服务提供者地址获取对应的Netty通道channel队列;
  3. 从队列中获取本次调用的Netty通道channel,若队列中没有可用的Channel,则重新注册一个Channel
  4. 将服务请求数据对象通过某种序列化协议编码成字节数组,通过Channel发送到服务端;
  5. 同步等待服务端返回调用结果;
  6. 最后在本次调用完毕后,将Netty的通道Channel重新释放到队列中,以便下次调用复用。

总结

至此,分布式服务框架底层通信篇也就结束了,现在客户端与服务端已经能够完成通信了,让我们来回顾一下:

  1. 第一篇我们介绍了Netty的基本使用方法和TCP半包粘包问题;

  2. 第二篇介绍了服务端启动的代码实现和服务端限流方案实现思路;

  3. 第三篇介绍了客户端如何完成Netty Channel复用和如何完成客户端发起服务调用后同步等待获取调用结果;

  4. 最终篇我们完成了客户端动态代理和异步请求。