分布式锁实现(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);
    }
    
    
}

 

posted @ 2018-08-06 15:00  戈壁飞翔  阅读(2692)  评论(0编辑  收藏  举报