1、什么是Redis (Remote Dictionary Server)?
Redis 是完全开源免费的, 遵守 BSD 协议, 是一个高性能的 key-value 形式的NoSQL内存数据库;
支持将内存中的数据以快照和日志的形式持久化到硬盘;
支持数据的备份即 master-slave 模式的数据备份;
支持丰富的数据结构
2、redis优势:
1)性能高,速度快
Redis命令执行速度非常快,官方给出的读写性能可以达到10W/秒。为什么会如此之快呢?有以下几个因素:
数据存储在内存中,直接与内存连接。
由相对底层的C语言实现,离操作系统更近。
实现源码很精湛,仅仅几万行代码,简单稳定。
使用了单线程模型,无多线程竞争、锁等问题。
2)丰富的数据结构
Redis与其他的内存数据库不同的是,Redis拥有丰富的数据类型(5种),如字符串、哈希、列表、集合、有序集合。正是因为Redis丰富的数据类型,所有它能应用的场景非常多。
其实还包括其他的数据结构:HyperLogLog、Geo、Pub/Sub,只是暂时没用过。
3)丰富的特性: 除了支持丰富的数据结构外,还支持以下高级功能:
支持键过期功能,可以用来实现定时缓存
支持发布/订阅功能,可以有来实现消息队列
支持事务功能,可以保证多条命令的事务性
支持供管道功能,能够批量处理命令
支持Lua脚本功能。
支持集群分片和数据复制功能
支持内存数据持久化硬盘功能
4)丰富的客户端:各种各种的语言都能接入到Redis,接入包括了所有的主流开发语言。
redis 支持的java 客户端连接工具有:Redisson、Jedis、lettuce等,官方推荐使用Redisson(Redisson是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象)
Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
3、Redis 的几种常见使用方式?
1)Redis 单副本:
采用单个 Redis 节点部署架构,没有备用节点实时同步数据,不提供数据持久化和备份策略,适用于数据可靠性要求不高的纯缓存业务场景。
缺点:
a.在缓存使用,进程重启后,数据丢失,即使有备用的节点解决高可用性,但是仍然不能解决缓存预热问题,因此不适用于数据可靠性要求高的业务;
b.高性能受限于单核 CPU的处理能力(Redis 是单线程机制),CPU 为主要瓶颈,所以适合操作命令简单,排序、计算较少的场景。也可以考虑用 Memcached 替代。
2)Redis 多副本(主从):
采用主从(replication)部署结构,相较于单副本而言最大的特点就是主从实例间数据实时同步,并且提供数据持久化和备份策略。主从实例部署在不同的物理服务器上,根据公司的基础环境配置,可以实现同时对外提供服务和读写分离策略。
优点:
高可靠性:支持主从切换,保障服务稳定性;
读写分离策略:能有效应对大并发量的读操作
缺点:
故障恢复复杂:如果没有 RedisHA 系统(需要开发),当主库节点出现故障时,需要手动将一个从节点晋升为主节点;
主库的写能力受到单机的限制,可以考虑分片;
主库的存储能力受到单机的限制,可以考虑 Pika;
3)Redis Sentinel(哨兵):
是社区版本推出的原生高可用解决方案,其部署架构主要包括两部分:Redis Sentinel 集群和 Redis 数据集群。其中 Redis Sentinel 集群是由若干 Sentinel 节点组成的分布式集群,
可以实现故障发现、故障自动转移、配置中心和客户端通知。Redis Sentinel 的节点数量要满足 2n+1(n>=1)的奇数个。
优点:
实现自动主从切换;
轻松实现Redis 数据节点的线形扩展,进而突破 Redis 自身单线程瓶颈,可极大满足 Redis 大容量或高性能的业务需求;
可通过 Sentinel 监控一组 Redis 数据节点或多组数据节点;
缺点:
部署相对 Redis 主从模式要复杂一些,原理理解更繁琐;
Redis Sentinel 主要是针对 Redis 数据节点中的主节点的高可用切换,对 Redis 的数据节点做失败判定分为主观下线和客观下线两种,对于 Redis 的从节点有对节点做主观下线操作,并不执行故障转移。
不能解决读写分离问题,实现起来相对复杂。
资源浪费,Redis 数据节点中 slave 节点作为备份节点不提供服务;
4)Redis Cluster
是社区版推出的 Redis 分布式集群解决方案,主要解决 Redis 分布式方面的需求,比如,当遇到单机内存,并发和流量等瓶颈的时候,Redis Cluster 能起到很好的负载均衡的目的。
Redis Cluster 集群节点最小配置 6 个节点以上(3 主 3 从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。
Redis Cluster 采用虚拟槽分区,所有的键根据哈希函数映射到 0~16383 个整数槽内,每个节点负责维护一部分槽以及槽所印映射的键值数据。
优点:
数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布;
可扩展性:可线性扩展到 1000 多个节点,节点可动态添加或删除;
高可用性:部分节点不可用时,集群仍可用。
缺点:
数据通过异步复制,不保证数据的强一致性;
Client 实现复杂;
节点会因为某些原因发生阻塞(阻塞时间大于 clutser-node-timeout),被判断下线,这种 failover 是没有必要的;
多个业务使用同一套集群时,无法根据统计区分冷热数据,资源隔离性较差,容易出现相互影响的情况;
Key 批量操作限制,如使用 mset、mget 目前只支持具有相同 slot 值的 Key 执行批量操作。对于映射为不同 slot 值的 Key 由于 Keys 不支持跨 slot 查询,所以执行 mset、mget、sunion 等操作支持不友好。
Key 事务操作支持有限,只支持多 key 在同一节点上的事务操作,当多个 Key 分布于不同的节点上时无法使用事务功能
不支持多数据库空间,单机下的 redis 可以支持到 16 个数据库,集群模式下只能使用 1 个数据库空间,即 db 0
避免产生 hot-key,导致主库节点成为系统的短板
避免产生 big-key,导致网卡撑爆、慢查询等
重试时间应该大于 cluster-node-time 时间
Redis Cluster 不建议使用 pipeline 和 multi-keys 操作,减少 max redirect 产生的场景。
5)Redis 自研
自研的高可用解决方案,主要体现在配置中心、故障探测和 failover 的处理机制上,通常需要根据企业业务的实际线上环境来定制化。某大型互联网公司sn 就在redis 2.8版本上进行修改定制的缓存服务器软件称为zedis,在jedis版本上定制的缓存客户端软件称为sedis
4、redis 的使用场景:
1)缓存:购物车、页面、推荐商品的缓存;
2)商品排行榜:Redis提供的有序集合数据结构的各种复杂的排行榜应用
3)计数统计:比如隐私政策多次取消后,就不再弹框提示;
4)分布式会话:
集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,
而是由session服务及内存数据库管理。
5)分布式锁:
分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是
不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。
6)社交网络:点赞、踩、关注/被关注、共同好友等,可通过redis 丰富的数据结构来实现。
7)最新列表:
Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容页即可。
8)消息系统
消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,
能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。
5、redis 不同数据结构的使用场景:
String:缓存、限流、计数器、分布式锁、分布式Session
Hash:存储用户相关信息(名称、角色、昵称)、用户主页访问量、组合查询
List:微博关注人时间轴列表、简单队列
Set:赞、踩、标签、好友关系
Zset:排行榜
6、redis 单线程操作为什么这么快:
纯内存操作;
单线程操作,避免了频繁的上下文切换;
采用了非阻塞 I/O 多路复用机制;
7、为什么要了解redis的内存模型?
估算 Redis 内存使用量,选择合适的机器配置,可以在满足需求的情况下节约成本;
了解 Redis 内存模型可以选择更合适的数据类型和编码,更好的利用 Redis 内存,优化内存占用;
利于分析解决问题:Redis 出现阻塞、内存占用等问题
8、内存统计查看命令:info memory 会显示如下信息:
used_memory:分配器分配的内存总量(单位字节),其是从redis的角度的到的量
used_memory_rss:Redis 进程占据操作系统的内存,其是从操作系统角度得到的量
mem_fragmentation_ratio:used_memory_rss / used_memory 的比值,存碎片比率(一般大于 1,且该值越大,内存碎片比例越大)
Redis 使用的内存分配器,默认的 jemalloc,在编译时指定;可以是 libc 、jemalloc 或者 tcmalloc
9、redis 内存划分?
1)数据存储占用的内存:在存储对象时,并不是直接将数据扔进内存,而是会对对象进行各种包装:如 RedisObject、SDS 等,该部分内存会统计在 used_memory 中;
2)进程本身运行需要的内存:Redis 主进程本身运行肯定需要占用内存,如代码、常量池等,这部分内存不是由 jemalloc 分配,因此不会统计在 used_memory 中。除了主进程外,Redis 创建的子进程运行也会占用内存,如 Redis 执行 AOF、RDB 重写时创建的子进程。当然,这部分内存不属于 Redis 进程,也不会统计在 used_memory 和 used_memory_rss 中。
3)缓冲内存:缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF 缓冲区等,这部分内存由 jemalloc 分配,因此会统计在 used_memory 中。
a.客户端缓冲区存储客户端连接的输入输出缓冲;
b.复制积压缓冲区用于部分复制功能;
c.AOF 缓冲区用于在进行 AOF 重写时,保存最近的写入命令。
4)内存碎片
a.内存碎片的产生与对数据进行的频繁更改操作、数据的大小差异特点等都有关;
b.与使用的内存分配器也有关系;
c.可通过重启减少内存碎片:因为重启之后,Redis 重新从备份文件中读取数据,在内存中进行重排,为每个数据重新选择合适的内存单元,减小内存碎片。
10、谈谈redis 的数据是如何存储的?
这就要涉及到内存分配器(如 jemalloc)、简单动态字符串(SDS)、5 种对象类型及内部编码、RedisObject;
以key=hello,value=world 为例进行说明:
1)dictEntry:Redis 是 Key-Value 数据库,因此对每个键值对都会有一个 dictEntry,里面存储了指向 Key 和 Value 的指针;next 指向下一个 dictEntry,与本 Key-Value 无关。其中 key(”hello”)并不是直接以字符串存储,而是存储在 SDS 结构中。
2)RedisObject:Value(“world”)既不是直接以字符串存储,也不是像 Key 一样直接存储在 SDS 中,而是存储在 RedisObject 中。实际上,不论 Value 是 5 种类型的哪一种,都是通过 RedisObject 来存储的;而 RedisObject 中的 type 字段指明了 Value 对象的类型,ptr 字段则指向对象所在的地址。不过可以看出,字符串对象虽然经过了 RedisObject 的包装,但仍然需要通过 SDS 存储。
3)jemalloc:Redis 在编译时便会指定内存分配器;内存分配器可以是 libc 、jemalloc 或者 tcmalloc,默认是 jemalloc。jemalloc 作为 Redis 的默认内存分配器,在减小内存碎片方面做的相对比较好。jemalloc 在 64 位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当 Redis 存储数据时,会选择大小最合适的内存块进行存储。无论是 DictEntry 对象,还是 RedisObject、SDS 对象,都需要内存分配器(如 jemalloc)分配内存进行存储。以 DictEntry 对象为例,有 3 个指针组成,在 64 位机器下占 24 个字节,jemalloc 会为它分配 32 字节大小的内存单元。
11、谈谈redisObject 这种数据结构
Redis 对象有 5 种类型;无论是哪种类型,Redis 都不会直接存储,而是通过 RedisObject 对象进行存储。 RedisObject 对象非常重要,Redis 对象的类型、内部编码、内存回收、共享对象等功能,都需要RedisObject 支持,其结构具体如下5部分:
1)type 字段:表示对象的类型,占 4 个比特。
目前包括 REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。
字段类型查看的命令:type keyName
2)encoding 字段:表示对象的内部编码,占 4 个比特,对于 Redis 支持的每种类型,都有至少两种内部编码。通过 encoding 属性,Redis 可以根据不同的使用场景来为对象设置不同的编码,大大提高了 Redis 的灵活性和效率。
例如:
对于字符串,有 int、embstr、raw 三种编码。
对于列表对象,有压缩列表和双端链表两种编码方式;如果列表中的元素较少,Redis 倾向于使用压缩列表进行存储,因为压缩列表占用内存更少,而且比双端链表可以更快载入。当列表对象元素较多时,压缩列表就会转化为更适合存储大量元素的双端链表。
对象采用的编码方式的查看命令:object encoding keyName
3)lru:lru 记录的是对象最后一次被命令程序访问的时间
4)refcount(引用数量)与共享对象:
a.refcount:记录的是该对象被引用的次数,类型为整型。refcount 的作用,主要在于对象的引用计数和内存回收。当创建新对象时,refcount 初始化为 1;当有新程序使用该对象时,refcount 加 1;当对象不再被一个新程序使用时,refcount 减 1;当 refcount 变为 0 时,对象占用的内存会被释放。
b.共享对象:Redis 中被多次使用的对象(refcount>1),称为共享对象。Redis 为了节省内存,当有一些对象重复出现时,新的程序不会创建新的对象,而是仍然使用原来的对象。这个被重复使用的对象,就是共享对象。目前共享对象仅支持整数值的字符串对象。共享对象的具体实现:Redis 的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和 CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作复杂度为O(1);对于普通字符串,判断复杂度为 O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为 O(n^2)。虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)。就目前的实现来说,Redis服务器在初始化时,会创建 10000 个字符串对象,值分别是 0~9999 的整数值;当 Redis 需要使用值为 0~9999 的字符串对象时,可以直接使用这些共享对象。10000 这个数字可以通过调整参数 REDIS_SHARED_INTEGERS(4.0 中是 OBJ_SHARED_INTEGERS)的值进行改变。
共享对象的引用次数的查看命令:object refcount keyName
5)ptr 指针:
指针指向具体的数据,如前面的例子中,set hello world,ptr 指向包含字符串 world 的 SDS。Redis 没有直接使用 C 字符串(即以空字符’ ’结尾的字符数组)作为默认的字符串表示,而是使用了 SDS。SDS 是简单动态字符串(Simple Dynamic String)的缩写。
12、谈谈SDS结构这种数据结构?
包含以下三个部分:
a.buf 表示字节数组,用来存储字符串;
b.len 表示 buf 已使用的长度;
c.free 表示 buf 未使用的长度;
13、redis 的五种对象类型及其内部编码?
Redis 支持的5种对象类型,而每种结构都有至少两种编码,Redis 内部编码的转换,都符合编码转换在 Redis 写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换。这样做的好处是:接口与实现分离,当需要增加或改变内部编码时,用户使用不受影响,另一方面可以根据不同的应用场景切换内部编码,提高效率。
1)redis 的对象类型--字符串
a. 字符串是最基础的类型,因为所有的键都是字符串类型,且字符串之外的其他几种复杂类型的元素也是字符串,字符串长度不能超过 512MB。
b. 字符串类型的内部编码有 3 种。具体使用场景如下:
int:8 个字节的长整型。字符串值是整型时,这个值使用 long 整型表示。
embstr:<=39 字节的字符串。embstr 与 raw 都使用 RedisObject 和 sds 保存数据。 embstr 的使用只分配一次内存空间(因此 RedisObject 和 sds 是连续的),而 raw 需要分配两次内存空间(分别为 RedisObject 和 sds分配空间)。因此与 raw 相比,embstr 的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便。而 embstr 的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个 RedisObject 和 sds 都需要重新分配空间,因此 Redis 中的 embstr 实现为只读;
raw:大于 39 个字节的字符串;
c.编码转换:当 int 数据不再是整数,或大小超过了 long 的范围时,自动转化为 raw。而对于 embstr,由于其实现是只读的,因此在对 embstr 对象进行修改时,都会先转化为 raw 再进行修改。因此,只要是修改 embstr 对象,修改后的对象一定是 raw 的,无论是否达到了 39 个字节。
2)redis 的对象类型--列表(list)
a.列表用来存储多个有序的字符串,每个字符串称为元素;一个列表可以存储 2^32-1 个元素。Redis 中的列表支持两端插入和弹出,并可以获得指定位置(或范围)的元素,可以充当数组、队列、栈等。
b.内部编码:列表的内部编码可以是压缩列表(ziplist)或双端链表(linkedlist)
双端链表:由一个 list 结构和多个 listNode 结构组成,双端链表同时保存了表头指针和表尾指针,并且每个节点都有指向前和指向后的指针。链表中保存了列表的长度;dup、free 和 match 为节点值设置类型特定函数。所以链表可以用于保存各种不同类型的值,而链表中每个节点指向的是type为字符串的 RedisObject。
压缩列表:压缩列表是 Redis 为了节约内存而开发的,是由一系列特殊编码的连续内存块(而不是像双端链表一样每个节点是指针)组成的顺序型数据结构,具体结构相对比较复杂。与双端链表相比,压缩列表可以节省内存空间,但是进行修改或增删操作时,复杂度较高。因此当节点数量较少时,可以使用压缩列表;但是节点数量多时,还是使用双端链表划算。压缩列表不仅用于实现列表,也用于实现哈希、有序列表;使用非常广泛。
c.编码转换
只有同时满足下面两个条件时,才会使用压缩列表:列表中元素数量小于 512 个、列表中所有字符串对象都不足 64 字节。如果有一个条件不满足,则使用双端列表;且编码只可能由压缩列表转化为双端链表,反方向则不可能。
3)redis 的对象类型--哈希
a.内部编码:内层的哈希使用的内部编码可以是压缩列表(ziplist)和哈希表(hashtable)2 种
与哈希表相比,压缩列表用于元素个数少、元素长度小的场景;其优势在于集中存储,节省空间。同时,虽然对于元素的操作复杂度也由 O(n)变为了 O(1),但由于哈希中元素数量较少,因此操作的时间并没有明显劣势。
b.编码转换
只有同时满足下面两个条件时,才会使用压缩列表:哈希中元素数量小于 512 个;哈希中所有键值对的键和值字符串长度都小于 64 字节。如果有一个条件不满足,则使用哈希表;且编码只可能由压缩列表转化为哈希表,反方向则不可能。
4)redis 的对象类型--集合(set)
a.集合中的元素是无序的,因此不能通过索引来操作元素;集合中的元素不能有重复。一个集合中最多可以存储 2^32-1 个元素;除了支持常规的增删改查,Redis 还支持多个集合取交集、并集、差集。
b.内部编码:集合的内部编码可以是整数集合(intset)或哈希表(hashtable)。集合在使用哈希表时,值全部被置为 null。
c.编码转换:只有同时满足下面两个条件时,集合才会使用整数集合:集合中元素数量小于 512 个,集合中所有元素都是整数值。如果有一个条件不满足,则使用哈希表;且编码只可能由整数集合转化为哈希表,反方向则不可能。
5)redis 的对象类型--有序集合
a.有序集合与集合一样,元素都不能重复;但与集合不同的是,有序集合中的元素是有顺序的。与列表使用索引下标作为排序依据不同,有序集合为每个元素设置一个分数(score)作为排序依据。
b.内部编码:有序集合的内部编码可以是压缩列表(ziplist)或跳跃表(skiplist)跳跃表是一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。除了跳跃表,实现有序数据结构的另一种典型实现是平衡树;大多数情况下,跳跃表的效率可以和平衡树媲美,且跳跃表实现比平衡树简单很多,因此 Redis 中选用跳跃表代替平衡树。跳跃表支持平均 O(logN)、最坏 O(N) 的复杂点进行节点查找,并支持顺序操作。Redis 的跳跃表实现由 zskiplist 和 zskiplistNode 两个结构组成:前者用于保存跳跃表信息(如头结点、尾节点、长度等),后者用于表示跳跃表节点,具体结构相对比较复杂。
c.编码转换:只有同时满足下面两个条件时,才会使用压缩列表:有序集合中元素数量小于 128 个;有序集合中所有成员长度都不足 64 字节。如果有一个条件不满足,则使用跳跃表;且编码只可能由压缩列表转化为跳跃表,反方向则不可能。
14、谈谈对redis 实例下数据库的理解,及集群环境中对数据库的使用?
在搭建配置好Redis服务器后,该redis实例下,默认建立了16个数据库。Redis是一个字典结构的存储服务器,一个Redis实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。这与在一个关系数据库实例中可以创建多个数据库类似,所以可以将其中的每个字典都理解成一个独立的数据库。Redis默认支持16个数据库,可以通过调整Redis的配置文件redis/redis.conf中的databases来修改这一个值,设置完毕后重启Redis便完成配置。客户端与Redis建立连接后会默认选择0号数据库,不过可以随时使用SELECT命令更换数据库。
由于Redis不支持自定义数据库的名字,所以每个数据库都以编号命名。开发者则需要自己记录存储的数据与数据库的对应关系。另外Redis也不支持为每个数据库设置不同的访问密码,Redis 设置密码教程,所以一个客户端要么可以访问全部数据库,要么全部数据库都没有权限访问。
清空一个Redis实例中所有数据库中的数据的命令:FLUSHALL。该命令可以清空实例下的所有数据库数据,这与我们所熟知的关系型数据库所不同。关系型数据库多个库常用于存储不同应用程序的数据 ,且没有方式可以同时清空实例下的所有库数据。所以对于Redis来说这些db更像是一种命名空间,且不适宜存储不同应用程序的数据。比如可以使用0号数据库存储某个应用生产环境中的数据,使用1号数据库存储测试环境中的数据,但不适宜使用0号数据库存储A应用的数据而使用1号数据库B应用的数据,不同的应用应该使用不同的Redis实例存储数据。Redis非常轻量级,一个空Redis实例占用的内在只有1M左右,所以不用担心多个Redis实例会额外占用很多内存。
Redis实例默认建立了16个db,由于不支持自主进行数据库命名所以以dbX的方式命名。默认数据库数量可以修改配置文件的database值来设定。对于db正确的理解应为“命名空间”,多个应用程序不应使用同一个Redis不同库,而应一个应用程序对应一个Redis实例,不同的数据库可用于存储不同环境的数据。Redis集群下只有db0,不支持多db,且不支持redis实例数据库切换。集群的情况下不支持使用select命令来切换db,因为Redis集群模式下只有一个db0。
15、谈谈对redis槽点(slot)的认识:Redis 集群中内置了 16384 个哈希槽,这些哈希槽就被称为槽点。
问题1:redis 集群中key,如果存储到相应的节点的?
当需要在 Redis 集群中放置一个 key-value时,redis 会进行如下的操作:
a.首先:redis 会对 key 使用 crc16 算法算出一个值
b.其次:把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽
c.再次:redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
问题2:Redis 集群为什么没有使用一致性hash, 而是引入了哈希槽的概念?
a.使用哈希槽这种结构很容易添加或者删除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;在这一点上,我们以后新增或移除节点的时候不用先停掉所有的 redis 服务。
问题3:集群总共有2的14次方,16384个哈希槽,那么每一个哈希槽中存的key 和 value是什么?
往Redis Cluster中加入一个Key时,会根据crc16(key) mod 16384计算这个key应该分布到哪个hash slot中,一个hash slot中会有很多key和value。你可以理解成表的分区,使用单节点时的redis时只有一个表,所有的key都放在这个表里;改用Redis Cluster以后会自动为你生成16384个分区表,你insert数据时会根据上面的简单算法来决定你的key应该存在哪个分区,每个分区里有很多key。
16、redis 海量数据搜索(如:keys mobile:login_*)导致redis卡死为什么?该怎么办?
问题1:为什么卡死?
keys算法是遍历算法,复杂度是O(n),也就是数据越多,时间越高。数据量达到几百万,keys这个指令就会导致 Redis 服务卡顿,因为 Redis 是单线程程序,顺序执行所有指令,其它指令必须等到当前的 keys 指令执行完了才可以继续。
问题2:如何去遍历大数据量呢?
可以使用scan命令。同 keys 一样,scan 也提供模式匹配功能,但scan具有如下特点:
a.复杂度虽然也是 O(n),但是它是通过游标分步进行的,不会阻塞线程
b.提供 count 参数,不是结果数量,是Redis单次遍历字典槽位数量(约等于)
c.服务器不需要为游标保存状态,游标的唯一状态就是 scan 返回给客户端的游标整数;
d.返回的结果可能会有重复,需要客户端去重复,这点非常重要;
e.单次返回的结果是空的并不意味着遍历结束,而要看返回的游标值是否为零
问题3:scan命令使用:
scan命令格式 :scan cusor [MATCH pattern] [COUNT count]
scan 游标 MATCH <返回和给定模式相匹配的元素> count 每次迭代所返回的元素数量。
SCAN命令是增量的循环,每次调用只会返回一小部分的元素。所以不会让Redis假死;SCAN命令返回的是一个游标,从0开始遍历,到0结束遍历;
示列:scan 0 MATCH mobile:login_* count 5
从0开始遍历,返回了游标6,又返回了数据,继续scan遍历,就要从6开始
17、热点问题产生的原因、危害、如何解决?
问题1:产生原因?
如:热卖商品、热点新闻、热点评论、明星直播,使的请求分片集中,超过单 Server 的性能极限
问题2:危害?
a.流量集中,达到物理网卡上限;
b.请求过多,缓存分片服务被打垮;
c.DB 击穿,引起业务雪崩;
问题3:如何解决?可以通过以下三种方式:
a.缓存降级方案:降级到其他的缓存数据库;
b.将缓存存放到客户端
c.阿里云数据库解热点之道--读写分离方案解决热读
d.在Proxy 上增加本地缓存
18、缓存淘汰算法有哪些?及如何使用?
缓存算法是指令的一个明细表,用于决定缓存系统中哪些数据应该被删去。常见类型包括LFU、LRU、ARC、FIFO、MRU。
1)最不经常使用算法(LFU Less Frequently Used)
这个缓存算法使用一个计数器来记录条目被访问的频率。通过使用LFU缓存算法,最低访问数的条目首先被移除。这个方法并不经常使用,因为它无法对一个拥有最初高访问率之后长时间没有被访问的条目缓存负责。
2)最近最少使用算法(LRU Least Recently Used):
这个缓存算法将最近使用的条目存放到靠近缓存顶部的位置。当一个新条目被访问时,LRU将它放置到缓存的顶部。当缓存达到极限时,较早之前访问的条目将从缓存底部开始被移除。这里会使用到昂贵的算法,而且它需要记录“年龄位”来精确显示条目是何时被访问的。此外,当一个LRU缓存算法删除某个条目后,“年龄位”将随其他条目发生改变。
3)自适应缓存替换算法(ARC):
在IBM Almaden研究中心开发,这个缓存算法同时跟踪记录LFU和LRU,以及驱逐缓存条目,来获得可用缓存的最佳使用。
4)先进先出算法(FIFO):
FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
5)最近最常使用算法(MRU):
这个缓存算法最先移除最近最常使用的条目。一个MRU算法擅长处理一个条目越久,越容易被访问的情况。
redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
19、redis 集群如何执行批量操作?
Redis集群是没法执行批量操作命令的,如mget,pipeline等。这是因为redis将集群划分为16383个哈希槽,不同的key会划分到不同的槽中。但是Jedis客户端提供了计算key的slot方法,已经slot和节点之间的映射关系,通过这两个数据,就可以计算出每个key所在的节点,然后使用pipeline获取数据。
20、你知道有哪些Redis分区实现方案?
1)客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取。大多数客户端已经实现了客户端分区。
2)代理分区 意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy
3)查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。
21、Redis分区有什么缺点?
1)涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用交集指令)。
2)同时操作多个key,则不能使用Redis事务.
3)分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set).
4)当使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。
5)分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。
22、Redis持久化数据和缓存怎么做扩容?
如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。
如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。
23、布式Redis是前期做还是后期规模上来了再做好?为什么?
既然Redis是如此的轻量(单实例只使用1M内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便你只有一台服务器,你也可以一开始就让Redis以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。一开始就多设置几个Redis实例,例如32或者64个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。这样的话,当你的数据不断增长,需要更多的Redis服务器时,你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的Redis实例从第一台机器迁移到第二台机器。
24、Twemproxy是什么?
Twemproxy是Twitter维护的(缓存)代理系统,代理Memcached的ASCII协议和Redis协议。它是单线程程序,使用c语言编写,运行起来非常快。它是采用Apache 2.0 license的开源软件。 Twemproxy支持自动分区,如果其代理的其中一个Redis节点不可用时,会自动将该节点排除(这将改变原来的keys-instances的映射关系,所以你应该仅在把Redis当缓存时使用Twemproxy)。 Twemproxy本身不存在单点问题,因为你可以启动多个Twemproxy实例,然后让你的客户端去连接任意一个Twemproxy实例。 Twemproxy是Redis客户端和服务器端的一个中间层,由它来处理分区功能应该不算复杂,并且应该算比较可靠的。
25、Redis是单线程的,如何提高多核CPU的利用率?
可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果想使用多个CPU,可以考虑一下分片(shard)
26、Redis常见性能问题和解决方案?
1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
3)为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
4)尽量避免在压力很大的主库上增加从库
5)主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
27、Redis提供了哪几种持久化方式?
1)RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
2)AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
备注:如果只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始。
28、如何选择合适的持久化方式?
1)一般来说, 如果想达到足以媲美PostgreSQL的数据安全性, 你应该同时使用两种持久化功能。
2)如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB持久化。
3)有很多用户都只使用AOF持久化,但并不推荐这种方式:因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外, 使用RDB还可以避免之前提到的AOF程序的bug。
29、修改配置不重启Redis会实时生效吗?
针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。
参看博文:
https://mp.weixin.qq.com/s/75uewvZWpSzxRbVvOrcOcg,
https://www.cnblogs.com/tony-zt/p/10185660.html 集群批处理
https://mp.weixin.qq.com/s/1wx2vA8GF1DHPed98tM57w
https://mp.weixin.qq.com/s/gB7oMjygGFF5M7WwTgrn4g
https://blog.csdn.net/liliangpin/article/details/90725363 redis 数据结构和对象
还有一些其他的资料,本次整理是基于之前学习的笔记整理的,但是忘记出处了,待回忆起来再补充相关的书籍及原文链接!