引言
众所周知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类
- 通过实现
InvocationHandler
类,实现以下方法
java
public R invoke(final Object proxy, final Method method, final Object[] args) {}
- 通过
Method
获取Mapping
java
Mapping annotation = method.getAnnotation(Mapping.class);
- 通过
args
构建请求参数 EasyRetryRequest
java
EasyRetryRequest easyRetryRequest = new EasyRetryRequest(args);
- 发送消息
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的实现,这也是非常简便的。这简单而巧妙的设计大大提升了系统的可维护性和易用性。