1,redis集群的配置文件
redis.cluster.nodes=192.168.1.102:7000,192.168.1.102:7001,192.168.1.102:7002,192.168.1.102:7003,192.168.1.102:7004,192.168.1.102:7005 redis.cluster.testOnReturn=true redis.cluster.maxIdle=10 redis.cluster.minIdle=2 redis.cluster.maxWaitMills=3000 redis.cluster.testOnBorrow=true redis.cluster.maxTotal=4 redis.cluster.connectionTimeOut=30000 |
2,redis集群的连接配置
/** * 提供集群部署的情况下面的redisTemplate支持 * @param factory * @return */ @Bean("redisTemplate") public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>(); redisTemplate.setConnectionFactory(factory); Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(om); redisTemplate.setKeySerializer(serializer); redisTemplate.setValueSerializer(serializer); redisTemplate.afterPropertiesSet(); //redisTemplate.setEnableTransactionSupport(true); return redisTemplate; } /** * jedis 连接池 * @return */ private JedisPoolConfig jedisPoolConfig() { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(redisConfig.getCluster().getMaxTotal()); config.setMaxIdle(redisConfig.getCluster().getMaxIdle()); config.setMaxWaitMillis(redisConfig.getCluster().getMaxWaitMills()); config.setTestOnBorrow(redisConfig.getCluster().getTestOnBorrow()); return config; } @Bean(name = "redisCacheManager") public CacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); cacheManager.setDefaultExpiration(1800); return cacheManager; } /** * redisCluster配置 * * @return */ @Bean public RedisClusterConfiguration redisProperties() { Map<String, Object> source = new HashMap<String, Object>(); source.put("spring.redis.cluster.nodes", redisConfig.getCluster().getNodes()); source.put("spring.redis.cluster.timeout", redisConfig.getCluster().getConnectionTimeOut()); return new RedisClusterConfiguration(new MapPropertySource("RedisProperties", source)); } @Bean("jedisConnFactory") public JedisConnectionFactory jedisConnectionFactory() { //redis cluster JedisConnectionFactory factory = new JedisConnectionFactory(redisProperties(),jedisPoolConfig()); return factory; } |
3,redis集群下分布式锁的主要实现方法
package com.dream21th.dream21thredis.lock; import java.util.Random; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import lombok.extern.slf4j.Slf4j; /** * Redis distributed lock implementation. * * @author dream21th */ @Slf4j public class RedisLock { private RedisTemplate redisTemplate; private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100; /** * Lock key path. */ private String lockKey; /** * 锁超时时间,防止线程在入锁以后,无限的执行等待 */ private int expireMsecs = 60 * 1000; /** * 锁等待时间,防止线程饥饿 */ private int timeoutMsecs = 10 * 1000; private volatile boolean locked = false; /** * Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs. * * @param lockKey lock key (ex. account:1, ...) */ public RedisLock(RedisTemplate redisTemplate, String lockKey) { this.redisTemplate = redisTemplate; this.lockKey = lockKey + "_lock"; } /** * Detailed constructor with default lock expiration of 60000 msecs. * */ public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs) { this(redisTemplate, lockKey); this.timeoutMsecs = timeoutMsecs; } /** * Detailed constructor. * */ public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs) { this(redisTemplate, lockKey, timeoutMsecs); this.expireMsecs = expireMsecs; } /** * @return lock key */ public String getLockKey() { return lockKey; } private String get(final String key) { Object obj = null; try { obj = redisTemplate.execute(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { //StringRedisSerializer serializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class); byte[] data = connection.get(serializer.serialize(key)); connection.close(); if (data == null) { return null; } return serializer.deserialize(data); } }); } catch (Exception e) { log.error("get redis error, key : {}", key); } return obj != null ? obj.toString() : null; } private boolean setNX(final String key, final String value) { Object obj = null; try { obj = redisTemplate.execute(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { //StringRedisSerializer serializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class); Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value)); connection.close(); return success; } }); } catch (Exception e) { log.info("{}",e); log.error("setNX redis error, key : {}", key); } return obj != null ? (Boolean) obj : false; } private String getSet(final String key, final String value) { Object obj = null; try { obj = redisTemplate.execute(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { //StringRedisSerializer serializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class); byte[] ret = connection.getSet(serializer.serialize(key), serializer.serialize(value)); connection.close(); return serializer.deserialize(ret); } }); } catch (Exception e) { log.error("setNX redis error, key : {}", key); } return obj != null ? (String) obj : null; } /** * 获得 lock. * 实现思路: 主要是使用了redis 的setnx命令,缓存了锁. * reids缓存的key是锁的key,所有的共享, value是锁的到期时间(注意:这里把过期时间放在value了,没有时间上设置其超时时间) * 执行过程: * 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁 * 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值 * * @return true if lock is acquired, false acquire timeouted * @throws InterruptedException in case of thread interruption */ public boolean lock() throws InterruptedException { int timeout = timeoutMsecs; while (timeout >= 0) { long expires = System.currentTimeMillis() + expireMsecs + 1; String expiresStr = String.valueOf(expires); //锁到期时间 //log.info("=========="+Thread.currentThread().getName()+"========="+timeout); if (this.setNX(lockKey, expiresStr)) { // lock acquired locked = true; return true; } String currentValueStr = this.get(lockKey); //redis里的时间 if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的 // lock is expired String oldValueStr = this.getSet(lockKey, expiresStr); //获取上一个锁到期时间,并设置现在的锁到期时间, //只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的 if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { //防止误删(覆盖,因为key是相同的)了他人的锁——这里达不到效果,这里值会被覆盖,但是因为什么相差了很少的时间,所以可以接受 //[分布式的情况下]:如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁 // lock acquired locked = true; return true; } } //timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS; int sleepTime=new Random().nextInt(10)*DEFAULT_ACQUIRY_RESOLUTION_MILLIS; timeout -=sleepTime; //log.info("=========="+Thread.currentThread().getName()+"========="+sleepTime); /* 延迟100 毫秒, 这里使用随机时间可能会好一点,可以防止饥饿进程的出现,即,当同时到达多个进程, 只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面有来了一些进行,也以同样的频率申请锁,这将可能导致前面来的锁得不到满足. 使用随机的等待时间可以一定程度上保证公平性 */ Thread.sleep(sleepTime); } return false; } /** * Acqurired lock release. */ public void unlock() { if (locked) { redisTemplate.delete(lockKey); locked = false; } } public boolean isExpired(){ String currentValueStr = this.get(lockKey); //redis里的时间 if(currentValueStr==null){ return true; } return Long.parseLong(currentValueStr) < System.currentTimeMillis()?true:false; } } |
4,测试方法,模仿100的线程操作()
package com.dream21th.dream21thredis.service; import java.util.stream.IntStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import com.dream21th.dream21thredis.lock.RedisLock; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class RedisLockService { @Autowired private RedisTemplate<Object, Object> redisTemplate; private static String key = "lock-test"; private static int count=10; public void test() { RedisLock lock = new RedisLock(redisTemplate, key, 10000, 20000); try { if (lock.lock()) { log.info(Thread.currentThread().getName()+"------>>>>begin"); Thread.sleep(1000); if(count>=1){ count--; } log.info(Thread.currentThread().getName()+"------>>>>end"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { // 为了让分布式锁的算法更稳键些,持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起, // 操作完的时候锁因为超时已经被别人获得,这时就不必解锁了. if(!lock.isExpired()){ lock.unlock(); } } } public void run(){ IntStream.range(0, 100).forEach(i->{ Thread run=new Thread(new Runnable() { @Override public void run() { test(); } }); run.start(); }); } } 5,测试controller
6,测试结果
2018-06-19 14:42:40.706 INFO 7064 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started 2018-06-19 14:42:40.821 INFO 7064 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 115 ms 2018-06-19 14:42:41.072 INFO 7064 --- [ Thread-93] c.d.d.service.RedisLockService : Thread-93------>>>>begin 2018-06-19 14:42:42.072 INFO 7064 --- [ Thread-93] c.d.d.service.RedisLockService : count:9 2018-06-19 14:42:42.072 INFO 7064 --- [ Thread-93] c.d.d.service.RedisLockService : Thread-93------>>>>end 2018-06-19 14:42:42.079 INFO 7064 --- [ Thread-73] c.d.d.service.RedisLockService : Thread-73------>>>>begin 2018-06-19 14:42:43.080 INFO 7064 --- [ Thread-73] c.d.d.service.RedisLockService : count:8 2018-06-19 14:42:43.080 INFO 7064 --- [ Thread-73] c.d.d.service.RedisLockService : Thread-73------>>>>end 2018-06-19 14:42:43.335 INFO 7064 --- [ Thread-100] c.d.d.service.RedisLockService : Thread-100------>>>>begin 2018-06-19 14:42:44.336 INFO 7064 --- [ Thread-100] c.d.d.service.RedisLockService : count:7 2018-06-19 14:42:44.336 INFO 7064 --- [ Thread-100] c.d.d.service.RedisLockService : Thread-100------>>>>end 2018-06-19 14:42:44.341 INFO 7064 --- [ Thread-21] c.d.d.service.RedisLockService : Thread-21------>>>>begin 2018-06-19 14:42:45.341 INFO 7064 --- [ Thread-21] c.d.d.service.RedisLockService : count:6 2018-06-19 14:42:45.341 INFO 7064 --- [ Thread-21] c.d.d.service.RedisLockService : Thread-21------>>>>end 2018-06-19 14:42:45.346 INFO 7064 --- [ Thread-78] c.d.d.service.RedisLockService : Thread-78------>>>>begin 2018-06-19 14:42:46.346 INFO 7064 --- [ Thread-78] c.d.d.service.RedisLockService : count:5 2018-06-19 14:42:46.346 INFO 7064 --- [ Thread-78] c.d.d.service.RedisLockService : Thread-78------>>>>end 2018-06-19 14:42:46.353 INFO 7064 --- [ Thread-16] c.d.d.service.RedisLockService : Thread-16------>>>>begin 2018-06-19 14:42:47.353 INFO 7064 --- [ Thread-16] c.d.d.service.RedisLockService : count:4 2018-06-19 14:42:47.353 INFO 7064 --- [ Thread-16] c.d.d.service.RedisLockService : Thread-16------>>>>end 2018-06-19 14:42:47.361 INFO 7064 --- [ Thread-47] c.d.d.service.RedisLockService : Thread-47------>>>>begin 2018-06-19 14:42:48.361 INFO 7064 --- [ Thread-47] c.d.d.service.RedisLockService : count:3 2018-06-19 14:42:48.361 INFO 7064 --- [ Thread-47] c.d.d.service.RedisLockService : Thread-47------>>>>end 2018-06-19 14:42:48.448 INFO 7064 --- [ Thread-31] c.d.d.service.RedisLockService : Thread-31------>>>>begin 2018-06-19 14:42:49.448 INFO 7064 --- [ Thread-31] c.d.d.service.RedisLockService : count:2 2018-06-19 14:42:49.448 INFO 7064 --- [ Thread-31] c.d.d.service.RedisLockService : Thread-31------>>>>end 2018-06-19 14:42:49.458 INFO 7064 --- [ Thread-61] c.d.d.service.RedisLockService : Thread-61------>>>>begin 2018-06-19 14:42:50.459 INFO 7064 --- [ Thread-61] c.d.d.service.RedisLockService : count:1 2018-06-19 14:42:50.460 INFO 7064 --- [ Thread-61] c.d.d.service.RedisLockService : Thread-61------>>>>end 2018-06-19 14:42:50.472 INFO 7064 --- [ Thread-94] c.d.d.service.RedisLockService : Thread-94------>>>>begin 2018-06-19 14:42:51.473 INFO 7064 --- [ Thread-94] c.d.d.service.RedisLockService : count:0 2018-06-19 14:42:51.473 INFO 7064 --- [ Thread-94] c.d.d.service.RedisLockService : Thread-94------>>>>end 2018-06-19 14:42:51.501 INFO 7064 --- [ Thread-12] c.d.d.service.RedisLockService : Thread-12------>>>>begin 2018-06-19 14:42:52.501 INFO 7064 --- [ Thread-12] c.d.d.service.RedisLockService : Thread-12------>>>>end |