redis总结

Redis的基本数据结构和实现方式

Redis命令大全查询
在这里插入图片描述

String

String 数据结构是简单的key-value类型,value其实不仅是String,也可以是数字
常用命令:get、set、incr(+1)、decr(-1)、mget(一次性返回多个get的值,性能要比多次使用get好)等。
应用场景:String是最常用的一种数据类型,普通的key/ value 存储都可以归为此类,即可以完全实现目前 Memcached 的功能,并且效率更高。还可以享受Redis的定时持久化,操作日志及 Replication等功能。除了提供与 Memcached 一样的get、set、incr、decr 等操作外,Redis还提供了下面一些操作:
获取字符串长度
往字符串append内容
设置和获取字符串的某一段内容
设置及获取字符串的某一位(bit)
批量设置一系列字符串的内容
使用场景:常规key-value缓存应用。常规计数: 微博数, 粉丝数。

实现方式:String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。

list

常用命令:lpush,rpush,lpop,rpop,lrange等。

应用场景

Redis
list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。

List
就是链表,相信略有数据结构知识的人都应该能理解其结构。使用List结构,我们可以轻松地实现最新消息排行等功能。List的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在List中,然后工作线程再用POP操作将任务取出进行执行。Redis还提供了操作List中某一段的api,你可以直接查询,删除List中某一段的元素。

实现方式:

Redis
list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样List即可以作为栈,也可以作为队列。

使用场景:

消息队列系统 使用list可以构建队列系统,使用sorted set甚至可以构建有优先级的队列系统。

例如:
取最新N个数据的操作
记录前N个最新登陆的用户Id列表,超出的范围可以从数据库中获得。

//把当前登录人添加到链表里
ret = r.lpush("login:last_login_times", uid)

//保持链表只有N位
ret = redis.ltrim("login:last_login_times", 0, N-1)

//获得前N个最新登陆的用户Id列表
last_login_list = r.lrange("login:last_login_times", 0, N-1)

set

常用命令:

sadd,spop,smembers,sunion 等。

应用场景:

Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。

Set 就是一个集合,集合的概念就是一堆不重复值的组合。利用Redis提供的Set数据结构,可以存储一些集合性的数据。

Set是集合,是String类型的无序集合,set是通过hashtable实现的,概念和数学中个的集合基本类似,可以交集,并集,差集等等,set中的元素是没有顺序的。

实现方式:

set 的内部实现是一个
value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。

使用场景:
交集,并集,差集:(Set)

> //book表存储book名称
> 
> set book:1:name    ”The Ruby Programming Language”
> 
> set book:2:name     ”Ruby on rail”
> 
> set book:3:name     ”Programming Erlang”
> 
> //tag表使用集合来存储数据,因为集合擅长求交集、并集
> 
> sadd tag:ruby 1
> 
> sadd tag:ruby 2
> 
> sadd tag:web 2
> 
> sadd tag:erlang 3
> 
> //即属于ruby又属于web的书?
> 
>  inter_list = redis.sinter("tag.web", "tag:ruby") 
> 
> //即属于ruby,但不属于web的书?
> 
>  inter_list = redis.sdiff("tag.ruby", "tag:web") 
> 
> //属于ruby和属于web的书的合集?
> 
>  inter_list = redis.sunion("tag.ruby", "tag:web")

5、Sorted Set

常用命令:

zadd,zrange,zrem,zcard等

使用场景:

Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sortedset数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。
和Set相比,Sorted Set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如一个存储全班同学成绩的SortedSet,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。另外还可以用SortedSet来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。

实现方式:

Redis sorted set的内部使用**HashMap和跳跃表(SkipList)**来保证数据的存储和有序HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

Hash

常用命令:hget,hset,hgetall 等。

应用场景:

Key仍然是用户ID,
value是一个Map,这个Map的key是成员的属性名,value是属性值,这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field),
也就是通过 key(用户ID) + field(属性标签)
就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。很好的解决了问题。

注意

Redis提供了接口(hgetall)可以直接取到全部的属性数据,但是如果内部Map的成员很多,那么涉及到遍历整个内部Map的操作,由于Redis单线程模型的缘故,这个遍历操作可能会比较耗时,而另其它客户端的请求完全不响应,这点需要格外注意。

使用场景

存储部分变更数据,如用户信息等。

实现方式:

上面已经说到RedisHash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的valueredisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。

Redis单线程为什么还这么快?

基于事件的驱动模型 - 调用epoll-epoll采用红黑树

Redis的过期策略有哪些

定期删除

