redis为什么这么快
redis是基于内存的采用的是单进程单线程模型的KV数据库,使用多路I/O复用模型。
redis的线程模型
内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以redis是单线程的。采用IO多路复用机制同时监听多个Socket,根据Socket上的事件来选择对应的事件处理器进行处理。
redis数据结构
基本:string(缓存,计数器,共享session),list(播放列表,文章列表,消息队列)hash(结构化数据),set(共同好友),sortedset(排行榜)
高级:bitmap,HyperLogLog,geo,pub/sub
常用命令
key相关
keys *
dbsize
type key
exists key [key ...]
del key [key ...]
move key db
ttl key(秒) / pttl key(毫秒)
expire key seconds / pexpire key milliseconds
persist key
rename key newkey
string相关
set key value [EX seconds] [PX milliseconds] [NX|XX]
get key
incr key (数字) / incrby / decr / decrby
mset key value [key value ...] # mset java1 1 java2 2 java3 3
mget key [key ...] # mget java1 java2
strlen key # 获取值长度
append key value # 追加 原:test:aaa append key test bbb 新:test:aaabbb
getrange key start end # 获取部分字符
set相关
sadd key member [member ...] # 存储值 sadd test java php c++ go python java
smembers key # 获取值 smembers test (java php c++ go python)
srandmember key count # 随机获取值 srandmember test 3
sismember key member # 判断值是否存在 sismember test go
scard key # 获取集合元素个数 (5个)
srem key member [member ...] # 删除集合元素
spop key [count] # 弹出元素
sortedset相关
zadd key score member # 存储值 zadd test 100 a 200 b
zscore key member # 获取元素分数 zscore test b (200)
zrange key start stop [WITHSCORES] # 获取排名范围排名 zrange test 0 -1 WITHSCORES (a,100,b,200)
zrangebyscore key min max [WITHSCORES] [LIMIT offset count] # 获取指定分数范围排名
# zrangebyscore test 100 150 WITHSCORES limit 0 1 (a,100)
zcard key # 获取集合元素个数 (2)
zincrby key increment member # 增加指定元素分数 zincrby test 300 b (500)
zcount key min max # 获取指定范围分数个数
zrem key member # 删除指定元素
zrank key member # 获取排名,从0开始
list相关
lpush key value [value ...] # 左端存值
rpush key value [value ...] # 右端存值
lset key index value # 索引存值 (会覆盖原值)
lrange key start stop # 获取列表元素
lpop key # 左端弹出
rpop key # 右端弹出
llen key # 长度
lindex key index # 索引获取值(index为负时代表从右边-1开始索引)
lrem key count value # 删除元素
ltrim key start stop # 范围删除(只保留start到stop的值)
hash相关
hset key field value [field value ...] # 存放键值 hset user name wghdr age 26
hsetnx key field value # key不存在时 hset users name wghdrr
hget key field # 获取字段值 hget user name
hmget key field [field ...] # 获取多个值 hmget user name age
hgetall key # 获取所有
hkeys key # 获取所有key的字段
hvals key # 获取所有值
hexists key field # 判断字段是否存在
hlen key # 获取字段数量
hdel key field [field ...] # 删除key的字段 hdel user age
线上keys命令
keys命令会导致线程阻塞一会,直到命令执行完成后,业务才能恢复。
使用scan命令,可以不用阻塞主线程,并支持游标按批次迭代返回数据。返回的数据有可能重复,需要在业务层按需要去重。
scan 0 match wp_wp:posts* count 1000
redis持久化
-
RDB:对redis中的数据执行周期性的持久化。
- 优点:
RDB对redis的性能影响非常小,是因为在同步数据的时候他只是fork了一个子进程去做持久化的,更适合做冷备,恢复速度快。 - 缺点:
RDB都是快照文件,都是默认五分钟甚至更久的时间才会生成一次,可能会丢失这5分钟的数据。
在生成RDB文件时,如果文件很大,会会间断性暂停服务。
- 优点:
-
AOF:对每条写入命令作为日志,以append-only追加的模式写入一个日志文件中。
- 优点:
追加模式减少了磁盘开销,AOF是一秒一次去通过一个后台的线程fsync操作,最多丢一秒的数据。AOF更适合做热备。 - 缺点:
一样的数据,AOF文件比RDB还要大。
AOF开启后,Redis支持写的QPS会比RDB支持写的要低。
- 优点:
-
选择
两种机制全部开启的时候,Redis在重启的时候会默认使用AOF去重新构建数据,因为AOF的数据是比RDB更完整的。
redis集群模式
启动多个redis-server实例,指定一个或者多个为master(奇数个),其他的为slave。比如一主两从,三主三从。集群启动后,数据写入到master上,slave只读,数据自动同步到slave上。缺点时出现故障无法自动恢复。
redis哨兵模式
哨兵会实时监控master和slave的状态,当master出现故障,会选择一个slave切换为master,并修改其他slave的配置文件指向新的master,还可以发送消息通知管理员。
哨兵模式故障转移流程
- 当一个sentinel发现master下线,它会将下线的master确认为主观下线。
- 当“法定个数”(quorum)sentinel确认该master节点下线,那么sentinel会将其确认为客观下线。
- sentinel发起选举,选举出一个sentinel作为leader,由它去进行故障转移,将原来连接已客观下线master最优的一个slave提升为新master。老的master如果重新上线,它将被降级为slave。
sentinel节点个数最好 >= 3,且为奇数,quorum为sentinel节点个数的一半+1。比如sentinel为3,quorum为2。这样不会出现两个票数一样的 leader同时被选上。
哨兵脑裂
当master断开连接,sentinel选择了一个slave为新的master,这时就出现了2个master。但是client仍然往老的master写入数据,这部分数据就会丢失。
解决
# master 须要有至少 x 个副本连接。
min-slaves-to-write x
# 数据复制和同步的延迟不能超过 x 秒。
min-slaves-max-lag x
redis的同步机制
redis可以使用主从同步,从从同步。第一次同步时,master做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到slave,slave接收完成后将RDB镜像写到磁盘,然后加载到内存。加载完成后,再通知master将期间修改的操作记录同步到slave进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog。
redis key的过期策略
是有定期删除,惰性删除,内存淘汰三种。
- 定期删除:默认100s就随机抽一些设置了过期时间的key,去检查是否过期,过期了就删了。
- 惰性删除:我等你来查询了我看看你过期没,过期就删了还不给你返回,没过期就直接返回。
- 内存淘汰:定期没删,也没有查询这个key的情况。
- noeviction:当内存达到限制时返回错误。
- allkeys-lru: 尝试回收最少使用的键(LRU).
- volatile-lru: 尝试回收最少使用的键(LRU),但仅限于过期的键.
- allkeys-random: 回收随机的键.
- volatile-random: 回收随机的键,但仅限于过期的键。
- volatile-ttl: 回收过期的键,并且优先回收存活时间(TTL)较短的键。
redis+db读写模式
读的时候先读缓存,缓存没有的话,就去读数据库,读取后写入到缓存,再返回给客户端。
更新的时候,先更新数据库,再删除缓存。
为什么是删除缓存
因为数据可能更新的很频繁,但是缓存访问的不频繁,有大量的冷数据,用到缓存时才去对缓存做修改。
数据不一致怎么处理
缓存不一致产生的原因一般是主动更新失败,例如更新db后,更新redis因为网络原因请求超时;或者是异步更新失败导致。
解决的办法是,如果服务对耗时不是特别敏感可以增加重试;如果服务对耗时敏感可以通过异步补偿任务来处理失败的更新,或者短期的数据不一致不会影响业务,那么只要下次更新时可以成功,能保证最终一致性就可以。
缓存穿透,缓存击穿,缓存雪崩
-
缓存穿透:用户不断的用缓存和db中不存在的数据发起请求,导致db压力过大,严重会导致db挂掉。
- 解决:
1.增加参数校验
2.缓存一个空对象,对不存在的请求直接返回空对象,这样缓存中可能有大量空对象。
3.在接入或者网关层增加对频繁访问ip的限制
4.增加布隆过滤器。原理就是判断请求的key是否存在,如果存在就去db查询刷新缓存再返回结果,如果key不存在,那么数据一定不存在,直接返回为空。
- 解决:
-
缓存击穿:有一个key非常热点一直有大量的请求访问,当这个key失效的瞬间,大量针对这个数据的请求会击穿缓存,直接访问db。
- 解决:
1.设置热点key用不过期。
2.使用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到db,减小db压力。
- 解决:
-
缓存雪崩:大量缓存失效或者缓存挂了,直接请求到db。
- 解决:
1.设置热点key随机过期,避免统一时间大量key过期。
2.使用集群部署,将热点key分布到多个实例上
3.设置热点key用不过期。
- 解决:
redis和memcached的区别
redis支持更多的数据结构
redis支持集群模式,memcached不支持,需要依靠客户端去往分片中写入数据。
memcached可以使用多核,在存储大数据上性能更高。
在集群模式下,redis的Key是如何寻址的
hash,一致性hash,hash slot算法。
如何保证redis中的数据都是热点数据
可以使用redis的allkeys-lru淘汰策略来实现,从redis的数据中挑选最近最少使用的数据删除,这样频繁被访问的数据就可以保留下来了。