线程安全使用(四) [.NET] 简单接入微信公众号开发:实现自动回复 [C#]C#中字符串的操作 自行实现比dotcore/dotnet更方便更高性能的对象二进制序列化 自已动手做高性能消息队列 自行实现高性能MVC WebAPI 面试题随笔 字符串反转

线程安全使用(四)

 

这是时隔多年第四篇,主要是因为身在东软受内网限制,好多文章就只好发到东软内部网站,懒的发到外面,现在一点点把在东软写的文章给转移出来。

这里主要讲解下CancellationTokenSource,CancellationTokenSource是用于取消线程,具体使用起来有点另类:首先定义实体,然后将其下的属性ToKen传递给线程,当需要取消线程时,调用下Cancel()方法。例子我依然采用了MSDN的例子,但我做了一些修改,这个例子虽然看起来挺复杂,但还是记录了许多内容。

由于不好理解,我就粗略讲解下:

Task<double> fTask = factory.ContinueWhenAll(tasks.ToArray(), 上面是创建任务,创建10个线程,并且线程中增加了判断,如果随即数等于0就取消该线程。

 再介绍下factory.ContinueWhenAll,他包含两个参数Task[] tasks,
Action<Task[]> continuationAction。MSDN的解释是:

ContinueWhenAll 方法执行 continuationAction 委托,在 tasks 数组的所有任务完成后,无论它们的完成状态。

MSDN上就这个翻译的还不错,其他的基本可以无视了。。。

继续看代码,代码中增加了try catch,这是为什么呢,看下ContinueWhenAll英文解释:

The exception that is thrown when the tasks array is empty

这里千万不能看中文解释,不然你会凌乱的。看了英文解释就懂了,让任务为空就抛异常。

那么为什么任务为空呢,因为任务已经被取消了啊,所以为空了。具体代码如下。

复制代码
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
    public static void Main()
    {
        // Define the cancellation token.
        CancellationTokenSource source = new CancellationTokenSource();
        CancellationToken token = source.Token;
        Random rnd = new Random();
        Object lockObj = new Object();
        List<Task<int[]>> tasks = new List<Task<int[]>>();
        TaskFactory factory = new TaskFactory(token);
        for (int taskCtr = 0; taskCtr <= 10; taskCtr++)
        {
            int iteration = taskCtr + 1;
            tasks.Add(factory.StartNew(() =>
            {
                int value;
                int[] values = new int[10];
                for (int ctr = 1; ctr <= 10; ctr++)
                {
                    lock (lockObj)
                    {
                        value = rnd.Next(0, 101);
                    }
                    if (value == 0)
                    {
                        source.Cancel();
                        Console.WriteLine("Cancelling at task {0}", iteration);
                        break;
                    }
                   
                    values[ctr - 1] = value;
                }
                Console.WriteLine("NO Cancel at task {0}", iteration);
                return values;
            }, token));
        }
        try
        {
            Task<double> fTask = factory.ContinueWhenAll(tasks.ToArray(),
                                                         (results) =>
                                                         {
                                                             Console.WriteLine("Calculating overall mean...");
                                                             long sum = 0;
                                                             int n = 0;
                                                             foreach (var t in results)
                                                             {
                                                                 foreach (var r in t.Result)
                                                                 {
                                                                     sum += r;
                                                                     n++;
                                                                 }
                                                             }
                                                             return sum / (double)n;
                                                         }, token);
            Console.WriteLine("The mean is {0}.", fTask.Result);
        }
        catch (AggregateException ae)
        {
            foreach (Exception e in ae.InnerExceptions)
            {
                if (e is TaskCanceledException)
                    Console.WriteLine("Unable to compute mean: {0}",
                                      ((TaskCanceledException)e).Message);
                else
                    Console.WriteLine("Exception: " + e.GetType().Name);
            }
        }
        Console.ReadLine();
        
    }
}
复制代码

显示结果图片,每次的结果都不一样的,所以我也是运行了好几次,看这个结果会发现一件事,线程只执行了两个,即当线程2中调用Cancel后,其他线程也被取消了。

 

[.NET] 简单接入微信公众号开发:实现自动回复

 

简单接入微信公众号开发:实现自动回复

一、前提

  先申请微信公众号的授权,找到或配置几个关键的信息(开发者ID、开发者密码、IP白名单、令牌和消息加解密密钥等)。

 

二、基本配置信息解读

  开发者ID:固定的;

  开发者密码:自己扫一下就可以看到;

  IP白名单:设置自己配置服务器的地址;

  服务器地址(URL):稍后详解;

  令牌:随便写,按规则;

  消息加解密密钥:随便写,或者随机生成;

 

三、配置服务器地址(URL)

  服务器地址(URL)应该怎么配置呢?图片上的配置的地址是:http://www.nidie.com.cn/wechat ,那么它对应的控制器应该是怎么样子的呢?

 

  在这里,我使用了第三方的包,需要通过 Nuget 来安装:

  <package id="Senparc.Weixin" version="4.22.1" targetFramework="net471" />
  <package id="Senparc.Weixin.MP" version="14.14.0" targetFramework="net471" />
  <package id="Senparc.Weixin.MP.MVC" version="5.4.5" targetFramework="net471" />

 

  接下来新建一个 WeChatController.cs:

复制代码
using System.Threading.Tasks;
using System.Web.Mvc;
using Senparc.Weixin.MP;
using Senparc.Weixin.MP.Entities.Request;
using Senparc.Weixin.MP.MvcExtension;
using Wen.MvcDemo.Application.WeChat.MessageHandlers.CustomMessageHandlers;
using Wen.MvcDemo.Infrastructure.Configuration;

namespace Wen.MvcDemo.Web.Controllers
{
    /// <summary>
    /// 微信
    /// </summary>
    public class WeChatController : Controller
    {
        #region private static field

        private static readonly string AppId = ApplicationSettingsFactory.GetApplicationSettings().WeChatAppId;

        private static readonly string EncodingAesKey = ApplicationSettingsFactory.GetApplicationSettings().WeChatEncodingAesKey;

        private static readonly string Token = ApplicationSettingsFactory.GetApplicationSettings().WeChatToken;

        #endregion private static field

        /// <summary>
        /// 微信后台验证地址
        /// </summary>
        /// <param name="signature"></param>
        /// <param name="timestamp"></param>
        /// <param name="nonce"></param>
        /// <param name="echostr"></param>
        /// <returns></returns>
        [HttpGet]
        public ActionResult Index(string signature, string timestamp, string nonce, string echostr)
        {
            return Content(echostr);
        }

        /// <summary>
        /// 处理用户发送消息后
        /// </summary>
        /// <param name="postModel"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<ActionResult> Index(PostModel postModel)
        {
            //校验签名
            if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, Token))
                return new WeixinResult("参数错误!");

            postModel.AppId = AppId;
            postModel.EncodingAESKey = EncodingAesKey;
            postModel.Token = Token;

            //接收消息,自定义 MessageHandler,对微信请求进行处理
            var messageHandler = new CustomMessageHandler(Request.InputStream, postModel);

            //执行微信处理过程
            await messageHandler.ExecuteAsync();
            //返回处理结果
            return new FixWeixinBugWeixinResult(messageHandler);
        }
    }
}
复制代码

   

  代码分析:

  里面主要包含了三个静态字段和两个 Index 方法。

  其中静态字段对应的就是基本配置信息里面对应的几个参数,平常都是写入配置文件中来进行读取。

 

  其中一个标识特性为 HttpGet 的 Index 方法,它是用来通过服务器地址(URL)验证的,当你成功部署到你的服务器后,再点击提交认证就可以通过了。注意的是,需要将代码先提交到服务器,再进行提交确认。

 

   可能你看到该方法好像只返回 return Content(echostr); 这么简单的代码感到质疑:这能行吗?“我”记得官方文档好像要调用很复杂的方法进行校验才行的!?

  上图就是官方文档,但是我只关心通过配置提交认证,也就是我用红圈着色的部分,即原样返回 echostr 参数内容即可。

  第二个是实现 Post 请求的 Index 方法,在这里我进行了签名校验(也就是上图文档的校验逻辑),因为使用了第三方库,我们知道传哪些参数过去就可以了,签名通过后就是读取请求信息并进行后续处理的步骤了。

 

四、请求处理

  在上面的处理请求信息的代码中,我自定义了一个类 CustomMessageHandler 来处理消息。

