springBoot中使用redis实现分布式锁实例demo

首先

RedisLockUtils工具类
package com.example.demo.utils;

import org.junit.platform.commons.util.StringUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


@Component
public class RedisLockUtils {
    @Resource
    private RedisTemplate redisTemplate;
    private static Map<String, LockInfo> lockInfoMap = new ConcurrentHashMap<>();
    private static final Long SUCCESS = 1L;
    public static class LockInfo {
        private String key;
        private String value;
        private int expireTime;
        //更新时间
        private long renewalTime;
        //更新间隔
        private long renewalInterval;
        public static LockInfo getLockInfo(String key, String value, int expireTime) {
            LockInfo lockInfo = new LockInfo();
            lockInfo.setKey(key);
            lockInfo.setValue(value);
            lockInfo.setExpireTime(expireTime);
            lockInfo.setRenewalTime(System.currentTimeMillis());
            lockInfo.setRenewalInterval(expireTime * 2000 / 3);
            return lockInfo;
        }

        public String getKey() {
            return key;
        }
        public void setKey(String key) {
            this.key = key;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
        public int getExpireTime() {
            return expireTime;
        }
        public void setExpireTime(int expireTime) {
            this.expireTime = expireTime;
        }
        public long getRenewalTime() {
            return renewalTime;
        }
        public void setRenewalTime(long renewalTime) {
            this.renewalTime = renewalTime;
        }
        public long getRenewalInterval() {
            return renewalInterval;
        }
        public void setRenewalInterval(long renewalInterval) {
            this.renewalInterval = renewalInterval;
        }
    }

    /**
     * 使用lua脚本更新redis锁的过期时间
     * @param lockKey
     * @param value
     * @return 成功返回true, 失败返回false
     */
    public boolean renewal(String lockKey, String value, int expireTime) {
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Boolean.class);
        redisScript.setScriptText(luaScript);
        List<String> keys = new ArrayList<>();
        keys.add(lockKey);

        Object result = redisTemplate.execute(redisScript, keys, value, expireTime);
        System.out.println("更新redis锁的过期时间:{}"+result);
        return (boolean) result;
    }

    /**
     * @param lockKey    锁
     * @param value      身份标识(保证锁不会被其他人释放)
     * @param expireTime 锁的过期时间(单位:秒)
     * @return 成功返回true, 失败返回false
     */
    public boolean lock(String lockKey, String value, long expireTime) {
        return redisTemplate.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);
    }

    /**
     * redisTemplate解锁
     * @param key
     * @param value
     * @return 成功返回true, 失败返回false
     */
    public boolean unlock2(String key, String value) {
        Object currentValue = redisTemplate.opsForValue().get(key);
        boolean result = false;
        if (StringUtils.isNotBlank(String.valueOf(currentValue)) && currentValue.equals(value)) {
            result = redisTemplate.opsForValue().getOperations().delete(key);
        }
        return result;
    }

    /**
     * 定时去检查redis锁的过期时间
     */
    @Scheduled(fixedRate = 5000L)
    @Async("redisExecutor")
    public void renewal() {
        long now = System.currentTimeMillis();
        for (Map.Entry<String, LockInfo> lockInfoEntry : lockInfoMap.entrySet()) {
            LockInfo lockInfo = lockInfoEntry.getValue();
            if (lockInfo.getRenewalTime() + lockInfo.getRenewalInterval() < now) {
                renewal(lockInfo.getKey(), lockInfo.getValue(), lockInfo.getExpireTime());
                lockInfo.setRenewalTime(now);

            }
        }
    }

    /**
     * 分布式锁设置单独线程池
     * @return
     */
    @Bean("redisExecutor")
    public Executor redisExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(1);
        executor.setMaxPoolSize(1);
        executor.setQueueCapacity(1);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("redis-renewal-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        return executor;
    }
}

完整的

Controller
package com.example.demo.controller;

