目录
一、Redis的事务 (Multi、Exec、discard)
3. WATCH key [key ...] 实现乐观锁的效果
一、Redis的事务 (Multi、Exec、discard)
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队。
注意:如此重要的场合居然没有提到原子性。因为Redis的事务没有原子性,和数据库的事务差别比较大。
Redis事务的三个命令:Multi、Exec、discard
Multi:排队
Exec:执行(相当于commit,其实差别很大)
Discard:放弃(相当于rollback,其实差别很大)
和MySQL的事务不同:
Commit:之前已经在内存中执行了SQL命令,commit的含义是写入数据库。Exec这才开始执行Redis命令,之前只是排队。Discard类似,只是排完队,然后立刻解散,一个命令也没有执行。
情况1:组队成功,exec成功
情况2:组队成功,discard成功
情况3:组队成功,exec失败。不会影响前面和后面正确命令的执行。和MySQL的事务差别之一所在,没有原子性。|
情况4:组队失败(指令错误),执行exec。所有的操作都不执行
只有事物,不上锁,也会出问题
账户总金额10000元,非信用卡。请求取款前先判断余额是否足够。多个并发请求,一个请求想给金额减8000,一个请求想给金额减5000,一个请求想给金额减1000。结果会出现余额负值的情况。
1. 悲观锁 — 安全、性能低
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次获取数据的操作都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
2. 乐观锁 — 安全、性能高 ★
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
3. WATCH key [key ...] 实现乐观锁的效果
在redis中通过使用 WATCH key [key ...] ,来实现乐观锁的效果
使用watch监视balance的值,达到了乐观锁的效果
不使用watch监视balance的值,出现了并发问题
unwatch:取消 WATCH 命令对所有 key 的监视。EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了
4. Redis事务三特性 (与MySQL事物区别)
- 单独的隔离操作:
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 - 没有隔离级别的概念:
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行 - 不保证原子性:
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
Redis事务和MySQL事务的区别
MySQL事务遵守严格的ACID特征,而Redis 设计更多的是追求简单与高性能,所以事务不会也没有必要受制于传统 ACID 的束缚。
- Redis 具备了一定的原子性,但不支持回滚,严格意义上无法保证原子性。
- Redis 不支持回滚,也就无法保证业务上的数据一致性。
- Redis 具备隔离性,但是没有隔离级别。
- Redis 通过一定策略可以保证持久性。Redis 是否具备持久化,取决于 Redis 的持久化模式比如AOF、RDB及其策略设置。
以后学习JUC,还会遇到“锁”。
5. Redis 事务:秒杀案例
1、秒杀的代码
1个页面、1个Servlet、1个秒杀类
秒杀的效果
2、添加并发请求插件ab(apache benchmark)
安装ab插件:yum install httpd-tools
使用 man ab查看说明
命令:
ab -n 1000 -c 100 -k -p ~/postfile -T application/x-www-form-urlencoded http://192.168.1.105:8080/day18seckill/doseckill
ab:并发测试
-n:发送1000个请求
-c:每次发送100个
-k:KeepAlive
-p:POST请求
~/postfile: 数据在/postfile内
-T:表单中的enctype
最后,表单提交地址
在root目录下准备文件postfile,内容如下
内容:prodid=0101&
3、解决超卖问题
原因:没有启用事务和锁
解决:使用Redis事物,乐观锁
6、解决库存遗留问题 (Lua)
问题:存在库存遗留的问题
原因:乐观锁。100个并发的请求发出去,按照乐观锁理论,1个成功。其他99个,可能不会再去发请求了。
解决:使用LUA语言的脚本
Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。
很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。
这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂。
LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。
利用lua脚本淘汰用户,解决超卖问题。
redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。
二、持久化:RDB (Redis DataBase)
RDB:可以在指定的时间间隔内将内存中的数据集的快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。也可以手动完成持久化。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用临时文件替换上次持久化好的文件。
个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。缺点:最后一次持久化后的数据可能丢失。
问题一:子进程具体是如何持久化的?
会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件
优点:安全;
缺点:最后一次持久化后的数据可能丢失(五分钟一次持久化,期间异常关机了)。
问题二:什么是fork
- Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程.
- 一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
问题3:什么是快照?
Snapshot;即写即拷(copy-on-write 写时复制)
拍摄快照是很快,和原来的内容是相同的,没有进行复制操作
后面当进行各种操作时,如果数据发生了变化,就复制变化的数据一份。快照中存储旧的数据。变化越多,复制操作越多。恢复快照时,直接执行快照数据即可。
1、RDB 配置项 (redis.conf)
dbfilename:持久化文件的名字 dump.rdb
dir:持久化文件的存储位置,默认值./ (在启动的目录生成文件,建议写死)
save:秒钟 写操作次数 自动持久化的频。设置为save “”,表示不进行持久化。默认是1分钟内改了1万次,或5分钟内改了10次,或15分钟内改了1次。
指令:find / -iname dump.rdb,查找rdb文件
2、如何实现持久化
方法1:save策略 自动持久化
RDB是整个内存的压缩过的Snapshot,RDB的数据结构,可以配置复合的快照触发条件,默认是1分钟内改了1万次,或5分钟内改了10次,或15分钟内改了1次。
方法2:手动持久化:save bgsave
save :save时只管保存,其它不管,全部阻塞。手动保存。不建议。
bgsave:Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求。
可以通过lastsave 命令获取最后一次成功执行快照的时间
方法3:flushall 会执行持久化(flushdb不会)
执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
方法4:shutdown 在redis关闭之前也会先执行持久化
3. RDB的优点和缺点
优点:
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高更适合使用 (最后一次可能数据丢失)
- 节省磁盘空间 (快照)
- 恢复速度快 (存储的是数据,而非操作)
缺点:
- Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
- 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
- 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。
三、持久化:AOF (Append Only File)
1. AOF的定义
以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
关于配置
redis.conf 中选项 appendonly no,默认no不开启。可以修改为 appendonly yes,代表开启AOF。
AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)
可以在redis.conf中通过 appendfilename配置文件名称,默认为 appendonly.aof。AOF文件的保存路径,同RDB的路径一致。
2. AOF持久化流程
(1)客户端的请求写命令会被append追加到AOF缓冲区内;
(2)AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;
(3)AOF文件大小超过重写策略(自动重写) 或手动重写(bgrewriteaof) 时,会对aof文件 rewrite重写,压缩AOF文件容量;
(4)Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;
3. AOF自动持久化策略
appendfsync always
始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好
appendfsync everysec ( 默认策略)
每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
appendfsync no
redis 不主动进行同步,把同步时机交给操作系统。
4. AOF重写rewrite策略
自动重写 — 配置
auto-aof-rewrite-percentage 100 自动重写百分比
auto-aof-rewrite-min-size 64mb 自动重写需要的大小
如果文件大小增长了一倍,且超过了64M,就触发了rewrite策略。
20M--------->40M-------->80M---------->rewrite
5. 手动重写
指令:bgrewriteaof
6. 如何对aof文件进行重写?
redis4.0版本后的重写,是指上就是把rdb 的快照,以二级制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作。
7. AOF 的优缺点
优点:
- 备份机制更稳健,丢失数据概率更低。
- 可读的日志文本,通过操作AOF稳健,可以处理误操作。
缺点:
- 比起RDB占用更多的磁盘空间。
- 恢复备份速度要慢。
- 每次读写都同步的话,有一定的性能压力。
- 存在个别Bug,造成恢复不能。
到底该用RDB还是AOF
1. 如果对数据不敏感,可以选单独用RDB。
2. 如果对数据敏感,同时使用RDB和AOF
(AOF不要单独使用,只有AOF重写aof文件无法进行)
3. 如果只是做纯内存缓存,可以都不用。
四、Redis主从复制 ★
主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。
优点:读写分离,性能扩展;容灾快速恢复,提高可用性。
实现主从复制:一个主机master两个从机slave (一主二仆)
方式1:三台物理机
方法2:一台物理机,装3个虚拟机,分别安装redis,端口都是6379,IP地址不同
方法3:一台物理机、一个虚拟机,三个redis,IP地址相同,端口不同 ,启动3个redis服务器
1、创建三个conf文件,分别是redis6379.conf、redis6380.conf、redis6381.conf、内容如下:
include /myredis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
dir /myredis
2、启动3个Redis服务器
[root@localhost myredis]# redis-server /myredis/redis6379.conf
[root@localhost myredis]# redis-server /myredis/redis6380.conf
[root@localhost myredis]# redis-server /myredis/redis6381.conf
[root@localhost myredis]# ps -ef | grep redis (查看进程)
root 2325 1 0 14:49 ? 00:00:02 redis-server *:6379
root 2368 1 0 15:21 ? 00:00:00 redis-server *:6380
root 2374 1 0 15:21 ? 00:00:00 redis-server *:6381
3、连接三个redis服务器
[root@localhost myredis]# redis-cli -p 6379
[root@localhost myredis]# redis-cli -p 6380
[root@localhost myredis]# redis-cli -p 6381
启动之后是三台独立的master主机,没有主从关系。可以通过info replication查看
4、设定主从关系。设从不设主 (slaveof)
127.0.0.1:6380> slaveof 127.0.0.1 6379
127.0.0.1:6381> slaveof 127.0.0.1 6379
5、观察结果
1、master的数据会同步到所有slave
2、Master可以读可以写,slave只能读
1、一主二仆
问题1:切入点问题:slave1、slave2是从头开始复制还是从切入点开始复制?
master 中已经哪有数据了,后面从机认了主机,主机之前都是护具会不会同步到主机?
会的,从头复制。从机数据永远和主机相同。
问题2:从机是否可以写?set可否?
只读,不可以写。
问题3:主机宕机后(shutdown),从机是上位还是原地待命?
从机原地待命。
问题4:主机又回来了后,主机新增记录,从机还能否顺利复制?
可以顺利复制
问题5:其中一台从机down后情况如何?依照原有它能跟上大部队吗?
跟不上了,需要重新认主机(但是如果将slaveof语句写入配置文件,启动时执行)
2、薪火相传 (上下级)
问题1:主机的数据是否可以同步到所有级别的从机?
可以
问题2:主从机(中间的) 是否可以写?
不可以
问题3:主从机宕机,主机的数据是否可以同步到从机?
不可以。因为中间断线了。
3、反客为主
先搭建出一主二仆,再让master宕机,再让一个从机站出来。
指令: slaveof no one 将从机变为主机。
其他没有变为 master 的 slave 需要重新指定 master(重新拜大哥)
问题1:老大走了,新老大是自动上位还是手动上位
手动上位 slaveof no one指令
问题2:新老大上位,下级是否自动认可新老大
不会,手动
问题3:之前的主机又回来了,怎么办?是自动拜新老大,还是需要手动。
手动
既然三个全部是手动的。如果想全部是自动的,可以吗?请使用下面的哨兵模式。
4、哨兵模式(sentinel) (自动版的反客为主)
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
1. 自定义的/myredis目录下新建sentinel.conf文件,名字绝不能错
内容:
sentinel monitor mymaster 127.0.0.1 6379 1
其中mymaster为监控对象起的服务器名称, 1 为至少有多少个哨兵同意迁移的数量。
2. 单独的链接,启动哨兵
redis-sentinel /opt/myredis/sentinel.conf
sentinel.conf
3、重启一主二仆
(同反客为主,略)
4、master宕机,等待。一切自动搞定
当主机挂掉,自动从机选举中产生新的主机
复制延时:由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
故障恢复规则:
优先级在redis.conf中默认:replica-priority 100,值越小优先级越高
偏移量是指获得原主机数据最全的
每个redis实例启动后都会随机生成一个40位的runid(通过info server获取查看)
五、集群 ★
主从:纵向扩容。解决备份,减少主机压力
集群:水平扩容。减少主机缓存压力。
一个Redis主机缓存容量不够,如何进行扩容?
Redis 集群实现了对 Redis 的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
注意:多个主机做成集群和主从复制解决问题是不同的。
Redis集群中最少3个主机,而每个主机最少一个从机。所以一个Redis集群最少需要6个机器。
1、删除持久化数据 (rdb、aof)
批量删除:rm *.rdb
2、准备6个redis的配置文件:端口分别是6379 6380 6381 6389 6390 6391
配置文件中要配置集群的信息:
include /myredis/redis.conf
pidfile "/var/run/redis_6379.pid"
port 6379
dbfilename "dump6379.rdb"
dir "/myredis"
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
创建好一个后复制,%s/6379/6380 全局替换
[root@localhost myredis]# cp redis6379.conf redis6380.conf
[root@localhost myredis]# cp redis6379.conf redis6381.conf
[root@localhost myredis]# cp redis6379.conf redis6389.conf
[root@localhost myredis]# cp redis6379.conf redis6390.conf
[root@localhost myredis]# cp redis6379.conf redis6391.conf
3、启动6个Redis服务器
cluster:集群
4、将六个节点合成一个集群:
先切换到目录下 cd /opt/redis-6.2.1/src
输入指令:
redis-cli --cluster create --cluster-replicas 1 192.168.86.86:6379 192.168.86.86:6380 192.168.86.86:6381 192.168.86.86:6389 192.168.86.86:6390 192.168.86.86:6391
redis-cli --cluster create --cluster-replicas 1
192.168.86.86:6379 192.168.86.86:6380
192.168.86.86:6381 192.168.86.86:6389
192.168.86.86:6390 192.168.86.86:6391
此处不要用127.0.0.1, 请用修改为linux服务器真实IP地址
--replicas 1 采用最简单的方式配置集群,一台主机,一台从机,正好三组。
普通方式登录
可能直接进入读主机,存储数据时,会出现MOVED重定向操作。所以,应该以集群方式登录。
采用集群策略连接 加入 -c 参数
在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口。
redis-cli客户端提供了 –c 参数实现自动重定向。
如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。
不在一个slot下的键值,不能使用mget,mset等多键操作。
可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。
查看集群信息: cluster nodes
关于故障恢复
1. 主节点下线后,从节点自动升为主节点。注意:15秒超时
2. 主节点恢复后,主节点回来变成从机。
如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes ,那么 ,整个集群都挂掉
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,该插槽数据全都不能使用,也无法存储。
redis.conf中的参数 cluster-require-full-coverage
什么是slots (插槽)?
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个,
集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
集群中每个节点负责处理一部分插槽。 例如, 如果一个集群可以有主节点, 其中:
节点 A 负责处理 0 号至 5460 号插槽。
节点 B 负责处理 5461 号至 10922 号插槽。
节点 C 负责处理 10923 号至 16383 号插槽。
关旭集群配置信息文件、集群命令介绍:
include /myredis/redis.conf
pidfile "/var/run/redis_6379.pid"
port 6379
dbfilename "dump6379.rdb" //rdb文件名称
dir "/myredis" //rdb存放位置
cluster-enabled yes //启用集群
cluster-config-file nodes-6379.conf //集群配置文件
cluster-node-timeout 15000 //集群超时时间
redis-cli(客户端连接) --cluster create(创建集群) --cluster-replicas 1(集群复制,一个复制一份)
192.168.86.86:6379
192.168.86.86:6380
192.168.86.86:6381 192.168.86.86:6389
192.168.86.86:6390 192.168.86.86:6391
优缺点:
优点:
实现扩容
分摊压力
无中心配置相对简单
不足:
多键操作是不被支持的
多键的Redis事务是不被支持的。lua脚本不被支持
由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。
尚医通,还会搭建集群。