本文最后更新于:4 分钟前
分布式锁优化过程、Redisson,AOP实现缓存
一 分布式锁优化过程
1 本地锁的局限性
我们学习过synchronized及lock锁,这些锁都是本地锁。接下来写一个案例,演示本地锁的问题
1.1 编写测试代码
在TestController中添加测试方法
1 2 3 4 5 6 7
| public interface RedisTestService {
public void lockDemo1(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Service public class RedisTestServiceImpl implements RedisTestService {
@Autowired private StringRedisTemplate stringRedisTemplate;
@Override public void lockDemo1() { String s = stringRedisTemplate.boundValueOps("demo1_num").get(); if(StringUtils.isEmpty(s)){ return; } Integer num = Integer.parseInt(s); stringRedisTemplate.boundValueOps("demo1_num").set(++num + ""); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @RestController @RequestMapping(value = "/redis/product/test") public class RedisTestController {
@Autowired private RedisTestService redisTestService;
@GetMapping(value = "/demo1") public Result demo1(){ redisTestService.lockDemo1(); return Result.ok(); } }
|
说明:通过reids客户端设置num=0
1.2 使用ab工具测试
在redis中,玩ab测试工具:httpd-tools(yum install -y httpd-tools)
1 2
| ab -n(一次发送的请求数) -c(请求的并发数) 访问路径 ab -n 5000 -c 100 http://192.168.200.1:8206/api/redis/test01
|
测试如下:5000请求,100并发
查看redis中的值:
1.3 使用本地锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Service public class RedisTestServiceImpl implements RedisTestService {
@Autowired private StringRedisTemplate stringRedisTemplate;
@Override public synchronized void lockDemo1() { String s = stringRedisTemplate.boundValueOps("demo1_num").get(); if(StringUtils.isEmpty(s)){ return; } Integer num = Integer.parseInt(s); stringRedisTemplate.boundValueOps("demo1_num").set(++num + ""); } }
|
使用ab工具压力测试:5000次请求,并发100
查看redis中的结果:
完美!与预期一致,是否真的完美?
接下来再看集群情况下,会怎样?
1.4 本地锁问题演示锁
接下来启动8206 8216 8226 三个运行实例。
运行多个service-product实例:
通过网关压力测试:
启动网关:
ab -n 5000 -c 100 http://192.168.200.1/redis/product/test/testLock
查看redis中的值:
集群情况下又出问题了!!!
以上测试,可以发现:
本地锁只能锁住同一工程内的资源,在分布式系统里面都存在局限性。
此时需要分布式锁
2 分布式锁实现
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
分布式锁主流的实现方案:
基于数据库实现分布式锁
基于缓存(Redis等)
基于Zookeeper
每一种分布式锁解决方案都有各自的优缺点:
性能:redis最高
可靠性:zookeeper最高
==这里,我们就基于redis实现分布式锁。==
3 使用redis实现分布式锁
redis命令
set sku:1:info “OK” NX PX 10000
EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 SETEX key millisecond value 。
NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
XX :只在键已经存在时,才对键进行设置操作。
多个客户端同时获取锁(setnx)
获取成功,执行业务逻辑,执行完成释放锁(del)
其他客户端等待重试
3.1 编写代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @Service public class RedisTestServiceImpl implements RedisTestService {
@Autowired private StringRedisTemplate stringRedisTemplate;
@Override public void lockDemo1() { Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111"); if(lock){ String s = stringRedisTemplate.boundValueOps("demo1_num").get(); if(StringUtils.isEmpty(s)){ return; } Integer num = Integer.parseInt(s); stringRedisTemplate.boundValueOps("demo1_num").set(++num + ""); stringRedisTemplate.delete("lock"); }else{ try { Thread.sleep(1000); lockDemo1(); }catch (Exception e){ e.printStackTrace(); } }
} }
|
重启,服务集群,通过网关压力测试:
查看redis中num的值:
基本实现。
问题:setnx刚好获取到锁,业务逻辑出现异常,导致锁无法释放
==解决:设置过期时间,自动释放锁。==
3.2 优化之设置锁的过期时间
设置过期时间有两种方式:
首先想到通过expire设置过期时间(缺乏原子性:如果在setnx和expire之间出现异常,锁也无法释放)
在set时指定过期时间(推荐)
设置过期时间:
压力测试肯定也没有问题。自行测试
1 2
| Boolean lock = redisTemplate.opsForValue() .setIfAbsent("lock", "111",3,TimeUnit.SECONDS);
|
问题:可能会释放其他服务器的锁。
==场景:如果业务逻辑的执行时间是7s。执行流程如下==
index1业务逻辑没执行完,3秒后锁被自动释放。
index2获取到锁,执行业务逻辑,3秒后锁被自动释放。
index3获取到锁,执行业务逻辑
index1业务逻辑执行完成,开始调用del释放锁,这时释放的是index3的锁,导致index3的业务只执行1s就被别人释放。
最终等于没锁的情况。
解决:setnx获取锁时,设置一个指定的唯一值(例如:uuid);释放前获取这个值,判断是否自己的锁
3.3 优化之UUID防误删
==问题:删除操作缺乏原子性。==
场景:
index1执行删除时,查询到的lock值确实和uuid相等
uuid=v1
set(lock,uuid);
index1执行删除前,lock刚好过期时间已到,被redis自动释放
在redis中没有了lock,没有了锁。
index2获取了lock
index2线程获取到了cpu的资源,开始执行方法
uuid=v2
set(lock,uuid);
index1执行删除,此时会把index2的lock删除
index1 因为已经在方法中了,所以不需要重新上锁。index1有执行的权限。index1已经比较完成了,这个时候,开始执行
删除的index2的锁!
3.4 优化之LUA脚本保证删除的原子性
lua脚本
1 2 3 4 5
| if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
|
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package slx.blue.gmall.product.service.impl;
import slx.blue.gmall.product.service.RedisTestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils;
import java.util.Arrays; import java.util.UUID; import java.util.concurrent.TimeUnit;
@Service public class RedisTestServiceImpl implements RedisTestService {
@Autowired private StringRedisTemplate stringRedisTemplate;
@Override public void lockDemo1() { String uuid = UUID.randomUUID().toString(); Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS); if(lock){ String s = stringRedisTemplate.boundValueOps("demo1_num").get(); if(StringUtils.isEmpty(s)){ return; } Integer num = Integer.parseInt(s); stringRedisTemplate.boundValueOps("demo1_num").set(++num + "");
DefaultRedisScript<Long> script = new DefaultRedisScript(); script.setScriptText("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"); script.setResultType(Long.class); stringRedisTemplate.execute(script, Arrays.asList("lock"), uuid); }else{ try { Thread.sleep(1000); lockDemo1(); }catch (Exception e){ e.printStackTrace(); } }
} }
|
Lua 脚本详解:
3.5 总结
- 加锁
1 2 3 4
| String uuid = UUID.randomUUID().toString(); Boolean lock = this.redisTemplate.opsForValue() .setIfAbsent("lock", uuid, 2, TimeUnit.SECONDS);
|
- 使用lua释放锁
1 2 3 4 5 6 7 8
| String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setResultType(Long.class); redisScript.setScriptText(script); redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
|
- 重试
1 2
| Thread.sleep(1000); testLock();
|
为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
==互斥性: 在任意时刻,只有一个客户端能持有锁;==
==不会发生死锁: 即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁;==
==解铃还须系铃人: 加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了;==
==加锁和解锁必须具有原子性;==
redis集群状态下的问题:
客户端A从master获取到锁
在master将锁同步到slave之前,master宕掉了。
slave节点被晋级为master节点
客户端B取得了同一个资源被客户端A已经获取到的另外一个锁。
安全失效!
解决方案:
但是!上述情况只是解决了redis单点的情况。如果redis也是集群情况,会有数据同步的情况,以主从为例,如果setNX给到了主机,但是数据还未同步,然后主机宕机了,从机便又没有锁了,服务即又可以给redis从机加新的锁,这会导致锁失效。所以经典白学系列,解决问题的还得看下面两节。
4 使用redisson 解决分布式锁
Github 地址:https://github.com/redisson/redisson
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
官方文档地址:https://github.com/redisson/redisson/wiki
4.1 实现代码
1 2 3 4 5 6
| <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.11.2</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| package slx.blue.gmall.common.config;
import lombok.Data; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.config.SingleServerConfig; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils;
@Data @Configuration @ConfigurationProperties("spring.redis") public class RedissonConfig {
private String host;
private String addresses;
private String password;
private String port;
private int timeout = 3000; private int connectionPoolSize = 64; private int connectionMinimumIdleSize=10; private int pingConnectionInterval = 60000; private static String ADDRESS_PREFIX = "redis://";
@Bean RedissonClient redissonSingle() { Config config = new Config();
if(StringUtils.isEmpty(host)){ throw new RuntimeException("host is empty"); } SingleServerConfig serverConfig = config.useSingleServer() .setAddress(ADDRESS_PREFIX + this.host + ":" + port) .setTimeout(this.timeout) .setPingConnectionInterval(pingConnectionInterval) .setConnectionPoolSize(this.connectionPoolSize) .setConnectionMinimumIdleSize(this.connectionMinimumIdleSize); if(!StringUtils.isEmpty(this.password)) { serverConfig.setPassword(this.password); } return Redisson.create(config); } }
|
4.2 可重入锁(Reentrant Lock)
基于Redis的Redisson分布式可重入锁RLock
Java对象实现了java.util.concurrent.locks.Lock
接口。
大家都知道,如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout
来另行指定。
另外Redisson还通过加锁的方法提供了leaseTime
的参数来指定加锁的时间。超过这个时间后锁便自动解开了。
快速入门使用的就是可重入锁。也是最常使用的锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| @Autowired private RedissonClient redissonClient;
@Override public void setRedisRedisson() { RLock lock = redissonClient.getLock("lock"); try { if (lock.tryLock(10, 20, TimeUnit.SECONDS)) { try { String value = stringRedisTemplate.boundValueOps("test01").get(); if (StringUtils.isEmpty(value)) { return; } Integer num = Integer.parseInt(value); stringRedisTemplate.boundValueOps("test01").set(++num + ""); } catch (NumberFormatException e) { System.out.println("加锁成功以后出现异常"); e.printStackTrace(); } finally { lock.unlock(); } } } catch (InterruptedException e) { System.out.println("加锁失败 出现异常"); e.printStackTrace(); } }
|
4.3 读写锁(ReadWriteLock)
基于Redis的Redisson分布式可重入读写锁RReadWriteLock
Java对象实现了java.util.concurrent.locks.ReadWriteLock
接口。其中读锁和写锁都继承了RLock接口。
分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
rwlock.readLock().lock();
rwlock.writeLock().lock();
rwlock.readLock().lock(10, TimeUnit.SECONDS);
rwlock.writeLock().lock(10, TimeUnit.SECONDS);
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS); ... lock.unlock();
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
@Override public String readLock() { RReadWriteLock lock = redissonClient.getReadWriteLock("lock"); lock.readLock().lock(10, TimeUnit.SECONDS); String s = stringRedisTemplate.boundValueOps("msg").get(); return s; }
@Override public String writeLock() { RReadWriteLock lock = redissonClient.getReadWriteLock("lock"); lock.writeLock().lock(10, TimeUnit.SECONDS); stringRedisTemplate.boundValueOps("msg").set(UUID.randomUUID().toString());
return "写完了"; }
|
打开开两个浏览器窗口测试:
localhost:8206/admin/product/test/read
http://localhost:8206/admin/product/test/write
- 同时访问写:一个写完之后,等待一会儿(约10s),另一个写开始
- 同时访问读:不用等待
- 先写后读:读要等待(约10s)写完成
- 先读后写:写要等待(约10s)读完成
二 分布式锁改造获取sku信息
3.1 使用redis方案
RedisConst 类中追加一个变量
1 2 3 4
|
public static final long SKUKEY_TEMPORARY_TIMEOUT = 5 * 60;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| @Autowired private StringRedisTemplate stringRedisTemplate;
private SkuInfo getSkuByRedis(Long skuId) { if (skuId == null) { return null; }
String key = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX; String s = (String) stringRedisTemplate.boundValueOps(key).get(); if (!StringUtils.isEmpty(s)) { return JSONObject.parseObject(s, SkuInfo.class); } else { String lock = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX; String uuid = UUID.randomUUID().toString(); Boolean isLock = stringRedisTemplate.opsForValue(). setIfAbsent(lock, uuid, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS); if (isLock) { SkuInfo skuInfo = skuInfoMapper.selectById(skuId); if (null == skuInfo || skuInfo.getId() == null) { skuInfo = new SkuInfo(); stringRedisTemplate.opsForValue() .set(key, JSONObject.toJSONString(skuInfo), RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS); return skuInfo; } else { stringRedisTemplate.opsForValue() .set(key, JSONObject.toJSONString(skuInfo), RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS); DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setScriptText("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end "); redisScript.setResultType(Long.class); stringRedisTemplate.execute(redisScript, Arrays.asList(lock), uuid); return skuInfo; } } else { try { TimeUnit.SECONDS.sleep(1); return getSkuByRedis(skuId); } catch (InterruptedException e) { return null; } } } }
|
3.2 使用redisson方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| @Autowired private RedissonClient redissonClient;
@Autowired private RedisTemplate redisTemplate;
private SkuInfo getSkuByRedisson(Long skuId) { if (skuId == null) { return null; } String key = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(key); if (null != skuInfo) { return skuInfo; } String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX; RLock rLock = redissonClient.getLock(lockKey); try { if (rLock.tryLock(20, 10, TimeUnit.SECONDS)) { try { skuInfo = skuInfoMapper.selectById(skuId); if (null == skuInfo || null == skuInfo.getId()) { redisTemplate.opsForValue() .set(key, skuInfo, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS); } else { redisTemplate.opsForValue() .set(key, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS); } return skuInfo; } catch (Exception e) { e.printStackTrace(); } finally { rLock.unlock(); } } else { try { TimeUnit.SECONDS.sleep(1); getSkuByRedisson(skuId); } catch (InterruptedException e) { }
} } catch (InterruptedException e) {
e.printStackTrace(); } return null;
}
|
3.3 在getSkuInfo 中调用上述两个方法进行测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@Override public SkuInfo getSkuInfo(Long skuId) {
SkuInfo skuInfo = getSkuByRedisson(skuId); if(skuInfo == null || skuInfo.getId() == null){ skuInfo = getSkuFromDb(skuId); } return skuInfo; }
|
三 分布式锁 + AOP实现缓存
随着业务中缓存及分布式锁的加入,业务代码变的复杂起来,除了需要考虑业务逻辑本身,还要考虑缓存及分布式锁的问题,增加了程序员的工作量及开发难度。而缓存的玩法套路特别类似于事务,而声明式事务就是用了aop的思想实现的。
以 @Transactional 注解为植入点的切点,这样才能知道@Transactional注解标注的方法需要被代理。
@Transactional注解的切面逻辑类似于@Around
模拟事务,缓存可以这样实现:
自定义缓存注解@GmallCache(类似于事务@Transactional)
编写切面类,使用环绕通知实现缓存的逻辑封装
4.1 定义一个注解
1 2 3 4 5 6 7 8 9 10 11 12
| package slx.blue.gmall.common.cache;
import java.lang.annotation.*;
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface GmallCache { String prefix() default "cache"; }
|
4.2 定义一个切面类加上注解
Spring aop 参考文档:
https://docs.spring.io/spring/docs/5.2.6.BUILD-SNAPSHOT/spring-framework-reference/core.html#aop-api-pointcuts-aspectj
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| package slx.blue.gmall.common.cache;
import com.alibaba.fastjson.JSONObject; import org.apache.commons.lang.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component;
import java.util.Arrays; import java.util.concurrent.TimeUnit;
@Component @Aspect public class GmallCacheAspect {
@Autowired private RedisTemplate redisTemplate;
@Autowired private RedissonClient redissonClient;
@Around("@annotation(slx.blue.gmall.common.cache.GmallCache)") public Object cacheAroundAdvice(ProceedingJoinPoint point){
Object result = null; try { Object[] args = point.getArgs(); System.out.println("gmallCache:"+args); MethodSignature signature = (MethodSignature) point.getSignature(); GmallCache gmallCache = signature.getMethod().getAnnotation(GmallCache.class); String prefix = gmallCache.prefix(); String key = prefix+Arrays.asList(args).toString();
result = cacheHit(signature, key); if (result!=null){ return result; } RLock lock = redissonClient.getLock(key + ":lock"); boolean flag = lock.tryLock(100, 100, TimeUnit.SECONDS); if (flag){ try { try { result = point.proceed(point.getArgs()); if (null==result){ Object o = new Object(); this.redisTemplate.opsForValue().set(key, JSONObject.toJSONString(o), 300,TimeUnit.SECONDS); return null; }else { this.redisTemplate.opsForValue().set(key, JSONObject.toJSONString(result), 20*60*60,TimeUnit.SECONDS); } } catch (Throwable throwable) { throwable.printStackTrace(); }
return result; }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } }catch (Exception e){ e.printStackTrace(); } return result; } private Object cacheHit(MethodSignature signature, String key) { String cache = (String)redisTemplate.opsForValue().get(key); if (StringUtils.isNotBlank(cache)) { Class returnType = signature.getReturnType(); return JSONObject.parseObject(cache, returnType); } return null; }
}
|
4.3 使用注解完成缓存
修改方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
@GmallCache(prefix = RedisConst.SKUKEY_PREFIX) @Override public SkuInfo getSkuInfo(Long skuId) { SkuInfo skuInfo = getSkuFromDb(skuId); return skuInfo; }
|
1 2 3 4 5 6 7 8 9 10 11
| @GmallCache(prefix = "saleAttrValuesBySpu:") public Map getSaleAttrValuesBySpu(Long spuId)
@GmallCache(prefix = "spuSaleAttrListCheckBySku:") public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(Long skuId, Long spuId)
@GmallCache(prefix = "skuPrice:") public BigDecimal getSkuPrice(Long skuId)
@GmallCache(prefix = "categoryViewByCategory3Id:") public BaseCategoryView getCategoryViewByCategory3Id(Long category3Id)
|
测试效果: