分布式锁实现(Redis和zookeeper)
锁,就是在同一时刻,某个资源被某一个线程独占。单机系统中,由于是在同一个虚拟机中,为了使得线程能够独占资源,我们通常是对资源加锁,或者每一个线程维护一个资源的备份。在分布式环境中,由于对资源的操作是跨域的,因此需要组件来实现分分布式锁。
一,使用redis实现分布式锁
redis中的set nx 命令,当key不存在时,才能在redis中将key添加成功,利用该属性可以实现分布式锁,并且redis对于key有失效时间,可以控制当某个客户端加锁成功之后挂掉,导致阻塞的问题。
废话不多说,上代码:
1,POM文件
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <!-- 项目信息 begin --> <groupId>com.microservice</groupId> <artifactId>spring-web</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-web</name> <url>http://maven.apache.org</url> <!-- 项目信息end --> <!-- 属性配置 begin --> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <!-- 属性配置end --> <!-- 父依赖 begin --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.1.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <!-- 父依赖 end --> <dependencies> <!-- 添加web包 begin --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- 该包中包含requestMapping restController 等注解 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- 添加web包 end --> <!-- mybatis依赖 begin --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <!-- mybatis依赖 end --> <!-- mysql数据库配置 begin --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- mysql数据库配置 end --> <!-- redis配置 begin --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.7.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>RELEASE</version> </dependency> <!-- redis配置 end --> <!-- mq begin --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-pool</artifactId> <!-- <version>5.7.0</version> --> </dependency> <!-- mq end --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <!-- 热部署 begin --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <!-- 热部署 end --> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork><!-- 如果没有该项配置,devtools不会起作用,即应用不会restart --> </configuration> </plugin> </plugins> </build> </project>
2,添加jedis客户端
package org.spring.web.component; import java.util.Arrays; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import redis.clients.jedis.Jedis; /** * redis配置类 */ @Configuration @EnableCaching @PropertySource(value = "classpath:application.properties", encoding = "UTF-8") public class RedisConfig extends CachingConfigurerSupport{ @Value("${spring.redis.host}") private String redisHost; @Value("${spring.redis.port}") private int redisPort; @SuppressWarnings("rawtypes") @Bean public CacheManager cacheManager(RedisTemplate redisTemplate) { RedisCacheManager rcm = new RedisCacheManager(redisTemplate); // 多个缓存的名称,目前只定义了一个 rcm.setCacheNames(Arrays.asList("thisredis")); //设置缓存过期时间(秒) rcm.setDefaultExpiration(600); return rcm; } @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) { StringRedisTemplate template = new StringRedisTemplate(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } @Bean //客户端连接信息是怎么获取的?不设置,或者设置错误的值也可以连接到redis public Jedis jedis(){ //return new Jedis(redisHost,redisPort); System.out.println("redisHost>>>>>>>"+redisHost); System.out.println("redisPort>>>>>>>"+redisPort); return new Jedis(); } }
3,重点,Redis加锁和解锁,此处代码来源 https://www.cnblogs.com/linjiqin/p/8003838.html
package org.spring.web.component; import java.util.Collections; import redis.clients.jedis.Jedis; /** * * 项目名称:spring-web 类名称:RedisDistributeLocak 类描述: 创建人:john 创建时间:2018年8月2日 * 上午11:50:10 修改人:john 修改时间:2018年8月2日 上午11:50:10 修改备注: * * @version * */ public class RedisDistributeLock { private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; private static final Long RELEASE_SUCCESS = 1L; /** * 尝试获取分布式锁 * * @param jedis * Redis客户端 * @param lockKey * 锁 * @param requestId * 请求标识 * @param expireTime * 超期时间 * @return 是否获取成功 */ public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { System.out.println("jedis>>>>>>>>"+jedis); String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); System.out.println("redisLockResult>>>>>>>"+result); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } /** * 释放分布式锁 * * @param jedis * Redis客户端 * @param lockKey * 锁 * @param requestId * 请求标识 * @return 是否释放成功 */ public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } }
4,redis加锁服务
package org.spring.web.service.serviceImpl; import org.spring.web.component.RedisDistributeLock; import org.spring.web.service.RedisService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; /** * * 项目名称:spring-web * 类名称:RedisServiceImpl * 类描述: * 创建人:john * 创建时间:2018年8月2日 下午12:34:29 * 修改人:john * 修改时间:2018年8月2日 下午12:34:29 * 修改备注: * @version * */ @Service public class RedisServiceImpl{ @Autowired private Jedis jedis; public boolean tryGetDistributedLock(String lockKey,String requestId,int expireTime ){ return RedisDistributeLock.tryGetDistributedLock(jedis, lockKey, requestId, expireTime); } public boolean releaseDistributedLock(String lockKey,String requestId){ return RedisDistributeLock.releaseDistributedLock(jedis, lockKey, requestId); } }
5,测试验证
package org.spring.web.controller; import org.spring.web.service.serviceImpl.RedisServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * * 项目名称:spring-web * 类名称:RedisController * 类描述: * 创建人:john * 创建时间:2018年8月2日 下午2:16:22 * 修改人:john * 修改时间:2018年8月2日 下午2:16:22 * 修改备注: * @version * */ @RestController @RequestMapping("/redis") public class RedisController { @Autowired private RedisServiceImpl redisService; @RequestMapping("/getLock") public boolean getRedisLock(){ System.out.println(">>>>>>>>>>>>>>>>>>"); return redisService.tryGetDistributedLock("redisLock", "11200", 2000000000); } @GetMapping("/releaseLock") public boolean releaseRedisLock(){ return redisService.releaseDistributedLock("redisLock", "11200"); } }
二,使用zookeeper实现分布式锁
zookeeper中在node下,可以创建临时节点,当加锁的客户端挂掉是,临时节点就会自动删除,利用该特性,可以实现分布式锁。
1,pom文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.2.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> </properties> <dependencies> <!-- 提供zookeeper整合的包 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- 热部署工具 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2,单例的 CuratorFramework,该类是操作 zookeeper的客户端
package zookeper.componnent; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; /** * * 项目名称:zookeper * 类名称:ClientSingleton * 类描述: * 创建人:john * 创建时间:2018年8月1日 下午9:06:00 * 修改人:john * 修改时间:2018年8月1日 下午9:06:00 * 修改备注: * @version * */ //@Configuration public class ClientSingleton { private static CuratorFramework client = null; @Value("${zookeeper.connectString}") private String connectString; private ClientSingleton() { System.out.println("zookeeper.connectString>>>>>>>>>>>>>>>>"+connectString); RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181").retryPolicy(retryPolicy) .sessionTimeoutMs(1000 * 6).connectionTimeoutMs(1000 * 6).build(); } public static synchronized CuratorFramework newClient() { if (client == null) { new ClientSingleton(); } return client; } public static void start() { client.start(); } public static void close() { client.close(); } }
3,zookeeper锁服务层
package zookeper.service; import java.util.concurrent.TimeUnit; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RestController; import zookeper.componnent.ClientSingleton; /** * * 项目名称:zookeper * 类名称:DistributeZookLock * 类描述: * 创建人:john * 创建时间:2018年8月1日 下午5:07:56 * 修改人:john * 修改时间:2018年8月1日 下午5:07:56 * 修改备注: * @version * */ @Service public class DistributeZookLock { private CuratorFramework curatorFramework=ClientSingleton.newClient(); //@Autowired private InterProcessSemaphoreMutex InterProcessMutex=new InterProcessSemaphoreMutex(curatorFramework, "/config"); public boolean tryLock(long time,TimeUnit unit){ try { return InterProcessMutex.acquire(time, unit); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return true; } public boolean unLock(){ try { InterProcessMutex.release(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return true; } public static void main(String[] args){ System.out.println("》》》》》"+new DistributeZookLock().curatorFramework); } }
4,验证 controller层
package zookeper.controller; import java.util.List; import java.util.concurrent.TimeUnit; import org.springframework.core.env.Environment; import org.apache.curator.framework.CuratorFramework; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import zookeper.service.DistributeZookLock; /** * * 项目名称:zookeper 类名称:ZookController 类描述: 创建人:john 创建时间:2018年7月31日 下午3:51:56 * 修改人:john 修改时间:2018年7月31日 下午3:51:56 修改备注: * * @version * */ @RestController @RequestMapping("/zook") public class ZookController { @Autowired private DiscoveryClient client; @Autowired private Environment environment; @Autowired private CuratorFramework curatorFramework; @Autowired private DistributeZookLock zookLock; public String getZook() { return ""; } @RequestMapping("/getServices") public String discoveryClent() { List<String> serviceList = client.getServices(); List<ServiceInstance> list=client.getInstances("info"); //获取实例化的服务 StringBuffer sb = new StringBuffer(); if (list != null && list.size() > 0 ) { sb.append(list.get(0).getUri()+","); System.out.println(">>>>>>>>>>>>>>>>"+list.get(0).isSecure()); } System.out.println("sb>>>>>"+sb); System.out.println("注册服务的数量>>>>>>>>>>>>>>>>>" + serviceList.size()); for (String service : serviceList) { System.out.println("注册的服务>>>>>>" + service); } return "info"; } @GetMapping("/env") public String test() { String[] profiles = environment.getActiveProfiles(); System.out.println("profiles>>>>>>>" + profiles.length); for (String item : profiles) { System.out.println("item>>>>>>>>>>>>>>>" + item); } String name = environment.getProperty("url"); try { System.out.println(">>>>>>curatorFramework>>>>>>"+curatorFramework); List <String> listChildren=curatorFramework.getChildren().forPath("/config/zook"); for(String child:listChildren ){ System.out.println("child>>>>>>>"+child); System.out.println(child+"的值是>>>>>>"+environment.getProperty(child)); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(name); return "Hello," + name; } @RequestMapping("/lock") public boolean zookLock(){ return zookLock.tryLock(10L, TimeUnit.MINUTES); } }