MQ入门之看MQ项目源码 XXL-MQ

类图架构

broker模块

在这里插入图片描述

provider/consumer

在这里插入图片描述

样例

在这里插入图片描述

无spring样例

在这里插入图片描述

兼容spring样例

在这里插入图片描述

从示范类深入使用流程

从frameless框架入手,有四个文件
在这里插入图片描述

XxlMqFramelessApplication

这是实例入口类,我们可以看到,先是使用了getInstance()再使用start(),之后每小时检查一次是否被中断,有就关闭类,这个getInstance()实际上是取了一个XxlMqConf单例,这将在下一部分展开

	public class XxlMqFramelessApplication {
	    public static void main(String[] args) throws Exception {
	        // consumer list
	        List<IMqConsumer> consumerList = new ArrayList<>();
	        consumerList.add(new Demo2MqComsumer());
	        // 开启消费者
	        XxlMqConf.getInstance().start(consumerList);
	        // 开启生产者
	        XxlMqProducer.produce(new XxlMqMessage("topic_2","test msg data"));
	        //每小时检查一次是否被中断,有就关闭
	        while (!Thread.currentThread().isInterrupted()) {
	            TimeUnit.HOURS.sleep(1);
	        }
	        // stop
	        XxlMqConf.getInstance().stop();
	    }
	}

Demo2MqComsumer

	@MqConsumer(topic = "topic_2")
	public class Demo2MqComsumer implements IMqConsumer {
	    private Logger logger = LoggerFactory.getLogger(Demo2MqComsumer.class);
	    @Override
	    public MqResult consume(String data) throws Exception {
	        logger.info("[Demo2MqComsumer] 消费一条消息:{}", data);
	        return MqResult.SUCCESS;
	    }
	}

XxlMqConf

主要分为三个部分

静态加载

Conf类采用了饿汉式的单例模式来新建Conf实例,并使用factory生成一个self4j日志。

	private static Logger logger = LoggerFactory.getLogger(XxlMqConf.class);

    private static XxlMqConf instance = new XxlMqConf();
    public static XxlMqConf getInstance() {
        return instance;
    }

    // XxlMqClientFactory
    private XxlMqClientFactory xxlMqClientFactory;

读取配置文件并初始化配置

	public void start(List<IMqConsumer> consumerList){

	     // 取出properties文件
	     Properties xxlJobProp = loadProperties("xxl-mq.properties");
	
	     xxlMqClientFactory = new XxlMqClientFactory();
	     xxlMqClientFactory.setAdminAddress(xxlJobProp.getProperty("xxl.mq.admin.address"));
	     xxlMqClientFactory.setAccessToken(xxlJobProp.getProperty("xxl.mq.accessToken"));
	     xxlMqClientFactory.setConsumerList(consumerList);
	     xxlMqClientFactory.init();
     }
	
	public static Properties loadProperties(String propertyFileName) {
        InputStreamReader in = null;
        try {
            ClassLoader loder = Thread.currentThread().getContextClassLoader();

            in = new InputStreamReader(loder.getResourceAsStream(propertyFileName), "UTF-8");;
            if (in != null) {
                Properties prop = new Properties();
                prop.load(in);
                return prop;
            }
        } catch (IOException e) {
            logger.error("load {} error!", propertyFileName);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    logger.error("close {} error!", propertyFileName);
                }
            }
        }
        return null;
    }

终止部分

	public void stop() throws Exception {
		if (xxlMqClientFactory != null) {
		    xxlMqClientFactory.destroy();
		}
	}

核心类之XxlMqClientFactory

init()

前情提要,XxlMqConf.getInstance().start(consumerList),从XxlMqConf中可以看出,已经拥有了单例Conf,然后start()中初始化了一个XxlMqClientFactory并且调用了其init(),以下是init()源码

    public void init() {
        //判断是否合法
        validConsumer();
        // 开启转发存储服务
        startBrokerService();
        // 开启消费者
        startConsumer();
    }

startBrokerService()

部分1初始化了XxlRpcInvokerFactory,然后会在底层使用HttpURLConnection向注册中心和发现中心传输
部分2写了一个Netty客户端,并且写了一个代理完成远程RPC
部分3开启了6个线程,3个负责直接传输,3个负责传输后会回调的任务,这六个线程会从阻塞队列中取出任务

