(四)库存超卖案例实战——优化redis分布式锁

这篇具有很好参考价值的文章主要介绍了(四)库存超卖案例实战——优化redis分布式锁。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

在上一节内容中,我们已经实现了使用redis分布式锁解决商品“超卖”的问题,本节内容是对redis分布式锁的优化。在上一节的redis分布式锁中,我们的锁有俩个可以优化的问题。第一,锁需要实现可重入,同一个线程不用重复去获取锁;第二,锁没有续期功能,导致业务没有执行完成就已经释放了锁,存在一定的并发访问问题。本案例中通过使用redis的hash数据结构实现可重入锁,使用Timer实现锁的续期功能,完成redis分布式锁的优化。最后,我们通过集成第三方redisson工具包,完成分布式锁以上俩点的优化内容。Redisson提供了简单易用的API,使得开发人员可以轻松地在分布式环境中使用Redis。

正文

  • 加锁的lua脚本:使用exists和hexists指令判断是否存在锁,如果不存在或者存在锁并且该锁下面的field有值,就使用hincrby指令使锁的值加1,实现可重入,否则直接返回0,加锁失败。
if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
"then " +
"   redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
"   redis.call('expire', KEYS[1], ARGV[2]) " +
"   return 1 " +
"else " +
"   return 0 " +
"end"
  • 解锁的lua脚本: 使用hexists指令判断是否存在锁,如果为0,代表没有对应field字段的锁,直接返回nil;如果使用hincrby指令使锁field字段锁的值减少1之后值为0,代表锁已经不在占用,可以删除该锁;否则直接返回0,代表是可重入锁,锁还没有释放。
if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " +
"then " +
"   return nil " +
"elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 " +
"then " +
"   return redis.call('del', KEYS[1]) " +
"else " +
"   return 0 " +
"end"
  •  实现续期的lua脚本:使用hexists指令判断锁的field值是否存在,如果值为1存在,则将该锁的过期时间更新,否则直接返回0,代表没有找到该锁,续期失败。
if redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
"then " +
"   return redis.call('expire', KEYS[1], ARGV[2]) " +
"else " +
"   return 0 " +
"end";
  • 创建一个自定义的锁工具类MyRedisDistributeLock,实现加锁、解锁、续期功能

- MyRedisDistributeLock实现

package com.ht.atp.plat.util;

import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;


public class MyRedisDistributeLock implements Lock {

    public MyRedisDistributeLock(StringRedisTemplate redisTemplate, String lockName, long expire) {
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.expire = expire;
        this.uuid = getId();
    }

    /**
     * redis工具类
     */
    private StringRedisTemplate redisTemplate;


    /**
     * 锁名称
     */
    private String lockName;

    /**
     * 过期时间
     */
    private Long expire;

    /**
     * 锁的值
     */
    private String uuid;

    @Override
    public void lock() {
        this.tryLock();
    }

    @Override
    public void lockInterruptibly() {

    }

    @Override
    public boolean tryLock() {
        try {
            return this.tryLock(-1L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException {
        if (time != -1) {
            this.expire = unit.toSeconds(time);
        }
        String script = "if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                "   redis.call('expire', KEYS[1], ARGV[2]) " +
                "   return 1 " +
                "else " +
                "   return 0 " +
                "end";
        while (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire))) {
            Thread.sleep(50);
        }
//        //加锁成功后,自动续期
        this.renewExpire();
        return true;
    }

    @Override
    public void unlock() {
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " +
                "then " +
                "   return nil " +
                "elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 " +
                "then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";
        Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid);
        if (flag == null) {
            throw new IllegalMonitorStateException("this lock doesn't belong to you!");
        }
    }

    @NotNull
    @Override
    public Condition newCondition() {
        return null;
    }

    /**
     * 给线程拼接唯一标识
     *
     * @return
     */
    private String getId() {
        return UUID.randomUUID() + "-" + Thread.currentThread().getId();
    }


    private void renewExpire() {
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   return redis.call('expire', KEYS[1], ARGV[2]) " +
                "else " +
                "   return 0 " +
                "end";
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("-------------------");
                Boolean flag = redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, String.valueOf(expire));
                if (flag) {
                    renewExpire();
                }
            }
        }, this.expire * 1000 / 3);
    }
}

- 实现加锁功能

