以良好的性能服务于数百万条路线

Posted

技术标签:

【中文标题】以良好的性能服务于数百万条路线【英文标题】:Serving millions of routes with good performance 【发布时间】:2013-04-16 15:44:05 【问题描述】:

我正在为一个尚未设置约束和规范的新项目做一些研究。需要的一件事是直接在根域下的大量路径。这可能会增加数百万条路径。这些路径没有共同的结构或独特的部分,所以我必须寻找完全匹配。

现在我知道分解这些路径更有效,这也有助于路径查找。不过我正在研究这里的可能性,所以请耐心等待。

我正在评估实现这一目标的方法,同时保持出色的性能。我想到了以下方法:

将路径存储在 SQL 数据库中并对每个请求进行查找。这似乎是最糟糕的选择,绝对不会使用。 将路径存储在 Redis 等键值存储中。这会好很多,而且我认为表现相当不错(不过必须对其进行基准测试)。 进行字符串/正则表达式匹配 - 就像许多框架一样,开箱即用 - 因为这么多可能的匹配是疯狂的,因此不是一个真正的选择。但我可以看到,在结合一些智能优化的情况下,逐字母比较的算法是如何发挥作用的。

但也许有一些我不知道的工具/方法更适合解决这类问题。不过,我可以使用任何技巧来完成此操作。

哦,如果有人想知道,不,这不是家庭作业。


更新

我已经测试了 Redis 方法。基于两组关键词,我得到了 1.5 亿条路径。我已经使用set 命令添加了它们中的每一个,其值是一个序列化的 id 字符串,我可以使用它来识别请求中的实际关键字。 (SET 'keyword1-keyword2' '<serialized_string>')

在具有一百万条记录的数据集的本地虚拟机中进行的快速测试返回了令人鼓舞的结果:对 1000 个请求进行基准测试平均需要 2 毫秒。这是在我的笔记本电脑上,它运行着很多其他的东西。

接下来我在一个 4 核 8GB RAM 的 VPS 上做了一个完整的测试,完整的 1.5 亿条记录。这产生了一个文件大小为 3.1G 的数据库和大约 9GB 的内存。由于数据库无法完全加载到内存中,Redis 开始交换,这导致了可怕的结果:平均大约 100 毫秒。

显然这不会起作用并且可以很好地扩展。要么每个 Web 服务器都需要有大量的 RAM,要么我们必须使用专用的 Redis 路由服务器。我从 Instagram 的工程师那里读到了an article,他们想出了一个显着减小数据库大小的技巧,但我还没有尝试过。无论哪种方式,这似乎都不是正确的方法。回到绘图板。

【问题讨论】:

尽管我已经读了好几遍了,但我并没有真正理解你认为性能问题的所在。你想查找像“abc/def/xyz”这样的路径并用它做点什么吗?只是散列路径以加快查找速度是否足够快,特别是因为只有几百万,还是涉及更多?一旦你查找路径你想做什么,或者你需要适应小内存等?或许还要加标签算法? 路径都是1级深度,例如:domain.com/path1/, domain.com/tag-othertag/, domain.com/tag-keyword/。这些路径将基于几个关键字集合的排列而存在,其中一些将有数千个条目。在像您的示例这样的嵌套路径中,使用唯一键的 3 个快速查询就足够了。但在这种情况下,它将是具有数百万个键的表中的查询。这就是性能问题。 与您的路径/键相关的可能/典型值是什么? (我的意思是“键值对”中的值,而不是“你的键长什么样?”) 键是 url-slugs,由 1 或 2 个标签组成(例如本网站上的标签)。这些值可能是某种散列或其他表示法,以将键链接到它们对应的 id。 【参考方案1】:

将路径存储在 SQL 数据库中并对每个请求进行查找。这似乎是最糟糕的选择,绝对不会被使用。

您可能低估了数据库的功能。我可以邀请你重新考虑你在那里的职位吗?

对于 Postgres(或带有 InnoDB 的 mysql),一百万个条目比微小的要高出一个档次。将整个路径存储在一个字段中,在其上添加索引,清理,分析。在您确定关键对象的 ID 之前,不要进行疯狂的连接,并且您在查找速度方面会很好。从 psql 运行查询时说几毫秒。

