CYQ.Data 對於分佈式緩存Redis、MemCache高可用的改進及性能測試

背景:

隨着.NET Core 在 Linux 下的熱動,相信動不動就要分佈式或集羣的應用的需求,會慢慢火起來。linux

因此這段時間一直在研究和思考分佈式集羣的問題,同時也在思考把幾個框架的思惟相對提高到這個Level。git

最近大力重構了框架兩個點:一個是分佈式緩存,一個是數據庫主從備。github

今天,先分享分佈式緩存的改進的兩個點:redis

一、高可用:能動態增長或減小Redis、MemCache的實例,而不影響程序。算法

二、高性能:保障在高併發下的穩定性及性能。數據庫

一、Redis、MemCache 分佈式下的高可用。

要實現分佈下應用下的高可用,就兩種方法:後端

一、創建集羣,內部調度,對外只提供一個接口。

優勢:對全部不一樣的開發語言和客戶端都通用。緩存

缺點:創建相對複雜,須要有專業的運維知識,並且對於提供出口的服務器,也要再作一次主備,總體硬件成本高。服務器

二、由客戶端進行調度。

優勢:比較簡單,不須要專業知識,上千個Redis實例,隨時就動起來。多線程

缺點:若是項目有混合多種後端語言開發的,還得有多種客戶端實現。

 

CYQ.Data 就是.NET Core 下集成了操做Redis的一種客戶端實現。

下面來看簡單的使用過程:

一、指定配置外鏈:

原有的配置

<add key="RedisServers" value="127.0.0.1:6379,127.0.0.1:6380 - pwd123456"/>
<add key="RedisServersBak" value="127.0.0.1:6381 - pwd123456"/>

將配置寫在原的config中,1是不適合大量的實例,要寫很長;2是當修改時,會引起(Window下)程序重啓(關鍵是NetCore下還不重啓)。

改進後配置(文件後綴能夠指定*.ini,*.txt):

<add key="RedisServers" value="redis.ini"/>
<add key="RedisServersBak" value="redisbak.txt"/>

對應的redis.init 文件(一行一個實例):

127.0.0.1:6379
127.0.0.1:6380 - pwd123456
127.0.0.1:6381 - pwd123456
127.0.0.1:6382 - pwd123456
127.0.0.1:6383 - pwd123456
...
能夠無限加

將配置外置後,程序會自動監控文件的變化,每次修改都會即時生效,內部自動調整算法,真正實現高可用。

接下來,你就能夠無限的找服務器,啓動N多個實例,想加就加,想減就減。

一個高可用的分佈式緩存就是這麼簡單了,固然了,之後要複雜,那就慢慢學習了。

二、關於一致性Hash及主備的說明:

在框架內部的算法中,若是節點失敗,會檢測有沒有設置備用節點:

若是沒有,會直接失敗。

若是有,會從備用實例中獲取來維持服務,若是備用也實例也失敗,而本次請求失敗(下一次請求會更換備用節點)。

這樣能夠避免在高併發的狀況下產生滾雪球的情景,由一個點失敗最終致使把全部的服務器都壓倒。

PS:在CYQ.Data 中,Redis 和 MemCache 的底層實現是同樣的,因此使用方式都是同樣的。

二、Redis、MemCache 分佈式下的性能測試。

對於原有的代碼改進重構,大約變化了50%左右的代碼。

本次在性能上的優化:除了底層Socket小調整,就是命令的合併操做,以及隊列池的優化,以及多線程下的穩定保障。

其實,Web are 只是個客戶端,要說高性能,其實都是比較出來,只要比別人的快一些,好像就真的高性能了。

因此,測試都須要有個比較的對象。

目前測試的是本機,Win7,Redis 老版本 2.4.6。

一、先本身和本身比:新改動的版本和舊版本比較:

舊版本的測試數據:

新版本的測試數據:

跑在Linux下時(1核2G內存的CentOS7,Redis 版本 3.2.12):

通過總體的代碼改動,性能仍是總體提高很多的,還支持了高可用的擴展。

只是離官方傳說的10w/s,還差了幾倍,我猜若是把5個命令打包一塊兒發送,應該就差很少了。

二、看看其它客戶端的:

一開始我是沒作比較測試,只是恰好在網上看到另外一個客戶端,寫着就是高性能Redis客戶端。

看到上面的測試數據,我有點驚訝,這有6w的數據是怎麼飄出來的:

SET_JSON using 1 threads run 100000 [use time 5.76s 17,370.26/sec]
SET_JSON using 4 threads run 100000 [use time 1.87s 53,407.17/sec]
SET_JSON using 8 threads run 100000 [use time 1.65s 60,517.8/sec]

但上面沒寫明是在window仍是linux也沒寫版本,

因而,我就下載了它的客戶端(它自帶Test運行程序),

而後連上個人redis,運行了一下:

實際上它的數據是這樣的:

這是我打開的姿式不對麼,仍是對環境有特定的要求?

三、和Redis自帶的redis-benchmark.exe工具進行比較。

想找找 Redis 在Window平臺測試相關的文章,發現都是Redis原生的測試工具的介紹,因而,也用它測試了一下:

發現原生的果真強悍,最高的時候能夠飄到6w,看來用C就是不同。

搞的我都懷疑,大家都是在用併發測試,而我只是用多線程。

 四、用了傳說中性能好到要收費的:StackExchange.Redis

