文章目录
一、自定义注解
使用注解@EasyRpcReference
用于声明一个需要远程调用服务的客户端。其中包含一些服务调用参数设置,具体可以见源码com/shaogezhu/easy/rpc/spring/starter/common/EasyRpcReference.java
。
二、客户端自动配置类
代码如下,其中最主要的为两个接口。
public class RpcClientAutoConfiguration implements BeanPostProcessor, ApplicationListener<ApplicationReadyEvent> {
private static RpcReference rpcReference = null;
private static Client client = null;
private volatile boolean needInitClient = false;
private volatile boolean hasInitClientConfig = false;
private static final Logger LOGGER = LoggerFactory.getLogger(RpcClientAutoConfiguration.class);
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {...}
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {...}
}
第一个接口BeanPostProcessor
,与服务端不同的是,实现该接口的作用是在Bean注入前的调用postProcessAfterInitialization
方法,以便通过反射生成代理类。第二个接口ApplicationListener
,顾名思义在容器启动时进行监听,实现方法onApplicationEvent
。
1.postProcessAfterInitialization() 方法
代码如下,在该方法主要实现代理类的功能。在每一个Bean进行注入之前,通过反射获取类的注解,field.isAnnotationPresent(EasyRpcReference.class)
判断当前类是否是一个远程调用客户端类。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(EasyRpcReference.class)) {
if (!hasInitClientConfig) {
client = new Client();
try {
client.initClientConfig();
rpcReference = client.initClientApplication();
} catch (Exception e) {
LOGGER.error("[IRpcClientAutoConfiguration] postProcessAfterInitialization has error ", e);
throw new RuntimeException(e);
}
hasInitClientConfig = true;
}
needInitClient = true;
EasyRpcReference easyRpcReference = field.getAnnotation(EasyRpcReference.class);
try {
field.setAccessible(true);
Object refObj = field.get(bean);
RpcReferenceWrapper rpcReferenceWrapper = new RpcReferenceWrapper();
rpcReferenceWrapper.setAimClass(field.getType());
rpcReferenceWrapper.setGroup(easyRpcReference.group());
rpcReferenceWrapper.setServiceToken(easyRpcReference.serviceToken());
rpcReferenceWrapper.setUrl(easyRpcReference.url());
rpcReferenceWrapper.setTimeOut(easyRpcReference.timeOut());
//失败重试次数
rpcReferenceWrapper.setRetry(easyRpcReference.retry());
rpcReferenceWrapper.setAsync(easyRpcReference.async());
// 得到代理对象
refObj = rpcReference.get(rpcReferenceWrapper);
// 注入属性
field.set(bean, refObj);
client.doSubscribeService(field.getType());
} catch (Throwable e) {
e.printStackTrace();
}
}
}
return bean;
}
第一步,对Netty客户端进行初始化,client.initClientConfig()
第二部,初始化完成通过client.initClientApplication()
得到静态远程调用类RpcReference
。
第三步,实现代理类后,再对静态远程调用类进行修饰,添加有关服务调用的配置信息,例如URL、Token等。
第四步,进行Bean实例的注入field.set(bean, refObj)
,此时该Bean就为代理对象的实例。
第五步,服务订阅client.doSubscribeService(field.getType())
。
2.onApplicationEvent() 方法
该方法主要是在Springboot容器启动时,同时启动Netty客户端建立client.doConnectServer()
与Netty服务端连接和开启异步发送线程client.startClient()
。
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
if (needInitClient && client != null) {
LOGGER.info(" ================== [{}] started success ================== ", CLIENT_CONFIG.getApplicationName());
client.doConnectServer();
client.startClient();
}
}
三、关键类和方法
1.RpcReference远程调用类
远程调用类代码如下。其中ProxyFactory
是代理工厂,支持多种实现。使用<T> T get(RpcReferenceWrapper<T> rpcReferenceWrapper)
方法来生成对应的代理类。
public class RpcReference {
public ProxyFactory proxyFactory;
public RpcReference(ProxyFactory proxyFactory) {
this.proxyFactory = proxyFactory;
}
/**
* 根据接口类型获取代理对象
*/
public <T> T get(RpcReferenceWrapper<T> rpcReferenceWrapper) throws Throwable {
initGlobalRpcReferenceConfig(rpcReferenceWrapper);
return proxyFactory.getProxy(rpcReferenceWrapper);
}
private void initGlobalRpcReferenceConfig(RpcReferenceWrapper<?> rpcReferenceWrapper) {
if (CommonUtil.isEmpty(rpcReferenceWrapper.getTimeOut())) {
rpcReferenceWrapper.setTimeOut(CLIENT_CONFIG.getTimeOut());
}
}
}
--------------------------------------------------------------------------------------------
// 代理工厂接口
public interface ProxyFactory {
<T> T getProxy(final RpcReferenceWrapper<T> rpcReferenceWrapper) throws Throwable;
}
// 代理工厂的具体实现
public class JavassistProxyFactory implements ProxyFactory {
@Override
public <T> T getProxy(RpcReferenceWrapper<T> rpcReferenceWrapper) throws Throwable {
return (T) ProxyGenerator.newProxyInstance(Thread.currentThread().getContextClassLoader(),
rpcReferenceWrapper.getAimClass(), new JavassistInvocationHandler(rpcReferenceWrapper));
}
}
--------------------------------------------------------------------------------------------
2.Client客户端类
该类是Client的核心类。
public class Client {
private AbstractRegister abstractRegister;
private final Bootstrap bootstrap = new Bootstrap();
public Bootstrap getBootstrap() { return bootstrap; }
public void initClientConfig() { CLIENT_CONFIG = PropertiesBootstrap.loadClientConfigFromLocal(); }
public RpcReference initClientApplication() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {...}
/**
* 启动服务之前需要预先订阅对应的dubbo服务
*/
public void doSubscribeService(Class<?> serviceBean) {...}
/**
* 开始和各个provider建立连接
*/
public void doConnectServer() {...}
/**
* 开启发送线程,专门从事将数据包发送给服务端
*/
public void startClient() {...}
/**
* 异步发送信息任务
*/
class AsyncSendJob implements Runnable {...}
}
2.1 initClientApplication()方法
该方法主要实现以下几个步骤。
public RpcReference initClientApplication() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
// 1.初始化Netty设置
NioEventLoopGroup clientGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
......
// 2.初始化连接器
ConnectionHandler.setBootstrap(bootstrap);
// 3.初始化监听器
RpcListenerLoader rpcListenerLoader = new RpcListenerLoader();
rpcListenerLoader.init();
// 4.初始化路由策略
String routerStrategy = CLIENT_CONFIG.getRouterStrategy();
EXTENSION_LOADER.loadExtension(Router.class);
......
// 5.初始化序列化器
String clientSerialize = CLIENT_CONFIG.getClientSerialize();
EXTENSION_LOADER.loadExtension(SerializeFactory.class);
......
// 6.初始化过滤链
ClientFilterChain clientFilterChain = new ClientFilterChain();
EXTENSION_LOADER.loadExtension(ClientFilter.class);
......
// 7.初始化代理工厂
String proxyType = CLIENT_CONFIG.getProxyType();
EXTENSION_LOADER.loadExtension(ProxyFactory.class);
......
return new RpcReference((ProxyFactory) proxyTypeClass.newInstance());
}
- 初始化Netty设置,重点是添加
ClientHandler
来处理远程调用结果。
NioEventLoopGroup clientGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(clientGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//初始化管道,包含了编解码器和客户端响应类
ByteBuf delimiter = Unpooled.copiedBuffer(DEFAULT_DECODE_CHAR.getBytes());
// DelimiterBasedFrameDecoder是将特殊符号作为一则消息的结束符,以此来解决TCP中粘包问题
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(CLIENT_CONFIG.getMaxServerRespDataSize(), delimiter));
ch.pipeline().addLast(new RpcEncoder());
ch.pipeline().addLast(new RpcDecoder());
ch.pipeline().addLast(new ClientHandler());
}
});
---------------------------------------ClientHandler.java------------------------------------------
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 转型
RpcProtocol rpcProtocol = (RpcProtocol) msg;
// 反序列化
RpcInvocation rpcInvocation = CLIENT_SERIALIZE_FACTORY.deserialize(rpcProtocol.getContent(), RpcInvocation.class);
if (rpcInvocation.getE() != null) {
rpcInvocation.getE().printStackTrace();
}
//通过之前发送的uuid来注入匹配的响应数值
if(!RESP_MAP.containsKey(rpcInvocation.getUuid())){
throw new IllegalArgumentException("server response is error!");
}
// 保存处理结果<key:UUID,value:对象>
RESP_MAP.put(rpcInvocation.getUuid(), rpcInvocation);
ReferenceCountUtil.release(msg);
}
--------------------------------------------------------------------------------------------
- 初始化连接器,
ConnectionHandler
作用是统一管理连接的状态。
- 其他各类序列化器、过滤器等的实例化,使用SPI的方式。
2.2 doSubscribeService()方法
该方法重点是进行服务的订阅,在Zookeeper上创建节点保存消费中信息。
public void doSubscribeService(Class<?> serviceBean) {
if (abstractRegister == null) {
try {
// 初始化注册中心
String registerType = CLIENT_CONFIG.getRegisterType();
EXTENSION_LOADER.loadExtension(RegistryService.class);
LinkedHashMap<String, Class<?>> registerMap = EXTENSION_LOADER_CLASS_CACHE.get(RegistryService.class.getName());
Class<?> registerClass = registerMap.get(registerType);
abstractRegister = (AbstractRegister) registerClass.newInstance();
} catch (Exception e) {
throw new RuntimeException("registryServiceType unKnow,error is ", e);
}
}
// 设置对应的属性
URL url = new URL();
url.setApplicationName(CLIENT_CONFIG.getApplicationName());
url.setServiceName(serviceBean.getName());
url.addParameter("host", CommonUtil.getIpAddress());
// 根据服务名称在zk上获取服务权重相关信息
Map<String, String> result = abstractRegister.getServiceWeightMap(serviceBean.getName());
// 每个服务对应的ip:port映射,一对多
URL_MAP.put(serviceBean.getName(), result);
// 服务订阅
abstractRegister.subscribe(url);
}
---------------------------------------ZookeeperRegister.java------------------------------------------
@Override
public void subscribe(URL url) {
if (!this.zkClient.existNode(ROOT)) {
zkClient.createPersistentData(ROOT, "");
}
String urlStr = URL.buildConsumerUrlStr(url);
if (zkClient.existNode(getConsumerPath(url))) {
zkClient.deleteNode(getConsumerPath(url));
}
zkClient.createTemporarySeqData(getConsumerPath(url), urlStr);
super.subscribe(url);
}
---------------------------------------------------------------------------------------------------
创建结果如下图。
2.3 doConnectServer()方法
该方法向服务提供者建立连接,使用ConnectionHandler
来建立连接。
public void doConnectServer() {
for (URL providerUrl : SUBSCRIBE_SERVICE_LIST) {
List<String> providerIps = abstractRegister.getProviderIps(providerUrl.getServiceName());
for (String providerIp : providerIps) {
try {
// 建立连接
ConnectionHandler.connect(providerUrl.getServiceName(), providerIp);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
URL url = new URL();
url.setServiceName(providerUrl.getServiceName());
url.addParameter("providerIps", JSON.toJSONString(providerIps));
//客户端在此新增一个订阅的功能
abstractRegister.doAfterSubscribe(url);
}
}
2.4 startClient()方法
该方法主要是启动异步发送消息线程。SEND_QUEUE
是一个阻塞队列,用来储存发送任务。在getChannelFuture
方法中会进行负载均衡策略和消息的过滤。
/**
* 开启发送线程,专门从事将数据包发送给服务端
*/
public void startClient() {
Thread asyncSendJob = new Thread(new AsyncSendJob(), "ClientAsyncSendJobThread");
asyncSendJob.start();
}
/**
* 异步发送信息任务
*/
class AsyncSendJob implements Runnable {
public AsyncSendJob() { }
@Override
public void run() {
while (true) {
try {
//阻塞模式
RpcInvocation data = SEND_QUEUE.take();
//进行序列化
byte[] serialize = CLIENT_SERIALIZE_FACTORY.serialize(data);
//将RpcInvocation封装到RpcProtocol对象中,然后发送给服务端
RpcProtocol rpcProtocol = new RpcProtocol(serialize);
//获取netty通道,负载均衡策略
ChannelFuture channelFuture = ConnectionHandler.getChannelFuture(data);
//netty的通道负责发送数据给服务端
channelFuture.channel().writeAndFlush(rpcProtocol);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
---------------------------------------CommonClientCache.java------------------------------------------
public static BlockingQueue<RpcInvocation> SEND_QUEUE = new ArrayBlockingQueue<>(100);
-------------------------------------------------------------------------------------------------------
3.JavassistInvocationHandler代理类
以Javassist
代理为例。通过此方法使用Javassist
方式动态代理服务消费者类。
@Override
public <T> T getProxy(RpcReferenceWrapper<T> rpcReferenceWrapper) throws Throwable {
return (T) ProxyGenerator.newProxyInstance(Thread.currentThread().getContextClassLoader(),
rpcReferenceWrapper.getAimClass(), new JavassistInvocationHandler(rpcReferenceWrapper));
}
在该类的invoke方法中,实现了消费者向服务提供者进行服务请求的操作。采用SEND_QUEUE
异步的方式发送消息,并且实现了重试次数和超时的功能。在一定时间内,消费者会不断尝试从RESP_MAP
中根据uuid获取结果,消费者每一次请求都以一个唯一标识作为区分。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RpcInvocation rpcInvocation = new RpcInvocation();
rpcInvocation.setArgs(args);
rpcInvocation.setTargetMethod(method.getName());
rpcInvocation.setTargetServiceName(rpcReferenceWrapper.getAimClass().getName());
rpcInvocation.setAttachments(rpcReferenceWrapper.getAttatchments());
//注入uuid,对每一次的请求都做单独区分
rpcInvocation.setUuid(UUID.randomUUID().toString());
rpcInvocation.setRetry(rpcReferenceWrapper.getRetry());
RESP_MAP.put(rpcInvocation.getUuid(), OBJECT);
//将请求的参数放入到发送队列中
SEND_QUEUE.add(rpcInvocation);
//如果是异步请求,就没有必要再在RESP_MAP中判断是否有响应结果了
if (rpcReferenceWrapper.isAsync()) {
return null;
}
long beginTime = System.currentTimeMillis();
while (System.currentTimeMillis() - beginTime < timeOut || rpcInvocation.getRetry() > 0) {
Object object = RESP_MAP.get(rpcInvocation.getUuid());
if (object instanceof RpcInvocation) {
RpcInvocation rpcInvocationResp = (RpcInvocation) object;
//异常结果+有重试次数=异常重试
if (rpcInvocationResp.getE() != null && rpcInvocationResp.getRetry() > 0) {
//重新请求
rpcInvocation.setE(null);
rpcInvocation.setResponse(null);
rpcInvocation.setRetry(rpcInvocation.getRetry() - 1);
RESP_MAP.put(rpcInvocation.getUuid(), OBJECT);
SEND_QUEUE.add(rpcInvocation);
beginTime = System.currentTimeMillis();
} else {
RESP_MAP.remove(rpcInvocation.getUuid());
return rpcInvocationResp.getResponse();
}
}
//超时重试
if (System.currentTimeMillis() - beginTime > timeOut) {
//重新请求
rpcInvocation.setResponse(null);
//每次重试之后都会将retry值扣减1
rpcInvocation.setRetry(rpcInvocation.getRetry() - 1);
RESP_MAP.put(rpcInvocation.getUuid(), OBJECT);
SEND_QUEUE.add(rpcInvocation);
//充值请求开始的时间
beginTime = System.currentTimeMillis();
}
}
RESP_MAP.remove(rpcInvocation.getUuid());
throw new TimeoutException("client wait server's response timeout!");
}