【黑马点评】(三)优惠卷秒杀

Redis实战《黑马点评》(有完整项目代码) 代码修改:ShopServiceImpl:添加方法trylock获取锁,添加方法unlock释放锁,修改queryById。utils下有个UserHolder来将用户信息存到线程中,需要修改为:提示爆红修改UserDTO为User。修改UserServiceImpl中的login方法:将UserDTO存入session,而不是User。获取锁:setnx lock “id” 为 0 ,说明已上锁; 阅读详情

(一)全局唯一ID

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

(二)Redis实现全局唯一id

@Component
public class RedisIdWorker {

    /**
     * 开始的时间戳
     */
    private static final long BEGIN_TIMESTAMP = 1640995200L;
    /**
     * 序列号的位数
     */
    private static final int COUNT_BITS = 32;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public long nextId(String keyPrefix){
        // 1.生成时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;
        // 2.生成序列号
        // 2.1 获取当前日期
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
        // 3.拼接并返回

        return timestamp << COUNT_BITS | count;
    }

//    public static void main(String[] args) {
//        LocalDateTime localDateTime = LocalDateTime.of(2022, 1, 1, 0, 0, 0);
//        long second = localDateTime.toEpochSecond(ZoneOffset.UTC);
//        System.out.println("second = " + second);
//    }
}
    @Resource
    private RedisIdWorker redisIdWorker;

    private ExecutorService es = Executors.newFixedThreadPool(500);

    @Test
    void testIdWorker() throws InterruptedException {

        CountDownLatch latch = new CountDownLatch(300);

        Runnable task = () -> {
            for (int i = 0; i < 100; i++) {
                long id = redisIdWorker.nextId("order");
                System.out.println("id = " + id);
            }
            latch.countDown();
        };
        long begin = System.currentTimeMillis();
        for (int i = 0; i < 300; i++) {
            es.submit(task);
        }

        latch.await();
        long end = System.currentTimeMillis();
        System.out.println("time = " + (end - begin));
    }

在这里插入图片描述

在这里插入图片描述

(三)添加优惠卷

普通优惠卷(平价卷)

CREATE TABLE `tb_voucher` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `shop_id` bigint unsigned DEFAULT NULL COMMENT '商铺id',
  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '代金券标题',
  `sub_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '副标题',
  `rules` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '使用规则',
  `pay_value` bigint unsigned NOT NULL COMMENT '支付金额,单位是分。例如200代表2元',
  `actual_value` bigint NOT NULL COMMENT '抵扣金额,单位是分。例如200代表2元',
  `type` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '0,普通券;1,秒杀券',
  `status` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '1,上架; 2,下架; 3,过期',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT;

秒杀卷

CREATE TABLE `tb_seckill_voucher` (
  `voucher_id` bigint unsigned NOT NULL COMMENT '关联的优惠券的id',
  `stock` int NOT NULL COMMENT '库存',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `begin_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '生效时间',
  `end_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '失效时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`voucher_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='秒杀优惠券表,与优惠券是一对一关系';

在这里插入图片描述

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_voucher")
public class Voucher implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 商铺id
     */
    private Long shopId;

    /**
     * 代金券标题
     */
    private String title;

    /**
     * 副标题
     */
    private String subTitle;

    /**
     * 使用规则
     */
    private String rules;

    /**
     * 支付金额
     */
    private Long payValue;

    /**
     * 抵扣金额
     */
    private Long actualValue;

    /**
     * 优惠券类型
     */
    private Integer type;

    /**
     * 优惠券类型
     */
    private Integer status;
    /**
     * 库存
     */
    @TableField(exist = false)
    private Integer stock;

    /**
     * 生效时间
     */
    @TableField(exist = false)
    private LocalDateTime beginTime;

    /**
     * 失效时间
     */
    @TableField(exist = false)
    private LocalDateTime endTime;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;


    /**
     * 更新时间
     */
    private LocalDateTime updateTime;


}

调用接口,这里记得添加请求凭证到请求头当中,当然你也可以在拦截器里面不拦截添加优惠卷的请求路由

在这里插入图片描述

(四)实现秒杀下单

