当前位置:

首页 > 编程开发 > redis中多样的数据类型及集群相关的知识有哪些

redis中多样的数据类型及集群相关的知识有哪些

多样的数据类型string类型简单方便,支持空间预分配,也就是每次会多分配点空间,这样string如果下次变长的话,就不需要额外的申请空了,当然前提是剩余的空间够用。【相关推荐:Redis视频教程】List类型可以实现简单的消息队列,但是注意可能存在消息丢失哦,它并不持ACK模式。Hash表有点像关系型数据库,但是当hash表越来越大的时候,请注意,避免使用hgetall之类的语句,因为请求大量的数据会导致redis阻塞,这样后面的兄弟们就得等待了。set集合类型可以帮你做一些统计,比如你要统计某天活跃的

redis中多样的数据类型及集群相关的知识有哪些

多样的数据类型

string 类型简单方便,支持空间预分配,也就是每次会多分配点空间,这样 string 如果下次变长的话,就不需要额外的申请空了,当然前提是剩余的空间够用。【相关推荐:Redis视频教程】

List 类型可以实现简单的消息队列,但是注意可能存在消息丢失哦,它并不持 ACK 模式。

Hash 表有点像关系型数据库,但是当 hash 表越来越大的时候,请注意,避免使用 hgetall 之类的语句,因为请求大量的数据会导致redis阻塞,这样后面的兄弟们就得等待了。

set 集合类型可以帮你做一些统计,比如你要统计某天活跃的用户,可以直接把用户ID扔到集合里,集合支持一些骚操作,比如 sdiff 可以获取集合之间的差集,sunion 可以获取集合之间的并集,功能很多,但是一定需要谨慎,因为牛逼的功能是有代价的,这些操作需要耗费一些 CPU 和IO 资源,可能会导致阻塞,因此大集合之间的骚操作要慎用,

zset 可以说是最闪耀的星,可以做排序,因为可以排序,因此应用场景挺多,比如点赞前xx名用户,延时队列等等。

bitmap 位图的好处就是在于节省空间,特别在做一些统计类的方面,比如要统计某一天有多少个用户签到了并且某个用户是否签到了,如果不用bitmap的话,你可能会想到用set。

SADD day 1234//签到就添加到集合
SISMEMBER day 1234//判断1234是否签到
SCARD day   //有多少个签到的

set 在功能上可以满足,但是相比bitmap的话,set要更耗费存储空间,set的底层主要是由整数集合或者 hashtable 组成,整数集合只有在数据量非常小的情况下才会使用,一般是小于512个元素,同时元素必须都是整数,对于set来说,整数集合的数据更加紧凑,他们在内存是上连续的,查询的话只能是二分查找了,时间复杂度是O(logN),而 hashtable 就不同了,这里的 hashtable 和 redis 的5大数据类型中的hash是一样的,只不过没有 value 而已,value 指向个 null,同时也不存在冲突,因为这里是集合,但是需要考虑 rehash 相关问题。ok,扯的有点远,我们说的用户签到问题,在用户非常多的情况下,set 的话肯定会用到 hashtable,hashtable 的话,其实每个元素都是个 dictEntry 结构体

typedef struct dictEntry {
    // 键
    void *key;
    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;
    // 指向下个哈希表节点,形成链表
    struct dictEntry *next;

} dictEntry;

从这个结构体可以看到什么呢?首先虽然值 union(没有 value)和 next(没有冲突)是空的,但是结构体本身需要空间,还需要加上个 key,这个占用空间是实打实的,而如果用 bitmap 的话,一个bit位就可以代表一个数字,很省空间,我们来看看 bitmap 的方式如何设置和统计。

SETBIT day 1234 1//签到
GETBIT day 1234//判断1234是否签到
BITCOUNT day//有多少个签到的

bf 这是 redis4.0 之后支持的布隆过滤器 RedisBloom,但是需要单独加载对应的 module,当然我们也可以基于上述的 bitmap 来实现自己的布隆过滤器,不过既然 redis 已经支持了,通过 RedisBloom 可以减少我们的开发时间,布隆过滤器是干嘛的,我这里就不赘述了,直接来看看 RedisBloom 相关的用法吧。

