Redis——某马点评day03——part2:秒杀业务异步优化

异步秒杀思路

原本的流程是如下所示,必须从开始到创建订单成功才会返回响应。就像饭店里面从下单到上菜都是一个人在服务,就导致服务员利用率很低,后一个顾客要等到前一个顾客上完菜才可以下单。

最简单的优化就是加员工,一次性就可以服务两个顾客。但是更好的优化是,只让一个服务员去记录下单信息,然后让后厨根据下单依次上菜即可。后面的顾客就可以不用等那么久了。

这个业务场景分为两个部分,对秒杀资格的判断和减库存下单,一个是查数据库,一个是改数据库,速度差异很大,所以这里可以将两个部分分给两个线程去执行。主线程判断购买资格,副线程负责减库存下单。 

 然后针对要查询数据库的操作也可以优化,将数据存在redis,判断有秒杀资格之后直接返回成功信息给用户,然后后续操作根据消息队列里面的消息进行异步执行。

将优惠券信息先存在redis里面,到时候下单先操作redis,再去操作mysql.然后用一个set去存储所有下过单的用户的id,防止重复下单。

基于Redis完成秒杀资格判断

1.保存优惠券信息到Redis

@Service
public class VoucherServiceImpl extends ServiceImpl<VoucherMapper, Voucher> implements IVoucherService {@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryVoucherOfShop(Long shopId) {// 查询优惠券信息...// 返回结果...}@Override@Transactionalpublic void addSeckillVoucher(Voucher voucher) {// 保存优惠券...// 保存秒杀信息...//保存秒杀库存到RedisstringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY+voucher.getId(),voucher.getStock().toString());}
}

