1. Redis简介
RedisRedis:REmote DIctionary Server(远程字典服务)。
Redis是一个速度非常快的非关系型数据库,它可以存储键与5种不同类型值的之间映射,可以将存储在内存中的键值对数据持久化到硬盘,可以使用复制特性来扩展读性能,还可以使用客户端分片来扩展写性能。
Redis事件处理器:
文件事件: 多个socket、IO多路复用程序、文件事件分派器、事件处理器(命令请求处理器、命令回复处理器、连接应答处理器,等等)
时间事件: 周期性事件 定时事件 (持久化、清理过期键、复制、节点ping等)
2. Redis单线程为什么快?
- 纯内存操作
- 核心是基于非阻塞的IO多路复用机制
- 单线程避免了多线程的频繁上下文切换问题
3. Redis 和 Memcached 区别
- redis支持更丰富的数据类型
- Redis支持数据的持久化
- 集群模式
- Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型
4. Redis持久化
Redis提供两种持久化方法:快照(snapshotting RDB)、只追加文件(append only file AOF)。前者可以将某一时刻所有的数据都写入硬盘,后者会在执行写命令时,将被执行的写命令复制到硬盘里。可根据业务场景单独使用,也可一同使用。
1. 快照持久化
- bgsave命令:fork子进程
- save命令:阻塞其他命令创建快照
- shutdown命令:save操作
- 主从:bgsave操作
- 配置文件(redis.conf)
save 60 10000 60秒内 有10000次写入
2. AOF持久化
- 配置文件(redis.conf)
appendonly yes
appendfsync everysec
- always(每个写命令都同步到硬盘)
- everysec(每秒同步一次)
- no(操作系统决定同步频率)
-
AOF重写
在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作
AOF重写不需要对现有的AOF文件进行任何的读取、分析。AOF重写是通过读取服务器当前数据库的数据来实现的!
RDB | AOF |
---|---|
丢失上一次save之后数据 | 丢失一秒数据(甚至不丢失数据) |
持久化key-value二进制文件 恢复快 | 持久化所有写命令 恢复慢 |
save bgsave | bgrewriteaof |
save时忽略过期键 | write时不忽略过期键 |
若RDB/AOF同时使用,以AOF文件为准,RDB文件作废。恢复数据时阻塞所有命令
5. Redis事务
命令:multi、exec、discard、watch、unwatch
Redis事务是以不可中断的方式依次执行一组缓存命令的队列将结果保存到内存中。具有事务的一致性、隔离性、原子性(不支持回滚),因为是存放在内存中故不保证持久性。
watch命令相当于乐观锁,将事务改变为有条件提交,watch监控key必须在开启事务之前,当乐观锁校验失败,将丢弃所有队列中的命令退出事务。
6. Redis集群
Redis集群的三种模式:主从复制(master/slave)、哨兵模式(sentinel)、集群模式(cluster)
主从复制过程:
复制过程sentinel故障切换过程:
- 监控: 主观下线:Ping命令超时,客观下线:半数Sentinel认为该服务器下线(半数原则)
- 提醒: Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
- 故障切换: 选举主sentinel服务器,选一个slave为master,原master上线做为新slave。
三种方式对比:
- 主从不支持故障切换,只能一台服务器执行写操作.
- sentinel提供了故障切换,同样只能一台服务器执行写
- cluster有多个主从服务链组成,每个主节点都提供读写能力,主节点之间保持心跳关系 ,无中心化
- cluster支持故障切换、负载均衡、在线扩容
cluster集群命令请求描述:
- 随机发送到一个服务器
- crc16(key)%16384利用求余,把正确服务器返回客户端
- 命令redirect正确服务器返回数据
7. Redis底层数据结构
数据类型:string、list、set、hash、zset
数据结构:int、raw、embstr、ht(字典)、linkedlist、ziplist、hashtable、intset、skiplist
关系图8. Redis内存回收机制和六大清除策略
redisObject 类结构中的 refcount 属性实现:
- 创建对象+1
- 对象复用+1
- 对象删除-1
- 当为0时,释放
redis6种(8种)清除策略:
- volatile-lru: 利用LRU算法移除设置过过期时间的最少使用的key
- volatile-random: 移除设置过过期时间的随机key
- volatile-ttl: 移除即将过期的key
- allkeys-lru: 利用LRU算法移除最近最少使用的key(常用)
- allkeys-random: 移除随机key
- noeviction noeviction 不移除任何key,只是返回一个写错误 (默认)
- volatile-lfu:从所有配置了过期时间的键中移除使用频率最少的键
- allkeys-lfu:从所有键中移除使用频率最少的键
Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略。
LFU 策略: 通过统计访问频率,将访问频率最少的数据淘汰。
LRU策略: 通过最近访问,将长时间没有访问的数据淘汰。
定期删除+惰性删除
-
定期删除:redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。
-
惰性删除 :当去查询已经过期的key时,Redis才会对其删除。
9. Jedis VS Lettuce
-
Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接。当连接数量增多时,物理连接成本较高。
-
Lettuce的连接是基于Netty的,可以在多个线程间并发访问是线程安全的,多线程可访问同一个链接实例。
数据序列化:
- JdkSerializationRedisSerializer(默认): 使用JDK提供的序列化功能,数据是JSON格式的5倍。
- Jackson2JsonRedisSerializer:使用Jackson库将对象序列化为JSON字符串速度快、短小精悍、会把类路径保存到josn中(反序列化)。
10. 缓存雪崩和缓存穿透问题解决方案
1. 缓存雪崩
介绍: 缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
方案: 尽量保证整个 redis 集群的高可用性,选择合适的内存淘汰策略;hystrix限流&降级,避免MySQL崩掉;利用 redis 持久化机制保存的数据尽快恢复缓存;
2. 缓存穿透
介绍: 故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
方案: 查询返回的数据为空把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
3. 缓存击穿
介绍: 某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
方案:
-
若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
-
若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
-
若缓存的数据更新频繁或者缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动的重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。
11. 如何解决 Redis 的并发竞争 Key 问题(分布式锁)
单线程的Redis并没有锁的概念,当运用连接池进行并发访问会存在竞争关系,即多个系统同时对一个 key 进行操作,执行顺序和我们预想顺序不一致。
1. 分布式锁+时间戳
- 准备一个分布式锁,大家去抢锁,抢到锁就做set操作。
- Redis利用setnx命令去加锁,获取锁成功返回1反之为0,。
- 通过比较两个时间戳来保证按照顺序执行。
2. 消息队列
- 放到消息队列中让并行的操作变成串行
12. 如何保证缓存与数据库双写时的数据一致性?
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,不要使用强一致性。
-
写请求先删除缓存,再去更新数据库,(异步等待段时间)再删除缓存(也有可能存在短时间不一致)
-
写请求先修改缓存为指定值,再去更新数据库,再更新缓存。读请求过来后,先读缓存,判断是指定值后进入循环状态,等待写请求更新缓存。如果循环超时就去数据库读取数据,更新缓存。(强一致性)
网友评论