import com.example.demo.utils.RedisLockUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    private String goodNumKey = "num";
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisLockUtils redisLock;

    /**
     * 设置商品库存
     * @param num 库存数量
     * @return
     */
    @GetMapping("/set-num/{num}")
    public int setNum(@PathVariable int num) {

        redisTemplate.opsForValue().set(goodNumKey, num);
        return num;
    }

    /**
     * 获取商品库存
     * @return
     */
    @GetMapping("/get-num")
    public int getNum() {
        Object objNum = redisTemplate.opsForValue().get(goodNumKey);
        int num = Integer.parseInt((String) objNum);
        return num;
    }

    /**
     * 用户带着id来秒杀商品
     * @param id 用户id
     * @return
     */
    @GetMapping("/user/{id}")
    public String getUser(@PathVariable String id) {

        String key = "user:" + id;

        String productId = "product001";
        String requestId = productId + Thread.currentThread().getId();
        boolean locked = redisLock.lock(productId, requestId, 10);

        //如果存在直接返回结果
        if (redisTemplate.hasKey(key)) {
            return (String) redisTemplate.opsForValue().get(key);
        }
        //如果有锁重试
        if (!locked) {
            return "error";
        }
        try {
            //查询库存
            Object objNum = redisTemplate.opsForValue().get(goodNumKey);
            int num = Integer.parseInt((String) objNum);
            if (num > 0) {
                num--;
                //保存库存
                redisTemplate.opsForValue().set(goodNumKey, num);
                //添加抢购成功的信息
                redisTemplate.opsForValue().set(key, 1);
                System.out.println(key + "成功");
                return (String) redisTemplate.opsForValue().get(key);
            } else {
                //添加抢购失败的信息
                System.out.println(key + "失败");
               // redisTemplate.opsForValue().set(key, 0);
                return "0";
            }

        } finally {
            redisLock.unlock2(productId, requestId);
        }
    }

    // 其他接口方法...
}

完整pom.xml

<?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.7.11</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-boot-redis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-redis</name>
    <description>spring-boot-redis</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <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.junit.platform</groupId>
            <artifactId>junit-platform-commons</artifactId>
        </dependency>

    </dependencies>

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

</project>

以上是完整服务端   git地址   spting-boot-redis: springBoot中使用redis实现分布式锁实例demo

下面来写一个程序,多线程异步去模拟大量同时的商品抢购请求  看一下抢购成功的用户数量和库存情况

package maomi.com;

import maomi.com.tools.RedisDistributedLock;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

import java.io.IOException;
import java.util.UUID;


public class ConcurrentHttpRequestTest implements Runnable {
    /**
     * 线程id
     */
    public int i;
    /**
     * 线程名称
     */
    public String name;

    public ConcurrentHttpRequestTest(int i) {
        this.i = i;
        this.name = String.format("线程[%s]", i);
    }

    public void run() {
        // 执行线程操作
        dosom();
    }

    public static void main(String[] args) {
        //开启500个线程去抢购这个商品
        for (int i = 1; i < 500; i++) {
            new Thread(new ConcurrentHttpRequestTest(i)).start();
        }
    }

    /**
     * 一个线程模拟30次抢购 带着随机用户id
     * @return
     */
    public boolean dosom() {
        for (int j = 0; j <30 ; j++) {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            String url = "http://127.0.0.1:8080/user/" + UUID.randomUUID();
            System.out.println(name + ":" + url);
            HttpGet httpGet = new HttpGet(url);
            try {
                CloseableHttpResponse response = httpClient.execute(httpGet);
                System.out.println(name+"-Request: " + response.getEntity().toString());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return false;
    }
}

下面我们来测试

1.首先设置200个库存 

 

 2.然后我们来模拟抢购

我们来看打印 一共服务端收到了2232个请求

 成功数量只有200个

 看下redis成功写入用户和库存  成功写入用户id为200  库存为0

下面我们去掉分布式锁 

来同样设置200库存模拟一下 发现库存为0 但是抢购成功的有6000多个用户

大家猜一下为什么会这样

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值