public void startBrokerService() {

        // 部分1
        xxlRpcInvokerFactory = new XxlRpcInvokerFactory(XxlRegistryServiceRegistry.class, new HashMap<String, String>(){{
            put(XxlRegistryServiceRegistry.XXL_REGISTRY_ADDRESS, adminAddress);
            put(XxlRegistryServiceRegistry.ACCESS_TOKEN, accessToken);
        }});
        try {
            xxlRpcInvokerFactory.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        // init ConsumerRegistryHelper
        XxlRegistryServiceRegistry commonServiceRegistry = (XxlRegistryServiceRegistry) xxlRpcInvokerFactory.getServiceRegistry();
        consumerRegistryHelper = new ConsumerRegistryHelper(commonServiceRegistry);
        
        // 部分2
        xxlMqBroker = (IXxlMqBroker) new XxlRpcReferenceBean(
                NetEnum.NETTY,
                Serializer.SerializeEnum.HESSIAN.getSerializer(),
                CallType.SYNC,
                LoadBalance.ROUND,
                IXxlMqBroker.class,
                null,
                10000,
                null,
                null,
                null,
                xxlRpcInvokerFactory).getObject();
        // async + mult, addMessages
        for (int i = 0; i < 3; i++) {
            clientFactoryThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    while (!XxlMqClientFactory.clientFactoryPoolStoped) {
                        try {
                            XxlMqMessage message = newMessageQueue.take();
                            if (message != null) {
                                // load
                                List<XxlMqMessage> messageList = new ArrayList<>();
                                messageList.add(message);
                                List<XxlMqMessage> otherMessageList = new ArrayList<>();
                                int drainToNum = newMessageQueue.drainTo(otherMessageList, 100);
                                if (drainToNum > 0) {
                                    messageList.addAll(otherMessageList);
                                }
                                // save
                                xxlMqBroker.addMessages(messageList);
                            }
                        } catch (Exception e) {
                            if (!XxlMqClientFactory.clientFactoryPoolStoped) {
                                logger.error(e.getMessage(), e);
                            }
                        }
                    }
                    // finally total
                    List<XxlMqMessage> otherMessageList = new ArrayList<>();
                    int drainToNum = newMessageQueue.drainTo(otherMessageList);
                    if (drainToNum> 0) {
                        xxlMqBroker.addMessages(otherMessageList);
                    }
                }
            });
        }
		//部分3
        // async + mult, addMessages
        for (int i = 0; i < 3; i++) {
            clientFactoryThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    while (!XxlMqClientFactory.clientFactoryPoolStoped) {
                        try {
                            XxlMqMessage message = callbackMessageQueue.take();
                            if (message != null) {
                                // load
                                List<XxlMqMessage> messageList = new ArrayList<>();
                                messageList.add(message);
                                List<XxlMqMessage> otherMessageList = new ArrayList<>();
                                int drainToNum = callbackMessageQueue.drainTo(otherMessageList, 100);
                                if (drainToNum > 0) {
                                    messageList.addAll(otherMessageList);
                                }
                                // callback
                                xxlMqBroker.callbackMessages(messageList);
                            }
                        } catch (Exception e) {
                            if (!XxlMqClientFactory.clientFactoryPoolStoped) {
                                logger.error(e.getMessage(), e);
                            }
                        }
                    }
                    // finally total
                    List<XxlMqMessage> otherMessageList = new ArrayList<>();
                    int drainToNum = callbackMessageQueue.drainTo(otherMessageList);
                    if (drainToNum> 0) {
                        xxlMqBroker.callbackMessages(otherMessageList);
                    }
                }
            });
        }
    }
