예제 #1
0
 /// <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发送异常,以后可以发送邮件,目前暂时不处理
     }
 }
예제 #2
0
        /// <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);
        }
예제 #3
0
        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);
        }
예제 #4
0
        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);
        }