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

相关推荐
卜锦元31 分钟前
Golang项目开发过程中好用的包整理归纳(附带不同包仓库地址)
开发语言·后端·golang
Tony Bai5 小时前
“我曾想付钱给 Google 去工作”—— Russ Cox 深度访谈:Go 的诞生、演进与未来
开发语言·后端·golang
serendipity_hky6 小时前
【SpringCloud | 第2篇】OpenFeign远程调用
java·后端·spring·spring cloud·openfeign
嘟嘟MD6 小时前
程序员副业 | 2025年11月复盘
后端·创业
SadSunset6 小时前
(15)抽象工厂模式(了解)
java·笔记·后端·spring·抽象工厂模式
汝生淮南吾在北6 小时前
SpringBoot+Vue养老院管理系统
vue.js·spring boot·后端·毕业设计·毕设
李慕婉学姐7 小时前
【开题答辩过程】以《基于springboot的地铁综合服务管理系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·spring boot·后端
期待のcode7 小时前
Springboot配置属性绑定
java·spring boot·后端
海上彼尚7 小时前
Go之路 - 6.go的指针
开发语言·后端·golang
LYFlied8 小时前
在AI时代,前端开发者如何构建全栈开发视野与核心竞争力
前端·人工智能·后端·ai·全栈