Redis分布式锁

一、搭建redis环境

1.1 安装redis

su root #密码root
wget http://download.redis.io/releases/redis-6.0.8.tar.gz
tar xzf redis-6.0.8.tar.gz
cd redis-6.0.8
make

1.2 关闭centos7防火墙

1.2.1 查看Linux防火墙状态命令

running是防火墙正在运行

systemctl status firewalld.service

1.2.2 关闭访问强命令

systemctl stop firewalld.service

1.3 更改redis配置文件

首次安装,需要改一下redis的配置文件

进入redis安装目录,找到redis.conf,做以下修改

# bind 127.0.0.1  #防止远程连接不上
daemonize  yes  #后台运行
protected-mode no #关闭保护模式

注意: 如果此刻应用服务还是会报错,要记得应用服务也要重启

1.4 启动redis

进入src目录,用以下命令启动

#开启服务端
./redis-server #普通启动
./redis-server ../redis.conf #根据配置文件启动
#开启客户端
./redis-cli

查看redis启动状态

ps -ef|grep redis

查看redis版本号

./redis-server -v

二、搭建Springboot环境

2.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>org.example</groupId>
    <artifactId>studySpace</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>redisson</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <!--spring boot 2.2.2-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--spring cloud Hoxton.SR1-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

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

</project>

2.2 写yaml

server:
  port: 1111
spring:
  application:
    name: redisson
  redis:
    database: 0
    host: 192.168.208.129
    port: 6379
    lettuce:
      pool:
        #连接池最大连接数(使用负值表示没有限制)默认8
        max-active: 8
        #连接池最大阻塞等待时间(使用负值表示没有限制)默认-1
        max-wait: -1
        #连接池中的最大空闲连接默认8
        max-idle: 8
        #连接池中的最小空闲连接默认0
        min-idle: 0

2.3 主启动

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class RedissonApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedissonApplication.class, args);
    }
}

2.4 业务类

@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String redisHost;

    /**
     * 保证不是序列化后的乱码配置
     */
    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<String, Serializable>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }

    @Bean
    public Redisson redisson() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + redisHost + ":6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GoodController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/buy_goods")
    public String buy_Goods(){

        String result = stringRedisTemplate.opsForValue().get("goods:001");
        int goodsNumber = result == null ? 0 : Integer.parseInt(result);

        if (goodsNumber > 0){
            int realNumber = goodsNumber - 1;
            stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");
            System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort);
            return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort;
        }else {
            System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort);
        }
        return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort;
    }
}

2.5 开启双实例

启动2号机,测试

 

2.6 测试

进入redis src目录启动redis客户端./redis-cli

设置值 set goods:001 100, get goods:001 

访问地址:http://localhost:1111/buy_goods

此时,单机版的使用redis减库存没有什么问题,但是多线程,高并发情况下,会出现什么问题,见后续

三、代码问题优化

3.1 单机版没加锁

3.1.1 单机版优化

上述代码没有加锁,单线程下不会存在什么问题,但是多线程并发情况下,就会出现超卖现象

此时,就要考虑加锁,那到底要加synchronized还是加ReentrantLock锁呢,这就要根据业务和两种锁的特性来分析了

synchronized:不见不散

reentrantLock:过时不候

1、ReentrantLock可以添加多个检控条件(condition),但是synchronized只可以添加一个;

2、ReentrantLock可以控制得到锁的顺序(公平锁),也可以和synchronized一样使用非公平锁;

3、ReentrantLock支持获取锁超时(tryLock()方法)以及获取锁响应中断的操作(lockInterruptibly()方法,synchronized不支持。

4、在高争用条件下,ReentrantLock的可伸缩性优于synchronized;

5.、ReentrantLock必须在finally块中手动释放锁;

@RestController
public class GoodController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/buy_goods")
    public String buy_Goods() {

        synchronized (this) {
            String result = stringRedisTemplate.opsForValue().get("goods:001");
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);

            if (goodsNumber > 0) {
                int realNumber = goodsNumber - 1;
                stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");
                System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort);
                return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;
            } else {
                System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临" + "\t 服务器端口: " + serverPort);
            }
            return "商品已经售罄/活动结束/调用超时,欢迎下次光临" + "\t 服务器端口: " + serverPort;
        }
    }
}