部分1深入查看
// init XxlRpcInvokerFactory
        xxlRpcInvokerFactory = new XxlRpcInvokerFactory(XxlRegistryServiceRegistry.class, new HashMap<String, String>(){{
            put(XxlRegistryServiceRegistry.XXL_REGISTRY_ADDRESS, adminAddress);
            put(XxlRegistryServiceRegistry.ACCESS_TOKEN, accessToken);
        }});
        try {
            xxlRpcInvokerFactory.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

以下是xxlRpcInvokerFactory.start(),先新建了一个服务注册类,然后

    public void start() throws Exception {
        // start registry
        if (serviceRegistryClass != null) {
            serviceRegistry = serviceRegistryClass.newInstance();
            serviceRegistry.start(serviceRegistryParam);
        }
    }											

以下是xxlRpcInvokerFactory.start()

    public void start(Map<String, String> param) {
        String xxlRegistryAddress = param.get(XXL_REGISTRY_ADDRESS);
        String accessToken = param.get(ACCESS_TOKEN);
        String biz = param.get(BIZ);
        String env = param.get(ENV);

        // fill
        biz = (biz!=null&&biz.trim().length()>0)?biz:"default";
        env = (env!=null&&env.trim().length()>0)?env:"default";

        xxlRegistryClient = new XxlRegistryClient(xxlRegistryAddress, accessToken, biz, env);
    }

下面这个方法就是新建了一个XxlRegistryBaseClient类,并开启了注册和发现两个守护进程

    public XxlRegistryClient(String adminAddress, String accessToken, String biz, String env) {
        registryBaseClient = new XxlRegistryBaseClient(adminAddress, accessToken, biz, env);
        // registry thread
        registryThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!registryThreadStop) {
                    try {
                        if (registryData.size() > 0) {
                            boolean ret = registryBaseClient.registry(new ArrayList<XxlRegistryDataParamVO>(registryData));
                        }
                    } catch (Exception e) {
                        if (!registryThreadStop) {
                            logger.error(">>>>>>>>>>> xxl-registry, registryThread error.", e);
                        }
                    }
                    try {
                        TimeUnit.SECONDS.sleep(10);
                    } catch (Exception e) {
                        if (!registryThreadStop) {
                            logger.error(">>>>>>>>>>> xxl-registry, registryThread error.", e);
                        }
                    }
                }
            }
        });
        registryThread.setName("xxl-registry, XxlRegistryClient registryThread.");
        registryThread.setDaemon(true);
        registryThread.start();
        // discovery thread
        discoveryThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!registryThreadStop) {
                    if (discoveryData.size() == 0) {
                        try {
                            TimeUnit.SECONDS.sleep(3);
                        } catch (Exception e) {
                            if (!registryThreadStop) {
                                logger.error(">>>>>>>>>>> xxl-registry, discoveryThread error.", e);
                            }
                        }
                    } else {
                        try {
                            // monitor
                            boolean monitorRet = registryBaseClient.monitor(discoveryData.keySet());
                            // avoid fail-retry request too quick
                            if (!monitorRet){
                                TimeUnit.SECONDS.sleep(10);
                            }
                            // refreshDiscoveryData, all
                            refreshDiscoveryData(discoveryData.keySet());
                        } catch (Exception e) {
                            if (!registryThreadStop) {
                                logger.error(">>>>>>>>>>> xxl-registry, discoveryThread error.", e);
                            }
                        }
                    }
                }
            }
        });
        discoveryThread.setName("xxl-registry, XxlRegistryClient discoveryThread.");
        discoveryThread.setDaemon(true);
        discoveryThread.start();
  	}
	public XxlRegistryBaseClient(String adminAddress, String accessToken, String biz, String env) {
        this.adminAddress = adminAddress;
        this.accessToken = accessToken;
        this.biz = biz;
        this.env = env;
        // valid
        if (adminAddress==null || adminAddress.trim().length()==0) {
            throw new RuntimeException("xxl-registry adminAddress empty");
        }
        if (biz==null || biz.trim().length()<4 || biz.trim().length()>255) {
            throw new RuntimeException("xxl-registry biz empty Invalid[4~255]");
        }
        if (env==null || env.trim().length()<2 || env.trim().length()>255) {
            throw new RuntimeException("xxl-registry biz env Invalid[2~255]");
        }
        // parse
        adminAddressArr = new ArrayList<>();
        if (adminAddress.contains(",")) {
            adminAddressArr.addAll(Arrays.asList(adminAddress.split(",")));
        } else {
            adminAddressArr.add(adminAddress);
        }
    }
BasicHttpUtil

这是使用的通信类,用于注册和发现中心通信

	public class BasicHttpUtil {
	    private static Logger logger = LoggerFactory.getLogger(BasicHttpUtil.class);
	    /**
	     * post
	     *
	     * @param url
	     * @param requestBody
	     * @param timeout
	     * @return
	     */
	    public static String postBody(String url, String requestBody, int timeout) {
	        HttpURLConnection connection = null;
	        BufferedReader bufferedReader = null;
	        try {
	            // connection
	            URL realUrl = new URL(url);
	            connection = (HttpURLConnection) realUrl.openConnection();
	            // connection setting
	            connection.setRequestMethod("POST");
	            connection.setDoOutput(true);
	            connection.setDoInput(true);
	            connection.setUseCaches(false);
	            connection.setReadTimeout(timeout * 1000);
	            connection.setConnectTimeout(3 * 1000);
	            connection.setRequestProperty("connection", "Keep-Alive");
	            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
	            connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");
	            // do connection
	            connection.connect();
	            // write requestBody
	            DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
	            dataOutputStream.writeBytes(requestBody);
	            dataOutputStream.flush();
	            dataOutputStream//ppp
	            return result.toString();
	        } catch (Exception e) {
	            logger.error(e.getMessage(), e);
	        } finally {
	            try {
	                if (bufferedReader != null) {
	                    bufferedReader.close();
	                }
	                if (connection != null) {
	                    connection.disconnect();
	                }
	            } catch (Exception e2) {
	                logger.error(e2.getMessage(), e2);
	            }
	        }
	        return null;
	    }
	}
getObject()完成代理类逻辑

这个方法是用于远程发出TCP请求,向broker发送数据

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 = mjethod.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");
			}
		}
	});
}

startConsumer()

