手机端公司网站怎么做ip查询网站备案查询系统
news/
2025/10/8 2:57:58/
文章来源:
手机端公司网站怎么做,ip查询网站备案查询系统,wordpress没有备案,做a 免费网站↑↑↑请在文章头部下载测试项目原代码↑↑↑ 文章目录 前言4.2 商户查询缓存4.2.1 缓存介绍4.2.2 查询商户信息的传统做法4.2.2.1 接口文档4.2.2.2 代码实现4.2.2.3 功能测试 4.2.3 查询商户信息添加Redis缓存4.2.3.1 逻辑分析4.2.3.2 代码实现4.2.3.3 功能测试 4.2.3 数据一致…↑↑↑请在文章头部下载测试项目原代码↑↑↑ 文章目录 前言4.2 商户查询缓存4.2.1 缓存介绍4.2.2 查询商户信息的传统做法4.2.2.1 接口文档4.2.2.2 代码实现4.2.2.3 功能测试 4.2.3 查询商户信息添加Redis缓存4.2.3.1 逻辑分析4.2.3.2 代码实现4.2.3.3 功能测试 4.2.3 数据一致性问题及其解决方案4.2.3.1 问题分析4.2.3.2 实现商户信息的双写一致 4.2.4 缓存穿透问题及其解决方案4.2.4.1 什么是缓存穿透问题4.2.4.2 缓存穿透问题的解决方案4.2.4.3 基于缓存空对象解决缓存穿透问题 4.2.5 缓存击穿问题及其解决4.2.5.1 什么是缓存击穿问题4.2.5.2 缓存击穿问题的解决方案4.2.5.3 利用互斥锁解决缓存击穿问题4.2.5.4 利用逻辑过期解决缓存击穿问题 前言
上一节实现了Redis实战项目的第一个功能短信登录。项目最初采用session方式实现短信登录但该方式存在session共享问题因此改为基于Redis来实现。
Redis从入门到精通(四)Redis实战(一)短信登录
4.2 商户查询缓存
4.2.1 缓存介绍
缓存Cache即数据交换的缓存区俗称的缓存就是指缓存区中的数据。缓存最大的特点就是它运行在内存中速度快可以大大降低用户访问并发量带来的服务器读写压力。
但缓存也有不足之处就是会增加代码复杂度和运营成本 在实际开发中会构筑多级缓存来使系统运行速度进一步提升包括
浏览器缓存浏览器在用户磁盘上对最近请求过的文档进行存储当访问者再次请求这个页面时浏览器就可以从本地磁盘显示文档这样就可以加速页面的阅览。。应用层缓存例如Tomcat本地缓存或Redis缓存。数据库缓存例如MySQL缓存是指MySQL数据库服务器中的内存区域用于存储经常访问的数据和查询结果以提高查询性能和响应时间。。CPU缓存CPU的L1、L2、L3级缓存。 4.2.2 查询商户信息的传统做法
根据ID查询商户信息传统做法是直接从数据库查询并将查询结果返回。
4.2.2.1 接口文档
项目说明请求方式GET请求路径/shop/{id}请求参数id返回值Shop
4.2.2.2 代码实现
在ShopController类中实现一个queryById()方法调用IShopService接口的queryShopById()方法
// com.star.redis.dzdp.controller.ShopControllerSlf4j
RestController
RequestMapping(/shop)
public class ShopController {Resourceprivate IShopService shopService;/*** 根据ID查询商户信息* author hsgx* since 2024/4/3 11:23* param id* return com.star.redis.dzdp.pojo.BaseResultcom.star.redis.dzdp.pojo.Shop*/GetMapping(/{id})public BaseResultShop queryById(PathVariable Long id) {return shopService.queryShopById(id);}
}然后在IShopService接口的实现类ShopServiceImpl类中实现queryShopById()方法
// com.star.redis.dzdp.service.impl.ShopServiceImplSlf4j
Service
Transactional(rollbackFor Exception.class)
public class ShopServiceImpl extends ServiceImplShopMapper, Shop implements IShopService {Overridepublic BaseResultShop queryShopById(Long id) {log.info(query Shop by id {}, id);// 查询数据库Shop shop getById(id);if(shop null) {return BaseResult.setFail(商户不存在);}return BaseResult.setOkWithData(shop);}
}4.2.2.3 功能测试
当id100时返回“商户不存在” 当id1时成功获取到商户信息耗时62ms 4.2.3 查询商户信息添加Redis缓存
4.2.3.1 逻辑分析
通常情况下该功能的逻辑是在查询数据库之前先查询Redis如果Redis中存在数据则直接从Redis中返回数据如果Redis中没有数据再查询数据库并将查询结果保存到Redis中。 4.2.3.2 代码实现
修改ShopServiceImpl类中实现queryShopById()方法添加查询Redis的逻辑
// com.star.redis.dzdp.service.impl.ShopServiceImplResource
private StringRedisTemplate stringRedisTemplate;Override
public BaseResultShop queryShopById(Long id) {log.info(query Shop by id {}, id);// 1.构建Key并从Redis中查询商户信息String key cache:shop: id;String shopJson stringRedisTemplate.opsForValue().get(key);log.info(get from Redis: Key {}, Value {}, key, shopJson);// 2.判断商户信息是否存在if(StrUtil.isNotBlank(shopJson)) {// 3.存在直接返回Shop shop JSONUtil.toBean(shopJson, Shop.class);return BaseResult.setOkWithData(shop);}// 4.不存在根据ID查询数据库Shop shop getById(id);if(shop null) {return BaseResult.setFail(商户不存在);}// 5.将商户信息写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));log.info(set to Redis: Key {}, Value {}, key, JSONUtil.toJsonStr(shop));// 6. 返回信息return BaseResult.setOkWithData(shop);
}4.2.3.3 功能测试
当id1时由于此时Redis中还没有数据所以会从数据库查询数据并保存到Redis。控制台打印信息如下省略了一些不重要的信息
query Shop by id 1
get from Redis: Key cache:shop:1, Value nullPreparing: SELECT id,name,type_id,images,area,address,x,y,avg_price,sold,comments,score,open_hours,create_time,update_time FROM tb_shop WHERE id?Parameters: 1(Long)Total: 1
set to Redis: Key cache:shop:1, Value {area:大关,openHours:10:00-22:00,...省略...,id:1}此时可以查询Redis中已经保存了商户的数据 再次查询id1的商户信息由于此时Redis中已经存在数据所以会直接从Redis中返回。控制台打印信息如下省略了一些不重要的信息
query Shop by id 1
get from Redis: Key cache:shop:1, Value {area:大关,openHours:10:00-22:00,...省略...,id:1}再比较一下两次查询的性能第二次查询耗时15ms比前面直接查询数据库的方式的62ms要快。
4.2.3 数据一致性问题及其解决方案
4.2.3.1 问题分析
如果我们向Redis添加的大量数据就会导致Redis缓存中的数据过多为了节约宝贵的内存资源Redis会对部分数据进行动态更新。 主要有三种方式
1内存淘汰
在Redis的配置文件redis.conf中有两个关于内存淘汰的配置
# 最大内存限制
maxmemory 512mb
# 当达到最大内存限制时的缓存更新策略
maxmemory-policy noevictionmaxmemory用于配置最大内存限制当Redis中保存的数据超过该限制时就会根据maxmemory-policy配置的策略自动淘汰一部分数据。
2超时剔除
Redis支持单独为每个Key设置有效期超过有效期后Redis会自动删除。
3主动更新
我们还可以手动调用Redis的DEL命令删除数据通常用于解决缓存和数据库不一致的问题。 Redis缓存中的数据来源于数据库因此当数据库的数据发生变化时如果Redis缓存没有同步就会产生数据一致性问题。
解决数据一致性问题也有三种方案
1Cache Aside Pattern人工编码方式由开发者在更新完数据库后再去更新缓存也称之为双写方案。2Read/Write Through Pattern缓存与数据库整合为一个服务由该服务来维护缓存与数据库的数据一致性。3Write Behind Caching Pattern调用者只操作缓存由其他线程去异步处理数据库实现数据的最终一致。
通常情况下双写方案是最易实现且可靠性最好的方案。因此本案例采用的就是双写方案。
下面继续来考虑双写方案的两个问题
1无效写操作过多问题
假设需要对数据库的记录进行N次修改。如果每次修改记录后都更新缓存则需要更新N次缓存但如果在这N次修改期间并没有另一个用户查询数据那对缓存来说只有最后一次更新是有效的中间的几次更新都是无效的。这就导致了无效写操作较多的问题。
要避免这个问题可以放弃每次修改记录都更新缓存的方案而是在修改记录后删除缓存等待再次查询时再更新缓存。这样就能确保缓存中的数据始终是最新的且避免多次写操作。
2操作顺序问题
先修改数据库再更新缓存还是先更新缓存再修改数据库
答案是先修改数据库再更新缓存。 原因如下 4.2.3.2 实现商户信息的双写一致
1修改ShopServiceImpl类的queryShopById()方法
根据id查询商户时如果缓存中没有则查询数据库再将数据库结果写入缓存并返回。此处将写入缓存这一步增设超时时间。到时间后商户缓存自动失效再次查询时自动更新为最新信息。
// com.star.redis.dzdp.service.impl.ShopServiceImpl#queryShopById()// 5.将商户信息写入Redis
// 旧逻辑
// stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
// 新逻辑增设超时时间30分钟
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES);2实现根据ID修改商户信息功能
接口文档如下
项目说明请求方式POST请求路径/shop/edit请求参数Shop返回值无
在ShopController类中白那些一个editById()方法调用IShopService接口的editShopById()方法
// com.star.redis.dzdp.controller.ShopController/*** 根据ID修改商户信息* author hsgx* since 2024/4/3 15:17* param shop * return com.star.redis.dzdp.pojo.BaseResult*/
PostMapping(/edit)
public BaseResult edit(RequestBody Shop shop) {return shopService.editShopById(shop);
}然后在IShopService接口的实现类ShopServiceImpl类中编写editShopById()方法
// com.star.redis.dzdp.service.impl.ShopServiceImplOverride
public BaseResult editShopById(Shop shop) {log.info(edit Shop by id {}, shop.toString());if(shop.getId() null) {return BaseResult.setFail(商户ID不能为空);}// 1.更新数据库记录updateById(shop);// 2.删除缓存Boolean delete stringRedisTemplate.delete(cache:shop: shop.getId());log.info(delete from Redis: Key {}, result {}, cache:shop: shop.getId(), delete);return BaseResult.setOk();
}最后进行功能测试
调用/shop/1接口获取id1的商户信息日志显示从Redis直接返回
query Shop by id 1
get from Redis: Key cache:shop:1, Value {area:大关,openHours:10:00-22:00,...省略...,id:1}调用/shop/edit接口修改id1的商户信息日志显示先修改数据库记录后删除Redis缓存 edit Shop by id Shop(id1, namenull, typeIdnull, imagesnull, area修改后的area, addressnull, xnull, ynull, avgPricenull, soldnull, commentsnull, scorenull, openHoursnull, createTimenull, updateTimenull, distancenull)Preparing: UPDATE tb_shop SET area? WHERE id?Parameters: 修改后的area(String), 1(Long)Updates: 1
delete from Redis: Key cache:shop:1, result true再次调用/shop/1接口获取id1的商户信息日志显示从数据库查询后再存入Redis且数据是最新的 query Shop by id 1
get from Redis: Key cache:shop:1, Value nullPreparing: SELECT id,name,type_id,images,area,address,x,y,avg_price,sold,comments,score,open_hours,create_time,update_time FROM tb_shop WHERE id?Parameters: 1(Long)Total: 1
set to Redis: Key cache:shop:1, Value {area:修改后的area,openHours:10:00-22:00,...省略...,id:1}4.2.4 缓存穿透问题及其解决方案
4.2.4.1 什么是缓存穿透问题
查询商户信息时目前的做法仍然存在漏洞即当id100时Redis和数据库都没有数据程序最终会操作数据库。那如果有大量id100的查询请求每次都会去查询数据库Redis缓存就相当于摆设了。
这就是缓存穿透问题即客户端请求的数据在缓存中和数据库中都不存在这些请求会穿透缓存直击数据库给数据库造成压力。
4.2.4.2 缓存穿透问题的解决方案
常见的解决缓存穿透问题的方案有两种缓存空对象、布隆过滤。 1缓存空对象
服务端接收到查询请求后如果Redis和数据库都不存在也将这个数据存入Redis只是其Value值设置为空字符串。那么下次查询这个不存在的数据时在Redis这里就可以知道数据不存在了而不用继续访问数据库。
缓存空对象的优点在于实现简单、维护方便缺点在于有额外的内存消耗。
2布隆过滤
布隆过滤是指采用哈希思想来解决这个问题通过一个庞大的二进制数组利用哈希思想判断当前要查询的数据是否存在。如果布隆过滤器判断存在则放行这个请求会去访问Redis或数据库如果布隆过滤器判断这个数据不存在则直接返回不存在。
布隆过滤的优点在于节约内存空间没有多余的Key缺点是布隆过滤器走的是哈希思想可能存在误判。
综合考虑本案例采用缓存空对象的方案。
4.2.4.3 基于缓存空对象解决缓存穿透问题
修改ShopServiceImpl类的queryShopById方法
// com.star.redis.dzdp.service.impl.ShopServiceImpl#queryShopById()// 2.判断商户信息是否存在
if(StrUtil.isNotBlank(shopJson)) {// 3.存在直接返回Shop shop JSONUtil.toBean(shopJson, Shop.class);return BaseResult.setOkWithData(shop);
} else if(.equals(shopJson)) {// 新增逻辑如果保存了空字符串则说明是商户信息不存在直接返回不存在return BaseResult.setFail(商户不存在);
}// 4.不存在根据ID查询数据库
Shop shop getById(id);
// 旧逻辑shop为空时直接返回不存在
// 新逻辑要将字符串写入Redis因此这里不直接返回
// if(shop null) {
// return BaseResult.setFail(商户不存在);
// }// 5.将商户信息写入Redis
// 新逻辑如果商户信息为空则将空字符串写入Redis
stringRedisTemplate.opsForValue().set(key,shop ! null ? JSONUtil.toJsonStr(shop) : ,30, TimeUnit.MINUTES);接下来进行功能测试调用/shop/100接口获取id100的商户信息日志显示将空字符串写入了Redis
query Shop by id 100
get from Redis: Key cache:shop:100, Value nullPreparing: SELECT id,name,type_id,images,area,address,x,y,avg_price,sold,comments,score,open_hours,create_time,update_time FROM tb_shop WHERE id?Parameters: 100(Long)Total: 0
set to Redis: Key cache:shop:100, Value null查看Redis中保存的数据 再次调用/shop/100接口获取id100的商户信息日志显示从Redis中获取到了空值注解返回商户不存在
query Shop by id 100
get from Redis: Key cache:shop:100, Value 4.2.5 缓存击穿问题及其解决
4.2.5.1 什么是缓存击穿问题
缓存击穿问题也叫热点Key问题就是一个被高并发访问并且缓存重建业务较复杂的Key突然失效了大量的请求击穿缓存到达数据库给数据库造成压力。 例如 如上图所示线程1查询缓存未命中然后继续查询数据库并重建缓存数据但由于重建缓存数据业务复杂没等重建完成线程2、3、4就来查询缓存了线程2、3、4都不能从缓存中查到数据转而去查数据库。最终结果是4个线程都访问了数据库给数据库造成压力。
4.2.5.2 缓存击穿问题的解决方案
解决方案一互斥锁 如上图所示线程1查询缓存未命中然后会获取互斥锁获取到了锁再继续查询数据库并重建缓存数据最后释放锁。而此时线程2查询缓存未命中也去获取互斥锁但锁只能一个线程使用所以会获取失败转而进入休眠状态不断地尝试查询缓存和获取锁这两步等线程1重建缓存完成并释放锁后线程2获取到互斥锁就能跳出休眠状态从缓冲中获取到数据了。
解决方案二逻辑过期 如上图所示逻辑过期方案的要点是不对key设置过期时间而是将过期时间设置在Value值中。 假设线程1查询缓存发现逻辑时间已经过期则开启一个新的线程2并直接返回过期的数据。线程2获取互斥锁去查询数据库和重构缓存数据完成后释放锁。假设线程3过来访问由于线程2持有着锁所以线程3无法获得锁线程3也直接返回过期数据只有等到线程2重建缓存数据完成后才能返回正确的数据。
两种解决方案各有优缺点
互斥锁由于保证了互斥性所以数据一致且实现简单仅仅只需要加一把锁没有额外的内存消耗缺点在于有锁就有死锁问题的发生且只能串行执行性能会受到影响。 逻辑过期线程读取过程中不需要等待性能好有一个额外的线程持有锁去进行重构数据但是在重构数据完成前其他的线程只能返回之前的脏数据且实现起来麻烦。 4.2.5.3 利用互斥锁解决缓存击穿问题 如上图所示要利用互斥锁解决缓存击穿问题可以在缓存未命中时尝试获取互斥锁只有获取到了互斥锁才能查询数据库并将查询结果写入Redis而没有获取到互斥锁时则进行休眠并重试查询缓存。
在持有互斥锁的线程完成查询数据库并将查询结果写入Redis的动作之前其他没有获得互斥锁的线程不断重复地从Redis拿数据直到互斥锁被释放后才能从Redis拿到数据。
下面正式进行代码编写。首先要对互斥锁进行设计
Redis的String类型有一个SETNX方法该方法会尝试添加一个String类型的键值对前提是这个key不存在否则不执行。我们可以利用这个方法来获取互斥锁如果调用该方法成功说明Key不存在获取互斥锁成功调用该方法失败说明Key已存在获取互斥锁失败。
在ShopServiceImpl类中编写获取互斥锁的tryLock()方法以及释放锁的unLock()方法
// com.star.redis.dzdp.service.impl.ShopServiceImpl/*** 获取互斥锁* author hsgx* since 2024/4/3 17:19* param key* return boolean*/
private boolean tryLock(String key) {Boolean flag stringRedisTemplate.opsForValue().setIfAbsent(key, 1, 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);
}/*** 释放锁* author hsgx* since 2024/4/3 17:20* param key* return void*/
private void unLock(String key) {stringRedisTemplate.delete(key);
}接着在IShopService接口中定义一个queryShopByIdWithLock()方法并在其实现类ShopServiceImpl中具体实现功能仍然是根据ID查询商户信息只是添加了互斥锁
// com.star.redis.dzdp.service.impl.ShopServiceImplpublic BaseResultShop queryShopByIdWithLock(Long id) {log.info(query Shop by id {}, id);// 1.构建Key并从Redis中查询商户信息String key cache:shop: id;String shopJson stringRedisTemplate.opsForValue().get(key);log.info(get from Redis: Key {}, Value {}, key, shopJson);// 2.判断商户信息是否存在if(StrUtil.isNotBlank(shopJson)) {// 存在直接返回Shop shop JSONUtil.toBean(shopJson, Shop.class);return BaseResult.setOkWithData(shop);} else if(.equals(shopJson)) {// 如果保存了空字符串则说明是商户信息不存在直接返回不存在return BaseResult.setFail(商户不存在);}// 3.使用互斥锁方案解决缓存击穿问题String lockKey lock:shop: id;Shop shop null;try {// 尝试获取互斥锁boolean isLock tryLock(lockKey);log.info(isLock {}, isLock);if(!isLock) {// 没有获取到互斥锁则休眠重试log.info(Retry queryShopByIdWithLock()...);Thread.sleep(50);return queryShopByIdWithLock(id);}// 获取到了互斥锁则根据ID查询数据并重构缓存数据shop getById(id);if(shop null) {// 商户不存在保存空字符串stringRedisTemplate.opsForValue().set(key, , 30, TimeUnit.MINUTES);log.info(set to Redis: Key {}, Value \\, key);return BaseResult.setFail(商户不存在);} else {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES);log.info(set to Redis: Key {}, Value {}, key, JSONUtil.toJsonStr(shop));}} catch (Exception e) {e.printStackTrace();} finally {// 释放锁unLock(lockKey);}return BaseResult.setOkWithData(shop);
}然后修改ShopController类中的调用方法
// com.star.redis.dzdp.controller.ShopControllerGetMapping(/{id})
public BaseResultShop queryById(PathVariable Long id) {//return shopService.queryShopById(id);// 使用互斥锁解决缓存击穿问题return shopService.queryShopByIdWithLock(id);
}修改完毕后进行功能测试首先在shop getById(id);这一行代码上打一个断点用于模拟重建缓存数据的时间很久。
然后调用/shop/102接口获取id102的商户信息日志显示线程5从Redis中没有获取到数据并获取互斥锁成功进入查询数据库和重建缓存的逻辑停在了断点处
[http-nio-8081-exec-5] query Shop by id 102
[http-nio-8081-exec-5] get from Redis: Key cache:shop:102, Value null
[http-nio-8081-exec-5] tryLock key lock:shop:102
[http-nio-8081-exec-5] isLock true接着再次调用/shop/102接口获取id102的商户信息日志显示线程6从Redis中没有获取到数据并获取互斥锁失败进入重试逻辑再次调用queryShopByIdWithLock()方法
[http-nio-8081-exec-6] query Shop by id 102
[http-nio-8081-exec-6] get from Redis: Key cache:shop:102, Value null
[http-nio-8081-exec-6] tryLock key lock:shop:102
[http-nio-8081-exec-6] isLock false
[http-nio-8081-exec-6] Retry queryShopByIdWithLock()...此时放开断点线程5继续执行查询数据库信息并将查询结果写入Redis最后释放锁
[http-nio-8081-exec-5] Preparing: SELECT id,name,type_id,images,area,address,x,y,avg_price,sold,comments,score,open_hours,create_time,update_time FROM tb_shop WHERE id?
[http-nio-8081-exec-5] Parameters: 102(Long)
[http-nio-8081-exec-5] Total: 0
[http-nio-8081-exec-5] set to Redis: Key cache:shop:102, Value 锁释放后线程6已可以从Redis中拿到数据并返回结果
[http-nio-8081-exec-6] query Shop by id 102
[http-nio-8081-exec-6] get from Redis: Key cache:shop:102, Value 至此基于互斥锁的方案测试完成。
4.2.5.4 利用逻辑过期解决缓存击穿问题 由上图可知当查询缓存未命中时则直接返回空如果命中了则将缓存数据中的Value值取出判断Value值中保存的时间是否过期如果没有过期则直接返回缓存数据如果过期了则获取互斥锁并开启一个独立的线程后直接返回过期数据。
独立线程负责查询数据库并更新缓存数据。而其他线程在获得过期数据后由于无法获取互斥锁也直接返回过期数据。
下面开始编写代码。由于要在Value值中保存过期时间我们首先要重新定义一个保存到Redis的实体类。
// com.star.redis.dzdp.pojo.RedisDataData
public class RedisData {private Date expireTime;private Object data;
}接着在IShopService接口中定义一个queryShopByIdWithExpire()方法并在其实现类ShopServiceImpl中具体实现功能仍然是根据ID查询商户信息只是添加了逻辑过期
// com.star.redis.dzdp.service.impl.ShopServiceImplprivate static final ExecutorService CACHE_REBUILD_EXECUTOR Executors.newFixedThreadPool(10);Override
public BaseResultShop queryShopByIdWithExpire(Long id) {log.info(query Shop by id {}, id);// 1.构建Key并从Redis中查询商户信息String key cache:shop: id;String shopJson stringRedisTemplate.opsForValue().get(key);log.info(get from Redis: Key {}, Value {}, key, shopJson);// 2.商户信息不存在直接返回if(StrUtil.isBlank(shopJson)) {return BaseResult.setFail(商户不存在);}// 3.商户信息存在先反序列化为Java对象RedisData redisData JSONUtil.toBean(shopJson, RedisData.class);Date expireTime redisData.getExpireTime();Shop shop JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);// 4.判断是否过期if(expireTime.after(new Date())) {// 没过期直接返回数据return BaseResult.setOkWithData(shop);}// 5.已过期则尝试获取互斥锁String lockKey lock:shop: id;try {boolean isLock tryLock(lockKey);log.info(isLock {}, isLock);if(!isLock) {// 没有获取到互斥锁则直接返回过期数据return BaseResult.setOkWithData(shop);}// 获取到了互斥锁则开启一个新的线程log.info(create a new thread ...);CACHE_REBUILD_EXECUTOR.submit(() - {log.info(a new thread begin...);// 查询数据库Shop newShop getById(id);// 写入RedisRedisData newRedisDate new RedisData();newRedisDate.setData(newShop);long expire System.currentTimeMillis() 30 * 60 * 1000;newRedisDate.setExpireTime(new Date(expire));stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(newRedisDate));log.info(set to Redis: Key {}, Value {}, key, JSONUtil.toJsonStr(newRedisDate));// 释放锁unLock(lockKey);log.info(a new thread end...);});} catch (Exception e) {e.printStackTrace();}// 当前线程直接返回过期数据return BaseResult.setOkWithData(shop);
}然后修改ShopController类中的调用方法
// com.star.redis.dzdp.controller.ShopControllerGetMapping(/{id})
public BaseResultShop queryById(PathVariable Long id) {// return shopService.queryShopById(id);// 使用互斥锁解决缓存击穿问题// return shopService.queryShopByIdWithLock(id);// 使用逻辑过期解决缓存击穿问题return shopService.queryShopByIdWithExpire(id);
}由于在queryShopByIdWithExpire()方法中只要Redis未命中就直接返回不存在因此这种方式需要进行初始化也就是将数据库的商户信息同步一次到Redis中。
假设现在初始化过了在Redis中有这样一条数据 然后我们在线程内部添加一行代码Thread.sleep(30 * 1000);用于模拟新线程重建缓存数据的时间很久。 接着调用/shop/2接口获取id2的商户信息日志显示线程1从Redis中没有获取到了过期数据并获取互斥锁成功然后创建新线程
[http-nio-8081-exec-1] query Shop by id 2
[http-nio-8081-exec-1] get from Redis: Key cache:shop:2, Value {data:{area:拱宸桥/上塘,openHours:11:30-03:00,...省略...,id:2},expireTime:1640170813000}
[http-nio-8081-exec-1] tryLock key lock:shop:2
[http-nio-8081-exec-1] isLock true
[http-nio-8081-exec-1] create a new thread ...然后新线程[pool-1-thread-1]进入30s睡眠模拟耗时很长。 此时一个新的请求过来线程2开始执行日志显示线程2从Redis拿到了过期数据但获取互斥锁失败直接返回过期数据
[pool-1-thread-1] a new thread begin...
[http-nio-8081-exec-2] query Shop by id 2
get from Redis: Key cache:shop:2, Value {data:{area:拱宸桥/上塘,openHours:11:30-03:00,...省略...,id:2},expireTime:1640170813000}
[http-nio-8081-exec-2] tryLock key lock:shop:2
[http-nio-8081-exec-2] isLock false新线程[pool-1-thread-1]休眠结束查询数据库并将查询结果更新到Redis最后释放互斥锁
[pool-1-thread-1] Preparing: SELECT id,name,type_id,images,area,address,x,y,avg_price,sold,comments,score,open_hours,create_time,update_time FROM tb_shop WHERE id?
[pool-1-thread-1] Parameters: 2(Long)
[pool-1-thread-1] Total: 1
[pool-1-thread-1] set to Redis: Key cache:shop:2, Value {data:{area:拱宸桥/上塘,openHours:11:30-03:00,...省略...,id:2},expireTime:1712234901457}
[pool-1-thread-1] unLock key lock:shop:2
[pool-1-thread-1] a new thread end...至此基于逻辑过期的方案也测试完成。
…
本节完更多内容请查阅分类专栏Redis从入门到精通
感兴趣的读者还可以查阅我的另外几个专栏
SpringBoot源码解读与原理分析(已完结)MyBatis3源码深度解析(已完结)再探Java为面试赋能(持续更新中…)
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/931067.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!