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

相关推荐
涡能增压发动积13 小时前
同样的代码循环 10次正常 循环 100次就抛异常?自定义 Comparator 的 bug 让我丢尽颜面
后端
Wenweno0o13 小时前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang
swg32132113 小时前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
tyung13 小时前
一个 main.go 搞定协作白板:你画一笔,全世界都看见
后端·go
gelald13 小时前
SpringBoot - 自动配置原理
java·spring boot·后端
殷紫川13 小时前
深入拆解 Java 内存模型:从原子性、可见性到有序性,彻底搞懂 happen-before 规则
java·后端
元宝骑士13 小时前
FIND_IN_SET使用指南:场景、优缺点与MySQL优化策略
后端·mysql
用户319523703477113 小时前
记一次 PostgreSQL WAL 日志撑爆磁盘的排查
后端
nghxni13 小时前
LightESB PlatformHttp v3.0.0:JSONPath 订单转换 HTTP 路由实战
后端
武子康14 小时前
大数据-263 实时数仓-Canal 增量订阅与消费原理:MySQL Binlog 数据同步实践
大数据·hadoop·后端