复制代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Senparc.Weixin.MP.AppStore;
using Senparc.Weixin.MP.AppStore.Utility;
using Senparc.Weixin.MP.Entities;
using Senparc.Weixin.MP.Entities.Request;
using Senparc.Weixin.MP.MessageHandlers;

namespace Wen.MvcDemo.Application.WeChat.MessageHandlers.CustomMessageHandlers
{
    /// <summary>
    /// 自定义消息处理
    /// </summary>
    public class CustomMessageHandler : MessageHandler<CustomMessageContext>
    {
        public CustomMessageHandler(Stream inputStream, PostModel postModel = null, int maxRecordCount = 0, DeveloperInfo developerInfo = null) : base(inputStream, postModel, maxRecordCount, developerInfo)
        {
        }

        public CustomMessageHandler(XDocument requestDocument, PostModel postModel = null, int maxRecordCount = 0, DeveloperInfo developerInfo = null) : base(requestDocument, postModel, maxRecordCount, developerInfo)
        {
        }

        public CustomMessageHandler(RequestMessageBase requestMessageBase, PostModel postModel = null, int maxRecordCount = 0, DeveloperInfo developerInfo = null) : base(requestMessageBase, postModel, maxRecordCount, developerInfo)
        {
        }

        /// <summary>
        /// 默认
        /// </summary>
        /// <param name="requestMessage"></param>
        /// <returns></returns>
        public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
        {
            var responseMessage = base.CreateResponseMessage<ResponseMessageText>(); //ResponseMessageText也可以是News等其他类型
            responseMessage.Content = $"您好,目前使用的微信公众号仍处于开发阶段,现已接入了【图灵机器人】,您可以尝试和他(她)交流。";
            return responseMessage;
        }
    }
}
复制代码

  

  CustomMessageHandler 类继承了 MessageHandler 类,然后重写了 DefaultResponseMessage() 方法,返回固定的文本值。base.CreateResponseMessage<T>() 方法可以返回多种不同类型的结果值,如:

复制代码
ResponseMessageText - 对应文本消息

ResponseMessageNews - 对应图文消息

ResponseMessageMusic - 对应音乐消息

ResponseMessageXXX - 其他类型以此类推
复制代码

  

  上述方法只是一种默认的消息处理,我们也可以专门针对不同的请求类型做出不同的回应,比如重写 OnTextRequest(),其它重载需要自己观察基类成员:

复制代码
        /// <summary>
        /// 文本请求
        /// </summary>
        /// <param name="requestMessage"></param>
        /// <returns></returns>
        public override IResponseMessageBase OnTextRequest(RequestMessageText requestMessage)
        {
            var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
            responseMessage.Content = $"您刚才发送的文字信息是:{requestMessage.Content}。";  //\r\n用于换行,requestMessage.Content即用户发过来的文字内容
            return responseMessage;
        }
复制代码

  因为在继承 MessageHandler<T> 类的同时,我创建了一个 CustomMessageContext 自定义消息上下文的类,该类内容如下,并没有包含其它方法,直接继承 MessageContext<IRequestMessageBase, IResponseMessageBase> 即可:

复制代码
using Senparc.Weixin.Context;
using Senparc.Weixin.MP.Entities;

namespace Wen.MvcDemo.Application.WeChat.MessageHandlers.CustomMessageHandlers
{
    /// <summary>
    /// 自定义消息上下文
    /// </summary>
    public class CustomMessageContext : MessageContext<IRequestMessageBase, IResponseMessageBase>
    {
    }
}
复制代码

  

  这样,就完成了所有代码的编写,现在我们再次把代码部署好之后就可以开始进行测试了。

  因为我除了部署自己的站点之外,还接入了【图灵机器人】回复,所以你看到了两条信息。

 

 