一开始,将consumerRespository中合格的消费者(由init()validConsumer()来判定)全部注册入ConsumerRegistryHelper(由startBrokerService()来创建),然后正式执行所有消费者

	private void startConsumer() {
        // valid
        if (consumerRespository ==null || consumerRespository.size()==0) {
            return;
        }
        // registry consumer
        getConsumerRegistryHelper().registerConsumer(consumerRespository);
        // execute thread
        for (ConsumerThread item: consumerRespository) {
            clientFactoryThreadPool.execute(item);
            logger.info(">>>>>>>>>>> xxl-mq, consumer init success, , topic:{}, group:{}", item.getMqConsumer().topic(), item.getMqConsumer().group());
        }
    }

将线程所有主题,组和线程标识位取出,注册进入和发现这些消费者

	public void registerConsumer(List<ConsumerThread> consumerThreadList) {
        List<XxlRegistryDataParamVO> registryParamList = new ArrayList<>();
        Set<String> registryParamKeyList = new HashSet<>();
        for (ConsumerThread consumerThread: consumerThreadList) {
            String registryKey = makeRegistryKey(consumerThread.getMqConsumer().topic());
            String registryVal = makeRegistryVal(consumerThread.getMqConsumer().group(), consumerThread.getUuid());
            registryParamList.add(new XxlRegistryDataParamVO(registryKey, registryVal));
            registryParamKeyList.add(registryKey);
        }
        // registry mult consumer
        serviceRegistry.getXxlRegistryClient().registry(registryParamList);
        // discovery mult consumer
        serviceRegistry.getXxlRegistryClient().discovery(registryParamKeyList);
    }

consumerThread线程run()

以下是run源码,clientFactoryPoolStoped会被设置为true,当且仅当客户端单例工厂调用destroy(),才会退出循环。
isActive()用于判断是否有新内容
discovery()用于更新主题信息,这个方法主要用于获取缓存discoveryData,discoveryData获取前,会判断主题

    public ActiveInfo isActice(ConsumerThread consumerThread){
        // init data
        String registryKey = makeRegistryKey(consumerThread.getMqConsumer().topic());
        String registryValPrefix = makeRegistryValPrefix(consumerThread.getMqConsumer().group());
        String registryVal = makeRegistryVal(consumerThread.getMqConsumer().group(), consumerThread.getUuid());
        // load all consumer
        TreeSet<String> onlineConsumerSet = serviceRegistry.discovery(registryKey);
        if (onlineConsumerSet==null || onlineConsumerSet.size()==0) {
            return null;
        }
        // filter by group
        TreeSet<String> onlineConsumerSet_group = new TreeSet<String>();
        for (String onlineConsumerItem : onlineConsumerSet) {
            if (onlineConsumerItem.startsWith(registryValPrefix)) {
                onlineConsumerSet_group.add(onlineConsumerItem);
            }
        }
        if (onlineConsumerSet_group==null || onlineConsumerSet_group.size()==0) {
            return null;
        }
        // rank
        int rank = -1;
        for (String onlineConsumerItem : onlineConsumerSet_group) {
            rank++;
            if (onlineConsumerItem.equals(registryVal)) {
                break;
            }
        }
        if (rank == -1) {
            return null;
        }
        return new ActiveInfo(rank, onlineConsumerSet_group.size(), onlineConsumerSet_group.toString());
    }

registryDataTmp不和keys相同,keys是Consumer订阅的主题

public Map<String, TreeSet<String>> discovery(Set<String> keys) {
        if (keys==null || keys.size() == 0) {
            return null;
        }

        // find from local
        Map<String, TreeSet<String>> registryDataTmp = new HashMap<String, TreeSet<String>>();
        for (String key : keys) {
            TreeSet<String> valueSet = discoveryData.get(key);
            if (valueSet != null) {
                registryDataTmp.put(key, valueSet);
            }
        }

        // not find all, find from remote
        if (keys.size() != registryDataTmp.size()) {

            // refreshDiscoveryData, some, first use
            refreshDiscoveryData(keys);

            // find from local
            for (String key : keys) {
                TreeSet<String> valueSet = discoveryData.get(key);
                if (valueSet != null) {
                    registryDataTmp.put(key, valueSet);
                }
            }

        }

        return registryDataTmp;
    }
