redis秒杀系统数据同步(保证不多卖)

2023-05-16

原文链接 http://www.cnblogs.com/shihaiming/p/6062663.html

东西不多卖

秒杀系统需要保证东西不多卖,关键是在多个客户端对库存进行减操作时,必须加锁。Redis中的Watch刚好可以实现一点。首先我们需要获取当前库存,只有库存中的食物小于购物车的数目才能对库存进行减。在高并发的情况下会出现某时刻查询库存够的,但下一时刻另外一个线程下单了,对库存进行减操作,刚好小于上个线程的购物车数目。照理现在的状态是不能下单成功的,因为库存已经不够了,但上一线程仍然认为数量还够,对库存进行减操作,从而导致库存出现负数的情况。如何避免?

Redis 中的watch可以在事务前对数据进行监控,如果在事务执行前,该数据发生改变,则事务不执行。刚好能满足我们的要求。看了很多代码,对watch功能还不是很理解,因为网上很多写的帖子都没有明确指出多客户端(理解之后发现还是有写的),所以不明白的可以参见下面的例子,是用Java写的。以下代码可以保证库存不多卖。

在redis中设置一个键为mykey,值为1000的变量,

public class Main {

    public static void main(String[] args) {
            new MyThread().start();
            new MyThread().start();
            new MyThread().start();
            new MyThread().start();
            new MyThread().start();
            new MyThread().start();
            new MyThread().start();
            new MyThread().start();
            new MyThread().start();
            new MyThread().start();
    }
}

class MyThread extends Thread {
    Jedis jedis = null;

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            System.out.println(Thread.currentThread().getName());
            jedis = RedisUtil.getJedis();
            try {
                int stock = Integer.parseInt(jedis.get("mykey"));
                if (stock > 0) {
                    jedis.watch("mykey");
                    Transaction transaction = jedis.multi();
                    transaction.set("mykey", String.valueOf(stock - 1));
                    List<Object> result = transaction.exec();
                    if (result == null || result.isEmpty()) {
                        System.out.println("Transaction error...");// 可能是watch-key被外部修改,或者是数据操作被驳回
                    }
                } else {
                    System.out.println("库存为0");
                    break;
                }
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
                RedisUtil.returnResource(jedis);
            }finally{
                RedisUtil.returnResource(jedis);
            }

        }
    }

}

对于Redis事务来说行不通,因为在exec命令之前,所有的命令都被Redis缓存起来了,根本就拿不到balance的值。那类似这种需要基于已经存在的某个值的事务在Redis中如何实现呢?答案是Watch命令:

redis.watch('balance')
balance = redis.get('balance')
if (balance < amtToSubtract) {
    redis.unwatch()
} else {
    redis.multi()
    redis.decrby('balance', amtToSubtract)
    redis.incrby('debt', amtToSubtract)
    redis.exec()
}

通俗点讲,watch命令就是标记一个键,如果标记了一个键,在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中重新再尝试一次。像上面的例子,首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减;足够的话,就启动事务进行更新操作,如果在此期间键balance被其它人修改,那在提交事务(执行exec)时就会报错,程序中通常可以捕获这类错误再重新执行一次,直到成功。
Redis事务失败后不支持回滚 与数据库事务很重要的一个区别是Redis事务在执行过程中出错后不会回滚。在exec命令后,Redis Server开始一个个的执行被缓存的命令,如果其中某个命令执行出错了,那之前的命令并不会被回滚。

 

 

Redis保证从数据只加载一次

我这里碰到的需求是一开始要从MySQL数据库中导入数据到Redis,由于有多台服务器,不进行控制会对数据进行多次加载,所以我们可以设置一个键值进行控制。

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Response;
import redis.clients.jedis.Transaction;
public class Main {

    public static void main(String[] args) {
            new lock().start();
            new lock().start();
            new lock().start();
            new lock().start();
            new lock().start();
            new lock().start();
            new lock().start();
            new lock().start();
            new lock().start();
            new lock().start();
    }
}

class lock extends Thread{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName());
        Jedis jedis = null;
        try {
            jedis = RedisUtil.getJedis();
            if(jedis.setnx("look", "1") == 1){
                jedis.set("food", Thread.currentThread().getName());
            }else{
                System.out.println(Thread.currentThread().getName() + "未访问");
            }
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            RedisUtil.returnResource(jedis);
        }finally{
            RedisUtil.returnResource(jedis);
        }
    }

}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

