异步简单实现一人一单

同步(单时钟)、异步(双时钟)FIFO的Verilog HDL实现(含Testbench仿真代码) 参考资料: 《FPGA自学笔记——设计与验证》;《硬件架构的艺术》;《Verilog HDL数字集成电路高级程序设计》等 链接: 一、FIFO的定义和应用场景 FIFO(First in First Out)是一种先进先出的数据缓冲器,通常用于接口电路的数据缓存。与普通存储器的区别是没有外部读写地址线,可以使用两个时钟分别进行读和写操作。 FIFO只能顺序写入数据和顺序读出数据,其数据地址由内部读、写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。 二、FIFO的 阅读详情

本项目码云地址:https://gitee.com/flowers-bloom-is-the-sea/distributeNodeSolvePessimisticLockByRedis/tree/version3/

项目前身:https://gitee.com/flowers-bloom-is-the-sea/distributeNodeSolvePessimisticLockByRedis/tree/version2.0/

异步实现一人一单

一、问题描述

对于一人一单的秒杀,因为这个项目之前本来就是没做异步处理的,因此对于一个业务,对于锁的操作其实可以放到其他异步线程执行。这样子就可以提高效率,而对于可以全部放到异步线程执行的操作也是根据实际业务来看的,总不能把所有业务都放异步处理吧。

对于原来的项目的具体过程是:

对于单个用户访问想买一个物品时,是先到redis里获取锁,如果获取锁成功,那么就去执行订单创建的业务,反之失败。

本篇文章是对于单个用户访问进行访问加速的,也就是当用户来操作这个业务时,使用异步线程来执行其他的操作,使用异步线程操作前就返回数据给用户使得用户不需要等待太久。

二、优化过程

第一步:

对于创建每一个物品,都将物品的库存信息放到缓存里吧。

对应的controller:

@Autowired
private GoodsOrderService goodsOrderService;
@PostMapping("addGoods")
public R addGoods(@RequestBody Goods goods) {
    goodsService.addGoods(goods);
    return R.success(goods.getId());
}

对应的impl:

@Override
public void addGoods(Goods goods) {
    save(goods);//1.save一下
    //2.TODO 添加lua脚本
    stringRedisTemplate.opsForValue().set("goods:stock:" + goods.getId(), goods.getStock().toString());
}

现在来测试一下:

`http://localhost:8081/goods/addGoods`
请求体:
{
    "id":2,
    "stock":100
}

预测:一个请求过去,正常情况下redis是有存储了该物品的库存信息了。另外数据库也添加一条id=2,stock = 100的物品信息了。

测试结果:

发现redis节点啊确实加入了库存信息

在这里插入图片描述

数据库信息:

    id   stock  
------  --------
     1       998
     2       100

结果评价:和预期的一样。

第二步:

对于一个用户来想购买一个物品,那么可以先返回结果,后面的加锁的操作再使用异步来实现。

先来实现一下:

1、首先肯定是先来查询redis一下看看有没有库存,如果有库存,那么再查一下该用户是否已经购买了该物品,如已经买过了,那么直接返回给用户:已购买,不可以买2次。如果没买,那么就操作redis,异步操作库存扣减和订单处理任务,返回已购买。

先写一下lua脚本吧:

-- 1.参数列表
-- 1.1.物品id
local goodsId = ARGV[1]
-- 1.2.用户id
local userId = ARGV[2]

-- 2.数据key
-- 2.1.库存key
local stockKey = 'goods:stock:' .. goodsId
-- 2.2.订单key
local orderKey = 'goods:order:' .. userId

-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if(tonumber(redis.call('get', stockKey)) <= 0) then
    -- 3.2.库存不足,返回1
    return 1
end
-- 3.2.判断用户是否下单 SISMEMBER orderKey userId
if(redis.call('sismember', orderKey, userId) == 1) then
    -- 3.3.存在,说明是重复下单,返回2
    return 2
end
-- 3.4.扣库存 incrby stockKey -1
redis.call('incrby', stockKey, -1)
-- 3.5.用户-添加到-->hashset
redis.call('sadd', orderKey, userId)
return 0

感觉这里有漏洞,什么漏洞?如果有多个物品goods对吧,那么多个goods都要实现一人一单,假如说有2个物品,分别是:

goods1:{
	id:1,
	stock:1000
}