在这里插入图片描述

在这里插入图片描述
controller

@RestController
@RequestMapping("/voucher-order")
public class VoucherOrderController {

    @Resource
    private IVoucherOrderService voucherOrderService;

    @PostMapping("seckill/{id}")
    public Result seckillVoucher(@PathVariable("id") Long voucherId) {
        return voucherOrderService.seckillVoucher(voucherId);
    }
}

service实现类

public interface IVoucherOrderService extends IService<VoucherOrder> {

    Result seckillVoucher(Long voucherId);
}

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService{

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    @Transactional
    public Result seckillVoucher(Long voucherId) {
        // 1. 查询优惠卷
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        // 2. 判断秒杀是否开始
        if(voucher.getBeginTime().isAfter(LocalDateTime.now())){
            // 尚未开始
            return Result.fail("秒杀尚未开始");
        }

        // 3. 判断秒杀是否结束
        if(voucher.getEndTime().isBefore(LocalDateTime.now())){
            // 尚未开始
            return Result.fail("秒杀已经结束");
        }
        // 4. 判断库存是否充足
        if(voucher.getStock() < 1){
            // 库存不足
            return Result.fail("库存不足");
        }
        // 5. 扣减库存
        boolean success = seckillVoucherService.update().
                setSql("stock = stock - 1").
                eq("voucher_id", voucherId).update();
        if(!success){
            // 库存不足
            return Result.fail("库存不足");
        }
        // 6. 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        // 订单id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        // 用户id
        Long userId = UserHolder.getUser().getId();
        voucherOrder.setUserId(userId);
        // 代金卷id
        voucherOrder.setVoucherId(voucherId);
        // 7. 返回订单id
        save(voucherOrder);
        return Result.ok(voucherOrder);
    }
}

在这里插入图片描述

(五)库存超卖问题分析

在这里插入图片描述

正常情况下:
在这里插入图片描述
高并发问题下:

在这里插入图片描述

自己去apipost实现压测,也是同样的问题

在这里插入图片描述
在这里插入图片描述
同时会发现订单表不止100条订单数据。。。就是超卖了。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(六)乐观锁解决超卖问题

cas操作:在执行sql语句时将stock与一开始查询到的stock进行比较

// 5. 扣减库存
boolean success = seckillVoucherService.update().
        setSql("stock = stock - 1"). // set stock = stock - 1
        eq("voucher_id", voucherId).eq("stock", voucher.getStock()) // where id = ? and stock =
        .update();

再次进行测试:发现100的库存剩下84 。。。 超卖解决完毕了,但是stock按道理应该为0吧

在这里插入图片描述
在这里插入图片描述
订单表也只有16个数据
在这里插入图片描述
重新修改代码,因为之前的代码是比较库存是否和当前查询的相同,比如当100张票的时候有十几个线程同时消费库存,此时只有一个线程会成功,因此只需要将条件改为库存大于0即可。

 // 5. 扣减库存
 boolean success = seckillVoucherService.update().
         setSql("stock = stock - 1"). // set stock = stock - 1
         eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0
         .update();

此时压测发现,库存成功从100变为0,订单表中数据也恰好为100条。

在这里插入图片描述
在这里插入图片描述

(七)实现一人一单的功能

之前使用的乐观锁,所有的购票都是同一个用户买的100单。。。。成黄牛了哈哈哈,因此需要判断用户是否买过这个优惠卷。

在这里插入图片描述
先上代码,这里的代码可能有点小复杂,在乐观锁的基础上,对用户id进行了加上了悲观锁,因为不加悲观锁也会导致同一个id买到多张卷的问题,原因类比乐观锁的超卖。

<?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.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.hmdp</groupId>
    <artifactId>hm-dianping</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>hm-dianping</name>
    <description>Demo project for Spring Boot</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.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.17</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

新增了 aspectj 依赖,实现代理。。。 同时启动类增加@EnableAspectJAutoProxy(exposeProxy = true) 暴露代理

@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan("com.hmdp.mapper")
@SpringBootApplication
public class HmDianPingApplication {

    public static void main(String[] args) {
        SpringApplication.run(HmDianPingApplication.class, args);
    }

}

