做网站的例子建设网站的五个步骤
news/
2025/9/22 15:25:45/
文章来源:
做网站的例子,建设网站的五个步骤,网站运行环境配置,个人网站开发网#x1f468;#x1f393;作者简介#xff1a;一位大四、研0学生#xff0c;正在努力准备大四暑假的实习 #x1f30c;上期文章#xff1a;Redis#xff1a;原理速成项目实战——Redis实战4#xff08;解决Redis缓存穿透、雪崩、击穿#xff09; #x1f4da;订阅专… 作者简介一位大四、研0学生正在努力准备大四暑假的实习 上期文章Redis原理速成项目实战——Redis实战4解决Redis缓存穿透、雪崩、击穿 订阅专栏Redis原理速成项目实战 希望文章对你们有所帮助 上一篇文章讲解了缓存击穿问题以及解决缓存击穿问题的2种解决思路即互斥锁与逻辑过期这里将分别用这两种方式解决缓存击穿问题。 互斥锁、逻辑过期解决缓存击穿问题 互斥锁解决缓存击穿获取锁与释放锁方法封装业务逻辑修改测试 逻辑过期解决缓存击穿代码实现测试 互斥锁解决缓存击穿
根据上次讲解的互斥锁解决缓存击穿问题的方式我们可以将客户端查询数据的流程修改为如下 这里有比较关键的点这里的锁并不是我们平常用的锁平常的锁如果没有获取到的话就一直等待而这边我们需要自定义逻辑。 我们该如何自定义获取锁跟释放锁的逻辑我们可以利用Redis的一些数据结构的特性。 我们可以联想到之前学习Redis数据结构的时候用到的setnx当且仅当key不存在的时候setnx才会执行set操作 可以看到当我们的key已经是存在的情况下我们用setnx指令无法对其value进行修改。所以如果有无数线程进行setnx操作的时候只有第一个进行操作的线程可以写入value其他线程都会失败。 所以这就满足了我们所说的互斥锁的实现方式其实这也是分布式锁的一个基本原理当然真正的分布式锁还是比较复杂的。 而释放锁只需要执行del操作即可这样其他线程就可以获取到这个锁了 当然了最好别把锁永远加在那因为如果加了锁的线程他的工作没办法完成他就永远没办法释放锁因此我们在执行setnx操作的时候需要设置一下有效期。
获取锁与释放锁方法封装
进行业务逻辑编写之前我们可以先把获取锁与释放锁的方法给自定义一下
private boolean tryLock(String key){//尝试获取锁//opsForValue里面没有真正的setnx而是setIfAbsent表示如果不存在就执行set//值就随便设定一下重点是要获取到锁但是设定了TTL为10sBoolean flag stringRedisTemplate.opsForValue().setIfAbsent(key, 1, 10, TimeUnit.SECONDS);/*** 如果是直接返回flag可能会有拆箱操作造成空指针需要用BooleanUtil工具类* 因为Boolean不是基本类型的boolean是boolean的封装类*/return BooleanUtil.isTrue(flag);
}private void unlock(String key){//释放锁stringRedisTemplate.delete(key);
}业务逻辑修改
我们可以先把之前会出现缓存穿透的代码给封装起来封装成返回值为Shop类型的防止代码丢失之后章节的代码优化会解决缓存穿透问题
public Shop queryWithPassThrough(Long id){String key CACHE_SHOP_KEY id;//从Redis中查询商铺缓存存储对象可以用String或者Hash这里用StringString shopJson stringRedisTemplate.opsForValue().get(key);//判断是否存在if (StrUtil.isNotBlank(shopJson)) {//存在直接返回Shop shop JSONUtil.toBean(shopJson, Shop.class);return shop;}//判断命中的是否是nullif (shopJson ! null){return null;}//不存在根据id查询数据库Shop shop getById(id);if (shop null){//存一个null到Redis中//这种没用的信息TTL没必要设置太长了这里我设置成了2minstringRedisTemplate.opsForValue().set(key, , CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}//存在写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);//返回return shop;
}同样的我们也可以把互斥锁解决缓存击穿的代码给单独封装起来注意我们不管获得互斥锁的线程有没有正常执行即便执行不成功也要在最后进行unlock因此我们要利用try…catch…finally
public Shop queryWithMutex(Long id){String key CACHE_SHOP_KEY id;//从Redis中查询商铺缓存存储对象可以用String或者Hash这里用StringString shopJson stringRedisTemplate.opsForValue().get(key);//判断是否存在if (StrUtil.isNotBlank(shopJson)) {//存在直接返回Shop shop JSONUtil.toBean(shopJson, Shop.class);return shop;}//判断命中的是否是nullif (shopJson ! null){//返回错误信息return null;}//未命中开始实现缓存重建//尝试获取互斥锁锁的key跟缓存的key不一样String lockKey lock:shop: id;Shop shop null;try {boolean isLock tryLock(lockKey);//判断是否获取成功if (!isLock){//失败则休眠并重试递归Thread.sleep(50);//单位ms//如果担心递归造成爆栈可以用循环一样的return queryWithMutex(id);}//成功就直接开始重建查询数据库并处理后保存到Redisshop getById(id);//由于在这里我们的数据库在本地重建很快所以添加休眠时间来模拟数据库重建过程的耗时长方便测试Thread.sleep(200);//不存在返回错误if (shop null){//存一个null到Redis中//这种没用的信息TTL没必要设置太长了这里我设置成了2minstringRedisTemplate.opsForValue().set(key, , CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}//存在写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {//释放互斥锁unlock(lockKey);}//返回return shop;
}queryById方法调用这个方法即可 Overridepublic Result queryById(Long id) {//缓存穿透的代码调用//Shop shop queryWithPassThrough(id);//互斥锁解决缓存击穿Shop shop queryWithMutex(id);if (shop null) {return Result.fail(店铺不存在);}//返回return Result.ok(shop);}测试
我们先把缓存清空然后运行代码再进行测试我们可以使用一个很好用的测试工具jmeter下载可以看下面这个博主的文章 jmeter下载教程 官网下载的实在是太慢了建议大家直接去这个博主的网盘里面下载。 我们可以这样设定我们设定我们的并发量为1000即有1000个线程同时访问 并配置好我们的http请求 运行完以后打开查看结果树 数据都查到了。 打开汇总报告可以看到我们的吞吐量接近200 如此大的数据量打下去但是我们的日志显示我们的数据库只执行了一次的查询语句 说明我们已经使用互斥锁成功避免了缓存击穿。
逻辑过期解决缓存击穿
代码实现
根据上次讲解的逻辑过期解决缓存击穿问题的方式我们可以将客户端查询数据的流程修改为如下 其实通常情况因为是热点key一般都是会出现在Redis里面的且因为我们没有设置TTL所以热点key是一定会一直存在的但为了严谨起见还是在判定缓存未命中的时候返回空。 我们主要解析一下主要的流程 1、我们判断一下缓存是否逻辑过期了如果没有过期我们直接返回信息到客户端即可 2、如果缓存逻辑过期了这个线程就尝试获取互斥锁如果获取成功说明它是第一个访问Redis的这个过期key的线程那么这个线程要做2件事 1返回这个旧数据给客户虽然数据是旧的但是这是一种暂时的牺牲 2开辟新的线程来进行缓存数据的重建重建完毕就释放这个互斥锁 3、除了第2种情况说的这个线程其他线程在知道自己访问的数据过期之后获取互斥锁都会失败那么这时候只需要直接返还数据给客户就好了可能是旧数据也可能是新数据第一个线程释放锁或者缓存数据重建成功了 首先我们要对Shop类增加逻辑过期时间这样一个字段一种方案是直接添加这种会违背开闭原则一种是可以新增加一个类并且类中包含了逻辑过期时间expireTime但是该怎么把这个属性添加到Shop里面呢可以让Shop继承这个类就可以获得这个类中的属性但同样会修改Shop这个类的源代码同样违背开闭原则所以最好的方法就是用关联来代替继承 如果不是很清楚的话可以先去学一下Java设计模式。
为了方便演示我们首先要进行一个数据预热我们需要有这个一个带着逻辑过期时间的店铺信息因此这里就先用单元测试的方式来让带着expireTime属性的数据存入Redis里面。
1、先编写一个类用于将带着逻辑过期时间的Shop存入Redis里面 public void saveShop2Redis(Long id, Long expireSeconds){//查询店铺数据Shop shop getById(id);//封装逻辑过期时间RedisData redisData new RedisData();//注入Shop对象redisData.setData(shop);//注入逻辑过期时间redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));//写入Redis不要设置TTLstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY id, JSONUtil.toJsonStr(redisData));}2、编写单元测试代码
SpringBootTest
class HmDianPingApplicationTests {Resourceprivate ShopServiceImpl shopService;Testvoid testSaveShop(){shopService.saveShop2Redis(1L, 10L);}}我们运行这个单元测试后打开Redis客户端 证明已经存储成功。
现在正式开始用逻辑过期思想去解决缓存击穿的问题编写这个方法 private static final ExecutorService CACHE_REBUILD_EXECUTOR Executors.newFixedThreadPool(10);public Shop queryWithLogicalExpire(Long id){String key CACHE_SHOP_KEY id;//从Redis中查询商铺缓存存储对象可以用String或者Hash这里用StringString shopJson stringRedisTemplate.opsForValue().get(key);//判断是否存在if (StrUtil.isBlank(shopJson)) {//未命中直接返回return null;}//命中先把json反序列化成对象RedisData redisData JSONUtil.toBean(shopJson, RedisData.class);JSONObject data (JSONObject) redisData.getData();//注意我们获取到了data信息以后返回的会是一个JSONObject的格式Shop shop JSONUtil.toBean(data, Shop.class);LocalDateTime expireTime redisData.getExpireTime();//判断是否过期if (expireTime.isAfter(LocalDateTime.now())){//未过期直接返回店铺信息return shop;}//已过期缓存重建//获取互斥锁String lockKey LOCK_SHOP_KEY id;//LOCK_SHOP_KEYlock:shop:boolean isLock tryLock(lockKey);//判断是否获取锁成功if (isLock){//成功获取锁开启独立线程来实现缓存重建用线程池来做CACHE_REBUILD_EXECUTOR.submit(() - {try {// 重建缓存this.saveShop2Redis(id, 20L);} catch (Exception e) {throw new RuntimeException(e);}finally {//释放锁unlock(lockKey);}});}//没有成功获取直接返回过期商铺信息return shop;}为了方便验证之前的缓存重建函数我们给他增加一个模拟重建时间200ms public void saveShop2Redis(Long id, Long expireSeconds) throws InterruptedException {//查询店铺数据Shop shop getById(id);Thread.sleep(200);//封装逻辑过期时间RedisData redisData new RedisData();//注入Shop对象redisData.setData(shop);//注入逻辑过期时间redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));//写入Redis不要设置TTLstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY id, JSONUtil.toJsonStr(redisData));}之前存储在Redis的数据其实早就已经逻辑过期了只是因为我们没有设置TTL它依旧存在于Redis里面只是可能已经过期了。 queryById直接改成调用该方法接着运行代码。
测试
1、我们直接将数据库中的餐厅名从101修改为102使得数据库数据与缓存不一致
2、打开jmeter进行测试这里把线程数改小一点不然不好观察 运行后观察结果树可以发现我们一开始查到的就是旧数据且这个过程会持续一段时间 到后面查询到的数据就是正常的数据了 查看idea后台可以发现我们只执行了一次重构说明只有一个线程操作了数据库其他数据库都被互斥锁拦住了 这也证明了逻辑过期方法会造成短暂的数据不一致的情况。
综上缓存击穿问题的两种解决思想其demo都已经调通。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/909464.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!