(四)库存超卖案例实战——优化redis分布式锁,ATP应用测试平台,# springboot,# 分布式锁,spring boot

- 实现解锁功能

(四)库存超卖案例实战——优化redis分布式锁,ATP应用测试平台,# springboot,# 分布式锁,spring boot
 - 使用Timer实现锁的续期功能

(四)库存超卖案例实战——优化redis分布式锁,ATP应用测试平台,# springboot,# 分布式锁,spring boot

  • 使用MyRedisDistributeLock实现库存的加锁业务 

- 使用自定义MyRedisDistributeLock工具类实现加锁业务

public void checkAndReduceStock() {
        //1.获取锁
        MyRedisDistributeLock myRedisDistributeLock = new MyRedisDistributeLock(stringRedisTemplate, "stock", 10);
        myRedisDistributeLock.lock();

        try {
            // 2. 查询库存数量
            String stockQuantity = stringRedisTemplate.opsForValue().get("P0001");
            // 3. 判断库存是否充足
            if (stockQuantity != null && stockQuantity.length() != 0) {
                Integer quantity = Integer.valueOf(stockQuantity);
                if (quantity > 0) {
                    // 4.扣减库存
                    stringRedisTemplate.opsForValue().set("P0001", String.valueOf(--quantity));
                }
            } else {
                System.out.println("该库存不存在!");
            }
        } finally {
            myRedisDistributeLock.unlock();
        }
    }

- 启动服务7000、7001、7002,压测优化后的自定义分布式锁:平均访问时间362ms,吞吐量每秒246,库存扣减为0,表明优化后的分布式锁是可用的。

(四)库存超卖案例实战——优化redis分布式锁,ATP应用测试平台,# springboot,# 分布式锁,spring boot

(四)库存超卖案例实战——优化redis分布式锁,ATP应用测试平台,# springboot,# 分布式锁,spring boot

  • 集成redisson工具包,使用第三方工具包实现分布式锁,完成并发访问“超卖”问题案例演示
<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson-spring-boot-starter</artifactId>
	<version>3.11.6</version>
</dependency>
  • 创建一个redisson配置类,引入redisson客户端工具
package com.ht.atp.plat.config;

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.Configuration;


@Configuration
public class MyRedissonConfig {

    @Bean
    RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://192.168.110.88:6379");
        //配置看门狗的默认超时时间为30s,供续期使用
        config.setLockWatchdogTimeout(30000);
        return Redisson.create(config);
    }
}
  • 使用Redisson锁实现“超卖”业务方法 
//可重入锁
    @Override
    public void checkAndReduceStock() {
        // 1.加锁,获取锁失败重试
        RLock lock = this.redissonClient.getLock("lock");
        lock.lock();

        try {
            // 2. 查询库存数量
            String stockQuantity = stringRedisTemplate.opsForValue().get("P0001");
            // 3. 判断库存是否充足
            if (stockQuantity != null && stockQuantity.length() != 0) {
                Integer quantity = Integer.valueOf(stockQuantity);
                if (quantity > 0) {
                    // 4.扣减库存
                    stringRedisTemplate.opsForValue().set("P0001", String.valueOf(--quantity));
                }
            } else {
                System.out.println("该库存不存在!");
            }
        } finally {
            // 4.释放锁
            lock.unlock();
        }
    }
  • 开启7000、7001、7002服务,压测扣减库存接口 

- 压测结果:平均访问时间222ms,吞吐量为384每秒

(四)库存超卖案例实战——优化redis分布式锁,ATP应用测试平台,# springboot,# 分布式锁,spring boot

- 库存扣减结果为0

(四)库存超卖案例实战——优化redis分布式锁,ATP应用测试平台,# springboot,# 分布式锁,spring boot

结语

综上所述,无论是自定义分布式锁还是使用redisson工具类,都能实现分布式锁解决并发访问的“超卖问题”,redisson工具使用集成更加方便简洁,推荐使用redisson工具包。本节内容到这里就结束了,我们下期见。。。。。。文章来源地址https://www.toymoban.com/news/detail-728611.html

