Remote Dictionary Server(Redis) 是一个由 Salvatore Sanfilippo写的 key-value存储系统,是跨平台的非关系型数据库,也属于一种nosql数据库,通常被称为数据结构服务器。
Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。
Redis 与其他 key - value 缓存产品有以下特点:
Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
Redis支持数据的备份,即master-slave模式的数据备份。
性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
原子性 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
丰富的特性 – Redis还支持 publish/subscribe, 通知, 设置key有效期等等特性。
下面从如下几个方面介绍下其相关理论:
目录
概述
架构
核心知识点:
部署方式:
优缺点分析
常见应用场景:
调优经验:
API应用:
1、数据类型
1)、string(字符串)
string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。
string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。
2)、list(双向列表)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
3)、set(集合)
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
集合对象的编码可以是 intset 或者 hashtable。
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
4)、hash(哈希)
Redis hash 是一个键值(key=>value)对集合。
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
5)、zset(sorted set:有序集合)
Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
6)、HyperLogLog
Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素
7)、GEO
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。
8)、Stream
Redis Stream 是 Redis 5.0 版本新增加的数据结构。
Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,
如果出现网络断开、Redis 宕机等,消息就会被丢弃。
简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。
而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。
9)、Bitmap
Bitmap在Redis中不是一种实际的数据类型,而是一种将String作为Bitmap使用的方法。可以理解为将String转换为bit数组。使用Bitmap来存储true/false类型的简单数据极为节省空间。
2、持久化方式
redis将内存中的数据异步写入硬盘中有三种方式:RDB(默认)、AOF、混合(RDB + AOF增量)
1)、RDB:
通过bgsave命令触发,然后父进程执行fork操作创建子进程,子进程创建RDB文件,根据父进程内存生成临时快照文件,
完成后对原有文件进行原子替换(定时一次性将所有数据进行快照生成一份副本存储在硬盘中)。
优点:是一个紧凑压缩的二进制文件,Redis加载RDB恢复数据远远快于AOF的方式,异步执行,非阻塞redis提供服务
缺点:由于每次生成RDB开销较大,非实时持久化,会阻塞redis提供服务
2)、AOF:
开启后,Redis每执行一个修改数据的命令,都会把这个命令添加到AOF文件中。
优点:实时持久化。
缺点:同步执行,会阻塞redis提供服务, 当AOF文件体积逐渐变大,需要定期执行重写操作来降低文件体积,加载慢。
3)、混合持久化:
AOF在重写时,会将 rdb 快照 和 增量的 AOF日志一起写入新的aof文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,原子的覆盖原有的AOF文件,完成新旧两个AOF文件的替换;
aof 根据配置规则(aof-use-rdb-preamble yes)在后台自动重写,也可以人为执行命令bgrewrite aof重写AOF。 于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。
3、内部执行方式(这是Redis快速的原因所在)
1)、数据存于内存
Redis的数据都在内存中,所以其处理速度很快,所有需要处理的文件都放在文件描述符的集合里rset(fds)。
2)、用了多路复用I/O
Redis内部采用了I/O多路复用技术,该技术依赖于底层的操作系统,一般有三种方式select、poll、epoll。前面两种本质上一致,一般操作系统都提供,epoll是Linux独有的。
1)、select模型每次都直接将rset(也就是fds)全部拷贝到内核态,因为内核态速度比用户空间态快很多。
2)、如果没数据的话,select函数会阻塞,如果有数据的话会执行两步
第一步:将有数据的那个fd置位(也就是标记一下,代表这个fd有数据)
第二步:select函数不在阻塞,将继续往下执行。也就是整体遍历fds,找到有数据的那个fd读取数据做处理。他的fd不能重用,每一次都需要重新创建新的fds且将用户空间态的fds拷贝到内核态
3)、缺点
fds最大支持1024个(可以更改,但是意义不大)
fd不可重用,每次内核态都给置位了,导致为了标记fd,必须创建一个新的rset从而导致fds在用户态内存态间多次拷贝(也就是fds)
用户控件态拷贝rset到内核态也需要时间,虽然内核态执行比用户态快,但是copy也需要开销
O(n)再次遍历问题。因为rset里的fd被置位后,select函数并不知道哪个被置位了,需要从头遍历到尾,逐个对比。
B、poll:
poll的结构体是为了fd重复利用,不需要每次都拷贝到内核态用的。
1)、解决了select哪些问题
采取的链表存储,而不是bitmap,解决了1024长度限制问题
采取结构体每次置位结构体内的revents字段,而不破坏fd本身,所以可重用,不需要每次都创建新的fd。
2)、缺点
用户控件态拷贝rset到内核态也需要时间,虽然内核态执行比用户态快,但是copy也需要开销
O(n)再次遍历问题。因为rset里的fd被置位后,select函数并不知道哪个被置位了,需要从头遍历到尾,逐个对比。
C、epoll:
epoll将fd放到了红黑树里,且不需要拷贝到内核态,因为他采取了“共享内存”的概念。(其实还是复制,只是复制采取了其他技术可以使开销极其的小)
epoll的置位是重排,比如五个fd, 1 2 3 4 5,1 3 5这三个fd有数据了,那么他会重排序,排成如下1 3 5 2 4。(也有的说是单独放到新的数组里)
每一次置位nfds的值都+1。且会回调epoll_wait
所以epoll_wait执行完会返回有几个fd有数据,那么下面的for直接遍历nfds次即可。解决了前面的两种O(n)。变成了O1
比如三个redis-cli,假设2个redis-cli写入命令,
select:那么select模型是轮询这三个redis-cli的fd,看哪个fd有消息,有的话读取处理消息。当他下次再写命令的时候还需要重新创建fd,然后复制到内核态然后再遍历全部。
poll:那么poll模型是轮询这三个redis-cli的fd,看哪个fd有消息,有的话读取处理消息。下次再写入的时候还是遍历全局fd,看哪个fd有消息进行处理。省去了每次都创建新的fd且复制的过程。
epoll:epoll就不轮询了,有消息进来后你通知我,我去处理你的消息,那些没消息的fd我不管。而且复制到内核态的过程我采取牛逼的技术让开销达到最小的极致。
多路复用I/O技术总结
3)、单线程
Redis是单线程提供应用的。
简单来说,就是我们的redis-client在操作的时候,会产生具有不同事件类型的socket。在服务端,有一段I/0多路复用程序,将其置入队列之中。然后,IO事件分派器,依次去队列中取,
转发到不同的事件处理器中。如下图:
4、过期策略
过期策略一般有三种方式:key过期清除策略、惰性策略、定期过期策略。
key过期清除:依据写入数据的过期时间来处理。
惰性过期(类比懒加载,这是懒过期):只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
redis采用的是定期删除+惰性删除策略。
为什么不用定时删除策略:
定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.
定期删除+惰性删除工作流程:
定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
所以需要惰性删除策略。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。
采用定期删除+惰性删除不能确保完全的过期该过期的数据:
如果定期删除没删除key,然后你也没及时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高,所以还需要配合内存淘汰机制。
5、内存淘汰机制
在redis.conf中有一行配置,专门配置内存淘汰机制。
# maxmemory-policy allkeys-lru
下面是一些备选机制:
1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。
2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用。
3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。应该也没人用吧,你不删最少使用Key,去随机删。
4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐
5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。依然不推荐
6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐
ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。
6、事务
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
批量操作在发送 EXEC 命令前被放入队列缓存。
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:
开始事务。
命令入队。
执行事务。
PS:
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
7、发布订阅
发布订阅(pub/sub)是一种消息通信模式,主要是解除消息发布者和消息订阅者之间通信的耦合。
Redis作为一个pub/sub的服务器,在订阅者和发布者之间起到了一个消息路由的功能。订阅者可以通过subscribe和psubscribe命令向redis 服务器订阅自己感兴趣的消息类型,
redis将信息类型称为通道(channel)。当发布者通过publish命令向redis server发送特定类型的信息时,订阅该信息类型的全部client都会收到此消息。
8、集群中节点间内部通信机制
1)、基础通信原理
a、redis cluster节点间采取gossip协议进行通信,没有采用集中式的存储在某个节点上
b、10000端口
每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口
每隔节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping之后返回pong
c、交换的信息
故障信息、节点的增加和移除、hash slot信息,等等
2)、gossip协议介绍
gossip 过程是由种子节点发起,当一个种子节点有状态需要更新到网络中的其他节点时,它会随机的选择周围几个节点散播消息,收到消息的节点也会重复该过程,
直至最终网络中所有的节点都收到了消息。这个过程可能需要一定的时间,由于不能保证某个时刻所有节点都收到消息,但是理论上最终所有节点都会收到消息,
因此它是一个最终一致性协议。
gossip协议包含多种消息,包括ping,pong,meet,fail,等等
meet: 某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信
redis-trib.rb add-node 其实内部就是发送了一个gossip meet消息,给新加入的节点,通知那个节点去加入我们的集群
ping: 每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据
每个节点每秒都会频繁发送ping给其他的集群,通过ping,频繁的互相之间交换数据,互相进行元数据的更新
pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新
fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了
3)、ping消息深入
ping很频繁,而且要携带一些元数据,所以可能会加重网络负担
每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点
当然如果发现某个节点通信延时达到了cluster_node_timeout / 2,那么立即发送ping,避免数据交换延时过长,落后的时间太长了
9、数据备份与恢复
Redis创建当前数据库的备份,有种方式:save、bgsave。
save命令:同步执行,会阻塞Redis服务
bgsave命令:异步执行,不会阻塞Redis服务
这两种方式都会在Redis安装目录中创建dump.rdb文件,如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。
10、管道技术和分区
管道技术:
Redis 管道技术可以在服务端未响应时,客户端可以继续异步向服务端发送请求,并最终一次性读取所有服务端的响应。
分区:
分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。
有两种分区类型:范围分区、哈希分区。
11、重要程序
redis-server:Redis服务器程序
redis-cli:Redis客户端程序,它是一个命令行操作工具。也可以使用telnet根据其纯文本协议操作。
redis-benchmark:Redis性能测试工具,测试Redis在你的系统及配置下的读写性能。
1、单机模式
由一台计算来提供Redis服务, 容量和性能受限于机器的配置且不具备高可用。
2、主从复制(本质和单机一样,只是多了查询负载均衡)
通过一台主服务器Master和多台备份服务器组成的集群提供服务,支持自动备份、负载均衡,主服务器挂掉,需要人工进行切换(不支持高可用)。
Redis为了解决单点数据库问题,会把数据复制多个副本部署到其他节点上,通过复制,对数据进行冗余备份,从而保证数据高度可靠性。
复制(Replication)的原理:
①从数据库向主数据库发送sync(数据同步)命令。
②主数据库接收同步命令后,会保存快照,创建一个RDB文件。
③当主数据库执行完保持快照后,会向从数据库发送RDB文件,而从数据库会接收并载入该文件。
④主数据库将缓冲区的所有写命令发给从服务器执行。
⑤以上处理完之后,之后主数据库每执行一个写命令,都会将被执行的写命令发送给从数据库。
注意:在Redis2.8之后,主从断开重连后会根据断开之前最新的命令偏移量进行增量复制
3、哨兵模式(主从复制+哨兵)(本质和单机一样,保证了高可用)
通过一台主服务器Master和多台备份服务器组成的集群提供服务,保证高可用。
哨兵是Redis集群架构中非常重要的一个组件,哨兵的出现主要是解决了主从复制出现故障时需要人为干预的问题。
哨兵模式架构图
A、Redis哨兵主要功能
(1)集群监控:负责监控Redis master和slave进程是否正常工作
(2)消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
(3)故障转移:如果master node挂掉了,会自动转移到slave node上
(4)配置中心:如果故障转移发生了,通知client客户端新的master地址
B、Redis哨兵的高可用原理:
当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。
哨兵机制建立了多个哨兵节点(进程),共同监控数据节点的运行状况。
同时哨兵节点之间也互相通信,交换对主从节点的监控状况。
每隔1秒每个哨兵会向整个集群:Master主服务器+Slave从服务器+其他Sentinel(哨兵)进程,发送一次ping命令做一次心跳检测。
哨兵用来判断节点是否正常的重要依据,涉及两个新的概念:主观下线和客观下线。
主观下线:一个哨兵节点判定主节点down掉是主观下线。
客观下线:只有半数哨兵节点都主观判定主节点down掉,此时多个哨兵节点交换主观判定结果,才会判定主节点客观下线。
基本上哪个哨兵节点最先判断出这个主节点客观下线,就会在各个哨兵节点中发起投票机制Raft算法(选举算法),最终被投为领导者的哨兵节点完成主从自动化切换的过程。
C、哨兵选举
一般情况下当哨兵发现主节点sdown之后 该哨兵节点会成为领导者负责处理主从节点的切换工作:
哨兵A发现Redis主节点失联;
哨兵A报出sdown,并通知其他哨兵,发送指令sentinel is-master-down-by-address-port给其余哨兵节点;
其余哨兵接收到哨兵A的指令后尝试连接Redis主节点,发现主节点确实失联;
哨兵返回信息给哨兵A,当超过半数的哨兵认为主节点下线后,状态会变成odown;
最先发现主节点下线的哨兵A会成为哨兵领导者负责这次的主从节点的切换工作;
哨兵的选举机制是以各哨兵节点接收到发送sentinel is-master-down-by-address-port指令的哨兵id 投票,票数最高的哨兵id会成为本次故障转移工作的哨兵Leader;
D、哨兵故障转移
当哨兵发现主节点下线之后经过上面的哨兵选举机制,选举出本次故障转移工作的哨兵节点完成本次主从节点切换的工作:
哨兵Leader 根据一定规则从各个从节点中选择出一个节点升级为主节点;
其余从节点修改对应的主节点为新的主节点;
当原主节点恢复启动的时候,变为新的主节点的从节点
哨兵Leader选择新的主节点遵循下面几个规则:
健康度:从节点响应时间快;
完整性:从节点消费主节点的offset偏移量尽可能的高;
稳定性:若仍有多个从节点,则根据从节点的创建时间选择最有资历的节点升级为主节点;
在哨兵模式下主从节点总是会变更,因此在应用中访问哨兵模式下的Redis时可以使用对应的哨兵接口连接:
例如 java:JedisSentinelPool;Python:SentienlConnectionPool
4、集群模式
redis集群在3.0以后提供了分布式存储方案,保证高可用,提高并发量。集群由多个节点(Node)组成,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,实现高扩展性。
集群中的节点分为主节点和从节点,只有主节点负责读写请求和集群信息的维护,从节点只进行主节点数据和状态信息的复制。为了适应选举算法要求,一般要求集群的主节点和从节点数量都采用奇数,最少需要3个节点。
可以直接使用 redis-cli --cluster 来管理集群,包括创建集群、伸缩集群节点、槽迁移、完整性检查、分区平衡等。
集群模式架构图
为了实现分布式集群,引入了槽的概念来处理数据分区。
数据分区规则一般考量2个重要因素:1、是否均匀, 2、伸缩节点对数据分布的影响。
一般有下面几种方法来实现分区算法:
1)、哈希取余
根据key计算hash值,然后对节点数量取余。该方法初始化时能够做到均匀分布,但后期伸缩时会引发大量数据迁移。
2)、一致性哈希
将hash值区间(0~2 32-1)抽象为一个顺时针环形,节点均匀分布在环形上,根据key计算hash值,然后在环形上顺时针查找节点,找到第一个就将数据落到该节点。
相比哈希取余来说,该方法减少数据迁移,因为其将影响访问控制到相邻节点,但会造成数据不均匀。
3)、带虚拟槽的一致性哈希
Redis就是采用这种方式来处理数据分布的。一共设定16384个槽slot,采用hash算法将这些槽分配到各个节点上,hash_slot = crc16(key) mod 16384。
该方法克服了上面两种的缺点,能够很好的解决均匀分布、伸缩节点对数据分布影响很小。
Redis集群内节点通过ping/pong消息实现节点通信,消息不但可以传播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。因此故障发现也是通过消息传播机制实现的,主要环节包括:主观下线(pfail)和客观下线(fail)
主客观下线:集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong消息作为响应。如果通信一直失败,则发送节点会把接收节点标记为主观下线(pfail)状态。
客观下线:超过半数,对该主节点做客观下线
主节点选举出某一主节点作为领导者,来进行故障转移
其中虚拟槽的分配算法没有采用最终一致性的hash算法原因如下:
1)、发生缩容时,需要知道被影响的那部分数据,要进行手动迁移
2)、因为其本质是一个顺时针环,所有会发生热点数据都集中在某一个Master上会出现性能瓶颈
集群限制
由于Redis集群中数据分布在不同的节点上,因此有些功能会受限:
db库:单机的Redis默认有16个db数据库,但在集群模式下只有一个db0;
复制结构:上面的复制结构有树状结构,但在集群模式下只允许单层复制结构;
事务/lua脚本:仅允许操作的key在同一个节点上才可以在集群下使用事务或lua脚本;(使用Hash Tag可以解决)
key的批量操作:如mget,mset操作,只有当操作的key都在同一个节点上才可以执行;(使用Hash Tag可以解决)
keys/flushall:只会在该节点之上进行操作,不会对集群的其他节点进行操作;
Hash Tag:
上面介绍集群限制的时候,由于key被分布在不同的节点之上,因此无法跨节点做事务或lua脚本操作,但我们可以使用hash tag方式解决。
hash tag:当key包含{}的时候,不会对整个key做hash,只会对{}包含的部分做hash然后分配槽slot;因此我们可以让不同的key在同一个槽内,这样就可以解决key的批量操作和事务及lua脚本的限制了;
但由于hash tag会将不同的key分配在相同的slot中,如果使用不当,会造成数据分布不均的情况,需要注意。
缺点:
1、容量及处理能力有限,受限于单台机器的配置。
为了应对容量的问题,一般做如下处理:
a、采用分布式集群处理(分片集群),将数据通过路由代理分片到不同的服务器
b、采用集群方式,利用虚拟槽的概念来做。
2、redis和数据库双写一致性问题
Redis只能做到最终一致性,不能保证强一致性。
3、缓存穿透
若缓存和数据库中都没有的数据,大流量都会直接打到DB,导致DB挂掉,这种现象俗称“缓存穿透”。
为了应对这种情况,一般做如下处理:
a、会将该key写入缓存值设置为null,为了减少对正常应用的影响,把有效时间设置的短一点,具体根据应用场景来定。
b、采用降级或限流控制流量入口
4、缓存雪崩
若缓存中的数据大批量已经过期,大流量都会直接打到DB,导致DB挂掉,这种现象俗称“缓存雪崩”。
为了应对这种情况,一般做如下处理:
a、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
b、如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中,且设置不同的过期时间。
c、热点数据考虑设置永远不过期。
d、采用降级或限流控制流量入口
5、缓存击穿
若缓存中没有但数据库中有(指同一条数据),大流量并发取这条数据到DB,导致DB挂掉,这种现象俗称“缓存击穿”。
为了应对这种情况,一般做如下处理:
a、热点数据考虑设置永远不过期。
b、采用布隆过滤器。
c、采用降级或限流控制流量入口。
6、热点Key并发竞争
若同时有多个有个多个或不同系统请求并发设置一个key,会发生数据应用不一致问题。例如像事务中的脏读、幻读等。
为了应对这种情况,一般做如下处理:
a、若不要求顺序,可以采用加锁或分布式锁
b、若要求按顺序,可以采用加锁或分布式锁,先放入队列,然后异步处理队列。
c、或者采用其他方式,做到串行处理set操作即可。
优点:
1、数据处理速度快
因为其基于内存操作,每秒可以执行大约 110000 个写入操作,或者 81000 个读操作,其速度远超数据库。
2、支持丰富的数据类型
Redis不仅仅支持简单的key-value类型的数据,同时还提供String,list,set,zset,hash等数据结构的存储。
3、支持数据的持久化
可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
4、支持数据的备份
内置支持master-slave模式的数据备份
5、内部操作原子性
Redis的所有操作都是原子性的(要不成功,要不失败),同时Redis还支持对几个操作全并后的原子性执行,也就是事务。
但要注意这里的事务只能保证批量块的事务,批量块内部的语句不具备事务性。
6、丰富的特性
Redis 可以在如缓存、消息传递队列中使用(Redis 支持“发布+订阅”的消息模式),在应用程序如 Web 应用程序会话、网站页面点击数、设置key有效期等中使用。
1、缓存
缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,
所以,现在Redis用在缓存的场合非常多。例如:token、热点数据。
2、排行榜
很多网站都有排行榜应用的,如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。
3、计数器
如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。
Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。
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提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。
PS:后续逐渐把实践调优过程补充上来
各个平台一般都有相应的操作组件,下面介绍下java和DoNet平台下的访问组件。
java平台:
1、jedis
2、Spring Boot整合Spring Cache应用Redis
3、spring-data-redis
4、Redisson(底层使用Netty)
5、Lettuce
DoNet平台:
1、CSRedisCore 推荐使用这个
Nuget: 下载CSRedisCore
2、StackExchange.Redis
Nuget: StackExchange.Redis
3、ServiceStack.Redis 微软提供 收费
Nuget: ServiceStack.Redis