# 可以通过docker的方式快速拉取镜像来玩耍
docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
docker exec -it redis-redisbloom bash
redis-cli
# 相关操作
bf.reserve sign 0.001 10000
bf.add sign 99 //99这个用户加入
bf.add exists 99//判断99这个用户是否存在

因为布隆过滤器是存在误判的,所有 bf 支持自定义误判率,0.001就代表误判率,10000 代表布隆过滤器可以存储的元素个数,当实际存储的元素个数超过这个值的时候,误判率会提高。

HyperLogLog 可以用于统计,它的优点就是占用的存储空间极小,只需要 12KB 的内存就可以统计 2^64 个元素,那它主要统计什么呢?其实主要就是基数统计,比如像 UV 这种,从功能上来说 UV 可以用 set 或者 hash 来存储,但是缺点就是耗费存储,容易使之变成大 key,如果想要节省空间,bitmap 也可以,12KB 空间的 bitmap 只能统计 12*1024*8=98304个元素,而 HyperLogLog 却可以统计 2^64 个元素,但是这么牛逼的技术其实是有误差的,HyperLogLog 是基于概率来统计的,标准误算率是 0.81%,在统计海量数据并且对精度要求不那么高的场景下,HyperLogLog 在节省空间这块还是很优秀的。

PFADD uv 1 2 3 //1 2 3是活跃用户
PFCOUNT uv //统计

GEO 是可以应用在地理位置的业务上,比如微信附近的人或者附近的车辆等等,先来看一下如果没有GEO 这种数据结构,你如何知道你附近的人?首先得上报自己的地理位置信息吧,比如经度 116.397128,纬度 39.916527,此时可以用 string、hash 数据类型存储,但是如果要查找你附近的人,string 和 hash 这种就无能为例了,你不可能每次都要遍历全部的数据来判断,这样太耗时了,当然你也不可能通过 zset 这种数据结构来把经纬度信息当成权重,但是如果我们能把经纬度信息通过某种方式转换成一个数字,然后当成权重好像也可以,这时我们只需通过zrangebyscore key v1 v2也可以找到附近的人。真的需要这么麻烦吗?于是 GEO 出现了,GEO 转换经纬度为数字的方法是“二分区间,区间编码”,这是什么意思呢?以经度为例,它的范围是[-180,180],如果要采用3位编码值,那么就是需要二分3次,二分后落在左边的用0表示,右边的用1表示,以经度是121.48941 来说,第一次是在[0,180]这个区间,因此记1,第二次是在[90,180],因此再记1,第三次是在[90,135],因此记0。纬度也是同样的逻辑,假设此时对应的纬度编码后是010,最后把经纬度合并在一起,需要注意的是经度的每个值在偶数位,纬度的每个值在奇数位。

1 1 0   //经度
 0 1 0  //纬度
------------
101100 //经纬度对应的数值

原理是这样,我们再来看看 redis 如何使用 GEO:

GEOADD location 112.123456 41.112345 99 //上报用户99的地理位置信息
GEORADIUS location  112.123456 41.112345 1 km ASC COUNT 10 //获取附近1KM的人

搞懂集群

生产环境用单实例 redis 的应该比较少,单实例的风险在于:

  • 单点故障即服务故障,没有backup

  • 单实例压力大,又要提供读,又要提供写

于是我们首先想到的就是经典的主从模式,而且往往是一主多从,这是因为大部分应用都是读多写少的情况,我们的主负责更新,从负责提供读,就算我们的主宕机了,我们也可以选择一个从来充当主,这样整个应用依然可以提供服务。

复制过程的细节

当一个 redis 实例首次成为某个主的从的时候,这时主得把数据发给它,也就是 rdb 文件,这个过程 master 是要 fork 一个子进程来处理的,这个子进程会执行 bgsave 把当前的数据重新保存一下,然后准备发给新来的从,bgsave 的本质是读取当前内存中的数据然后保存到 rdb 文件中,这个过程涉及大量的 IO,如果直接在主进程中来处理的话,大概率会阻塞正常的请求,因此使用个子进程是个明智的选择。

