java面试(缓存Redis)

为什么使用缓存

高性能,高可用,高并发。

什么是缓存击穿?缓存穿透?缓存雪崩?

击穿:redis中没有查询到数据。解决:设置热点数据永不过期。加载DB时防止并发。

穿透:redis和mysql中都没有查询到数据。解决:参数校验,将没有数据的情况也存入redis中,引入布隆过滤器

雪崩:redis中大量数据同时过期。解决:设置不同的过期时间。

布隆过滤器:

  1. 如果判断一个元素不在集合中则一定不在
  2. 如果判断一个元素在集合中则存在一定误判率
  3. 布隆过滤器只能加数据不能减数据

对数据进行修改时,如何保证Redis与数据库的数据一致?

  • 先删缓存,后写数据库。存在脏读。
    解决:
    1.先把缓存修改为特殊值(-999),客户端读到特殊值延时查询。
    2.延时双删,先删缓存,再写数据库,再删缓存。
  • 先写数据库,再删缓存。存在缓存删除失败,数据不一致。
    解决:
    1.缓存设置过期时间
    2.引入MQ,保证原子操作。mq设置两个消费者同时删除缓存和db,删除失败消息还在,MQ重试机制会删除。
    通用解决:
    将热点数据设置为永不过期,但再value中写入逻辑过期,另起线程扫描key,对于逻辑上过期的缓存进行删除。
    总结:始终只能保证一定时间内的最终一致性。

如何设计一个分布式锁?并优化

分布式锁:所有进程都能访问到的地方,设置一个锁资源,让进程来竞争。(数据库,zookeeper,Redis)

Redis实现分布式锁:

SETNX key value:当key不存在时,就将key设置为value。并返回1.如果key存在,就返回0.

EXPIRE key locktime:设置key的有效时长。

DEL key:删除

GETSET key value:先GET,在SET,先返回key对应的值,如果没有,将key设置成value。

1.最简单的分布式锁:SETNX 加锁,DEL解锁。

问题1:锁获取失败后不回主动解锁,这个锁就被锁死了。

解决1:给锁设置时长。

问题2:SETNX和EXPIRE不是原子性的,获取锁进程没到EXPIRE指令就挂了。

解决2:将锁的内容设置为过期时间(客户端时间+过期时长),SETNX获取失败时,拿这个时间和当前时间比对,如果过期,就先删除锁,再重新上锁。

问题3:在高并发场景下,会产生多个进程同时拿到锁的情况。

解决3:SETNX失败后,获取锁上时间戳,然后用GETSET,将自己的过期时间更新上去,并获取旧值,和之前获取的时间戳不一致,就表示这个锁被其他线程占用。

java 复制代码
public boolean tryLock(RedisConnection conn) {
	long nowTIme = System.currentTimeMills();
	long expireTime = nowTime + 1000;
	if(conn.SETNX("myKey", "1") == 1) {
		conn.EXPIRE("myKey", 1000);
		return true;
	} else {
		long oldVal = conn.get("myTime");
		if(oldVal != null && oldVal < nowTime) {
			long currnetVal = conn.GETSET("myKey", expireTime);  //CAS,如果和之前获取的锁不一样,就失败
			if(oldVal == currentVal) {
				conn.SET("mykey", 1000);
				return true;
			}
			return false;
		}
		return false;
	}
}

注:上面优化根本问题是SETNX和EXPIRE指令无法保证原子性。Redis2.6提供了直接执行Lua脚本方式。通过Lua脚本保证原子性。Redission就是在此基础上实现公平锁,非公平锁等。

Redis如何设置Key的过期时间?

redis设置过期时间:1.EXPIRE 2.SETEX

实现原理:

1.定期删除:每隔一段时间执行一次删除过期key的操作。

2.懒汉式删除:当使用get,getset等指令获取数据时,判断key是否过期,过期先把key删除,在执行后面操作。

定期删除会遍历每个databasae(默认16个),检查当前库中指定个数的key(默认20个)。随机抽查这些key,过期就删除。

海量数据下,如何快速查找一条记录?

1.使用布隆过滤器,快速过滤不存在的记录。使用redis的bitmap结构来实现布隆过滤器。

2.在Redis中建立数据缓存。以普通字符串形式存储(userId->UserInfo)。以hash来存储(userId key -> Username field)。以整个hash来存储所有的数据。UserInfo -> field用userId,value就用user.json。一个hash最多支持2^32-1个键值对。

缓存击穿:对不存在的数据也建立key。

缓存过期:将热点数据设置成永不过期,定期重建缓存,使用分布式锁重建缓存。

3.查询优化:redis按槽位分配数据,自己实现槽位激素那,找到记录应分配的机器,直接去目标机器上去找。

相关推荐
杨充5 分钟前
13.观察者模式设计思想
java·redis·观察者模式
Lizhihao_7 分钟前
JAVA-队列
java·开发语言
喵叔哟16 分钟前
重构代码之移动字段
java·数据库·重构
喵叔哟16 分钟前
重构代码之取消临时字段
java·前端·重构
fa_lsyk19 分钟前
maven环境搭建
java·maven
Daniel 大东38 分钟前
idea 解决缓存损坏问题
java·缓存·intellij-idea
wind瑞44 分钟前
IntelliJ IDEA插件开发-代码补全插件入门开发
java·ide·intellij-idea
HappyAcmen44 分钟前
IDEA部署AI代写插件
java·人工智能·intellij-idea
马剑威(威哥爱编程)1 小时前
读写锁分离设计模式详解
java·设计模式·java-ee
鸽鸽程序猿1 小时前
【算法】【优选算法】前缀和(上)
java·算法·前缀和