public interface IVoucherOrderService extends IService<VoucherOrder> {

    Result seckillVoucher(Long voucherId);

    Result createVoucherOrder(Long voucherId);
}


@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService{

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    public Result seckillVoucher(Long voucherId) {
        // 1. 查询优惠卷
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        // 2. 判断秒杀是否开始
        if(voucher.getBeginTime().isAfter(LocalDateTime.now())){
            // 尚未开始
            return Result.fail("秒杀尚未开始");
        }

        // 3. 判断秒杀是否结束
        if(voucher.getEndTime().isBefore(LocalDateTime.now())){
            // 尚未开始
            return Result.fail("秒杀已经结束");
        }
        // 4. 判断库存是否充足
        if(voucher.getStock() < 1){
            // 库存不足
            return Result.fail("库存不足");
        }
        // ctrl + alt + m 封装方法
        Long userId = UserHolder.getUser().getId();
        synchronized(userId.toString().intern()){
            // 获取代理对象(事务)
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }

    }

    @Transactional
    public  Result createVoucherOrder(Long voucherId) {
        // 一人一单
        // 查询订单,判断是否存在
        // 用户id
        Long userId = UserHolder.getUser().getId();

        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if(count > 0){
            // 用户已经购买过了
            return Result.fail("用户已经购买过一次!");
        }

        // 5. 扣减库存
        boolean success = seckillVoucherService.update().
                setSql("stock = stock - 1"). // set stock = stock - 1
                        eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0
                .update();
        if(!success){
            // 库存不足
            return Result.fail("库存不足");
        }


        // 6. 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        // 订单id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);

        voucherOrder.setUserId(userId);
        // 代金卷id
        voucherOrder.setVoucherId(voucherId);
        // 7. 返回订单id
        save(voucherOrder);
        return Result.ok(voucherOrder);


    }
}

本地apipost压测200个线程跑1s,因为都是同一个用户,最终stock的数量只会到99,订单表里面也只有一个订单数据。

总体来说:这是一个实现秒杀优惠券功能的Java服务代码,主要逻辑是:先校验秒杀活动时间(未开始/已结束则报错)和库存(不足则报错),然后通过用户ID级别的同步锁确保同一用户只能有一个请求进入下单流程,在事务方法中再次校验"一人一单"规则(防止重复购买),使用数据库乐观锁(stock > 0条件)安全扣减库存,最后用Redis生成分布式ID创建订单并返回结果。整个过程通过双重校验(活动状态+库存+用户订单)和锁机制保障数据一致性。

很多小伙伴不懂这个地方的代理为啥要这样写:

 // ctrl + alt + m 封装方法
 Long userId = UserHolder.getUser().getId();
 synchronized(userId.toString().intern()){
     // 获取代理对象(事务)
     IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
     return proxy.createVoucherOrder(voucherId);
 }

使用代理对象是为了确保事务注解 @Transactional 生效。由于 Spring 的事务功能通过动态代理实现,当同一个类中直接调用 this.createVoucherOrder() 时,会绕过代理对象导致事务失效。通过 AopContext.currentProxy() 获取当前类的代理对象再调用方法,能够触发 Spring 的事务拦截器,保证库存扣减和订单创建在同一个事务中执行,实现操作的原子性(要么同时成功,要么同时回滚)。

小伙伴的疑问又来了 当前类的代理对象是什么 为什么Service是代理对象 而不是当前impl的是代理对象

关于代理对象和实现类的关系(清晰版解释):

实现类:你编写的 VoucherOrderServiceImpl 类(带 @Service 注解)

代理对象:Spring 运行时动态生成的增强版实现类

为什么实现类本身不是代理对象?

你编写的 VoucherOrderServiceImpl.java 是原始代码
→ 无法包含动态生成的事务逻辑(事务开启/提交/回滚)

类比理解:
想象你买了部手机(实现类),Spring 就像手机贴膜+装保护壳的服务:

原始手机(实现类):功能正常但易损坏(无事务保护)

带壳手机(代理对象):增加了防摔保护(事务能力)

当你从口袋(@Autowired)拿出时,拿到的是带壳手机

