Netty 如何做到像Feign一样简单的发送消息?

1,173 阅读3分钟

引言

众所周知Netty是一个高性能、异步事件驱动的网络应用程序框架,它基于Java NIO提供了非阻塞的网络通信。Netty的设计目标是处理高并发连接和大规模分布式系统,适用于构建各种网络应用。

为什么选择使用Netty作为通信框架呢?

EasyRetry作为高性能的分布式重试平台,从设计之初就充分考虑了重试数据上报的集中性和异步特点。为了满足这些需求,我们选择了Netty作为客户端与服务端之间通信的框架。Netty提供了高效的异步通信能力,使得重试数据的上报可以在绝大部分场景下以异步方式进行,从而更好地满足平台的高性能要求。

背景

在开发EasyRetry的过程中随着需要通信的节点越来越多,发现通过相似代码逐渐增多从而导致了代码的可读性差、维护困难等问题,比如下面的代码

image.png image.png

作为有追求的程序员,我绝不容忍这种情况💪。各种设计模式和六大原则在脑海中一一检索。 Feign在平时工作中频繁使用,它简化了定义和调用RESTful服务,使微服务通信更加便捷。思及此,决定实现类似Feign简单的Netty通信。

设计模式的选择

首先我们看下FeignClient的一个简单使用,通过用户名查询用户的信息.

@FeignClient(name = "xxx", url = "xxxx")
public interface UserClient {

    @GetMapping({"/user/info"})
    Result<UserDTO> getUserInfo(@RequestParam("username") String username);

}

因为NettyClient和FeignClient都是接口,无法直接实例化。所以需要使用JDK动态代理来创建它们的代理类。在本实现中,我们仿照FeignClient的思路,也实现了一个NettyClient。

public interface NettyClient {

    @Mapping(method = RequestMethod.GET, path = CONFIG)
    Result getConfig(Integer version);

    @Mapping(method = RequestMethod.GET, path = BEAT)
    Result beat(String mark);

    @Mapping(method = RequestMethod.POST, path = BATCH_REPORT)
    NettyResult reportRetryInfo(List<RetryTaskDTO> list);

}

代码实现

实现InvocationHandler类

  1. 通过实现InvocationHandler类,实现以下方法
public R invoke(final Object proxy, final Method method, final Object[] args) {}
  1. 通过Method获取Mapping
Mapping annotation = method.getAnnotation(Mapping.class);
  1. 通过args构建请求参数 EasyRetryRequest
EasyRetryRequest easyRetryRequest = new EasyRetryRequest(args);
  1. 发送消息
NettyChannel.send(HttpMethod.valueOf(annotation.method().name()), annotation.path(), easyRetryRequest.toString());

总结: 将公共的代码在代理类中进行实现,外部只需要传入动态的参数即可。完整的代码

使用建造者模式生成代理类

通过建造者设计模式创建接口的代理类

/**
 * 构建请求类型
 *
 * @author: www.byteblogs.com
 * @date : 2023-05-12 16:47
 * @since 1.3.0
 */
public class RequestBuilder<T, R> {

    private Class<T> clintInterface;
    
    ......

    public T build() {
        if (Objects.isNull(clintInterface)) {
            throw new EasyRetryClientException("clintInterface cannot be null");
        }

        try {
            clintInterface = (Class<T>) Class.forName(clintInterface.getName());
        } catch (Exception e) {
            throw new EasyRetryClientException("class not found exception to: [{}]", clintInterface.getName());
        }

        ClientInvokeHandler<R> clientInvokeHandler = new ClientInvokeHandler<>(async, timeout, unit, callback);

        return (T) Proxy.newProxyInstance(clintInterface.getClassLoader(),
            new Class[]{clintInterface}, clientInvokeHandler);
    }

}

实战应用

     NettyClient client = RequestBuilder.<NettyClient, NettyResult>newBuilder()
                .client(NettyClient.class)
                .callback(nettyResult -> {
                    if (Objects.isNull(nettyResult.getData())) {
                        LogUtils.error(log, "获取配置结果为null");
                        return;
                    }

                    GroupVersionCache.configDTO = JsonUtil.parseObject(nettyResult.getData().toString(), ConfigDTO.class);
                })
                .build();
            client.getConfig(0);

总结:通过上述设计,我们能够轻松地通过NettyClient进行接口调用。如果你希望与Spring结合使用,可以参考Feign的实现,这也是非常简便的。这简单而巧妙的设计大大提升了系统的可维护性和易用性。