/// <summary> /// 记录日志 /// </summary> /// <param name="key">消息的key</param> /// <param name="message">消息的正文内容,即model序列化后的内容</param> /// <param name="shardingKey">顺序消息要用的shardingKey,其他消息留空字符串</param> /// <param name="transactionStatus">事务消息的返回类型,其他消息留空字符串</param> /// <param name="failureReason">失败原因,通常有错误时会写入错误原因</param> /// <param name="accomplishment">是否已经完成消息</param> /// <param name="producedTimes">上游执行次数</param> /// <param name="serviceResult">上游执行结果</param> protected void LogData(string key, string message, string shardingKey, string transactionStatus, string failureReason, bool accomplishment, int producedTimes, bool serviceResult) { try { ProducerData producerData = new ProducerData(this.GetRequestTraceId()); producerData.Accomplishment = accomplishment; producerData.ApplicationAlias = _ApplicationAlias; producerData.Topic = this.Topic; producerData.Tag = this.Tag; producerData.ProducerId = this.Pid; producerData.Key = key; producerData.Type = this.MessageType.ToString(); producerData.Message = Base64Util.Decode(message); producerData.TransactionType = ""; producerData.Method = this.GetType().Name; producerData.ServiceResult = serviceResult; producerData.TransactionStatus = transactionStatus; producerData.FailureReason = failureReason; producerData.ProducedTimes = producedTimes; producerData.ShardingKey = shardingKey; producerData.ServerIp = ONSHelper.GetServerIp(); NestDataHelper.WriteData(producerData); } catch (Exception e) { //如果es发送异常,以后可以发送邮件,目前暂时不处理 } }
/// <summary> /// 尝试发送消息 /// </summary> /// <param name="producer">生产者实例</param> /// <param name="message">消息实例</param> /// <param name="parameter">发送所需参数</param> /// <param name="key">消息的唯一标识</param> /// <param name="errorTimes">本系统自己自己执行时发现出错后,记录的错误次数(此参数不适用于生产次数)</param> /// <returns></returns> protected SendResultONS TryToSend(IONSProducer producer, Message message, object parameter, string key, int errorTimes) { SendResultONS sendResultONS = null; try { sendResultONS = producer.send(message, parameter); } catch (Exception e) { //错误计数累加 errorTimes++; //无论是阿里云服务不可用,还是生产者挂了,一共3次机会,即执行1次,然后最多重试两次 if (errorTimes < 3) { //如果生产者挂了 if (e.ToString().IndexOf("Your producer has been shutdown.") >= 0) { //置空producer producer = null; //重新获取producer并启动 producer = GetProducer(); //等待3秒 System.Threading.Thread.Sleep(3000); } else { //如果是阿里云服务不可用,等待1秒 System.Threading.Thread.Sleep(1000); } //递归 sendResultONS = TryToSend(producer, message, parameter, key, errorTimes); } else { //重试2次都还是失败 string className = this.GetType().Name; string methodName = "Process"; string errorMessage = _Environment + "." + _ApplicationAlias + "." + className + "." + methodName + "尝试发送时,出现了第" + errorTimes + "次出错,key=" + key + ":" + e.ToString(); //记录本地错误日志 DebugUtil.Debug(errorMessage); //记录FATAL日志 ONSHelper.SaveLog(LogTypeEnum.FATAL, className, methodName, errorMessage); //发送邮件 ONSHelper.SendDebugMail(_Environment + "." + _ApplicationAlias + "." + className + "." + methodName + "尝试发送时出错", errorMessage); //抛出异常 throw new Exception(errorMessage); } } return(sendResultONS); }
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(); pid = "GID_" + value.getTopic().ToUpper(); //cid = ("CID_" + topic + "_" + _ApplicationAlias + "_" + classType.Name).ToUpper(); cid = ("GID_" + 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") ?? ""; //由于CallContext中不存在TraceId,则直接添加,以便在非http请求环境下获取到TraceId CallContext.LogicalSetData("TraceId", requestTraceId); object parameter; RedisTool RT = new RedisTool(_AliyunOnsRedisDbNumber, _RedisExchangeHosts); if (RT != null) { string antirepeatKey = key + "_" + cid + "_antirepeat"; DateTime dateTime = DateTime.Now; TimeSpan timeSpan = dateTime.AddSeconds(1) - dateTime; bool setResult = RT.StringSet(antirepeatKey, "1", timeSpan, When.NotExists); if (!setResult) { //如果设置失败,则说明key已经存在,本消息属于重复消费,直接返回true,不执行后面的消费逻辑 return(true); } /* * //在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 + ""); } } else { failureReason = "尝试通过redis写入“key来做避免重复消费的判断”时,无法实例化redis工具类,可能是redis服务暂不可用。"; } } catch (Exception e) { failureReason = "尝试消费时,key=" + key + ",捕获异常:" + e.ToString(); //DebugUtil.Debug(e.ToString()); } //*/ //尝试记录消费信息 try { //获取redis客户端工具实例 RedisTool RT = new RedisTool(_AliyunOnsRedisDbNumber, _RedisExchangeHosts); if (RT != null) { string consumedTimesKey = key + "_" + cid + "_consumedtimes"; //设置消费次数并自增 consumedTimes = (int)RT.StringIncrement(consumedTimesKey); //获取过期时间 TimeSpan?timeSpan = RT.KeyTimeToLive(consumedTimesKey); if (timeSpan == null) { //没设置过过期时间,则设置过期时间 RT.KeyExpire(consumedTimesKey, TimeSpan.FromSeconds(_AliyunOnsRedisServiceResultExpireIn)); } } 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; consumerData.ServerIp = ONSHelper.GetServerIp(); NestDataHelper.WriteData(consumerData); } catch (Exception e) { ONSHelper.SendDebugMail(_Environment + "." + _ApplicationAlias + "环境发送下游消费日志失败", "消息key:" + key + ",错误信息如下:" + e.ToString()); } } return(needToCommit); }
public override TransactionStatus execute(Message value) { Console.WriteLine("execute 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 = "Executer"; bool serviceResult = false; TransactionStatus transactionStatus = TransactionStatus.Unknow; string failureReason = ""; string topic = ""; string tag = ""; string pid = ""; string key = ""; string body = ""; string executerMethodParameter = ""; string method = ""; string requestTraceId = ""; string producedTimesKey = ""; int producedTimes = 0; try { topic = value.getTopic(); tag = value.getTag(); //pid = "PID_" + value.getTopic().ToUpper(); pid = "GID_" + value.getTopic().ToUpper(); key = value.getKey(); body = value.getMsgBody(); requestTraceId = value.getUserProperties("requestTraceId") ?? ""; executerMethodParameter = Base64Util.Decode(body); producedTimesKey = key + ":" + _ApplicationAlias + ":producedtimes"; DebugUtil.Debug("MESSAGE_KEY:" + value.getKey() + ",ONSLocalTransaction" + transactionType + "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() + ",ONSLocalTransactionExecuter.execute.after try..."); DebugUtil.Debug("MESSAGE_KEY:" + value.getKey() + ",ONSLocalTransactionExecuter.execute.executerMethod " + value.getUserProperties("executerMethod")); DebugUtil.Debug("MESSAGE_KEY:" + value.getKey() + ",ONSLocalTransactionExecuter.execute.executerMethodParameter " + executerMethodParameter); if (ONSHelper.ExecuterMethodDictionary.ContainsKey(value.getUserProperties("executerMethod"))) { MethodInfo methodInfo = ONSHelper.ExecuterMethodDictionary[value.getUserProperties("executerMethod")]; 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[] { executerMethodParameter }); } else { //自定义类型 object parameter = JsonConvert.DeserializeObject(executerMethodParameter, parameterInfos[0].ParameterType); serviceResult = (bool)methodInfo.Invoke(service, new object[] { parameter }); } //DebugUtil.Log("MESSAGE_KEY:" + value.getKey() + ",ONSLocalTransactionExecuter.execute.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方法了,因此必须返回 transactionStatus = TransactionStatus.Unknow; failureReason = "ExecuterMethodDictionary中不存在key:" + value.getUserProperties("executerMethod"); DebugUtil.Debug("MESSAGE_KEY:" + value.getKey() + ",ONSLocalTransactionExecuter.execute.error:ExecuterMethodDictionary中不存在key:" + value.getUserProperties("executerMethod")); } //事务已经执行,尝试通过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 = "事务消息在执行Executer.execute方法时捕获异常:" + 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 = executerMethodParameter; producerData.TransactionType = transactionType; producerData.Method = method; producerData.ServiceResult = serviceResult; producerData.TransactionStatus = transactionStatus.ToString(); producerData.FailureReason = failureReason; producerData.ProducedTimes = producedTimes; producerData.ShardingKey = ""; producerData.ServerIp = ONSHelper.GetServerIp(); NestDataHelper.WriteData(producerData); } return(transactionStatus); }