public void run() {

        int waitTim = 0;

        while (!XxlMqClientFactory.clientFactoryPoolStoped) {
            try {
                // check active
                ConsumerRegistryHelper.ActiveInfo activeInfo = XxlMqClientFactory.getConsumerRegistryHelper().isActice(this);
                logger.debug(">>>>>>>>>>> xxl-mq, consumer active check, topic:{}, group:{}, ActiveInfo={}", mqConsumer.topic(), mqConsumer.group(), activeInfo);

                if (activeInfo != null) {

                    // pullNewMessage
                    List<XxlMqMessage> messageList = XxlMqClientFactory.getXxlMqBroker().pullNewMessage(mqConsumer.topic(), mqConsumer.group(), activeInfo.rank, activeInfo.total, 100);
                    if (messageList != null && messageList.size() > 0) {

                        // reset wait time
                        if (mqConsumer.transaction()) {
                            waitTim = 0;    // transaction message status timely updated by lock, will not repeat pull
                        } else {
                            waitTim = 1;    // no-transaction message status delay updated by callback, may be repeat, need wail for callback
                        }

                        for (final XxlMqMessage msg : messageList) {

                            // check active twice
                            ConsumerRegistryHelper.ActiveInfo newActiveInfo = XxlMqClientFactory.getConsumerRegistryHelper().isActice(this);
                            if (!(newActiveInfo != null && newActiveInfo.rank == activeInfo.rank && newActiveInfo.total == activeInfo.total)) {
                                break;
                            }

                            // lock message, for transaction
                            if (mqConsumer.transaction()) {
                                String appendLog_lock = LogHelper.makeLog(
                                        "锁定消息",
                                        ("消费者信息="+newActiveInfo.toString()
                                                +";<br>消费者IP="+IpUtil.getIp())
                                );
                                int lockRet = XxlMqClientFactory.getXxlMqBroker().lockMessage(msg.getId(), appendLog_lock);
                                if (lockRet < 1) {
                                    continue;
                                }
                            }

                            // consume message
                            MqResult mqResult = null;
                            try {

                                if (msg.getTimeout() > 0) {
                                    // limit timeout
                                    Thread futureThread = null;
                                    try {
                                        FutureTask<MqResult> futureTask = new FutureTask<MqResult>(new Callable<MqResult>() {
                                            @Override
                                            public MqResult call() throws Exception {
                                                return consumerHandler.consume(msg.getData());
                                            }
                                        });
                                        futureThread = new Thread(futureTask);
                                        futureThread.start();

                                        mqResult = futureTask.get(msg.getTimeout(), TimeUnit.SECONDS);
                                    } catch (TimeoutException e) {
                                        logger.error(e.getMessage(), e);
                                        mqResult = new MqResult(MqResult.FAIL_CODE, "Timeout:" + e.getMessage());
                                    } finally {
                                        futureThread.interrupt();
                                    }
                                } else {
                                    // direct run
                                    mqResult = consumerHandler.consume(msg.getData());
                                }

                                if (mqResult == null) {
                                    mqResult = MqResult.FAIL;
                                }
                            } catch (Exception e) {
                                logger.error(e.getMessage(), e);
                                String errorMsg = ThrowableUtil.toString(e);
                                mqResult = new MqResult(MqResult.FAIL_CODE, errorMsg);
                            }

                            // log
                            String appendLog_consume = null;
                            if (mqConsumer.transaction()) {
                                appendLog_consume = LogHelper.makeLog(
                                        "消费消息",
                                        ("消费结果="+(mqResult.isSuccess()?"成功":"失败")
                                                +";<br>消费日志="+mqResult.getLog())
                                );
                            } else {
                                appendLog_consume = LogHelper.makeLog(
                                        "消费消息",
                                        ("消费结果="+(mqResult.isSuccess()?"成功":"失败")
                                                +";<br>消费者信息="+activeInfo.toString()
                                                +";<br>消费者IP="+IpUtil.getIp()
                                                +";<br>消费日志="+mqResult.getLog())
                                );
                            }

                            // callback
                            msg.setStatus(mqResult.isSuccess()? XxlMqMessageStatus.SUCCESS.name():XxlMqMessageStatus.FAIL.name());
                            msg.setLog(appendLog_consume);
                            XxlMqClientFactory.callbackMessage(msg);

                            logger.info(">>>>>>>>>>> xxl-mq, consumer finish,  topic:{}, group:{}, ActiveInfo={}", mqConsumer.topic(), mqConsumer.group(), activeInfo.toString());
                        }

                    } else {
                        waitTim = (waitTim+10)<=60?(waitTim+10):60;
                    }
                } else {
                    waitTim = 2;
                }

            } catch (Exception e) {
                if (!XxlMqClientFactory.clientFactoryPoolStoped) {
                    logger.error(e.getMessage(), e);
                }
            }

            // wait
            try {
                TimeUnit.SECONDS.sleep(waitTim);
            } catch (Exception e) {
                if (!XxlMqClientFactory.clientFactoryPoolStoped) {
                    logger.error(e.getMessage(), e);
                }
            }

        }
    }

init()总结

无论是springboot还是frameless,启动client都离不开init(),init主要做三个工作
1,验证是否有效,有效的加入consumerRespository
2,初始化XxlRpcInvokerFactory,XxlRegistryServiceRegistry,ConsumerRegistryHelper和xxlMqBroker。XxlRpcInvokerFactory用于创建XxlRegistryServiceRegistry,同时开启两个线程,分别用于注册和发现数据。然后后续开启三个线程用于普通队列,三个线程用于回调队列。
3,将消费者的组,主题,uuid加入注册进去,然后将消费者主题加入发现进去,然后开启消费者线程