那 fork 的子进程在 bgsave 过程中如果有新的变更请求会怎么办?

严格来说子进程出来的一瞬间,要保存的数据应该就是当时那个点的快照数据,所以是直接把当时的内存再复制一份吗?不复制的话,如果这期间又有变更改怎么办?其实这要说到写实复制(COW)机制,首先从表象上来看内存是一整块空间,其实这不太好维护,因此操作系统会把内存分成一小块一小块的,也就是内存分页管理,一页的大小一般是4K、8K或者16K等等,redis 的数据都是分布在这些页面上的,出于效率问题,fork 出来的子进程是和主进程是共享同一块的内存的,并不会复制内存,如果这期间主进程有数据变更,那么为了区分,这时最快捷的做法就是把对应的数据页重新复制一下,然后主的变更就在这个新的数据页上修改,并不会修改来的数据页,这样就保证了子进程处理的还是当时的快照。

以上说的变更是从快照的角度来考虑的,如果从数据的一致性来说,当快照的 rdb 被从库应用之后,这期间的变更该如何同步给从库?答案是缓冲区,这个缓冲区叫做 replication buffer,主库在收到需要同步的命令之后,会把期间的变更都先保存在这个缓冲区中,这样在把 rdb 发给从库之后,紧接着会再把 replication buffer 的数据也发给从库,最终主从就保持了一致。

replication buffer不是万能的补给剂

我们来看看 replication buffer 持续写入的时间有多长。

  • 我们知道主从同步的时候,主库会执行 fork 来让子进程完成相应地工作,因此子进程从开始执行 bgsave 到执行完毕这期间,变更是要写入 replication buffer 的。

  • rdb 生成好之后,需要把它发送给从库,这个网络传输是不是也需要耗点时间,这期间也是要写入 replication buffer 的。

  • 从库在收到 rdb 之后需要把 rdb 应用到内存里,这期间从库是阻塞的,无法提供服务,因此这期间也是要写入 replication buffer 的。

replication buffer 既然是个 buffer,那么它的大小就是有限的,如果说上面3个步骤中,只要有一个耗时长,就会导致 replication buffer 快速增长(前提是有正常的写入),当 replication buffer 超过了限制之后就会导致主库和从库之间的连接断开,断开之后如果从库再次连接上来就会导致重新开始复制,然后重复同样的漫长的复制步骤,因此这个 replication buffer 的大小还是很关键的,一般需要根据写入的速度、每秒写入的量和网络传输的速度等因素来综合判断。

从库网络不好和主库断了该怎么办?

正常来说,只要主从之间的连接建立好了,后面主库的变更可以直接发给从库,让从库直接回放,但是我们并不能保证网络环境是百分百的通畅的,因此也要考虑从库和主库之间的断联问题。

应该是在 redis2.8 以前,只要从库断联,哪怕只有很短的时间,后面从库再次连接上来的时候,主库也会直接无脑的进行全量同步。在 2.8 版本及以后,开始支持增量复制了,增量复制的原理就是得有个缓冲区来保存变更的记录,这里这个缓冲区叫做repl_backlog_buffer,这个缓冲区从逻辑上来说是个环形缓冲区,写满了就会从头开始覆盖,所以也有大小限制。在从库重新连接上来的时候,从库会告诉主库:“我当前已经复制到了xx位置”,主库收到从库的消息之后开始查看xx位置的数据是否还在 repl_backlog_buffer 中,如果在的话,直接把xx后面的数据发给从库即可,如果不在的话,那无能为力了,只能再次进行全量同步。

需要一个管理者

在主从模式下,如果主库挂了,我们可以把一个从库升级成主库,但是这个过程是手动的,靠人力来操作,不能使损失降到最低,还是需要一套自动管理和选举的机制,这就是哨兵,哨兵它本身也是个服务,只不过它不处理数据的读写而已,它只负责管理所有的 redis 实例,哨兵每隔一段时间会和各个 redis 通信(ping 操作),每个 redis 实例只要在规定的时间内及时回复,就可以表明自己的立场。当然哨兵本身也可能存在宕机或者网络不通的情况,因此一般哨兵也会搭建个哨兵集群,这个集群的个数最好是奇数,比如3个或者5这个这种,奇数的目的主要就是为了选举(少数服从多数)。