 2.基于Lua脚本判断是否下单成功

-- 1.参数列表
-- 1.1 优惠券id
local voucherId=ARGV[1]
-- 1.2 用户id
local userId=ARGV[2]-- 2.数据key
-- 2.1库存key
local stockKey='seckill:stock:'..voucherId
-- 2.2.订单key
local orderKey='seckill:order:'..voucherId-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if(tonumber(redis.call('get',stockKey))<= 0)  then--获取到字符串转成数值类型-- 3.2. 库存不足 返回1return 1
end
-- 3.2 判断用户是否下单 SISMEMBER orderKey userId
if(redis.call('sismember',orderKey,userId)==1) then--3.3存在,说明是重复下单,返回2return 2
end-- 3.4.扣减库存 incrby stockKey -1
redis.call('incrby',stockKey,-1)
-- 3.5.下单(保存用户) sadd orderKey userId
redis.call('add',orderKey,userId)

改造秒杀下单资格判断业务

    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;static{SECKILL_SCRIPT=new DefaultRedisScript<>();SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));SECKILL_SCRIPT.setResultType(Long.class);}@Overridepublic Result seckillVoucher(Long voucherId) {//获取用户Long userId = UserHolder.getUser().getId();//1.执行Lua脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString());int r = result.intValue();//2.判断结果是否为0if(r!=0){//2.1.不为0,代表没有购买资格return Result.fail(r==1?"库存不足":"不能重复下单");}//2.2为0,有购买资格,把下单信息保存到阻塞队列long orderId = redisIdWorker.nextId("order");//TODO 保存阻塞队列//3.返回订单idreturn Result.ok(orderId);}

基于阻塞队列实现秒杀异步下单 

3. 封装优惠券id和用户id进阻塞队列4.获取阻塞队列消息,实现异步下单

public interface IVoucherOrderService extends IService<VoucherOrder> {Result seckillVoucher(Long voucherId);void createVoucherOrder(VoucherOrder voucherOrder);}
@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate RedisIdWorker redisIdWorker;@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Resourceprivate RedissonClient redissonClient;private static final DefaultRedisScript<Long> SECKILL_SCRIPT;static{SECKILL_SCRIPT=new DefaultRedisScript<>();SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));SECKILL_SCRIPT.setResultType(Long.class);}private BlockingQueue<VoucherOrder>orderTasks=new ArrayBlockingQueue<>(1024*1024);private static final ExecutorService  SECKILL_ORDER_EXECUTOR= Executors.newSingleThreadExecutor();@PostConstruct //当前类初始化完毕时就执行private void init(){SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}private class VoucherOrderHandler implements Runnable{@Overridepublic void run() {while(true){try {//1.获取队列中的订单信息VoucherOrder voucherOrder = orderTasks.take();//2.创建订单handleVoucherOrder(voucherOrder);} catch (InterruptedException e) {log.error("处理订单异常",e);}}}}//处理订单private void handleVoucherOrder(VoucherOrder voucherOrder) {//1. 获取用户idLong userId = voucherOrder.getUserId();//2.创建锁对象RLock lock = redissonClient.getLock("lock:order:" + userId);//3.获取锁
//        boolean isLock = lock.trylock(1200);boolean isLock = lock.tryLock();//4.判断是否获取锁成功if(!isLock){//获取锁失败,返回报错log.error("不允许重复下单"); //理论上不会有问题,redis已经判断过了}try {//取到代理对象proxy.createVoucherOrder(voucherOrder);}finally {//释放锁lock.unlock();}}private IVoucherOrderService proxy;@Overridepublic Result seckillVoucher(Long voucherId) {//获取用户Long userId = UserHolder.getUser().getId();//1.执行Lua脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString());int r = result.intValue();//2.判断结果是否为0if(r!=0){//2.1.不为0,代表没有购买资格return Result.fail(r==1?"库存不足":"不能重复下单");}//2.2为0,有购买资格,把下单信息保存到阻塞队列//TODO 保存阻塞队列VoucherOrder voucherOrder = new VoucherOrder();//2.3订单Idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);//2.4用户IdvoucherOrder.setUserId(userId);//2.5代金券IdvoucherOrder.setVoucherId(voucherId);//2.6放入阻塞队列orderTasks.add(voucherOrder);//3.获取代理对象  为了让后序线程可以拿到代理对象,可以放在成员变量或者是voucherOrder里面proxy =(IVoucherOrderService) AopContext.currentProxy();//4.返回订单idreturn Result.ok(orderId);}@Override@Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {//5.一人一单Long userId = voucherOrder.getUserId();//5.1查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();//5.2判断是否存在if (count > 0) {//用户已经购买过了log.error("用户已经购买过一次"); //redis已经判断过了,这里几乎不会出错return ;}//6.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1") //set stock =stock - 1.eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0)//where id=? and stock > 0.update();if (!success) {log.error("库存不足"); //这里也几乎不会出错return ;}//7.创建订单//此处传了voucherOrder进来,就不用重新创建订单了save(voucherOrder);}
}

太强了,这个代码.

Redis消息队列

 基于List实现消息队列

 

 

 基于PubSub实现消息队列

 

Stream消息队列

单消费模式

 

 

消费者组模式

使用XACK命令移除已经确认的消息

 

 

基于Stream消息队列实现异步秒杀

 创建消息队列

XGROUP CREATE stream.orders  g1 0 MKSTREAM

修改Lua脚本

新增了一个订单id和3.6的操作,使用id作为orderId的key可以直接对应实体类中的属性。

-- 1.参数列表
-- 1.1 优惠券id
local voucherId=ARGV[1]
-- 1.2 用户id
local userId=ARGV[2]
-- 1.3 订单id
local OrderId=ARGV[3]-- 2.数据key
-- 2.1库存key
local stockKey='seckill:stock:'..voucherId
-- 2.2.订单key
local orderKey='seckill:order:'..voucherId-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if(tonumber(redis.call('get',stockKey))<= 0)  then--获取到字符串转成数值类型-- 3.2. 库存不足 返回1return 1
end
-- 3.2 判断用户是否下单 SISMEMBER orderKey userId
if(redis.call('sismember',orderKey,userId)==1) then--3.3存在,说明是重复下单,返回2return 2
end-- 3.4.扣减库存 incrby stockKey -1
redis.call('incrby',stockKey,-1)
-- 3.5.下单(保存用户) sadd orderKey userId
redis.call('sadd',orderKey,userId)
-- 3.6 发送消息到队列中,XADD stream.orders * k1 v1 k2 v2
redis.call('xadd','stream.orders','*','userId',userId,'voucherId',voucherId,'id',OrderId)
return 0

