前言
Redis是一款开源的、高性能的键-值存储。它常被称作是一款数据结构服务器。redis的键值开源包括字符串(String)、哈希(hash)、列表(list)、集合(set)和有序集合(zset)等基本数据类型。
为了获得优异的性能,redis采用了内存中数据集的方式。同时redis支持数据的持久化,可以每隔一段时间将数据转存到磁盘上(RDB),也可以在日志尾部追加每一条操作命令(AOF)。
目录
安装
基本数据结构
持久化原理
持久化配置
redis实现分布式锁
安装
1、Docker方式 # 拉取 redis 镜像 > docker pull redis # 运行 redis 容器 > docker run --name myredis -d -p6379:6379 redis # 执行容器中的 redis-cli,可以直接使用命令行操作 redis > docker exec -it myredis redis-cli... 2、直接安装方式 # mac > brew install redis # ubuntu > apt-get install redis # redhat > yum install redis # 运行客户端 > redis-cli
基本数据结构
String
> set name allen OK > get name “allen” 获取字符串的长度 > strlen name 5 获取子串 > getrange name 1 3 lle 追加子串 > append name cool (integer) 9 > get name "allencool" > exists name (integer) 1 > del name (integer) 1 > get name (nil) >mset name1 boy name2 girl name3 unknown >mget name1 name2 name3 "boy" "girl" "unknown" # 5s 后过期,等价于 set+expire >setex name 5 codehole //等5秒 >get name “codehole“ >get name (nil) # 如果 name 不存在就执行 set 创建 >setnx name codehole (integer) 1 > get name "codehole"
List
队列/堆栈 链表可以从表头和表尾追加和移除元素,结合使用rpush/rpop/lpush/lpop四条指令,可以将链表作为队列或堆栈使用,左向右向进行都可以 # 右进左出 > rpush name go (integer) 1 > rpush ireader java python (integer) 3 > lpop name "go" > lpop name "java" > lpop name "python" # 左进右出 > lpush name go java python (integer) 3 > rpop name "go" ...
Hash
#可以通过hget定位具体key对应的value. #可以通过hmget获取多个key对应的value. #可以使用hgetall获取所有的键值对. #可以使用hkeys和hvals分别获取所有的key列表和value列表。 #这些操作和Java语言的Map接口是类似的。 >hset name go fast (integer) 1 > hmset name java fast python slow OK > hmset name go fast java fast python slow OK > hget name go "fast" > hmget name go python 1) "fast" 2) "slow" > hgetall name 1) "go" 2) "fast" 3) "java" 4) "fast" 5) "python" 6) "slow" > hkeys name 1) "go" 2) "java" 3) "python" > hvals name 1) "fast" 2) "fast" 3) "slow"
Set
set 结构可以用来存储活动中奖的用户 ID,因为有去重功能,可以保证同一个用户不会中奖两次。 > sadd books python (integer) 1 > sadd books python # 重复 (integer) 0 > sadd books java golang (integer) 2 > smembers books # 注意顺序,和插入的并不一致, 因为 set 是无序的 1) "java" 2) "python" 3) "golang“ > scard books # 获取长度相当于 count() (integer) 3
Zset
> zadd books 9.0 "think in java" (integer) 1 > zadd books 8.9 "java concurrency" (integer) 1 > zadd books 8.6 "java cookbook" (integer) 1 > zrange books 0 -1 # 按 score 排序列出,以 0 表示有序集第一个成员以,-1 表 示最后一个成员 1) "java cookbook" 2) "java concurrency" 3) "think in java" > zrevrange books 0 -1 # 按 score 逆序列出,参数区间为排名范围 1) "think in java" 2) "java concurrency" 3) "java cookbook"...
过期时间
Redis 所有的数据结构都可以设置过期时间,时间到了,Redis 会自动删除相应的对象。需要注意的是过期是以对象为单位,比如一个 hash 结构的过期是整个 hash 对象的过期,而不是其中的某个子 key。 还有一个需要特别注意的地方是如果一个字符串已经设置了过期时间,然后你调用了 set 方法修改了它,它的过期时间会消失。
127.0.0.1:6379> set name allen OK 127.0.0.1:6379> expire name 600 (integer) 1 127.0.0.1:6379> ttl name (integer) 597 127.0.0.1:6379> set name allen OK 127.0.0.1:6379> ttl name (integer) -1
持久化原理
RDB和AOF两种持久化机制的介绍
RDB持久化机制,对redis中的数据执行周期性的持久化
AOF机制对每条写入命令作为日志,以append-only的模式写入一个日志文件中,在redis重启的时候,可以通过回放AOF日志中的写入指令来重新构建整个数据集
如果我们想要redis仅仅作为纯内存的缓存来用,那么可以禁止RDB和AOF所有的持久化机制
通过RDB或AOF,都可以将redis内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云,云服务
如果redis挂了,服务器上的内存和磁盘上的数据都丢了,可以从云服务上拷贝回来之前的数据,放到指定的目录中,然后重新启动redis,redis就会自动根据持久化数据文件中的数据,去恢复内存中的数据,继续对外提供服务
如果同时使用RDB和AOF两种持久化机制,那么在redis重启的时候,会使用AOF来重新构建数据,因为AOF中的数据更加完整
RDB持久化机制的优点、缺点
RDB持久化机制的优点
(1)RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中redis的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说Amazon的S3云服务上去,在国内可以是阿里云的ODPS分布式存储上,以预定好的备份策略来定期备份redis中的数据(2)RDB对redis对外提供的读写服务,影响非常小,可以让redis保持高性能,因为redis主进程只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可
(3)相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复redis进程,更加快速
RDB持久化机制的缺点
(1)如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,那么会丢失最近5分钟的数据(2)RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒
AOF持久化机制的优点、缺点
AOF持久化机制的优点
(1)AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据(2)AOF日志文件以append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复
(3)AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在rewrite log的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可。
(4)AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据。
AOF持久化机制的缺点
(1)对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大(2)AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的
(3)以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似AOF这种较为复杂的基于命令日志/merge/回放的方式,比基于RDB每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有bug。不过AOF就是为了避免rewrite过程导致的bug,因此每次rewrite并不是基于旧的指令日志进行merge的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。
RDB和AOF到底该如何选择
(1)不要仅仅使用RDB,因为那样会导致你丢失很多数据
(2)也不要仅仅使用AOF,因为那样有两个问题,第一,你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快; 第二,RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug
(3)综合使用AOF和RDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复的第一选择; 用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复
持久化配置
配置RDB持久化机制
1、如何配置RDB持久化机制
redis.conf文件,也就是/etc/redis/6379.conf,去配置持久化
save 60 1000
每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的dump.rdb文件,就是当前redis内存中完整的数据快照,这个操作也被称之为snapshotting,快照。也可以手动调用save或者bgsave命令,同步或异步执行rdb快照生成
save可以设置多个,就是多个snapshotting检查点,每到一个检查点,就会去check一下,是否有指定的key数量发生了变更,如果有,就生成一个新的dump.rdb文件
2、RDB持久化机制的工作流程
(1)redis根据配置自己尝试去生成rdb快照文件
(2)fork一个子进程出来
(3)子进程尝试将数据dump到临时的rdb快照文件中
(4)完成rdb快照文件的生成之后,就替换之前的旧的快照文件
dump.rdb,每次生成一个新的快照,都会覆盖之前的老快照
配置AOF持久化机制
1、AOF持久化的配置AOF持久化,默认是关闭的,默认是打开RDB持久化。appendonly yes,可以打开AOF持久化机制。
2、三种策略可以选择,
(1)一种是每次写入一条数据就执行一次fsync;
(2)一种是每隔一秒执行一次fsync
(3)一种是不主动执行fsyncalways: 每次写入一条数据,立即将这个数据对应的写日志fsync到磁盘上去,性能非常非常差,吞吐量很低
3、 redis每次接收到一条写命令,就会写入日志文件中,当然是先写入os cache的,然后每隔一定时间再fsync一下而且即使AOF和RDB都开启了,redis重启的时候,也是优先通过AOF进行数据恢复的。
4、AOF破损文件的修复如果redis在append数据到AOF文件时,机器宕机了,可能会导致AOF文件破损用redis-check-aof --fix命令来修复破损的AOF文件。
AOF和RDB同时工作
(1)如果RDB在执行snapshotting操作,那么redis不会执行AOF rewrite; 如果redis再执行AOF rewrite,那么就不会执行RDB snapshotting
(2)如果RDB在执行snapshotting,此时用户执行BGREWRITEAOF命令,那么等RDB快照生成之后,才会去执行AOF rewrite
(3)同时有RDB snapshot文件和AOF日志文件,那么redis重启的时候,会优先使用AOF进行数据恢复,因为其中的日志更完整
redis实现分布式锁
分布式锁本质上要实现的目标就是在 Redis 里面占一个“茅坑”,当别的进程也要来占时,发现已经有人蹲在那里了,就只好放弃或者稍后再试。 占坑一般是使用 setnx(set if not exists) 指令,只允许被一个客户端占坑。先来先占, 用完了,再调用 del 指令释放茅坑。 >setnx lock:codehole true ... do something critical ... > del lock:codehole 但是有个问题,如果逻辑执行到中间出现异常了,可能会导致 del 指令没有被调用,这样就会陷入死锁,锁永远得不到释放。 于是我们在拿到锁之后,再给锁加上一个过期时间,比如 5s,这样即使中间出现异常也可以保证 5 秒之后锁会自动释放。 但是以上逻辑还有问题。如果在 setnx 和 expire 之间服务器进程突然挂掉了,可能是因为机器掉电或者是被人为杀掉的,就会导致 expire 得不到执行,也会造成死锁。这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。如果这两条指令可以一起执行就不会出现问题。 >setnx lock:codehole true expire lock:codehole 5 ... do something critical ... > del lock:codehole 为了解决这个问题,Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行,彻底解决了分布式锁的乱象。。 > set lock true ex 5 nx OK ... do something critical ... > del lock