当某个哨兵在发起 ping 后没有及时收到 pong,那么就会把这个 redis 实例标记下线,此时它还是不是真正的下线,这时其他的哨兵也会判定当前这个哨兵是不是真正的下线,当大多数哨兵都认定这个 redis 是下线状态,那么就会把它从集群中踢出去,如果下线的是从库,那么还好,直接踢出去就ok,如果是主库还要触发选举,选举也不是盲目选举,肯定是要选出最合适的那个从来充当新的主库。这个最合适充当主库的库,一般会按照以下优先级来确定:

  • 权重,每个从库其实都可以设置一个权重,权重越高的从库会被优先选择

  • 复制的进度,每个从库复制的进度可能是不一样的,优先选择当前和主库数据差距最小的那个

  • 服务的 ID,其实每个 redis 实例都有自己的 ID,如果以上条件都一样,那么会选择 ID 最小的那个库来充当主库

更强的横向伸缩性

主从模式解决了单点故障问题,同时读写分离技术使得应用支撑能力更强,哨兵模式可以自动监管集群,实现自动选主,自动剔除故障节点的能力。

正常来说只要读的压力越来越大,我们可以添加从库来缓解,那如果主库压力很大怎么办?这就得提到接下来要说的分片技术了,我们只需要把主库切成几片,部署到不同的机器上即可。这个分片就是 redis 中的概念了,当分片的时候,redis 会默认分成 0~16383 也就是一共 16384 个槽,然后把这些槽平均分到每个分片节点上就可以起到负载均衡的作用了。每个 key 具体该分到哪个槽中,主要是先 CRC16 得到一个 16bit 的数字,然后这个数字再对 16384 取模即可:

crc16(key)%16384

然后客户端会缓存槽信息,这样每当一个 key 到来时,只要通过计算就知道该发给哪个实例来处理来了。但是客户端缓存的槽信息并不是一成不变的,比如在增加实例的时候,这时候会导致重新分片,那么原来客户端缓存的信息就会不准确,一般这时候会发生两个常见的错误,严格来说也不是错误,更像一种信息,一个叫做MOVED,一个叫做ASK。moved的意思就说,原来是实例A负责的数据,现在被迁移到了实例B,MOVED 代表的是迁移完成的,但是 ASK 代表的是正在迁移过程中,比如原来是实例A负责的部分数据,现在被迁移到了实例B,剩下的还在等待迁移中,当数据迁移完毕之后 ASK 就会变成 MOVED,然后客户端收到 MOVED 信息之后就会再次更新下本地缓存,这样下次就不会出现这两个错误了。

本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发
相关文章 更多
using namespace 使用中遇到的问题怎么解决
using namespace 使用中遇到的问题怎么解决

命名空间的基本概念与常见引入问题在C++等编程语言中,命名空间(namespace)是一种将代码标识符(如变量、函数、类名)封装在特定名称下的机制,其主要目的是避免命名冲突,尤其是在大型项目或使用多个第三方库时。使用“using namespace”指令可以将指定命名空间中的所有名称引入当前作用域,

c语言函数递归 实操经验总结:这些技巧很实用
c语言函数递归 实操经验总结:这些技巧很实用

理解递归的基本原理在C语言中,递归是一种函数调用自身的编程技术。要掌握它,首先需要理解其核心思想:将一个复杂的大问题,分解为一个或几个与原问题相似但规模更小的子问题,直到子问题足够简单,可以直接求解。这个过程通常包含两个关键部分:递归出口和递归体。递归出口定义了问题何时不再继续分解,即最简单、可直接

c语言函数递归 怎么选?常见方案对比分析
c语言函数递归 怎么选?常见方案对比分析

