在非分布式系統中要實現鎖的機制很簡單,利用java.util.concurrent.locks包下的Lock和關鍵字synchronized都可以實現。但是在分布式系統中,如何實現各個單獨的微服務需要共享某個資源的時候進行有效的鎖的保護機制呢?這邊使用Redisson來實現。
一、Redisson簡介
Redisson是一個在Redis的基礎上實現的Java駐內存數據網格(In-Memory Data Grid)。它不僅提供了一系列的分布式的Java常用對象,還提供了許多分布式服務。Redisson提供了使用Redis的最簡單和最便捷的方法。Redisson的宗旨是促進使用者對Redis的關注分離(Separation of Concern),從而讓使用者能夠將精力更集中地放在處理業務邏輯上。更多,https://github.com/redisson/redisson/wiki/1.-%E6%A6%82%E8%BF%B0
二、springboot+redission 演示
1、pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>1.5.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>1.5.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>1.5.8.RELEASE</version> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.3.2</version> </dependency> </dependencies>
2、application.properties
server.port=8070
# redisson
redisson.address=192.168.3.3:6379
redisson.password=test123
3、DistributedLocker
import java.util.concurrent.TimeUnit; /** * 定義Lock的接口類 * @author ko * */ public interface DistributedLocker { void lock(String lockKey); void unlock(String lockKey); void lock(String lockKey, int timeout); void lock(String lockKey, TimeUnit unit ,int timeout); }
4、RedissonDistributedLocker
import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import com.redlock.locker.DistributedLocker; import java.util.concurrent.TimeUnit; /** * Lock接口實現類 * @author ko * */ public class RedissonDistributedLocker implements DistributedLocker { private 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.SECONDS); } @Override public void lock(String lockKey, TimeUnit unit ,int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, unit); } public void setRedissonClient(RedissonClient redissonClient) { this.redissonClient = redissonClient; } }
5、RedissonProperties
import org.springframework.boot.context.properties.ConfigurationProperties; /** * redisson屬性裝配類 * @author ko * */ @ConfigurationProperties(prefix = "redisson") public class RedissonProperties { private int timeout = 3000; private String address; private String password; private int connectionPoolSize = 64; private int connectionMinimumIdleSize=10; private int slaveConnectionPoolSize = 250; private int masterConnectionPoolSize = 250; private String[] sentinelAddresses; private String masterName; public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } public int getSlaveConnectionPoolSize() { return slaveConnectionPoolSize; } public void setSlaveConnectionPoolSize(int slaveConnectionPoolSize) { this.slaveConnectionPoolSize = slaveConnectionPoolSize; } public int getMasterConnectionPoolSize() { return masterConnectionPoolSize; } public void setMasterConnectionPoolSize(int masterConnectionPoolSize) { this.masterConnectionPoolSize = masterConnectionPoolSize; } public String[] getSentinelAddresses() { return sentinelAddresses; } public void setSentinelAddresses(String sentinelAddresses) { this.sentinelAddresses = sentinelAddresses.split(","); } public String getMasterName() { return masterName; } public void setMasterName(String masterName) { this.masterName = masterName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public int getConnectionPoolSize() { return connectionPoolSize; } public void setConnectionPoolSize(int connectionPoolSize) { this.connectionPoolSize = connectionPoolSize; } public int getConnectionMinimumIdleSize() { return connectionMinimumIdleSize; } public void setConnectionMinimumIdleSize(int connectionMinimumIdleSize) { this.connectionMinimumIdleSize = connectionMinimumIdleSize; } }
6、RedissLockUtil
import java.util.concurrent.TimeUnit; import com.redlock.locker.DistributedLocker; /** * redis分布式鎖工具類 * @author ko * */ public class RedissLockUtil { private static DistributedLocker redissLock; public static void setLocker(DistributedLocker locker) { redissLock = locker; } public static void lock(String lockKey) { redissLock.lock(lockKey); } public static void unlock(String lockKey) { redissLock.unlock(lockKey); } /** * 帶超時的鎖 * @param lockKey * @param timeout 超時時間 單位:秒 */ public static void lock(String lockKey, int timeout) { redissLock.lock(lockKey, timeout); } /** * 帶超時的鎖 * @param lockKey * @param unit 時間單位 * @param timeout 超時時間 */ public static void lock(String lockKey, TimeUnit unit ,int timeout) { redissLock.lock(lockKey, unit, timeout); } }
7、RedissonAutoConfiguration
import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.config.SentinelServersConfig; import org.redisson.config.SingleServerConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.redlock.bean.RedissonProperties; import com.redlock.locker.DistributedLocker; import com.redlock.locker.impl.RedissonDistributedLocker; import com.redlock.util.RedissLockUtil; /** * Redisson+springboot 自動配置類 * @author ko * */ @Configuration @ConditionalOnClass(Config.class) @EnableConfigurationProperties(RedissonProperties.class) public class RedissonAutoConfiguration { @Autowired private RedissonProperties redssionProperties; /** * 哨兵模式自動裝配 * @return */ @Bean @ConditionalOnProperty(name="redisson.master-name") RedissonClient redissonSentinel() { Config config = new Config(); SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redssionProperties.getSentinelAddresses()) .setMasterName(redssionProperties.getMasterName()) .setTimeout(redssionProperties.getTimeout()) .setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize()) .setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize()); if(redssionProperties.getPassword() != null && !"".equals(redssionProperties.getPassword())) { serverConfig.setPassword(redssionProperties.getPassword()); } return Redisson.create(config); } /** * 單機模式自動裝配 * @return */ @Bean @ConditionalOnProperty(name="redisson.address") RedissonClient redissonSingle() { Config config = new Config(); SingleServerConfig serverConfig = config.useSingleServer() .setAddress(redssionProperties.getAddress()) .setTimeout(redssionProperties.getTimeout()) .setConnectionPoolSize(redssionProperties.getConnectionPoolSize()) .setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize()); if(redssionProperties.getPassword() != null && !"".equals(redssionProperties.getPassword())) { serverConfig.setPassword(redssionProperties.getPassword()); } return Redisson.create(config); } /** * 裝配locker類,並將實例注入到RedissLockUtil中 * @return */ @Bean DistributedLocker distributedLocker(RedissonClient redissonClient) { RedissonDistributedLocker locker = new RedissonDistributedLocker(); locker.setRedissonClient(redissonClient); RedissLockUtil.setLocker(locker); return locker; } }
8、暴露接口 TestController
import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.redlock.util.RedissLockUtil; /** * 測試類 * @author ko * */ @RestController public class TestController { // private static final Logger LOG = Logger.getLogger(BootApplication.class.getName()); // LOG.log(Level.INFO, "All procn"); private final String _lock = "_lock"; static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String getCurrentDate(){ return sdf.format(new Date()); } @RequestMapping(value = "/testlock",method=RequestMethod.GET) public String testlock(String name) { new Thread(new Runnable() { @Override public void run() { RedissLockUtil.lock(_lock, TimeUnit.MINUTES, 10); System.out.println(getCurrentDate()+" "+name+" begin..."); for (int i = 0; i < 20; i++) { try { Thread.sleep(1000); System.out.println(getCurrentDate()+" "+name+" "+i); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getCurrentDate()+" "+name+" end..."); RedissLockUtil.unlock(_lock); } }).start(); return "testlock"; } }
9、微服務啟動類 BootApplication
@SpringBootApplication public class BootApplication { public static void main(String[] args) { SpringApplication.run(BootApplication.class, args); } }
三、啟動演示
復制項目,第二個服務的端口改為8071,啟動兩個服務,先在瀏覽器里輸入http://localhost:8070/testlock?name=zhang,3s后再輸入http://localhost:8071/testlock?name=wang,3s后再輸入http://localhost:8070/testlock?name=zhang1,3s后再輸入http://localhost:8071/testlock?name=wang1,如果沒有鎖機制,打印的數據應該是交叉的,反之,同一時刻只會打印一次的請求,不會出現交叉打印數據的情況。
官方文檔:https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95