改造秒杀业务逻辑

    private IVoucherOrderService proxy;@Overridepublic Result seckillVoucher(Long voucherId) {//获取用户Long userId = UserHolder.getUser().getId();//获取订单IDlong orderId = redisIdWorker.nextId("order");//1.执行Lua脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString(),String.valueOf(orderId));int r = result.intValue();//2.判断结果是否为0if(r!=0){//2.1.不为0,代表没有购买资格return Result.fail(r==1?"库存不足":"不能重复下单");}//3.获取代理对象  为了让后序线程可以拿到代理对象,可以放在成员变量或者是voucherOrder里面proxy =(IVoucherOrderService) AopContext.currentProxy();//4.返回订单idreturn Result.ok(orderId);}

开启线程任务获取消息队列的消息

    @PostConstruct //当前类初始化完毕时就执行private void init(){SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}private class VoucherOrderHandler implements Runnable{String queueName="stream.orders";@Overridepublic void run() {while(true){try {//1.获取消息队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.order >List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create(queueName, ReadOffset.lastConsumed()));//2.判断消息获取是否成功if(list==null|| list.isEmpty()) {//2.1如果获取失败,说明没有消息,继续下一次循环continue;}//3.解析消息中的订单信息MapRecord<String, Object, Object> record = list.get(0);Map<Object, Object> values = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);//4.如果获取成功,可以下单handleVoucherOrder(voucherOrder);//5.ACK确认 SACK stream.orders g1 idstringRedisTemplate.opsForStream().acknowledge(queueName,"g1",record.getId());} catch (Exception e) {log.error("处理订单异常",e);handlePendingList();}}}private void handlePendingList() {while(true){try {//1.获取pending-list中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 STREAMS stream.order 0List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create(queueName, ReadOffset.from("0")));//2.判断消息获取是否成功if(list==null|| list.isEmpty()) {//如果获取失败,说明pending-list没有异常消息,结束循环break;}//3.解析消息中的订单信息MapRecord<String, Object, Object> record = list.get(0);Map<Object, Object> values = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);//4.如果获取成功,可以下单handleVoucherOrder(voucherOrder);//5.ACK确认 SACK stream.orders g1 idstringRedisTemplate.opsForStream().acknowledge(queueName,"g1",record.getId());} catch (Exception e) {log.error("处理订单异常",e);try {Thread.sleep(50);} catch (InterruptedException ex) {throw new RuntimeException(ex);}//休眠一会儿后进入下一次循环}}}}

 秒杀业务最终代码

@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate RedisIdWorker redisIdWorker;@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Resourceprivate RedissonClient redissonClient;private static final DefaultRedisScript<Long> SECKILL_SCRIPT;static{SECKILL_SCRIPT=new DefaultRedisScript<>();SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));SECKILL_SCRIPT.setResultType(Long.class);}private BlockingQueue<VoucherOrder>orderTasks=new ArrayBlockingQueue<>(1024*1024);private static final ExecutorService  SECKILL_ORDER_EXECUTOR= Executors.newSingleThreadExecutor();@PostConstruct //当前类初始化完毕时就执行private void init(){SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}private class VoucherOrderHandler implements Runnable{String queueName="stream.orders";@Overridepublic void run() {while(true){try {//1.获取消息队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.order >List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create(queueName, ReadOffset.lastConsumed()));//2.判断消息获取是否成功if(list==null|| list.isEmpty()) {//2.1如果获取失败,说明没有消息,继续下一次循环continue;}//3.解析消息中的订单信息MapRecord<String, Object, Object> record = list.get(0);Map<Object, Object> values = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);//4.如果获取成功,可以下单handleVoucherOrder(voucherOrder);//5.ACK确认 SACK stream.orders g1 idstringRedisTemplate.opsForStream().acknowledge(queueName,"g1",record.getId());} catch (Exception e) {log.error("处理订单异常",e);handlePendingList();}}}private void handlePendingList() {while(true){try {//1.获取pending-list中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 STREAMS stream.order 0List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create(queueName, ReadOffset.from("0")));//2.判断消息获取是否成功if(list==null|| list.isEmpty()) {//如果获取失败,说明pending-list没有异常消息,结束循环break;}//3.解析消息中的订单信息MapRecord<String, Object, Object> record = list.get(0);Map<Object, Object> values = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);//4.如果获取成功,可以下单handleVoucherOrder(voucherOrder);//5.ACK确认 SACK stream.orders g1 idstringRedisTemplate.opsForStream().acknowledge(queueName,"g1",record.getId());} catch (Exception e) {log.error("处理订单异常",e);try {Thread.sleep(50);} catch (InterruptedException ex) {throw new RuntimeException(ex);}//休眠一会儿后进入下一次循环}}}}//阻塞队列的写法/*private static final ExecutorService  SECKILL_ORDER_EXECUTOR= Executors.newSingleThreadExecutor();@PostConstruct //当前类初始化完毕时就执行private void init(){SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}private class VoucherOrderHandler implements Runnable{@Overridepublic void run() {while(true){try {//1.获取队列中的订单信息VoucherOrder voucherOrder = orderTasks.take();//2.创建订单handleVoucherOrder(voucherOrder);} catch (InterruptedException e) {log.error("处理订单异常",e);}}}}*///处理订单private void handleVoucherOrder(VoucherOrder voucherOrder) {//1. 获取用户idLong userId = voucherOrder.getUserId();//2.创建锁对象RLock lock = redissonClient.getLock("lock:order:" + userId);//3.获取锁
//        boolean isLock = lock.trylock(1200);boolean isLock = lock.tryLock();//4.判断是否获取锁成功if(!isLock){//获取锁失败,返回报错log.error("不允许重复下单"); //理论上不会有问题,redis已经判断过了}try {//取到代理对象proxy.createVoucherOrder(voucherOrder);}finally {//释放锁lock.unlock();}}private IVoucherOrderService proxy;@Overridepublic Result seckillVoucher(Long voucherId) {//获取用户Long userId = UserHolder.getUser().getId();//获取订单IDlong orderId = redisIdWorker.nextId("order");//1.执行Lua脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString(),String.valueOf(orderId));int r = result.intValue();//2.判断结果是否为0if(r!=0){//2.1.不为0,代表没有购买资格return Result.fail(r==1?"库存不足":"不能重复下单");}//3.获取代理对象  为了让后序线程可以拿到代理对象,可以放在成员变量或者是voucherOrder里面proxy =(IVoucherOrderService) AopContext.currentProxy();//4.返回订单idreturn Result.ok(orderId);}//阻塞队列的写法/*@Overridepublic Result seckillVoucher(Long voucherId) {//获取用户Long userId = UserHolder.getUser().getId();//1.执行Lua脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString());int r = result.intValue();//2.判断结果是否为0if(r!=0){//2.1.不为0,代表没有购买资格return Result.fail(r==1?"库存不足":"不能重复下单");}//2.2为0,有购买资格,把下单信息保存到阻塞队列// 保存阻塞队列VoucherOrder voucherOrder = new VoucherOrder();//2.3订单Idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);//2.4用户IdvoucherOrder.setUserId(userId);//2.5代金券IdvoucherOrder.setVoucherId(voucherId);//2.6放入阻塞队列orderTasks.add(voucherOrder);//3.获取代理对象  为了让后序线程可以拿到代理对象,可以放在成员变量或者是voucherOrder里面proxy =(IVoucherOrderService) AopContext.currentProxy();//4.返回订单idreturn Result.ok(orderId);}*///不使用异步的写法??/*@Overridepublic 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.一人一单Long userId = UserHolder.getUser().getId();//创建锁对象
//        SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);RLock lock = redissonClient.getLock("lock:order:" + userId);//获取锁
//        boolean isLock = lock.trylock(1200);boolean isLock = lock.tryLock();//判断是否获取锁成功if(!isLock){//获取锁失败,返回报错return Result.fail("不允许重复下单");}try {//取到了当前代理对象IVoucherOrderService proxy =(IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}finally {//释放锁lock.unlock();}}*/@Override@Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {//5.一人一单Long userId = voucherOrder.getUserId();//5.1查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();//5.2判断是否存在if (count > 0) {//用户已经购买过了log.error("用户已经购买过一次"); //redis已经判断过了,这里几乎不会出错return ;}//6.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1") //set stock =stock - 1.eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0)//where id=? and stock > 0.update();if (!success) {log.error("库存不足"); //这里也几乎不会出错return ;}//7.创建订单//此处传了voucherOrder进来,就不用重新创建订单了save(voucherOrder);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/202710.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