感受也差很少啊,收費我仍是支持的。

關鍵是這貨相同的NetCore代碼,放Linux CentOS7 跑不動:

居然連不上了,直接返回錯誤了:

{"success":false,"msg":"It was not possible to connect to the redis server(s). InternalFailure (None, last-recv: 803) on 127.0.0.1:6379/Interactive, Idle, last: GET, origin: ReadFromPipe, outstanding: 2, last-read: 0s ago, last-write: 0s ago, unanswered-write: 0s ago, keep-alive: 60s, state: ConnectedEstablished, mgr: 9 of 10 available, in: 0, last-heartbeat: never, global: 0s ago, v: 2.0.571.20511"}

總結:

CYQ.Data 通過重構升級後,總體提高了很多。

這裏要了解一下:CYQ.Data 集成的分佈式緩存操做,和其它單獨的客戶端是不同的。

由於其它客戶端是把全部的Redis命令都實現了,你能夠其它客戶端操做完整的Redis。

而 CYQ.Data 只是:

Get、Set、DEL、Contains、Clear。

統一了全部類型並保持最簡單的緩存操做接口。

最後,獻上測試代碼:

 AppConfig.Cache.RedisServers = "127.0.0.1:6379";//,127.0.0.1:6380 - c123456,127.0.0.1:6381 - c123456,127.0.0.1:6382 - c123456,127.0.0.1:6383 - c123456";
            //AppConfig.Cache.RedisServers = "redis.txt";
            //AppConfig.Cache.RedisServersBak = "redis.bak.txt";


            int readCount = 100000, userDBCount = 1;
            ThreadPool.SetMaxThreads(1000, 1000);
           

            new ThreadRun(1, readCount, userDBCount).Start();
            new ThreadRun(4, readCount, userDBCount).Start();
            new ThreadRun(8, readCount, userDBCount).Start();
            new ThreadRun(10, readCount, userDBCount).Start();
            new ThreadRun(20, readCount, userDBCount).Start();
            new ThreadRun(50, readCount, userDBCount).Start();
            new ThreadRun(100, readCount, userDBCount).Start();
            new ThreadRun(200, readCount, userDBCount).Start();
            new ThreadRun(500, readCount, userDBCount).Start();
            new ThreadRun(1000, readCount, userDBCount).Start();
            new ThreadRun(2000, readCount, userDBCount).Start();

            new ThreadRun(5000, readCount, userDBCount).Start();
            new ThreadRun(2000, readCount, userDBCount).Start();
            new ThreadRun(2000, readCount, userDBCount).Start();
            new ThreadRun(500, readCount, userDBCount).Start();
            new ThreadRun(200, readCount, userDBCount).Start();
            new ThreadRun(100, readCount, userDBCount).Start();
            new ThreadRun(50, readCount, userDBCount).Start();
            new ThreadRun(20, readCount, userDBCount).Start();
            new ThreadRun(10, readCount, userDBCount).Start();
            new ThreadRun(8, readCount, userDBCount).Start();
            new ThreadRun(4, readCount, userDBCount).Start();
            new ThreadRun(1, readCount, userDBCount).Start();
            CacheManage.RedisInstance.Clear();//操做對象
            Console.WriteLine("End");
            Console.WriteLine(CacheManage.RedisInstance.WorkInfo);
            Console.WriteLine(CacheManage.RedisInstance.CacheInfo.ToJson(false, false));
----------------------------------------------------------
 public class ThreadRun
    {
        int threadCount, setOrReadCount, useDBCount;
        CacheManage cache;
        public ThreadRun(int threadCount, int setOrReadCount, int useDBCount)
        {
            cache = CacheManage.RedisInstance;//操做對象
            cache.Clear();
            this.threadCount = threadCount;
            this.setOrReadCount = setOrReadCount;
            this.useDBCount = useDBCount;
        }
        System.Diagnostics.Stopwatch gloWatch = new System.Diagnostics.Stopwatch();
        int runEndCount = 0;
        bool isEnd = false;
        public void Start()
        {
            AppConfig.Cache.RedisUseDBCount = useDBCount;
           
            gloWatch.Start();
            for (int i = 0; i < threadCount; i++)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadStart), setOrReadCount / threadCount);
            }
           
            while (!isEnd)
            {
                Thread.Sleep(10);
            }

        }

        public void ThreadStart(object readCount)
        {
            string rndKey = Guid.NewGuid().ToString().Substring(0, 5);
            int max = (int)readCount;
            for (int i = 0; i < max; i++)
            {
                string key = rndKey + "key" + i;
                if(cache.Set(key, Guid.NewGuid().ToString()))
                {

                }
                else
                {
                    Console.WriteLine("Set 失敗 key :" + key);
                }

            }

            Interlocked.Increment(ref runEndCount);
            if (runEndCount >= threadCount && !isEnd)
            {
                isEnd = true;
                //gloWatch.Stop();
                //Ng  2000ms
                //x     1000ms
                long t = gloWatch.ElapsedMilliseconds;
                Console.WriteLine(string.Format("ThreadCount : {0} , Run : {1}  Time {2} ms ,{3} requests per second. ", threadCount, setOrReadCount, t.ToString(), setOrReadCount * 1000 / t));

            }
        }
    }
相關文章
相關標籤/搜索