一日一技:二分偏左,二分搜索在分布式系统里面也有用?

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 一日一技:二分偏左,二分搜索在分布式系统里面也有用?

相信大家都知道二分搜索,在一个有序的列表中,使用二分搜索,能够以O(logN)的时间复杂度快速确定目标是不是在列表中。

二分搜索的代码非常简单,使用递归只需要几行代码就能搞定:

def binary_search(sorted_list, target):
    """
    sorted_list是单调递增的列表
    """
    if not sorted_list:
        return False
    mid = len(sorted_list) // 2
    if target > sorted_list[mid]:
        return binary_search(sorted_list[mid + 1:], target)
    elif target < sorted_list[mid]:
        return binary_search(sorted_list[:mid], target)
    else:
        return True

运行效果如下图所示:

Python自带了一个二分搜索的模块,叫做bisect,它也能实现二分搜索,但是它的执行结果跟我们上面代码的效果有点不同:

import bisect
a = [41, 46, 67, 74, 75, 76, 80, 86, 92, 100]
index = bisect.bisect(a, 75)
print(index)
index = bisect.bisect(a, 82)
print(index)

运行效果如下图所示:

可以看到,bisect.bisect()返回一个索引。如果要搜索的数已经在列表里面了,那么它返回的是这个数在列表中,最右边的这个目标数的索引+1. 以列表[41, 46, 67, 74, 75, 76, 80, 86, 92, 100]为例,要搜索75。由于75在原来列表中的索引是4。因此返回索引+1也就是5. 如果原来列表中,75出现了多次,比如[41, 46, 67, 74, 75, 75, 76, 80, 86, 92, 100]那么返回的是最右边那个75对应的索引+1,也就是6

如果要找的数字不在原来列表中,那么bisect.bisect()会返回一个索引,当我们把目标数字插入到这个列表中对应索引的位置时,列表依然有序。例如[41, 46, 67, 74, 75, 76, 80, 86, 92, 100]中,我们找82。它返回的是7。原来列表里面索引为7的位置是数字86,我们把82插入到这个位置,原有的数据依次后移一位,此时列表依然有序。

bisect这个模块还有一个函数,叫做bisect.bisect_left()。如果目标数字在原来的列表中,那么返回的是最左边那个数字对应的索引.如果不在列表中,那么返回的索引插入目标数字以后依然有序,如下图所示:

这个函数看起来非常简单,但你可能不知道,它在分布式系统中也有重要的用途。

假设现在你有10个Redis的单节点用来做分布式缓存。因为某种原因,你不能做集群。当你要搜索一个数据的时候,你要先确定这个数据在不在Redis中。如果在,就直接从Redis中读取数据;如果不在,就先去数据库里面读取,然后缓存到Redis中。

因为数据量很大,你不能把同一份数据同时存在10个Redis节点里面,因此你需要设计一个算法,不同的数据存放在不同的Redis节点中。

当你要查询数据的时候,你能根据这个算法查询到数据(如果在缓存中)应该存放在哪个Redis中。

稍微有一点分布式系统设计经验的同学肯定会想到,这个简单啊,10个Redis节点编号0-9.对key计算Hash值,这个哈希值是32位的十六进制数,可以转换成十进制以后对10求余数,余数是多少,就放到对应的节点里面。

这样一来,只要来了一个新的数据,你只需要去余数对应的Redis中判断它有没有缓存就可以了。

但问题来了,如果你开始使用这个方法,Redis中已经有数据了,那么你的Redis节点数就不能变了。一旦你增加或者减少1个节点,所有余数全部变了,新来的数据找到的Redis节点肯定是错的。例如key的Hash值原来除以10,余数是2,现在除以9,余数是1.那本来你应该去2号Redis找缓存,现在却跑到1号Redis找缓存,那一定找不到。

这个问题要怎么解决呢?我们用一个简单的例子来做演示。假设我现在有一个列表:[200, 250, 300, 400, 500, 530, 600]。每个数字代表这个价位的房子。单位是万。你想买一个房子,但便宜的房子太破,好的房子又太贵。因此你只找价格等于你的期望,或者虽然比你的期望略高但差距最小的房子。

假设现在你的期望是250万,而正好有个房子卖250万,因此你可以买它。

假设现在你的期望是470万,那么你唯一的选择是500万的房子。

到目前为止应该非常好理解,那么我们来增加或者减少候选项:

  1. 500万的房子被别人买走了。列表变成[200, 250, 300, 400, 530, 600],因此唯一适合你的是530万的房子。
  2. 如果现在250万的房子被人买走了,列表变成[200, 300, 400, 500, 530, 600]。此时对你没有任何影响,适合你的房子还是500万的房子。
  3. 如果现在增加了一个480万的房子,列表变成[200, 250, 300, 400, 480, 500, 530, 600]。那么现在适合你的房子变成了480万。
  4. 如果现在增加了一个240万的房子,列表变成[200, 240, 250, 300, 400, 500, 530, 600]。此时对你没有任何影响,适合你的还是500万的房子。

这个场景,我们正好可以使用bisect.bisect_left()!效果如下图所示:

当备选项发生改变的时候,只有你目标选项附近的房子受到了影响。而小于你候选项的房子和贵的多的房子的变动,对你没有任何影响。

你注意到了吗,这个场景跟我们分布式缓存增减Redis节点的场景非常像。我们原来有10台Redis,现在新增了一台,变成11台了。那么只有一台Redis的部分缓存会迁移到这个新增的Redis中。而其它9台Redis的缓存不需要做任何改变。

