一文詳解分佈式緩存(附代碼)

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/44/4420d86ddb5b981b0793da555140cb6a.jpeg","alt":"image","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"又是一個沒有開工紅包的公司!!!"}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#問題分析","title":null}},{"type":"text","text":"問題分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過以上對話,各位是否能夠猜到所有緩存穿透的原因呢?回答之前我們先來看一下緩存策略的具體代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"緩存服務器IP=hash(key)%服務器數量"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏還要多說一句,key的取值可以根據具體業務具體設計。比如,我想要做負載均衡,key可以爲調用方的服務器IP;獲取用戶信息,key可以爲用戶ID;等等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在服務器數量不變的情況下,以上設計沒有問題。但是要知道,程序員的現實世界是悲慘的,唯一不變的就是業務一直在變。我本無奈,只能靠技術來改變這種狀況。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假如我們現在服務器的數量爲10,當我們請求key爲6的時候,結果是4,現在我們增加一臺服務器,服務器數量變爲11,當再次請求key爲6的服務器的時候,結果爲5.不難發現,不光是key爲6的請求,幾乎大部分的請求結果都發生了變化,這就是我們要解決的問題, 這也是我們設計分佈式緩存等類似場景時候主要需要注意的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們終極的設計目標是:在服務器數量變動的情況下"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"儘量提高緩存的命中率(轉移的數據最少)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"緩存數據儘量平均分配"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#解決方案","title":null}},{"type":"text","text":"解決方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過以上的分析我們明白了,造成大量緩存失效的根本原因是公式分母的變化,如果我們把分母保持不變,基本上可以減少大量數據被移動"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"link","attrs":{"href":"#分母不變方案","title":null}},{"type":"text","text":"分母不變方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果基於公式:緩存服務器IP=hash(key)%服務器數量 我們保持分母不變,基本上可以改善現有情況。我們選擇緩存服務器的策略會變爲:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"緩存服務器IP=hash(key)%N (N爲常數) N的數值選擇,可以根據具體業務選擇一個滿足情況的值。比如:我們可以肯定將來服務器數量不會超過100臺,那N完全可以設定爲100。那帶來的問題呢?"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前的情況可以認爲服務器編號是連續的,任何一個請求都會命中一個服務器,還是以上作爲例子,我們服務器現在無論是10還是增加到11,key爲6的請求總是能獲取到一臺服務器信息,但是現在我們的策略公式分母爲100,如果服務器數量爲11,key爲20的請求結果爲20,編號爲20的服務器是不存在的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上就是簡單哈希策略帶來的問題(簡單取餘的哈希策略可以抽象爲連續的數組元素,按照下標來訪問的場景)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決以上問題,業界早已有解決方案,那就是"},{"type":"text","marks":[{"type":"strong"}],"text":"一致性哈希"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一致性哈希算法在1997年由麻省理工學院的Karger等人在解決分佈式Cache中提出的,設計目標是爲了解決因特網中的熱點(Hot spot)問題,初衷和CARP十分類似。一致性哈希修正了CARP使用的簡單哈希算法帶來的問題,使得DHT可以在P2P環境中真正得到應用。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一致性哈希具體的特點,請各位百度,這裏不在詳細介紹。至於解決問題的思路這裏還要強調一下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"首先求出服務器(節點)的哈希值,並將其配置到環上,此環有2^32個節點。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"採用同樣的方法求出存儲數據的鍵的哈希值,並映射到相同的圓上。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"然後從數據映射到的位置開始順時針查找,將數據保存到找到的第一個服務器上。如果超過2^32仍然找不到服務器,就會保存到第一臺服務器上"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null}}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0c/0c9672a5bcfa4ac751210ffa74867498.jpeg","alt":"image","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當增加新的服務器的時候會發生什麼情況呢? "}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/56/56b66c4eefdfabba3d91fb9730fd6b7d.jpeg","alt":"image","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過上圖我們可以發現發生變化的只有如黃色部分所示。刪除服務器情況類似。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過以上介紹,一致性哈希正是解決我們目前問題的一種方案。解決方案千萬種,能解決問題即爲好。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#優化方案","title":null}},{"type":"text","text":"優化方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到目前爲止方案都看似完美,但現實是殘酷的。以上方案雖好,但還存在瑕疵。假如我們有3臺服務器,理想狀態下服務器在哈希環上的分配如下圖:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2f/2f7b364a67b8a8854c0174ccccb72a86.jpeg","alt":"image","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是現實往往是這樣:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/da/dab36fa3e127538c4f66a63c09023d4f.jpeg","alt":"image","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就是所謂的哈希環偏斜。分佈不均勻在某些場景下會依次壓垮服務器,實際生產環境一定要注意這個問題。爲了解決這個問題,虛擬節點應運而生。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fd/fdc50c0e39c71f5c57b41ae36a386e7a.jpeg","alt":"image","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖,哈希環上不再是實際的服務器信息,而是服務器信息的映射信息,比如:ServerA-1,ServerA-2 都映射到服務器A,在環上是服務器A的一個複製品。這種解決方法是利用數量來達到均勻分佈的目的,隨之需要的內存可能會稍微大一點,算是空間換取設計的一種方案。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#擴展閱讀","title":null}},{"type":"text","text":"擴展閱讀"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然是哈希就會有哈希衝突,那多個服務器節點的哈希值相同該怎麼辦呢?我們可以採用散列表尋址的方案:從當前位置順時針開始查找空位置,直到找到一個空位置。如果未找到,菜菜認爲你的哈希環是不是該擴容了,或者你的分母參數是不是太小了呢。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在實際的業務中,增加服務器或者減少服務器的操作要比查找服務器少的多,所以我們存儲哈希環的數據結構的查找速度一定要快,具體說來本質是:自哈希環的某個值起,能快速查找第一個不爲空的元素。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你度娘過你就會發現,網上很多介紹虛擬哈希環節點個數爲2^32(2的32次方),千篇一律。難道除了這個個數就不可以嗎?在菜菜看來,這個數目完全必要這麼大,只要符合我們的業務需求,滿足業務數據即可。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一致性哈希用到的哈希函數,不止要保證比較高的性能,還要保持哈希值的儘量平均分佈,這也是一個工業級哈希函數的要求,一下代碼實例的哈希函數其實不是最佳的,有興趣的同學可以優化一下。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有些語言自帶的GetHashCode()方法應用於一致性哈希是有問題的,例如c#。程序重啓之後同一個字符串的哈希值是變動的。所有需要一個更加穩定的字符串轉int的哈希算法。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一致性哈希解決的本質問題是:相同的key通過相同的哈希函數,能正確路由到相同的目標。像我們平時用的數據庫分表策略,分庫策略,負載均衡,數據分片等都可以用一致性哈希來解決。"}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#理論結合實際纔是真諦netcore代碼","title":null}},{"type":"text","text":"理論結合實際纔是真諦(NetCore代碼)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下代碼經過少許修改可直接應用於中小項目生產環境"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" //真實節點的信息\n public abstract class NodeInfo\n {\n public abstract string NodeName { get; }\n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"測試程序所用節點信息:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" class Server : NodeInfo\n {\n public string IP { get; set; }\n public override string NodeName\n {\n get => IP;\n }\n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下爲一致性哈希核心代碼:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" /// \n /// 1.採用虛擬節點方式 2.節點總數可以自定義 3.每個物理節點的虛擬節點數可以自定義\n /// \n public class ConsistentHash\n {\n //哈希環的虛擬節點信息\n public class VirtualNode\n {\n public string VirtualNodeName { get; set; }\n public NodeInfo Node { get; set; }\n }\n\n //添加元素 刪除元素時候的鎖,來保證線程安全,或者採用讀寫鎖也可以\n private readonly object objLock = new object();\n\n //虛擬環節點的總數量,默認爲100\n int ringNodeCount;\n //每個物理節點對應的虛擬節點數量\n int virtualNodeNumber;\n //哈希環,這裏用數組來存儲\n public VirtualNode[] nodes = null;\n public ConsistentHash(int _ringNodeCount = 100, int _virtualNodeNumber = 3)\n {\n if (_ringNodeCount <= 0 || _virtualNodeNumber <= 0)\n {\n throw new Exception(\"_ringNodeCount和_virtualNodeNumber 必須大於0\");\n }\n this.ringNodeCount = _ringNodeCount;\n this.virtualNodeNumber = _virtualNodeNumber;\n nodes = new VirtualNode[_ringNodeCount];\n }\n //根據一致性哈希key 獲取node信息,查找操作請業務方自行處理超時問題,因爲多線程環境下,環的node可能全被清除\n public NodeInfo GetNode(string key)\n {\n var ringStartIndex = Math.Abs(GetKeyHashCode(key) % ringNodeCount);\n var vNode = FindNodeFromIndex(ringStartIndex);\n return vNode == null ? null : vNode.Node;\n }\n //虛擬環添加一個物理節點\n public void AddNode(NodeInfo newNode)\n {\n var nodeName = newNode.NodeName;\n int virtualNodeIndex = 0;\n lock (objLock)\n {\n //把物理節點轉化爲虛擬節點\n while (virtualNodeIndex < virtualNodeNumber)\n {\n var vNodeName = $\"{nodeName}#{virtualNodeIndex}\";\n var findStartIndex = Math.Abs(GetKeyHashCode(vNodeName) % ringNodeCount);\n var emptyIndex = FindEmptyNodeFromIndex(findStartIndex);\n if (emptyIndex < 0)\n {\n // 已經超出設置的最大節點數\n break;\n }\n nodes[emptyIndex] = new VirtualNode() { VirtualNodeName = vNodeName, Node = newNode };\n virtualNodeIndex++;\n \n }\n }\n }\n //刪除一個虛擬節點\n public void RemoveNode(NodeInfo node)\n {\n var nodeName = node.NodeName;\n int virtualNodeIndex = 0;\n List lstRemoveNodeName = new List();\n while (virtualNodeIndex < virtualNodeNumber)\n {\n lstRemoveNodeName.Add($\"{nodeName}#{virtualNodeIndex}\");\n virtualNodeIndex++;\n }\n //從索引爲0的位置循環一遍,把所有的虛擬節點都刪除\n int startFindIndex = 0;\n lock (objLock)\n {\n while (startFindIndex < nodes.Length)\n {\n if (nodes[startFindIndex] != null && lstRemoveNodeName.Contains(nodes[startFindIndex].VirtualNodeName))\n {\n nodes[startFindIndex] = null;\n }\n startFindIndex++;\n }\n }\n\n }\n\n\n //哈希環獲取哈希值的方法,因爲系統自帶的gethashcode,重啓服務就變了\n protected virtual int GetKeyHashCode(string key)\n {\n var sh = new SHA1Managed();\n byte[] data = sh.ComputeHash(Encoding.Unicode.GetBytes(key));\n return BitConverter.ToInt32(data, 0);\n\n }\n\n #region 私有方法\n //從虛擬環的某個位置查找第一個node\n private VirtualNode FindNodeFromIndex(int startIndex)\n {\n if (nodes == null || nodes.Length <= 0)\n {\n return null;\n }\n VirtualNode node = null;\n while (node == null)\n {\n startIndex = GetNextIndex(startIndex);\n node = nodes[startIndex];\n }\n return node;\n }\n //從虛擬環的某個位置開始查找空位置\n private int FindEmptyNodeFromIndex(int startIndex)\n {\n\n while (true)\n {\n if (nodes[startIndex] == null)\n {\n return startIndex;\n }\n var nextIndex = GetNextIndex(startIndex);\n //如果索引回到原地,說明找了一圈,虛擬環節點已經滿了,不會添加\n if (nextIndex == startIndex)\n {\n return -1;\n }\n startIndex = nextIndex;\n }\n }\n //獲取一個位置的下一個位置索引\n private int GetNextIndex(int preIndex)\n {\n int nextIndex = 0;\n //如果查找的位置到了環的末尾,則從0位置開始查找\n if (preIndex != nodes.Length - 1)\n {\n nextIndex = preIndex + 1;\n }\n return nextIndex;\n }\n #endregion\n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"link","attrs":{"href":"#測試生成的節點","title":null}},{"type":"text","text":"測試生成的節點"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" ConsistentHash h = new ConsistentHash(200, 5);\n h.AddNode(new Server() { IP = \"192.168.1.1\" });\n h.AddNode(new Server() { IP = \"192.168.1.2\" });\n h.AddNode(new Server() { IP = \"192.168.1.3\" });\n h.AddNode(new Server() { IP = \"192.168.1.4\" });\n h.AddNode(new Server() { IP = \"192.168.1.5\" });\n\n for (int i = 0; i < h.nodes.Length; i++)\n {\n if (h.nodes[i] != null)\n {\n Console.WriteLine($\"{i}===={h.nodes[i].VirtualNodeName}\");\n }\n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果(還算比較均勻):"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"2====192.168.1.3#4\n10====192.168.1.1#0\n15====192.168.1.3#3\n24====192.168.1.2#2\n29====192.168.1.3#2\n33====192.168.1.4#4\n64====192.168.1.5#1\n73====192.168.1.4#3\n75====192.168.1.2#0\n77====192.168.1.1#3\n85====192.168.1.1#4\n88====192.168.1.5#4\n117====192.168.1.4#1\n118====192.168.1.2#4\n137====192.168.1.1#1\n152====192.168.1.2#1\n157====192.168.1.5#2\n158====192.168.1.2#3\n159====192.168.1.3#0\n162====192.168.1.5#0\n165====192.168.1.1#2\n166====192.168.1.3#1\n177====192.168.1.5#3\n185====192.168.1.4#0\n196====192.168.1.4#2\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"link","attrs":{"href":"#測試一下性能","title":null}},{"type":"text","text":"測試一下性能"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" Stopwatch w = new Stopwatch();\n w.Start();\n for (int i = 0; i < 100000; i++)\n {\n var aaa = h.GetNode(\"test1\");\n }\n w.Stop();\n Console.WriteLine(w.ElapsedMilliseconds);\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果(調用10萬次耗時657毫秒):"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"657\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"link","attrs":{"href":"#寫在最後","title":null}},{"type":"text","text":"寫在最後"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上代碼實有優化空間"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"哈希函數"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"很多for循環的臨時變量 有興趣優化的同學可以留言哦!!"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"更多精彩文章"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1342955119549267969&__biz=MzIwNTc3OTAxOA==#wechat_redirect","title":null},"content":[{"type":"text","text":"分佈式大併發系列"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1342959003139227648&__biz=MzIwNTc3OTAxOA==#wechat_redirect","title":null},"content":[{"type":"text","text":"架構設計系列"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1342962375443529728&__biz=MzIwNTc3OTAxOA==#wechat_redirect","title":null},"content":[{"type":"text","text":"趣學算法和數據結構系列"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1342964237798391808&__biz=MzIwNTc3OTAxOA==#wechat_redirect","title":null},"content":[{"type":"text","text":"設計模式系列"}]}]}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f8/f8af5984765a267892bf1a1272272625.png","alt":"image","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章