分佈式鎖實現(Redis和zookeeper)

鎖,就是在同一時刻,某個資源被某一個線程獨佔。單機系統中,因爲是在同一個虛擬機中,爲了使得線程可以獨佔資源,咱們一般是對資源加鎖,或者每個線程維護一個資源的備份。在分佈式環境中,因爲對資源的操做是跨域的,所以須要組件來實現分分佈式鎖。html

一,使用redis實現分佈式鎖java

redis中的set  nx 命令,當key不存在時,才能在redis中將key添加成功,利用該屬性能夠實現分佈式鎖,而且redis對於key有失效時間,能夠控制當某個客戶端加鎖成功以後掛掉,致使阻塞的問題。node

廢話很少說,上代碼:mysql

1,POM文件web

<?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客戶端redis

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.htmlspring

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加鎖服務sql

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實現分佈式鎖apache

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);
    }
    
    
}
相關文章
相關標籤/搜索