goods2:{
	id:2,
	stock:100
}

那么对于同一个用户,这个用户已经购买了物品1,接下来还想买物品2,

那么这个用户去查redis时,便会被判断已经买了物品2。这个不就是误判了吗,为什么会误判?觉得lua脚本有问题:

stockKey的定义是:goods:stock:goodsId没问题对吧;

但是orderKey的定义有问题,因为orderKey定义为:goods:order:userId这里不就是不能对唯一种类物品进行唯一标识吗?

应该这样设计orderKey定义:goods:order:goodsId:userId。

经过分析,应该是可修改的

第三步:

对于要生成goodsOrder可以丢进一个队列里面,然后直接返回结果。也就是像下面一样:

@Override
public R buyOneGoods(Long goodsId, Long userId) {
    Long result = stringRedisTemplate.execute(
            GOODS_SCRIPT,
            Collections.emptyList(),
            goodsId.toString().intern(), userId.toString()
    );

    int r = result.intValue();

    // 2.判断结果是否为0
    if (r != 0) {
        // 2.1.不为0 ,代表没有购买资格
        return R.fail(r == 1 ? "库存不足" : "不能重复下单");
    }
    GoodsOrder goodsOrder = new GoodsOrder();
    goodsOrder.setGoodsId(goodsId);
    goodsOrder.setUserId(userId);
    //先丢到队列里面
    orderTasks.add(goodsOrder);

    proxy = (GoodsOrderService)AopContext.currentProxy();
    return R.success("buy one success");
}

交给队列后,队列的goodsOrder对象会被异步取出来,进行相应的处理:

因为在这整个业务开始处理的时候就有一个线程池开启一个异步线程来处理上面的goodsOrder

private static final ExecutorService GOODS_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
@PostConstruct
private void init(){
    GOODS_ORDER_EXECUTOR.submit(new GoodsOrderHandler());
}