redis秒杀系统数据同步(保证不多卖) 的相关文章

  • libevent详解四(http服务)

    创建基于libevent的http服务 先上代码 span class token macro property span class token directive keyword include span span class toke
  • 生产者消费者模式解决API响应速度不一致的问题

    问题引入 项目里需要用到 问题生成大模型来生成问题 xff0c 所以采用了FastAPI部署 xff0c 然后对外提供API从而在后端调用 xff0c 模型一次只能对一个文本进行问题生成 xff0c 然而每次后端都会收到前端传来的文件 xf
  • 如何在用户目录下建立.kube文件夹

    文章目录 步骤新建文件夹如果直接输入 文件名 系统提示错误我们要输入 文件名 成功 步骤 在指定的路径下 新建一个文件夹输入 文件名 完成 新建文件夹 在指定的路径下 新建一个文件夹 如果直接输入 文件名 系统提示错误 我们要输入 文件名
  • 面向对象设计 个人汇总

    文章目录 七大原则三大特性 七大原则 开 面向扩展开放 面向修改关闭口 接口隔离原则合 组合 聚合原则里 里氏替换原则最 最少知识原则单 单一责任原则依 依赖倒置原则 三大特性 封装继承多态
  • idea单词检测出错怎么办,自定义添加单词

    完成
  • Spring Boot 2下导入依赖后使用Feign找不到@EnableFeignClients的解决办法

    文章目录 解决方案详细解决步骤1 导入依赖2 引用注解3 然后看一下maven是否引好4 在pom依赖中加上代码5 maven看一下是否成功6 使用注解 完结撒花 解决方案 pom文件中这样引用 lt feign依赖 gt lt depen
  • idea隐藏资源栏目下的文件显示

    在资源栏目下忽略这两个文件 widowS系统 步骤 MAC系统
  • swagger依赖的选用

    总结 如果你的项目使用jax rs来实现RESTful接口 xff0c 你就用io swagger来集成swagger xff1b 如果你的项目使用springmvc来实现RESTful接口 xff0c 最新的方法还是推荐使用springf
  • 谷歌chrome的页面保持为图片

  • 什么是中间件

    中间件 xff0c 是位于操作系统和应用程序中间的组件 为应用程序提供运行环境和管理支撑 常见的中间件 xff0c 商业的有weblogic WebSphere 用友 金蝶的 xff0c xff0c 开源的jboss tomcat也算中间件
  • Spring框架的7大功能

    目录 Spring框架的7大功能核心容器 Spring core Spring面向切面编程 Spring AOP Spring ORM模块Spring DAO模块Spring Web模块Spring Web模块Spring MVC框架 Sp
  • Stream流 初级使用

    文章目录 Stream流提供更好操作的集合库初始操作stream stream流parallelStream 多核流Map进行流操作 中间操作filter 筛选map 提取limit 截断sorted 排序distinct 去重 终端操作f
  • SpringMVC笔记

    1 SpringMVC概述 1 1 SpringMVC概念 SpringMVC也叫Spring web mvc 是Spring内置的一个MVC框架 xff0c 在Spring3 0后发布 SpringMVC框架解决了WEB开发中常见的问题
  • 使用Spring OpenFeign 传输文件

    这里写自定义目录标题 B模块上传文件调用了A模块的文件保存实现1 1 业务场景2 1 排查过程3 1 解决办法4 1 知识扩展4 1 1 64 PostMapping 参数含义4 1 2 接口参数描述 完整代码1 引入OpenFeign依赖
  • mysql8.0配置my.ini文件中的sql_mode=NO_AUTO_CREATE_USER能启动

    my ini配置文件中 设置 sql mode span class token operator 61 span STRICT TRANS TABLES span class token punctuation span NO ZERO
  • 当switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断。Switch(null) NullPointerException

    强制 当switch括号内的变量类型为String并且此变量为外部参数时 xff0c 必须先进行null判断 span class token comment 64 Author 陈艺博 64 Date 2020 04 01 15 27 6
  • 中央Maven 仓库,提交指定 JAR

    原则上 Maven 的设计是不需要这么做的 xff0c 因为 pom xml 中依赖的 jar 包会自动实现从中央仓库下载到本地仓库 但也有特殊情况 比如私库没有及时更新最新版本的jar 或者需要将特定的jar放到仓库中 注意 请windo
  • 中间件是什么意思?中间件技术简介

    中间件 功能 为应用提供通用服务为应用提供通用功能 使用场景 数据管理应用服务消息传递身份验证API管理 优点 为 应用 数据 用户 之间提供API管理 帮助开发人员更高效的构建应用 作用 在云原生开发中 可能会跨多种基础架构进行部署 从而
  • java中 “|=“是什么意思

    今天偶然间看到程序中有这样一个操作 乍看一脸懵逼 span class token keyword int span a span class token operator 61 span span class token number 6
  • java实现 ipv4转int保持到数据库中, 并将int转会String

    public class Ipv4Covert public static void main String args String ip 61 34 10 108 149 219 34 step1 分解IP字符串 xff0c 并对应写对字

随机推荐