System.out.println(“######开始发送数据库DB请求########”);
Users user = userMapper.getUser(id);
String value = null;
if (user == null) {
// 标识为null
value = “”;
} else {
value = user.getName();
}
redisService.setString(key, value);
return value;
}
注意:再给对应的ip存放真值的时候,需要先清除对应的之前的空缓存。
3. 缓存击穿
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
通俗理解:有一个热点key突然过期
,这时大量请求进来全部去查询数据库,可能导致数据库崩掉。
热点key:
某个key访问非常频繁,当key失效的时候有大量线程来构建缓存,导致负载增加,系统崩溃。
解决办法:
① 使用锁:单机用synchronized,lock等,分布式用分布式锁。
② 实时调整:缓存过期时间不设置,而是设置在key对应的value里。如果检测到存的时间超过过期时间则异步更新缓存。
③ 预先设置热门数据: 在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
使用分布式锁要满足的几个条件:
-
系统是一个分布式系统(关键是分布式,单机的可以使用ReentrantLock或者synchronized代码块来实现)
-
共享资源(各个系统访问同一个资源,资源的载体可能是传统关系型数据库或者NoSQL)
-
同步访问(即有很多个进程同时访问同一个共享资源。)
什么是分布式锁?
① 线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。
② 进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。
③ 分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
应用的场景
线程间并发问题和进程间并发问题都是可以通过分布式锁解决的,但是强烈不建议这样做!因为采用分布式锁解决这些小问题是非常消耗资源的!分布式锁应该用来解决分布式情况下的多进程并发问题才是最合适的。
有这样一个情境,线程A和线程B都共享某个变量X。
如果是单机情况下(单JVM),线程之间共享内存,只要使用线程锁就可以解决并发问题。
如果是分布式情况下(多JVM),线程A和线程B很可能不是在同一JVM中,这样线程锁就无法起到作用了,这时候就要用到分布式锁来解决。
分布式锁可以基于很多种方式实现,比如zookeeper、redis… 不管哪种方式,他的基本原理是不变的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识。
这里主要讲如何用redis实现分布式锁。
使用redis的setNX命令实现分布式锁
实现的原理
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。redis的SETNX命令可以方便的实现分布式锁。
2、基本命令解析
1)setNX(SET if Not eXists)
语法:
SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写
返回值:
设置成功,返回 1 。
设置失败,返回 0 。
例子:
redis> EXISTS job # job 不存在
(integer) 0
redis> SETNX job “programmer” # job 设置成功
(integer) 1
redis> SETNX job “code-farmer” # 尝试覆盖 job ,失败
(integer) 0
redis> GET job # 没有被覆盖
“programmer”
所以我们使用执行下面的命令SETNX可以用作加锁原语(locking primitive)。比如说,要对关键字(key) foo 加锁,
客户端可以尝试以下方式:
SETNX lock.foo <current Unix time + lock timeout + 1>
如果 SETNX返回 1
,说明客户端已经获得了锁,SETNX将键 lock.foo 的值设置为锁的超时时间(当前时间 + 锁
的有效时间)。 之后客户端可以通过 DEL lock.foo
来释放锁。
如果 SETNX返回 0
,说明 key 已经被其他客户端上锁了。如果锁是非阻塞(non blocking lock)的,我们可以选
择返回调用,或者进入一个重试循环,直到成功获得锁或重试超时(timeout)。
2)getSET
先获取key对应的value值。若不存在则返回nil,然后将旧的value更新为新的value。
语法:
GETSET key value
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
当 key 存在但不是字符串类型时,返回一个错误。
返回值:
返回给定 key 的旧值[之前的值]。
当 key 没有旧值时,也即是, key 不存在时,返回 nil 。
注意的关键点:(回答面试的核心点)
1、同一时刻只能有一个进程获取到锁。setnx
2、释放锁:锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁;
(最简单的方式就是del, 如果在删除之前死锁了。)
ex:
53秒设置–58秒到期
当前时间为56秒,没有过期
当前时间为59秒,过期.(当前时间大于设置的时间)
死锁情况是在判断超时后,直接操作业务,设置过期时间,执行业务,然后删除释放锁。其他进程再次通过 setnx
来抢锁。
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-DRVskEq2-1710889902241)]
[外链图片转存中…(img-YZhJBA7O-1710889902241)]
[外链图片转存中…(img-ARRxaTsX-1710889902242)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-ePiAxdPK-1710889902242)]