Redis高频面试题

缓存穿透

场景说明

恶意使用一些非法的key,key对应的数据在数据库不存在,由于不存在,故没有缓存起来,所以这些非法key的请求都到了数据库,可能会压垮数据库。

解决方法

  • 一种简单粗暴的方法就是,如果从数据库返回的结果为空,仍然缓存这个空结果,但它的过期时间要设置的短一些,比如三分钟,以防Redis中缓存的数据过多,占用太多的内存。
  • 采用布隆过滤器,将所有的合法结果散列到足够大的bitmap中,一个一定不存在的数据会被bitmap拦截掉,从而避免了对底层存储系统的查询压力

缓存击穿

场景说明

key对应的数据存在,但在Redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期,同时从后端DB加载数据并回设到缓存,这个时候大并发请求可能会瞬间把后端DB压垮。

解决办法

使用互斥锁

如果是单机用ReentrantLock,如果是集群则用分布式锁。并且要用到分段锁思想和double checked locking.伪代码如下:

public static String call() throws InterruptedException {
    String key = "";
    String value = getCache(key);
    if (value != null) {
        return value;
    }
    ReentrantLock lock = map.computeIfAbsent(key, k -> new ReentrantLock());
    if (lock.tryLock(3L, TimeUnit.SECONDS)) {
        try {
            value = getCache(key);
            if (value != null) {
                return value;
            }
            value = getFromDB(key);
            setCache(key, value);
            return value;
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            lock.unlock();
        }
    } else {
        value = getCache(key);
        if (value != null) {
            return value;
        }
    }
    return value;
}

这种方法虽然保护了DB,但是会影响系统吞吐量。

后台定时任务刷新缓存

后台定义一个定时任务专门主动更新缓存数据,比如一个缓存中的数据过期时间是5分钟,那么定时任务每隔4分钟刷新数据。这种方案比较容易理解,但是会增加系统复杂度。比较适合那些key较少cache粒度大的业务。

检查更新

将缓存key的过期时间(绝对时间)一起保存到缓存中,可以拼接,可以添加新字段,可以采用单独的key保存。不管用什么方式,只要两者建立好关联关系就行。在每次执行get操作后,都将get出来的缓存过期时间与当前系统时间做一个对比,如果 缓存过期时间-当前系统时间<=1分钟(自定义的一个值),则主动更新缓存.这样就能保证缓存中的数据始终是最新的(和方案一一样,让数据不过期.)
这种方案在特殊情况下也会有问题。假设缓存过期时间是12:00,而 11:59
到 12:00这 1 分钟时间里恰好没有 get 请求过来,又恰好请求都在 12:00 分的时候高并发过来,那就悲剧了。这种情况比较极端,但并不是没有可能。因为“高并发”也可能是阶段性在某个时间点爆发。

多级缓存

采用 L1 (一级缓存)和 L2(二级缓存) 缓存方式,L1 缓存失效时间短,L2 缓存失效时间长。 请求优先从 L1 缓存获取数据,如果 L1缓存未命中则加锁,只有 1 个线程获取到锁,这个线程再从数据库中读取数据并将数据再更新到到 L1 缓存和 L2 缓存中,而其他线程依旧从 L2 缓存获取数据并返回。
这种方式,主要是通过避免缓存同时失效并结合锁机制实现。所以,当数据更新时,只能淘汰 L1 缓存,不能同时将 L1 和 L2 中的缓存同时淘汰。L2 缓存中可能会存在脏数据,需要业务能够容忍这种短时间的不一致。而且,这种方案可能会造成额外的缓存空间浪费。代码示例如下:

public static String call(String key) throws InterruptedException {
	String value = getLevel1Cache(key);
	if (value != null) {
		return value;
	}
	ReentrantLock lock = map.computeIfAbsent(key, k -> new ReentrantLock());
	if (lock.getHoldCount() > 0) {
		value = getLevel2Cache(key);
		if (value == null) {
			for (int count = 0; count < 3; count++) {
				TimeUnit.MILLISECONDS.sleep(20L +  count * 150);
				value = getLevel1Cache(key);
				if (value != null) {
					return value;
				}
			}
		}
	} else {
		if (lock.tryLock(500L, TimeUnit.MILLISECONDS)) {
			try {
				value = getLevel1Cache(key);
				if (value != null) {
					return value;
				}
				value = getFromDB(key);
				setLevel1Cache(key, value);
				setLevel2Cache(key, value);
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				lock.unlock();
			}
		} else {
			return getLevel1Cache(key);
		}
	}
	return value;
}

缓存雪崩

场景说明

当缓存服务器重启或者大量缓存在某一个时间段失效,这样在失效的时候,也会给后端DB带来很大的压力。

解决办法

  • 缓存时间设置成随机值,比如1-5分钟的随机值,这样缓存就不会同时失效

删除BigKey

问题

BigKey是指key对应的value很大,对这类bigkey直接使用del命令进行删除,会导致redis长时间阻塞,甚至崩溃,因为redis是单线程的,单个命令执行时间过长,就会阻塞其他命令。

一般来讲,字符串类型的bigkey删除都挺快的,不会引起问题,而hash、list、set、zset等类型的bigkey在删除时容易引起问题,总体上看,元素个数越多,所占的空间越大,删除越慢。

解决方案

设计上避免BigKey

不存BigKey,也就不需要解决这个问题,但有时候无法避免,比如一开始内存占用不多,慢慢才变多的。

渐进式删除

  1. 先将key改名,通过旧key无法访问数据了,等于逻辑上删除了key。
  2. 分批删除,通过 scan 命令遍历改名后的bigkey,每次取得少部分元素,对其删除,然后再获取和删除下一批元素。

unlink

Redis 4.0 推出了一个重要命令unlink,用来拯救 del 删大key的困境。unlink的工作思路:

  1. 在所有命名空间中把 key 删掉,立即返回,不阻塞。
  2. 后台线程执行真正的释放空间的操作。

UNLINK 基本可以替代 del,但个别场景还是需要 del 的,例如在空间占用积累速度特别快的时候就不适合使用UNLINK,因为 UNLINK 不是立即释放空间。

详细参考:https://blog.csdn.net/wade1010/article/details/128841411?spm=1001.2014.3001.5506

谨慎使用的命令

面试时一般会问“在生产环境谨慎使用的命令”或“使用redis时应该注意什么”,可以回答keys命令。

KEYS foo*

将返回所有以"foo"开头的键,例如"foobar"、"football"等。

如果要匹配所有以"foo"结尾的键,可以使用以下命令:

KEYS *foo

这将返回所有以"foo"结尾的键,例如"barfoo"、"bazfoo"等。

如果要匹配包含"foo"的键,可以使用以下命令:

KEYS *foo*

这将返回所有包含"foo"的键,例如"foobar"、"barfoo"等。

在Redis中,可以使用通配符 “*” 和 “?” 来进行模糊匹配。其中,“*” 表示匹配任意多个字符,“?” 表示匹配单个字符。

KEYS命令在处理大量键时可能会对性能产生影响,因为它需要遍历所有键来进行匹配。如果需要在生产环境中使用模糊匹配,建议使用SCAN命令来替代KEYS命令,因为SCAN命令可以逐步迭代地获取匹配的键,减少对性能的影响。

scan 0 match key99* count 1000

更详细的:https://zhuanlan.zhihu.com/p/651194482

集群数据倾斜怎么解决

参考:https://blog.csdn.net/wang0907/article/details/128373795

redis原子性保证

参考:https://www.cnblogs.com/gossip/p/13901425.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值