但在手机内部电路(this)看来,它仍是原始手机

这就是为什么需要显式获取代理对象——为了唤醒"保护壳"的事务功能。

(八)集群下的线程并发安全问题

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

用这个接口测试两个服务是否成功:http://localhost:8080/api/shop/1 因为不需要token 但是可能会被缓存,第二个8082不需要走数据库了。

理想情况下:
在这里插入图片描述

但是集群下,syn锁同一个用户在两个服务下可能都能进去,因为不是同一把锁,因此需要->分布式锁

在这里插入图片描述

【黑马点评】项目知识点及面经整理 阅读详情

相关推荐

黑马点评项目笔记(三)分布式锁、异步秒杀、lua脚本

全局唯一IDredis自增主键秒杀优惠卷悲观锁乐观锁一人一单问题单体项目通过动态代理对象调用方法集群项目分布式锁超时释放导致误删锁问题编写Lua脚本Redission简介可重入锁原理重试原理锁释放原理主从一致原理异步秒杀redis 消息队列ist结构PubSubStream消费者组三者对比使用redisMQ完成异步秒杀总结

m0_52889702的博客 3936

仿黑马点评项目(三、优惠卷秒杀 SET、String、Stream)

位运算拼接时间戳和序列号返回 id 给调用者;在Util工具包下定义一个RedisIDWorker类,加上@Component标签使他成为Spring容器中管理的Bean;开始时间戳的秒数生成及定义;定义一个nextId方法,参数为String keyPreFix,表示redis中不同业务的key对应的唯一Id,自增长可对对应的key进行操作。

wenyuwbq的博客 319

【2025CVPR】基于脉冲神经网络的能效目标检测模型:Multi-scale Spiking Detector(MSD)深度解析

本文提出了一种创新的多尺度脉冲神经网络目标检测器(MSD),通过生物启发的ONNB模块和MSDF框架实现了高效准确的目标检测。该模型采用端到端直接训练策略,在COCO数据集上达到62.0%mAP的精度,同时仅需7.8M参数和6.43mJ能耗,较传统方法提升2.8%精度并降低82.9%能耗。实验验证了其在小目标检测和事件数据处理的优越性,为边缘计算应用提供了新范式。

hasakie的博客 1109

美团首次面试项目话术1——黑马点评篇

黑马点评项目针对项目的话术

做一个善良的人,做好自己的事,如果愿意,再发一份光。 2万+

COMSOL仿真教程—激光烧蚀

本例使用二维模型,将来自激光的入射热通量模拟为金属表面上空间分布的热源,从而得到金属的烧蚀过程和瞬态温度分布。

qq_40977332的博客 2万+

Lumerical官方案例、FDTD时域有限差分法仿真学习(三)——环形谐振器(Ring resonator)之第二&第三部分

二、使用 MODE 的参数提取和蒙特卡罗 三、最终参数提取

weixin_45719141的博客 1万+

黑马点评面试知识总结3-优惠卷秒杀(单机环境)

乐观锁:会有一个版本号,每次操作数据会对版本号+1,再提交回数据时,会去校验是否比之前的版本大1 ,如果大1 ,则进行操作成功,这套机制的核心逻辑在于,如果在操作过程中,版本号只比原来大1 ,那么就意味着操作过程中没有人对他进行过修改,他的操作就是安全的,如果不大1,则数据被修改过,当然乐观锁还有一些变种的处理方式比如cas。悲观锁可以实现对于数据的串行化执行,比如syn,和lock都是悲观锁的代表,同时,悲观锁中又可以再细分为公平锁,非公平锁,可重入锁,等等。这是最基础的数据库操作,还没用到redis。

qq_55198752的博客 1223

02.Palantir Foundry平台功能和应用全景图

本文深入解析 Palantir Foundry 平台核心功能与应用全景。

687

【Java项目推荐】值得写到简历上的项目--黑马点评

项目是b站黑马程序员的redis教程中的案例,建议所有java程序员做一下!这篇博客会从最简单的实现优惠卷秒杀到加分布式锁、对秒杀优化、使用消息队列异步下单做详细介绍!