6.1810: Operating System Engineering 2023 <Lab3: page tables>

一、本节任务 实验环境&#xff1a; 二、要点 如何防止程序破坏内核或其他进程空间&#xff1f;隔离地址空间&#xff0c;进程只能读写自己的内存空间。 在保证隔离的同时&#xff0c;如何将多个地址空间复用到一个物理内存上&#xff1f;虚拟内存/页表。操作系统通过页表来为…

DDSP-SVC-3.0完全指南:一步步教你用AI声音开启音乐之旅

本教程教你怎么使用工具训练数据集推理出你想要转换的声音音频&#xff0c;并且教你处理剪辑伴奏和训练后的音频合并一起&#xff0c;快来试试看把&#xff01; 1.使用的工具 要想训练ai声音&#xff0c;首先需要有各种工具&#xff0c;还需要我们提供你需要训练的声音&#…

Avalonia中如何将View事件映射到ViewModel层

前言 前面的文章里面我们有介绍在Wpf中如何在View层将事件映射到ViewModel层的文章&#xff0c;传送门&#xff0c;既然WPF和Avalonia是两套不同的前端框架&#xff0c;那么WPF里面实现模式肯定在这边就用不了&#xff0c;本篇我们将分享一下如何在Avalonia前端框架下面将事件…