到了这里,关于(四)库存超卖案例实战——优化redis分布式锁的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis实战之-分布式锁

    分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行,这就是分布式锁的核心思路 那么分布式锁他应该满足一些什么样的

    2024年01月19日
    浏览(40)
  • Redis实战——Redisson分布式锁

    目录 1 基于Redis中setnx方法的分布式锁的问题 2 Redisson         2.1 什么是Redisson         2.2 Redisson实现分布式锁快速入门         2.3 Redisson 可重入锁原理                 什么是可重入锁?                 Redisson中又是如何实现的呢?         2

    2024年02月15日
    浏览(49)
  • 4、Redis高并发分布式锁实战

    在分布式系统中,保证数据的一致性和避免竞争条件是至关重要的。分布式锁是一种常用的机制,而Redis作为一款高性能的内存数据库,提供了简单而强大的分布式锁方案。本文将深入探讨如何利用Redis高并发分布式锁来解决分布式系统中的并发控制问题,并提供实战案例。

    2024年01月18日
    浏览(56)
  • redis实战-redis实现分布式锁&redisson快速入门

    前言 集群环境下的并发问题  分布式锁 定义 需要满足的条件 常见的分布式锁 redis实现分布式锁 核心思路 代码实现 误删情况 逻辑说明 解决方案 代码实现 更为极端的误删情况 Lua脚本解决原子性问题 分布式锁-redission redisson的概念 快速入门 总结 在前面我们已经实现了单机

    2024年02月09日
    浏览(52)
  • Redis学习(三)分布式缓存、多级缓存、Redis实战经验、Redis底层原理

    单节点Redis存在着: 数据丢失问题:单节点宕机,数据就丢失了。 并发能力和存储能力问题:单节点能够满足的并发量、能够存储的数据量有限。 故障恢复问题:如果Redis宕机,服务不可用,需要一种自动的故障恢复手段。 RDB持久化 RDB(Redis database backup file,Redis数据库备份

    2024年02月16日
    浏览(41)
  • Redis7实战加面试题-高阶篇(手写Redis分布式锁)

    面试题: 1.Redis除了拿来做缓存,你还见过基于Redis的什么用法? 数据共享,分布式session分布式锁 全局ID 计算器、点赞位统计 购物车 轻量级消息队列(list,stream) 抽奖 点赞、签到、打卡 差集交集并集,用户关注、可能认识的人,推荐模型 热点新闻、热搜排行榜 2.Redis做分

    2024年02月07日
    浏览(37)
  • Redis学习(三)持久化机制、分布式缓存、多级缓存、Redis实战经验

    单节点Redis存在着: 数据丢失问题:单节点宕机,数据就丢失了。 并发能力和存储能力问题:单节点能够满足的并发量、能够存储的数据量有限。 故障恢复问题:如果Redis宕机,服务不可用,需要一种自动的故障恢复手段。 RDB持久化 RDB(Redis database backup file,Redis数据库备份

    2024年02月16日
    浏览(55)
  • 中间件系列 - Redis入门到实战(高级篇-分布式缓存)

    学习视频: 黑马程序员Redis入门到实战教程,深度透析redis底层原理+redis分布式锁+企业解决方案+黑马点评实战项目 中间件系列 - Redis入门到实战 本内容仅用于个人学习笔记,如有侵扰,联系删除 学习目标 Redis持久化 Redis主从 Redis哨兵 Redis分片集群 - 基于Redis集群解决单机R

    2024年02月03日
    浏览(53)
  • elasticsearch(ES)分布式搜索引擎03——(RestClient查询文档,ES旅游案例实战)

    文档的查询同样适用昨天学习的 RestHighLevelClient对象,基本步骤包括: 1)准备Request对象 2)准备请求参数 3)发起请求 4)解析响应 我们以match_all查询为例 3.1.1.发起查询请求 代码解读: 第一步,创建 SearchRequest 对象,指定索引库名 第二步,利用 request.source() 构建DSL,DSL中可

    2024年02月07日
    浏览(53)
  • 1+X 云计算运维与开发(中级)案例实战——Zabbix分布式监控系统

    学而不思则罔,思而不学则殆。 IP 主机名 节点 192.168.200.10/24 zabbix-server Server节点 192.168.200.20/24 zabbix-agent Agent节点 这个实验非常简单,就是最基本的配置yum源,安装,修改配置文件,启动,然后在图形化界面操作。 1.配置yum源 2.安装服务 [ server节点 : lamp+zabbix-server] [ agent节点

    2024年02月04日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包