XXL-MQ之Consumer启动流程

概要

   本篇主要分析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消费消息都是通过代理完成的;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值