陀螺仪LSM6DSV16X与AI集成(2)----姿态解算

陀螺仪LSM6DSV16X与AI集成.2--姿态解算 概述视频教学样品申请完整代码下载欧拉角万向节死锁四元数法姿态解算双环PI控制器偏航角陀螺仪解析代码上位机通讯加速度演示陀螺仪工作方式主程序演示 概述 LSM6DSV16X包含三轴陀螺仪与三轴加速度计。 姿态有多种数学表示方式&#xff…

多人聊天室

多人聊天包 由于要先创建服务面板&#xff0c;接收客户端连接的信息&#xff0c;此代码使用顺序为先启动服务端&#xff0c;在启动客户端&#xff0c;服务端不用关&#xff0c;不然会报错。多运行几次客户端&#xff0c;实现单人聊天 1.创建服务面板 package yiduiy;import j…

【计算机二级MS Office】word(上)

这里写目录标题 文件选项卡保存和另存为属性检查文档 开始选项卡字体更改字体和字号设置中文和英文为两种不同字体的快捷方式介绍其余图标文本效果突出颜色如何挑选字体颜色字符底纹带圈字符字体对话框&#xff08;隐藏&#xff09; 段落 插入选项卡设计选项卡布局选项卡引用选…

【头歌系统数据库实验】实验6 SQL的多表查询-2

目录 第1关&#xff1a;查询每个选手的信息及其提交的解答信息&#xff0c;没做题的选手不显示 第2关&#xff1a;查询做了1001题且耗时大于500&#xff08;time&#xff09;的选手信息 第3关&#xff1a;查询所有选手信息及其提交的解答信息&#xff0c;没做题的选手也要显…

力扣每日一题:2646. 最小化旅行的价格总和(2023-12-06)

力扣每日一题 题目&#xff1a;2646. 最小化旅行的价格总和 日期&#xff1a;2023-12-06 用时&#xff1a;30 m 14 s 时间&#xff1a;8ms 内存&#xff1a;42.98MB 思路&#xff1a;先统计旅行中每个节点路过的次数&#xff08;dfs方法&#xff09;&#xff0c;再计算减半后的…

