5台redis实现红锁(完整demo)

示例环境:

Spring Boot 

JDK1.8.0_131  

apache-maven-3.5.4  

nginx-1.14.2   

redis-3.2.1

1.Controller :

package com..redlock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/lock")
public class RedLockController {

    @Autowired
    // 红锁
    @Qualifier("RedLockLockService")
    private RedLockService grabService;

    @RequestMapping("/do")
    public String grabMysql(){

        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        int port = request.getLocalPort();
        long i = Thread.currentThread().getId();
          String type = grabService.grabOrder(port+"-"+i);
        System.out.println(port+"-"+i+"返回页面:"+type);
        return type;
    }
}

2. Service  :

package com..redlock;

public interface RedLockService {

    public String grabOrder(String orderId);
}

3. ServiceImpl:

package com..redlock;

import com..dao.SaveInfoMapper;
import com..entity.SaveInfo;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.Date;


@Service("RedLockLockService")
public class RedLockLockServiceImpl implements RedLockService {

    // 红锁
    @Autowired
    @Qualifier("redissonRed1")
    private RedissonClient redissonRed1;
    @Autowired
    @Qualifier("redissonRed2")
    private RedissonClient redissonRed2;
    @Autowired
    @Qualifier("redissonRed3")
    private RedissonClient redissonRed3;
    @Autowired
    @Qualifier("redissonRed4")
    private RedissonClient redissonRed4;
    @Autowired
    @Qualifier("redissonRed5")
    private RedissonClient redissonRed5;


    @Autowired
    SaveInfoMapper saveInfoMapper;

    @Override
    public String grabOrder(String orderId ){
//        System.out.println(orderId+" : 来加锁");

        //生成key
        String lockKey = ("lock_red").intern();

        //红锁 redis son
        RLock rLock1 = redissonRed1.getLock(lockKey);
        RLock rLock2 = redissonRed2.getLock(lockKey);
        RLock rLock3 = redissonRed3.getLock(lockKey);
        RLock rLock4 = redissonRed4.getLock(lockKey);
        RLock rLock5 = redissonRed5.getLock(lockKey);
        RedissonRedLock rLock = new RedissonRedLock(rLock1,rLock2,rLock3,rLock4,rLock5);
        String count ="";
        boolean b1= true;
        boolean b2= true;
        try {
            //包含锁续命 默认设置key 超时时间30秒,过10秒,再延时
             b1 = rLock.tryLock();
            if (b1==true){
                System.out.println(orderId+" : 加锁成功");
                count = updataData();
                System.out.println(orderId+" 数据库返回结果 :"+count);
                if(count.equals("0")){
                    return "数据库总数已为0";
                }
            }else {
//                System.out.println(orderId+" : 加锁失败#####");
                b2 = false;
                try {
                    //未拿到锁,暂停一下再去加锁
                    Thread.sleep(100);
                    grabOrder(orderId);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }catch (Exception e){
            System.out.println("加锁过程异常 :"+e);
        }finally {
            if(b2==true){
                System.out.println(orderId+" : 去解锁~~~~~");
                rLock.unlock();
            }
        }
        return count;
    }


    //数据库写入操作
    public Boolean sendData(String orderId){

        Timestamp saveTime=new Timestamp(new Date().getTime());
        SaveInfo info = new SaveInfo();
        info.setId(orderId);
        info.setApplyno("004098020000002");
        info.setUserName("user");
        info.setUserNumber("002");
        info.setAddress("上海");
        info.setSaveTime(saveTime);
        info.setType(orderId);
        info.setCount("count");
        int mu = saveInfoMapper.insert(info);
        if(mu!=1){
           return false;
        }
        return true;
    }


    //数据库减一操作
    public String updataData() {

        //查出id为1112的数据,判断需要减1的字段当前值
        SaveInfo save = saveInfoMapper.selectInfoByid("1112");
        String type = save.getType();
        if(type.equals("0")){
            return "0";
        }
        int count = Integer.parseInt(type)-1;
        System.out.println("数据库剩余个数 :"+count);
        SaveInfo info = new SaveInfo();
        info.setId("1112");
        info.setType(count+"");
        int mu = saveInfoMapper.updateByPrimaryid(info);
        System.out.println("mu : "+mu);
        if(mu!=1){
            return "数据库扣除报错";
        }
        System.out.println("mu2 : "+mu);
        return "数据库总数减一";
    }
}

4.RedLockConfig:

package com..redlock;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;


@Component
public class RedLockConfig {

    //红锁:
    @Bean(name = "redissonRed1")
    @Primary
    public RedissonClient redissonRed1(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6379").setDatabase(0);
        return Redisson.create(config);
    }
    @Bean(name = "redissonRed2")
    public RedissonClient redissonRed2(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6380").setDatabase(0);
        return Redisson.create(config);
    }
    @Bean(name = "redissonRed3")
    public RedissonClient redissonRed3(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6381").setDatabase(0);
        return Redisson.create(config);
    }
    @Bean(name = "redissonRed4")
    public RedissonClient redissonRed4(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6382").setDatabase(0);
        return Redisson.create(config);
    }
    @Bean(name = "redissonRed5")
    public RedissonClient redissonRed5(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6383").setDatabase(0);
        return Redisson.create(config);
    }

}

5.dao :

package com..dao;

import com..entity.SaveInfo;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import java.util.List;

@Mapper
@Component(value = "SaveInfoMapper")
public interface SaveInfoMapper {

    int insert(SaveInfo record);

    List<SaveInfo> selectAll();

    SaveInfo selectInfoByid(String id);

    int updateByPrimaryid(SaveInfo record);

}

6.entity :

package com..entity;

import java.io.Serializable;
import java.util.Date;

/**
 *
 * This class was generated by MyBatis Generator.
 * This class corresponds to the database table save
 */
public class SaveInfo{

    private String id;

    private String applyno;

    private String userName;

    private String userNumber;

    private String address;

    private Date saveTime;

    private String type;

    private String count;

    private byte[] saveBolb;

    private String saveClob;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id == null ? null : id.trim();
    }

    public String getApplyno() {
        return applyno;
    }

    public void setApplyno(String applyno) {
        this.applyno = applyno == null ? null : applyno.trim();
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName == null ? null : userName.trim();
    }

    public String getUserNumber() {
        return userNumber;
    }

    public void setUserNumber(String userNumber) {
        this.userNumber = userNumber == null ? null : userNumber.trim();
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address == null ? null : address.trim();
    }

    public Date getSaveTime() {
        return saveTime;
    }

    public void setSaveTime(Date saveTime) {
        this.saveTime = saveTime;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type == null ? null : type.trim();
    }

    public String getCount() {
        return count;
    }

    public void setCount(String count) {
        this.count = count == null ? null : count.trim();
    }

    public byte[] getSaveBolb() {
        return saveBolb;
    }

    public void setSaveBolb(byte[] saveBolb) {
        this.saveBolb = saveBolb;
    }

    public String getSaveClob() {
        return saveClob;
    }

    public void setSaveClob(String saveClob) {
        this.saveClob = saveClob == null ? null : saveClob.trim();
    }
}

7.Mapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com..dao.SaveInfoMapper">