1 是什么?是每隔 100ms 随机抽取一些 key 来检查和删除的。
2 缺点 可能会导致很多过期 key 到了时间并没有被删除掉
3 如何解决缺点?惰性删除

惰性删除

1 是什么? 在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间(TTL)那么是否过期了?如果过期了此时就会删除
2 缺点 如果定期删除漏掉了很多过期 key,然后也没及时去查,也就没走惰性删除,就会有大量过期 key 堆积在内存里,导致 redis 内存块耗尽了
3 如何解决缺点? 内存淘汰机制

redis的内存淘汰机制

noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个
key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

Redis的持久化

RDB

是什么

RDB是一种快照存储持久化方式。客户端可以通过向Redis服务器发送save(主进程save,同步阻塞的)或bgsave(fork一个子进程,异步非阻塞的)命令让服务器生成rdb(dumb.rdb)文件。(也可以配置文件配置实现一定条件下的自动save)。而在Redis服务器启动时,会重新加载dump.rdb文件的数据到内存当中恢复数据。

优点

1 快~!(1)通过RDB的bgsave进行数据备,由于使用子进程生成,所以对Redis服务器性能影响较小。(2)基于RDB来恢复redis更快
2 适合冷备! 生成很多个rdb文件可以用亚马逊的S3存起来,方便冷备~!

缺点

1 丢失数据比AOF更多 RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 redis
进程宕机,那么会丢失最近 5 分钟的数据。
2 数据量很大时也会阻塞客户端短暂的时间
3 fork的子进程占用内存空间

AOF

1 是什么

AOF(Append-onlyfile):AOF持久化方式会记录客户端对服务器的每一次写操作命令,并将这些写操作以Redis协议追加保存到以后缀为aof文件末尾,在Redis服务器重启时,会加载并运行aof文件的命令,以达到恢复数据的目的。

2 AOF的优化策略:AOF重写

incr num 1 incr num 2 incr num 3 incr num 4 incr num 5 …incr10000
优化为 set num 100000 i

3 优点

AOF只是追加日志文件,因此对服务器性能影响较小,速度比RDB要快,消耗的内存较少

4 缺点

AOF方式生成的日志文件太大,即使通过AFO重写,文件体积仍然很大。 恢复数据的速度比RDB慢

Redis为什么这么快

1 完全基于内存
2 单线程,避免了线程切换和锁
3 使用IO多路复用模型(linux相关)

select poll epoll 见linux总结

Redis的线程模型
在这里插入图片描述

接收到用户的请求后,全部推送到一个队列里,然后交给文件事件分派器,而它是单线程的工作方式。Redis 又是基于它工作的,所以说 Redis 是单线程的。

4 即使部署在单台的多核服务器,也可以开多个实例,master-master或者master-slave利用多核心。slave解决读
5 4.0开始支持多线程

Redis如何保证高并发,高可用

解决高并发~!
主从!
在这里插入图片描述

1 redis 采用异步方式复制数据到 slave 节点
2 slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
3 slave node 做复制的时候,不会 block master node 的正常工作;
注:!!建议必须开启 master node 的持久化,不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。
4 总之!,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能确保启动的时候,是有数据的。要确保!!!master重启,有数据,要不slave就都空了!!!!

主从复制原理
在这里插入图片描述

如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。
1 此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件
2 将从客户端 client 新收到的所有写命令缓存在内存中
3 DB 文件生成完毕后, master 会将这个 RDB 发送给 slave
4 slave会先写入本地磁盘,然后再从本地磁盘加载到内存
5 接着 master 会将内存中缓存的写命令发送到 slave,slave也会同步这些数据
6 slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 masternode 仅会复制给 slave 部分缺少的数据。

解决高可用!

redis 的高可用架构,叫做 failover 故障转移,也可以叫做主备切换。 master node 在故障时,自动检测,并且将某个 slave node 自动切换为 master node 的过程,叫做主备切换。这个过程,实现了 redis 的主从架构下的高可用。

哨兵~!

sentinel:功能:(功能看类似zookeeper)

集群监控:负责监控 redis master 和 slave 进程是否正常工作。
消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
哨兵至少需要 3 个实例,来保证自己的健壮性

为什么哨兵要至少三个节点(分布式选举基本都是至少三个节点):

quorum和majority

quorum:确认odown的最少的哨兵数量

majority:授权进行主从切换的最少的哨兵数量

每次一个哨兵要做主备切换,首先需要quorum数量的哨兵认为odown,然后选举出一个哨兵来做切换,这个哨兵还得得到majority哨兵的授权,才能正式执行切换

