redis支持的数据类型
string, hash, list, set, zset
redis持久化
1.RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储。
手动备份
save命令使用主线程并且阻塞其他请求。
bgsave命令主线程fork出子进程来进行数据快照,但是该快照只会备份出fork时的数据,在备份过程中的新数据不会备份。
fork子进程的原因:(1)Redis RDB持久化机制会阻塞主进程,这样主进程就无法响应客户端请求。(2)我们知道Redis对客户端响应请求的工作模型是单进程和单线程的,如果在主进程内启动一个线程,这样会造成对数据的竞争条件,为了避免使用锁降低性能。
自动备份
save seconds times 如果seconds秒内发生了times次变化则备份一次
2.AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
配置为:appendonly yes/no(是否开启aof)
appendfsync everysec/always/no(每秒/每次数据更新/不主动更新)
因为AOF采用追加的方式,所以文件会越来越大,针对这个问题,新增了重写机制,就是当日志文件大到一定程度的时候,会fork出一条新进程来遍历进程内存中的数据,每条记录对应一条set语句,写到临时文件中,然后再替换到旧的日志文件。
配置为:auto-aof-rewrite-percentage 10(如果超过上次aof文件的百分之10则重写)
auto-aof-rewrite-min-size 64mb(如果超过上次aof文件64mb则重写)
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复,因为丢数据的风险小
rdb优势:1.恢复速度快(都是数据)2.父进程只需要fork无需任何磁盘操作 3.方便备份
rdb劣势:1.会丢点数据2.如果数据集比较庞大,fork时间会比较长
aof优势:数据丢失风险小
aof劣势:1.aof文件大2.fsync策略可能导致耗时长
memcache和redis的区别
1.memcache不可以持久化,redis可以持久化
2.memcache仅支持string,redis支持多种类型
3.使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
4.redis的速度比memcached快很多(因为redis是单线程避免了上下文切换,且使用非阻塞I/O多路复用机制)
redis的过期策略和内存淘汰机制
redis采用的是定期删除+惰性删除策略。
如果使用定时器过期自动删除的话,极其消耗cpu资源。所以使用每隔100MS随机抽取一部分key进行检查,如果过期则删除(定期删除),并且在用户get的时候判断是否过期,如果过期则删除并返回空(惰性删除)
但是如果上述两种情况都没有发生,会导致内存持续增高,所以需要内存淘汰机制:
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据,新写入操作会报错
ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致!
在redis.conf中配置:例如,maxmemory-policy volatile-lru
redis分布式锁
在应用服务器外搭建一个存储服务器,存储锁信息,这时候我们很容易就想到了Redis。首先我们要搭建一个Redis服务器,用Redis服务器来存储锁信息。
要注意:
1、锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁;
2、同一时刻只能有一个线程获取到锁。
获取锁(代码实现)
def acquire_lock(lock_name, acquire_time=10, time_out=10):
"""获取一个分布式锁"""
identifier = str(uuid.uuid4())
end = time.time() + acquire_time
lock = "string:lock:" + lock_name
while time.time() < end:
if redis_client.setnx(lock, identifier):
# 给锁设置超时时间, 防止进程崩溃导致其他进程无法获取锁
redis_client.expire(lock, time_out)
return identifier
elif not redis_client.ttl(lock):
# 如果没有设置锁的过期时间,则重新设置过期时间
redis_client.expire(lock, time_out)
time.sleep(0.001)
return False
释放锁
def release_lock(lock_name, identifier):
"""通用的锁释放函数"""
lock = "string:lock:" + lock_name
pip = redis_client.pipeline(True)
while True:
try:
pip.watch(lock)
lock_value = redis_client.get(lock)
if not lock_value:
return True
if lock_value.decode() == identifier:
pip.multi()
pip.delete(lock)
pip.execute()
return True
pip.unwatch()
break
except redis.excetions.WacthcError:
pass
return False
redis单线程的好处、弊端
1)绝大部分请求是纯粹的内存操作(非常快速)
2)采用单线程,避免了不必要的上下文切换和竞争条
3) 不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
4)使用多路复用IO,单线程使用select epoll高效的处理多个连接请求
弊端:
无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来完善;
redis发布、订阅
subscribe 频道名(订阅)
publish 频道名 内容(发布)
redis架构
单机
主从(master/slave)
配置:从库配置slaveof 主库ip 端口
slave只能读不能写
哨兵(sentinel)
配置:
port 26379
daemonize yes
logfile "26379.log"
dir "./"
sentinel monitor mymaster 192.168.250.132 7000 2(主节点名,ip,端口,几个监听失效可以判断其失效)
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 15000
sentinel auth-pass mymaster 123
bind 192.168.250.132 127.0.0.1
多个哨兵都仅需要配置master节点的信息,可以自动获得slave信息以及其他sentinel信息
集群cluster
Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。
redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配)
Redis实现消息队列
最好使用brpop,相比rpop而言,brpop是阻塞式的。
Redis与数据库的数据一致性问题
一般缓存未命中会从数据库中查找并放入缓存,更新数据时也会更新缓存。
这时更新数据时先删缓存再更新数据库还是先更新数据库再更新缓存都会有数据不一致的问题。
优化策略:
1.双删:在更新数据库前后都删除缓存
2.设置过期时间
3.实现binlog异步更新缓存
Redis 的并发竞争 Key问题解决
1.分布式锁+时间戳:锁的value为时间戳,必须要大于时间戳才能set
2.消息队列:将并行的set改为串行
Redis如何保证都是热点数据
设置redis最大内存,配置文件中的maxmemory参数
设置淘汰策略为allkeys-lru或volatile-lru
主从同步原理
当一个从库向主库发送slaveof命令后:
首先全量同步:
1.slave服务器向master发送psync命令,告诉master我要同步数据了
2.master接收到psync命令后会进行BGSAVE命令生成RDB文件快照并发送给slave
3.slave将RDB文件载入数据库
4.master将缓冲区的写命令发送给slave
5.slave执行写命令
增量同步:
master每执行一个写命令就发送给slave,slave执行该命令
部分同步:老版本的redis在同步中断重连后,slave会重新进行全量同步。在新版本则不需要全量同步,因为依赖于部分同步,部分同步的实现依赖于在master服务器内存中给每个slave服务器维护了一份同步日志和同步标识,每个slave服务器在跟master服务器进行同步时都会携带自己的同步标识和上次同步的最后位置。如果slave进行重连时,master能够在同步日志中找到标识则直接发送之后的写命令,如果找不到则重新进行全量同步。