[C#]C#中字符串的操作

 

1.Replace(替换字符):
public string Replace(char oldChar,char newChar);在对象中寻找oldChar,如果寻找到,就用newChar将oldChar替换掉。
如:
            string st = "abcdef";
            string newstring = st.Replace('a', 'x');
            Console.WriteLine(newstring);   //即:xbcdef

public string Replace(string oldString,string newString);在对象中寻找oldString,如果寻找到,就用newString将oldString替换掉。
如:
            string st = "abcdef";
            string newstring = st.Replace("abc", "xyz");
            Console.WriteLine(newstring);   //即:xyzdef


2.Remove(删除字符):
public string Remove(int startIndex);从startIndex位置开始,删除此位置后所有的字符(包括当前位置所指定的字符)。
如:  
     string st = "abcdef";
            string newstring = st.Remove(4);
            Console.WriteLine(newstring);  //即:abcd

public string Remove(int startIndex,int count);从startIndex位置开始,删除count个字符。
如:  
     string st = "abcdef";
            string newstring = st.Remove(4,1);
            Console.WriteLine(newstring);  //即:abcdf


3.Substring(字符串提取):
public string Substring(int startIndex);从startIndex位置开始,提取此位置后所有的字符(包括当前位置所指定的字符)。
如:  
     string st = "abcdef";
            string newstring = st.Substring(2);
            Console.WriteLine(newstring);  //即:cdef

public string Substring(int startIndex,int count);从startIndex位置开始,提取count个字符。
如:  
     string st = "abcdef";
            string newstring = st.Substring(2,2);
            Console.WriteLine(newstring);  //即:cd

4.Trim(清空空格):
public string Trim ():将字符串对象包含的字符串两边的空格去掉后返回。
public string Trim ( params char[] trimChars ):从此实例的开始和末尾移除数组中指定的一组字符的所有匹配项。
如:
     string st ="abcdef";
     string newstring = st.Trim(new char[] {'a'});//寻找st字符串中开始与末尾是否有与'a'匹配,如有,将其移除。
     Console.WriteLine(newstring); //即:bcdef
注:如果字符串为"aaaabcdef",返回依然为bcdef。当移除第一个a时,开始依然为a,继续移除,直到没有。
public string TrimEnd ( params char[] trimChars ):对此实例末尾与指定字符进行匹配,true则移除
public string TrimStart ( params char[] trimChars ):对此实例开始与指定字符进行匹配,true则移除

5.ToLower(转换大小写)

public string ToLower():将字符串对象包含的字符串中的大写全部转换为小写。

6.IndexOf(获取指定的字符串的开始索引)
public int IndexOf (sring field):在此实例中寻找field,如果寻找到,返回开始索引,反之,返回-1。
如:
       string st = "abcdef";
            int num=st.IndexOf("bcd");
            Console.WriteLine(num);  //即:1

7.Equals(是否相等)
public bool Equals (string value):比较调用方法的字符串对象包含字符串和参数给出的对象是否相同,如相同,就返回true,反之,返回false。
如:        string a = "abcdef";
            bool b = a.Equals("bcdef");
            Console.WriteLine(b);//即:false

public bool Equals ( string value, StringComparison comparisonType ):比较调用方法的字符串对象包含字符串和参数给出的对象是否在不区分大小写的情况下相同,如相同,就返回true,反之,返回false,第二个参数将指定区域性、大小写以及比较所用的排序规则.
如:
       string a = "ABCDEF";
            bool b = a.Equals("abcdef",StringComparison.CurrentCultureIgnoreCase);
            Console.WriteLine(b);//即:true


8.Split(拆分)
public string[] Split ( params char[] separator ):根据separator 指定的没有字符分隔此实例中子字符串成为Unicode字符数组, separator可以是不包含分隔符的空数组或空引用。
public string[] Split ( char[] separator, int count ):参数count 指定要返回的子字符串的最大数量。 
如:
            string st = "语文|数学|英语|物理";
            string[] split = st.Split(new char[]{'|'},2);
            for (int i = 0; i < split.Length; i++)
            {
                Console.WriteLine(split[i]);
            }
注:count不填则全部拆分

public enum StringSplitOptions 
成员名称            说明
None                返回值包括含有空字符串的数组元素
RemoveEmptyEntries  返回值不包括含有空字符串的数组元素

如:
            string st = "语文|数学||英语|物理";
            string[] split = st.Split(new char[]{'|'},StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < split.Length; i++)
            {
                Console.WriteLine(split[i]);
            }
将StringSplitOptions枚举和Split()方法联系起来:
1.  public string[] Split ( char[] separator, StringSplitOptions options ):options指定StringSplitOptions枚举的RemoveEmptyEntries以省略返回的数组中的空数组元素,或指定StringSplitOptions枚举的None以包含返回的数组中的空数组元
2.  public string[] Split ( char[] separator, int count, StringSplitOptions options ) 
3.  public string[] Split ( string[] separator, StringSplitOptions options ) 
4.  public string[] Split ( string[] separator, int count, StringSplitOptions options )


9.Contains(判断是否存在)
public bool Contains(string text):如果字符串中出现text,则返回true,反之false,如果text为("")也返回true。
如:
 string st="语文数学英语";
 bool b=st.Contains("语文");
 Console.WriteLine(b);//true


10.EndsWith,StartsWith(判断字符串的开始或结束)
public bool EndsWith ( string value ):判断对象包含字符串是否以value指定的字符串结束,是则为 true;否则为 false。 
public bool EndsWith ( string value, StringComparison comparisonType ):第二个参数设置比较时区域、大小写和排序规则。
public bool StartsWith ( string value ):判断对象包含字符串是否以value指定的字符串开始,是则为 true;否则为 false。 
public bool StartsWith ( string value, StringComparison comparisonType ) :第二个参数设置比较时区域、大小写和排序规则。
如:
 string st="语文数学英语abc";
 bool b=st.EndsWith("英语ABC",StringComparison.CurrentCultureIgnoreCase);//第二个参数忽略大小比较。
 Console.WriteLine(b);//true

11.Insert(字符串插入)
public string Insert ( int startIndex, string value ):在指定的字符串下标为startIndex前插入字符串value。返回插入后的值。
如:
 string st="语文数学英语abc";
 string newst=st.Insert(6,"物理");//注:在指定索引“前”插入。
 Console.WriteLine(newst);//即:语文数学英语物理abc

 
 

自行实现比dotcore/dotnet更方便更高性能的对象二进制序列化

 

        二进制序列化可以方便快捷的将对象进行持久化或者网络传输,并且体积小、性能高,应用面甚至还要高于json的序列化;开始之前,先来看看dotcore/dotne自带的二进制序列化:C#中对象序列化和反序列化一般是通过BinaryFormatter类来实现的二进制序列化、反序列化的。

        BinaryFormatter序列化:

复制代码
1 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
2 
3 System.IO.MemoryStream memStream = new System.IO.MemoryStream();
4 
5 serializer.Serialize(memStream, request);
复制代码

        BinaryFormatter反序列化:

复制代码
 1  memStream.Position=0;
 2 
 3  System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserializer =
 4 
 5  new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
 6 
 7  object newobj = deserializer.Deserialize(memStream);
 8 
 9  memStream.Close();
10 
11  return newobj;
复制代码

        用着多了就发现BinaryFormatter有很多地方不妥,下面就来数数这个序列化的“三宗罪”:

        1.类名上面要加上[Serializable],不加不给序列化;正常的用法应该是序列化一个对象,不需的地方加上NonSerialized才合理吧;

        2.序列化byte[]结果非常大,使用System.Text.Encoding.UTF8.GetString(bytes)查看下,发现里面有一大堆的元数据;对比看看google的protobuf,pb为什么在网络上应用的越来越多,这和他本身序列化完后体积小有着绝大部门的原因;

        3.序列化对象需要完全一致,连类的命名空间都要相同,这点对于分面式开发的应用来说也是不可接受的;

        既然BinaryFormatter不好用,那就只能动手自行实现一个解决上述问题的二进制序列化方案;首先去掉[Serializable]这个标签,接着主要是分析对象,并定义对象序列化后的数据结构;这里的想法是按长度加内容的方式来定义,举个例子:使用int作为长度,来保存一个int值,序列化完应该是:4,0,0,0,1,0,0,0这样的一组bytes,同理可以将int、short、long、float、double、datetime、enum、array、string、class、generic等按照这个格式进行序列化,这里主要使用的是BitConverter、反射等来实现序列化与反序列化;

        序列化实现如下:

复制代码
  1         public static byte[] Serialize(object param)
  2         {
  3             List<byte> datas = new List<byte>();
  4 
  5             var len = 0;
  6 
  7             byte[] data = null;
  8 
  9             if (param == null)
 10             {
 11                 len = 0;
 12             }
 13             else
 14             {
 15                 if (param is string)
 16                 {
 17                     data = Encoding.UTF8.GetBytes((string)param);
 18                 }
 19                 else if (param is byte)
 20                 {
 21                     data = new byte[] { (byte)param };
 22                 }
 23                 else if (param is bool)
 24                 {
 25                     data = BitConverter.GetBytes((bool)param);
 26                 }
 27                 else if (param is short)
 28                 {
 29                     data = BitConverter.GetBytes((short)param);
 30                 }
 31                 else if (param is int)
 32                 {
 33                     data = BitConverter.GetBytes((int)param);
 34                 }
 35                 else if (param is long)
 36                 {
 37                     data = BitConverter.GetBytes((long)param);
 38                 }
 39                 else if (param is float)
 40                 {
 41                     data = BitConverter.GetBytes((float)param);
 42                 }
 43                 else if (param is double)
 44                 {
 45                     data = BitConverter.GetBytes((double)param);
 46                 }
 47                 else if (param is DateTime)
 48                 {
 49                     var str = "wl" + ((DateTime)param).Ticks;
 50                     data = Encoding.UTF8.GetBytes(str);
 51                 }
 52                 else if (param is Enum)
 53                 {
 54                     var enumValType = Enum.GetUnderlyingType(param.GetType());
 55 
 56                     if (enumValType == typeof(byte))
 57                     {
 58                         data = new byte[] { (byte)param };
 59                     }
 60                     else if (enumValType == typeof(short))
 61                     {
 62                         data = BitConverter.GetBytes((Int16)param);
 63                     }
 64                     else if (enumValType == typeof(int))
 65                     {
 66                         data = BitConverter.GetBytes((Int32)param);
 67                     }
 68                     else
 69                     {
 70                         data = BitConverter.GetBytes((Int64)param);
 71                     }
 72                 }
 73                 else if (param is byte[])
 74                 {
 75                     data = (byte[])param;
 76                 }
 77                 else
 78                 {
 79                     var type = param.GetType();
 80 
 81 
 82                     if (type.IsGenericType || type.IsArray)
 83                     {
 84                         if (TypeHelper.DicTypeStrs.Contains(type.Name))
 85                             data = SerializeDic((System.Collections.IDictionary)param);
 86                         else if (TypeHelper.ListTypeStrs.Contains(type.Name) || type.IsArray)
 87                             data = SerializeList((System.Collections.IEnumerable)param);
 88                         else
 89                             data = SerializeClass(param, type);
 90                     }
 91                     else if (type.IsClass)
 92                     {
 93                         data = SerializeClass(param, type);
 94                     }
 95 
 96                 }
 97                 if (data != null)
 98                     len = data.Length;
 99             }
100             datas.AddRange(BitConverter.GetBytes(len));
101             if (len > 0)
102             {
103                 datas.AddRange(data);
104             }
105             return datas.Count == 0 ? null : datas.ToArray();
106         }
复制代码

        反序列化实现如下:

复制代码
  1         public static object Deserialize(Type type, byte[] datas, ref int offset)
  2         {
  3             dynamic obj = null;
  4 
  5             var len = 0;
  6 
  7             byte[] data = null;
  8 
  9             len = BitConverter.ToInt32(datas, offset);
 10             offset += 4;
 11             if (len > 0)
 12             {
 13                 data = new byte[len];
 14                 Buffer.BlockCopy(datas, offset, data, 0, len);
 15                 offset += len;
 16 
 17                 if (type == typeof(string))
 18                 {
 19                     obj = Encoding.UTF8.GetString(data);
 20                 }
 21                 else if (type == typeof(byte))
 22                 {
 23                     obj = (data);
 24                 }
 25                 else if (type == typeof(bool))
 26                 {
 27                     obj = (BitConverter.ToBoolean(data, 0));
 28                 }
 29                 else if (type == typeof(short))
 30                 {
 31                     obj = (BitConverter.ToInt16(data, 0));
 32                 }
 33                 else if (type == typeof(int))
 34                 {
 35                     obj = (BitConverter.ToInt32(data, 0));
 36                 }
 37                 else if (type == typeof(long))
 38                 {
 39                     obj = (BitConverter.ToInt64(data, 0));
 40                 }
 41                 else if (type == typeof(float))
 42                 {
 43                     obj = (BitConverter.ToSingle(data, 0));
 44                 }
 45                 else if (type == typeof(double))
 46                 {
 47                     obj = (BitConverter.ToDouble(data, 0));
 48                 }
 49                 else if (type == typeof(decimal))
 50                 {
 51                     obj = (BitConverter.ToDouble(data, 0));
 52                 }
 53                 else if (type == typeof(DateTime))
 54                 {
 55                     var dstr = Encoding.UTF8.GetString(data);
 56                     var ticks = long.Parse(dstr.Substring(2));
 57                     obj = (new DateTime(ticks));
 58                 }
 59                 else if (type.BaseType == typeof(Enum))
 60                 {
 61                     var numType = Enum.GetUnderlyingType(type);
 62 
 63                     if (numType == typeof(byte))
 64                     {
 65                         obj = Enum.ToObject(type, data[0]);
 66                     }
 67                     else if (numType == typeof(short))
 68                     {
 69                         obj = Enum.ToObject(type, BitConverter.ToInt16(data, 0));
 70                     }
 71                     else if (numType == typeof(int))
 72                     {
 73                         obj = Enum.ToObject(type, BitConverter.ToInt32(data, 0));
 74                     }
 75                     else
 76                     {
 77                         obj = Enum.ToObject(type, BitConverter.ToInt64(data, 0));
 78                     }
 79                 }
 80                 else if (type == typeof(byte[]))
 81                 {
 82                     obj = (byte[])data;
 83                 }
 84                 else if (type.IsGenericType)
 85                 {
 86                     if (TypeHelper.ListTypeStrs.Contains(type.Name))
 87                     {
 88                         obj = DeserializeList(type, data);
 89                     }
 90                     else if (TypeHelper.DicTypeStrs.Contains(type.Name))
 91                     {
 92                         obj = DeserializeDic(type, data);
 93                     }
 94                     else
 95                     {
 96                         obj = DeserializeClass(type, data);
 97                     }
 98                 }
 99                 else if (type.IsClass)
100                 {
101                     obj = DeserializeClass(type, data);
102                 }
103                 else if (type.IsArray)
104                 {
105                     obj = DeserializeArray(type, data);
106                 }
107                 else
108                 {
109                     throw new RPCPamarsException("ParamsSerializeUtil.Deserialize 未定义的类型:" + type.ToString());
110                 }
111 
112             }
113             return obj;
114         }
复制代码

        其他详细的代码可以查看ParamsSerializeUtil.cs

        功能基本实现了,下面对比一下10000次的实体序列化与反序列化测试结果:

        实体代码:

复制代码
 1             var groupInfo = new GroupInfo()
 2             {
 3                 GroupID = 1,
 4                 IsTemporary = false,
 5                 Name = "yswenli group",
 6                 Created = DateTimeHelper.Now,
 7                 Creator = new UserInfo()
 8                 {
 9 
10                     ID = 1,
11                     Birthday = DateTimeHelper.Now.AddYears(-100),
12                     UserName = "yswenli"
13                 },
14                 Users = new System.Collections.Generic.List<UserInfo>()
15                 {
16                     new UserInfo()
17                     {
18 
19                         ID = 1,
20                         Birthday = DateTimeHelper.Now.AddYears(-100),
21                         UserName = "yswenli"
22                     }
23                 }
24             };
复制代码

        测试代码:

复制代码
 1         public static byte[] SerializeBinary(object request)
 2         {
 3 
 4             System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer =
 5 
 6             new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
 7 
 8             using (System.IO.MemoryStream memStream = new System.IO.MemoryStream())
 9             {
10                 serializer.Serialize(memStream, request);
11 
12                 return memStream.ToArray();
13             }
14         }
15 
16 
17         public static object DeSerializeBinary(byte[] data)
18         {
19             using (System.IO.MemoryStream memStream = new System.IO.MemoryStream(data))
20             {
21                 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserializer =
22 
23                 new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
24 
25                 return deserializer.Deserialize(memStream);
26             }
27         }
28 
29         static void SerializeTest()
30         {
31             var groupInfo = new GroupInfo()
32             {
33                 GroupID = 1,
34                 IsTemporary = false,
35                 Name = "yswenli group",
36                 Created = DateTimeHelper.Now,
37                 Creator = new UserInfo()
38                 {
39 
40                     ID = 1,
41                     Birthday = DateTimeHelper.Now.AddYears(-100),
42                     UserName = "yswenli"
43                 },
44                 Users = new System.Collections.Generic.List<UserInfo>()
45                 {
46                     new UserInfo()
47                     {
48 
49                         ID = 1,
50                         Birthday = DateTimeHelper.Now.AddYears(-100),
51                         UserName = "yswenli"
52                     }
53                 }
54             };
55 
56             var count = 100000;
57             var len1 = 0;
58             var len2 = 0;
59 
60             Stopwatch sw = new Stopwatch();
61             sw.Start();
62 
63             List<byte[]> list = new List<byte[]>();
64             for (int i = 0; i < count; i++)
65             {
66                 var bytes = SerializeBinary(groupInfo);
67                 len1 = bytes.Length;
68                 list.Add(bytes);
69             }
70             ConsoleHelper.WriteLine($"BinaryFormatter实体序列化平均:{count * 1000 / sw.ElapsedMilliseconds} 次/秒");
71 
72             sw.Restart();
73             for (int i = 0; i < count; i++)
74             {
75                 var obj = DeSerializeBinary(list[i]);
76             }
77             ConsoleHelper.WriteLine($"BinaryFormatter实体反序列化平均:{count * 1000 / sw.ElapsedMilliseconds} 次/秒");
78             ConsoleHelper.WriteLine($"BinaryFormatter序列化生成bytes大小:{len1 * count * 1.0 / 1024 / 1024} Mb");
79             list.Clear();
80             sw.Restart();
81 
82             for (int i = 0; i < count; i++)
83             {
84                 var bytes = RPC.Serialize.ParamsSerializeUtil.Serialize(groupInfo);
85                 len2 = bytes.Length;
86                 list.Add(bytes);
87             }
88             ConsoleHelper.WriteLine($"ParamsSerializeUtil实体序列化平均:{count * 1000 / sw.ElapsedMilliseconds} 次/秒");
89             sw.Restart();
90             for (int i = 0; i < count; i++)
91             {
92                 int os = 0;
93 
94                 var obj = RPC.Serialize.ParamsSerializeUtil.Deserialize(groupInfo.GetType(), list[i], ref os);
95             }
96             ConsoleHelper.WriteLine($"ParamsSerializeUtil实体反序列化平均:{count * 1000 / sw.ElapsedMilliseconds} 次/秒");
97             ConsoleHelper.WriteLine($"ParamsSerializeUtil序列化生成bytes大小:{len2 * count * 1.0 / 1024 / 1024} Mb");
98             sw.Stop();
99         }
复制代码

        运行结果:

 

 

自已动手做高性能消息队列

 

前言

        本人觉得码农的技术提升应该是从how to do到why do,而项目或产品都是从why do到how to do,按题来,所以呢下面先从大的方面介绍一下消息队列。

        消息队列是分布式高并发面目中必不可少的一部分,随着互联网、云计算、大数据的使用,消息队列的应用越来越多,消息队列在系统的可伸缩性、稳定性、提升吞吐量等方面有着显著的作用;它主要的作用一般如下:

       1.通过异步处理提高系统性能

 

 

        如上图,在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。

        通过以上分析我们可以得出消息队列具有很好的削峰作用的功能——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:

 

        因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此使用消息队列进行异步处理之后,需要适当修改业务流程进行配合,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。这就类似我们平时手机订火车票和电影票。

       2.降低系统耦合性

        我们知道模块分布式部署以后聚合方式通常有两种:1.分布式消息队列和2.分布式服务。先来简单说一下分布式服务目前使用比较多的用来构建SOA(Service Oriented Architecture面向服务体系结构)分布式服务框架是阿里巴巴开源的Dubbo.如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章:《高性能优秀的服务框架-dubbo介绍》

        我们最常见的事件驱动架构类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示:

 

 

        消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。 从上图可以看到消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计

        消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。

        另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。

        前面说了这么多消息队列的重要性、使用场景、工作模式,有很多人就可能会说了,现有的ActiveMQ、RabbitMQ、Kafka、RocketMQ等多了去了,那在项目架构的时候选一个用上去就不行了,完全没有必要重复造轮子啊!本人认为对于重复造轮子的事情和其它任何事情都是一样的——任何事情没有绝对的好处或者坏处,比如是刚入门的码农、又或者很急的项目,完全可以选用现有一种通用的、成熟的产品,没必要去从零开始做;实际上没有任何一个优秀的产品全部使用三方的产品来组装完成的,任何一个好一点的项目发展到一定的时候都不约而同的进行底层开发。原因很简单:第一个任何通用型的产品总用功能覆盖不到的场景;第二个任何通用型的产品为了实现通用必将做了一些性能或架构的牺牲;现在道理都讲完了,开始动手了(都听你逼半天,能动手就尽量少逼逼!)。

 概述

  动手前先构思一下,本人需要一个简单的、可发布订阅的、高吞吐量的消息队列,并将之简单大的方面分成QServer、QClient;QServer主要有Exchange、Binding、MessageQueue构成;QClient和QServer共用一套相同的传输编解码器QCoder ,主要实现Publish、Subscribe、Unsubcribe、Closes等功能;先想这么多,开干!

Exchange

  主要在QServer中提供发布、订阅、连接、队列信息等管理

复制代码
  1 /****************************************************************************
  2 *Copyright (c) 2018 Microsoft All Rights Reserved.
  3 *CLR版本: 4.0.30319.42000
  4 *机器名称:WENLI-PC
  5 *公司名称:Microsoft
  6 *命名空间:SAEA.QueueSocket.Model
  7 *文件名: Exchange
  8 *版本号: V1.0.0.0
  9 *唯一标识:6a576aad-edcc-446d-b7e5-561a622549bf
 10 *当前的用户域:WENLI-PC
 11 *创建人: yswenli
 12 *电子邮箱:wenguoli_520@qq.com
 13 *创建时间:2018/3/5 16:36:44
 14 *描述:
 15 *
 16 *=====================================================================
 17 *修改标记
 18 *修改时间:2018/3/5 16:36:44
 19 *修改人: yswenli
 20 *版本号: V1.0.0.0
 21 *描述:
 22 *
 23 *****************************************************************************/
 24 
 25 using SAEA.Commom;
 26 using SAEA.Sockets.Interface;
 27 using System;
 28 using System.Collections.Generic;
 29 using System.Linq;
 30 using System.Text;
 31 using System.Threading;
 32 using System.Threading.Tasks;
 33 
 34 namespace SAEA.QueueSocket.Model
 35 {
 36     class Exchange : ISyncBase
 37     {
 38         object _syncLocker = new object();
 39 
 40         public object SyncLocker
 41         {
 42             get
 43             {
 44                 return _syncLocker;
 45             }
 46         }
 47 
 48         long _pNum = 0;
 49 
 50         long _cNum = 0;
 51 
 52         long _inNum = 0;
 53 
 54         long _outNum = 0;
 55 
 56         private Binding _binding;
 57 
 58         private MessageQueue _messageQueue;
 59 
 60         public Exchange()
 61         {
 62             this._binding = new Binding();
 63 
 64             this._messageQueue = new MessageQueue();
 65         }
 66 
 67 
 68         public void AcceptPublish(string sessionID, QueueResult pInfo)
 69         {
 70             lock (_syncLocker)
 71             {
 72                 this._binding.Set(sessionID, pInfo.Name, pInfo.Topic);
 73 
 74                 this._messageQueue.Enqueue(pInfo.Topic, pInfo.Data);
 75 
 76                 _pNum = this._binding.GetPublisherCount();
 77 
 78                 Interlocked.Increment(ref _inNum);
 79             }
 80         }
 81 
 82         public void AcceptPublishForBatch(string sessionID, QueueResult[] datas)
 83         {
 84             if (datas != null)
 85             {
 86                 foreach (var data in datas)
 87                 {
 88                     if (data != null)
 89                     {
 90                         AcceptPublish(sessionID, data);
 91                     }
 92                 }
 93             }
 94         }
 95 
 96 
 97         public void GetSubscribeData(string sessionID, QueueResult sInfo, int maxSize = 500, int maxTime = 500, Action<List<string>> callBack = null)
 98         {
 99             lock (_syncLocker)
100             {
101                 var result = this._binding.GetBingInfo(sInfo);
102 
103                 if (result == null)
104                 {
105                     this._binding.Set(sessionID, sInfo.Name, sInfo.Topic, false);
106 
107                     _cNum = this._binding.GetSubscriberCount();
108 
109                     Task.Factory.StartNew(() =>
110                     {
111                         while (this._binding.Exists(sInfo))
112                         {
113                             var list = this._messageQueue.DequeueForList(sInfo.Topic, maxSize, maxTime);
114                             if (list != null)
115                             {
116                                 list.ForEach(i => { Interlocked.Increment(ref _outNum); });
117                                 callBack?.Invoke(list);
118                                 list.Clear();
119                                 list = null;
120                             }
121                         }
122                     });
123                 }
124             }            
125         }
126 
127         public void Unsubscribe(QueueResult sInfo)
128         {
129             Interlocked.Decrement(ref _cNum);
130             this._binding.Del(sInfo.Name, sInfo.Topic);
131         }
132 
133         public void Clear(string sessionID)
134         {
135             lock (_syncLocker)
136             {
137                 var data = this._binding.GetBingInfo(sessionID);
138 
139                 if (data != null)
140                 {
141                     if (data.Flag)
142                     {
143                         Interlocked.Decrement(ref _pNum);
144                     }
145                     else
146                     {
147                         Interlocked.Decrement(ref _cNum);
148                     }
149                     this._binding.Remove(sessionID);
150                 }
151             }
152         }
153 
154         public Tuple<long, long, long, long> GetConnectInfo()
155         {
156             return new Tuple<long, long, long, long>(_pNum, _cNum, _inNum, _outNum);
157         }
158 
159         public List<Tuple<string, long>> GetQueueInfo()
160         {
161             List<Tuple<string, long>> result = new List<Tuple<string, long>>();
162             lock (_syncLocker)
163             {
164                 var list = this._messageQueue.ToList();
165                 if (list != null)
166                 {
167                     var tlts = list.Select(b => b.Topic).Distinct().ToList();
168 
169                     if (tlts != null)
170                     {
171                         foreach (var topic in tlts)
172                         {
173                             var count = this._messageQueue.GetCount(topic);
174                             var t = new Tuple<string, long>(topic, count);
175                             result.Add(t);
176                         }
177                         tlts.Clear();
178                     }
179                     list.Clear();
180                 }
181             }
182             return result;
183         }
184 
185     }
186 }
复制代码

  思维发散:这里可以增加全局消息队列、指定连接消息队列等;将连接通过类型redis cluster模式进行一个均衡分布等

Binding

  主要功能是将连接、主题进行映射管理

复制代码
  1 /****************************************************************************
  2 *Copyright (c) 2018 Microsoft All Rights Reserved.
  3 *CLR版本: 4.0.30319.42000
  4 *机器名称:WENLI-PC
  5 *公司名称:Microsoft
  6 *命名空间:SAEA.QueueSocket.Model
  7 *文件名: Binding
  8 *版本号: V1.0.0.0
  9 *唯一标识:7472dabd-1b6a-4ffe-b19f-2d1cf7348766
 10 *当前的用户域:WENLI-PC
 11 *创建人: yswenli
 12 *电子邮箱:wenguoli_520@qq.com
 13 *创建时间:2018/3/5 17:10:19
 14 *描述:
 15 *
 16 *=====================================================================
 17 *修改标记
 18 *修改时间:2018/3/5 17:10:19
 19 *修改人: yswenli
 20 *版本号: V1.0.0.0
 21 *描述:
 22 *
 23 *****************************************************************************/
 24 
 25 using SAEA.Commom;
 26 using SAEA.Sockets.Interface;
 27 using System;
 28 using System.Collections.Generic;
 29 using System.Linq;
 30 using System.Text;
 31 
 32 namespace SAEA.QueueSocket.Model
 33 {
 34     /// <summary>
 35     /// 连接与主题的映射
 36     /// </summary>
 37     class Binding : ISyncBase, IDisposable
 38     {
 39         List<BindInfo> _list = new List<BindInfo>();
 40 
 41         object _syncLocker = new object();
 42 
 43         public object SyncLocker
 44         {
 45             get
 46             {
 47                 return _syncLocker;
 48             }
 49         }
 50 
 51         bool _isDisposed = false;
 52 
 53         int _minutes = 10;
 54 
 55         public Binding(int minutes = 10)
 56         {
 57             _minutes = minutes;
 58 
 59             ThreadHelper.PulseAction(() =>
 60             {
 61                 lock (_syncLocker)
 62                 {
 63                     var list = _list.Where(b => b.Expired <= DateTimeHelper.Now).ToList();
 64                     if (list != null)
 65                     {
 66                         list.ForEach(item =>
 67                         {
 68                             _list.Remove(item);
 69                         });
 70                         list.Clear();
 71                         list = null;
 72                     }
 73                 }
 74             }, new TimeSpan(0, 0, 10), _isDisposed);
 75         }
 76 
 77 
 78         public void Set(string sessionID, string name, string topic, bool isPublisher = true)
 79         {
 80 
 81             lock (_syncLocker)
 82             {
 83                 var result = _list.FirstOrDefault(b => b.Name == name && b.Topic == topic);
 84                 if (result == null)
 85                 {
 86                     _list.Add(new BindInfo()
 87                     {
 88                         SessionID = sessionID,
 89                         Name = name,
 90                         Topic = topic,
 91                         Flag = isPublisher,
 92                         Expired = DateTimeHelper.Now.AddMinutes(_minutes)
 93                     });
 94                 }
 95                 else
 96                 {
 97                     result.Expired = DateTimeHelper.Now.AddMinutes(_minutes);
 98                 }
 99             }
100         }
101 
102         public void Del(string sessionID, string topic)
103         {
104             lock (_syncLocker)
105             {
106                 var result = _list.FirstOrDefault(b => b.Name == sessionID && b.Topic == topic);
107                 if (result != null)
108                 {
109                     _list.Remove(result);
110                 }
111             }
112         }
113 
114         public void Remove(string sessionID)
115         {
116             lock (_syncLocker)
117             {
118                 var result = _list.Where(b => b.SessionID == sessionID).ToList();
119                 if (result != null)
120                 {
121                     result.ForEach((item) =>
122                     {
123                         _list.Remove(item);
124                     });
125                     result.Clear();
126                 }
127             }
128         }
129 
130         public BindInfo GetBingInfo(QueueResult sInfo)
131         {
132             lock (_syncLocker)
133             {
134                 var bi = _list.FirstOrDefault(b => b.Name == sInfo.Name && b.Topic == sInfo.Topic);
135 
136                 if (bi != null)
137                 {
138                     if (bi.Expired <= DateTimeHelper.Now)
139                     {
140                         Remove(bi.SessionID);
141                     }
142                     else
143                     {
144                         return bi;
145                     }
146                 }
147                 return null;
148             }
149         }
150 
151         public BindInfo GetBingInfo(string sessionID)
152         {
153             lock (_syncLocker)
154             {
155                 return _list.FirstOrDefault(b => b.SessionID == sessionID);
156             }
157         }
158 
159         public bool Exists(QueueResult sInfo)
160         {
161             lock (_syncLocker)
162             {
163                 var data = _list.FirstOrDefault(b => b.Name == sInfo.Name && b.Topic == sInfo.Topic);
164 
165                 if (data != null)
166                 {
167                     if (data.Expired <= DateTimeHelper.Now)
168                     {
169                         Remove(data.SessionID);
170 
171                         return false;
172                     }
173 
174                     data.Expired = DateTimeHelper.Now.AddMinutes(_minutes);
175 
176                     return true;
177                 }
178             }
179             return false;
180         }
181 
182 
183         public IEnumerable<BindInfo> GetPublisher()
184         {
185             lock (_syncLocker)
186             {
187                 return _list.Where(b => b.Flag);
188             }
189         }
190 
191         public int GetPublisherCount()
192         {
193             lock (_syncLocker)
194             {
195                 return _list.Where(b => b.Flag).Count();
196             }
197         }
198 
199         public IEnumerable<BindInfo> GetSubscriber()
200         {
201             lock (_syncLocker)
202             {
203                 return _list.Where(b => !b.Flag);
204             }
205         }
206 
207         public int GetSubscriberCount()
208         {
209             lock (_syncLocker)
210             {
211                 return _list.Where(b => !b.Flag).Count();
212             }
213         }
214 
215 
216         public void Dispose()
217         {
218             _isDisposed = true;
219             lock (_syncLocker)
220             {
221                 _list.Clear();
222                 _list = null;
223             }
224         }
225     }
226 }
复制代码

  思维发散:实现多个QServer的主题与队列映射克隆、或者队列消息转发实现容灾集群或大容量集群等

MessageQueue

  将主题与队列形成一个映射,并对主题映射进行管理

复制代码
  1 /****************************************************************************
  2 *Copyright (c) 2018 Microsoft All Rights Reserved.
  3 *CLR版本: 4.0.30319.42000
  4 *机器名称:WENLI-PC
  5 *公司名称:Microsoft
  6 *命名空间:SAEA.QueueSocket.Model
  7 *文件名: QueueCollection
  8 *版本号: V1.0.0.0
  9 *唯一标识:89a65c12-c4b3-486b-a933-ad41c3db6621
 10 *当前的用户域:WENLI-PC
 11 *创建人: yswenli
 12 *电子邮箱:wenguoli_520@qq.com
 13 *创建时间:2018/3/6 10:31:11
 14 *描述:
 15 *
 16 *=====================================================================
 17 *修改标记
 18 *修改时间:2018/3/6 10:31:11
 19 *修改人: yswenli
 20 *版本号: V1.0.0.0
 21 *描述:
 22 *
 23 *****************************************************************************/
 24 
 25 using SAEA.Commom;
 26 using SAEA.Sockets.Interface;
 27 using System;
 28 using System.Collections.Concurrent;
 29 using System.Collections.Generic;
 30 using System.Linq;
 31 using System.Threading.Tasks;
 32 
 33 namespace SAEA.QueueSocket.Model
 34 {
 35     public class MessageQueue : ISyncBase, IDisposable
 36     {
 37         bool _isDisposed = false;
 38 
 39         ConcurrentDictionary<string, QueueBase> _list;
 40 
 41         object _syncLocker = new object();
 42 
 43         public object SyncLocker
 44         {
 45             get
 46             {
 47                 return _syncLocker;
 48             }
 49         }
 50 
 51         public MessageQueue()
 52         {
 53             _list = new ConcurrentDictionary<string, QueueBase>();
 54 
 55             ThreadHelper.Run(() =>
 56             {
 57                 while (!_isDisposed)
 58                 {
 59                     var list = _list.Values.Where(b => b.Expired <= DateTimeHelper.Now);
 60                     if (list != null)
 61                     {
 62                         foreach (var item in list)
 63                         {
 64                             if (item.Length == 0)
 65                             {
 66                                 _list.TryRemove(item.Topic, out QueueBase q);
 67                             }
 68                         }
 69                     }
 70                     ThreadHelper.Sleep(10000);
 71                 }
 72             }, true, System.Threading.ThreadPriority.Highest);
 73         }
 74 
 75 
 76         public void Enqueue(string topic, string data)
 77         {
 78             var queue = _list.Values.FirstOrDefault(b => b.Topic.Equals(topic));
 79             lock (_syncLocker)
 80             {
 81                 if (queue == null)
 82                 {
 83                     queue = new QueueBase(topic);
 84                     _list.TryAdd(topic, queue);
 85                 }                
 86             }
 87             queue.Enqueue(data);
 88         }
 89 
 90 
 91         public string Dequeue(string topic)
 92         {
 93             var queue = _list.Values.FirstOrDefault(b => b.Topic.Equals(topic));
 94             if (queue != null)
 95             {
 96                 return queue.Dequeue();
 97             }
 98             return null;
 99         }
100 
101         /// <summary>
102         /// 批量读取数据
103         /// </summary>
104         /// <param name="topic"></param>
105         /// <param name="maxSize"></param>
106         /// <param name="maxTime"></param>
107         /// <returns></returns>
108         public List<string> DequeueForList(string topic, int maxSize = 500, int maxTime = 500)
109         {
110             List<string> result = new List<string>();
111             bool running = true;
112             var m = 0;
113             var task = Task.Factory.StartNew(() =>
114             {
115                 while (running)
116                 {
117                     var data = Dequeue(topic);
118                     if (data != null)
119                     {
120                         result.Add(data);
121                         m++;
122                         if (m == maxSize)
123                         {
124                             running = false;
125                         }
126                     }
127                     else
128                     {
129                         ThreadHelper.Sleep(1);
130                     }
131                 }
132             });
133             Task.WaitAll(new Task[] { task }, maxTime);
134             running = false;
135             return result;
136         }
137 
138         public string BlockDequeue(string topic)
139         {
140             var queue = _list.Values.FirstOrDefault(b => b.Topic == topic);
141             if (queue != null)
142             {
143                 return queue.BlockDequeue();
144             }
145             return null;
146         }
147 
148         public List<QueueBase> ToList()
149         {
150             lock (_syncLocker)
151             {
152                 return _list.Values.ToList();
153             }
154         }
155 
156         public long GetCount(string topic)
157         {
158             var queue = _list.Values.FirstOrDefault(b => b.Topic == topic);
159             if (queue != null)
160                 return queue.Length;
161             return 0;
162         }
163 
164         public void Dispose()
165         {
166             _isDisposed = true;
167             _list.Clear();
168                 _list = null;
169         }
170     }
171 }
复制代码

  思维发散:增加硬盘持久化以实现down机容灾、增加ack确认再移除以实现高可靠性等

QCoder

  在QServer和QClient之间进行传输编解码,这个编解码的速度直接影响消息队列的传输性能;本人使用了2种方案:1.使用类似redis传输方案,使用回车作为分隔符方式,这种方案结果要么一个字节一个字节检查分隔符,这种for操作还是C、C++屌,C#做这个真心不行;要么先将字节数组通过Encoding转换成String再来for,虽说能提升几倍性能,但是遇到不完整的字节数组时,本人没有找一个好的方法。2.使用自定义类型+长度+内容这种格式

复制代码
  1 /****************************************************************************
  2 *Copyright (c) 2018 Microsoft All Rights Reserved.
  3 *CLR版本: 4.0.30319.42000
  4 *机器名称:WENLI-PC
  5 *公司名称:Microsoft
  6 *命名空间:SAEA.QueueSocket.Net
  7 *文件名: QCoder
  8 *版本号: V1.0.0.0
  9 *唯一标识:88f5a779-8294-47bc-897b-8357a09f2fdb
 10 *当前的用户域:WENLI-PC
 11 *创建人: yswenli
 12 *电子邮箱:wenguoli_520@qq.com
 13 *创建时间:2018/3/5 18:01:56
 14 *描述:
 15 *
 16 *=====================================================================
 17 *修改标记
 18 *修改时间:2018/3/5 18:01:56
 19 *修改人: yswenli
 20 *版本号: V1.0.0.0
 21 *描述:
 22 *
 23 *****************************************************************************/
 24 
 25 using SAEA.Commom;
 26 using SAEA.QueueSocket.Model;
 27 using SAEA.QueueSocket.Type;
 28 using SAEA.Sockets.Interface;
 29 using System;
 30 using System.Collections.Generic;
 31 using System.Text;
 32 
 33 namespace SAEA.QueueSocket.Net
 34 {
 35     public sealed class QCoder : ICoder
 36     {
 37         static readonly int MIN = 1 + 4 + 4 + 0 + 4 + 0 + 0;
 38 
 39         private List<byte> _buffer = new List<byte>();
 40 
 41         private object _locker = new object();
 42 
 43         public void Pack(byte[] data, Action<DateTime> onHeart, Action<ISocketProtocal> onUnPackage, Action<byte[]> onFile)
 44         {
 45 
 46         }
 47 
 48         /// <summary>
 49         /// 队列编解码器
 50         /// </summary>
 51         public QueueCoder QueueCoder { get; set; } = new QueueCoder();
 52 
 53         /// <summary>
 54         /// 包解析
 55         /// </summary>
 56         /// <param name="data"></param>
 57         /// <param name="OnQueueResult"></param>
 58         public void GetQueueResult(byte[] data, Action<QueueResult> OnQueueResult)
 59         {
 60             lock (_locker)
 61             {
 62                 try
 63                 {
 64                     _buffer.AddRange(data);
 65 
 66                     if (_buffer.Count > (1 + 4 + 4 + 0 + 4 + 0 + 0))
 67                     {
 68                         var buffer = _buffer.ToArray();
 69 
 70                         QCoder.Decode(buffer, (list, offset) =>
 71                         {
 72                             if (list != null)
 73                             {
 74                                 foreach (var item in list)
 75                                 {
 76                                     OnQueueResult?.Invoke(new QueueResult()
 77                                     {
 78                                         Type = (QueueSocketMsgType)item.Type,
 79                                         Name = item.Name,
 80                                         Topic = item.Topic,
 81                                         Data = item.Data
 82                                     });
 83                                 }
 84                                 _buffer.RemoveRange(0, offset);
 85                             }
 86                         });
 87                     }
 88                 }
 89                 catch (Exception ex)
 90                 {
 91                     ConsoleHelper.WriteLine("QCoder.GetQueueResult error:" + ex.Message + ex.Source);
 92                     _buffer.Clear();
 93                 }
 94             }
 95         }
 96 
 97 
 98 
 99         /// <summary>
100         /// socket 传输字节编码
101         /// 格式为:1+4+4+x+4+x+4
102         /// </summary>
103         /// <param name="queueSocketMsg"></param>
104         /// <returns></returns>
105         public static byte[] Encode(QueueSocketMsg queueSocketMsg)
106         {
107             List<byte> list = new List<byte>();
108 
109             var total = 4 + 4 + 4;
110 
111             var nlen = 0;
112 
113             var tlen = 0;
114 
115             byte[] n = null;
116             byte[] tp = null;
117             byte[] d = null;
118 
119             if (!string.IsNullOrEmpty(queueSocketMsg.Name))
120             {
121                 n = Encoding.UTF8.GetBytes(queueSocketMsg.Name);
122                 nlen = n.Length;
123                 total += nlen;
124             }
125             if (!string.IsNullOrEmpty(queueSocketMsg.Topic))
126             {
127                 tp = Encoding.UTF8.GetBytes(queueSocketMsg.Topic);
128                 tlen = tp.Length;
129                 total += tlen;
130             }
131             if (!string.IsNullOrEmpty(queueSocketMsg.Data))
132             {
133                 d = Encoding.UTF8.GetBytes(queueSocketMsg.Data);
134                 total += d.Length;
135             }
136 
137             list.Add(queueSocketMsg.Type);
138             list.AddRange(BitConverter.GetBytes(total));
139             list.AddRange(BitConverter.GetBytes(nlen));
140             if (nlen > 0)
141                 list.AddRange(n);
142             list.AddRange(BitConverter.GetBytes(tlen));
143             if (tlen > 0)
144                 list.AddRange(tp);
145             if (d != null)
146                 list.AddRange(d);
147             var arr = list.ToArray();
148             list.Clear();
149             return arr;
150         }
151 
152         /// <summary>
153         /// socket 传输字节解码
154         /// </summary>
155         /// <param name="data"></param>
156         /// <param name="onDecode"></param>
157         public static bool Decode(byte[] data, Action<QueueSocketMsg[], int> onDecode)
158         {
159             int offset = 0;
160 
161             try
162             {
163                 if (data != null && data.Length > offset + MIN)
164                 {
165                     var list = new List<QueueSocketMsg>();
166 
167                     while (data.Length > offset + MIN)
168                     {
169                         var total = BitConverter.ToInt32(data, offset + 1);
170 
171                         if (data.Length >= offset + total + 1)
172                         {
173                             offset += 5;
174 
175                             var qm = new QueueSocketMsg((QueueSocketMsgType)data[0]);
176                             qm.Total = total;
177 
178                             qm.NLen = BitConverter.ToInt32(data, offset);
179                             offset += 4;
180 
181 
182                             if (qm.NLen > 0)
183                             {
184                                 var narr = new byte[qm.NLen];
185                                 Buffer.BlockCopy(data, offset, narr, 0, narr.Length);
186                                 qm.Name = Encoding.UTF8.GetString(narr);
187                             }
188                             offset += qm.NLen;
189 
190                             qm.TLen = BitConverter.ToInt32(data, offset);
191 
192                             offset += 4;
193 
194                             if (qm.TLen > 0)
195                             {
196                                 var tarr = new byte[qm.TLen];
197                                 Buffer.BlockCopy(data, offset, tarr, 0, tarr.Length);
198                                 qm.Topic = Encoding.UTF8.GetString(tarr);
199                             }
200                             offset += qm.TLen;
201 
202                             var dlen = qm.Total - 4 - 4 - qm.NLen - 4 - qm.TLen;
203 
204                             if (dlen > 0)
205                             {
206                                 var darr = new byte[dlen];
207                                 Buffer.BlockCopy(data, offset, darr, 0, dlen);
208                                 qm.Data = Encoding.UTF8.GetString(darr);
209                                 offset += dlen;
210                             }
211                             list.Add(qm);
212                         }
213                         else
214                         {
215                             break;
216                         }
217                     }
218                     if (list.Count > 0)
219                     {
220                         onDecode?.Invoke(list.ToArray(), offset);
221                         return true;
222                     }
223                 }
224             }
225             catch (Exception ex)
226             {
227                 ConsoleHelper.WriteLine($"QCoder.Decode error:{ex.Message} stack:{ex.StackTrace} data:{data.Length} offset:{offset}");
228             }
229             onDecode?.Invoke(null, 0);
230             return false;
231         }
232 
233 
234         /// <summary>
235         /// dispose
236         /// </summary>
237         public void Dispose()
238         {
239             _buffer.Clear();
240             _buffer = null;
241         }
242 
243 
244 
245     }
246 }
复制代码

测试

  简单的How to do和Why do已经完成了,是时候定义个Producer、Consumer来测试一把了

复制代码
  1 using SAEA.QueueSocket;
  2 using SAEA.Commom;
  3 using SAEA.QueueSocket.Model;
  4 using System;
  5 using System.Collections.Generic;
  6 using System.Diagnostics;
  7 using System.Text;
  8 using System.Threading;
  9 using System.Threading.Tasks;
 10 
 11 namespace SAEA.QueueSocketTest
 12 {
 13     class Program
 14     {
 15         static void Main(string[] args)
 16         {
 17             do
 18             {
 19                 ConsoleHelper.WriteLine("输入s启动队列服务器,输入p启动生产者,输入c启动消费者");
 20 
 21                 var inputStr = ConsoleHelper.ReadLine();
 22 
 23                 if (!string.IsNullOrEmpty(inputStr))
 24                 {
 25                     var topic = "测试频道";
 26 
 27                     switch (inputStr.ToLower())
 28                     {
 29                         case "s":
 30                             ConsoleHelper.Title = "SAEA.QueueServer";
 31                             ServerInit();
 32                             break;
 33                         case "p":
 34                             ConsoleHelper.Title = "SAEA.QueueProducer";
 35                             ConsoleHelper.WriteLine("输入ip:port连接到队列服务器");
 36                             inputStr = ConsoleHelper.ReadLine();
 37                             ProducerInit(inputStr, topic);
 38                             break;
 39                         case "c":
 40                             ConsoleHelper.Title = "SAEA.QueueConsumer";
 41                             ConsoleHelper.WriteLine("输入ip:port连接到队列服务器");
 42                             inputStr = ConsoleHelper.ReadLine();
 43                             ConsumerInit(inputStr, topic);
 44                             break;
 45                         default:
 46                             ServerInit();
 47                             inputStr = "127.0.0.1:39654";
 48                             ProducerInit(inputStr, topic);
 49                             ConsumerInit(inputStr, topic);
 50                             break;
 51                     }
 52                     ConsoleHelper.WriteLine("回车退出!");
 53                     ConsoleHelper.ReadLine();
 54                     return;
 55                 }
 56             }
 57             while (true);
 58         }
 59 
 60 
 61 
 62         static QServer _server;
 63         static void ServerInit()
 64         {
 65             _server = new QServer();
 66             _server.OnDisconnected += Server_OnDisconnected;
 67             _server.CalcInfo((ci, qi) =>
 68             {
 69                 var result = string.Format("生产者:{0} 消费者:{1} 收到消息:{2} 推送消息:{3}{4}", ci.Item1, ci.Item2, ci.Item3, ci.Item4, Environment.NewLine);
 70 
 71                 qi.ForEach((item) =>
 72                 {
 73                     result += string.Format("队列名称:{0} 堆积消息数:{1} {2}", item.Item1, item.Item2, Environment.NewLine);
 74                 });
 75                 ConsoleHelper.WriteLine(result);
 76             });
 77             _server.Start();
 78         }
 79 
 80         private static void Server_OnDisconnected(string ID, Exception ex)
 81         {
 82             _server.Clear(ID);
 83             if (ex != null)
 84             {
 85                 ConsoleHelper.WriteLine("{0} 已从服务器断开,err:{1}", ID, ex.ToString());
 86             }
 87         }
 88 
 89         static void ProducerInit(string ipPort, string topic)
 90         {
 91             int pNum = 0;
 92 
 93             //string msg = "主要原因是由于在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,比如说,大量的insert,update之类的请求同时到达MySQL,直接导致无数的行锁表锁,甚至最后请求会堆积过多,从而触发too many connections错误。通过使用消息队列,我们可以异步处理请求,从而缓解系统的压力。";
 94             string msg = "123";
 95             if (string.IsNullOrEmpty(ipPort)) ipPort = "127.0.0.1:39654";
 96 
 97             QClient producer = new QClient("productor" + Guid.NewGuid().ToString("N"), ipPort);
 98 
 99             producer.OnError += Producer_OnError;
100 
101             producer.OnDisconnected += Client_OnDisconnected;
102 
103             producer.ConnectAsync((s) =>
104             {
105                 Task.Factory.StartNew(() =>
106                 {
107                     var old = 0;
108                     var speed = 0;
109                     while (producer.Connected)
110                     {
111                         speed = pNum - old;
112                         old = pNum;
113                         ConsoleHelper.WriteLine("生产者已成功发送:{0} 速度:{1}/s", pNum, speed);
114                         Thread.Sleep(1000);
115                     }
116                 });
117 
118                 var list = new List<Tuple<string, byte[]>>();
119                 
120 
121                 while (producer.Connected)
122                 {                   
123 
124                     producer.Publish(topic, msg);
125 
126                     Interlocked.Increment(ref pNum);
127                 }
128             });
129 
130 
131         }
132 
133         private static void Producer_OnError(string ID, Exception ex)
134         {
135             ConsoleHelper.WriteLine("id:" + ID + ",error:" + ex.Message);
136         }
137 
138         static void ConsumerInit(string ipPort, string topic)
139         {
140             if (string.IsNullOrEmpty(ipPort)) ipPort = "127.0.0.1:39654";
141             QClient consumer = new QClient("subscriber-" + Guid.NewGuid().ToString("N"), ipPort);
142             consumer.OnMessage += Subscriber_OnMessage;
143             consumer.OnDisconnected += Client_OnDisconnected;
144             consumer.ConnectAsync((s) =>
145             {
146                 Task.Factory.StartNew(() =>
147                 {
148                     var old = 0;
149                     var speed = 0;
150                     while (consumer.Connected)
151                     {
152                         speed = _outNum - old;
153                         old = _outNum;
154                         ConsoleHelper.WriteLine("消费者已成功接收:{0} 速度:{1}/s", _outNum, speed);
155                         Thread.Sleep(1000);
156                     }
157                 });
158 
159                 consumer.Subscribe(topic);
160             });
161 
162         }
163 
164         private static void Client_OnDisconnected(string ID, Exception ex)
165         {
166             ConsoleHelper.WriteLine("当前连接已关闭");
167         }
168 
169         static int _outNum = 0;
170 
171         private static void Subscriber_OnMessage(QueueResult obj)
172         {
173             if (obj != null)
174                 _outNum += 1;
175         }
176     }
177 }
复制代码

  单线程的、单生产者、单消费者、单队列服务器的测试结果如下图:

QueueSocketTest

  到此一个自行实现的简单的消息队列完成了,虽说它离实际产品还很遥远,但是本人还是觉的技术的提升离不开钻研,路漫漫其修远兮,吾将上下而求索!

转载请标明本文来源:http://www.cnblogs.com/yswenli/p//9029587.html 
更多内容欢迎star作者的github:https://github.com/yswenli/SAEA
如果发现本文有什么问题和任何建议,也随时欢迎交流~

posted @ 2018-06-24 22:28  ~雨落忧伤~  阅读(410)  评论(0编辑  收藏  举报