概要
本篇主要分析Consumer的启动流程。
整体架构流程
主要做了3件事:
1、查询所有带@MqConsumer的bean,封装成一个一个ConsumerThread;
2、构建一个代理,和服务端代理进行连接通信,让客户端有能力调用服务端的方法;
3、将每个Consumer注册到注册中心,保持心跳,不停拉取服务端存入的消息进行消费。
技术细节
- 查找@MqConsumer标记的bean:
- 封装ConsumerThread:
这里会进行一些必要的校验,并且如果没有分组的话,会随机分配一个group编号
- 构建客户端的代理:
这里主要是创建了一个注册中心的实例,注册中心可以通过http的形式和服务端进行通信,主要执行注册和服务发现、服务摘除动作。 ConsumerRegistryHelper会持有注册中心实例,这里方便后面获取。
这里是构建代理的关键。注意一点,红框标记的地址是没传值,这是为什么?刚开始看这段代码的时候,会不会觉得这里是不是应该传配置的服务端的地址呢?其实这里有个小细节,配置的服务端地址是给注册中心使用的,因为注册中心是用http来调用服务端的API进行注册、发现和摘除等动作,但是客户端和服务端通信是通过netty代理来通信的,需要通过注册中心找到活跃的Broke的地址和配置的通信端口号,然后再进行连接。当然这里传一个也是可以的,需要服务端和客户端约定好通信的地址和端口。这里代理的是IXxlMqBroker,我们看一下这个接口里面的方法:
也就是说,后面客户端只要调用这里面的方法,就都会走代理。看一下具体的代理逻辑:
public Object getObject() {
return Proxy.newProxyInstance(Thread.currentThread()
.getContextClassLoader(), new Class[] { iface },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// method param
String className = method.getDeclaringClass().getName(); // iface.getName()
String varsion_ = version;
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] parameters = args;
// filter for generic
if (className.equals(XxlRpcGenericService.class.getName()) && methodName.equals("invoke")) {
Class<?>[] paramTypes = null;
if (args[3]!=null) {
String[] paramTypes_str = (String[]) args[3];
if (paramTypes_str.length > 0) {
paramTypes = new Class[paramTypes_str.length];
for (int i = 0; i < paramTypes_str.length; i++) {
paramTypes[i] = ClassUtil.resolveClass(paramTypes_str[i]);
}
}
}
className = (String) args[0];
varsion_ = (String) args[1];
methodName = (String) args[2];
parameterTypes = paramTypes;
parameters = (Object[]) args[4];
}
// filter method like "Object.toString()"
if (className.equals(Object.class.getName())) {
logger.info(">>>>>>>>>>> xxl-rpc proxy class-method not support [{}#{}]", className, methodName);
throw new XxlRpcException("xxl-rpc proxy class-method not support");
}
// address
String finalAddress = address;
if (finalAddress==null || finalAddress.trim().length()==0) {
if (invokerFactory!=null && invokerFactory.getServiceRegistry()!=null) {
// discovery
String serviceKey = XxlRpcProviderFactory.makeServiceKey(className, varsion_);
TreeSet<String> addressSet = invokerFactory.getServiceRegistry().discovery(serviceKey);
// load balance
if (addressSet==null || addressSet.size()==0) {
// pass
} else if (addressSet.size()==1) {
finalAddress = addressSet.first();
} else {
finalAddress = loadBalance.xxlRpcInvokerRouter.route(serviceKey, addressSet);
}
}
}
if (finalAddress==null || finalAddress.trim().length()==0) {
throw new XxlRpcException("xxl-rpc reference bean["+ className +"] address empty");
}
// request
XxlRpcRequest xxlRpcRequest = new XxlRpcRequest();
xxlRpcRequest.setRequestId(UUID.randomUUID().toString());
xxlRpcRequest.setCreateMillisTime(System.currentTimeMillis());
xxlRpcRequest.setAccessToken(accessToken);
xxlRpcRequest.setClassName(className);
xxlRpcRequest.setMethodName(methodName);
xxlRpcRequest.setParameterTypes(parameterTypes);
xxlRpcRequest.setParameters(parameters);
// send
if (CallType.SYNC == callType) {
// future-response set
XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(invokerFactory, xxlRpcRequest, null);
try {
// do invoke
client.asyncSend(finalAddress, xxlRpcRequest);
// future get
XxlRpcResponse xxlRpcResponse = futureResponse.get(timeout, TimeUnit.MILLISECONDS);
if (xxlRpcResponse.getErrorMsg() != null) {
throw new XxlRpcException(xxlRpcResponse.getErrorMsg());
}
return xxlRpcResponse.getResult();
} catch (Exception e) {
logger.info(">>>>>>>>>>> xxl-rpc, invoke error, address:{}, XxlRpcRequest{}", finalAddress, xxlRpcRequest);
throw (e instanceof XxlRpcException)?e:new XxlRpcException(e);
} finally{
// future-response remove
futureResponse.removeInvokerFuture();
}
} else if (CallType.FUTURE == callType) {
// future-response set
XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(invokerFactory, xxlRpcRequest, null);
try {
// invoke future set
XxlRpcInvokeFuture invokeFuture = new XxlRpcInvokeFuture(futureResponse);
XxlRpcInvokeFuture.setFuture(invokeFuture);
// do invoke
client.asyncSend(finalAddress, xxlRpcRequest);
return null;
} catch (Exception e) {
logger.info(">>>>>>>>>>> xxl-rpc, invoke error, address:{}, XxlRpcRequest{}", finalAddress, xxlRpcRequest);
// future-response remove
futureResponse.removeInvokerFuture();
throw (e instanceof XxlRpcException)?e:new XxlRpcException(e);
}
} else if (CallType.CALLBACK == callType) {
// get callback
XxlRpcInvokeCallback finalInvokeCallback = invokeCallback;
XxlRpcInvokeCallback threadInvokeCallback = XxlRpcInvokeCallback.getCallback();
if (threadInvokeCallback != null) {
finalInvokeCallback = threadInvokeCallback;
}
if (finalInvokeCallback == null) {
throw new XxlRpcException("xxl-rpc XxlRpcInvokeCallback(CallType="+ CallType.CALLBACK.name() +") cannot be null.");
}
// future-response set
XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(invokerFactory, xxlRpcRequest, finalInvokeCallback);
try {
client.asyncSend(finalAddress, xxlRpcRequest);
} catch (Exception e) {
logger.info(">>>>>>>>>>> xxl-rpc, invoke error, address:{}, XxlRpcRequest{}", finalAddress, xxlRpcRequest);
// future-response remove
futureResponse.removeInvokerFuture();
throw (e instanceof XxlRpcException)?e:new XxlRpcException(e);
}
return null;
} else if (CallType.ONEWAY == callType) {
client.asyncSend(finalAddress, xxlRpcRequest);
return null;
} else {
throw new XxlRpcException("xxl-rpc callType["+ callType +"] invalid");
}
}
});
}
重点看一下这段代码:
String finalAddress = address;
if (finalAddress==null || finalAddress.trim().length()==0) {
if (invokerFactory!=null && invokerFactory.getServiceRegistry()!=null) {
// discovery
String serviceKey = XxlRpcProviderFactory.makeServiceKey(className, varsion_);
TreeSet<String> addressSet = invokerFactory.getServiceRegistry().discovery(serviceKey);
// load balance
if (addressSet==null || addressSet.size()==0) {
// pass
} else if (addressSet.size()==1) {
finalAddress = addressSet.first();
} else {
finalAddress = loadBalance.xxlRpcInvokerRouter.route(serviceKey, addressSet);
}
}
}
if (finalAddress==null || finalAddress.trim().length()==0) {
throw new XxlRpcException("xxl-rpc reference bean["+ className +"] address empty");
}
address必然是null,因为压根就没传。所以,通过注册中心实例去拉取了活跃的Broker的地址。ServiceRegistry()#discovery(serviceKey)这个方法取的是缓存中的地址,可以提高效率。注册中心定时更新Broker的信息,下线的Broker会被摘除。所以,这里保证了每次拿到的都是活跃的Broker的地址。可以看看具体缓存的逻辑和更新缓存的逻辑,一路跟进去就行:
public XxlRegistryClient(String adminAddress, String accessToken, String biz, String env) {
this.discoveryThread = new Thread(new Runnable() {
public void run() {
while(!XxlRegistryClient.this.registryThreadStop) {
if (XxlRegistryClient.this.discoveryData.size() == 0) {
try {
TimeUnit.SECONDS.sleep(3L);
} catch (Exception var2) {
if (!XxlRegistryClient.this.registryThreadStop) {
XxlRegistryClient.logger.error(">>>>>>>>>>> xxl-registry, discoveryThread error.", var2);
}
}
} else {
try {
boolean monitorRet = XxlRegistryClient.this.registryBaseClient.monitor(XxlRegistryClient.this.discoveryData.keySet());
if (!monitorRet) {
TimeUnit.SECONDS.sleep(10L);
}
XxlRegistryClient.this.refreshDiscoveryData(XxlRegistryClient.this.discoveryData.keySet());
} catch (Exception var3) {
if (!XxlRegistryClient.this.registryThreadStop) {
XxlRegistryClient.logger.error(">>>>>>>>>>> xxl-registry, discoveryThread error.", var3);
}
}
}
}
XxlRegistryClient.logger.info(">>>>>>>>>>> xxl-registry, discoveryThread stoped.");
}
});
this.discoveryThread.setName("xxl-registry, XxlRegistryClient discoveryThread.");
this.discoveryThread.setDaemon(true);
this.discoveryThread.start();
logger.info(">>>>>>>>>>> xxl-registry, XxlRegistryClient init success.");
}
初始化的时候,这里起了一条守护线程在不停的更新最新数据。以上这些操作就完成了代理的构建,以及Broker地址的更新,还有后面Consumer信息的更新。
- 注册Consumer信息,并且启动所有的ConsumerThread线程:
前面找到@MqConsumer标识的Bean之后,封装成ConsumerThread,存到了consumerRespository中,这里就可以直接从里面拿到: 1、Consumer的注册:
心跳机制里就包含了插入操作,所以,其实②这里完全可以不要。初始化的时候,会起一条线程不停的进行心跳维护: 2、Consumer发现处理。还是刚才的方法,也就是discoveryData里面放的是活跃的Broker和Consumer的信息,客户端如果需要,可以直接从里面拿数据:
- 启动每个ConsumerThread:
直接通过线程池的方式将每个ConsumerThread启动了。我们看一下run方法:
不停的在去拉取服务端存入的消息进行消费。Producer将消息存入服务端,consumer在不停来取到了执行时间的消息进行消费,整个过程就是这样。
小结
- 首先,consumer会将自己的信息注册到服务端,也可以说是注册中心,因为注册中心借用了服务端的能力;
- 其次,客户端这边构建了一个代理,和服务端代理进行连接,提供操作消息的能力。后面Producer存储消息,Consumer消费消息都是通过代理完成的;