*簡介:RedisConnection實現分布鎖的方式,采用redisTemplate操作redisConnection實現setnx和setex兩個命令連用**
- redisTemplate本身有沒通過valueOperation實現分布式鎖
* 問題探索:
Spring Data Redis提供了與Java客戶端包的集成服務,比如Jedis, JRedis等
通過getNativeConnection的方式可以解決問題嗎?
- Spring Data Redis提供了與Java客戶端包的集成服務,比如Jedis, JRedis等
* 代碼演示
/** * 重寫redisTemplate的set方法 * <p> * 命令 SET resource-name anystring NX EX max-lock-time 是一種在 Redis 中實現鎖的簡單方法。 * <p> * 客戶端執行以上的命令: * <p> * 如果服務器返回 OK ,那么這個客戶端獲得鎖。 * 如果服務器返回 NIL ,那么客戶端獲取鎖失敗,可以在稍后再重試。 * * @param key 鎖的Key * @param value 鎖里面的值 * @param seconds 過去時間(秒) * @return */ private String set(final String key, final String value, final long seconds) { Assert.isTrue(!StringUtils.isEmpty(key), "key不能為空"); return redisTemplate.execute(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { Object nativeConnection = connection.getNativeConnection(); String result = null; if (nativeConnection instanceof JedisCommands) { result = ((JedisCommands) nativeConnection).set(key, value, NX, EX, seconds); } if (!StringUtils.isEmpty(lockKeyLog) && !StringUtils.isEmpty(result)) { logger.info("獲取鎖{}的時間:{}", lockKeyLog, System.currentTimeMillis()); } return result; } }); }
- 為什么新版本的spring-data-redis會報class not can not be case錯誤
io.lettuce.core.RedisAsyncCommandsImpl cannot be cast to redis.clients.jedis.JedisCommands
- 探索spring-data-redis升級
* 官網api分析
https://docs.spring.io/spring-data/redis/docs/1.5.0.RELEASE/api/
https://docs.spring.io/spring-data/redis/docs/2.0.13.RELEASE/api/
* 源碼改造
public Boolean doInRedis(RedisConnection connection) throws DataAccessException { RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection(); return redisConnection.set(key.getBytes(), getHostIp().getBytes(), Expiration.seconds(expire), RedisStringCommands.SetOption.ifAbsent()); }
這樣通過jedis源碼改造后,同樣實現了setnx,setex的連用效果
完整代碼:
package com.concurrent.schedule; import com.concurrent.util.RedisService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisStringCommands; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.types.Expiration; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; /***** * RedisConnection實現分布式鎖 */ @Component public class JedisDistributedLock { private final Logger logger = LoggerFactory.getLogger(JedisDistributedLock.class); private static String LOCK_PREFIX = "JedisDistributedLock_"; private DefaultRedisScript<Boolean> lockScript; @Resource private RedisTemplate<Object, Object> redisTemplate; @Autowired private RedisService redisService; public static final String UNLOCK_LUA; static { StringBuilder sb = new StringBuilder(); sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] "); sb.append("then "); sb.append(" return redis.call(\"del\",KEYS[1]) "); sb.append("else "); sb.append(" return 0 "); sb.append("end "); UNLOCK_LUA = sb.toString(); } @Scheduled(cron = "0/10 * * * * *") public void lockJob() { String lock = LOCK_PREFIX + "JedisNxExJob"; boolean lockRet = false; try { lockRet = this.setLock(lock, 600); //獲取鎖失敗 if (!lockRet) { String value = (String) redisService.genValue(lock); //打印當前占用鎖的服務器IP logger.info("jedisLockJob get lock fail,lock belong to:{}", value); return; } else { //獲取鎖成功 logger.info("jedisLockJob start lock lockNxExJob success"); Thread.sleep(5000); } } catch (Exception e) { logger.error("jedisLockJob lock error", e); } finally { if (lockRet) { logger.info("jedisLockJob release lock success"); releaseLock(lock,getHostIp()); } } } public boolean setLock(String key, long expire) { try { Boolean result = redisTemplate.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.set(key.getBytes(), getHostIp().getBytes(), Expiration.seconds(expire) ,RedisStringCommands.SetOption.ifAbsent()); } }); return result; } catch (Exception e) { logger.error("set redis occured an exception", e); } return false; } /* public String get(String key) { try { RedisCallback<String> callback = (connection) -> { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); return commands.get(key); }; String result = redisTemplate.execute(callback); return result; } catch (Exception e) { logger.error("get redis occured an exception", e); } return ""; }*/ /** * 釋放鎖操作 * @param key * @param value * @return */ private boolean releaseLock(String key, String value) { lockScript = new DefaultRedisScript<Boolean>(); lockScript.setScriptSource( new ResourceScriptSource(new ClassPathResource("unlock.lua"))); lockScript.setResultType(Boolean.class); // 封裝參數 List<Object> keyList = new ArrayList<Object>(); keyList.add(key); keyList.add(value); Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList); return result; } /** * 獲取本機內網IP地址方法 * * @return */ private static String getHostIp() { try { Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces(); while (allNetInterfaces.hasMoreElements()) { NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement(); Enumeration<InetAddress> addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress ip = (InetAddress) addresses.nextElement(); if (ip != null && ip instanceof Inet4Address && !ip.isLoopbackAddress() //loopback地址即本機地址,IPv4的loopback范圍是127.0.0.0 ~ 127.255.255.255 && ip.getHostAddress().indexOf(":") == -1) { return ip.getHostAddress(); } } } } catch (Exception e) { e.printStackTrace(); } return null; } }