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

引言

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

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

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

背景

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

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

设计模式的选择

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

java 复制代码
@FeignClient(name = "xxx", url = "xxxx")
public interface UserClient {

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

}

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

java 复制代码
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类,实现以下方法
java 复制代码
public R invoke(final Object proxy, final Method method, final Object[] args) {}
  1. 通过Method获取Mapping
java 复制代码
Mapping annotation = method.getAnnotation(Mapping.class);
  1. 通过args构建请求参数 EasyRetryRequest
java 复制代码
EasyRetryRequest easyRetryRequest = new EasyRetryRequest(args);
  1. 发送消息
java 复制代码
NettyChannel.send(HttpMethod.valueOf(annotation.method().name()), annotation.path(), easyRetryRequest.toString());

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

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

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

java 复制代码
/**
 * 构建请求类型
 *
 * @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);
    }

}

实战应用

java 复制代码
     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的实现,这也是非常简便的。这简单而巧妙的设计大大提升了系统的可维护性和易用性。

相关推荐
宫水三叶的刷题日记24 分钟前
馋猫外卖:AI 让字节式试错没有天花板
后端
SomeB1oody1 小时前
【RustyML入门】7.4. 按需裁剪与模块化集成
开发语言·后端·机器学习·rust·教程
罗超驿1 小时前
SpringBoot 快速入门
java·spring boot·后端
卷无止境2 小时前
除了开发api,FastAPI其实也可以配合jinja2模板写页面
后端·python·fastapi
newerp2 小时前
Redis 操作与缓存策略
后端·程序员·go
newerp2 小时前
GORM ORM 基础
后端·程序员·go
小岛前端2 小时前
AI Skills 已经封神,但新的问题却越来越严重!
前端·后端·github
拖孩2 小时前
一个人 + AI 做的小程序,上线 15 天赚了 10 块 5
前端·后端·微信小程序
newerp2 小时前
CRUD 操作与预处理语句
后端·程序员·go
wei_shuo2 小时前
KES 数据同步与ETL实战:数据集成、转换与实时同步方案
后端