Redis话术
1.请介绍一下你对redis的理解以及在项目中的应用
redis是一个基于key-value的非关系型数据库,支持两种持久化方式(rdb和aof),支持5种数据类型。
在之前的项目中,我们会使用redis做缓存数据库来加快请求的响应速度,也会利用redis数据可以设置有效期的特性来做一些功能。
- 使用String缓存首页商品分类菜单
- 使用Hash缓存大广告位信息
- 使用redis来做分布式锁
- 使用redis来保存token
2.Redis是什么
简述:Redis是一个高性能的键值对缓存数据库,是个nosql数据库,提供了5种数据类型供我们使用。另外redis支持两种持久化方式RDB和AOF。
3.nosql数据库是什么?和关系型数据库有什么区别?
nosql是指非关系型数据库,一般用于超大规模数据的存储,相比关系型数据库表与表之前可以设定逻辑关系,而非关系型数据库的话,数据之间是没有什么关系的。我之前在项目中用过的nosql数据库有:redis、mongodb(文档型数据库)。
4.Redis支持的数据类型?
字符串类型
list 我们可以向list的两端添加数据
集合set 存放的数据是无序的,集合中的数据是不重复的,由于它是无序的,所以不能通过下标来获取制定元素
zset(order set) 有序集合
hash 适合存储对象
实际上我们常用的也就是string 和hash
redis是通过key-value存储的 set key value string
hset key value hget key value
5.redis的持久化方式
有RDB和AOF这两种,RDB是一种快照的方式来存储的,这也是redis的默认的持久化方式,每隔一段对数据进行一次存储,默认是15S,这个也可以通过配置文件里修改,这种存储方式性能比较高。
还有一种是AOF是即时性的持久化方式,只要数据发生改变都会保存到硬盘一份,这种方式对数据的保存完整性比较高,但是性能比较差。而RDB存在的问题主要是服务器宕机或者断电,会造成数据丢失。
两种持久化方式可以同时使用。
6.什么是缓存穿透,如何避免?
缓存穿透是指缓存和数据库中都没有数据,而用户不断发起请求则这些请求会穿过缓存直接访问数据库,如发起为id为“-1”的数据或id为特别大不存在的数据。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VRJLC7gI-1621474804617)(assets\1589718533834.png)]
穿透的解决方案
缓存空对象:
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间(避免控制占用更多的存储空间),之后再访问这个数据将会从缓存中获取,保护了后端数据源;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4RwT01bc-1621474804619)(assets\1589718137952.png)]
代码实现
@Override
public TbItem selectItemInfo(Long itemId) {
//查询缓存
TbItem tbItem = (TbItem) redisClient.get(ITEM_INFO + ":" + itemId + ":"+ BASE);
if(tbItem!=null){
return tbItem;
}
tbItem = tbItemMapper.selectByPrimaryKey(itemId);
/********************解决缓存穿透************************/
if(tbItem == null){
//把空对象保存到缓存
redisClient.set(ITEM_INFO + ":" + itemId + ":"+ BASE,new TbItem());
//设置缓存的有效期
redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ BASE,30);
return tbItem;
}
//把数据保存到缓存
redisClient.set(ITEM_INFO + ":" + itemId + ":"+ BASE,tbItem);
//设置缓存的有效期
redisClient.expire(ITEM_INFO + ":" + itemId + ":"+ BASE,ITEM_INFO_EXPIRE);
return tbItem;
}
7.什么是缓存雪崩,如何避免?
缓存雪崩,是指在某一个时间段,缓存集中过期失效。
比如在商品抢购的时候,缓存里存的商品都是在晚上12点失效,而这时候刚好大量用户来访问这些商品,这时候数据库的压力就会一下上来了
所以在做电商项目的时候,一般是采取不同分类商品,缓存不同周期。在同一分类中的商品,加上一个随机因子。这样能尽可能分散缓存过期时间,而且,热门类目的商品缓存时间长一些,冷门类目的商品缓存时间短一些,也能节省缓存服务的资源。
解决方案:
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
设置热点数据永远不过期。
8.什么是缓存击穿,如何避免?
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个key不停进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
解决方案:
- 设置热点数据永远不过期
- 加分布式锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-drLcMfL1-1621474804620)(assets\20190520171934776.png)]
1、如何释放锁?del
2、业务处理失败?expire
代码实现
/**
* 分布式锁
* @param key
* @param count
* @param value
* @return
*/
public Boolean setnx(String key, Object value, long time) {
try {
return redisTemplate.opsForValue().setIfAbsent(key, value, time,
TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 查询商品信息
* @param itemId
* @return
*/
@Override
public TbItem selectItemInfo(Long itemId){
//1、先查询redis,如果有直接返回
TbItem tbItem = (TbItem) redisClient.get(ITEM_INFO+":"+itemId+":"+BASE);
if(tbItem!=null){
return tbItem;
}
/*****************解决缓存击穿***************/
if(redisClient.setnx("SETNX_BASC_LOCK_KEY"+":"+itemId,itemId,30L)){
//2、再查询mysql,并把查询结果缓存到redis,并设置失效时间
tbItem = tbItemMapper.selectByPrimaryKey(itemId);
/*****************解决缓存穿透*****************/
if(tbItem!=null){
redisClient.set(ITEM_INFO+":"+itemId+":"+BASE,tbItem);
redisClient.expire(ITEM_INFO+":"+itemId+":"+BASE,ITEM_INFO_EXPIRE);
}else{
redisClient.set(ITEM_INFO+":"+itemId+":"+BASE,null);
redisClient.expire(ITEM_INFO+":"+itemId+":"+BASE,30L);
}
redisClient.del(SETNX_BASC_LOCK_KEY+":"+itemId);
return tbItem;
}else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return selectItemInfo(itemId);
}
}
9.Redis到底是多线程还是单线程?线程安全?为什么效率高?
redis是单线程,线程安全
redis可以能够快速执行的原因:
(1) 绝大部分请求是纯粹的内存操作(非常快速)
(2) 采用单线程,避免了不必要的上下文切换和竞争条件
(3) 非阻塞IO - IO多路复用
但是我最近了解到,在redis6.0中是引入了多线程, 多线程任务可以分摊 Redis 同步 IO 读写负荷 。
10.除了redis,用过memcached的吗?
我知道他俩本质的区别就是Redis除了在内存中保存数据,还会把数据保存到硬盘中重启了服务器redis里的数据还有,但是memcached只保存到内存中,重启了服务器数据就没有了。还有就是应用场景不一样:Redis除了作为NoSQL数据库使用外,还能用做消息队列、数据堆栈和数据缓存等;Memcached适合于缓存SQL语句、数据集、用户临时性数据、延迟查询数据和session等。
- redis的数据类型比memcached更丰富
- redis支持持久化,而memcached不支持
11.Redis中数据同步问题
读取数据的时候先从redis里面查,若没有,再去数据库查,同时写到redis里面,并且要设置失效时间。存数据的时候要具体情况具体分析,可以选择同时插到数据库和redis(要是存放到redis中,最好设置失效时间),也可以选择直接插到数据库里面,删除内容时,把对应的redis里的数据的删除掉。第一个人查的时候从数据库里查询,把数据放到的缓存中,第二个人访问就可以直接从缓存中访问数据了。
12.Rdis中的数据淘汰策略,了解吗?
了解过。当redis的内存(默认500G)不足的时候,redis会根据配置的数据淘汰策略淘汰部分key,以保证写入成功。 当无淘汰策略时或没有找到适合淘汰的key时,Redis直接返回out of memory错误。 redis中数据淘汰策略有6种,
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 。
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
另外我们 在平时使用时应尽量主动设置/更新key的expire时间,主动剔除不活跃的旧数据,有助于提升查询性能
13.redis是否支持事务
支持。redis事务可以用来一次执行多个命令,批量操作在发送EXEC命令前被放入缓存,在事务中任意命令失败,其余命令仍然是可以执行的。事务可以结合watch命令来使用,在事务执行之前,使用watch监听某些键值,如果在EXEC命令执行之前,键值发生改变,事务就不能成功执行。
multi // 开启事务
set name a
set age 11
exec //事务提交
14.redis能否来做消息中间件
可以。redis有发布订阅机制,另外在5.0的版本中还新增加了一个redis stream数据结构来实现MQ。不过我们项目主要还是使用传统的MQ产品来做的,因为传统的MQ产品在使用上更加便捷一点。
Redis Stream 是 Redis 5.0 版本新增加的数据结构。
Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。
简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。
而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。
15.java如何使用redis?
两种方式:
- 在之前做的项目中,我们是使用jedis在java中操作redis,使用spring集成jedis
- springboot项目的话,我们是集成springDataRedis来操作redis
16.redis集群(了解)
据我了解的,redis集群有3种解决方案。
- 主从复制
主从复制模式中包含一个主数据库实例(master)与一个或多个从数据库实例(slave),如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AltH6AQB-1621474804622)(H:/桌面文档/技术话术/Redis/assets/1.png)]
客户端可对主数据库进行读写操作,对从数据库进行读操作,主数据库写入的数据会实时自动同步给从数据库。
具体工作机制为:
- slave启动后,向master发送SYNC命令,master接收到SYNC命令后通过bgsave保存快照(即上文所介绍的RDB持久化),并使用缓冲区记录保存快照这段时间内执行的写命令
- master将保存的快照文件发送给slave,并继续记录执行的写命令
- slave接收到快照文件后,加载快照文件,载入数据
- master快照发送完后开始向slave发送缓冲区的写命令,slave接收命令并执行,完成复制初始化
- 此后master每次执行一个写命令都会同步发送给slave,保持master与slave之间数据的一致性
主从复制的优缺点
优点:
- master能自动将数据同步到slave,可以进行读写分离,分担master的读压力
- master、slave之间的同步是以非阻塞的方式进行的,同步期间,客户端仍然可以提交查询或更新请求
缺点:
- 不具备自动容错与恢复功能,master或slave的宕机都可能导致客户端请求失败,需要等待机器重启或手动切换客户端IP才能恢复
- master宕机,如果宕机前数据没有同步完,则切换IP后会存在数据不一致的问题
- 难以支持在线扩容,Redis的容量受限于单机配置
- Sentinel(哨兵)模式
哨兵模式基于主从复制模式,只是引入了哨兵来监控与自动处理故障。
哨兵顾名思义,就是来为Redis集群站哨的,一旦发现问题能做出相应的应对处理。其功能包括
- 监控master、slave是否正常运行
- 当master出现故障时,能自动将一个slave转换为master(大哥挂了,选一个小弟上位)
- 多个哨兵可以监控同一个Redis,哨兵之间也会自动监控
哨兵模式的优缺点
优点:
- 哨兵模式基于主从复制模式,所以主从复制模式有的优点,哨兵模式也有
- 哨兵模式下,master挂掉可以自动进行切换,系统可用性更高
缺点:
- 同样也继承了主从模式难以在线扩容的缺点,Redis的容量受限于单机配置
- 需要额外的资源来启动sentinel进程,实现相对复杂一点,同时slave节点作为备份节点不提供服务
- Cluster模式
哨兵模式解决了主从复制不能自动故障转移,达不到高可用的问题,但还是存在难以在线扩容,Redis容量受限于单机配置的问题。Cluster模式实现了Redis的分布式存储,即每台节点存储不同的内容,来解决在线扩容的问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y9sMWq7l-1621474804627)(H:/桌面文档/技术话术/Redis/assets/3.png)]
Cluster采用无中心结构,它的特点如下:
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽
- 节点的fail是通过集群中超过半数的节点检测失效时才生效
- 客户端与redis节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
Cluster模式的具体工作机制:
- 在Redis的每个节点上,都有一个插槽(slot),取值范围为0-16383
- 当我们存取key的时候,Redis会根据CRC16的算法得出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作
- 为了保证高可用,Cluster模式也引入主从复制模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点
- 当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点都宕机了,那么该集群就无法再提供服务了
Cluster模式集群节点最小配置6个节点(3主3从,因为需要半数以上),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。
17.分布式锁
我们使用redis的setnx来做分布式锁。
if(redisClient.setnx("SETNX_BASC_LOCK_KEY"+":"+itemId,itemId,30L)){
//2、再查询mysql,并把查询结果缓存到redis,并设置失效时间
tbItem = tbItemMapper.selectByPrimaryKey(itemId);
/*****************解决缓存穿透*****************/
if(tbItem!=null){
redisClient.set(ITEM_INFO+":"+itemId+":"+BASE,tbItem);
redisClient.expire(ITEM_INFO+":"+itemId+":"+BASE,ITEM_INFO_EXPIRE);
}else{
redisClient.set(ITEM_INFO+":"+itemId+":"+BASE,null);
redisClient.expire(ITEM_INFO+":"+itemId+":"+BASE,30L);
}
redisClient.del(SETNX_BASC_LOCK_KEY+":"+itemId);
return tbItem;
}else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return selectItemInfo(itemId);
}
EM_INFO+":"+itemId+":"+BASE,null);
redisClient.expire(ITEM_INFO+":"+itemId+":"+BASE,30L);
}
redisClient.del(SETNX_BASC_LOCK_KEY+":"+itemId);
return tbItem;
}else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return selectItemInfo(itemId);
}