项目中使用之Maven BOM

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 工具教程 ✨特色专栏&#xff1a; MyS…

Linux文件部分知识

目录 认识inode 如何理解创建一个空文件&#xff1f; 如何理解对文件写入信息&#xff1f; 如何理解删除一个文件&#xff1f; 为什么拷贝文件的时候很慢&#xff0c;而删除文件的时候很快&#xff1f; 如何理解目录 ​编辑 文件的三个时间 ​编辑 Access&#xff1a; …

Linux系统调试课:网络性能工具总结

文章目录 一、网络性能指标二、netstat三、route四、iptables沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章一起了解下网络性能工具。 一、网络性能指标 从网络性能指标出发,你更容易把性能工具同系统工作原理关联起来,对性能问题有宏观的认识和把握。这样,…

【LeetCode刷题-链表】--92.反转链表II

92.反转链表II /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; }* }*/ cla…

前端JavaScript入门-day08-正则表达式

目录 介绍 语法 元字符 边界符 量词 字符类&#xff1a; 修饰符 介绍 正则表达式&#xff08;Regular Expression&#xff09;是用于匹配字符串中字符组合的模式。在 JavaScript中&#xff0c;正则表达式也是对象&#xff0c;通常用来查找、替换那些符合正则表达式的…

书-用数组存储高于60低于70的人单独存起来

#include<stdio.h> # define N 10 //书-用数组存储高于60低于70的人单独存起来 int main(){float s[N]{68.2,62.3,63.4,34.5,45.6,56.7,67.8,78.9,89.0,100};int i;float diyu[100];int j0;for(i0;i<N;i){if(s[i]>60 && s[i]<70)diyu[j]s[i];//这里的范…

【数据结构】——二叉树特点

前言&#xff1a;我们前面已经了解了二叉树的一些概念&#xff0c;那么我们今天就来了解下二叉树的遍历实现和一些性质。 二叉树的遍历方式有三种&#xff1a;前序&#xff0c;中序&#xff0c;后序。 前序&#xff1a;先根节点&#xff0c;再左子树&#xff0c;最后右子树。 中…

绘制6层及以上PCB板,需要明白PCB板的结构和叠层

PCB主要由PP半固化片和core芯板压合而成&#xff0c;其中core芯板两面都有铜箔&#xff0c;是PCB板的导电介质&#xff1b;PP半固化片是绝缘材料&#xff0c;用于芯板的粘合。 在PP半固化片被层压后&#xff0c;其环氧树脂被挤压开来&#xff0c;将core芯板粘合在一起。 PCB的叠…

Python代码将txt里面多行json字符串转成excel文件

python 代码 将txt里面的多行json字符串转成excel history.txt文件json代码样例 Json转换Excel代码 import json import pandas as pddef json_out(file_path,excel_path):all_list[]with open(file_path, "r", encodingutf-8) as f:for line in f:all_list.append…

Tuxera NTFS2024安装包下载教程

在听到小凡的电话说“Tuxera NTFS for Mac软件安装失败&#xff0c;怎么办”的时候&#xff0c;小编心里真像有一万头草泥马在奔腾——苹果软件还能安装失败&#xff01;&#xff1f; 挥手把一万头草泥马赶走&#xff0c;脑补着苹果软件的安装&#xff0c;小编对着电话吼道&am…

配置主机与外网时间服务器同步时间

目录 NTP服务简介 时间管理命令 第一步&#xff1a;更改当前主机所在地的时间 方法一&#xff1a;使用tzselect命令查询需要的时区 1、使用tzselect命令查询需要的时区 2、添加变量到 ~/.bash_profile 文件中&#xff0c;即追加类似的内容&#xff1a; 3、重新连接一个…

LLM大语言模型(一):ChatGLM3-6B本地部署

目录 前言 本机环境 ChatGLM3代码库下载 模型文件下载 修改为从本地模型文件启动 启动模型网页版对话demo 超参数设置 GPU资源使用情况 &#xff08;网页对话非常流畅&#xff09; 前言 LLM大语言模型工程化&#xff0c;在本地搭建一套开源的LLM&#xff0c;方便后续的…