全网粉丝10W+、全栈领域优质创作者、掘金、阿里云等社区博客专家、专注于全栈领域和毕业项目实战 2万+

rockchip rk3566 android11 网口log报错: DMA engine initialization failed

问题描述:上电初始化前将网口插入,然后上电初始化网口能够正常使用,且能够找到PHY,ifconfig 查看能够有eth0 产生,网口正常使用且能够热插拔,但上电初始化时,不插入网口,就会报DMA engine initialization failed 错误,DMA 初始化的时候出错了。 分析:一般产生这个问题可以认为是GMAC 的工作时钟出问题了。先测量时钟引脚是否有时钟,时钟频率以及幅度等指标是否正常,主要确认以下几个方面: 1.IOMUX 出错,检查时钟脚寄存器值是否正确。 2.时钟方向以及配置

qq_48709036的博客 2万+

震坤行:企业 MRO 物料标准化革新之路

那产生这些问题的原因是什么呢?要想解决此问题,企业高层必须下定决心对历史数据进行梳理,并善于利用信息技术,融合企业管理的流程机制,建立起一套适合企业发展的物料标准化管理体系,循序渐进,逐步规避由物料不规范带来的管理问题,最终使得这套标准化体系能够满足企业多元化业务发展需要。国内某电子制造头部企业原先内部物料无对应分类,仅有物料编码,给企业对采购、领料的过程中造成极大的不变,基于震坤行的目录结构,预测物料对应的分类,预测准确率达到80%以上,再辅以人工校正,最终搭建起一套标准的目录体系,如图2.2所示。

carson99的博客 421

黑马点评总结

⿊马点评是⼀个⼤量使⽤Redis的项⽬,该项⽬的功能类似⼤众点评界面:数据库表:后端代码:config 目录:存放项目依赖相关配置controller 目录:存放 Restful 风格的 API 接口dto 目录:存放业务封装类,如 Result 通用响应封装(不推荐学习它的写法)entity 目录:存放和数据库对应的 Java POJO,一般是用 MyBatisX 等插件自动生成mapper 目录:存放操作数据库的代码,基本没有自定义 SQL,都是复用了 MyBatis Plus 的方法,不做重点学习。s

小辉的编程学习记录 6696

Halcon手眼标定实战:九点标定法从入门到精通(附完整代码)

本文详细介绍了Halcon环境下九点标定法的完整实现流程,从基础概念到实战技巧,帮助开发者快速掌握手眼标定技术。通过精确采集图像坐标与机械坐标,计算仿射变换矩阵,实现相机与机械臂坐标系的精确映射,适用于工业自动化中的平面定位场景。

v4w5x6的博客 411

黑马点评part3 -- 优惠卷秒杀

黑马点评part3 -- 优惠卷秒杀

ros275229的博客 2955

如何在Ubuntu 22.04系统上安装CUDA 12.9

本文提供Ubuntu22.04系统上安装CUDA12.9的简明指南:1)确认已安装适配575驱动;2)清理旧版本后安装必需依赖库;3)通过deb包方式添加英伟达源并安装CUDA12.9工具包;4)配置环境变量;5)通过nvcc-V和nvidia-smi命令验证安装。文中包含常见问题解决方法及多显卡使用说明,全程约需10分钟完成安装。

2501_93651177的博客 1480

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

二刷 黑马点评 优惠卷秒杀

核心要求包括:高可用、唯一性、高性能、安全性(非规律可猜)、递增性。基于 Redis 的实现方案:ID 由 1 位符号位(固定为 0)、31 位时间戳(当前时间与起始时间的秒级偏移)、32 位序列号(Redis 每日自增计数器)组成,通过INCR命令实现序列号原子递增,拼接后生成全局唯一 ID。

qq_34832548的博客 461

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

【黑马点评|项目】万字总结(下)

优惠卷秒杀接口 单体项目下一人多单的情况 线程1查询库存,发现库存充足,创建订单,然后准备对库存进行扣减,但此时线程2和线程3也进行查询,同样发现库存充足,然后线程1执行完扣减操作后,库存变为了0,线程2和线程3同样完成了库存扣减操作,最终导致库存变成了负数!这就是超卖问题的完整流程那么我们该如何有效防止超卖问题的发生呢,以下提供几种常见的解决方案 首先我们要为 tb_seckill_voucher 表新增一个版本号字段 version ,线程1查询完库存,在进行库存扣减操作的同时将版本号+1,线程2

