1.Redis
1.1 初识Redis
1.1.1 Redis特性
1.1.2 Redis使用场景
1.1.3 Redis常用数据类型
1.1.4 单线程
1.2 常用API
1.2.1 通用命令
1.3 数据结构&内部编码
1.4 持久化
1.4.1 RDB
1.4.2 AOF
1.4.3 RDB VS AOF
1.4.4 持久化存在的问题
2.Redis安装
2.1 linux安装
2.2 常用操作
2.3 配置
2.3.1 配置文件
2.3.4 Redis安装脚本
3. Redis
3.1 慢查询
3.2 pipeline
3.2.1 pipeline VS m操作
3.3 Hyperloglog
3.1 Redis事务
3.1.1 常用命令:
3.1.2 特性:
3.4 发布订阅
3.4.1 订阅消息
3.4.2 发布消息
3.3 主从复制
3.3.1 主从配置
3.3.2 情景分析
3.3.3 薪火相传
3.3.4 反客为主
3.3.5 全量复制
3.3.6 部分复制
3.3.7 复制运维
3.3.8 优缺点
3.4 哨兵模式
3.4.1 哨兵原理
3.4.2 客户端
3.4.3 哨兵内部定时任务
3.4.4 主客观下线
3.4.5 领导者选举
3.4.6 故障转移
1.面试题:
1.1 为什么要用Redis:
1.2 Redis存在的问题
1.3 Redis的文件事件处理器
1.4 Redis过期策略
1.4.1 惰性删除 && 定期删除
1.4.2 内存淘汰机制
1.4.3 LRU算法
1.5 缓存雪崩
1.5.1 缓存雪崩
1.5.2 解决策略
1.6 缓存穿透
1.7 缓存和数据库双写的一致性问题
1.7.1 双写的策略
1.Redis
1.1 初识Redis
1.1.1 Redis特性
1.1.1.1 快
1.1.1.2 持久化
AOF + RDB
1.1.1.3 多种数据结构
String、Hash、Set、List、SortedSet、BitMap(位图)、HyperLogLog(超小内存唯一计数)、Geo(地理信息)
1.1.1.4 多种客户端语言
Java、python、Ruby
1.1.1.5 功能丰富
发布订阅、lua脚本、事务、pipeline
1.1.1.6 主从复制
1.1.1.7 高可用、分布式(集群)
1.1.2 Redis使用场景
缓存系统、计数器、排行榜、社交网络、简易消息队列、实时系统(布隆过滤器)
1.1.3 Redis常用数据类型
Redis 的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于 Java 的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,内存为当前字符串实际分配的空间一般要高于实际字符串长度。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是字符串最大长度为 512M。
使用场景:记录用户页面访问量、缓存基本数据、分布式Id生成器
------------键值对------------- > set key value //设置键值对,重复设置会更新key > get key // > exists key (integer) 1 > del key (integer) 1 > strlen key //获取value的长度 > append key valu //在原先的value后面追加值 ------------批量键值对------------- (可以批量对多个字符串进行读写,节省网络耗时开销。) > mset k1 v1 k2 v2 k3 v3 > mget k1 k2 k3 > msenx k1 v1 k2 v2 //当且仅当key都不存在时,设置成功 ------------过期和 set 命令扩展------------- > set key value > expire key 5 # 5s 后过期 > setex key 5 value # 5s 后过期,等价于 set+expire > setnx key value# 如果 name 不存在就执行 set 创建 (integer) 1 > setnx key value (integer) 0 # 因为 name 已经存在,所以 set 创建不成功 ---------------------计数------------------- > set age 30 > incr age //自增值 decr key // 自减 (integer) 31 > incrby age 5 //decrby key 值 //-值 (integer) 36 -------------------获取----------------------- getrange key startIdx endIdx //获取内容,包括起始位置和终止位置 setrang key startIdx value //从指定位置开始写
还有一个需要特别注意的地方是如果一个key已经设置了过期时间,然后你调用了set 方法修改了它,它的过期时间会消失。
key:fileld : value ;MapMap结构
Redis 的Hash相当于 Java 语言里面的 HashMap,它是无序字典。内部通过数组 + 链表实现。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。不同的是,Redis 的字典的值只能是字符串,另外它们扩容 方式不一样,因为Java 的HashMap 在字典很大时,扩容是个耗时的操作,需要一次性全部 扩容。Redis为了高性能,不能堵塞服务,所以采用了渐进式扩容策略。渐进式 扩容会在 扩容的同时,保留新旧两个hash 结构,查询时会同时查询两个hash 结构,然后在后续的定时任务中以及 hash 的子指令中,循序渐进地将旧 hash 的内容一点点迁移到新的 hash 结构中。当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。
hset sunpeipei age 22 //设置修改年龄 hget sunpei age //获取年龄 hmset sunpei name "sunpei" age 23 phone "153..."//批量设置 hmget sunpei name age hgetall sunpei //获取全部key和v hlen sunpei //获取value有多少键值对 hexists sunpei name //判断key存在否 hkeys hash名 //获取所有的key hvals 哈希名 //获取所有的value hincrby hash名 k 数 //将指定的值+数 【整数】 hsetnx sunpei age 22 //当且仅当元素不存在时设置成功
Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表而不是数组。这意味着list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为O(n)。当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。
Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符
串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。
Redis 底层存储的不是一个简单的 linkedlist,而是称之为快速链表 quicklist 的一个结构。
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是 ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成 quicklist。因为普通的链表需要的附加指针空间太大,会比较浪费空间,而且会加重内存的碎片化。所以 Redis 将链表和 ziplist 结合起来组成了 quicklist。也就是将多个ziplist 使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
Tips:
LPUSH+LPOP =栈
LPUSH+RPOP =队列
LPUSH+ LTRIM = 固定数量的列表
LPUSH +BRPOP =消息队列
--------------右边进左边出:队列------------- > rpush books python java golang // lpush 列表名 值 (integer) 3 > lpop books //正序弹出一个元素 "python" > lpop books "java" > lpop books "golang" -----------右边进右边出:栈--------------- > rpush books python java golang (integer) 3 > rpop books //倒序弹出一个元素 "golang" > rpop books "java" > rpop books "python" -----------慢操作(慎用)--------------- > lindex 列表名 索引 //获取指定索引的元素 > lrange 列表名 startIdx endIdx //获取指定数据【包括起始索引和结束索引】 > ltrim 列表名 starIdx endIdx //截取指定范围的值再赋值给key 【包括起止索引】 > lset 列表名 index value // > linsert 列表名 before/after v1 v2 //在v1前/后面插入v2 -----------其他--------------- > llen 列表名 //获取列表的长度 > lrem 列表名 个数 值 //删除n个值 > rpoplpush 源列表 目的列表 //将原列表表的最后一个元素添加到目的列表的第一位
sadd friends lilei //成功返回 1 sadd friends lilei //重复返回 0 不可重复设置 smembers set名 //查看元素,返回结果无序 sismember set名 value //查看该集合中是否包含该元素 1-包含 0-不包含 scard set名 //获取集合元素个数 srem set名 值 //移除某元素 srandmember set名 个数 //随机获取指定个数值 spop set名 //随机出栈 smove set1名 set2名 v //将集合1中的某值移动到集合2中 sdiff set1 set2 //在集合1中且不再集合2中的元素 sinter set1 set2 //集合1和集合2的交集部分 sunion set1 set2 //集合1和集合2的并集 //用户关注人 、粉丝 可以求交集、差集、并集操作【共同操作、共同爱好】
Tips:
SADD :标签
SPOP + SRANDMEMBER : 随机数
SADD +SINTER :社交
zset 可能是 Redis 提供的最为特色的数据结构,它类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫着「跳跃列表」的数据结构。
zset 要支持随机的插入和删除,所以它不好使用数组来表示。我们先看一个普通的
链表结构。
我们需要这个链表按照 score 值进行排序。这意味着当有新元素需要插入时,要定位到
特定位置的插入点,这样才可以继续保证链表是有序的。通常我们会通过二分查找来找到插入点,但是二分查找的对象必须是数组,只有数组才可以支持快速位置定位,链表做不到。
「跳跃列表」之所以「跳跃」,是因为内部的元素可能「身兼数职」,比如上图中间的
这个元素,同时处于 L0、L1 和 L2 层,可以快速在不同层次之间进行「跳跃」。
定位插入点时,先在顶层进行定位,然后下潜到下一级定位,一直下潜到最底层找到合
适的位置,将新元素插进去。你也许会问,那新插入的元素如何才有机会「身兼数职」呢?跳跃列表采取一个随机策略来决定新元素可以兼职到第几层。
首先 L0 层肯定是 100% 了,L1 层只有 50% 的概率,L2 层只有 25% 的概率,L3
层只有 12.5% 的概率,一直随机到最顶层 L31 层。绝大多数元素都过不了几层,只有极少数元素可以深入到顶层。
zadd 集合名 分数1 值1 分数2 值2 //添加数据 zrange key startIdx endIdx 【withscores】 //遍历集合 【带分数】 zrangebyscore 集合名 【(】开始socre 【(】 结束score 【limit 开始idx 个数】//获取指定范围的元素 zrem 集合名 元素 // 删除指定元素 zcard 集合名 //统计集合中有多少元素 zcount 集合名 开始score 结束score //统计得分在指定范围的元素有多少个,包括边界 zrank 集合名 k1 //获取元素的下标 zcore 集合名 元素 //获取元素的分数 zrevrank 集合名 元素 //逆序获取元素的下标 zrevrange 集合名 开始idx 结束idx //逆序获取元素 zrevrangebyscore 集合名 结束分数 开始分数 //获取指定分数范围的集合
1.1.4 单线程
单线程的理解:
Redis一次只会执行一个命令,因此要避免长命令(keys、flush、flushdb、slow lua script、multi/exec、operate、big value)
单线程为什么这么快?
1.2 常用API
1.2.1 通用命令
keys //keys * keys h[h-l]* keys jav? config get * //获取配置
命令 |
时间复杂度 |
keys |
O(n) |
dbsize |
O(1) |
exists、hexists、hlen |
O(1) |
del 、hdel |
O(1) |
expire、ttl、persist |
O(1) |
type |
O(1) |
get、set、del、hget、hset、hdel |
O(1) |
incr、decr、incrby、decrby、incrbyflocat、hincrby、hincrbyflocat |
O(1) |
setnx(add)、set xx(update)、hsetnx |
O(1) |
mget、mset、hmget、hmset、 |
O(n)[1次网络时间+n次命令时间] |
getset(设置新值、返回旧值)、append、strlen(中文) |
O(1) |
getrange、setrange |
O(1) |
hgetall、hvals、hkeys |
O(n) |
rpush、lpush |
O(1-n) |
linsert |
O(n) |
lpop、rpop、 |
O(1) |
lrem、ltrim |
O(n) |
lindex、lrange(包含end)、 |
O(n) |
llen |
O(1) |
lset、 |
O(n) |
blpop、brpop |
O(1) |
sadd、srem |
O(1) |
scard、sismember、srandmember、spop |
O(1) |
smembers |
O(n) |
sdiff、sinter、sunion、 |
集合间操作 |
zadd |
O(lgn) |
zrem、zscore、zincrby、zcard |
O(1) |
zrange、zrangebyscore、zcount、zremrangebyrank、zremrangebyscore |
O(log(n)+m) |
zinterstore、zunionstore |
1.3 数据结构&内部编码
这样设计的目的: 空间换时间? 时间换空间
Redis内置对象结构:
1.4 持久化
1.4.1 RDB
触发机制:save、bgsave、自动触发
文件策略:新文件覆盖旧文件
1.4.1.1 save命令
1.4.1.2 bgsave命令
1.4.1.3 save VS bgsave
1.4.1.4 自动生成
自动触发:达到配置、shutdown、全量复制
推荐配置:关闭自动save、开启文件压缩、开启文件校验
1.4.1.5 总结
1.4.2 AOF
1.4.2.1 策略
推荐使用第二种:最多丢失1s数据
1.4.2.2 AOF重写
重写的作用:减少硬盘占用量、加快恢复速度
1.4.2.3 AOF重写实现方式
注意:重写是基于内存重写,不是基于旧的AOF文件;
min-size:第一次重写最小内存
percentage:下一次重写的条件
重写条件:(同时满足)
1.当期内存 > 最小内存
2.(当期尺寸-上次尺寸)/上次尺寸 > 增长率
no-appendfsync-on-rewrite :yes 在往缓存区写重写内容时,是否停止写aof;性能 VS 数据(如果重写失败,会导致aof丢失部分数据),推荐使用yes,提高性能;
1.4.3 RDB VS AOF
RDB建议:主节点关闭RDB、从节点打开RDB,便于备份数据;
1.4.4 持久化存在的问题
1.4.4.1 fork
latest_fork_usec :上次fork花费的时间
1.4.4.2 子进程的开销
Linux在2.6版本后,优化了内存页(内存页更大),然而这对Redis来说并不是一个好消息;echo:nerver > /sys/kernel/mm/transparent_hugepage/enable
1.4.4.3 AOF追加阻塞
2.Redis安装
2.1 linux安装
下载压缩包:http://download.redis.io/releases/
// 下载 wget http://download.redis.io/releases/+版本 //解压 tar -zxvf 文件名 // 安装gcc[c编译器]: yum install gcc-c++ // 安装Redis 【redis目录下】 make // 出错用 : make MALLOC=libc make install // 开启守护线程 daemonize yes // 运行Redis redis-server 配置文件 // 查询线程信息 ps -ef|grep "redis" // 连接客户端: redis-cli -h host-p 端口号
2.2 常用操作
性能测试:
redis-benchmark
切换库:
select 编号 【从0开始,默认16个库】
查看当前数据库key的数量:
DBSIZE
查看当前库所有的key
keys * // keys k? 精确查找
清空当前库
flushdb
清空全库
flushall
判断某个key是否存在
exists key //0-不存在 1-存在
剪切key到另一个库
move key db //当前库就不存在了
给key设置过期时间
expire key 10 //过期时间
查看key还有多久过期
ttl key //-1 永久有效,-2 已过期
查看key的类型
type key
2.3 配置
2.3.1 配置文件
redis.conf 配置项说明如下: 1.daemonize no Redis默认不是以守护进程的方式运行,可以通过该配置项修改, 使用yes启用守护进程 2. pidfile /var/run/redis.pid 当Redis以守护进程方式运行时,Redis默认会把pid 写入/var/run/redis.pid文件,可以通过pidfile指定 3. port 6379 指定Redis监听端口,默认端口为6379, 4. bind 127.0.0.1 绑定的主机地址 5. timeout 300 当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能 6. loglevel verbose 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、 warning,默认为verbose 7. logfile stdout 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行, 而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null 8. databases 16 Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享, 并且基于单机才有,如果是集群就没有数据库的概念 。 设置数据库的数量,默认数据库为0,可以使用SELECT
2.3.4 Redis安装脚本
#!/bin/bash #检查用户是否已经存在 ############################## ## 描述:创建用户,安装Redis ## 作者:孙培 ## 时间:2019-09-17 ############################## set -o nounset set -o errexit readonly redisDir="/opt/cachecloud/redis" readonly redisTarGz="redis-3.0.7.tar.gz" checkExist(){ #查看passwd里是否有用户记录 local num=`cat /etc/passwd | grep -w ${1}|wc -l` if [ ${num} -ge 1 ] then echo "==>>当前用户已经存在,是否覆盖已有数据?:[y/n]" read replace if [ ${replace} == "y" ] then echo "==>>删除已存在用户: ${1}" userdel -r "${1}" createUser "${1}" init "${1}" return 0 fi else createUser "${1}" init "${1}" fi return 0 } #创建用户 createUser() { # 添加用户 useradd -m -d /home/${1} -s /bin/bash ${1} # 添加密码 echo "==>> 请输入用户密码" passwd ${1} # 密码保存的有效天数 chage -M 9999 ${1} echo "==>> 用户${1}创建成功!" } # 初始化 init() { # 创建目录 mkdir -p /opt/cachecloud/data mkdir -p /opt/cachecloud/conf mkdir -p /opt/cachecloud/logs mkdir -p /opt/cachecloud/redis mkdir -p /tmp/cachecloud echo "==>>初始化文件目录" # 为用户授权 chown -R ${1}:${1} /opt/cachecloud chown -R ${1}:${1} /tmp/cachecloud echo "==>>为${1}用户授权" } # 安装redis installRedis() { # 提示语: echo "===>>开始安装Redis," # 安装GCC yum install -y gcc # 创建目录 mkdir -p ${redisDir} && cd ${redisDir} # 下载解压目录 wget http://download.redis.io/releases/${redisTarGz} && mv ${redisTarGz} redis.tar.gz && tar zxvf redis.tar.gz --strip-component=1 # 安装Redis make && make install if [ $? == 0 ] then echo "==>> Redis安装成功,Redis默认安装目录:${redisDir},Redis默认版本:${redisTarGz},如有需要,请修改脚本" #授权 chown -R $1:$1 ${redisDir} #配置环境变量 export PATH=$PATH:${redisDir}/src return 0 fi echo "==>>Redis安装失败,原因:Redis已经存在" } username=$1 checkExist "${username}" installRedis "${username}"
3. Redis
3.1 慢查询
Redis会将慢查询命令保存在内存队列中,慢查询数据不会持久化;
slowlog get [n] //获取慢查询列表 slowlog len //获取慢查询队列长度 slowlog reset //清理慢查询队列 //动态修改 config set slowlog-max-len 1000 //慢查询队列长度 config set slowlog-log-slower-than 1000 //单位微妙:即1毫秒
tip:
3.2 pipeline
1次请求时间 = 1次网络请求时间(发送请求+接收请求) + 命令排队时间+执行时间
注意:Redis执行命令的时间是微妙级别、所以Redis的瓶颈是在网络时间上;北京到上海的一次网 络请求大概需要花费13毫秒。
pipeline就是为了节省网络开销,一次执行多条命令;
3.2.1 pipeline VS m操作
原生的M操作是原子性的,pipeline是非原子性的;
3.3 Hyperloglog
pfadd key element //添加元素 pfcount key :计算总数 pfmerge destkey sourcekey [sourcekey..] //合并多个key
Tips:
3.1 Redis事务
3.1.1 常用命令:
①标记开始一个事务:MULTI
②取消事务:DISCARD
③执行所有的事务:EXEC
④监视一个(或多个)key,如果在事务执行前这个key被改动,那么事务被打断:WATCH key [key...]
⑤取消WATCH命令多所有key的监视: UNWATCH
redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> set k1 v1 QUEUED redis 127.0.0.1:6379> DISCARD OK redis 127.0.0.1:6379> get k1 (nil) redis 127.0.0.1:6379> EXEC 1) OK redis 127.0.0.1:6379> get k1 "v1"
3.1.2 特性:
①冤头债主:
部分支持事务,成功的操作不会受错误操作的影响;
redis 127.0.0.1:6379> set k1 v1 QUEUED redis 127.0.0.1:6379> getset k2 (error) ERR wrong number of arguments for 'getset' command redis 127.0.0.1:6379> set k3 v3 QUEUED redis 127.0.0.1:6379> exec 1) OK 3) OK redis 127.0.0.1:6379> get k3 "v3"
②WATCH锁
WATCH锁,底层由乐观锁实现;
redis 127.0.0.1:6379> get k1 "800" //设置监视锁 redis 127.0.0.1:6379> watch k1 OK //开启事务 redis 127.0.0.1:6379> multi OK // 修改k1为29 redis 127.0.0.1:6379> set k1 29 QUEUED /----------- 别的线程---------------- redis 127.0.0.1:6379> get k1 "800" redis 127.0.0.1:6379> set k1 200 OK /----------- 别的线程---------------- //执行失败 redis 127.0.0.1:6379> exec (nil) //修改值失败 redis 127.0.0.1:6379> get k1 "200"
3.4 发布订阅
Redis消息队列特点:1. 无法获取历史消息 2.消息是非抢占式的(都可以收到)
3.4.1 订阅消息
//订阅多个消息 redis 127.0.0.1:6379> subscribe cctv-1 cctv-2 //订阅多个消息 psubscribe cctv*
3.4.2 发布消息
publish cctv-1 xinweblianbo
3.3 主从复制
作用:数据副本(高可用)、扩展读性能(读写分离)
3.3.1 主从配置
3.3.1.1 命令
查看当前节点信息:
info replication
作为从节点:【重启无效】
slaveof 主机IP 主机端口号 slaveof no one // 恢复为主节点,旧数据不会清除,跟随新主后会清空数据
3.3.1.2 配置
slaveof ip port slave-read-only yes //从节点只读
3.3.2 情景分析
情景一:先写入数据,后进行主从备份,之前的数据会不会被同步?
主从备份前的数据也会被同步
情景二:从机是否可以写数据?
从机只支持读取数据,不支持写数据
情景三:主机宕机后,从机是否会选举为主机?主机恢复后,从机是否可以正常工作?
主机宕机后,从机不会进行选举;
主机恢复后,从机正常工作;
情景四:从机宕机重启后是否可以正常工作?
命令式:重启会从机不会识别主机,需要重新执行命令;
配置型:重启后从机会识别主机
3.3.3 薪火相传
①:中间的节点还是属于从节点,不支持写操作;
②:中间节点宕机,最后的从节点也无法正常工作;中间节点恢复后需要重新执行slaveof命令
3.3.4 反客为主
原主机宕机 -->从机执行 slaveof no one -->选出新的主机【剩余的从机不会更新主机(忠心不二);谁执行这条命令谁就会变为主机(谋权篡位)】
原主机恢复---> 新主机不会禅让(反客为主) ---> 丛机会跟随原主机(坚贞不渝)
3.3.5 全量复制
全量复制开销:
1.bgsave时间
2.RDB文件网络传输时间
3.从节点清空数据时间
4.从节点加载RDB文件时间
5.从节点AOF重写时间(如果从节点开启了AOF功能)
3.3.6 部分复制
3.3.7 复制运维
3.3.8 优缺点
优点:1.数据备份 2、读写分离,减轻主机压力
缺点:1.手动故障转移 2.写能力、存储能力有限
3.4 哨兵模式
3.4.1 哨兵原理
新建配置文件:sentinel.conf
添加配置:
sentinel monitor mastser-name(区分不同的组) 主机IP 主机端口 投票数(达到该票时就会选举为主机) daemonized yesse port 端口号 logfile "${port}.log" ###########以下配置会重写################## sentinel down-after-milliseconds 组名 30000(30s,超过该时间,就认为该节点下线) sentinel parallel-syncs 组名 1(新主机成立后,一次可以有几个从机复制主机) sentinel failover-timeout 组名 180000(故障转移时间)
启动哨兵: /Redis/src目录下
redis-sentinel sentinel.conf【配置文件位置】
命令查看:
redis-cli -p 26379 //哨兵是一个特殊的 info //查看哨兵信息
3.4.2 客户端
3.4.2.1 客户端如何连接Master
step1: 客户端需要提供哨兵集合和组名,遍历哨兵集合,找到一个可用的哨兵
step2:通过组名获取master地址
step3: 验证节点的信息
step4:如果发生故障转移,哨兵通过发布订阅模式通知客户端
3.4.3 哨兵内部定时任务
3.4.4 主客观下线
从节点:因为不涉及故障转移,固只需要主观下线就行;
3.4.5 领导者选举
3.4.6 故障转移
1.面试题:
1.1 为什么要用Redis:
高性能:Redis读取速率高于DB查找
高并发:缓解数据库压力
1.2 Redis存在的问题
1.3 Redis的文件事件处理器
1.4 Redis过期策略
1.4.1 惰性删除 && 定期删除
定期删除:每隔100ms,就会随机抽取一些设置了过期时间的key,j检查其是否过期,如果过期,就删除;
惰性删除:在获取key时,Redis会检查这个key是否设置过期时间以及是否过期,如果已经过期则删除;
其余:走内存淘汰机制
1.4.2 内存淘汰机制
如果Redis内存占用过多,就会进行内存淘汰,对应的策略:
策略一:内存不足时,拒绝写入;(noeviction)
策略二:内存不足时,删除最近最少使用的key[全量key];推荐使用(allkeys-lru)
策略三:内存不足时,随机删除;(allkeys-random)
策略四:内存不足时,删除最近最少使用的key【设置了过期时间的key】;(volatile-lru)
策略五:内存不足时,随机删除;【设置了过期时间的key】 (volatile-random)
策略六:内存不足时,优先删除过期时间早的key; (volatile-ttl)
1.4.3 LRU算法
public class MyLRU
1.5 缓存雪崩
1.5.1 缓存雪崩
缓存崩溃 -->数据库崩溃 -->系统不可用
1.5.2 解决策略
事前:保证Redis的高可用 : Redis集群
事中:本地ehcache+限流组件
事后:根据Redis持久化文件,恢复Redis集群
1.6 缓存穿透
穿透:Redis->DB ->穿透
根本原因:数据库中不存在的数据不会回写到Redis中,进而每次都请求数据库
解决方案:查询不到数据时,就写空值到缓存;
1.7 缓存和数据库双写的一致性问题
1.7.1 双写的策略
读数据:读取数据时,先从Redis获取,获取不到时,再查数据库,并将结果回写数据库。
更新数据:更新数据时,先删除缓存,再更新数据库。
更新数据时删除缓存原因:懒加载机制,频繁更新不等于频繁查询,只有查询的时候才放入缓存;
1.7.2
3