使用的版本是3.13,因为springboot使用的是2.1.3版本,因此按照官方上的提示,移除redisson-spring-data-22
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.redisson</groupId> <artifactId>redisson-spring-data-22</artifactId> </exclusion> </exclusions> <version>3.13.1</version> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-data-21</artifactId> <version>3.13.1</version> </dependency>SentinelConfig-local.yml文件在项目的resource目录下,具体参数查看redisson依赖包下面的config/SentinelServersConfig.java
#本地开发使用 sentinelServersConfig: #baseConfig idleConnectionTimeout: 10000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 password: 123456 clientName: null loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {} #BaseMasterSlaveServersConfig slaveConnectionMinimumIdleSize: 24 slaveConnectionPoolSize: 64 failedSlaveReconnectionInterval: 3000 failedSlaveCheckInterval: 180000 masterConnectionMinimumIdleSize: 24 masterConnectionPoolSize: 64 subscriptionConnectionMinimumIdleSize: 1 subscriptionConnectionPoolSize: 50 dnsMonitoringInterval: 5000 #SentinelServersConfig database: 8 sentinelAddresses: - "redis://192.168.1.213:26379" - "redis://192.168.1.214:26380" - "redis://192.168.1.214:26381" masterName: "mymaster" scanInterval: 1000 threads: 0 # Netty线程池数量,默认值: 当前处理核数量 * 2 nettyThreads: 0 # 编码 codec: !<org.redisson.codec.JsonJacksonCodec> {} # 传输模式 transportMode : "NIO"添加DistributeLocker接口,以及它的实现类RedissonDistributeLocker
public interface DistributeLocker { /** * 加锁 * * @param lockKey key */ void lock(String lockKey); /** * 释放锁 * * @param lockKey key */ void unlock(String lockKey); /** * 加锁锁,设置有效期 * * @param lockKey key * @param timeout 有效时间,默认时间单位在实现类传入 */ void lock(String lockKey, int timeout); /** * 加锁,设置有效期并指定时间单位 * * @param lockKey key * @param timeout 有效时间 * @param unit 时间单位 */ void lock(String lockKey, int timeout, TimeUnit unit); /** * 尝试获取锁,获取到则持有该锁返回true,未获取到立即返回false * * @param lockKey * @return true-获取锁成功 false-获取锁失败 */ boolean tryLock(String lockKey); /** * 尝试获取锁,获取到则持有该锁leaseTime时间. * 若未获取到,在waitTime时间内一直尝试获取,超过waitTime还未获取到则返回false * * @param lockKey key * @param waitTime 尝试获取时间 * @param leaseTime 锁持有时间 * @param unit 时间单位 * @return true-获取锁成功 false-获取锁失败 */ boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit); /** * 锁是否被任意一个线程锁持有 * * @param lockKey * @return true-被锁 false-未被锁 */ boolean isLocked(String lockKey); /** * lock.isHeldByCurrentThread()的作用是查询当前线程是否保持此锁定 * @param lockKey * @return */ boolean isHeldByCurrentThread(String lockKey); /** * 添加元素到布隆过滤器中 * @param bloomFilterName * @param value */ void bfAdd(String bloomFilterName, String value); /** * 初始化布隆过滤器 * @param bloomFilterName 默认给了expectedInsertions = 1000000L falseProbability = 0.0001 * @return 布隆过滤器名称 */ RBloomFilter<String> init(String bloomFilterName); /** * 初始化布隆过滤器 * @param bloomFilterName 名称 * @param expectedInsertions 预计插入元素总量 * @param falseProbability 精度(误判率) * @return 布隆过滤器名称 */ RBloomFilter<String> init(String bloomFilterName, Long expectedInsertions, Double falseProbability); /** * 删除布隆过滤器(注意不是删除元素) * @param bloomFilterName 名称 */ void bfDelete(String bloomFilterName); /** * 判断 * @param bloomFilterName 名称 * @param value false表示则一定不存在,true 则可能存在 * @return */ boolean bfContains(String bloomFilterName, String value); } public class RedissonDistributeLocker implements DistributeLocker { private final RedissonClient redissonClient; public RedissonDistributeLocker(RedissonClient redissonClient) { this.redissonClient = redissonClient; } @Override public void lock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.lock(); } @Override public void unlock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.unlock(); } @Override public void lock(String lockKey, int leaseTime) { RLock lock = redissonClient.getLock(lockKey); lock.lock(leaseTime, TimeUnit.MILLISECONDS); } @Override public void lock(String lockKey, int timeout, TimeUnit unit) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, unit); } @Override public boolean tryLock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); return lock.tryLock(); } @Override public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) { RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { e.printStackTrace(); } return false; } @Override public boolean isLocked(String lockKey) { RLock lock = redissonClient.getLock(lockKey); return lock.isLocked(); } @Override public boolean isHeldByCurrentThread(String lockKey) { RLock lock = redissonClient.getLock(lockKey); return lock.isHeldByCurrentThread(); } @Override public void bfAdd(String bloomFilterName, String value) { RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter(bloomFilterName); bloomFilter.add(value); } @Override public RBloomFilter<String> init(String bloomFilterName) { long expectedInsertions = 1000000L; double falseProbability = 0.0001; RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter(bloomFilterName); bloomFilter.tryInit(expectedInsertions, falseProbability); return bloomFilter; } @Override public RBloomFilter<String> init(String bloomFilterName, Long expectedInsertions, Double falseProbability) { RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter(bloomFilterName); bloomFilter.tryInit(expectedInsertions, falseProbability); return bloomFilter; } @Override public void bfDelete(String bloomFilterName) { RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter(bloomFilterName); bloomFilter.delete(); } @Override public boolean bfContains(String bloomFilterName, String value) { RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter(bloomFilterName); return bloomFilter.contains(value); } }添加工具类
public class RedissonUtils { private static DistributeLocker locker; public static void setLocker(DistributeLocker locker) { RedissonUtils.locker = locker; } public static void lock(String lockKey) { locker.lock(lockKey); } public static void unlock(String lockKey) { locker.unlock(lockKey); } public static void lock(String lockKey, int timeout) { locker.lock(lockKey, timeout); } public static void lock(String lockKey, int timeout, TimeUnit unit) { locker.lock(lockKey, timeout, unit); } public static boolean tryLock(String lockKey) { return locker.tryLock(lockKey); } public static boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) { return locker.tryLock(lockKey, waitTime, leaseTime, unit); } public static boolean isLocked(String lockKey) { return locker.isLocked(lockKey); } public static boolean isHeldByCurrentThread(String lockKey) { return locker.isHeldByCurrentThread(lockKey); } public static void bfAdd(String bloomFilterName, String value) { locker.bfAdd(bloomFilterName, value); } public static void bfDelete(String bloomFilterName) { locker.bfDelete(bloomFilterName); } public static RBloomFilter<String> init(String bloomFilterName) { return locker.init(bloomFilterName); } public static RBloomFilter<String> init(String bloomFilterName, Long expectedInsertions, Double falseProbability) { return locker.init(bloomFilterName, expectedInsertions, falseProbability); } public static boolean bfContains(String bloomFilterName, String value) { return locker.bfContains(bloomFilterName, value); } }把工具类注入到spring容器中
@Bean public RedissonDistributeLocker redissonLocker(RedissonClient redissonClient) { RedissonDistributeLocker locker = new RedissonDistributeLocker(redissonClient); RedissonUtils.setLocker(locker); return locker; }布隆过滤器测试
public static void main(String[] args) { Config config = new Config(); config.useSentinelServers() .setMasterName("mymaster") .addSentinelAddress("redis://192.168.1.213:26379", "redis://192.168.1.214:26380", "redis://192.168.1.214:26381") .setPassword("123456") .setDatabase(11) .setRetryInterval(5000) .setTimeout(10000) .setConnectTimeout(10000); RedissonClient redissonClient = Redisson.create(config); DistributeLocker locker = new RedissonDistributeLocker(redissonClient); RedissonUtils.setLocker(locker); String bloomFilterName = "bloomFilter"; RedissonUtils.bfDelete(bloomFilterName); long st = System.currentTimeMillis(); RedissonUtils.isLocked("123"); log.debug("test connection used time={} ms", System.currentTimeMillis() - st); long st2 = System.currentTimeMillis(); RedissonUtils.isLocked("123"); log.debug("test connection used time={} ms", System.currentTimeMillis() - st2); RBloomFilter<String> bloomFilter = RedissonUtils.init(bloomFilterName); for (int i = 0; i < 1000; i++) { RedissonUtils.bfAdd(bloomFilterName, bloomFilterName + i); } log.debug("insert 1000 data used time={} ms", System.currentTimeMillis() - st); // log.debug("current bloomFilter count={}", bloomFilter.count()); log.debug("bfContains 1001={} require false", RedissonUtils.bfContains(bloomFilterName, bloomFilterName + 1001)); log.debug("bfContains 1002={} require false", RedissonUtils.bfContains(bloomFilterName, bloomFilterName + 1002)); log.debug("bfContains 1003={} require false", RedissonUtils.bfContains(bloomFilterName, bloomFilterName + 1003)); log.debug("bfContains 1004={} require false", RedissonUtils.bfContains(bloomFilterName, bloomFilterName + 1004)); log.debug("bfContains 1005={} require false", RedissonUtils.bfContains(bloomFilterName, bloomFilterName + 1005)); for (int i = 1000; i < 2000; i++) { RedissonUtils.bfAdd(bloomFilterName, bloomFilterName + i); } log.debug("bfContains 1001={} require true", RedissonUtils.bfContains(bloomFilterName, bloomFilterName + 1001)); log.debug("bfContains 1002={} require true", RedissonUtils.bfContains(bloomFilterName, bloomFilterName + 1002)); log.debug("bfContains 1003={} require true", RedissonUtils.bfContains(bloomFilterName, bloomFilterName + 1003)); log.debug("bfContains 1004={} require true", RedissonUtils.bfContains(bloomFilterName, bloomFilterName + 1004)); log.debug("bfContains 1005={} require true", RedissonUtils.bfContains(bloomFilterName, bloomFilterName + 1005)); }避免用户同一个操作,因为网络延迟等原因,多次提交到服务端,导致的重复操作。 通过切面方法,在切面内实现,方法进来时,尝试加锁,加锁成功则继续操作,加锁失败则返回操作失败
@Around(value = "pointcut(logAndNoRepeatSubmit)") public Object around(ProceedingJoinPoint point, LogAndNoRepeatSubmit logAndNoRepeatSubmit) throws Throwable { String key = LogAspectUtil.getKeyFromRequest(); if (RedissonUtils.tryLock(key)) { Object result = LogAspectUtil.getObject(point, key); return result; } else { return R.fail("操作太频繁,请稍后再试"); } } /** * 根据用户的uid+用户请求的url路径作为唯一标识 * @return 用户请求唯一标识 */ public static String getKeyFromRequest() { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // Assert.notNull(request, "request can not null"); // 此处可以用token或者JSessionId // String token = request.getHeader("Authorization"); String path = request.getServletPath(); long uid = JwtUtil.getUid(); return uid + path; } public static Object getObject(ProceedingJoinPoint point, String key) throws Throwable { Object result = null; try { result = point.proceed(); } catch (Throwable e) { log.error("不重复提交日志", e); throw e; } finally { //双重检查释放锁 if (RedissonUtils.isLocked(key)) { if (RedissonUtils.isHeldByCurrentThread(key)) { RedissonUtils.unlock(key); } } } return result; }待实现 思路: 1、通过任务调度器或者在项目启动完成之后,把项目中免权限的表查询的id初始化到布隆过滤器 2、查询进来之前,先通过布隆过滤器判断,是否存在,不存在则直接返回不存在,可能存在才去查找数据库 3、对于后续增长的id,则可以通过同步的方式或者查询数据库成功时,动态添加到过滤器中
