redis分布式锁实现案例之redisson和StringRedisTemplate

论坛 期权论坛 脚本     
匿名技术用户   2021-1-9 05:40   324   0

redis实现分布式锁,单机情况下加synchronize关键字就ok了~,但是分布式情况下就会出现问题,一个简单的扣减库存问题来做分布式锁的demo~~

1、先添加pom依赖,我这里就将redis和redisson的依赖一起引入了

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

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

2、构建yml配置文件,添加redis配置,这只是为了redis配置的,redisson框架不需要,后面需要用到改端口号,起两个服务来做分布式环境下测试~

server:
  port: 8081

spring:
  # Redis 配置
  redis:
    database: 0  #数据库索引(默认为0)
    host: 127.0.0.1
    port: 6379  #默认链接端口
    password:  #默认为空
    jedis:
      pool:
        max-active: 8 #最大链接池
        max-wait: -1 #最大阻赛等待时间(使用负值没有限制)默认为-1
        max-idle: 8 #连接池中的最大空闲连接 默认 8
        min-idle: 0

3、构建主启动类

package com.king;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * created by king on 2020/6/8 11:45 上午
 */
@SpringBootApplication
public class LockRedisMain6633 {
    public static void main(String[] args) {
            SpringApplication.run(LockRedisMain6633.class,args);
        }
}

4、下面先放StringRedisTemplate的案例代码

package com.king.controller;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * created by king on 2020/6/8 8:01 下午
 */
@RestController
@Slf4j
public class RedisLockController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 1、加setIfAbsent是为了setnx来判断能不能获取锁
     * 2、加expire时间是为了防止宕机等情况造成死锁
     * 3、加value的equals判断是为了防止误删锁
     * 4、但是要是完全很好的实现分布式锁,还需要开启守护线程去动态续航expire时间,防止过期,下面引入了redisson框架,
     *    框架包含一个watchdog看门狗,可以去开启一个守护线程去动态续航
     * @return
     */
    @GetMapping("decreseStorage1/")
    public String decreaseStorage1() {
        String lockkey = "sku001";
        String value = Thread.currentThread().getName();
//        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, value);
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, value, 10, TimeUnit.SECONDS);
//        log.info("result:"+result);

        if (!result) {
            return "error";
        }
        try {
            int storage = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (storage > 0) {
                int still = storage - 1;
                stringRedisTemplate.opsForValue().set("stock", still + "");
                System.out.println("扣减库存成功,目前库存剩余量为:" + still);
            } else {
                System.out.println("扣减库存失败,库存不足");
            }
        } finally {
            if (value.equals(stringRedisTemplate.opsForValue().get(lockkey))) {
                stringRedisTemplate.delete(lockkey);
            }
        }
        return "end";
    }


}

5、如果使用redisson框架来实现的话:

注意:要防止watchdog未生效问题

  • redisson的自动续期,如果业务超长,运行期间自动续上30s,不用担心业务时间长,锁自动过期被删掉
  • 加锁得业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除
  • //lock.lock(10, TimeUnit.SECONDS);//10秒以后自动解锁,自动解锁时间一定要大于业务时间
  • // lock.lock(10, TimeUnit.SECONDS)在锁时间到了以后,不会自动续期
  • //1.如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认时间就是我我们传递的时间
  • //2.如果我们未制定锁的超时时间,就使用lockWatchdogTimeout = 30 * 1000;看门狗默认时间
  • //只要占锁成功,就会启动定时任务(重新给锁设置过期时间)新的时间就是看门狗的默认时间,每10秒,都会自动续期续成满时间
package com.king.controller;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * created by king on 2020/6/8 8:01 下午
 */
@RestController
@Slf4j
public class RedisLockController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private Redisson redisson;

    /**
     * 1、redisson框架实现分布式锁,自带watchdog看门狗
     *
     * @return
     */
    @GetMapping("decreseStorage/")
    public String decreaseStorage() {
        String lockkey = "sku001";
        //获取一把锁,只要锁的名字一样就是同一把锁
        RLock lock = redisson.getLock(lockkey);
 
       

        //2。加锁
        // lock.lock();
        //1).redisson的自动续期,如果业务超长,运行期间自动续上30s,不用担心业务时间长,锁自动过期被删掉
        //2).加锁得业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除
        //lock.lock(10, TimeUnit.SECONDS);//10秒以后自动解锁,自动解锁时间一定要大于业务时间
        // lock.lock(10, TimeUnit.SECONDS)在锁时间到了以后,不会自动续期
        //1.如果我们传递了锁的超时时间,就发送给redis执行脚本,进行站锁,默认时间就是我我们传递的时间
        //2.如果我们危制定锁的超时时间,就使用lockWatchdogTimeout = 30 * 1000;看门狗默认时间
        //只要站锁成功,就会启动定时任务(重新给锁设置过期时间)新的时间就是看门狗的默认时间,每10秒,都会自动续期续成满时间


        lock.lock(30,TimeUnit.SECONDS);
        try{
            int storage = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (storage > 0) {
                int still = storage - 1;
                stringRedisTemplate.opsForValue().set("stock", still + "");
                System.out.println("扣减库存成功,目前库存剩余量为:" + still);
            } else {
                System.out.println("扣减库存失败,库存不足");
            }
        } finally {
            lock.unlock();
        }
        return "end";
    }

6、进行测试,先准备测试环境,

①、启动8081和8082两个端口,即修改端口启动微服务

②、配置host ip redislock.com ,并配置nginx的配置文件设置反向代理微服务8081和8082,并实现负载均衡

upstream redis {   
         server 192.168.0.153:8081 weight=1 ;   
         server 192.168.0.153:8082 weight=1 ;
    }


    
     server{
        listen 80;
        charset utf-8;
        server_name redislock.com;

        location  ~* \.(php|jsp|cgi|asp|aspx)$
            {
                proxy_pass http://redis;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header REMOTE-HOST $remote_addr;
            }

        location /
            {
                proxy_pass http://redis;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header REMOTE-HOST $remote_addr;

                add_header X-Cache $upstream_cache_status;
                
                #Set Nginx Cache
                
                    add_header Cache-Control no-cache;
            }

     }

③、启动jmeter压测软件,进行配置请求来测试会不会超卖等情况

④、查看控制台打印结果,发现两种情况都能控制住扣减库存的问题,而且也在扣减完库存后将锁删掉了~

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP