类图架构
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/