shenqibaobao的博客 1663

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

黑马Redis(三)黑马点评项目

全局唯一Id、优惠卷超卖、乐观锁、一人一单分布式锁实现、Lua脚本、Redisson实现分布式锁、消息队列、Redis消息队列实现方式、Redis基于stream的消息队列、异步秒杀改造

DDDiccc的博客 753

苍穹外卖&黑马点评——简历包装分享

管理端可以对商品的分类、订单、员工等信息进行管理维护,统计各类数据,以及发放优惠卷;本项目是一款用户点评和商户信息为主的生活服务平台,实现了商户点评、优惠券秒杀、好友关注和粉丝关注博主后,主动推送博主的相关评价等多个模块;用户可以浏览首页的推荐内容,搜索附近商家,查看商家的详情和评价以及发表探店评价。为防止接口被恶意调用,设计API秘钥、令牌校验等方式来提高接口安全性,并通过。实现UV统计,测量网站流量,降低内存占用,使用Redis中的。,确保库存和订单的并发安全,避免超卖和数据不一致;

2201_75411399的博客 3万+

【Redis实战篇】黑马点评学习笔记(16万字超详细、Redis实战项目学习必看、欢迎点赞⭐收藏)

本文对黑马点评项目做了详细的思路分析,以及各种踩坑解决方案,全面使用了Redis各种应用技巧和解决方案,深度透析redis底层原理+redis分布式锁+企业解决方案。

实用技术干货分享⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄对您有用的话请点个关注吧~ 2万+

JMeter压测黑马点评优惠券秒杀的配置及请求爆红问题的解决(详细图解)

改JSON的断言解决已配置token但是请求全部爆红且状态码为200

2301_80558092的博客 2516

黑马点评学习笔记总结

黑马点评学习笔记总结

ros275229的博客 5952

大厂面试----黑马点评项目面试过程中可能遇到的问题整理

该文总结了黑马点评过程中的一些常问问题, 相关解答我也会后续编撰并发出

XiaoDuof优快云的博客 3372

Redis项目实战——黑马点评(优惠卷秒杀部分)

redis实战-黑马点评的优惠券秒杀部分

m0_60301012的博客 2340

【黑马点评|项目日记】第七天 秒杀优化

异步秒杀优化方案摘要 该方案针对传统串行秒杀流程(查询优惠券、库存判断、订单校验等6个步骤)进行优化,采用Redis+Lua+异步队列实现高性能秒杀。主要优化点包括: 将库存判断、一人一单校验等逻辑移到Redis中执行,使用Lua脚本保证原子性 快速校验通过后直接返回成功,下单操作异步执行 采用阻塞队列缓冲下单请求,避免线程池资源耗尽 单独线程从队列中消费消息完成实际下单操作 实现步骤: 新增秒杀券时同步库存到Redis 用户请求时执行Lua脚本校验库存和用户资格 校验通过将订单信息放入阻塞队列 异步线程处

2303_78970037的博客 1018

黑马点评面试知识总结6-秒杀优化和redis消息队列

现在我们再把思路拉回到优惠卷秒杀,我们已经阐述了单机环境下和集群环境下的优惠卷秒杀,并且学会了使用redission这个终极大招,现在对秒杀业务代码进行优化。秒杀业务如下所示:查询优惠卷 -> 判断秒杀库存是否足够 -> 查询订单 -> 一人一单?-> 扣减库存 -> 创建订单这六部操作中,有很多部分需要查询数据库,比如查询优惠卷,查询用户订单,扣减库存和创建订单。而且是串行执行的,这就会导致进程执行很慢,那么我们应该如何优化呢?

qq_55198752的博客 1352
上一篇: 【黑马点评】(二)缓存
下一篇: 【Java八股文总结 — 包学会】(一)java基础
夜斗小神社
博客等级 码龄7年 3718粉丝 67原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值