递归函数的基本概念与适用场景在C语言编程中,递归是一种函数调用自身的编程技巧。它并非适用于所有问题,但在处理某些具有自相似结构的问题时,能提供极其清晰和优雅的解决方案。递归的核心思想是将一个大规模问题分解为一个或多个同类型但规模更小的子问题,直到子问题简单到可以直接求解。典型的适用场景包括树形结构的

Objective-C 内存管理入门:从 alloc 到 dealloc 的生命周期详解
Objective-C 内存管理入门:从 alloc 到 dealloc 的生命周期详解

理解内存管理的基石在Objective-C的编程世界中,内存管理是开发者必须掌握的核心技能之一。它直接关系到应用的性能、稳定性与资源利用效率。与一些采用自动垃圾回收机制的语言不同,Objective-C在很长一段时间里,依赖一套基于引用计数的、需要开发者部分介入的管理规则。这套规则的核心思想是明确的

如何正确使用 dealloc 以避免 iOS 应用中的内存泄漏
如何正确使用 dealloc 以避免 iOS 应用中的内存泄漏

理解 dealloc 的角色与时机在 iOS 应用开发中,内存管理是保障应用性能与稳定性的基石。dealloc 方法是 Objective-C 中对象生命周期结束时的关键回调,它标志着对象即将被系统回收内存。正确理解其触发时机至关重要:当一个对象的引用计数降为零时,运行时系统会自动调用该对象的 de

深入理解 Objective-C 中的 dealloc 方法:内存管理核心机制
深入理解 Objective-C 中的 dealloc 方法:内存管理核心机制

内存管理的基石在Objective-C的世界里,内存管理是开发者必须掌握的核心技能之一。作为一门在手动引用计数(MRC)时代诞生的语言,Objective-C要求程序员对对象的生命周期有清晰的认识。dealloc方法正是这一生命周期中至关重要的终点站。它是一个实例方法,当对象的引用计数降为零时,系统

理解 native2ascii:Java 国际化开发中的字符编码工具
理解 native2ascii:Java 国际化开发中的字符编码工具

native2ascii 工具的基本定位在Ja va应用程序的国际化与本地化开发过程中,处理非拉丁字符集是一个常见且关键的环节。Ja va内部使用Unicode字符集来统一表示全球各种语言的文字,但其属性文件(.properties)在历史上要求使用ASCII编码,或者更准确地说,要求非ASCII字

如何使用 native2ascii 转换中文字符为 Unicode 转义序列
如何使用 native2ascii 转换中文字符为 Unicode 转义序列

理解 native2ascii 工具的基本用途在软件开发,特别是涉及国际化处理的场景中,开发者常常需要处理不同编码的文本资源。native2ascii 是 Ja va 开发工具包(JDK)中提供的一个命令行实用程序,其主要功能是将包含本地字符编码(非ASCII字符)的文件,转换为包含 Unicode

Java native2ascii 命令详解:解决属性文件乱码问题
Java native2ascii 命令详解:解决属性文件乱码问题

native2ascii 命令的由来与作用在Ja va开发中,处理国际化资源文件是一个常见需求。资源文件通常以.properties格式存储,用于支持多语言界面。然而,Ja va属性文件默认采用ISO-8859-1字符集编码,这导致了一个直接的问题:当文件中包含非拉丁字符(如中文、日文、韩文等)时,

一个 memwatch 实战案例:定位野指针问题
一个 memwatch 实战案例:定位野指针问题

内存监控工具的价值与挑战在软件开发,尤其是使用C/C++这类手动管理内存的语言时,内存错误是程序员最常遭遇的难题之一。其中,野指针问题因其隐蔽性和破坏性,往往成为最难定位的“幽灵”缺陷。它可能潜伏在代码中,在特定条件下才被触发,导致程序崩溃、数据损坏或难以预测的行为。传统的调试手段,如打印日志或使用

查看更多
精品专题 更多
装机必备
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows
Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件
macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

Mac软件 更多
灵活计算器
灵活计算器
macOS/iOS/Android

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师
macOS

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

WINDOWS 更多
Windows 10
Windows 10
Windows

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘
Windows/macOS/iOS/Android

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。