/// <summary> /// 初始化ONS事务消息相关对象,一般在Application_Start中调用 /// </summary> public static void Initialize() { if (_AliyunOnsIsEnabled == "1") { try { //初始化服务类列表和服务类标签列表 InitializeProperties(); //判断生产者的服务类是否存在 if (ONSProducerServiceList.Count > 0) { //输出生产服务类类名 ONSProducerServiceList.ForEach(service => DebugUtil.Debug("生产者的服务类:" + service.GetType().FullName)); //判断生产者实例列表是否为空 if (ONSProducerList != null && ONSProducerList.Count > 0) { ONSProducerList.ForEach(producer => { //启动上游事务生产者 producer.start(); DebugUtil.Debug("Topic:" + producer.Topic + ",ProducerId(" + producer.Type.ToString() + @"):" + producer.ProducerId + @"生产者.start()"); }); } } //判断消费者的服务类是否存在 if (ONSConsumerServiceList.Count > 0) { //输出消费服务类类名 ONSConsumerServiceList.ForEach(service => DebugUtil.Debug("消费者的服务类:" + service.GetType().FullName)); //判断消费者实例列表是否为空 if (ONSConsumerList != null && ONSConsumerList.Count > 0) { ONSConsumerList.ForEach(consumer => { string tags = GetConsumerServiceTags(consumer.TagList); consumer.subscribe(consumer.Topic, tags); //启动上游事务生产者 consumer.start(); DebugUtil.Debug("Topic:" + consumer.Topic + ",ConsumerId(" + consumer.Type.ToString() + @"):" + consumer.ConsumerId + @"消费者.start(),topic:" + consumer.Topic + ",tags:" + tags); }); } } //延时若干毫秒 Thread.Sleep(1000); DebugUtil.Debug(_Environment + "." + _ApplicationAlias + ".ONSHelper.Initialize最后延迟1000毫秒"); } catch (Exception e) { //记录本地错误日志 DebugUtil.Debug(_Environment + "." + _ApplicationAlias + ".ONSHelper.Initialize出错:" + e.ToString()); //记录FATAL日志,发送FATAL目前出错 //SaveLog(LogTypeEnum.FATAL, "ONSHelper", "Initialize", _Environment + "." + _ApplicationAlias + ".ONSHelper.Initialize出错:" + e.ToString()); //发送邮件 SendDebugMail(_Environment + "." + _ApplicationAlias + ".ONSHelper.Initialize出错", ":" + e.ToString()); } } }
public static bool React(Message value, Type classType) { bool needToCommit = false; string failureReason = ""; string topic = ""; string tag = ""; string pid = ""; string cid = ""; string key = ""; string type = ""; string body = ""; string method = ""; string requestTraceId = ""; string shardingKey = ""; Enum topicTag = null; int consumedTimes = 0; //尝试找到消费者服务类实例来消费 try { topic = value.getTopic(); tag = value.getTag(); pid = "PID_" + value.getTopic().ToUpper(); cid = ("CID_" + topic + "_" + _ApplicationAlias + "_" + classType.Name).ToUpper(); key = value.getKey(); type = value.getUserProperties("type"); body = value.getMsgBody(); body = Base64Util.Decode(body); requestTraceId = value.getUserProperties("requestTraceId") ?? ""; shardingKey = value.getUserProperties("shardingKey") ?? ""; object parameter; /* * //在ONSConsumerServiceList中找到能匹配TopicTag的消费者服务类实例 * object service = ONSHelper.ONSConsumerServiceList.Where(s => * { * string className = s.GetType().Name; * IAbstractConsumerService iservice = (IAbstractConsumerService)s; * Enum[] topicTagList = iservice.TopicTagList; * if (topicTagList != null) * { * //需要同时判断topic和tag都匹配 * topicTag = topicTagList.Where(tt => * { * string serviceTopic = (_Environment + "_" + tt.GetType().Name).ToUpper(); * string serviceTag = tt.ToString(); * return (topic.ToUpper() == serviceTopic) && (tag.ToUpper() == serviceTag.ToUpper()); * }).FirstOrDefault(); * * if (topicTag != null) * { * return true; * } * } * return false; * }).FirstOrDefault(); * //*/ object service = ONSHelper.ONSConsumerServiceList.Where(s => s.GetType().Name == classType.Name).FirstOrDefault(); //如果消费者服务类实例存在则消费消息 if (service != null) { //获取消费服务类的核心方法(即开发者自己实现的方法) method = service.GetType().FullName + ".ProcessCore"; //获取内部方法(此方法是受保护的,因此获取MethodInfo复杂一些) MethodInfo methodInfo = service.GetType().GetMethod("InternalProcess", BindingFlags.NonPublic | BindingFlags.Instance); //获取参数列表,实际就一个泛型T参数 ParameterInfo[] parameterInfos = methodInfo.GetParameters(); //判断类型 if (parameterInfos[0].ParameterType.ToString().ToLower() == "system.string") { //string类型 parameter = body; } else { //自定义类型 parameter = JsonConvert.DeserializeObject(body, parameterInfos[0].ParameterType); } //执行InternalProcess方法 needToCommit = (bool)methodInfo.Invoke(service, new object[] { parameter }); if (needToCommit == false) { failureReason = method + "执行返回false,可能是该方法逻辑上返回false,也可能是该方法执行时它自己捕捉到错误返回false"; } } else { //找不到消费者实例对象 DebugUtil.Debug("MESSAGE_KEY:" + key + ",找不到消费者实例,topic:" + topic + ",tag:" + tag + ""); } } catch (Exception e) { failureReason = "尝试消费时,key=" + key + ",捕获异常:" + e.ToString(); //DebugUtil.Debug(e.ToString()); } //*/ //尝试记录消费信息 try { RedisTool RT = new RedisTool(_AliyunOnsRedisDbNumber, _RedisExchangeHosts); if (RT != null) { string consumedTimesKey = key + "_" + cid + "_consumedtimes"; string consumedTimesValue = RT.StringGet(consumedTimesKey); if (string.IsNullOrEmpty(consumedTimesValue)) { //不存在key,则新增 consumedTimes++; bool isSaved = RT.StringSet(consumedTimesKey, consumedTimes, TimeSpan.FromSeconds(_AliyunOnsRedisServiceResultExpireIn)); if (!isSaved) { //设置消费次数失败 failureReason = "设置消费次数失败。"; } } else { //存在key,则递增 int.TryParse(consumedTimesValue, out consumedTimes); consumedTimes++; RT.StringIncrement(consumedTimesKey); } } else { failureReason = "尝试通过redis更新生产方法执行次数时,无法实例化redis工具类,可能是redis服务暂不可用。"; } } catch (Exception e) { failureReason = "尝试通过redis更新生产方法执行次数时,捕捉异常:" + e.ToString(); //DebugUtil.Debug(e.ToString()); } finally { try { //写ConsumerData数据 ConsumerData consumerData = new ConsumerData(requestTraceId); //string data; consumerData.ApplicationAlias = _ApplicationAlias; consumerData.Accomplishment = needToCommit; consumerData.Topic = topic; consumerData.Tag = tag; consumerData.ProducerId = pid; consumerData.ConsumerId = cid; consumerData.Key = key; consumerData.Type = type; consumerData.Message = body; consumerData.Method = method; consumerData.FailureReason = failureReason; consumerData.ConsumedStatus = needToCommit ? "Commit" : "Reconsume"; consumerData.ConsumedTimes = consumedTimes; consumerData.ShardingKey = shardingKey; NestDataHelper.WriteData(consumerData); } catch (Exception e) { ONSHelper.SendDebugMail(_Environment + "." + _ApplicationAlias + "环境发送下游消费日志失败", "消息key:" + key + ",错误信息如下:" + e.ToString()); } } return(needToCommit); }
/// <summary> /// 生成消费者实例 /// </summary> /// <typeparam name="T">消费者服务基类类型</typeparam> /// <param name="assembly">消费者服务类所在程序集</param> /// <param name="type">消费者服务类的类型</param> /// <param name="messageType">消息类型BASE,ORDER,TRAN</param> /// <param name="func">生成消费者实例的委托代码</param> internal static void CreateConsumer <T>(Assembly assembly, Type type, ONSMessageType messageType, Func <ONSFactoryProperty, string, string, Type, IONSConsumer> func) { if (type.BaseType.FullName.IndexOf(typeof(T).Name) >= 0) { //添加到消费者服务类实例列表 //ONSConsumerServiceList.Add(type); object service = assembly.CreateInstance(type.FullName); ONSConsumerServiceList.Add(service); //获取服务接口 IAbstractConsumerService iservice = (IAbstractConsumerService)service; //获取枚举数组对象 Enum[] topicTagList = iservice.TopicTagList; if (topicTagList != null && topicTagList.Length > 0) { foreach (Enum topicTag in topicTagList) { string serviceTopic = topicTag.GetType().Name; string serviceTag = topicTag.ToString(); string className = type.Name; string topic = (_Environment + "_" + serviceTopic).ToUpper(); //CID修改成GID //string consumerId = ("CID_" + topic + "_" + _ApplicationAlias + "_" + className).ToUpper(); string consumerId = ("GID_" + topic + "_" + _ApplicationAlias + "_" + className).ToUpper(); DebugUtil.Debug("consumerId:" + consumerId); IONSConsumer consumer = ONSConsumerList.Where(c => c.Type == messageType.ToString() && c.ConsumerId == consumerId).FirstOrDefault(); if (consumer == null) { //实例化ONSFactoryProperty ONSFactoryProperty onsConsumerFactoryProperty = new ONSFactoryProperty(); onsConsumerFactoryProperty.setFactoryProperty(ONSFactoryProperty.AccessKey, _AliyunOnsAccessKey); onsConsumerFactoryProperty.setFactoryProperty(ONSFactoryProperty.SecretKey, _AliyunOnsSecretKey); onsConsumerFactoryProperty.setFactoryProperty(ONSFactoryProperty.ConsumerId, consumerId); //onsConsumerFactoryProperty.setFactoryProperty(ONSFactoryProperty.PublishTopics, _ONSTopic); if (_AliyunOnsConsumerLogPath != "") { if (Directory.Exists(_AliyunOnsConsumerLogPath)) { onsConsumerFactoryProperty.setFactoryProperty(ONSFactoryProperty.LogPath, _AliyunOnsConsumerLogPath); } } //获取消费者IONSConsumer consumer = func(onsConsumerFactoryProperty, topic, consumerId, type); //记录消费者初始化信息 DebugUtil.Debug("Topic:" + topic + ",ConsumerId(" + consumer.Type + "):" + consumer.ConsumerId + "消费者.new()"); //新增代理类ONSConsumer实例到ONSConsumerList中 ONSConsumerList.Add(consumer); } if (!consumer.TagList.Contains(serviceTag)) { consumer.TagList.Add(serviceTag); } } } } }
public override TransactionStatus check(Message value) { Console.WriteLine("check topic: {0}, tag:{1}, key:{2}, msgId:{3},msgbody:{4}, userProperty:{5}", value.getTopic(), value.getTag(), value.getKey(), value.getMsgID(), value.getBody(), value.getUserProperties("VincentNoUser")); // 消息 ID(有可能消息体一样,但消息 ID 不一样。当前消息 ID 在控制台无法查询) //string msgId = value.getMsgID(); // 消息体内容进行 crc32, 也可以使用其它的如 MD5 // 消息 ID 和 crc32id 主要是用来防止消息重复 // 如果业务本身是幂等的, 可以忽略,否则需要利用 msgId 或 crc32Id 来做幂等 // 如果要求消息绝对不重复,推荐做法是对消息体 body 使用 crc32或 md5来防止重复消息 string transactionType = "Checker"; bool serviceResult = false; TransactionStatus transactionStatus = TransactionStatus.Unknow; string failureReason = ""; string topic = ""; string tag = ""; string pid = ""; string key = ""; string body = ""; string checkerMethodParameter = ""; string method = ""; string requestTraceId = ""; string producedTimesKey = ""; int producedTimes = 0; try { topic = value.getTopic(); tag = value.getTag(); pid = "PID_" + value.getTopic().ToUpper(); key = value.getKey(); body = value.getMsgBody(); requestTraceId = value.getUserProperties("requestTraceId") ?? ""; checkerMethodParameter = Base64Util.Decode(body); producedTimesKey = key + ":" + _ApplicationAlias + ":producedtimes"; DebugUtil.Debug("MESSAGE_KEY:" + value.getKey() + ",ONSLocalTransactionChecker.check.key " + key); /*在LocalTransactionExecuterExecute.execute方法中返回unknown,之后会调用LocalTransactionChecker.check方法 * 如果此消息topic被多个网站用作生产者,那么会导致此message会被随即传递到其他网站或当前网站中去时长调用check方法 * 这种传递紊乱的情况可以称为“同topic被多站点作为生产者导致check紊乱的现象” * 因此如果被传递到其他网站后,需要判断当前网站是否是消息的来源网站,如果不是则返回unknown,也不记录任何记录。 * 如果按同topic被5个网站作为生产者,那么当execute或check出错后,能正确来到消息原始网站的概率为20%,而每次调用check历经5秒 * 假设是随即平均发送到5个网站随即一个中,那么平均需要历经25秒后,才能来到原始网站,因此能来到消息原始网站的平均等待时间的公式为 * time=n*5秒,此处n为使用该topic的网站个数。 * //*/ int applicationAliasLength = _ApplicationAlias.Length; if (key.Substring(2, applicationAliasLength) != _ApplicationAlias) { return(transactionStatus); } DebugUtil.Debug("MESSAGE_KEY:" + value.getKey() + ",ONSLocalTransactionChecker.check.after try..."); DebugUtil.Debug("MESSAGE_KEY:" + value.getKey() + ",ONSLocalTransactionChecker.check.checkerMethod " + value.getUserProperties("checkerMethod")); DebugUtil.Debug("MESSAGE_KEY:" + value.getKey() + ",ONSLocalTransactionChecker.check.checkerMethodParameter " + value.getUserProperties("checkerMethodParameter")); if (ONSHelper.CheckerMethodDictionary.ContainsKey(value.getUserProperties("checkerMethod"))) { MethodInfo methodInfo = ONSHelper.CheckerMethodDictionary[value.getUserProperties("checkerMethod")]; Type type = methodInfo.ReflectedType; Assembly assembly = Assembly.GetAssembly(type); object service = assembly.CreateInstance(type.FullName); ParameterInfo[] parameterInfos = methodInfo.GetParameters(); //DebugUtil.Log(parameterInfos[0].ParameterType.ToString()); method = type.FullName + ".ProcessCore"; //判断类型 if (parameterInfos[0].ParameterType.ToString().ToLower() == "system.string") { //string类型 serviceResult = (bool)methodInfo.Invoke(service, new object[] { checkerMethodParameter }); } else { //自定义类型 object parameter = JsonConvert.DeserializeObject(checkerMethodParameter, parameterInfos[0].ParameterType); serviceResult = (bool)methodInfo.Invoke(service, new object[] { parameter }); } //DebugUtil.Log("MESSAGE_KEY:" + value.getKey() + ",ONSLocalTransactionChecker.check.body:" + body); if (serviceResult) { // 本地事务成功则提交消息 transactionStatus = TransactionStatus.CommitTransaction; } else { // 本地事务失败则回滚消息 transactionStatus = TransactionStatus.RollbackTransaction; failureReason = method + "执行返回serviceResult为false,可能是该方法逻辑上返回serviceResult为false,也可能是该方法执行时它自己捕捉到错误返回serviceResult为false"; } } else { // 不存在key则回滚消息,这里不能用RollbackTransaction,因为我们使用相同的topic在各个网站之间,这种机制会导致其他网站也会调用check方法,一旦RollbackTransaction,那么原先的网站不再重试调用check方法了,因此必须返回unknow transactionStatus = TransactionStatus.Unknow; failureReason = "CheckerFuncDictionary中不存在key:" + value.getUserProperties("checkerMethod"); DebugUtil.Debug("MESSAGE_KEY:" + value.getKey() + ",ONSLocalTransactionChecker.check.error:CheckerFuncDictionary中不存在key:" + value.getUserProperties("checkerMethod")); } //在写log之前先做base64解密 body = Base64Util.Decode(body); //事务已经执行,尝试通过redis更新生产方法执行次数,此处如果出错是可以容忍的,但是得把错误信息记录到ProduceData中 RedisTool RT = new RedisTool(_AliyunOnsRedisDbNumber, _RedisExchangeHosts); if (RT != null) { try { //获取已经生产次数 string producedTimesValue = RT.StringGet(producedTimesKey); //如果取不到 if (string.IsNullOrEmpty(producedTimesValue)) { //不存在key,则新增 producedTimes++; bool isSaved = RT.StringSet(producedTimesKey, producedTimes, TimeSpan.FromSeconds(_AliyunOnsRedisServiceResultExpireIn)); if (!isSaved) { transactionStatus = TransactionStatus.Unknow; } } else { //存在key,则递增 int.TryParse(producedTimesValue, out producedTimes); producedTimes++; RT.StringIncrement(producedTimesKey); } } catch (Exception e) { throw new Exception("事务已经执行,返回" + serviceResult + "。但是尝试通过redis更新生产方法执行次数时,捕捉异常:" + e.ToString()); } } else { //redis有问题,导致无法实例化工具类 throw new Exception("事务已经执行,返回" + serviceResult + "。但是尝试通过redis更新生产方法执行次数时,无法实例化redis工具类,可能是redis服务暂不可用。"); } } catch (Exception e) { failureReason = "事务消息在执行Checker.check方法时捕获异常:" + e.ToString(); DebugUtil.Debug(e.ToString()); } finally { ProducerData producerData = new ProducerData(requestTraceId); producerData.Accomplishment = transactionStatus != TransactionStatus.Unknow; producerData.ApplicationAlias = _ApplicationAlias; producerData.Topic = topic; producerData.Tag = tag; producerData.ProducerId = pid; producerData.Key = key; producerData.Type = ONSMessageType.TRAN.ToString(); producerData.Message = checkerMethodParameter; producerData.TransactionType = transactionType; producerData.Method = method; producerData.ServiceResult = serviceResult; producerData.TransactionStatus = transactionStatus.ToString(); producerData.FailureReason = failureReason; producerData.ProducedTimes = producedTimes; producerData.ShardingKey = ""; NestDataHelper.WriteData(producerData); } return(transactionStatus); }