3.1.2 解释

在单机环境下,可以使用synchronized或lock来实现。

但是,在分布式系统中,因为竞争的线程可能不在同一个节点上(同一个JVM中),所以,需要一个让所有进程都能访问到的锁来实现,比如redis或者zookeeper来构建;

不同进程jvm层面的锁就不管用了,那么可以利用第三方的一个组件,来获取锁,未获取到锁,则阻塞当前想要运行的线程。

3.2 Nginx分布式微服务架构

3.2.1 存在问题

分布式部署后,单机锁还是出现超卖现象,需要分布式锁

通过nginx,进行转发,根据设置权重路由到不同的机器

上面问题,通过加锁可以解决单机版情况下,高并发访问数据问题。对于单机版,竞争的线程都是来自同一个JVM,但是,生产环境下,我们的服务大多是多个微服务,分布式部署。每一个服务,都会有多台机器,这样的话,这些线程就可能来自不同的JVM,这样用synchronized或是lock就解决不了上述问题。

3.2.2 配置nginx

1. 安装nginx

yum -y install gcc gcc-c++ make libtool zlib zlib-devel openssl openssl-devel pcre pcre-devel

2. 配置nginx.conf

3. 启动nginx

进入nginx sbin目录输入以下命令

./nginx #启动

./nginx -s reload #重启

./nginx -s stop #关闭

查看nginx启动情况

ps -ef|grep nginx

3.2.3 启动两个微服务

启动1111,2222两个服务

通过nginx所在ip+port来访问

http://192.168.208.129/buy_goods

1111控制台输出

2222控制台输出

点击访问可以看到效果,一边一个,默认轮询,从结果看,好像也没出什么问题,上面在一个一个减少,即使狂点好像也没出问题,但是,我们手速毕竟有限,接下来我们用jmeter来压测一下

3.2.4 Jmeter进行压测

假设,一瞬间有几百个线程砸过来,来测试单机情况下的锁,还有没有效果。或者说,在分布式情况下,我们加了个单机锁,还有没有效果

1.添加线程组

2.添加http请求

3. 恢复数据开始压测

高并发,情况下,此时就出现上面问题,1号机和2号机,都会卖出相同的商品,出现超卖的情况。

3.2.5 解决方法

1. 上redis分布式锁

Redis具有极高的性能,且其命令对分布式锁支持友好,借助SET命令即可实现加锁处理.

2. 官网

https://redis.io/commands/set

3. 升级代码

@RestController
public class GoodController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    //相当于门栓
    public static final String REDIS_LOCK_KEY = "best_lock";

    @GetMapping("/buy_goods")
    public String buy_Goods() {

        String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
        //setIfAbsent() 就是如果不存在就新建
        Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value);//setnx,多个人竞争一个房间,如果没有上锁,进门加锁,别人就进不来了
        if (!lockFlag) {
            return "抢锁失败";
        }
        String result = stringRedisTemplate.opsForValue().get("goods:001");
        int goodsNumber = result == null ? 0 : Integer.parseInt(result);
        if (goodsNumber > 0) {
            int realNumber = goodsNumber - 1;
            stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");
            System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort);
            stringRedisTemplate.delete(REDIS_LOCK_KEY);//释放锁,出门解锁,别人就可以进来了
            return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;
        } else {
            System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临" + "\t 服务器端口: " + serverPort);
        }
        return "商品已经售罄/活动结束/调用超时,欢迎下次光临" + "\t 服务器端口: " + serverPort;
    }
}

3.3 异常无法释放锁

3.3.1  无法释放锁风险

上面代码还有一个问题,就是释放锁在业务逻辑中,假如释放锁的上一步出现异常,可能就会无法释放锁,必须要在代码层面finally释放锁

3.3.2 升级代码

加锁解锁,lock/unlock必须同时出现并保证调用

