面试题
redis相比memcached有哪些优势?(重点)
- memcached的所有value是简单的string类型,redis作为其替代者支持了丰富的数据类型
- redis 运行速度比memcached快很多,并且它支持持久化操作
- redis支持master-salve复制机制
- redis的value最大可以是512m,memcached最大只能是1m
面试官:“redis都支持哪些数据类型?应用场景有哪些?”(重点)
- string:字符串的value最大可以达到512m,可以用来做一些计数功能的缓存
- list:是有序可以重复的集合,可以实现简单的消息队列功能
- set:是无序不可以重复的集合,可以用来进行全局去重
- sorted set:有序的不可以重复的集合,它给每一个元素分配一个固定的分数来保持顺序。可以用来做排行榜应用,如:微博热搜等
- hash:键值对集合,key和value都是string类型,可以用来存放一些特定结构的信息
- geospatial:指定地理位置的经纬度,可以用来做位置共享或附近的人等应用。
- bitmaps:进行位存储,只有0和1两种状态,可以用来做只有两种状态的应用,如:用户是否登录、是否在线、是否打卡等
- hyperloglog:统计不重复元素的个数,可以用统计注册人数、登录人数、访问人数、在线人数等
redis是单线程的吗?为什么执行速度这么快?(重点掌握)
redis是单线程的。执行速度快原因:
- redis基于内存存储
- redis是单线程的,没有多线程的上下文切换
- redis使用了多路IO复用的线程模型,一个线程监控多个IO流
- redis是轻量级的内存数据库,对外部依赖较少
使用redis可能出现的问题(重点)
缓存雪崩:是指在某个时间段,缓存集体失效,新来的请求直接打在数据库上,导致数据库异常。
产生雪崩的原因之一:设置缓存时间已到,例如:双11零点开始抢购,设置的一批商品是1点失效,到1点的时候,请求直接打在数据库上导致数据库异常
解决方案:
- redis高可用:多设置几台redis
- 给缓存设置不同的过期时间,使过期时间均匀分布
- 使用互斥锁:控制线程数量
缓存击穿:缓存中存放某一个热点数据,持久被高并发请求访问,某一时刻该热点数据过期,导致高并发请求直接打在数据库上,导致数据库异常
解决办法:
- 设置热点数据永不过期
- 设置
互斥锁
使得只有一个线程对该热点数据进行访问
缓存穿透:故意去请求缓存中不存在的数据,导致请求直接打在数据库上,造成数据库异常
解决办法:
- 布隆过滤器
- 使用互斥锁:控制线程数量
数据库和缓存的双写一致性问题(重点)
在高并发请求下很容易导致数据库和缓存数据不一致问题,如果你的业务需要保持高强度的一致性,建议删除缓存,在数据库和缓存数据的删除和写入过程中,如果有失败情况,很容易导致数据的不一致。
解决办法:
- 双删延时:先删除缓存,再更新数据库,在隔固定时间删除缓存。
- 更新数据库产生的binlog订阅(使用canal):将有变化的key记录下来,然后尝试去不断删除缓存
redis的持久化方式有哪些?(重点掌握)
RDB(redis database)
什么是rdb?
在指定的时间间隔内,将内存中的数据集快照写入磁盘中,也就是行话snapshot(快照)。恢复时将数据集快照直接读入内存中。
持久化过程:
redis会单独创建(fork)一个子进程来进行持久化,先将数据写入到一个临时文件中,待持久化过程都结束了,再用临时文件代替上次持久化好的文件,整个过程主进程不进行任何IO操作,确保了极高的性能。
rdb默认保存的文件是啥?
dump.rdb
rdb优点和缺点?
优点:
- 适合大规模的数据恢复,在大规模数据恢复时候,rdb比aof效率高
缺点:
- rdb对数据丢失不敏感,在最后一次持久化的时候可能会丢失数据。
- fork的时候,内存中的数据被克隆了一份,会占用更多的内存
aof(append only file)
什么是aof?
相当于一个日志纪律文件,将redis的所有操作指令记录下来,在进行数据恢复的时候,从头到尾将aof文件执行一遍,只允许在末尾追加文件,不允许修改文件。当aof文件出错时,可以使用命令redis-check-aof --fix
对它进行修复。
aof默认保存的文件是啥?
appendonly.aof
aof的优缺点?
优点:
- aof可以做到秒级持久化,相比于rdb恢复的数据更加完整
缺点:
- 同样大小的数据,aof文件要大于rdb文件,恢复速度较慢
持久化策略选择(重点)
- aof可以做到秒级持久化,因此需要更多IO操作,aof相比较于rdb数据恢复更加完整和安全,同样大小的数据,aof文件比rdb更大,恢复速度也慢些
- rdb数据恢复存在一定的丢失性,相较于aof也没有那么安全,但是它是master-slave数据备份和同步的最佳选择,文件尺寸相较于aof也小些恢复速度快。
redis概述
- 什么是redis?
redis(remote dictionary server)远程字典服务
redis是开源使用C语言编写的、支持网络、基于内存、可持久化的日志型,k-v数据库,提供多种语言的API - redis有啥作用?
用于内存存储和持久化(AOF和RDB)
用于高速缓存
发布订阅系统
用于计时器和计数器(统计点赞数和网站访问量) - redis是单线程
- redis单线程为什么执行效率还高?
redis将所有数据放入内存中,它没有多线程耗时的上下文切换操作,多次读写都在一个cpu上,所以效率很高。
redis数据的过期回收策略与内存淘汰机制(重点)
redis中的数据过期回收策略使用了定期删除和惰性删除相结合的方式。
- 定期删除:每隔一定时间抽查一定量的数据判断是否过期,过期直接删除。
- 惰性删除:获取key,判断是否过期,过期直接删除。
内存淘汰机制
果redis的内存不足的时候,使用如下的策略进行淘汰:
- volatile-lru:当redis内存不足的时候,在设置了过期时间的key中,移除最近最少使用的key
- allkeys-lru:当redis内存不足的时候,移除最近最少时候的key
- volatile-random:当redis内存不足的时候,在设置了过期时间的key中,随机移除key
- allkeys-random:当redis内存不足的时候,随机移除key
- volatile-ttl:当redis内存不足的时候,在设置了过期时间的key中,移除即将过期的key
- noeviction :当redis内存不足的时候,不移除任何key,只是返回一个写错误
redis主从复制(重点)
redis的主从复制机制(重点)
将一台redis服务器的数据,复制到其他redis服务器上,前者叫做主节点(master),后者叫做从节点(slave),数据的复制是单向的,master以写为主,salve以读为主,实现了读写分离。
- 主从复制的作用?
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现数据快速恢复。
负载均衡:在主从复制的基础上,配合读写分离,master提供写服务,slave读服务,分担服务器压力。
实现redis服务器高可用 - 为什么不使用一台redis服务器?
1、从结构上来说,不够科学合理,单个redis服务器发生故障,整个系统就瘫痪。
2、从容量上来说,单个redis服务器内存容量有限,一般redis服务器内存容量不超过20G
什么是CAP理论?(重点)
当网络分区发生时,一致性和可用性无法同时保证
- consistency:一致性
- availability:可用性
- partition tolerance:分区容忍度
- 网络分区中:在分布式系统中节点是分布在不同的机器上进行网络隔离的,当网络断开时,就意味着网络分区发生了
- 最终一致性:从节点会努力追上主节点,最终和主节点的状态保持一致。
redis对事务支持(重点)
- 隔离性:redis是单进程单线程的,保证事务在执行的过程中不会被中断,事务可以运行直到事务队列中的所有命令被执行完为止。
- redis会将事务进行序列化,事务中的命令按顺序执行,redis运行过程中不会被其他客户端发送过来的执行打断。
redis操作事务的相关命令如下所示:
- multi:开启事务
- exec:提交事务
- discard:回滚事务
- watch key [key…]:监视一个或多个key,如果事务在执行之前key被修改了事务就会中断。
- unwatch:取消watch命令对所有key的监视
redis安装和启动
参考链接:
Redis安装和启动
redis性能测试
步骤:
- 启动redis
redis-server /usr/local/redis-4.0.6/etc/redis.conf(这里是指定自己的配置文件路径) - 连接redis 6379端口
redis-cli -p 6379 - 测试redis性能
redis性能测试工具可选参数:
示例: 100个连接,100000个请求
redis-benchmark -h localhost -p 6379 -c 1000 -n 100000
运行结果:
redis基本操作命令
redis中有16个数据库,编号为0-15
切换数据库
select 索引号
运行结果:
设置key获取value
set 键 值
运行结果:
查看数据库所有键
keys *
运行结果:
清空当前数据库
flushdb
运行结果:
清空全部数据库
flushall
运行结果:
判断key是否存在
exists key #0表示不存在 1表示存在
运行结果:
设置键的过期时间
expire 键 时间(以s为单位)
ttl 键 #查看键剩余过期时间:
运行结果:
设置name 10s过期
查看当前key的类型
type 键
运行结果:
redis5大基本数据类型
String
追加字符串
append name zhangsan
运行结果:
字符串的长度
strlen name
运行结果:
加1操作
incr count
运行结果:
减1操作
decr count
运行结果:
增量操作
incrby count 9
运行结果:
减量操作
decrby count 9
运行结果:
获取指定范围内的字符串
getrange name 0 3 #获取0-3这一段
运行结果:
getrange name 0 -1 #获取整个字符串
替换字符串
setrange name 1 xx
运行结果:
设置键的过期时间并赋值
setex name 30 dongjie
运行结果:
创建键并赋值
setnx name dongjie
运行结果:
创建多个k-v对
mset k1 v1 k2 v2 k3 v3 #创建多个键值对
mget k1 k2 k3 #获取多个键
运行结果:
封装对象/获取对象
mset user:1:name zhangsan user:1:age 55 user:1:sex nan
mget user:1:name user:1:age user:1:sex
运行结果:
应用场景
可以用来做一些计数功能的缓存
List
往左边压入元素
lpush list one #往左边压入元素one
原理图:
运行结果:
获取左边区间元素值
lrange list 0 -1 #获取左边全部元素
lrange list 0 1 #获取左边部分元素
运行结果:
往右边压入元素
rpush list a
原理图:
弹出列表元素
lpop list #弹出左边第一个元素
rpop list #弹出右边第一个元素
原理图:
运行结果:
获取左边元素值
lindex list 0 #获取左边索引为0的元素值
运行结果:
获取左边列表长度
llen list
运行结果:
移除列表元素
lrem list 1 one
lrem list 2 three #移除两个元素three
运行结果:
截取指定长度的元素
ltrim list 1 2 #截取长度为2的元素
原理图:
运行结果:
弹出列表最外一个元素并将它压入其他列表
rpoplpush mylist mylist
原理图:
运行结果:
更新指定索引的元素
lset list 0 item
运行结果:
插入元素
linsert list before world other #在world之前插入元素other
linsert list after world new #在world之后插入元素new
原理图:
运行结果:
应用场景
可以实现一个简单消息队列功能,做基于redis的分页功能等
Set(无序不能重复)
添加元素
sadd myset hello
运行结果:
获取所有元素
smembers myset
运行结果:
判断元素是否在集合中
sismember myset hello
运行结果:
获取当前元素个数
scard myset
运行结果:
移除元素
srem myset hello
运行结果:
随机获取元素
srandmember myset
srandmember myset 2
运行结果:
随机弹出元素
spop myset
运行结果:
将一个集合中的元素移到另外一个集合中
smove myset myset2 kuangsheng
运行结果:
两个集合的差集和交集
sdiff key1 key2
sinter key1 key2
运行结果:
两个集合的并集
sunion key1 key2
运行结果:
应用场景
可以用来进行全局去重等
Zset(sorted set有序不重复集合)
添加值
zadd myset 1 one
zadd myset 2 two 3 three
运行结果:
获取所有值
zrange myset 0 -1
运行结果:
升序排列
zrangebyscore salary -inf +inf #-inf代表负无穷大,+inf代表正无穷大
运行结果:
升序排列并打印出key
zrangebyscore salary -inf +inf withsocres
运行结果:
降序排列并打印出key
zrevrange salary 0 -1 withscores
运行结果:
移除指定元素
zrem salary zhangsan
运行结果:
集合长度
zcard salary
运行结果:
获取指定区间的元素个数
zcount salary -inf +inf #-inf代表负无穷大
运行结果:
应用场景
可以用来做排行榜应用或者进行范围查找等
Hash
设置键值对
hset myhash field1 kuangsheng
运行结果:
根据键获取值
hget myhash field1
运行结果:
设置多个键值对
hmset myhash field1 hello field2 world
运行结果:
根据多个键获取值
hmget myhash field1 field2
运行结果:
获取所有的键值对
hgetall myhash
运行结果:
删除hash指定的key
hdel myhash field1
运行结果:
获取hash的长度
hlen myhash
运行结果:
判断hash中某个key是否存在
hexists myhash field1
运行结果:
获取hash中所有key/value
hkeys myhash
hvals myhash
运行结果:
hash更适合用来存储对象,string更适合用来存储字符串
3大特殊数据类型
geospatial(地理位置)
添加地理位置
经度有效范围:-180~180
纬度有效范围: -85.05112878~ 85.05112878
geoadd china:city 116.4 23.2 beijing
geoadd china:city 112.1 12.1 shanghai 144.2 21.3 shenzhen
运行结果:
获取地理位置
geopos china:city beijing
geopos china:city beijing shenzhen
运行结果:
两地理位置之间距离
geoadd china:city beijing shanghai km
运行结果:
查询指定经纬度附近位置
georadius china:city 110 20 1000 km
georadius china:city 110 20 1000 km withcoord
georadius china:city 110 20 1000 km withcoord withdist
georadius china:city 110 20 1000 km count 2
运行结果:
查询指定位置附近的位置
georadiusbymember china:city shenzhen 1000 km
运行结果:
获取全部位置
zrange china:city 0 -1
运行结果:
删除指定位置
zrem china:city beijing
运行结果:
hyperloglog(统计元素个数)
简介:hyperloglog统计不重复的元素个数,具有容错率,如果允许有容错率,可以使用hyperloglog来统计元素个数。
优点:占用的内存是固定的,只需要占用12kb内存
添加元素
pfadd mykey 1 2 3 4 5 6 7 8 9 10
运行结果:
统计元素个数
pfcount mykey
合并元素
pfmerge mykey3 mykey1 mykey2
运行结果:
bitmaps
简介:位存储,操作二进制来进行存储,只有0和1两种状态
使用场景:统计用户信息,是否活跃,是否登录,是否打卡,2种状态,都可以使用bitmaps
添加数据
setbit day 0 1
setbit day 1 0
setbit day 2 0
运行结果:
获取数据(查看某一天是否打卡)
getbit day 0
getbit day 1
getbit day 2
运行结果:
统计打卡人数
bitcount day
运行结果:
事务
redis事务本质:一组命令的集合,一个事务中所有命令都会被序列化,在事务执行过程中,会按照顺序执行。一次性的、顺序性的、排他性的执行一些命令。
redis开启事务:
- 开启事务–multi
- 命令入列–…
- 执行事务–exec
运行结果:
redis事务没有隔离级别的概念,所有的命令在事务中,并没有直接被执行,而是发起执行命令exec才会被执行。
redis放弃事务:
- 开启事务—multi
- 命令入列—…
- 放弃事务—discard
运行结果:
编译型异常:
代码写错,事务中所有命令不会被执行
运行结果:
运行时异常:
类似于1/0这种语法错误,其他命令可以正常执行,错误命令抛出异常
运行结果:
悲观锁和乐观锁(重点)
- 悲观锁(类似于上厕所关门)
很悲观,认为什么时候都会出现问题,无论做什么都会加锁。 - 乐观锁(类似于上厕所不关门)
很乐观,认为什么时候都不会出现问题,所以不会上锁。更新数据的时候去判断一下,在此期间是否有人修改过这个数据。
通过版本号version进行更新数据。
测试多线程修改值,使用watch监视功能(上锁)操作乐观锁
线程1:
线程1监视money(加锁),money此时是100,在开启事务后,对money就行了操作,但是并没有执行事务。
线程2:
在线程1执行之前,线程2拿到money,并对money进行了修改
运行结果:
在线程2对money进行修改后,线程1执行事务,出现错误
乐观锁操作失败解决办法
- 如果事务执行失败,先解锁----unwatch
- 再次获取最新的值,再上锁—watch
- 开启事务—multi
- 命令入列—…
- 执行事务—exec
运行结果:
通过jedis操作redis
Jedis是Redis官方推荐的Java连接开发工具
java连接远程服务器redis
测试代码:
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.43.120",6379); #192.168.43.120是远程服务器的ip地址
System.out.println(jedis.ping());
}
}
运行结果:
redis.conf详解
bind 127.0.0.1 #绑定的ip
protected-mode no #保护模式关闭
port 6379 #端口号
daemonize yes #以守护进程的方式,后台运行,默认是no 开启为yes
loglevel notice #日志的4种级别
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
logfile "" #日志的文件位置名
databases 16 #数据库的个数
always-show-logo yes #是否显示redis开启logo
#redis是内存数据库,如果没有持久化,断电即失
save 900 1 #如果900s内,有1个key进行了修改,就进行持久化操作
save 300 10 #如果300s内,有10个key进行了修改,就进行持久化操作
save 60 10000 #如果60s内,有10000个key进行了修改,就进行持久化操作
stop-writes-on-bgsave-error yes #持久化出错,是否还需要继续工作
rdbcompression yes #是否压缩rdb文件,需要消耗资源
rdbchecksum yes #保存rdb文件的时候,进行错误的检查校验
dir ./ #rdb文件保存目录
requirepass foobared #设置密码
requirepass 123456 #设置密码
27.0.0.1:6379> auth 123456 #登录密码
OK
config get requirepass #获取密码
1) "requirepass"
2) "123456"
config set requirepass 123456 #设置密码
maxclients 10000 #设置客户端最大连接数
maxmemory <bytes> #设置内存最大容量
maxmemory-policy noeviction #内存到达上限后的处理策略
# volatile-lru 只对设置了过期时间的key进行lru
# allkeys-lru 删除lru算法的key
# volatile-random 随机删除即将过期的key
# allkeys-random 随机删除
# volatile-ttl 删除即将过期的
# noeviction 永不过期,返回错误
appendonly no #默认是不开启aof模式的,默认是使用rdb模式进行持久化的
appendfilename "appendonly.aof" #持久化文件的名称
# appendfsync always 每次都会同步,消耗性能
appendfsync everysec 每秒执行一次同步,可能会丢失这1s的数据
# appendfsync no 不执行同步,这时操作系统自己同步数据,速度最快
redis持久化(重点)
redis是基于内存存储的,如果不将其保存在硬盘中,数据是断电即失的
redis默认是使用rdb持久化,rdb持久化也够用了
RDB(redis database)
- 什么是rdb?
在指定的时间间隔内,将内存中的数据集快照写入磁盘中,也就是行话snapshot(快照)。恢复时将快照文件直接读入内存中。
过程:
redis会单独创建(fork)一个子进程来进行持久化,先将数据写入到一个临时文件中,待持久化过程都结束了,再用临时文件代替上次持久化好的文件,整个过程主进程不进行任何IO操作,确保了极高的性能,如果需要大规模数据恢复,且对数据丢失不是很敏感,rdb比aof方式更加高效。rdb缺点是最后一次持久化数据可能会丢失。
- rdb默认保存的文件是啥?
dump.rdb - rdb优点和缺点?
优点:
适合大规模的数据恢复
缺点:
在数据恢复的时候可能会丢失数据
fork的时候,内存中的数据被克隆了一份,会占用更多的内存
rdb持久化演示操作
演示图:
- 首先设置save 60 5,即在60s内有5个key发生修改,就进行持久化操作
- 设置k1~k5
- 当在60s内有5个key发生修改,redis自动生成新的dump.rdb文件
- 此时有5个key
- 我们将dump.rdb文件复制给dump_bk.rdb文件
- 此时清空数据库并且断电,redis会产生新的dump.rdb文件。但是之前的dump.rdb文件已经备份为dump_bk.rdb文件
- 重新启动redis并且连接,默认它会加载dump.rdb文件(因为在redis.conf中有这样的设置),所以目前的操作都是null
- 将之前备份的dump_bk.rdb复制为dump.rdb文件即可,因为重新启动redis并连接,它会默认加载dump.rdb文件
- 操作恢复正常
图一:
图二:
图三:
AOF(append only file)
-
什么是aof?
将我们的所有命令都记录下来,相当于一个日志文件,恢复的时候把这个文件全部再执行一遍。
以日志的形式来记录每个写操作,将redis执行过的指令记录下来,只许追加文件不许修改文件,redis启动之初会读取该文件重新构建数据=,简而言之,redis重启的话就会根据日志文件的内容将写指令从前到后执行一次,已完成数据的恢复。 -
aof默认保存的文件是啥?
appendonly.aof
-
aof的缺点和优点?
优点:
appendfsync always #每一次修改都会同步,文件的完整性会更好
appendfsync everysec #每秒都会同步一次,可能会丢失1s的数据
appendfsync no #从不同步,效率最高
AOF文件可以做到秒级持久化,使用追加写的方式来写入,可读性强并且可以使用命令进行文件修复。
缺点:
相对于数据文件来说,aof远大于rdb,修复的速度也比rdb慢 aof运行效率也比rdb慢,所以redis默认持久化是rdb。
aof持久化操作演示
- 演示appendonly.aof文件记录redis命令
1、当我们设置redis.conf配置文件中appendonly为 yes状态,会产生一个appendonly.aof文件
2、重启redis并连接redis,他会自动加载appendonly.aof文件
3、连接上redis后,输入redis指令,会被appendonly.aof文件记录下来
- 演示redis-check-aof文件对appendonly.aof文件出错时的恢复
1、当appendonly.aof文件发生错误时
2、此时连接redis无法连接上
3、需要使用redis-check-aof工具来恢复appendonly.aof文件
redis-check-aof --fix appendonly.aof
redis发布订阅(了解)
- 发布订阅是什么?
进程间的一种通信模式,发送者(pub)发送消息,订阅者(sub)接收消息 - 发布订阅常用命令?
publish channel message #将信息发送到指定频道
subscribe channel[channel...] #订阅一个或多个频道
unsubscribe [channel[channel...]] #退订指定的频道
psubscribe pattern[pattern...] #订阅一个或多个模式的频道
pubsub subcommand[argument[argument...]]
模拟订阅者与发布者
订阅端:
127.0.0.1:6379> subscribe chan1 #订阅频道chan1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "chan1"
3) (integer) 1
#订阅者接收到发布者发布的消息zhangsan
1) "message"
2) "chan1"
3) "zhangsan"
#订阅者接收到发布者发布的消息lisi
1) "message"
2) "chan1"
3) "lisi"
发布端:
127.0.0.1:6379> publish chan1 zhangsan #在频道chan1里发布消息zhangsan
(integer) 1
127.0.0.1:6379> publish chan1 lisi #在频道chan1里发布消息lisi
(integer) 1
redis主从复制(重点)
- 什么是主从复制?
将一台redis服务器的数据,复制到其他redis服务器上,前者叫做主节点(master),后者叫做从节点(slave),数据的复制是单向的,master以写为主,salve以读为主。 - 主从复制的作用?
数据冗余
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现数据快速恢复。
负载均衡:在主从复制的基础上,配合读写分离,master提供写服务,slave读服务,分担服务器压力。
高可用基石 - 为什么不使用一台redis服务器?
1、从结构上来说,单个redis服务器发生故障,无法自救,而且一台Redis服务器,需要处理所有的请求压力大
2、从容量上来说,单个redis服务器内存容量有限,一般redis服务器内存容量不超过20G - redis服务器集群示例图?
主从复制环境配置
只配置从机,不用配置主机
查看当前主机信息
模拟一主二从环境搭建步骤(使用命令):
- 复制3个redis.conf配置文件
- 修改每个配置文件如下信息:
- port,进程信息,日志文件,dump.rdb
- 启动每个redis
- 从机认主
slaveof 主机ip 端口号
slaveof 127.0.0.1 6379
从机信息:
主机信息:
模拟一主二从环境搭建步骤(使用配置文件):
实际开发中主从配置应该在redis.conf配置文件中进行配置,这样的话是永久的,我们这里使用的是命令,是暂时的。
细节: - 主机写,从机读,主机中的所有信息和数据,都会自动保存到从机中,如果是主机瘫痪掉,从机中依然保存了内容
- 主机如果断开连接,从机依旧连接到主机,如果主机回来了,从机依旧可以获取主机的信息,保证了高可用性。
- 如果使用命令进行主从配置,如果从机断开连接后,又再连接,从机会变成主机,因为命令是暂时的。如果使用配置文件进行主从配置,从机断开后又连接,它依然是从机,它可以继续读主机中的内容。
主从复制原理
- 全量复制:slave在接收到到数据库文件后,将其存盘并加载到内存中。
slave启动成功并连接到master后会发送sync同步命令。
master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件给slave,并完成一次完全同步,这个过程叫做全量复制。 - slave只要连接master,一次完全同步(全量复制)将被启动。
- 增量复制:master继续将新的所有收集到的修改命令依次传给slave,并完成同步
宕机后手动配置主机
如果6379宕机了,这个时候能不能选出一个主机出来了?
手动选择
我们选择6381作为主机,使用命令slaveof no one
,6380作为从机。
哨兵模式(重点)
(自动选取主机模式)
哨兵模式原理
假设主服务器宕机,哨兵1先检测到这个结果,但系统不会立马进行failover过程,仅仅是哨兵1主观认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量到达一定值时,那么哨兵之间就会进行一次投票,投票的结果由其中一个哨兵发起,进行failover(故障转移)操作,切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器切换主机,这个过程称为客观下线
哨兵监控测试
步骤
- 创建哨兵配置文件sentinel.conf,进行相关配置
sentinel monitor myredis 127.0.0.1 6379 1
- 启动哨兵
redis-sentinel /usr/local/redis-4.0.6/etc/sentinel.conf
- 如果master宕机后,它会自动从slave中选择一个服务器做为master(这里有个投票算法!)
细节:
如果这个时候之前的master重新启动并连接,它也只能归并到当前主机下。
哨兵模式的优缺点
优点:
- 哨兵集群,基于主从复制模式,所有的主从配置优点它都有
- 主从可以切换,故障可以转移,系统可用性更好
- 哨兵模式就是主从复制模式的升级,手动到自动,更加健壮
缺点:
- redis不好在线扩容,集群容量一旦到达上限,在线扩容十分麻烦
- 实现哨兵模式的配置很复杂,里面有很多选择
哨兵模式的全部配置
redis缓存穿透和雪崩(重点)
(都是服务的高可用问题)
缓存穿透
用户想要查询一个数据,发现redis内存数据库中没有,也就是redis没有命中,于是想持久层数据库查询,发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀!)于是读取请求持久层数据库,这会给持久层数据库造成很大的压力,这种现象就称为缓存穿透
解决方案
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,控制层进行数据校验,不符合则丢弃,从而避免了对底层数据库的查询压力
缓存空对象
当数据库没有命中后,将返回的空对象缓存起来,同时会设置一个过期时间,之后再查询这个数据,就从缓存中取,保护了数据库。
但是这种方法会存在2个问题:
1.如果空值被缓存起来,这就意味着缓存需要更多的空间存储更多的键,这些空值的键没啥意义
2.即使对空值设置了过期时间,也会造成redis和数据库时间上的不一致性,对需要保持一致性的业务也会造成影响
缓存击穿
是指一个key非常热点,在不停的扛着高并发,高并发集中对这一点进行访问,当这个key在失效的瞬间,持久的高并发击穿缓存,直接请求数据库,就像在屏幕上凿开 了一个洞。
解决方案
设置热点数据永不过期
加互斥锁
使用分布式锁,保证对每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁,因此只需要等待,这种方式将高并发的压力转移到分布式锁上来。
缓存雪崩:
是指在某个时间段,缓存集体失效
产生雪崩的原因之一:
假如马上迎来双11零点的时候,会迎来一波抢购高峰,这波商品都被放入缓存中,假设缓存设置的过期时间是1小时,那么到了双11 1点钟的时候,这批商品的缓存就过期了,而对这批商品的查询都落到了数据库身上,对于数据库而言,就会产生周期性的压力波峰,如果数据库没抗住,就会挂掉!
解决方案:
- redis高可用
既然redis有可能挂掉,那我们就多增设几台redis,这样一台挂掉之后其他还可以工作,其实就是搭建redis集群 - 限流降级
在缓存失效后,通过加锁或者队列的方式控制读数据库写回缓存的线程数量 - 数据预热
在正式部署之前,先把可能的数据预先访问一遍,这样大部分数据就会加载到缓存中,在即将发生高并发前手动触发加载缓存不同的key,设置不同的时间,让缓存失效的时间点尽量均匀。