如果您获得大量流量,您真正的问题将是与磁盘 IO 相关的瓶颈。这里的经营座右铭是:越少越好。除了在您的 php 服务器上安装 APC、如果您使用 Ruby 则使用Passenger等基础知识之外:

    确保服务器有足够的 RAM 来适应该索引。

    缓存对与memcached中每个路径相关的对象的引用。

    如果您可以在十几个正则表达式中对所有路由进行分类,那么它们可能会有所帮助,因为它允许使用更小、更有针对性的索引,这些索引更容易保存在内存中。如果没有,请坚持存储(可能是尾部斜线)整个路径并继续。

    担心错过。如果您有一个重定向到规范 URL 的非规范 URL,请将重定向存储在没有任何到期日期的 memcached 中并继续使用它。

    我是否提到了很多 RAM 和内存缓存?

    哦,也不要高估您正在使用的 ORM。构建查询所花费的时间可能比数据存储解析、检索和返回结果所花费的时间更多。

    RAM...内存缓存...

老实说,Reddis 与 SQL + memcached 选项没有太大区别,除了内存管理(如您所见)、分片、复制和语法方面。当然还有熟悉度。

您的关键决策点(不包括迭代多个正则表达式)应该是您的数据的结构。如果它是高度结构化的,对原子性有关键需求,SQL + memcached 应该是你的首选。如果您的自定义字段遍地都是并且 EAV 表过于庞大,那么使用 Reddis 或 CouchDB 或其他 NoSQL 存储应该在您的雷达上。

在任何一种情况下,拥有 大量 的 RAM 以将这些索引保存在内存中会有所帮助,并且如果您需要扩展,整个事物前面的 memcached 集群将永远不会受到伤害。

【讨论】:

公平点。也许我很快就放弃了 MySQL。数据大多是只读的,因为它的更改很少。当涉及到 RDBMS 时,我(现在仍然)担心缩放和高负载,但是在它前面有一个 K/V 存储,也许它还不错。虽然我更喜欢 Redis 而不是 Memcached,但这当然是可以互换的。 Redis 还有一个 LRU 过期算法,在这里可能会派上用场。无论如何,很棒的提示,谢谢! 据我所知,在集群支持方面,没有任何基于内存的 SQL 存储能胜过 memcached,而且 memcached 使用 LRU 来决定在填满内存分配时从缓存中删除哪些键. 对于扩展和高负载,如果您选择正确的数据库引擎,这真的不是问题。 Postgres 非常好,fwiw。 Skype(是的,那个 Skype)运行在 Postgres 之上。 MySQL 需要一个称职的保姆,但如果你避免使用 MyISAM 表,这也不错。 (在之前的评论中:s/no memory-based SQL/no memory-based NoSQL/)【参考方案2】:

我认为 Redis 是你最好的选择。 SQL 会很慢,根据我的经验,正则表达式的查询速度总是很慢。

我会执行以下步骤来测试 Redis:

    使用本地 VM 或在 EC2 之类的云中启动 Redis 实例。 下载一两个字典并将此数据输入 Redis。例如这里的一些东西:http://wordlist.sourceforge.net/ 确保你规范化数据。例如,始终将字符串小写并删除字符串开头/结尾处的空格等。 我会忽略散列。我看不出您需要对 URL 进行哈希处理的原因?如果您想调试东西并且它似乎没有“购买”您任何东西,那么以后就不可能阅读。我去了http://www.sha1-online.com/,输入ryan,得到ea3cd978650417470535f3a4725b6b5042a6ab59作为哈希值。将原始文本放入 RAM 中会小得多,这将有助于 Redis。显然,对于更长的路径,散列会更好,但是您的示例非常小。 =) 编写一个从 Redis 读取数据的工具,看看它的性能如何。 利润!

请记住,Redis 需要将整个数据集保存在 RAM 中,因此请相应地进行规划。

【讨论】:

这也是我的首选方法。查看更新后的问题以了解我的结果。 我刚刚阅读了一篇关于使用 Redis 作为主要数据存储的团队的文章。他们有一些提示和技巧可能会有所帮助。 moot.it/blog/technology/redis-as-primary-datastore-wtf.html【参考方案3】:

我建议使用某种键值存储(即散列存储),可能还会对键进行散列处理以使其更短(恕我直言,SHA-1 之类的就可以了)。

【讨论】:

这也是我最好的猜测,如问题中所述。您有任何经验或实时数据可以证实这一点吗?

以上是关于以良好的性能服务于数百万条路线的主要内容,如果未能解决你的问题,请参考以下文章

SOA架构之学习路线

OR-Tools - 最小化每条路线覆盖的区域

设计路线被调用时被跳过

在数据表中显示数百万条记录

性能测试学习和性能瓶颈分析路线

MySQL 的良好“中间路线”c3p0 配置示例?