xxl-mq-admin

权限认证

WebMvcConfig

用于将拦截器初始化为bean,然后加入拦截器队列。

	@Configuration
	public class WebMvcConfig extends WebMvcConfigurerAdapter {
	    @Resource
	    private PermissionInterceptor permissionInterceptor;
	    @Resource
	    private CookieInterceptor cookieInterceptor;
	    @Override
	    public void addInterceptors(InterceptorRegistry registry) {
	        registry.addInterceptor(permissionInterceptor).addPathPatterns("/**");
	        registry.addInterceptor(cookieInterceptor).addPathPatterns("/**");
	        super.addInterceptors(registry);
	    }
	}

PermissionInterceptor

这是第一个拦截器,在注册入容器时,它会将账号密码通过md5加密成一个token
如果被登录,就会比较token和目前token,相同就把token加入cookie然后通过,这样后续就可以不用账号密码登录,不同false返回
如果登出,就会在客户端删除这个cookie
在进入Controller方法前,会进入preHandle()会先通过isLogin()判断是否有登录过并且携带正确token,如果没有就会判断是否有PermessionLimit限制,有限制,又没有token,就会重定向到toLogin页面等待输入账号密码

	@Component
	public class PermissionInterceptor extends HandlerInterceptorAdapter implements InitializingBean {
	    // ---------------------- init ----------------------
	    @Value("${xxl.mq.login.username}")
	    private String username;
	    @Value("${xxl.mq.login.password}")
	    private String password;
	    @Override
	    public void afterPropertiesSet() throws Exception {
	        // valid
	        if (username==null || username.trim().length()==0 || password==null || password.trim().length()==0) {
	            throw new RuntimeException("权限账号密码不可为空");
	        }
	        // login token
	        String tokenTmp = DigestUtils.md5DigestAsHex(String.valueOf(username + "_" + password).getBytes());		//.getBytes("UTF-8")
	        tokenTmp = new BigInteger(1, tokenTmp.getBytes()).toString(16);
	        LOGIN_IDENTITY_TOKEN = tokenTmp;
	    }
	    // ---------------------- tool ----------------------
		public static final String LOGIN_IDENTITY_KEY = "XXL_MQ_LOGIN_IDENTITY";
		private static String LOGIN_IDENTITY_TOKEN;
		public static String getLoginIdentityToken() {
	        return LOGIN_IDENTITY_TOKEN;
		}
		public static boolean login(HttpServletResponse response, String username, String password, boolean ifRemember){
			// login token
			String tokenTmp = DigestUtils.md5DigestAsHex(String.valueOf(username + "_" + password).getBytes());
			tokenTmp = new BigInteger(1, tokenTmp.getBytes()).toString(16);
			if (!getLoginIdentityToken().equals(tokenTmp)){
				return false;
			}
			// do login
			CookieUtil.set(response, LOGIN_IDENTITY_KEY, getLoginIdentityToken(), ifRemember);
			return true;
		}
		public static void logout(HttpServletRequest request, HttpServletResponse response){
			CookieUtil.remove(request, response, LOGIN_IDENTITY_KEY);
		}
		public static boolean ifLogin(HttpServletRequest request){
			String indentityInfo = CookieUtil.getValue(request, LOGIN_IDENTITY_KEY);
			if (indentityInfo==null || !getLoginIdentityToken().equals(indentityInfo.trim())) {
				return false;
			}
			return true;
		}
		@Override
		public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
			if (!(handler instanceof HandlerMethod)) {
				return super.preHandle(request, response, handler);
			}
			if (!ifLogin(request)) {
				HandlerMethod method = (HandlerMethod)handler;
				PermessionLimit permission = method.getMethodAnnotation(PermessionLimit.class);
				if (permission == null || permission.limit()) {
					response.sendRedirect(request.getContextPath() + "/toLogin");
					//request.getRequestDispatcher("/toLogin").forward(request, response);
					return false;
				}
			}
			return super.preHandle(request, response, handler);
		}
	}
PermissionInterceptor返回cookie逻辑

默认时间经过计算约为70年,如果在设置ifRemember为true时,可以当成cookie永不失效,token加入cookie,随response返回,然后一直保存在客户端浏览器cookie中。

	// 默认缓存时间,单位/秒
	private static final int COOKIE_MAX_AGE = Integer.MAX_VALUE;

	private static final String COOKIE_PATH = "/";
	
		/**
		 * 保存
		 *
		 * @param response
		 * @param key
		 * @param value
		 * @param ifRemember
		 */
		public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) {
			int age = ifRemember?COOKIE_MAX_AGE:-1;
			set(response, key, value, null, COOKIE_PATH, age, true);
		}
	
		/**
		 * 保存
		 *
		 * @param response
		 * @param key
		 * @param value
		 * @param maxAge
		 */
		private static void set(HttpServletResponse response, String key, String value, String domain, String path, int maxAge, boolean isHttpOnly) {
			Cookie cookie = new Cookie(key, value);
			if (domain != null) {
				cookie.setDomain(domain);
			}
			cookie.setPath(path);
			cookie.setMaxAge(maxAge);
			cookie.setHttpOnly(isHttpOnly);
			response.addCookie(cookie);
		}