同理,当我们删除一台Redis节点时,这个被删除的节点里面的数据,只需要同步到它旁边的另一台Redis节点中就可以了。另外8个Redis节点不需要做任何修改!(也可以不同步,只有一小部分key会因为删除这个节点导致找不到数据,而重新读数据库。80%的缓存不会受到任何影响。)

这就是一致性Hash的算法。

我来简单描述一下这个算法的实现过程。首先,我们使用redis.Redis(不同redis的连接参数)创建10个连接对象。然后把每个连接对象和一个Hash值创建映射,如下图所示:

然后,我们把这10个Hash值排序以后放到一个列表中。如下图所示:

现在,来了一条新的缓存查询需求,我们计算key对应的Hash值,然后使用bisect.bisect_left()到列表中去寻找它对应的Redis节点的Hash值的索引。如果返回的索引等于列表的长度,那么让索引等于0. 找到索引以后,拿到对应的Redis节点的Hash,最后再用这个Hash去找到对应的Redis节点,简化代码如下:

`

如果新增或者删除了Redis节点,那么只需要更新node_mapcycle就可以了。只会发生很小的数据迁移,对绝大部分的缓存都不会造成任何影响。例如我现在把第1个Redis链接对象对应的Hash:fbef6b15be1abe9edc8f6aaac6a86357node_mapcycle中删除。再进行查询,会发现依然找到的是编号为6的Redis节点。

一致性Hash在分布式系统中有广泛的应用。但你可能想不到,它的核心原理就是二分搜索里面的bisect_left

当然,上面只是简化算法。一致性Hash的完整算法还涉及到虚拟节点和避免数据倾斜的算法。如果大家有兴趣的话,我也可以写一篇文章,完整解释它的算法实现。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
7月前
|
分布式计算 Ubuntu Hadoop
百度搜索:蓝易云【Ubuntu搭建全分布式Hadoop】
请注意,以上只是概述,并不包含详细的步骤和指令。搭建全分布式Hadoop是一个复杂的过程,需要对Hadoop的架构和配置有深入的理解,并熟悉Linux系统管理。建议在搭建全分布式Hadoop之前,先学习相关知识并查阅官方文档和教程,以确保正确搭建和配置Hadoop集群。
64 0
|
4月前
|
SQL JSON 大数据
ElasticSearch的简单介绍与使用【进阶检索】 实时搜索 | 分布式搜索 | 全文搜索 | 大数据处理 | 搜索过滤 | 搜索排序
这篇文章是Elasticsearch的进阶使用指南,涵盖了Search API的两种检索方式、Query DSL的基本语法和多种查询示例,包括全文检索、短语匹配、多字段匹配、复合查询、结果过滤、聚合操作以及Mapping的概念和操作,还讨论了Elasticsearch 7.x和8.x版本中type概念的变更和数据迁移的方法。
ElasticSearch的简单介绍与使用【进阶检索】 实时搜索 | 分布式搜索 | 全文搜索 | 大数据处理 | 搜索过滤 | 搜索排序
因为一个问题、我新学了一门技术 ElasticSearch 分布式搜索
这篇文章讲述了作者因为一个检索问题而学习了ElasticSearch技术,并分享了排查和解决ElasticSearch检索结果与页面展示不符的过程。
因为一个问题、我新学了一门技术 ElasticSearch 分布式搜索
|
5月前
|
缓存 Devops 微服务
微服务01好处,随着代码越多耦合度越多,升级维护困难,微服务技术栈,异步通信技术,缓存技术,DevOps技术,搜索技术,单体架构,分布式架构将业务功能进行拆分,部署时费劲,集连失败如何解决
微服务01好处,随着代码越多耦合度越多,升级维护困难,微服务技术栈,异步通信技术,缓存技术,DevOps技术,搜索技术,单体架构,分布式架构将业务功能进行拆分,部署时费劲,集连失败如何解决
|
5月前
|
运维 监控 Java
在大数据场景下,Elasticsearch作为分布式搜索与分析引擎,因其扩展性和易用性成为全文检索首选。
【7月更文挑战第1天】在大数据场景下,Elasticsearch作为分布式搜索与分析引擎,因其扩展性和易用性成为全文检索首选。本文讲解如何在Java中集成Elasticsearch,包括安装配置、使用RestHighLevelClient连接、创建索引和文档操作,以及全文检索查询。此外,还涉及高级查询、性能优化和故障排查,帮助开发者高效处理非结构化数据。
79 0
|
7月前
|
分布式计算 Hadoop Java
百度搜索:蓝易云【HBase分布式安装配置教程。】
以上是一个简要的HBase分布式安装和配置教程。需要注意的是,HBase的配置和部署涉及更多的细节和参数设置,取决于你的特定环境和需求。建议你参考HBase官方文档或其他可靠资源,以获得更详细和全面的指导。
80 6
|
Web App开发 Docker 容器
百度搜索:蓝易云【用docker搭建selenium grid分布式环境实践】
通过这些步骤,您可以使用Docker搭建Selenium Grid分布式环境,并在多个节点上并行运行Selenium测试。根据实际需求,您还可以进行更高级的配置和扩展,如增加更多的节点、配置浏览器版本等。
72 1
|
存储 索引
ES 分布式搜索的运行机制
ES 分布式搜索的运行机制
59 1
ES 分布式搜索的运行机制
|
前端开发
47分布式电商项目 - 商品关键字搜索
47分布式电商项目 - 商品关键字搜索
46 0
47分布式电商项目 - 商品关键字搜索
|
消息中间件 搜索推荐 索引
57分布式电商项目 - ActiveMQ 实现运营商后台与搜索服务的零耦合(二)
57分布式电商项目 - ActiveMQ 实现运营商后台与搜索服务的零耦合(二)
154 0