Redis 是一个开源的非关系型数据库 NoSQL 。适用于的场景有
-
由于数据都存储于内存,对高并发下海量数据的读写速度快
-
由于存储结构为简单的 K-V 对,所以可扩展性高
-
由于不满足事务的 ACID ,不适用需要事务支持的场景
- Redis 支持多数据类型:字符串、列表、集合、有序集合、哈希。由于 Redis 是单线程的,所以对数据的操作都是原子性的
- Redis 支持持久化。
- Redis 单线程 + 多路 IO 复用。单线程避免了线程切换和竞争所产生的消耗,多路 IO 复用能并发处理大量的 socket ,吞吐量高
String 的数据结构为简单动态字符串 (SDS),是可以修改的字符串,类似于 Java 的 ArrayList 。当字符串长度小于1M 时,扩容都是加倍现有的空间,如果超过 1M ,扩容时一次只会多扩 1M 的空间。String 类型是二进制安全的,可以包含任何数据,比如图片或者序列化的对象。一个 Redis 中字符串 value 最多可以是 512M。
列表 List 的单键映射多值。列表按照插入顺序排序,可以添加一个元素到列表的左边或者右边。它的底层实际是个双向链表。数据元素较少的情况下会使用一块连续的内存存储,是压缩链表 ziplist 。当数据元素较多的时候将每个 ziplist 用双向指针串起来,变成 quicklist 。
集合 Set 是 String 类型的无序的不可重复的集合,底层是一个 value 为 null 的哈希表,添加、删除、查找的复杂度都是 O(1) 。
有序集合 Zset 是一个没有重复元素的字符串集合。与 Set 不同之处是有序集合的每个元素都关联了一个评分( score ), score 被用来按照从低到高排序集合中的元素,所以集合元素是有序的。集合的元素是唯一的,但是评分可以重复 。
Zset 底层使用了两个数据结构 :
(1)哈希,关联元素 value 和评分 score 。
(2)跳跃表,根据 score 给元素 value 排序。
哈希 Hash 是一个 String 类型的 field 和 value 的哈希表,即 Map< key , Map
通过 key(用户 ID )+ field (属性标签)就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。当 field-value 长度较短且个数较少时,底层数据结构使用 ziplist ,否则使用 hashtable 。
Redis 的三种扩展数据类型 Bitmaps Bitmaps 是一个以位为单位的字符串, 每个字符只能存储 0 和 1 , 下标在 Bitmaps 中叫做偏移量。每个用户是否访问过存放在 Bitmaps 中, 访问过记做 1, 没有访问过记做 0 。Bitmaps 相比起 set 更适合存储活跃 key,节省很多的内存空间。
HyperLogLog 求独立访客数、独立 IP 数、搜索记录数等需要去重和计数的问题称为基数问题。在 Redis 里面,每个 HyperLogLog 的 key 只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
Geospatial 对 GEO 地理信息类型的支持。
Redis 持久化- RDB:在指定的时间间隔内将内存中的数据快照写入磁盘,数据恢复时是将快照直接读到内存里。Redis 会单独创建一个子进程(Fork)来进行持久化,会先将数据写入到 一个临时文件中,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何 IO 操作的,适合大规模数据的恢复,但不适合数据一致性要求高的数据恢复(最后一次持久化的数据可能会丢失)。
- AOF:以日志的形式来记录 Redis 执行过的所有写记录。Redis 重启根据日志的内容将所有写指令从前到后执行一次以完成数据的恢复工作。它适合数据一致性要求高的数据恢复,但不适合大规模数据的恢复(占用更多的磁盘空间,恢复速度慢)。
- RDB 更适合用于备份数据库,因为高并发下 AOF 不断变化不好备份,而且 AOF 可能由于潜在 bug 导致恢复失败。
问: Redis在持久化时fork出一个子进程,这时已经有两个进程了,怎么能说是单线程呢?
答:单线程主要是指 Redis 的网络 IO 和读写是由一个线程来完成的。而 Redis 的持久化、集群数据同步等则是依赖多个线程来执行的。
Redis 主从复制 主服务器 Master 数据更新后自动同步到 Slaver 从服务器中,Master 以写为主,Slave 以读为主,实现读写分离。
同时实现了容灾快速恢复,如果 Master 挂掉后,后面的 Slave 可以升为 Master,其它 Slave 不用做任何修改。
主从复制原理:Slave 启动成功后向 Master 发送数据同步消息。数据同步过程分为全量复制和增量复制。全量复制一般用于初次连接,Master 持久化自己的数据,然后通过 RDB 文件全量复制给 Slave 。增量复制则用于 Master 每次进行写操作后将新的写命令 增量复制到 Slave 。
Redis 哨兵和集群 实现Redis的高可用,主要有哨兵和集群两种方式。
- 哨兵模式能够后台监控 Master 是否故障,如果故障了根据选择选择条件自动将 Slave 转换为 Master 。条件首先选择优先级高的,优先级相同选择与 Master 数据量重复高的。
- Redis 集群通过分区来提供一定程度的可用性: 即使集群中有部分分区出现故障, 集群也可以继续使用。一个 Redis 集群包含 16384 个插槽 slot, 数据库中的每个 key 根据哈希函数映射到这些插槽内。
-
缓存穿透:key 对应的数据在 DB 中并不存在,每次针对此 key 的请求从缓存获取不到,数据库压力剧增,相当于缓存被穿透了。解决方案有
(1) 空值缓存:如果一个查询返回的数据为空,我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间,最长不超过五分钟
(2) 设置白名单:使用 bitmaps 类型定义一个可以访问的名单,每次访问和 bitmap 里面的 key 进行比较,不存在的 key 进行拦截,不允许访问。
(3) 使用布隆过滤器。
-
缓存击穿:Redis 中某个 key 过期了,在过期时间内大量并发请求过来,可能会瞬间把后端 DB 压垮。解决方案有
(1)预先设置热点 key :提前把热门数据存入到 Redis 里面,并增加这些 key 的过期时间
(2)使用分布式锁。
-
缓存雪崩:在极少时间段内大量的 key 失效过期,在过期时间内大量并发请求过来,可能会瞬间让后端 DB 崩溃
(1) 构建多级缓存架构:nginx 缓存 + Redis 缓存 +其他缓存
(2) 将缓存失效时间分散开:在原有失效时间基础上增加一个随机值,使每一个缓存的过期时间的重复率就会降低。
set key value nx ex seconds 将加锁、过期命令放到一起,变成原子操作了,避免死锁
上面操作仍有问题。如下图显示,进程 A 在任务没有执行完毕时,锁到期释放了。等进程 A 的任务执行结束后,它依然会尝试释放锁。但是,它的锁早已自动释放过了,此时 A 释放的可能是其他线程的锁。
解决方案:在加锁时为每个锁生成一个标识。解锁时判断是自己持有的锁才能释放,否则不能释放。 采用 Lua 脚本将判断和释放两个命令放在一起,因为整个 Lua 脚本的执行是原子的。
set key random-value nx ex seconds # 加锁
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) # 解锁
else return 0 end
Redis 过期清理策略(Redis保证热点数据)
- volatile-lru:移除最近最少使用的设置了过期时间的 key(LRU算法)
- allkeys-lru:移除最近最少使用的 key
- volatile-random:移除随机的设置了过期时间的 key
- allkeys-random:移除随机的 key
- volatile-ttl:移除最近要过期的设置了过期时间的 key
- noeviction:不移除 key
-
单独的隔离操作
事务中的所有命令都会按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
-
没有隔离级别
因为事务提交前任何指令都不会被实际执行
-
不保证原子性
提交后的事务中如果有一条命令执行失败,其后的命令仍然会被执行,不会回滚
- multi :输入的命令依次进入命令队列中,但不会执行(类似 MySQL 中的 begin 或 start transaction)
- exec:将命令队列中的命令依次执行(类似于 MySQL 中的 commit)
- discard:放弃组队进行回滚(类似于 MySQL 中的 rollback)
如果组队时某个命令出现了报告错误,执行时整个的所有队列都会回滚。如果执行阶段某个命令出现了报告错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
Redis 的 watch 命令 Redis 的 watch 命令是一种乐观锁的机制。通过 watch 命令,可以对 key 进行监视。如果执行事务之前 key 发生了变化,则放弃执行事务。
Centos7 中 Redis 启动+- 启动 redis.conf
redis-server /etc/redis.conf
- 进入 /usr/local/bin 目录下
cd /usr/local/bin
- 开启 Redis
redis-cli -pRedis 常用命令
| key 命令 | 功能 |
|---|---|
| keys * | 查看当前库所有 key |
| exists key | 判断某个 key 是否存在 |
| type key | 查看你的 key 是什么类型 |
| del key | 删除指定的 key 数据 |
| unlink key | 仅将 key 从元数据中删除,真正的删除异步操作 |
| expire key 10 | 为给定的 key 设置 10 秒钟过期时间 |
| ttl key | 查看还有多少秒过期,-1表示永不过期,-2表示已过期 |
| dbsize | 查看当前数据库的 key 的数量 |
| flushdb | 清空当前库 |
| String 命令 | 功能 |
|---|---|
| set | 设置 K-V 对,key 重复的元素会进行覆盖 |
| append | 将给定的元素追加到原来元素的末尾 |
| get | 查询 key 对应的元素 |
| strlen | 获得 key 的长度 |
| setnx | 只有 key 不存在时才能设置元素 |
| mset | 同时设置多个元素 |
| msetnx | 同时设置多个 K-V 对,当所有给定 key 都不存在才能设置(原子性) |
| setex | 设置元素的同时设置过期时间 |
| mget | 同时获取多个元素 |
| incr / decr | 将纯数字值的 key 中储存的数字值增1 / 减1 |
| incrby / decrby | 将 key 中储存的数字值按步长增减 |
| List 命令 | 功能 |
|---|---|
| lpush/rpush | 从左边/右边插入一个或多个元素 |
| lpop/rpop | 从左边/右边弹出一个元素 |
| rpoplpush | 从 key1 列表右边弹出一个元素插到 key2 列表左边 |
| lindex | 按照索引下标获得对应的元素 |
| lrange | 0 左边第一个,-1 右边第一个,表示获取 key 列表中所有元素 |
| lset | 将列表 key 下标为 index 的元素替换成 value |
| Set 命令 | 功能 |
|---|---|
| sadd | 添加多个元素到 set 中 |
| smembers | 取出集合 key 中的所有元素 |
| sismember | 判断集合 key 是否为含有该元素 |
| scard | 返回集合 key 中元素个数 |
| srem | .删除集合 key 中的某个元素 |
| spop | 随机从集合 key 中弹出一个值 |
| sinter | 返回两个集合的交集元素 |
| sunion | 返回两个集合的并集元素 |
| sdiff | 返回集合 key1 有的,集合 key2 没有的元素 |
| Zset 命令 | 功能 |
|---|---|
| zadd | 添加多个元素及其 score 到 Zset 中 |
| zrange | 返回全部元素,加上 withscores 可以让 score 一起返回 |
| zrangebyscore key min max | 按 score 值从小到大返回所有 score 介于 min 和 max 之间的元素 |
| zrevrangebyscore key max min | 按 score 值从大到小返回所有 score 介于 max 和 min 之间的元素 |
| zincrby | 为元素的 score 加上增量 |
| zrem | 删除指定值的元素 |
| zcount | 统计 score 介于 min 和 max 之间的元素个数 |
| zrank | 返回元素在集合中的排名,从 0 开始 |
| Hash 命令 | 功能 |
|---|---|
| hset | 添加元素到 Hash 中 |
| hget | 取出哈希表中的 field 的 value |
| hmset | 批量添加添加元素到 Hash 中 |
| hexists | 查看哈希 key 中 field 是否存在 |
| hkeys | 列出 key 的所有域 field |
| hvals | 列出 key 的所有值 value |
| hincrby | 为哈希中的域 field 的 value 加上增量 |
| hsetnx | 当且仅当 field 不存在时设置 field 的 value |