CookieInterceptor

在HandlerAdapter执行完Controller方法之后,将cookie中收集数据全部加入modelAndView中,这样就可以在视图渲染时全部返回给前端。

@Component
	public class CookieInterceptor extends HandlerInterceptorAdapter {
		@Override
		public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
				ModelAndView modelAndView) throws Exception {
			
			if (modelAndView!=null && request.getCookies()!=null && request.getCookies().length>0) {
				HashMap<String, Cookie> cookieMap = new HashMap<String, Cookie>();
				for (Cookie ck : request.getCookies()) {
					cookieMap.put(ck.getName(), ck);
				}
				modelAndView.addObject("cookieMap", cookieMap);
			}
			super.postHandle(request, response, handler, modelAndView);
		}
	}

异常处理

如果出错了,会判断是返回JSON字符串还是视图,如果是Json直接写入response,如果不是,会返回数据并指定视图

@Override
	public ModelAndView resolveException(HttpServletRequest request,
										 HttpServletResponse response, Object handler, Exception ex) {
		logger.error("WebExceptionResolver:{}", ex);
		// if json
		boolean isJson = false;
		HandlerMethod method = (HandlerMethod)handler;
		ResponseBody responseBody = method.getMethodAnnotation(ResponseBody.class);
		if (responseBody != null) {
			isJson = true;
		}
		// error result
		ReturnT<String> errorResult = new ReturnT<String>(ReturnT.FAIL_CODE, ex.toString().replaceAll("\n", "<br/>"));
		// response
		ModelAndView mv = new ModelAndView();
		if (isJson) {
			try {
				response.setContentType("application/json;charset=utf-8");
				response.getWriter().print("{\"code\":"+errorResult.getCode()+", \"msg\":\""+ errorResult.getMsg() +"\"}");
			} catch (IOException e) {
				logger.error(e.getMessage(), e);
			}
			return mv;
		} else {
			mv.addObject("exceptionMsg", errorResult.getMsg());
			mv.setViewName("/common/common.exception");
			return mv;
		}
	}

注册流程

XxlCommonRegistryServiceImpl

总共开启四个线程

线程1

第一个线程用于查看注册队列中是否有数据,这个队列有数据只有两种情况,一,有人使用了/api/registry,二,线程4从staticRegistryData加入,staticRegistryData会有信息当且仅当服务器新建,也就是brokerImpl()调用了initServer()时,放入key为broker类名,value为主机ip+端口的Data。
如果数据库没有匹配记录,就会返回0,那么就会走添加。然后读取对应property文件,如果有相同value,说明这个broker已经存在,无需添加,就会直接返回。如果没有

以下是线程一的getFileRegistryData()
第一步任务在于生成一个路径为/data/applogs/xxl-mq/registrydata/IXxlMqBroker.properties文件
第二在于加载路径下data模块,然后返回

public XxlCommonRegistry getFileRegistryData(XxlCommonRegistryData xxlCommonRegistryData){
        // fileName
        String fileName = parseRegistryDataFileName(xxlCommonRegistryData.getKey());
        // read
        Properties prop = PropUtil.loadProp(fileName);
        if (prop!=null) {
            XxlCommonRegistry fileXxlCommonRegistry = new XxlCommonRegistry();
            fileXxlCommonRegistry.setData(prop.getProperty("data"));          fileXxlCommonRegistry.setDataList(JacksonUtil.readValue(fileXxlCommonRegistry.getData(), List.class));
            return fileXxlCommonRegistry;
        }
        return null;
    }
