|
@@ -1130,3 +1130,785 @@ string
|
|
|
(integer) 4
|
|
|
```
|
|
|
|
|
|
+# 事务
|
|
|
+
|
|
|
+Redis的单条命令是保证原子性的,但是redis事务不能保证原子性
|
|
|
+
|
|
|
+> Redis事务本质:一组命令的集合。
|
|
|
+>
|
|
|
+> ----------------- 队列 set set set 执行 -------------------
|
|
|
+>
|
|
|
+> 事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令进行干扰。
|
|
|
+>
|
|
|
+> - 一次性
|
|
|
+> - 顺序性
|
|
|
+> - 排他性
|
|
|
+>
|
|
|
+> ------
|
|
|
+>
|
|
|
+> 1. Redis事务没有隔离级别的概念
|
|
|
+> 2. Redis单条命令是保证原子性的,但是事务不保证原子性!
|
|
|
+
|
|
|
+## Redis事务操作过程
|
|
|
+
|
|
|
+- 开启事务(`multi`)
|
|
|
+- 命令入队
|
|
|
+- 执行事务(`exec`)
|
|
|
+
|
|
|
+所以事务中的命令在加入时都没有被执行,直到提交时才会开始执行(Exec)一次性完成
|
|
|
+
|
|
|
+```shell
|
|
|
+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 k1
|
|
|
+QUEUED
|
|
|
+127.0.0.1:6379> set k3 v3
|
|
|
+QUEUED
|
|
|
+127.0.0.1:6379> keys *
|
|
|
+QUEUED
|
|
|
+127.0.0.1:6379> exec # 事务执行
|
|
|
+1) OK
|
|
|
+2) OK
|
|
|
+3) "v1"
|
|
|
+4) OK
|
|
|
+5) 1) "k3"
|
|
|
+ 2) "k2"
|
|
|
+ 3) "k1"
|
|
|
+```
|
|
|
+
|
|
|
+取消事务(`discurd`)
|
|
|
+
|
|
|
+```shell
|
|
|
+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> DISCARD # 放弃事务
|
|
|
+OK
|
|
|
+127.0.0.1:6379> EXEC
|
|
|
+(error) ERR EXEC without MULTI # 当前未开启事务
|
|
|
+127.0.0.1:6379> get k1 # 被放弃事务中命令并未执行
|
|
|
+(nil)
|
|
|
+```
|
|
|
+
|
|
|
+## 事务错误
|
|
|
+
|
|
|
+代码语法错误(编译时异常)所有的命令都不执行
|
|
|
+
|
|
|
+```shell
|
|
|
+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> error k1 # 这是一条语法错误命令
|
|
|
+(error) ERR unknown command `error`, with args beginning with: `k1`, # 会报错但是不影响后续命令入队
|
|
|
+127.0.0.1:6379> get k2
|
|
|
+QUEUED
|
|
|
+127.0.0.1:6379> EXEC
|
|
|
+(error) EXECABORT Transaction discarded because of previous errors. # 执行报错
|
|
|
+127.0.0.1:6379> get k1
|
|
|
+(nil) # 其他命令并没有被执行
|
|
|
+```
|
|
|
+
|
|
|
+代码逻辑错误 (运行时异常) **其他命令可以正常执行 ** >>> 所以不保证事务原子性
|
|
|
+
|
|
|
+```shell
|
|
|
+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> INCR k1 # 这条命令逻辑错误(对字符串进行增量)
|
|
|
+QUEUED
|
|
|
+127.0.0.1:6379> get k2
|
|
|
+QUEUED
|
|
|
+127.0.0.1:6379> exec
|
|
|
+1) OK
|
|
|
+2) OK
|
|
|
+3) (error) ERR value is not an integer or out of range # 运行时报错
|
|
|
+4) "v2" # 其他命令正常执行
|
|
|
+
|
|
|
+# 虽然中间有一条命令报错了,但是后面的指令依旧正常执行成功了,没有进行回滚。
|
|
|
+# 所以说Redis单条指令保证原子性,但是Redis事务不能保证原子性。
|
|
|
+```
|
|
|
+
|
|
|
+## 监控
|
|
|
+
|
|
|
+悲观锁:
|
|
|
+
|
|
|
+- 很悲观,认为什么时候都会出现问题,无论做什么都会加锁
|
|
|
+
|
|
|
+乐观锁:
|
|
|
+
|
|
|
+- 很乐观,认为什么时候都不会出现问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
|
|
|
+- 获取version
|
|
|
+- 更新的时候比较version
|
|
|
+
|
|
|
+使用`watch key`监控指定数据,相当于乐观锁加锁。
|
|
|
+
|
|
|
+> 正常执行
|
|
|
+
|
|
|
+```shell
|
|
|
+127.0.0.1:6379> set money 100 # 设置余额:100
|
|
|
+OK
|
|
|
+127.0.0.1:6379> set use 0 # 支出使用:0
|
|
|
+OK
|
|
|
+127.0.0.1:6379> watch money # 监视money (上锁)
|
|
|
+OK
|
|
|
+127.0.0.1:6379> multi
|
|
|
+OK
|
|
|
+127.0.0.1:6379> DECRBY money 20
|
|
|
+QUEUED
|
|
|
+127.0.0.1:6379> INCRBY use 20
|
|
|
+QUEUED
|
|
|
+127.0.0.1:6379> exec # 监视值没有被中途修改,事务正常执行
|
|
|
+1) (integer) 80
|
|
|
+2) (integer) 20
|
|
|
+```
|
|
|
+
|
|
|
+> 测试多线程修改值,使用watch可以当做redis的乐观锁操作(相当于getversion)
|
|
|
+
|
|
|
+我们启动另外一个客户端模拟插队线程。
|
|
|
+
|
|
|
+线程1:
|
|
|
+
|
|
|
+```shell
|
|
|
+127.0.0.1:6379> watch money # money上锁
|
|
|
+OK
|
|
|
+127.0.0.1:6379> multi
|
|
|
+OK
|
|
|
+127.0.0.1:6379> DECRBY money 20
|
|
|
+QUEUED
|
|
|
+127.0.0.1:6379> INCRBY use 20
|
|
|
+QUEUED
|
|
|
+127.0.0.1:6379> # 此时事务并没有执行
|
|
|
+```
|
|
|
+
|
|
|
+模拟线程插队,线程2:
|
|
|
+
|
|
|
+```shell
|
|
|
+127.0.0.1:6379> INCRBY money 500 # 修改了线程一中监视的money
|
|
|
+(integer) 600
|
|
|
+```
|
|
|
+
|
|
|
+回到线程1,执行事务:
|
|
|
+
|
|
|
+```shell
|
|
|
+127.0.0.1:6379> EXEC # 执行之前,另一个线程修改了我们的值,这个时候就会导致事务执行失败
|
|
|
+(nil) # 没有结果,说明事务执行失败
|
|
|
+
|
|
|
+127.0.0.1:6379> get money # 线程2 修改生效
|
|
|
+"600"
|
|
|
+127.0.0.1:6379> get use # 线程1事务执行失败,数值没有被修改
|
|
|
+"0"
|
|
|
+```
|
|
|
+
|
|
|
+> 解锁获取最新值,然后再加锁进行事务。
|
|
|
+>
|
|
|
+> `unwatch`进行解锁。
|
|
|
+
|
|
|
+注意:每次提交执行exec后都会自动释放锁,不管是否成功
|
|
|
+
|
|
|
+# Redis.conf
|
|
|
+
|
|
|
+```shell
|
|
|
+# 单位, 对大小写不敏感
|
|
|
+# 1k => 1000 bytes
|
|
|
+# 1kb => 1024 bytes
|
|
|
+# 1m => 1000000 bytes
|
|
|
+# 1mb => 1024*1024 bytes
|
|
|
+# 1g => 1000000000 bytes
|
|
|
+# 1gb => 1024*1024*1024 bytes
|
|
|
+#
|
|
|
+# units are case insensitive so 1GB 1Gb 1gB are all the same.
|
|
|
+
|
|
|
+################################## INCLUDES ###################################
|
|
|
+# 可以包含其他配置文件
|
|
|
+# include /path/to/local.conf
|
|
|
+# include /path/to/other.conf
|
|
|
+
|
|
|
+################################## NETWORK #####################################
|
|
|
+# 绑定的本地IP
|
|
|
+bind 127.0.0.1
|
|
|
+
|
|
|
+# 是否是受保护模式
|
|
|
+protected-mode yes
|
|
|
+
|
|
|
+# 端口号
|
|
|
+port 6379
|
|
|
+
|
|
|
+################################# GENERAL #####################################
|
|
|
+
|
|
|
+# 是否开启守护进程,即为后台运行
|
|
|
+daemonize no
|
|
|
+
|
|
|
+# 管理守护进程
|
|
|
+supervised no
|
|
|
+
|
|
|
+# 如果以后台方式允许,就需要执行Pid进程文件
|
|
|
+pidfile /var/run/redis_6379.pid
|
|
|
+
|
|
|
+# 日志级别
|
|
|
+# Specify the server verbosity level.
|
|
|
+# This can be one of:
|
|
|
+# debug (a lot of information, useful for development/testing)
|
|
|
+# verbose (many rarely useful info, but not a mess like the debug level)
|
|
|
+# notice (moderately verbose, what you want in production probably) 生成环境使用
|
|
|
+# warning (only very important / critical messages are logged)
|
|
|
+loglevel notice
|
|
|
+
|
|
|
+# 日志的文件名
|
|
|
+logfile ""
|
|
|
+
|
|
|
+# 默认的数据库数量,默认16个
|
|
|
+databases 16
|
|
|
+
|
|
|
+# 是否显示redis的logo
|
|
|
+always-show-logo yes
|
|
|
+
|
|
|
+################################ 快照 ################################
|
|
|
+# 持久化,在规定时间执行多少次操作,则会持久化到文件.rdb .aof
|
|
|
+# 没有持久化,数据断电即失去
|
|
|
+# 900秒内,至少有一个key进行了修改,进行持久化操作
|
|
|
+save 900 1
|
|
|
+save 300 10
|
|
|
+save 60 10000
|
|
|
+# 之后会执行自己的
|
|
|
+
|
|
|
+# 持久化出错,是否需要继续工作
|
|
|
+stop-writes-on-bgsave-error yes
|
|
|
+
|
|
|
+# 是否压缩rdb文件,需要消耗CPU资源
|
|
|
+rdbcompression yes
|
|
|
+
|
|
|
+# 是否校验rdb文件,如果出错,会自动继续修复
|
|
|
+rdbchecksum yes
|
|
|
+
|
|
|
+# The filename where to dump the DB
|
|
|
+dbfilename dump.rdb
|
|
|
+
|
|
|
+# 持久化文件生成的目录
|
|
|
+dir ./
|
|
|
+
|
|
|
+################################# 主从复制 #################################
|
|
|
+
|
|
|
+# 配置主机的ip+端口
|
|
|
+# replicaof <masterip> <masterport>
|
|
|
+
|
|
|
+# 配置主机的密码
|
|
|
+# masterauth <master-password>
|
|
|
+
|
|
|
+# 当 replica 与 master 失去连接或者主从复制在进行时,replica 可以有两种不同的设置:
|
|
|
+# yes(默认值),则 replica 仍将响应客户端请求,可能会有过期数据,或者如果这是第一次同步,则数据集可能为空。
|
|
|
+# no , replica 将对所有请求命令(但不包含 INFO, replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, HOST: and LATENCY)返回 SYNC with master in progress 的错误。
|
|
|
+# 是否是只读模式
|
|
|
+replica-read-only yes
|
|
|
+################################## 安全 ###################################
|
|
|
+
|
|
|
+# 设置redis密码,默认是空值
|
|
|
+# requirepass foobared
|
|
|
+
|
|
|
+################################### 客户端限制 ####################################
|
|
|
+
|
|
|
+# 最大的客户端数量
|
|
|
+# maxclients 10000
|
|
|
+
|
|
|
+############################## 内存设置 ################################
|
|
|
+
|
|
|
+# 最大内存容量
|
|
|
+# maxmemory <bytes>
|
|
|
+
|
|
|
+# 内存达到上限的处理策略
|
|
|
+1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
|
|
|
+
|
|
|
+2、allkeys-lru : 删除lru算法的key
|
|
|
+
|
|
|
+3、volatile-random:随机删除即将过期key
|
|
|
+
|
|
|
+4、allkeys-random:随机删除
|
|
|
+
|
|
|
+5、volatile-ttl : 删除即将过期的
|
|
|
+
|
|
|
+6、noeviction : 永不过期,返回错误
|
|
|
+# maxmemory-policy noeviction
|
|
|
+
|
|
|
+############################## AOF的配置 ###############################
|
|
|
+
|
|
|
+# 默认不开启AOF,默认使用rdb持久化,大部分情况下rdb完全够用!
|
|
|
+appendonly no
|
|
|
+
|
|
|
+# 持久化的文件名字
|
|
|
+appendfilename "appendonly.aof"
|
|
|
+
|
|
|
+# 同步策略
|
|
|
+# 每次修改就会同步
|
|
|
+# appendfsync always
|
|
|
+# 每秒会同步,有可能丢失1s数据
|
|
|
+appendfsync everysec
|
|
|
+# 不执行sync,操作系统自己同步
|
|
|
+# appendfsync no
|
|
|
+```
|
|
|
+
|
|
|
+# redis持久化
|
|
|
+
|
|
|
+***
|
|
|
+
|
|
|
+面试和工作,持久化都是重点! Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中 的数据库状态也会消失。所以 Redis 提供了持久化功能!
|
|
|
+
|
|
|
+## 持久化—RDB
|
|
|
+
|
|
|
+在主从复制中,rdb就是备用了!从机上面
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快 照文件直接读到内存里。
|
|
|
+
|
|
|
+Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程 都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。 这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是 RDB,一般情况下不需要修改这个配置!
|
|
|
+
|
|
|
+有时候在生产环境我们会将这个文件进行备份!
|
|
|
+
|
|
|
+rdb保存的文件是dump.rdb都是在我们的配置文件中快照中进行配置的!
|
|
|
+
|
|
|
+### 触发机制
|
|
|
+
|
|
|
+1. save的规则满足的情况下,会自动触发rdb原则
|
|
|
+2. 执行flushall命令,也会触发我们的rdb原则
|
|
|
+3. 退出redis,也会自动产生rdb文件
|
|
|
+
|
|
|
+### 如何恢复rdb文件
|
|
|
+
|
|
|
+1.只需要将rdb文件放在redis启动目录就可以,redis启动时就会自动检测dum.rdb文件,恢复其中数据
|
|
|
+2.rdb文件存放的位置
|
|
|
+
|
|
|
+### 优缺点
|
|
|
+
|
|
|
+**优点:**
|
|
|
+
|
|
|
+1. 适合大规模的数据恢复
|
|
|
+2. 对数据的完整性要求不高
|
|
|
+
|
|
|
+**缺点:**
|
|
|
+
|
|
|
+1. 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
|
|
|
+2. fork进程的时候,会占用一定的内容空间
|
|
|
+
|
|
|
+## 持久化—AOF
|
|
|
+
|
|
|
+将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部在执行一遍!
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件 但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件 的内容将写指令从前到后执行一次以完成数据的恢复工作
|
|
|
+
|
|
|
+**Aof保存的是 appendonly.aof 文件**
|
|
|
+
|
|
|
+`appendonly no yes`则表示启用AOF
|
|
|
+
|
|
|
+默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!
|
|
|
+
|
|
|
+如果这个aof文件有错位,这时候redis是启动不起来的,我需要修改这个aof文件
|
|
|
+
|
|
|
+redis给我们提供了一个工具`redis-check-aof --fix`
|
|
|
+
|
|
|
+> 优点和缺点
|
|
|
+
|
|
|
+```bash
|
|
|
+appendonly yes # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分的情况下,rdb完全够用
|
|
|
+appendfilename "appendonly.aof"
|
|
|
+
|
|
|
+# appendfsync always # 每次修改都会sync 消耗性能
|
|
|
+appendfsync everysec # 每秒执行一次 sync 可能会丢失这一秒的数据
|
|
|
+# appendfsync no # 不执行 sync ,这时候操作系统自己同步数据,速度最快
|
|
|
+```
|
|
|
+
|
|
|
+优点
|
|
|
+
|
|
|
+1. 每一次修改都会同步,文件的完整性会更加好
|
|
|
+2. 每秒同步一次,可能会丢失一秒的数据
|
|
|
+3. 从不同步,效率最高
|
|
|
+
|
|
|
+缺点
|
|
|
+
|
|
|
+1. 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
|
|
|
+2. Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化
|
|
|
+
|
|
|
+## RDB和AOP选择
|
|
|
+
|
|
|
+### RDB 和 AOF 对比
|
|
|
+
|
|
|
+| | RDB | AOF |
|
|
|
+| ---------- | ------ | ------------ |
|
|
|
+| 启动优先级 | 低 | 高 |
|
|
|
+| 体积 | 小 | 大 |
|
|
|
+| 恢复速度 | 快 | 慢 |
|
|
|
+| 数据安全性 | 丢数据 | 根据策略决定 |
|
|
|
+
|
|
|
+### 如何选择使用哪种持久化方式?
|
|
|
+
|
|
|
+一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。
|
|
|
+
|
|
|
+如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
|
|
|
+
|
|
|
+有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+# redis发布订阅
|
|
|
+
|
|
|
+Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
|
|
|
+
|
|
|
+Redis 客户端可以订阅任意数量的频道。
|
|
|
+
|
|
|
+订阅/发布消息图: 第一个:消息发送者, 第二个:频道 第三个:消息订阅者!
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+## 命令
|
|
|
+
|
|
|
+| 命令 | 描述 |
|
|
|
+| ---------------------------------------- | ---------------------------------- |
|
|
|
+| `PSUBSCRIBE pattern [pattern..]` | 订阅一个或多个符合给定模式的频道。 |
|
|
|
+| `PUNSUBSCRIBE pattern [pattern..]` | 退订一个或多个符合给定模式的频道。 |
|
|
|
+| `PUBSUB subcommand [argument[argument]]` | 查看订阅与发布系统状态。 |
|
|
|
+| `PUBLISH channel message` | 向指定频道发布消息 |
|
|
|
+| `SUBSCRIBE channel [channel..]` | 订阅给定的一个或多个频道。 |
|
|
|
+| `SUBSCRIBE channel [channel..]` | 退订一个或多个频道 |
|
|
|
+
|
|
|
+## 示例
|
|
|
+
|
|
|
+```shell
|
|
|
+------------订阅端----------------------
|
|
|
+127.0.0.1:6379> SUBSCRIBE sakura # 订阅sakura频道
|
|
|
+Reading messages... (press Ctrl-C to quit) # 等待接收消息
|
|
|
+1) "subscribe" # 订阅成功的消息
|
|
|
+2) "sakura"
|
|
|
+3) (integer) 1
|
|
|
+1) "message" # 接收到来自sakura频道的消息 "hello world"
|
|
|
+2) "sakura"
|
|
|
+3) "hello world"
|
|
|
+1) "message" # 接收到来自sakura频道的消息 "hello i am sakura"
|
|
|
+2) "sakura"
|
|
|
+3) "hello i am sakura"
|
|
|
+
|
|
|
+--------------消息发布端-------------------
|
|
|
+127.0.0.1:6379> PUBLISH sakura "hello world" # 发布消息到sakura频道
|
|
|
+(integer) 1
|
|
|
+127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 发布消息
|
|
|
+(integer) 1
|
|
|
+
|
|
|
+-----------------查看活跃的频道------------
|
|
|
+127.0.0.1:6379> PUBSUB channels
|
|
|
+1) "sakura"
|
|
|
+```
|
|
|
+
|
|
|
+## 原理
|
|
|
+
|
|
|
+每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。
|
|
|
+
|
|
|
+## 缺点
|
|
|
+
|
|
|
+1. 如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
|
|
|
+2. 这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。
|
|
|
+
|
|
|
+## 应用
|
|
|
+
|
|
|
+1. 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
|
|
|
+2. 多人在线聊天室。
|
|
|
+
|
|
|
+稍微复杂的场景,我们就会使用消息中间件MQ处理。
|
|
|
+
|
|
|
+# Redis主从复制
|
|
|
+
|
|
|
+ ## 概念
|
|
|
+
|
|
|
+主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
|
|
|
+
|
|
|
+默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。
|
|
|
+
|
|
|
+## 作用
|
|
|
+
|
|
|
+1. **数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。**
|
|
|
+2. **故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式**
|
|
|
+3. **负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。**
|
|
|
+4. **高可用基石:主从复制还是哨兵和集群能够实施的基础。**
|
|
|
+
|
|
|
+## **为什么使用集群**
|
|
|
+
|
|
|
+1. **单台服务器难以负载大量的请求**
|
|
|
+2. **单台服务器故障率高,系统崩坏概率大**
|
|
|
+3. **单台服务器内存容量有限。**
|
|
|
+
|
|
|
+## 环境配置
|
|
|
+
|
|
|
+我们在讲解配置文件的时候,注意到有一个`replication`模块 (见Redis.conf中第8条)
|
|
|
+
|
|
|
+查看当前库的信息:`info replication`
|
|
|
+
|
|
|
+```shell
|
|
|
+127.0.0.1:6379> info replication
|
|
|
+# Replication
|
|
|
+role:master # 角色
|
|
|
+connected_slaves:0 # 从机数量
|
|
|
+master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
|
|
|
+master_replid2:0000000000000000000000000000000000000000
|
|
|
+master_repl_offset:0
|
|
|
+second_repl_offset:-1
|
|
|
+repl_backlog_active:0
|
|
|
+repl_backlog_size:1048576
|
|
|
+repl_backlog_first_byte_offset:0
|
|
|
+repl_backlog_histlen:0
|
|
|
+```
|
|
|
+
|
|
|
+既然需要启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息:
|
|
|
+
|
|
|
+- 端口号
|
|
|
+- pid文件名
|
|
|
+- 日志文件名
|
|
|
+- rdb文件名
|
|
|
+
|
|
|
+启动单机多服务集群:
|
|
|
+
|
|
|
+## 一主二从配置
|
|
|
+
|
|
|
+默认情况下,每台Redis服务器都是主节点;我们一般情况下只用配置从机就好了!
|
|
|
+
|
|
|
+认老大!一主(79)二从(80,81)
|
|
|
+
|
|
|
+使用`SLAVEOF host port`就可以为从机配置主机了。
|
|
|
+
|
|
|
+有树状模型和普通的1对多模型
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+## 使用规则
|
|
|
+
|
|
|
+1. 从机只能读,不能写,主机可读可写但是多用于写。
|
|
|
+
|
|
|
+ ```shell
|
|
|
+ 127.0.0.1:6381> set name sakura # 从机6381写入失败
|
|
|
+ (error) READONLY You can't write against a read only replica.
|
|
|
+
|
|
|
+ 127.0.0.1:6380> set name sakura # 从机6380写入失败
|
|
|
+ (error) READONLY You can't write against a read only replica.
|
|
|
+
|
|
|
+ 127.0.0.1:6379> set name sakura
|
|
|
+ OK
|
|
|
+ 127.0.0.1:6379> get name
|
|
|
+ "sakura"
|
|
|
+ ```
|
|
|
+
|
|
|
+2. 当主机断电宕机后,默认情况下从机的角色不会发生变化(此时从机不能进行读操作) ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
|
|
|
+
|
|
|
+3. 当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。
|
|
|
+
|
|
|
+4. 第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:
|
|
|
+
|
|
|
+ - 从机手动执行命令`slaveof no one`,这样执行以后从机会独立出来成为一个主机
|
|
|
+ - 使用哨兵模式(自动选举)
|
|
|
+
|
|
|
+> 如果没有老大了,这个时候能不能选择出来一个老大呢?手动!
|
|
|
+
|
|
|
+如果主机断开了连接,我们可以使用`SLAVEOF no one`让自己变成主机!其他的节点就可以手动连接到最新的主节点(手动)!如果这个时候老大修复了,那么久重新连接!
|
|
|
+
|
|
|
+## 复制原理
|
|
|
+
|
|
|
+Slave 启动成功连接到 master 后会发送一个sync同步命令
|
|
|
+
|
|
|
+Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行 完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
|
|
|
+
|
|
|
+全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
|
|
|
+
|
|
|
+增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行! 我们的数据一定可以在从机中 看到!
|
|
|
+
|
|
|
+# 哨兵模式
|
|
|
+
|
|
|
+(自动选举老大的模式)
|
|
|
+
|
|
|
+***
|
|
|
+
|
|
|
+## 概述
|
|
|
+
|
|
|
+主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题。
|
|
|
+
|
|
|
+谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
|
|
|
+
|
|
|
+哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+这里的哨兵有两个作用
|
|
|
+
|
|
|
+- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
|
|
|
+- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
|
|
|
+
|
|
|
+然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
|
|
|
+
|
|
|
+假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为**主观下线**。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover故障转移操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为**客观下线**
|
|
|
+
|
|
|
+## 优缺点
|
|
|
+
|
|
|
+优点:
|
|
|
+
|
|
|
+1、哨兵集群,基于主从复制模式,所有的主从配置优点,它全有
|
|
|
+
|
|
|
+2、 主从可以切换,故障可以转移,系统的可用性就会更好
|
|
|
+
|
|
|
+3、哨兵模式就是主从模式的升级,手动到自动,更加健壮!
|
|
|
+
|
|
|
+缺点:
|
|
|
+
|
|
|
+1、Redis 不好在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦!
|
|
|
+
|
|
|
+2、实现哨兵模式的配置其实是很麻烦的,里面有很多选择!
|
|
|
+
|
|
|
+## 配置
|
|
|
+
|
|
|
+```shell
|
|
|
+# Example sentinel.conf
|
|
|
+# 哨兵sentinel实例运行的端口 默认26379
|
|
|
+port 26379
|
|
|
+# 哨兵sentinel的工作目录
|
|
|
+dir /tmp
|
|
|
+# 哨兵sentinel监控的redis主节点的 ip port
|
|
|
+# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
|
|
|
+# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
|
|
|
+# sentinel monitor <master-name> <ip> <redis-port> <quorum>
|
|
|
+sentinel monitor mymaster 127.0.0.1 6379 2
|
|
|
+# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
|
|
|
+# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
|
|
|
+# sentinel auth-pass <master-name> <password>
|
|
|
+sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
|
|
|
+# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
|
|
|
+# sentinel down-after-milliseconds <master-name> <milliseconds>
|
|
|
+sentinel down-after-milliseconds mymaster 30000
|
|
|
+# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行同步,
|
|
|
+# 这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
|
|
|
+# 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
|
|
|
+# sentinel parallel-syncs <master-name> <numslaves>
|
|
|
+sentinel parallel-syncs mymaster 1
|
|
|
+# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
|
|
|
+#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
|
|
|
+#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
|
|
|
+#3. 当想要取消一个正在进行的failover所需要的时间。
|
|
|
+#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
|
|
|
+# 默认三分钟
|
|
|
+# sentinel failover-timeout <master-name> <milliseconds>
|
|
|
+sentinel failover-timeout mymaster 180000
|
|
|
+# SCRIPTS EXECUTION
|
|
|
+#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
|
|
|
+#对于脚本的运行结果有以下规则:
|
|
|
+#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
|
|
|
+#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
|
|
|
+#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
|
|
|
+#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
|
|
|
+#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
|
|
|
+#通知脚本
|
|
|
+# shell编程
|
|
|
+# sentinel notification-script <master-name> <script-path>
|
|
|
+sentinel notification-script mymaster /var/redis/notify.sh
|
|
|
+# 客户端重新配置主节点参数脚本
|
|
|
+# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
|
|
|
+# 以下参数将会在调用脚本时传给脚本:
|
|
|
+# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
|
|
|
+# 目前<state>总是“failover”,
|
|
|
+# <role>是“leader”或者“observer”中的一个。
|
|
|
+# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
|
|
|
+# 这个脚本应该是通用的,能被多次调用,不是针对性的。
|
|
|
+# sentinel client-reconfig-script <master-name> <script-path>
|
|
|
+sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般都是由运维来配置!
|
|
|
+```
|
|
|
+
|
|
|
+# Redis缓存穿透和雪崩
|
|
|
+
|
|
|
+***
|
|
|
+
|
|
|
+服务的高可用问题!
|
|
|
+
|
|
|
+在这里我们不会详细的区分析解决方案的底层!
|
|
|
+
|
|
|
+Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+## 缓存穿透(查不到)
|
|
|
+
|
|
|
+### 概念
|
|
|
+
|
|
|
+缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
|
|
|
+
|
|
|
+### 解决方案
|
|
|
+
|
|
|
+布隆过滤器
|
|
|
+
|
|
|
+布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+## 缓存击穿(量太大,缓存过期)
|
|
|
+
|
|
|
+### 概述
|
|
|
+
|
|
|
+这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
|
|
|
+
|
|
|
+当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
|
|
|
+
|
|
|
+### 解决方案
|
|
|
+
|
|
|
+**设置热点数据永不过期**
|
|
|
+
|
|
|
+从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。
|
|
|
+
|
|
|
+**加互斥锁**
|
|
|
+
|
|
|
+分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+## 缓存雪崩
|
|
|
+
|
|
|
+### 概念
|
|
|
+
|
|
|
+缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!
|
|
|
+
|
|
|
+产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
|
|
|
+
|
|
|
+### 解决方案
|
|
|
+
|
|
|
+**redis高可用**
|
|
|
+
|
|
|
+这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)
|
|
|
+
|
|
|
+**限流降级**(在SpringCloud讲解过!)
|
|
|
+
|
|
|
+这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
|
|
|
+
|
|
|
+**数据预热**
|
|
|
+
|
|
|
+数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
|