自己对Redis的理解
数据类型
最基本的五种数据类型
String (字符串):最基本的数据类型
list(列表):相当于java语言里面的LinkedList,是链表结构,所以插入和删除非常快,时间复杂度是O(1)
set(集合):相当于hashset
hash (哈希):相当于hashmap,数组+链表
zset(有序集合):在set的基础上 给每一个value会与了一个score 代表这个value的排序权重
特殊的数据类型
HyperLogLogs(基数统计):只统计数量,不会存入值,占用空间极少。结果会有误差,约为0.81。因此适用于网站在线人数,UV、注册IP数等对精准度要求不高的场景
geo(地理位置):可以将一个或多个和经纬度加入到key中
1 | geoadd china:city 116.40 39.90 beijing |
BitMap( 位存储): 只能存储0和1 可以用来统计用户的活跃状态
1 | setbit sign userId 1 |
redis为什么这么快
- 基于内存的操作
- 使用了非阻塞IO多路复用模型
- 单线程可以避免不必要的上下文切换和竞争条件,减少了这方面的性能消耗
redis是否是多线程
redis在6.0版本引入了多线程,在此之前redis为单线程。但多线程也只处理异步操作、持久化、集群通信等。网络IO以及键值存储仍然是由单线程去完成(因为redis的瓶颈不在cpu而在于IO)
Redis的线程模型
redis内部实际上就是一个文件时间处理器。文件事件处理器结构包含4个部分:
多个socket
IO多路复用程序
文件事件分配器
事件处理器(连接应答处理器,命令请求处理器 命令回复处理器)
多个socket可能会产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,将socket产生的事件放入队列中排队。事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
redis的持久化
redis具有RDB和AOF 以及混合持久化(redis4.0引入)三种持久化方式。
RDB
是redis默认的持久化方式,类似于快照,在某个时间点,将redis在内存中的数据库,也就是键值对等信息保存到磁盘里面,生成的RDB文件是经过压缩的二进制文件。可以通过SAVE或BGSAVE进行生成快照。SAVE是同步的,BGSAVE是异步的,会生成一个子进程去处理。
优点:因为是压缩过的二进制文件,所以占用空间很小,适合灾难恢复。可以最大化redis的性能,只需要一个子进程来处理保存的工作,在恢复数据是比AOF速度要快
缺点: RDB是隔一段时间进行持久化,如果持久化的时候发生故障宕机,意味着丢失数据
AOF
则保存redis服务器执行的所有写操作命令来记录数据库状态,服务器重启时,重新执行这些命令来还原数据集,可以通过appendonly on来开启。
优点是比RDB可靠,可以通过设置fsync策略,no,everysec和always 默认为everysec 。假如说发生了宕机,最多丢失1秒钟的数据,如果写入时磁盘已满或者宕机,可以通过redis-check-aof工具来修复
缺点:对于相同的数据集,AOF文件一般比RDB文件大,且恢复速度慢
混合持久化
是在4.0开始有的,5.0默认开启。可以通过 config aof-use-rdb-preamble 命令查看是否开启。config set aof-use-rdb-preamble yes 用来开启混合持久化。不过这样设置的话,redis重启我们设置的混合持久化就会失效,永久生效可以通过修改redis配置文件开启 配置文件中的 aof-use-rdb-preamble yes 来开启。文件格式如下
优点很明显,混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 的格式,使得 Redis 可以更快的启动,同时结合 AOF 的优点,又减低了大量数据丢失的风险。但缺点是添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差。兼容性也不高,如果开启混合持久化,那么此混合持久化 AOF 文件,就不能用在 Redis 4.0 之前版本了。
Redis的过期策略
惰性删除只会在每次获取键的时候检查键是否过期。如果过期就删除
定期删除:每隔100ms一定的时间,就对数据库进行一次随机检查,删除里面的过期键、对cpu和内存比较平衡的模式
定时删除:会给每一个key设置一个定时器,时间到了就删除
redis的淘汰策略
当redis的内存空间已满时,会根据配置的过期策略进行对应操作
No eviction:默认策略,不移除任何key,直接返回错误
Allkeys-lru:在所有的key中,移除最近最少使用的 key
Allkeys-random:在所有的key中,随机移除key
Volatile-lru:在设置过期时间的key中,移除最近最少使用的 key
Volatile-random:在设置过期时间的key中,随机移除key
Volatile-ttl:在设置过期时间的key中,挑选ttl(剩余时间)短的移除
4.0之前有6种过期策略,4.0之后新加入2个过期策略
Volatile-lfu:在设置过期时间的key中,使用LFU算法淘汰部分key 4.0新增
Allkeys-lfu:在所有key中,使用LFU算法淘汰部分key 4.0新增
Redis哨兵模式的特点
集群监控 监控着redis的master和slave 的工作是否正常
消息通知: 当发现redis实例有故障,会通知管理员
故障转移: 如果master节点宕机了 会将从slave中选举一个新的master并进行数据同步
哨兵集群至少需要三个节点,而且是奇数,保证自己的健壮性。哨兵选举涉及到判断主节点宕机的机制。有主管宕机和客观宕机。主观宕机,就是一个哨兵觉得自己master宕机了,这个叫主观宕机。客观宕机是我们设置的quorum(同意数),至少有多少个哨兵认为master宕机了,master才是真正的宕机了。
双写的考虑
先更新数据库在更新缓存: 现在有A和B两个请求,都是修改数据的。但是因为网络原因,B的请求比A先完成了。那么这个时候,A在更新。最后缓存存入的是A修改后的数据,直接导致了数据不一致性。
先删缓存再更新数据库: 还是2个请求。A和B A是更新数据的,B是查询数据的。现在我们A请求开始执行了。发现有缓存,把缓存删掉了,在进行写操作,但没有写入完成的时候。B来查询了,发现没有缓存。去数据库查询,并且将该数据存到缓存。这个时候我们的A更新完之后。缓存内的数据仍然是脏数据。
扯淡的解决方案!!!!!!!!!!
于是我们就衍生出一种延时双删。将A更新完之后,延时1S左右。具体要根据我们的业务逻辑判断。如果mysql有主从复制还得更长。再休眠一小段时间后,再对这条数据的缓存进行一个删除。就是为了删掉这个脏数据。
先更新数据库,再删除缓存: 现在请求A和请求B。请求A来进行查询,请求B来进行更新数据库。而且两者操作的都是同一条数据。A是查询,B是写入。对于mysql来说。查询的性能是比写入要高很多的。所以绝大多数情况,都是查询请求先完成。然后请求B是要晚于请求A的。B写入完成后。删除掉redis中的旧缓存。
缓存穿透、缓存雪崩、缓存击穿
缓存穿透
:访问一个缓存和数据库都不存在的key,此时会直接访问数据库,并且因为查不到数据,无法写入缓存,所以下一次还会访问数据库。流量大的情况可能会导致数据库宕机
解决方案:
缓存空值:可以将空值写入缓存,设置一个较短的过期时间
布隆过滤器:使用布隆过滤器来存储所有可能被访问的key,不存在的key直接被过滤,存在的key则去查询缓存和数据库
缓存击穿
:如果有一个热点key,在缓存过期的一瞬间,有大量的请求打过来,因为此时缓存过期了,所以所有的请求都会走数据库,造成顺时数据库请求量大,可能导致数据库宕机
解决方案:
使用互斥锁:只有第一个线程可以拿到锁并执行数据库查询,其他的线程就在锁外面阻塞。
等第一个线程把数据写入缓存后,剩下的线程就可以直接从缓存取值
永不过期:可以直接将这个热点数据设置为永不过期,通过定时异步的方式去更新数据
缓存雪崩
: 设置缓存时使用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到数据库,数据库因为请求量大,压力骤增,导致数据库挂掉、和击穿很像,击穿是一个key,雪崩是多个key
解决方案:
使用互斥锁:
永不过期:
将过期的时间打散: 可以在原来的时间上加随机值,防止每个缓存的过期时间一样
其他数据类型