@Override
    public void afterPropertiesSet() throws Exception {
        /**
         * registry registry data         (client-num/10 s)
         */
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                while (!executorStoped) {
                    try {
                        XxlCommonRegistryData xxlCommonRegistryData = registryQueue.take();
                        if (xxlCommonRegistryData !=null) {
                            // refresh or add
                            int ret = xxlCommonRegistryDataDao.refresh(xxlCommonRegistryData);
                            if (ret == 0) {
                                xxlCommonRegistryDataDao.add(xxlCommonRegistryData);
                            }
                            // valid file status
                            XxlCommonRegistry fileXxlCommonRegistry = getFileRegistryData(xxlCommonRegistryData);
                            if (fileXxlCommonRegistry!=null && fileXxlCommonRegistry.getDataList().contains(xxlCommonRegistryData.getValue())) {
                                continue;     // "Repeated limited."
                            }
                            // checkRegistryDataAndSendMessage
                            checkRegistryDataAndSendMessage(xxlCommonRegistryData);
                        }
                    } catch (Exception e) {
                        if (!executorStoped) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
            }
        });
        /**
         * remove registry data         (client-num/start-interval s)
         */
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                while (!executorStoped) {
                    try {
                        XxlCommonRegistryData xxlCommonRegistryData = removeQueue.take();
                        if (xxlCommonRegistryData != null) {
                            // delete                          xxlCommonRegistryDataDao.deleteDataValue(xxlCommonRegistryData.getKey(), xxlCommonRegistryData.getValue());
                            // valid file status
                            XxlCommonRegistry fileXxlCommonRegistry = getFileRegistryData(xxlCommonRegistryData);
                            if (fileXxlCommonRegistry!=null && !fileXxlCommonRegistry.getDataList().contains(xxlCommonRegistryData.getValue())) {
                                continue;   // "Repeated limited."
                            }

                            // checkRegistryDataAndSendMessage
                            checkRegistryDataAndSendMessage(xxlCommonRegistryData);
                        }
                    } catch (Exception e) {
                        if (!executorStoped) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
            }
        });
        /**
         * broadcase new one registry-data-file     (1/1s)
         * clean old message   (1/10s)
         */
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                while (!executorStoped) {
                    try {
                        // new message, filter readed
                        List<XxlCommonRegistryMessage> messageList = xxlCommonRegistryMessageDao.findMessage(readedMessageIds);
                        if (messageList!=null && messageList.size()>0) {
                            for (XxlCommonRegistryMessage message: messageList) {
                                readedMessageIds.add(message.getId());
                                // from registry、add、update、deelete,ne need sync from db, only write
                                XxlCommonRegistry xxlCommonRegistry = JacksonUtil.readValue(message.getData(), XxlCommonRegistry.class);
                                // default, sync from db (aready sync before message, only write)
                                // sync file
                                setFileRegistryData(xxlCommonRegistry);
                            }
                        }
                        // clean old message;
                        if (System.currentTimeMillis() % registryBeatTime ==0) {
                            xxlCommonRegistryMessageDao.cleanMessage(10);
                            readedMessageIds.clear();
                        }
                    } catch (Exception e) {
                        if (!executorStoped) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (Exception e) {
                        if (!executorStoped) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
            }
        });
        /**
         *  clean old registry-data     (1/10s)
         *  sync total registry-data db + file      (1+N/10s)
         *  clean old registry-data file
         */
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                while (!executorStoped) {
                    try {
                        // + static registry
                        if (staticRegistryData != null) {
                            registryQueue.add(staticRegistryData);
                        }
                        // clean old registry-data in db
                        xxlCommonRegistryDataDao.cleanData(registryBeatTime * 3);
                        // + clean old registry in db
                        xxlCommonRegistryDao.cleanDead();
                        // sync registry-data, db + file
                        int offset = 0;
                        int pagesize = 1000;
                        List<String> registryDataFileList = new ArrayList<>();
                        List<XxlCommonRegistry> registryList = xxlCommonRegistryDao.pageList(offset, pagesize);
                        while (registryList!=null && registryList.size()>0) {
                            for (XxlCommonRegistry registryItem: registryList) {
                                // default, sync from db
                                List<XxlCommonRegistryData> xxlCommonRegistryDataList = xxlCommonRegistryDataDao.findData(registryItem.getKey());
                                List<String> valueList = new ArrayList<String>();
                                if (xxlCommonRegistryDataList!=null && xxlCommonRegistryDataList.size()>0) {
                                    for (XxlCommonRegistryData dataItem: xxlCommonRegistryDataList) {
                                        valueList.add(dataItem.getValue());
                                    }
                                }
                                String dataJson = JacksonUtil.writeValueAsString(valueList);
                                // check update, sync db
                                if (!registryItem.getData().equals(dataJson)) {
                                    registryItem.setData(dataJson);
                                    xxlCommonRegistryDao.update(registryItem);
                                }
                                // sync file
                                String registryDataFile = setFileRegistryData(registryItem);
                                // collect registryDataFile
                                registryDataFileList.add(registryDataFile);
                            }
                            offset += 1000;
                            registryList = xxlCommonRegistryDao.pageList(offset, pagesize);
                        }
                        // clean old registry-data file
                        cleanFileRegistryData(registryDataFileList);
                    } catch (Exception e) {
                        if (!executorStoped) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                    try {
                        TimeUnit.SECONDS.sleep(registryBeatTime);
                    } catch (Exception e) {
                        if (!executorStoped) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
            }
        });
    }

参考链接
https://www.xuxueli.com/xxl-mq/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值