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

相关推荐
Nan_Shu_6141 小时前
学习: 尚硅谷Java项目之小谷充电宝(3)
java·后端·学习
智能工业品检测-奇妙智能1 小时前
AIFlowy如何实现与现有Spring Boot项目的无缝集成?
java·spring boot·后端
Ama_tor1 小时前
Flask零基础进阶(中)
后端·python·flask
人道领域1 小时前
苍穹外卖:菜品新增功能全流程解析
数据库·后端·状态模式
野犬寒鸦1 小时前
TCP协议核心:TCP详细图解及TCP与UDP核心区别对比(附实战解析)
服务器·网络·数据库·后端·面试
毕设源码-朱学姐1 小时前
【开题答辩全过程】以 基于springBoot微服务架构的老年人社交系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
csdn_aspnet2 小时前
Asp.Net Core 10.0 中的 Blazor 增强功能
前端·后端·asp.net·blazor·.net10
rannn_1112 小时前
【Redis|实战篇1】黑马点评|短信登录功能实现
java·redis·后端·缓存·项目
AI_56782 小时前
RabbitMQ消息队列:高可用集群搭建与消息幂等处理
开发语言·后端·ruby
古城小栈2 小时前
Rust 1.94.0 闪亮登台
开发语言·后端·rust