@RestController
public class GoodController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    //相当于门栓
    public static final String REDIS_LOCK_KEY = "best_lock";

    @GetMapping("/buy_goods")
    public String buy_Goods() {

        String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
        try {
            //setIfAbsent() 就是如果不存在就新建
            Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value);//setnx,多个人竞争一个房间,如果没有上锁,进门加锁,别人就进不来了
            if (!lockFlag) {
                return "抢锁失败";
            }
            String result = stringRedisTemplate.opsForValue().get("goods:001");
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);
            if (goodsNumber > 0) {
                int realNumber = goodsNumber - 1;
                stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");
                System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort);
                return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;
            } else {
                System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临" + "\t 服务器端口: " + serverPort);
            }
            return "商品已经售罄/活动结束/调用超时,欢迎下次光临" + "\t 服务器端口: " + serverPort;
        } finally {
            stringRedisTemplate.delete(REDIS_LOCK_KEY); //释放锁
        }
    }
}

3.4 宕机无法释放锁

3.4.1 问题

上面我们把释放锁的操作,写入了finally中,即使代码出现异常也会释放锁,但是,假如我们部署了微服务jar包的机器挂了,代码层面 根本没有走到finally这块,没办法保证解锁,这个key没有被删除,这时就需要加入一个过期时间限定key

3.4.2 解决

需要对lockKey有过期时间的设定,这样加入服务宕机,过一定时间这个锁也会释放

1. 代码升级

3.5 原子性问题

3.5.1 问题

设置key+过期时间分开了,必须要合并成一行具备原子性

3.5.2 解决

3.6 删除了别人的锁

3.6.1 问题

进程A,尽量判断redis发现没有锁,然后,它设置一个key加上锁,并设置过期时间30s,然后,后面一顿操作,但是,进程A由于某些原因,比如网络延迟,导致30s后还没有执行完,就被redis过期掉了。此时,进程B进来发现没有锁,也开始加上锁执行,但是,此时的进程A并没有结束,当它执行完后,开始执行删除操作,自己key已经被redis过期掉了,它就会把进程的B的key删除掉,但是,当进程B执行完后,要删除key时,却发现自己的key不见了,此时就尴尬了,就会出现张冠李戴的现象。

3.6.2 解决

只能自己删除自己的,不许动别人的

3.7 finally块的判断和删除不是原子性的

3.7.1 问题

finally块的判断+del删除操作不是原子性的

3.7.2 解决

1. 用redis自身的事务

@RestController
public class GoodController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    //相当于门栓
    public static final String REDIS_LOCK_KEY = "best_lock";
    @GetMapping("/buy_goods")
    public String buy_Goods() {

        String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
        try {
            //setIfAbsent() 就是如果不存在就新建,同时加上过期时间保证原子性
            Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value, 10L, TimeUnit.SECONDS);//setnx,多个人竞争一个房间,如果没有上锁,进门加锁,别人就进不来了
            if (!lockFlag) {
                return "抢锁失败";
            }
            String result = stringRedisTemplate.opsForValue().get("goods:001");
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);
            if (goodsNumber > 0) {
                int realNumber = goodsNumber - 1;
                stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");
                System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort);
                return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;
            } else {
                System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临" + "\t 服务器端口: " + serverPort);
            }
            return "商品已经售罄/活动结束/调用超时,欢迎下次光临" + "\t 服务器端口: " + serverPort;
        } finally {
            while (true) {
                stringRedisTemplate.watch(REDIS_LOCK_KEY); //加事务,乐观锁
                if (stringRedisTemplate.opsForValue().get(REDIS_LOCK_KEY).equalsIgnoreCase(value)) {
                    stringRedisTemplate.setEnableDefaultSerializer(true);
                    stringRedisTemplate.multi();
                    stringRedisTemplate.delete(REDIS_LOCK_KEY);
                    List<Object> list = stringRedisTemplate.exec();
                    if (list == null) {
                        continue;
                    }
                }
                stringRedisTemplate.unwatch();
                break;
            }
        }
    }
}

2. lua脚本解决原子性

