文章目录
- redis是什么
- redis设计
- 缓存穿透、缓存击穿、缓存雪崩
- 缓存穿透
- 布隆过滤器
- 构造空对象
- 缓存击穿
- 缓存雪崩
- redis的回调机制
- redis的持久化
- RDB持久化
- AOF持久化
- redis 的发布订阅模式
- 相关命令
- redis 的事务
- redis主从复制和哨兵
- 从节点配置与主从复制
- 哨兵机制
- 参考文献
redis是什么
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker. Redis provides data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes, and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions, and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.
摘自:https://redis.io/
可以看到redis的这么几个特点:
- 数据存储在缓存上
- 可用作数据库、缓存、消息中间件
- 支持string、hash、list、set、sort set
- 支持range query、bitmap、hyperloglog、geospatial index
- 支持磁盘持久化(RDB、AOF)
- 支持哨兵、自动分区来实现高可用
而这些机制使得其在企业之间有了广泛的应用
redis设计
redis作为一个非关系型数据库(NoSql)服务器,默认具有16个服务器,每个数据库对象基本的数据存储方式就是键-值;同时每个键值都有设置一个过期时间,只有在这个时间段内才能正确访问数据。
缓存穿透、缓存击穿、缓存雪崩
redis被设计为实现内存数据库,其会和持久层的数据库不断交互,根据局部性等原则将放在内存上。其性能是十分高的,能够达到8w/s写,11w/s读。
但是在内存上其占用内存是其一,另外一个值得注意的就是缓存穿透、缓存击穿和缓存雪崩的问题。
缓存穿透
这个问题的发生场景就是,一个数据如果既不在redis中,也不在mysql中。那么假如说有黑客恶意攻击,利用这个数据先去访问我的redis,然后这个数据的访问就会转移到磁盘上的mysql,而mysql的高并发能力并不出色,这会给服务器造成巨大的压力。
为了解决这个问题有两种方法:
- 布隆过滤器
- 制造空对象
布隆过滤器
布隆过滤器常常和位图结合使用,其思想就是,假如说有一个数据过来,那么经过一个hash函数,其就在某一个bit上 标为1,表示这个数据在redis上存在,图示中采用的多个hash函数。
虽然说,布隆过滤器可能在判断数据存在上存有一定的错误偏差,应为其他数据的hash函数可能也可以把这个bit标位1,但是它在判断数据不存在上是百分百正确的,因为只要下图这三个bit有一个为0,那么是不是就可以表示这个数据肯定不存在?
这样,每次有数据来的时候,就可以先经过布隆过滤器去判断这个数据是否存在,如果不存在就直接返回 nil 了。但是每次这样判断无疑增加了一个时间上的开销。
构造空对象
这个方法的思想就是,客户去访问我当前的mysql,如果mysql上也没有数据,就在redis构造这个空对象 key-nil
返回。
但是这个方法也会存在问题:
- 内存数据和磁盘数据不一致
- 存储空对象增加缓存开销
具体是选择时间还是空间就看开发者的抉择了
缓存击穿
这个问题的的场景是,如果我一个数据是个热门数据,就像微博热搜那样。我们知道,内存上的数据是有时限的,如果它过期了,那么是不是就有大量的数据去访问我的mysql造成巨大的高并发压力。
解决这个问题也有两种方式:
- 热点的数据延长其寿命
- 使用锁机制
第一个延长数据寿命很好理解,这里讲一下锁机制。
可以给访问mysql的操作去加锁,保证同一时间只有一个客户端去访问我的mysql,访问完了再把这个数据设置到redis上。
缓存雪崩
造成这个问题的原因就是大量的数据同时过期或者说我这个服务器挂掉了,这个时候找数据就又又又又去mysql了。这个时候就会造成服务器的周期性压力。
方法一:针对数据库挂掉,多设几台服务器
方法二:针对数据同时过期,可以设置不同的过期时间,使过期时间点均匀
redis的回调机制
对于每个连接的客户端,redis都会把他存储在clients字段。这个字段里面可看成一个链表,每个节点都是一个个client对象,这个对象有两个缓冲区,一个定长,一个不定长;当输入命令的时候,首先会去字典中去查询,这个字典中存储了两个元素,一个是键SDS字符串,另外一个就是函数指针及其他信息。
然后命令执行之后会把一些简短的回复,像”ok",这样的存储在定长缓冲区,把一些长的回复存储在不定长缓冲区。
redis的持久化
与memcache不同的是,redis还支持持久化的功能,说白了也就是把数据存储到磁盘上。
在redis中有两种持久化:
- RDB
- AOF
RDB持久化
RDB持久化就是把当前数据写入到一个RDB文件,主要有两个命令save和bgsave。
当服务器执行save
命令的时候,服务器会阻塞,然后把当前缓存里面的数据写入一个RDB文件,当服务器启动的时候,就会自动去读入这个RDB文件恢复数据。
另外一个命令就是bgsave
命令,执行这个命令会开辟一个子进程,子进程唯一的事情就是将当前数据去写入RDB文件。
当然,这都是手动保存,我们可以更改redis 的配置文件redis.conf
//save 时间 次数
save 100 1 //如果100秒内出现1个数据更改,就进行持久化操作
save 600 3 //600秒内出现3个数据更改,就进行持久化操作
另外,除了手动操作和自动操作,redis在以下情况也会进行RDB持久化操作:
- 执行 flushall
- redis 退出
AOF持久化
与RDB不同的是,AOF是将写命令写入到AOF文件,当服务器启动的时候,会启动一个伪客户端,这个客户端的套接字为-1,这个客户端去执行AOF文件里面的命令,以达到恢复服务器数据的目的。
一般情况下,AOF持久化是不开启的,但是一旦开始,redis则优先执行AOF操作,而不是RDB操作。
redis 的发布订阅模式
这个模式的使用场景一般是在微博的关注,小红书的关注等。我关注了一个人,每当这个人有新动态,微博就会给我发消息。
而在redis中呢,就拿我做的集群聊天项目来说,我的服务器使用subscribe + 用户id
向redis服务器去注册,redis服务器就把我这个服务器添加到这个用户id(键)所对应的一个链表(值)上,每当有关于这个用户id的信息,redis就会遍历这个链表上的服务器信息去转发消息。
相关命令
//订阅一个channel
SUBSCRIBE channel_id
//取消订阅的channel
UNSUBSCRIBE channel_id
//发布一个消息
PUBLISH channel_id message
这里需要注意一下,publish发布字段会有三个:
//服务器1
PUBLISH 1 "hello world"
//服务器2
"message"
"1"
"hello world"
redis 的事务
事务这个概念学过mysql的都知道,其在mysql具有四个特性:
- 原子性
- 一致性
- 隔离性
- 持久性
但是跟mysql不同的是,redis的事务并不是原子的。
我们来看一个例子:
127.0.0.1:6379> multi //开启事务
OK
127.0.0.1:6379> set k1 v1 //命令入队
QUEUED
127.0.0.1:6379> set k2 v2 //命令入队
QUEUED
127.0.0.1:6379> get k2 //命令入队
QUEUED
127.0.0.1:6379> set k3 v3 //命令入队
QUEUED
127.0.0.1:6379> exec //执行事务
1) OK
2) OK
3) "v2"
4) OK
可以看到,其事务更像是一个函数一样,只有当exec的时候,这些命令才会去执行。
而为什么说其不是原子性呢?
看下面的例子:
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 //会执行的时候失败,因为字符串无法自增
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec //执行事务时,第一个命令执行出错,其他命令依旧会执行
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v2"
127.0.0.1:6379> get k2
"v2"
可以看到,虽然说我们的第一条命令失败了,但是我们后面的命令还是成功执行了。
redis主从复制和哨兵
在面试中,在服务器领域一直有一个经典的问题:如果我服务器宕机了怎么办?
redis给出它的解决方案:主从复制
在redis设计中有主节点和从节点两个概念,从节点和主节点的数据内容是一致的,客户端在从节点去读取数据,因为一般来说,读的需求是远远大于写的需求。而主节点负责数据写入。
以来这样可以降低主节点的压力,二来可以在主节点宕机的时候启用从节点进行切换。
从节点配置与主从复制
一般来说,每个redis的节点都是默认为主节点的。如果需要给其配置从节点需要使用以下命令:
SLAVEOF 主节点ip 主节点port
执行这个命令之后会给主节点发送一个sync同步命令,如果是一开始的状态,那么就会执行全量复制,如果是不是,就会执行增量复制。
- 全量复制:主节点执行BGSAVE命令,生成一个RDB文件,然后发送给从服务器
- 增量复制:在主节点及其从节点都有一个复制偏移量,计算主节点和从节点的偏移量是否一致,如果不一致就从主节点的缓冲区复制偏移量差值大小的数据到从节点。缓冲区默认大小为1M。
哨兵机制
很遗憾,主从复制不是自动的,需要手动操作。
所以说,redis又引入了哨兵机制。哨兵是一个单独的进程,其利用心跳检测机制监视每一个节点。
当哨兵1检测到主节点1下线了,哨兵1就会判断其为主观下线。然后去询问其他哨兵,如果判断其下线的哨兵数量达到一定数量,就会判断其为客观下线。然后哨兵经过选举选出一个领头哨兵。这个哨兵会根据优先级从下线的主节点中挑选出一个从节点并进行故障转移。
参考文献
[1] 图论科技redis课件.王晨
[2] redis设计与实现.黄健宏