一文详解分布式缓存(附代码)

{"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}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章