public class RedisUtils {
    private static JedisPool jedisPool;

    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(20);
        jedisPoolConfig.setMaxIdle(10);
        new JedisPool(jedisPoolConfig, "192.168.208.129", 6379);
    }

    public static Jedis getJedis() throws Exception {
        if (jedisPool != null) {
            return jedisPool.getResource();
        } else {
            throw new Exception("JedisPool is not ok");
        }
    }
}
@RestController
public class GoodController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    //相当于门栓
    public static final String REDIS_LOCK_KEY = "best_lock";
    @GetMapping("/buy_goods")
    public String buy_Goods() throws Exception {

        String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
        try {
            //setIfAbsent() 就是如果不存在就新建,同时加上过期时间保证原子性
            Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value, 10L, TimeUnit.SECONDS);//setnx,多个人竞争一个房间,如果没有上锁,进门加锁,别人就进不来了
            if (!lockFlag) {
                return "抢锁失败";
            }
            String result = stringRedisTemplate.opsForValue().get("goods:001");
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);
            if (goodsNumber > 0) {
                int realNumber = goodsNumber - 1;
                stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");
                System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort);
                return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;
            } else {
                System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临" + "\t 服务器端口: " + serverPort);
            }
            return "商品已经售罄/活动结束/调用超时,欢迎下次光临" + "\t 服务器端口: " + serverPort;
        } finally {
            Jedis jedis = RedisUtils.getJedis();
            //lua脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1]"+"then "
                    +"return redis.call('del', KEYS[1])"+"else "+ "  return 0 " + "end";
            try {
                Object result = jedis.eval(script, Collections.singletonList(REDIS_LOCK_KEY), Collections.singletonList(value));
                if ("1".equals(result.toString())) {
                    System.out.println("-------del REDIS_LOCK_KEY success");
                } else {
                    System.out.println("-------del REDIS_LOCK_KEY error");
                }
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }
    }
}

 3.8 redis集群环境下问题

3.8.1 问题

确保redisLock过期时间大于业务执行时间的问题

Redis分布式锁如何续期?

3.8.2 集群+CAP对比zookeeper

1. Redis

redis是AP模式,redis异步复制可能会造成锁丢失,比如,主节点没来得及把刚刚set进来的这条数据给从节点,就挂了。此时,如果集群模式下,就得上Redisson来解决

2. Zookeeper

zookeeper是CP模式,主节点会先把数据同步到从节点,不会存在锁丢失问题

3.8.3 解决

redis集群环境下,我们自己写的也不ok,直接上RedLock之Redisson落地实现

@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String redisHost;

    /**
     * 保证不是序列化后的乱码配置
     */
    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<String, Serializable>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }

    @Bean
    public Redisson redisson() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + redisHost + ":6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }
}

 

@RestController
public class GoodController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${server.port}")
    private String serverPort;

    @Autowired
    private Redisson redisson;

    //相当于门栓
    public static final String REDIS_LOCK_KEY = "best_lock";
    @GetMapping("/buy_goods")
    public String buy_Goods() throws Exception {

        String value = UUID.randomUUID().toString() + Thread.currentThread().getName();

        RLock redissonLock = redisson.getLock(REDIS_LOCK_KEY);
        redissonLock.lock();
        try {
            String result = stringRedisTemplate.opsForValue().get("goods:001");
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);
            if (goodsNumber > 0) {
                int realNumber = goodsNumber - 1;
                stringRedisTemplate.opsForValue().set("goods:001", realNumber + "");
                System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort);
                return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件" + "\t 服务器端口: " + serverPort;
            } else {
                System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临" + "\t 服务器端口: " + serverPort);
            }
            return "商品已经售罄/活动结束/调用超时,欢迎下次光临" + "\t 服务器端口: " + serverPort;
        } finally {
            redissonLock.unlock();
        }
    }
}

再次,压测,已经解决超卖问题

但是,当并发更高的时候,可能会出现以下错误

 出现这个错误的原因是,在并发多的时候就可能会遇到这种错误,可能会被重新抢占

不见得当前这个锁的状态还是在锁定,并且本线程持有

再次优化代码

四、总结

1. synchronized 单机版ok,上分布式

2. nginx分布式微服务,单机锁不行

3. 取消单机锁,上redis分布式锁setnx

4. 只加了锁,没有释放锁,出异常的话,可能无法释放锁,必须要在代码层面finally释放锁

5. 宕机了,部署了微服务代码层面根本没有走到finally这块,没办法保证解锁,这个key没有被删除,需要有lockKey的过期时间设定

6. 为redis的分布式锁key,增加过期时间,此外,还必须要setnx+过期时间必须同一行的原子性操作

7. 必须规定只能自己删除自己的锁,你不能把别人的锁删除了,防止张冠李戴,1删2,2删3

8. lua或者事务,解决原子性问题

9. redis集群环境下,可能会导致锁丢失,我们自己写的也不ok,直接上RedLock之Redisson落地实现

视频教程源码链接

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值