如果quorum <
majority,比如5个哨兵,majority就是3,quorum设置为2,那么就3个哨兵授权就可以执行切换,但是如果quorum >=
majority,那么必须quorum数量的哨兵都授权,比如5个哨兵,quorum是5,那么必须5个哨兵都同意授权,才能执行切换
为什么哨兵至少3个节点 哨兵集群必须部署2个以上节点。如果哨兵集群仅仅部署了个2个哨兵实例,那么它的majority就是2(2的majority=2,3的majority=2,5的majority=3,4的majority=2),如果其中一个哨兵宕机了,就无法满足majority>=2这个条件,那么在master发生故障的时候也就无法进行主从切换。

哨兵主从架构导致的数据丢失:
异步复制导致的数据丢失
在这里插入图片描述

因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。

脑裂导致的数据丢失
在这里插入图片描述

脑裂,也就是说,某个 master 所在机器突然脱离了正常的网络,跟其他 slave 机器不能连接,但是实际上 master还运行着。此时哨兵可能就会认为 master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候,集群里就会有两个master ,也就是所谓的脑裂。
为何产生丢失:此时虽然某个 slave 被切换成了 master,但是可能 client 还没来得及切换到新的 master,还继续向旧 master 写数据。因此旧 master 再次恢复的时候,会被作为一个 slave 挂到新的 master 上去,自己的数据会清空,重新从新的 master 复制数据。而新的 master 并没有后来 client 写入的数据,因此,这部分数据也就丢失了。

如何解决?

min-slaves-to-write 1
min-slaves-max-lag 10

表示,要求至少有 1 个 slave,数据复制和同步的延迟不能超过 10 秒。

如果说一旦所有的 slave,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候,master 就不会再接收任何请求了。

减少异步复制数据的丢失 有了 min-slaves-max-lag 这个配置,就可以确保说,一旦 slave 复制数据和 ack 延时太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master 宕机时由于部分数据未同步到slave 导致的数据丢失降低的可控范围内。

减少脑裂的数据丢失 如果一个 master 出现了脑裂,跟其他 slave 丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的slave 发送数据,而且 slave 超过 10 秒没有给自己 ack 消息,那么就直接拒绝客户端的写请求。因此在脑裂场景下,最多就丢失10 秒的数据。

集群

在这里插入图片描述

上图 展示了 Redis Cluster 典型的架构图,集群中的每一个 Redis 节点都 互相两两相连,客户端任意 直连 到集群中的任意一台,就可以对其他 Redis 节点进行 读写 的操作。

基本原理

Redis 集群中内置了 16384 个哈希槽。当客户端连接到 Redis 集群之后,会同时得到一份关于这个 集群的配置信息,当客户端具体对某一个 key 值进行操作时,会计算出它的一个 Hash 值,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,Redis 会根据节点数量 大致均等 的将哈希槽映射到不同的节点。再结合集群的配置信息就能够知道这个 key 值应该存储在哪一个具体的 Redis 节点中,如果不属于自己管,那么就会使用一个特殊的 MOVED 命令来进行一个跳转,告诉客户端去连接这个节点以获取数据:

主要作用

1数据分区: 数据分区 (或称数据分片) 是集群最核心的功能。集群将数据分散到多个节点,一方面 突破了 Redis 单机内存大小的限制,存储容量大大增加;另一方面 每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。Redis 单机内存大小受限问题,在介绍持久化和主从复制时都有提及,例如,如果单机内存太大,bgsave 和 bgrewriteaof 的 fork 操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出……

2 高可用: 集群支持主从复制和主节点的 自动故障转移 (与哨兵类似),当任一节点发生故障时,集群仍然可以对外提供服务。

集群一般如何分区

方案一:哈希值 % 节点数

哈希取余分区思路非常简单:计算 key 的 hash 值,然后对节点数量进行取余,从而决定数据映射到哪个节点上。
不过该方案最大的问题是,当新增或删减节点时,节点数量发生变化,系统中所有的数据都需要 重新计算映射关系,引发大规模数据迁移。

方案二:一致性哈希分区

一致性哈希算法将 整个哈希值空间 组织成一个虚拟的圆环,范围是 [0 - 232 - 1],对于每一个数据,根据 key 计算 hash值,确数据在环上的位置,然后从此位置沿顺时针行走,找到的第一台服务器就是其应该映射到的服务器:

在这里插入图片描述

