Redis
- 1.NoSQL数据库
- 概述
- NoSQL使用场景
- NoSQL不适用场景
- 2.Redis
- 2.1应用场景
- 2.1.1 配合关系型数据库做高速缓存
- 2.1.2 多样的数据结构存储持久化数据
- 2.1.3 Redis内存管理
- 2.1.3.1 删除策略
- 2.1.4 Redis持久化机制
- 2.1.4.1 什么是RDB持久化?
- 2.1.4.2 RDB创建快照时会阻塞主线程吗?
- 2.1.4.3 什么是AOF持久化?
- 2.1.4.4 AOF日志是如何实现的
- 2.1.5 Redis事务
- 2.1.5.1 缓存穿透
- 2.1.5.1.1 缓存无效key
- 2.1.5.1.2 布隆过滤器
- 2.1.5.2 缓存雪崩
- 2.1.6 Redis 主从同步
- 2.1.7哨兵模式
1.NoSQL数据库
概述
NoSQL(Not Only SQL),泛指非关系型的数据库
NoSQL不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。
不遵循SQL标准
不支持ACID (四种特性)
远超于SQL的性能
NoSQL使用场景
对数据高并发的读写
海量数据的读写
对数据高可扩展性的
NoSQL不适用场景
需要事务支持
基于sql的结构化查询存储,处理复杂的关系,需要即席查询
***用不着sql的和用了sql也不行的情况,请考虑NoSQL ***
2.Redis
Redis是一个开源的 key-value 存储系统
和Memcacheed类似,它支持存储的value类型相对更多,包括String、list、set、zset(sorted set --有序集合)和hash。
这些数据类型都支持 push/pop 、add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的
在此基础上,Redis支持各种不同方式的排序
与memcached一样,为了保证效率,数据都是缓存在内存中
区别是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的 记录文件
2.1应用场景
2.1.1 配合关系型数据库做高速缓存
※ 高频次,热门访问的数据,降低数据库IO
※ 分布式架构,做session共享
2.1.2 多样的数据结构存储持久化数据
2.1.3 Redis内存管理
2.1.3.1 删除策略
常用的过期数据的删除策略就两个
1.惰性删除:只会在取出key的时候才对数据进行过期检查。这样对CPU最友好,但是可能会造成太多过期key没有被删除。
2.定期删除:每隔一段时间抽取一批key执行删除过期key操作。并且,redis底层会通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。
定期删除对内存更加友好。惰性删除对CPU更加友好。redis一般采用的是定期删除+惰性/懒汉式删除
但是,仅仅通过给key设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期key的情况。这样就导致大量过期key堆积在内寸中,然后就 Out of memory 了 。
解决方案:Redis内寸淘汰机制
6种数据淘汰策略:
volatile-lru(least recently used):从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru:当内寸不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(最常用)
allkeys-random:从数据集中任意选择数据淘汰
no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时。新写入操作会报错。
4.0版本后增加以下两种:
volatile-lfu(least frequently used):从已设置过期时间的数据集中挑选最不经常使用的数据淘汰
allkeys-lfu:当内寸不足以容纳新写入数据时,在键空间中,移除最不经常使用的key。
2.1.4 Redis持久化机制
怎么保证Redis挂掉之后再重启数据可以进行恢复?
Redis的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF)。这两种方法各有千秋:
2.1.4.1 什么是RDB持久化?
Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。
快照持久化是Redis默认采用的持久化方法,在 redis.conf 配置文件中默认有此配置:
save 900 1 //在900秒之后,如果至少有1个key发生改变,Redis就会自动触发bgsave命令创建快照
save 300 10 //在300秒之后,如果至少有10个key发生改变,Redis就会自动触发bgsave命令创建快照
2.1.4.2 RDB创建快照时会阻塞主线程吗?
Redis提供了两个命令来生成RDB快照文件:
save:主线程执行,会阻塞主线程;
bgsave:子线程执行,不会阻塞主线程,默认选项
2.1.4.3 什么是AOF持久化?
与快照持久化相比,AOF持久化实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过apppendonly参数开启:
appendonly yes
开启AOF 持久化后每执行一条会更改Redis中的数据命令,Redis就会将该命令写入到内存缓存 server.aof_buf 中,然后再根据 appendfsync 配置来决定何时将其同步到硬盘中的 AOF 文件。
AOF 文件的保存位置和 RDB文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。
在Redis的配置文件中存在三种不同的AOF持久化方式,他们分别是:
appendfsync always //每次有数据修改发生时都会写入AOF文件,这样会严重降低 Redis的速度
appendfsync everysec //每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no //让操作系统决定何时进行同步
为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
2.1.4.4 AOF日志是如何实现的
关系型数据库(如MYSQL)通常都是执行命令之前记录日志(方便故障恢复),而Redis AOF 持久化机制是在执行命令之后再记录日志。
执行完命令之后记录日志的原因:
避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
在命令之后完之后再记录,不会阻塞当前的命令执行。
风险:
如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)。
2.1.5 Redis事务
2.1.5.1 缓存穿透
简单来说就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。
解决方法:
2.1.5.1.1 缓存无效key
如果缓存和数据库都查不到某个key 的数据就写一个到 Redis 中去设置过期时间。但这种方案不能从根本解决此问题。
2.1.5.1.2 布隆过滤器
加入布隆过滤器后缓存处理流程:
可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。相比于我们平时常用的List、Map、Set等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。理论情况下添加到集合中的元素越多,误报 的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。
位数组中的每个元素都只占用 1 bit,并且每个元素只能是 0 或者 1.
总结:一种来检索元素是否在给定大集合中的数据结构,这种数据结构是高效且性能很好的 ,但缺点是具有一定的错误识别率和删除难度。并且,理论情况下,添加到集合中的元素越多,误报的可能性就越大。
如上图所示,当要添加该字符串到布隆过滤器中时,该字符串首先由多个哈希函数生成不同的哈希值,然后将对应的位数组下标设置为1,(当位数组初始化时,所有位置均为0)。当第二次存储相同字符串时,因为先前的对应位置已设置为1 ,所以很容易知道此值已存在(去重非常方便)
当需要判断某个字符串是否在布隆过滤器中时,只需要对给定字符串再次进行相同的哈希计算,得到值后判断位数组中的每个元素会否都为1 。如果都为1 ,那么说明这个值在布隆过滤器中,如果存在一个值不为1,说明该元素不在布隆过滤器中。
因此,一定会出现这样一种情况:不同的字符串可能哈希出来的位置相同。
解决方法:适当增加位数组大小或者调整我们的的哈希函数来降低概率。
布隆过滤器使用场景:
- 判断给定数据是否存在: 比如判断一个数字是否存在于包含大量数字的数字集中(数字集很大,5亿以上!)、防止缓存穿透(判断请求 的数据是否有效避免直接绕过缓存请求数据库)等等 、邮箱的垃圾邮件过滤、黑名单功能等等。
- 去重:比如爬给定网址的时候对已经爬取过的URL去重 。
2.1.5.2 缓存雪崩
场景一:缓存在同一时间大面积失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。
举例:系统的缓存模块出了问题比如宕机导致不可用。早晨系统的所有访问,都要走数据库。
场景二:有一些被大量访问数据(热点缓存)在某一时刻大面积失效,导致对应的 请求直接落到了数据库上。
举例:秒杀开始12个小时之前,我们同一存放了一批商品到Redis中,设置的缓存过期时间也是12个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致相应的请求直接就落到了数据库上。
解决方法:
针对Redis 服务不可用的情况:
- 采用Redis集群,避免单机出现问题整个缓存服务都没办法使用
- 限流,避免同时处理大量的请求
针对热点缓存失效的情况:
- 设置不同的 失效时间比如随机设置缓存的失效时间
- 缓存永不失效
2.1.6 Redis 主从同步
主从同步(主从复制)是Redis高可用服务的基石,也是多机运行中最基础的一个。我们把主要存储数据的节点叫做主节点(master),把其他通过复制主节点数据的副本节点叫做从节点。
在Redis中一个主节点可以拥有多个从节点,一个从节点也可以是其他服务器的主节点:
主从同步的优点
主从同步具有以下三个优点:
性能方面:有了主从同步之后,可以把查询任务分配给从服务器,用主服务器来执行写操作,这样极大的提高了程序运行的效率,把所有压力分摊到各个服务器了;
高可用:当有了主从同步之后,当主服务器节点宕机之后,可以很迅速的把从节点提升为主节点,为 Redis 服务器的宕机恢复节省了宝贵的时间;
防止数据丢失:当主服务器磁盘坏掉之后,其他从服务器还保留着相关的数据,不至于数据全部丢失。
既然主从同步有这么多的优点,那接下来我们来看如何开启和使用主从同步功能。
开启主从同步
运行中设置从服务器
在 Redis 运行过程中,我们可以使用 replicaof host port 命令,把自己设置为目标 IP 的从服务器,执行命令如下:
127.0.0.1:6379> replicaof 127.0.0.1 6380
OK
如果主服务设置了密码,需要在从服务器输入主服务器的密码,使用 config set masterauth 主服务密码 命令的方式,例如:
127.0.0.1:6377> config set masterauth pwd654321
OK
- 执行流程
在执行完 replicaof 命令之后,从服务器的数据会被清空,主服务会把它的数据副本同步给从服务器。
- 测试同步功能
主从服务器设置完同步之后,我们来测试一下主从数据同步,首先我们先在主服务器上执行保存数据操作,再去从服务器查询。
主服务器执行命令:
127.0.0.1:6379> set lang redis
OK
从服务执行查询:
127.0.0.1:6379> get lang
"redis"
可以看出数据已经被正常同步过来了。
启动时设置从服务器
我们可以使用命令 redis-server --port 6380 --replicaof 127.0.0.1 6379 将自己设置成目标服务器的从服务器。
数据同步
完整数据同步
当有新的从服务器连接时,为了保障多个数据库的一致性,主服务器会执行一次 bgsave 命令生成一个 RDB 文件,然后再以 Socket 的方式发送给从服务器,从服务器收到 RDB 文件之后再把所有的数据加载到自己的程序中,就完成了一次全量的数据同步。
注意事项:
数据一致性问题: 当从服务器已经完成和主服务的数据同步之后,再新增的命令会以异步的方式发送至从服务器,在这个过程中主从同步会有短暂的数据不一致,如在这个异步同步发生之前主服务器宕机了,会造成数据不一致。
从服务器只读性 默认情况下,处于复制模式的主服务器既可以执行写操作也可以执行读操作,而从服务器则只能执行读操作。
可以在从服务器上执行 config set replica-read-only no 命令,使从服务器开启写模式,但需要注意:
- 在从服务器上写的数据不会同步到主服务器;
- 当键值相同时主服务器上的数据可以覆盖从服务器;
- 在进行完整数据同步时,从服务器数据会被清空。
2.1.7哨兵模式
主从复制模式属于Redis 多机运行的基础,但这种模式本身存在一个致命的问题:当主节点奔溃后,需要人工干预才能恢复Redis的正常使用。
例如:我们有3台服务器做了主从复制,一个主服务器A和两个从服务器B、C,当A 发生故障之后,需要人工把服务器设置为主服务器,同时再去C服务器设置成从服务器并且从主服务器B同步数据,如果是发生在晚上或者从服务器节点很多的情况下,对于人工来说想要立即实现恢复的难度很多,所以我们需要一个自动的工具——Redis Sentinel(哨兵模式)来把手动的过程变成自动的,让Redis拥有自动容灾恢复(failover)的能力。
注:Redis Sentinel 的最小分配单位是一主一从。
我们需要使用命令 ./src/redis-sentinel sentinel.conf 来启动 Sentinel,可以看出我们在启动它时必须设置一个 sentinel.conf 文件,这个配置文件中必须包含监听的主节点信息:
sentinel monitor master-name ip port quorum
例如:
sentinel monitor mymaster 127.0.0.1 6379 1
其中:
- master-name 表示给监视的主节点起一个名称;
- ip 表示主节点的 IP;
- port 表示主节点的端口;
- quorum 表示确认主节点下线的 Sentinel 数量,如果 quorum 设置为 1 表示只要有一台 Sentinel 判断它下线了,就可以确认它真的下线了。
注意:如果主节点 Redis 服务器有密码,还必须在 sentinel.conf 中添加主节点的密码,不然会导致 Sentinel 不能自动监听到主节点下面的从节点。
所以如果 Redis 有密码,sentinel.conf 必须包含以下内容:
sentinel monitor mymaster 127.0.0.1 6379 1
sentinel auth-pass mymaster pwd654321
当我们配置好 sentinel.conf 并执行启动命令 ./src/redis-sentinel sentinel.conf 之后,Redis Sentinel 就会被启动,如下图所示:
从上图可以看出 Sentinel 只需配置监听主节点的信息,它会自动监听对应的从节点。
启动 Sentinel 集群:
上面为单个 Sentinel 的启动,如果发生宕机,就不能提供自动容灾的服务了,因此需要在不同的物理机上启动多个 Sentinel 来组成 Sentinel 集群,来保证 Redis服务的高可用。
启动第二个Sentinel:
[@iZ2ze0nc5n41zomzyqtksmZ:redis2]$ ./src/redis-sentinel sentinel.conf
5547:X 19 Feb 2020 20:29:30.047 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
5547:X 19 Feb 2020 20:29:30.047 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=5547, just started
5547:X 19 Feb 2020 20:29:30.047 # Configuration loaded
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 5.0.5 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26377
| `-._ `._ / _.-' | PID: 5547
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
5547:X 19 Feb 2020 20:29:30.049 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
5547:X 19 Feb 2020 20:29:30.049 # Sentinel ID is 6455f2f74614a71ce0a63398b2e48d6cd1cf0d06
5547:X 19 Feb 2020 20:29:30.049 # +monitor master mymaster 127.0.0.1 6379 quorum 1
5547:X 19 Feb 2020 20:29:30.049 * +slave slave 127.0.0.1:6377 127.0.0.1 6377 @ mymaster 127.0.0.1 6379
5547:X 19 Feb 2020 20:29:30.052 * +slave slave 127.0.0.1:6378 127.0.0.1 6378 @ mymaster 127.0.0.1 6379
5547:X 19 Feb 2020 20:29:30.345 * +sentinel sentinel 6455f2f74614a71ce0a63398b2e48d6cd1cf0d08 127.0.0.1 26379 @ mymaster 127.0.0.1 6379
从以上启动命令可以看出,比单机模式多了最后一行发现其他 Sentinel 服务器的命令,说明这两个 Sentinel 已经组成一个集群了。
Sentinel 集群示意图如下:
一般情况下 Sentinel 集群的数量取大于 1 的奇数,例如 3、5、7、9,而 quorum 的配置要根据 Sentinel 的数量来发生变化,例如 Sentinel 是3 台,那么对应的quorum 最好是2,如果Sentinel 是5台,那么 quorum最好是3 ,它表示当有 3台Sentinel 都确认主节点下线了,就可以确定主节点真的下线了。
与 quorum参数相关的有两个概念:主观下线和客观下线。
当 Sentinel 集群中,有一个Sentinel认为主服务器已经下线时,它会将这个主服务器标记为主观下线(Subjetively Down,SDOWN),然后询问集群中的其他Sentinel,是否也认为该服务器已下线,当同意主服务器已下线的 Sentinel数量达到 quorum参数所指定的数量时,Sentinel就会将相应的主服务器标记为客观下线(Objectively down,ODDWN),然后开始对其进行故障转移。
哨兵工作原理:
首先每个 Sentinel 会以每秒钟1 次的频率,向已知的主服务器、从服务器和以及其他 Sentinel 实例,发送一个 PING命令。
如果最后一次有效回复PING命令的时间超过 down-after-milliseconds所配置的值(默认30秒),那么这个实例会被 Sentinel标记为主观下线。
如果一个主服务器被标记为主观下线,那么正在监视这个主服务器的所有 Sentinel 节点,要以每秒 1次的频率确认 主服务器的确进入了主观下线状态。
如果有足够数量(quorum配置值) 的 Sentinel 在指定的时间范围内同意这一判断,那么这个主服务器被标记为客观下线。此时所有的 Sentinel会按照规则协商自动选出新的主节点。
注意:一个有效的PING 回复可以是:+PONG、-LOADING、或者-MASTERDOWN。如果返回值非以上三种回复,或者在指定时间内没有回复PING命令,那么 Sentinel认为服务器返回的回复无效(non-valid)。