  <resultMap id="BaseResultMap" type="com..entity.SaveInfo">
    <result column="ID" jdbcType="VARCHAR" property="id" />
    <result column="APPLYNO" jdbcType="VARCHAR" property="applyno" />
    <result column="USER_NAME" jdbcType="VARCHAR" property="userName" />
    <result column="USER_NUMBER" jdbcType="VARCHAR" property="userNumber" />
    <result column="ADDRESS" jdbcType="VARCHAR" property="address" />
    <result column="SAVE_TIME" jdbcType="TIMESTAMP" property="saveTime" />
    <result column="TYPE" jdbcType="VARCHAR" property="type" />
    <result column="COUNT" jdbcType="VARCHAR" property="count" />
    <result column="SAVE_BOLB" jdbcType="LONGVARBINARY" property="saveBolb" />
    <result column="SAVE_CLOB" jdbcType="LONGVARCHAR" property="saveClob" />
  </resultMap>

  <insert id="insert" parameterType="com..entity.SaveInfo">
    insert into save (ID, APPLYNO, USER_NAME, 
      USER_NUMBER, ADDRESS, SAVE_TIME, 
      TYPE, COUNT, SAVE_BOLB, 
      SAVE_CLOB)
    values (#{id,jdbcType=VARCHAR}, #{applyno,jdbcType=VARCHAR}, #{userName,jdbcType=VARCHAR}, 
      #{userNumber,jdbcType=VARCHAR}, #{address,jdbcType=VARCHAR}, #{saveTime,jdbcType=TIMESTAMP}, 
      #{type,jdbcType=VARCHAR}, #{count,jdbcType=VARCHAR}, #{saveBolb,jdbcType=LONGVARBINARY}, 
      #{saveClob,jdbcType=LONGVARCHAR})
  </insert>

  <select id="selectAll" resultMap="BaseResultMap">
    select ID, APPLYNO, USER_NAME, USER_NUMBER, ADDRESS, SAVE_TIME, TYPE, COUNT, SAVE_BOLB, 
    SAVE_CLOB
    from save
  </select>

  <select id="selectInfoByid" parameterType="java.lang.String" resultMap="BaseResultMap">
    select ID, APPLYNO, USER_NAME, USER_NUMBER, ADDRESS, SAVE_TIME, TYPE, COUNT, SAVE_BOLB,
    SAVE_CLOB
    from save
    where id = #{id,jdbcType=VARCHAR}
  </select>

  <update id="updateByPrimaryid" parameterType="com..entity.SaveInfo">
        update save
        set type = #{type,jdbcType=VARCHAR}
        where id = #{id,jdbcType=INTEGER}
    </update>
</mapper>

application.properties 配置:

#服务端口--模仿集群 (6011,7011,8011)
server.port: 6011

#redis配置
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.timeout=2000
spring.redis.password=

#数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/ac-new?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.druid.datasource.max-idle=10
spring.druid.datasource.max-wait=10000
spring.druid.datasource.minIdle=5
spring.druid.datasource.initial-size=5

#mybatis配置
mybatis.mapper-locations:classpath:mapper/*.xml

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wond</groupId>
    <artifactId>redlock</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>redlock</name>
    <description>redLock</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <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>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.3.2</version>
        </dependency>

        <!-- mysql:MyBatis相关依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!-- 整合MyBatis java类依赖 -->
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.4.0</version>
            <type>maven-plugin</type>
        </dependency>
        <!-- mysql:mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- mysql:阿里巴巴数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Jmeter 测试:

后台打印信息:

 

 

最后,两个坑:

1.如果第一个业务线程过来加锁,加了3台之后redis突然挂掉一台,怎么解决?

  方案:延时启动挂掉的redis.

2.一个业务线程加锁成功,突然出现 Stop-The-World(GC导致),锁超时之后系统(集群)其他服务的业务线程来 加锁成功,第一个线程又恢复了 怎么办?

方案:(目前还没好的解决方案) 建议换zookeeper.

原文地址:https://www.cnblogs.com/lifan12589/p/14270945.html