与哈希取余分区相比,一致性哈希分区将 增减节点的影响限制在相邻节点。以上图为例,如果在 node1 和 node2 之间增加node5,则只有 node2 中的一部分数据会迁移到 node5;如果去掉 node2,则原 node2 中的数据只会迁移到 node4中,只有 node4 会受影响。

一致性哈希分区的主要问题在于,当 节点数量较少 时,增加或删减节点,对单个节点的影响可能很大,造成数据的严重不平衡。还是以上图为例,如果去掉 node2,node4 中的数据由总数据的 1/4 左右变为 1/2 左右,与其他节点相比负载过高。

方案三:带有虚拟节点的一致性哈希分区

该方案在 一致性哈希分区的基础上,引入了 虚拟节点 的概念。Redis 集群使用的便是该方案,其中的虚拟节点称为槽(slot)。槽是介于数据和实际节点之间的虚拟概念,每个实际节点包含一定数量的槽,每个槽包含哈希值在一定范围内的数据。

在使用了槽的一致性哈希分区中,槽是数据管理和迁移的基本单位。槽 解耦 了 数据和实际节点之间的关系,增加或删除节点对系统的影响很小。仍以上图为例,系统中有 4 个实际节点,假设为其分配 16 个槽(0-15);槽 0-3 位于 node1;4-7 位于 node2;以此类推… 如果此时删除 node2,只需要将槽 4-7 重新分配即可,例如槽4-5 分配给 node1,槽 6 分配给 node3,槽 7 分配给 node4;可以看出删除 node2后,数据在其他节点的分布仍然较为均衡。

了解什么是 redis 的雪崩、穿透和击穿?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透?

缓存雪崩

如果所有首页的Key失效时间都是12小时,中午12点刷新的,我零点有个秒杀活动大量用户涌入,假设当时每秒 6000个请求,本来缓存在可以扛住每秒 5000 个请求,但是缓存当时所有的Key都失效了。此时 1 秒 6000个请求全部落数据库,数据库必然扛不住,它会报一下警,真实情况可能DBA都没反应过来就直接挂了。此时,如果没用什么特别的方案来处理这个故障,DBA很着急,重启数据库,但是数据库立马又被新的流量给打死了。这就是我理解的缓存雪崩。

处理缓存雪崩简单,在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效,我相信,Redis这点流量还是顶得住的。

如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效的问题

缓存穿透

不对参数做校验,数据库id都是大于0的,我一直用小于0的参数去请求你,每次都能绕开Redis直接打到数据库,数据库也查不到,每次都这样,并发高点就容易崩掉了。

解决缓存穿透
1 做校验
2 bloom filter 可以用来判断一个元素是否在一个集合中 位数组,节省内存
3 对单个IP每秒访问次数超出阈值的IP都拉黑。

假设布隆过滤器有 3 个哈希函数:f1, f2, f3 和一个位数组 arr。现在要把 https://jaychen.cc
插入布隆过滤器中: 对值进行三次哈希计算,得到三个值 n1, n2, n3。 把位数组中三个元素 arr[n1], arr[n2],
arr[3] 置为 1。 当要判断一个值是否在布隆过滤器中,对元素再次进行哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为
1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。 当插入的元素原来越多,位数组中被置为 1
的位置就越多,当一个不在布隆过滤器中的元素,经过哈希计算之后,得到的值在位数组中查询,有可能这些位置也都被置为
1。这样一个不存在布隆过滤器中的也有可能被误判成在布隆过滤器中。但是如果布隆过滤器判断说一个元素不在布隆过滤器中,那么这个值就一定不在布隆过滤器中。简单来说:

布隆过滤器说某个元素在,可能会被误判。 布隆过滤器说某个元素不在,那么一定不在。

这个布隆过滤器的缺陷放到上面爬虫的需求中,可能存在某些没有访问过的 URL 可能会被误判为访问过,但是如果是访问过的 URL
一定不会被误判为没访问过。

缓存击穿

缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。

解决方案:可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。

如何保证缓存与数据库的双写一致性?

并发竞争

什么是并发竞争

系统A、B、C三个系统,分别去操作Redis的同一个Key,本来顺序是1,2,3是正常的,但是因为系统A网络突然抖动了一下,B,C在他前面操作了Redis,这样数据不就错了么。

就好比下单,支付,退款三个顺序你变了,你先退款,再下单,再支付,那流程就会失败,那数据不就乱了?你订单还没生成你却支付,退款了?明显走不通了,这在线上是很恐怖的事情。

如何解决

在这里插入图片描述

Redis的分布式锁

使用lua实现原子性 利用setnx特性实现分布式锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值