Redis
使用C语言开发的数据库。Redis是存在内存中的,读写速度飞快
使用场景:
- 缓存。一般都是分布式缓存
- 分布式锁
- 消息队列【很少】
1、Redis和Memcached的区别
- Redis 数据类型更加丰富。Redis支持K/V、list、set、zset、hash等;Memcached只支持K/V
- Redis支持持久化,将内存中的数据刷新到磁盘上;Memcached把数据全部存储在缓存中,内存使用完的话会直接报异常
- Redis支持集群模式;Memcached不支持
- Redis是单线程的多路IO服用模型(6.0后引入多线程);Memcached是多线程,非阻塞IO复用的网络模型
- Redis过期策略是多行删除 + 定期删除;Memcached过期策略是惰性删除
MySQL这类的数据库的QPS大概在1W左右(4核8G),使用Redis的话可达10W+,最高能到30W+(单机情况)
2、Redis的作用
这部分内容需要详细了解
- 分布式锁:
- 限流:!!!todo 详细了解
- 消息队列。
3、Redis常见数据结构
1、字符串
Redis使用的是 简单动态字符串(SDS) ,与C的原生字符串的区别?
常用命令:set、mset、get、mget、strlen、exists、decr、incr、setex......
2、list
list是链表,实现是双向链表,支持反向查找和遍历
常用命令:rpush、lpop、lpush、rpop、lrange、llen....
3、hash
类似于JDK1.8之前的HashMap,内部实现是数组 + 链表。
常用命令:hset、hmset、hexists、hget、hgetall、hkeys、hvals....
127.0.0.1:6379> hmset userInfoKey name "guide" description "dev" age "24"
OK
127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。
(integer) 1
127.0.0.1:6379> hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值
1) "name"
2) "guide"
3) "description"
4) "dev"
5) "age"
6) "24"
4、set
类似于Java中的hashSet。是无序集合,元素不会重复。
常用命令:sadd、spop、smembers、sismember、scard(查看key下集合长度)、sinterstore、sunion....
使用场景:存储粉丝列表、共同关注、共同粉丝.....
127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交集并存放在 mySet3 中
(integer) 1
5、sorted set
和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。
常用命令: zadd,zcard,zscore,zrange,zrevrange,zrem 等。
使用场景:礼物排行榜、在线用户列表等
127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重
(integer) 1
127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素
(integer) 2
6、bitmap
存储的是连续的二进制数字(0或者1),通过bitmap,只需要一个bit位来表示某个元素对应的值或者状态,key就是对应元素本身。
常用命令:setbit 、getbit 、bitcount、bitop
应用场景:用户签到情况、活跃用户情况、用户行为统计....
127.0.0.1:6379> setbit mykey 7 1
(integer) 0
127.0.0.1:6379> setbit mykey 7 0
(integer) 1
127.0.0.1:6379> getbit mykey 7
(integer) 0
127.0.0.1:6379> setbit mykey 6 1
(integer) 0
127.0.0.1:6379> setbit mykey 8 1
(integer) 0
# 通过 bitcount 统计被被设置为 1 的位的数量。
127.0.0.1:6379> bitcount mykey
(integer) 2
5、Redis线程模型
Redis单线程模型
基于Reactor模式设计开发了自己一套高效的事件处理模型。这套模型对应的就是Redis中的文件事件处理器(file event handler),是以单线程方式运行的
单线程如何监听大量的客户端链接?
通过IO多路复用程序实现监听大量客户端链接
6、Redis内存管理
设置过期时间:
127.0.0.1:6379> exp key 60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire) 字符串类型
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 56
字符串类型使用setex 命令设置过期时间,其他类型使用expire设置过期时间
如何实现数据过期?
1、存储结构:
过期字典。是一种Hash表,redis Db结构体中保存的expires字段是每个key的过期时间,value是long long类型,保存的是过期时间戳 (毫秒维度)
typedef struct redisDb {
...
dict *dict; //数据库键空间,保存着数据库中所有键值对
dict *expires // 过期字典,保存着键的过期时间
...
} redisDb;
2、删除策略
惰性删除:查询到key的时候判断key是不是已过期。
定期删除:每隔一段时间抽取一批key执行删除过期key操作。底层会限制删除操作的执行时长和频率
7、内存淘汰机制
6种淘汰策略:
1、volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
2、volatile-ttl:将要过期的数据淘汰
3、volatile-random:任意选择数据淘汰
4、allkeys-lru:内存不足时,键空间中,移除最近最少使用的key
5、allkeys-random:数据集(server.db[i].dict)中任意选择数据淘汰
6、no-eviction:内存不足时,新写入会报错
4.0之后新增的:
7、volatile-lfu:最近不经常使用的数据淘汰
8、allkeys-lfu:内存不足时,键空间中,移除最不经常使用的key
8、Redis持久化机制
如何保证机器挂掉后数据可以恢复?
两种方式支持持久化:
-
RDB,快照。创建快照存储在内存里面的数据在某个时间点上的副本。
Redis默认采用的持久化方式,在
redis.conf配置文件中的配置如下save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 -
AOF,追加文件。目前主流的都采用该方式。将命令写入到内存缓存
server.aof_buf中,根据appendsync配置决定何时将其同步到磁盘中的AOF文件中aof文件保存的地址通过dir参数设置,默认文件名是
appendonly.aofAOF有三种不同的持久化方式
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 appendfsync everysec #每秒钟同步一次,显式地将多个写命令同步到硬盘【默认使用该配置】 appendfsync no #让操作系统决定何时进行同步
9、Redis事务
Redis事务在开发过程中不建议使用
1、使用方式
> MULTI #开启事务
OK
> SET USER "Guide哥" #事务内命令
QUEUED
> GET USER #事务内命令
QUEUED
> EXEC #提交事务
1) OK
2) "Guide哥"
事务内执行的命令都会进入一个事务队列。队列存储方式是数组,保证了先进先出
2、WATCH机制
watch命令使用:watch key1 key2
watch是一个乐观锁,在exec命令执行之前,监视任意数量的key键,执行exec时判断监视的键是否被修改过?修改过则事务执行失败
- 事务的原子性:Redis事务中一串命令执行时其中一个失败,其他已经执行的不会回滚,所以不保证原子性
事务一致性:数据库在事务执行前是一致的,在事务执行后,无论执行成功与否,数据库也应该是一致性的
- Redis支持一致性
- 入队错误:错误的命令入队时可检查出来,事务中止
- 执行错误:命令执行错误时不影响之前和之后的命令执行
- 服务器停机:通过持久化机制停机后重启可保证数据恢复到之前的情况
- 事务隔离性:每个客户端都是单线程执行的,所以客户端内的事务都是串行的,保证了隔离性
- 事务持久性:由Redis本身的持久化机制保证,或者事务执行完 再执行一次
save命令
10、大key治理
一个key对应的value占用空间较大时,key就是大key。
String类型value>10KB
复合类型value元素超过5000个
如何发现:
# redis-cli -p 6379 --bigkeys #--bigkeys参数
如何治理?
设置过期时间
11、Redis常见问题
1、缓存穿透
大量不存在与缓存中的key,直接请求到了数据库上,数据库压力增加
如何避免?
- 缓存无效key。数据库查不到的key写到缓存中并设置过期时间。一般设置key的方式:
表名:列名:主键名:主键值 - 参数校验拦截
- 布隆过滤器。todo 详细了解
2、缓存雪崩
缓存在同一时间大面积的失效(大量key过期 / redis服务不可用),后面请求都落到了数据库层面,数据库压力增加
如何避免?
1、Redis服务不可用情况下
- 采用集群方式,避免单机不可用
- 限流
2、热点缓存失效情况下
- 设置失效时间时采用随机时间
3、缓存与数据库的一致性
缓存数据与数据库数据不一致
12、3种常见的缓存读写策略
1、Cache Aside Pattern(旁路缓存模式)
常用模式
读:
- 从 cache 中读取数据,读取到就直接返回
- cache中读取不到的话,就从 DB 中读取数据返回
- 再把数据放到 cache 中。
写:
- 先更新 DB
- 然后直接删除 cache
2、READ/WRITE Through Pattern读写穿透
此种方式重点是在缓存上
写(Write Through):
- 先查 cache,cache 中不存在,直接更新 DB。
- cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(同步更新 cache 和 DB)
读(Read Through):
- 从 cache 中读取数据,读取到就直接返回 。
- 读取不到的话,先从 DB 加载,写入到 cache 后返回响应。
3、Write Behind Pattern(异步缓存写入)
Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 则是只更新缓存,不直接更新 DB,而是改为异步批量的方式来更新 DB。
太危险了,cache更新完缓存服务异常时,DB数据无法更新
13、Redis内存碎片
为什么会产生内存碎片?
1、Redis 存储存储数据的时候向操作系统申请的内存空间可能会大于数据实际需要的存储空间。
2、频繁修改 Redis 中的数据也会产生内存碎片。Redis key被删除时不会立即归还空间给操作系统
info memory 命令即可查看 Redis 内存相关的信息
Redis 内存碎片率的计算公式:mem_fragmentation_ratio (内存碎片率)= used_memory_rss (操作系统实际分配给 Redis 的物理内存空间大小)/ used_memory(Redis 内存分配器为了存储数据实际申请使用的内存空间大小)
mem_fragmentation_ratio (内存碎片率)的值越大代表内存碎片率越严重