redis-cluster
1. 概念
-
为何要搭建Redis集群。Redis是在内存中保存数据的,而我们的电脑一般内存都不大,这也就意味着Redis不适合存储大数据,适合存储大数据的是Hadoop生态系统的Hbase或者是MogoDB。
-
Redis更适合处理高并发,一台设备的存储能力是很有限的,但是多台设备协同合作,就可以让内存增大很多倍,这就需要用到集群。
-
redis 3.0之后版本支持redis-cluster集群,它是Redis官方提出的解决方案,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
-
客户端与 redis 节点直连,不需要中间 proxy 层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
所有的 redis 节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
2. 分布式存储机制-槽
-
redis-cluster 把所有的物理节点映射到[0-16383]slot 上,cluster 负责维护
-
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点
-
例如三个节点:槽分布的值如下:
-
SERVER1: 0-5460
-
SERVER2: 5461-10922
-
SERVER3: 10923-16383
-
3. 容错机制-投票
选举过程是集群中所有master参与,如果半数以上master节点与故障节点通信超过(cluster-node-timeout),认为该节点故障,自动触发故障转移操作. 故障节点对应的从节点自动升级为主节点。
-
什么时候整个集群不可用?
如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完成时进入fail状态。
redis的事务
1.概念
可以一次执行多个命令,本质是一组命令的集合(队列)。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞。
2.特点
redis的事务是弱原子性,弱事务的;关系型数据库是强原子性的。
3.弱原子性的表现
-
正常执行
127.0.0.1:6379[15]> multi OK 127.0.0.1:6379[15]> set k1 v1 QUEUED 127.0.0.1:6379[15]> set k2 v2 QUEUED 127.0.0.1:6379[15]> get k1 QUEUED 127.0.0.1:6379[15]> exec 1) OK 2) OK 3) "v1"
-
放弃事务
127.0.0.1:6379[15]> multi OK 127.0.0.1:6379[15]> set k1 v1 QUEUED 127.0.0.1:6379[15]> set k2 v2 QUEUED 127.0.0.1:6379[15]> discard OK 127.0.0.1:6379[15]> get k1 (nil)
-
全体连坐
语法错误,会导致全体连坐,都失败
127.0.0.1:6379[15]> multi OK 127.0.0.1:6379[15]> set k1 v1 QUEUED 127.0.0.1:6379[15]> set k2 v2 QUEUED 127.0.0.1:6379[15]> set k3 (error) ERR wrong number of arguments for 'set' command 127.0.0.1:6379[15]> exec (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379[15]> get k1 (nil)
-
冤有头债有主
逻辑错误,哪个错误找哪个
127.0.0.1:6379[15]> multi OK 127.0.0.1:6379[15]> set k1 v1 QUEUED 127.0.0.1:6379[15]> set k2 2 QUEUED 127.0.0.1:6379[15]> incr k2 QUEUED 127.0.0.1:6379[15]> incr k1 QUEUED 127.0.0.1:6379[15]> exec 1) OK 2) OK 3) (integer) 3 4) (error) ERR value is not an integer or out of range
4.乐观锁机制
watch
监视一个或多个key,如果执行事物之前被其他key修改,那么事物会被提交成功吗? 不能。
如果没有监视这个key,那么在事物中这个key被其他操作修改后,能成功吗? 能unwatch 取消watch命令对所有key的监视
代码:
两个线程
set balance 1000
watch balance
multi
set balance 1
exec
watch balance
multi
set balance 2
exec (结果为nil)
先执行exec的修改成功,因为进入事务后尚未提交,watch认为没有改变,就可以提交;线程2提交的时候发现已经和预期不一样了。
redis的持久化策略
AOF+RDB
持久化策略-RDB
1. 什么是RDB?
RDB 是 Redis 默认的持久化方案。在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件。Redis 重启会通过加载dump.rdb文件恢复数据。
2.RDB之被动持久化
save 900 1:表示900 秒内如果至少有 1 个 key 的值变化,则保存
save 300 10:表示300 秒内如果至少有 10 个 key 的值变化,则保存
save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存
总结:redis 的rdb 策略在开启时,当有满足其任意一个条件时,都会触发被动持久化,生成dump.rdb 文件
3.RDB之主动持久化
当我们使用flushall,flushDB,save ,和bgsave时都会触发主动持久化
3.1save持久化
save 持久化将造成redis 数据阻塞,是一个同步持久化方案,即当我们在执行save操作时,在save未执行完毕时,无法执行其他操作。
save操作会造成阻塞
3.2bgsave 持久化
bgsave是异步的,不会造成阻塞(在fork子进程阶段会产生阻塞),在持久化结束后,会生成dump.rdb文件。
bgsave持久化原理:
底层会fork 出一条子进程,这条子进程来进行持久化操作,而主进程进行相应其他操作,在子进程进行持久过程中,会生成temp.rdb,当子进程持久化结束后,会用temp.rdb 去替换 dump.rdb
总结:RDB:被动持久化方案,采用的是bgsave方案,适合大规模数据恢复,但可能会损失一个时间段的数据(因为此时并未触发持久化就宕机了)
持久化策略-AOP
1. AOP是什么?
aof 的出现是为了弥补rdb的不足,因为rdb会损失一部分数据,aof (默认关闭)以日志的方式来记录用户的每一次写操作在磁盘上,在需要恢复数据时,从磁盘的aof 文件中,重新执行这些指令,从而恢复数据。
2.rdb和aof优先级
aop 优先级较高,如果有aof 文件先加载aof 文件,如果没有aof文件才会去加载dump.rdb文件。
3.Redis 的同步策略
命令 | 描述 |
---|---|
appendfsync always | 表示每次写入都执行fsync,以保证数据同步到磁盘,效率很低; |
appendfsync everysec | 表示每秒执行一次fsync,可能会导致丢失这1s数据,默认选项 |
appendfsync no | 由操作系统保证数据同步到磁盘,速度最快,但是不太安全; |
Redis 同步数据流程:
Redis 会将这些写的内容,写如到缓冲区aof_buf中,然后由同步策略来决定到底是如何进行持久化,默认情况下是1s
4.AOF的重写机制
4.1 重写以及原理介绍
- aof 是将client 的写操作要记录在appendonly.aof的文件中,那么势必导致appendonly.aof文件越来越大,为了给aof文件进行压缩,redis 采用重写的方式来实现对aof文件进行压缩
- 关于aof重写机制条件的配置
命令 | 描述 |
---|---|
auto-aof-rewrite-percentage :100 | 当aof文件大小为原来的百分之一百 |
auto-aof-rewrite-min-size 64mb:64mb | 当aof文件大小大于64mb |
当同上满足以上两个条件,redis 将对aof 文件进行重写,同时也可以使用bgrewriteaof 让其自动重写 。
- 重写原理:
- 当开始重写时,对aof进程执行bgrwriteaof 的指令
- 父进程收到这个指令后,会进行fork 子进程(此时主进程阻塞),由子进程按照第四步来完成数据的重写生成新的aof文件
- 当子进程fork 结束后,通过5-1 产生信号通知主进程已经fork完毕,可以继续接受请求,同时将写命令依然写入AOF缓冲区 3-1 (aof_buf),并根据appendfsync策略同步到硬盘即旧的aof文件中,保证原有AOF机制的正确,由于子进程只能共享fork操作时的内存数据。父进程依然在响应命令,因此Redis使用AOF重写缓冲区(aof_rewrite_buf)保存这部分新日志,防止新AOF文件生成期间丢失这部分数据,在执行aof完毕后,将 aof_rewrite_buf中的这些数据 追加到 新的aof文件中,也就是说,在执行重写的过程中,写指令是同时追加到aof_buf和 aof_rewrite_buf中的。
- 最后 5-3 利用新的aof 文件去替换旧的aof 文件
Redis 过期策略和淘汰机制
一、过期策略
1. 为什么要有过期策略
- 内存是很宝贵的,我们不能让数据永久存储在内存中, 所以对于某些数据我们在操作时,需要对其指令过期时间,当过期时间到达后,redis 会采用过期策略来处理这些过期的key
2.Key过期的三种策略
2.1 定时删除(了解)
在设置key的过期时间的同时,为该key创建一个定时器,当过期的时间到了,那么redis 就会将这个key删除,这种方式虽然可以精确的删除过期的时间key,但会对cpu造成极大的负担,所以redis 并不采用这种方式来删除过期时间key
2.2 定期删除
每隔一段时间执行一次删除过期key操作,采用这种方式对cpu 负担不大,但可能会产生一些漏网之鱼,从而导致内存中还存在部分过期的key
原理如下:
- Redis 会依次扫描 内部的16 个库
- 每次循环随机选择每个db中的20个key,判断其是否过期
- 如果没有25% 的key 过期,那么表明该库处于健康状态,则开始遍历下一个db
- 如果超过25%的key过期,那么会判断当前循环是否已经超过了规定的扫描时间,如果未超过,则继续遍历该db,再抽取25 %的key判断是否过期,如果超过时间,则同样开始下一个db的循环
2.3 惰性删除
正是因为定期删除可能会导致部分过期的key无法删除,所以redis 除了采用定期删除以外,还采用了惰性删除,惰性删除,是在我们去获取key时,首先判断这个key是否过期,如果过期,那么Redis 才会将这个key删除,同时返回一个null给client。
而Redis 采用的过期策略:就是惰性删除+ 定期删除
二、淘汰机制
淘汰机制: 当redis 的内存达到最大值后,redis 对内存的处理方案
2.1 配置文件&讲解
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key according to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys-random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# noeviction -> don't expire at all, just return an error on write operations
# The default is:
#
# maxmemory-policy noeviction
由配置文件可知:当Redis 达到最大内存后,你可以采用上述五个方案让Redis淘汰掉内存中的数据
命令 | 描述 |
---|---|
volatile -lru | 最近最少使用 针对的是设置了过期的key |
allkeys-lru | 最近最少使用 针对所有的key |
volatile-random | 随机移除 针对的是设置了过期的key |
allkeys-random | 随机移除 针对所有的key |
volatile-ttl | 移除规定时间内,更早过期的key 针对的是设置了过期的key |
noeviction | 完全不管,仅仅只是在写操作时返回一个错误即可 |
redis 的主从和哨兵
1. 读写分离
读写分离基本原理:让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE)操作,而从数据库处理SELECT查询操作。
读写分离的好处:
- 将读操作和写操作分离到不同的数据库上,避免主服务器出现性能瓶颈;
- 主服务器进行写操作时,不影响查询应用服务器的查询性能,降低阻塞,提高并发;
- 数据拥有多个容灾副本,提高数据安全性,同时当主服务器故障时,可立即切换到其他服务器,提高系统可用性;
2. redis主从
配从不配主
命令:
查看当前主机状态
127.0.0.1:6379[15]> info replication
# Replication
role:master
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
如何变成主机变成别的从机: slaveof 主库 ip 主库端口
SLAVEOF 127.0.0.1 6379
实现的效果:
- 主机可写可读
- 从机只能读
- 更新方面的问题
- 如果是从机第一次连接上主机: 全量更新
- 如果是一直连接着主机,当主机数据增多,从机增量更新 runid
3. 投票
哨兵:
-
创建sentinel.conf文件
sentinel monitor host6379(被监控数据库名字(自己起名字)) 127.0.0.1 6379 1
-
投票
./redis-sentinel sentinel.conf
效果:主机挂了之后,从机自我投票变成master
redis的缓存穿透
1. 定义
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
2. 如何解决
- 制ip(运维角度)
- 将所有商品的商品id 放到集合中,当用户过来查询时,先判断要查询的商品是否存在集合中,如果集合中有对应数据,才进入下一步。
3. 缺陷
集合太大,假设10亿商品,每个商品id都是15位整型的id,需要大概500g的缓存,明显不可能, 如何解决?采用布隆过滤器。
4. 布隆过滤器
bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。对于每个key,只需要k个比特位,每个存储一个标志,用来判断key是否在集合中。
算法:
- 首先需要k个hash函数,每个函数可以把key散列成为1个整数
- 初始化时,需要一个长度为n比特的数组,每个比特位初始化为0
- 某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为1
- 判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。
优点:不需要存储key,节省空间
缺点:
- 算法判断key在集合中时,有一定的误判概率(哈希冲突)
- 无法删除
典型的应用场景:
电商数据库查询、垃圾邮件过滤、爬虫去重