这个线程创建的GoodsOrderHandler类里面就要一个方法专门处理goodsOrder的:

    private class GoodsOrderHandler implements Runnable{
        @Override
        public void run() {
            while (true){
                try {
                    // 1.获取队列中的订单信息
                    GoodsOrder goodsOrder = orderTasks.take();
                    // 2.创建订单
                    handleGoodsOrder(goodsOrder);
                } catch (Exception e) {
                    log.error("处理订单异常", e);
                }
            }
        }
    }
    private void handleGoodsOrder(GoodsOrder goodsOrder) {
        Long userId = goodsOrder.getUserId();
        // 创建锁对象
        RLock lock = redissonClient.getLock("lock:order:" + userId);
        // 获取锁
        boolean isLock = lock.tryLock();
        // 判断是否获取锁成功
        if(!isLock){
            // 获取锁失败,返回错误或重试
            log.error("不允许重复下单");
            return;
        }
        try {
            // 获取代理对象(事务)
            proxy.createGoodsOrder(goodsOrder);
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

因为run里有一个死循环,于是会不断地获取队列里的goodsOrder对象,然后再在handleGoodsOrder方法里完成订单和数据库库存扣减的业务。

全都配置好后再来测试一下:

最终测试:

先用postman测试接口:http://localhost:8081/goods/buyGoodByUserId/2/1

结果:

{
  "code": 200,
  "data": "buy one success"
}

查看一下数据库goods_order表:

    id  user_id  goods_id  
------  -------  ----------
     7        1           2

goods表:

    id   stock  
------  --------
     1       998
     2        99

再来查看一下缓存:
在这里插入图片描述

在这里插入图片描述

发现没问题!

再将redis里的用户购买记录去掉,在数据库里删除goods_order里的数据,将goods里物品id=2的库存恢复到100,对100库存使用jmeter配置100线程对/goods/buyGoodByUserId/2/1测压:

对于数据库预测goods里的id为2的物品库存减1,goods_order里的数据新增一条,100线程只有1个通过。

上述的预测都成立,测试成功。

goods_order表:

    id  user_id  goods_id  
------  -------  ----------
     8        1           2

聚合报告:

在这里插入图片描述

结果确实是只有1个通过。

这里的线程还是不敢开太多的,因为毕竟队列还是使用java内部自带的队列,性能肯定是比不上市面上成熟了的消息队列。

三、反思与总结

对于这个业务也就是使用了异步来处理其他请求,而对于也就是将同一个业务的其他操作放到异步线程里执行,就这样。

对于消息队列,可以使用比较成熟的框架比如其他rabbitmq、卡夫卡等,这里就写得随便了,不管了,有点意思就可以了。

另外,对于其他的方面,比如对于多个物品的一人一单还是要优化的。这就不管了,因为本文重点在于异步实现。

异步简单双口RAM的实现 RAM的分类 RAM,随机存取存储器,有单口和双口之分,单口RAM只有一个数据线和地址线,不能同时进行读取操作,双口RAM有读和写两个地址线和数据线。因此双口RAM使用要比单口RAM广泛。 双口RAM有真双口和简单双口之分,真双口有两个读端口和两个写端口,简单双口只有一个读端口和一个写端口,一般读写要求不高时,简单双口RAM就能满足我们的需求。另外根据时钟的不同,RAM还有同步异步之分,同步RAM是指读写采用同一个时钟,异步RAM是指读写的时钟不同。 异步简单双口RAM的实现 实现的双口RAM的功能 阅读详情

相关推荐

Unity AR开发实战:从Vuforia图片识别到实体识别全流程解析

增强现实(AR)技术通过计算机视觉算法将虚拟信息叠加到现实世界,其核心原理在于对现实环境的感知与理解。在移动端AR开发中,图像识别与物体识别是实现虚实融合的关键技术,它们决定了虚拟内容能否准确、稳定地与现实对象对齐。Vuforia作为成熟的商业AR SDK,为开发者提供了从平面图像识别(Image Target)到三维实体识别(Model Target)的完整解决方案,极大降低了计算机视觉技术的应用门槛。在工程实践中,Vuforia通过封装复杂的特征提取与匹配算法,使开发者能够专注于交互设计与内容制作,广泛

weixin_30972263的博客 301

day4_redis中分布式锁实现一人一单

单体系统中实现一人一单 集群中实现一人一单

奋斗的龙猫的博客 2255

告别Keil!用VSCode+PlatformIO玩转STC单片机(附自动下载配置)

本文详细介绍了如何从Keil迁移到VSCode+PlatformIO进行STC单片机开发,包括环境搭建、自动下载配置等实用技巧。通过对比传统IDE与现代工具链的差异,展示了VSCode+PlatformIO在代码编辑体验、跨平台支持和开发效率上的优势,帮助开发者实现更流畅的STC单片机开发流程。

weixin_27252001的博客 457

45、优惠券秒杀(实现一人一单)

优化1、(调用一个字符串方法,它的值相同返回的对象是一样的toString().intern())解决(优化)思路:(还是加锁啊,这里就不能更新再来判断了,就必须是悲观锁了(syn))优化2、我们应该是在事务提交后才释放锁(我们将syn加在这个函数调用前面去加锁了)第一步:查询订单嘛(才能判断(将下面的查询用户id提上来先用))确保当我们的用户的id一样的时候,锁是一样的。第三步:(优化:不要将整个方法锁住(提高效率),锁用户)第二步:判断是否存在了(不存在返回失败)坑2:(我们这里的锁范围又有点小了)

分享JAVA/ Python/ Ts学习路上的一些笔记,不足之处多多见谅。 813

『ISOBUS 入门』第 00 节 导言:ISOBUS 的宏观图景与入门路径

ISOBUS 是由三部分构成的集成系统:两段CAN网络(拖拉机与农具侧)、标准化通信约定及认证流程。其核心在于通过统一协议实现跨品牌设备即插即用。标准体系以ISO 11783为基础,分层涵盖物理层、传输层至应用层,关键功能如虚拟终端(VT)、任务控制器(TC)和文件服务器依赖严格的会话管理与角色分工。实际部署中,总线上传输多种协议报文,故障排查需结合地址声明、多包传输与角色权限分析。学习应从真实报文入手,建立“角色—数据流—功能块”认知链,避免仅学标准或仿真替代实测,方能有效应对复杂现场问题。

linx295的博客 280

《UDS协议从入门到精通》系列——图解0x23:通过地址读取内存

如何读取ECU中指定内存地址的数据?图解UDS 0x23服务带你快速上手!!

车载系统攻城狮 2622

限购(一人一单)问题解剖及解决方案

目录限购问题问题:活动产品,每个用户购买不能超过1个?解决方案:Java锁(单机)解决方案:分布式锁(集群)Redis实现分布式锁问题?setnx锁超时释放使用 redisson 来实现分布式锁总结 限购问题 限购问题是生活中很常见的,如某某手机限购不能超过几台,商品搞活动时每人购买不能超过1件… 这种问题看似很简单,但一个处理不好,就会出现超买问题 问题:活动产品,每个用户购买不能超过1个? 线程1进来先校验A客户是否下单过,此时是没有,在还未来得及生成订单,线程2进来校验A客户是否下单过,由于线程

。。。 3442

A*搜索算法在三维路径规划(避障)中的MATLAB实现与Python实现

A star algorithmA*搜索算法简介A*搜索算法的Python简化代码MATLAB实现启发函数回溯轨迹RRT算法对比A\*算法在三维路径规划中的一些不足 A*搜索算法简介 A*搜索算法是Greedy Best-First算法的改进,因为Greedy Best-First算法只考虑将预估距离(当前节点到目标点的预估距离)作为遴选开节点集的条件。即: priority=heuristic(goal,next)priority=heuristic(goal, next)priority=heurist

ZYunfei的博客 2万+

优化秒杀系统:实现一人一单功能

通过本文的介绍,我们实现了基于乐观锁和 CAS 技术的一人一单机制,有效防止了用户重复购买同一张秒杀优惠券的问题。同时,我们也讨论了进一步优化的方法,包括引入分布式锁、Redis 缓存和消息队列,以提升系统在高并发环境下的稳定性。

Takumilove的博客 872

【Power Compiler手册】4.saif文件

功耗计算的准确性取决于切换活动数据的准确性。这些数据是通过使用RTL仿真或门级仿真生成的,并存储在SAIF文件中。在执行功耗优化和分析之前,应该使用SAIF文件来反标设计对象上的切换活动信息。SAIF是由Synopsys支持的ASCII格式,用于促进不同Synopsys工具之间的信息交换(参见IEEE 1801标准,附录J)。使用`read_saif`命令读取SAIF文件,使用`write_saif`命令写出SAIF文件。有关更多信息,请参见`read_saif`和`write_saif`命令的手册页。

QRBQ 2186

RK3568平台开发系列讲解:UBOOT详细介绍

U-boot是一款广泛使用的开源引导加载程序,支持多种嵌入式操作系统和处理器架构。其主要功能包括系统上电时的硬件初始化、引导操作系统内核等。U-boot的引导流程通常包括Boot Code、idbloader.img、uboot.img、boot.img和rootfs.img等步骤。启动U-boot时,通过Debug串口连接板卡,按下ctrl+c键可进入U-Boot命令行模式。U-boot启动过程中会执行一系列初始化操作,如打印系统信息、初始化内存、加载内核设备树、初始化I2C、PMIC电源芯片、DRM框架

内核笔记 808

【Redis】电商项目秒杀问题之超卖问题与一人一单问题

在如双11等购物需求剧增的背景下,一个物品库存里有100件但是由于并发等问题可能会导致该物品被卖出超过100件。这就是超卖问题,他是由于库存量被高并发请求而产生的线程安全问题。

qq_61903414的博客 4616

CT探测器普遍采用“星形拓扑+多ADC模组”架构的TDC(时间数字转换器)设计方案

本文提出一种基于TDC反馈机制的CT探测器时钟同步方法,通过延时测量、偏差计算和动态补偿三步硬件流水线,实现高精度同步。系统采用Xilinx Kintex UltraScale FPGA内置CARRY8进位链构建3.5ps分辨率的TDC,结合多级比较器和延迟单元,形成闭环反馈控制。实验表明,在512通道、1.28m传输距离下,同步精度稳定在10ps以内,较传统方案提升两个数量级,并能动态补偿干扰抖动。该方案解决了传统方法在CT旋转腔体环境下精度不足的问题,为下一代超高清CT提供了有效的时钟同步解决方案。

whm128的博客 274

黑马点评-实现安全秒杀优惠券(使并发一人一单,防止并发超卖)

出现的问题:在高并法的情况下存在多买的情况,导致商品成为负数。

2301_81230656的博客 1471

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

微服务Spring Boot 整合 Redis 分布式锁 Redission 实现优惠卷秒杀 一人一单

Redis 首选 Java客户端 Redission,如何使用Redission 实现分布式锁?

wanghuichen的博客 6393

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

优惠券秒杀之超卖问题、一人一单问题、异步秒杀

优惠券秒杀之超卖问题、一人一单问题(乐观锁、悲观锁、分布式锁、Lua、Redisson可重入锁、消息队列)

蔡林浩的博客 1030

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

【Solution】商品秒杀之Redis缓存与MQ异步优化以及超卖一人一单等问题的解决

主要有三张表:用户表、商品表、订单表,将上述sql脚本执行一遍即可。

qq_61903414的博客 1063

Java 异步解决一人一单问题

theme: nico 思路 先判断用户是否有购买资格: 1.1 商品优惠的起始时间与当前时间做对比 1.2 判断商品库存是否充足 判断用户是否拥有购买资格后,根据redis存入的内容,返回答复信息 需要更新的内容:如商品数量,订单详细... 存入缓存队列,异步的去数据库更新 代码 1. 判断用户资格 下图为秒杀方法,秒杀的商品为限时购物券,传入的参数为购物券id。只有在规...

xy的博客 175

Redisson 的使用 以及它解决一人一单的多种使用方案

用redission来解决一人一单优惠券的使用,比前面的方案更好

2302_78290720的博客 1022

基于Redis实现高并发优惠券秒杀系统:全局ID、超卖控制与一人一单

本文介绍了如何利用Redis实现高并发环境下的优惠券秒杀系统。主要内容包括: 全局唯一ID生成方案:通过Redis自增特性配合时间戳,实现分布式系统的唯一ID生成,满足高并发需求。 优惠券管理实现: 区分平价券和特价券 特价券需记录库存、抢购时间等额外信息 将秒杀库存同步到Redis 秒杀下单流程: 校验秒杀时间有效性 检查库存充足性 扣减库存 创建订单 系统设计解决了高并发环境下的ID唯一性问题,并通过Redis预存库存提高秒杀性能。后续可进一步优化超卖问题和一人一单限制。该方案利用Redis的原子操作特

weixin_45919016的博客 978

用 Redisson 解决秒杀 “一人一单” 并彻底避免 超卖

本文介绍了使用Redisson实现秒杀场景下"一人一单"和防止超卖的完整解决方案。核心思路包括:通过商品维度分布式锁保证并发安全,Redis Set记录已购用户实现一人一单,预加载库存和原子扣减避免超卖。代码演示了完整的Java实现方案,包含分布式锁获取、库存扣减、用户去重和异步下单流程。方案采用细粒度锁、双重检查等优化手段确保高吞吐,并提供了上线前的完整检查清单,包括库存预热、锁粒度控制等关键点。最后还提出了热点商品的分段锁优化方案,形成了一套标准的秒杀系统实现方案。

qq_45672232的博客 506

微服务 Spring Boot 整合Redis 实现优惠卷秒杀 一人一单

高并发场景下,如何解决分布式ID存储,限制每人一单? Redis来搞定!

wanghuichen的博客 6万+

秒杀业务: 简单梳理

(1)在一人一单优惠券秒杀中,先是解决了因线程安全导致的超卖问题,为了满足一人一单的业务需求需要给查询订单表查询库存表上锁,但是synchronized只能满足单机环境下线程安全,在分布式环境对多个JVM需要使用分布式锁,于是手写分布式锁,但是在极端情况下,例如业务超时,会出现“误删”问题,手写锁无法解决,于是引入redisson,借助看门狗机制可以有效解决这个问题。

m0_59925573的博客 1847

秒杀扣库存,一人一单,缓存击穿用redisson怎么解决

问题:库存剩 1 件,用户A和用户B同时查到有库存,都去扣,库存变 -1,超卖了。

mxhnb666的博客 206

黑马Redis课程—单机环境下一人一单问题完整解析

首先,这个实现一人一单问题的代码是只适合用于学习知识点的,不适合投入实际生产。

x81287的博客 435

关于Socket长连接异步单工

1.关于异步单工长连接的概念问题,百度上有很多大牛给出了解释,在这边就不进行说明了,直接来代码 2.长连接异步单工 保持心跳,15秒发送一个心跳,服务器端60秒接收一次心跳,以此来判断两条链路是否在线

想记就记 2737
上一篇: 160相交链表-hot100
下一篇: docker部署redis实例
xin麒
博客等级 码龄6年 110粉丝 161原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值