#Redis黑马点评#(四)优惠券秒杀
目录
一 生成全局id
二 添加优惠券
三 实现秒杀下单
方案一(会出现超卖问题)
方案二(解决了超卖但是错误率较高)
方案三(解决了错误率较高和超卖但是会出现一人抢多张问题)
方案四(解决一人抢多张问题“非分布式情况”)
方案五(实现一人一单,跨JVM锁的实现“分布式情况”)
最终解决方案:Lua脚本(解决原子性)
一 生成全局id
策略:
- 每天一个Key值,方便统计订单量
- ID构造为:时间戳+计数器
代码实现:
package com.hmdp.utils;import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;@Component
public class RedisIdWorker {// 序列号位数private static final int COUNT_BITS = 32;private final StringRedisTemplate stringRedisTemplate;public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}/*** 开始时间戳*/private static final long BEGIN_TIMESTAMP = 1640995200;/*** 序列号的位数*/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"));// 2.2自增长Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);// 3.拼接并返回return timestamp << COUNT_BITS | count;}
}
核心思路:
Redis的角色:分布式计数器->其不存储订单号,仅提供原子递增的序列号。
订单号的本质:由时间戳(高32位)和序列号位(低32位)运算生成的Long型数值。
1 按照一定的规则来设置key键值。
String key = "icr:" + keyPrefix + ":" + date;
- icr:代表自增(区分键值种类)
- keyPrefix:业务标识(区分业务种类)
- date:当前日期(实现按天区分避免单个键值过大)
2 向redis数据库当中添加数据。
Long count = stringRedisTemplate.opsForValue().increment(key);
Redis的INCR是一个原子操作,对指定键的值执行加1。
若键不存在,就先初始化为0,后续再次执行的时候就会加1,可在Redis当中统计数量,
同时,订单号则作为返回的值。
二 添加优惠券
策略:
存在两个表,一个是普通优惠券的表tb_voucher,一个是秒杀优惠券的表tb_voucher_order。
但是秒杀优惠券的表是建立在普通优惠券的基础之上的。有些共有属性存储在普通优惠表(里面同时也存储一个type类型0/1用于区分是否是优惠券),在秒杀优惠券的表当中存储券开启的时间,结束时间,张数这些核心参数。
代码:
controller控制层的业务实现
/*** 新增普通券* @param voucher 优惠券信息* @return 优惠券id*/@PostMappingpublic Result addVoucher(@RequestBody Voucher voucher) {voucherService.save(voucher);return Result.ok(voucher.getId());}
下面是实体类的形式(一些特有属性不做强制要求)
package com.hmdp.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;import java.io.Serializable;
import java.time.LocalDateTime;/*** <p>* * </p>** @author 虎哥* @since 2021-12-22*/
@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;}
三 实现秒杀下单
下单之前首先需要判断两点:
- 时间是否开始,如果尚未开始或者已经结束则无法下单。
- 库存是否充足,不足则无法下单。
方案一(会出现超卖问题)
VoucherOrderController控制层
@PostMapping("seckill/{id}")public Result seckillVoucher(@PathVariable("id") Long voucherId) {return voucherOrderService.seckillVoucher(voucherId);}
Service业务层(业务层接口)
/*** 秒杀优惠券* @param voucherId 优惠券id* @return 结果*/Result seckillVoucher(Long voucherId);
Service业务层(业务层实现类)
/*** 秒杀优惠券** @param voucherId 优惠券id* @return*/@Transactional@Overridepublic Result seckillVoucher(Long voucherId) {// 1查询优惠券SeckillVoucher byId = seckillVoucherService.getById(voucherId);// 2判断时间范围if (byId.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("秒杀尚未开始");}if (byId.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("秒杀已经结束");}// 3判断库存if (byId.getStock() < 1) {return Result.fail("库存不足");}// 4扣减库存boolean update = seckillVoucherService.update().set("stock", byId.getStock() - 1).eq("voucher_id", voucherId).update();if (!update) {return Result.fail("库存不足");}// 5生成订单VoucherOrder voucherOrder = new VoucherOrder();// 6订单idLong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 7用户idvoucherOrder.setUserId(1L);// 8优惠券idvoucherOrder.setVoucherId(voucherId);// 9保存订单save(voucherOrder);// 10返回订单idreturn Result.ok(orderId);}
超卖问题的解决
乐观锁的关键是判断之前查询得到的数据是否有被修改过,常见的方式有两种:
1 版本号法
2 CAS
代码实现:(CAS)
方案二(解决了超卖但是错误率较高)
乐观锁的 WHERE stock=原库存-1
条件在高并发下导致大量冲突。
核心修改:在修改之前判断库存与刚开始查询到的数据是否相同,但是会出现错误率较高,导致很多人提前抢到但是别人修改了,再次校验stock时,出现错误,就无法抢购成功。(出现有前一百个人抢但是他们并没有得到这些优惠券)
boolean update = seckillVoucherService.update().set("stock", byId.getStock() - 1)// set stock=stock-1(更新操作的条件).eq("voucher_id", voucherId)// where voucher_id=? and stock=?(判断秒杀券的id).eq("stock", byId.getStock())// where stock=?(判断票数是否与刚开始查询到的相同).update();
方案三(解决了错误率较高和超卖但是会出现一人抢多张问题)
核心修改:在修改时判断库存是否还是>0即可(出现类似黄牛使用脚本同时发送请求将优惠券抢完,破坏了一人一单的规则)
boolean update = seckillVoucherService.update().setSql("stock=stock-1")// set stock=stock-1(更新操作的条件).eq("voucher_id", voucherId)// where voucher_id=? and stock=?(判断秒杀券的id).gt("stock", 0)// where stock>0(判断修改时券是否>0).update();
方案四(解决一人抢多张问题“非分布式情况”)
bug版(会出现一个用户开多个线程并发的查询操作,出现查询的都是0,导致都去抢购订单并抢购成功,导致一个用户购买多次)
//实现一人一单Long userId = UserHolder.getUser().getId();if (query().eq("user_id", userId).eq("voucher_id", voucherId).count() > 0) {return Result.fail("用户已经购买过");}
改进版
实现代理,将购买订单加上锁。(分布式情况就会出现错误)
- synchronized是基于JVM的内存锁,确保同意用户ID的请求在单机内串行执行。
- userId.toString().intern()保证相同用户ID的字符串对象唯一避免锁失效。
- AopContext.currentProxy()确保@Transactional事务注解生效避免事务失效问题。
Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()){IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}
方案五(实现一人一单,跨JVM锁的实现“分布式情况”)
实现原理
满足分布式系统或集群模式下多线程可见并且互斥/
- 多进程可见
- 互斥
- 高可用
- 高性能
- 安全性
分布式锁的实现
分布式锁的核心是实现多进程之间互斥,而满足这一点的方式有很多,常见的有三种
我们需要实现的就是一个用户名id只能抢购一个优惠券的目的。
我们先定义一个锁工具接口
package com.hmdp.utils;import org.springframework.stereotype.Component;public interface ILock {/*** 尝试获取锁** @param timeoutSec 锁持有的超时时间,过期自动释放* @return true代表获取锁成功,false代表获取锁失败*/boolean tryLock(Long timeoutSec);/*** 释放锁*/void unLock();
}
再在实现类当中完善相关方法
我们是根据lock前缀以及用户名来写入锁的名称,以到达区分效果,不同的JVM当中的线程读取时达到互斥效果。
package com.hmdp.utils;import lombok.Data;
import org.springframework.data.redis.core.StringRedisTemplate;import java.util.concurrent.TimeUnit;@Data
public class SimpleRedisLock implements ILock {private StringRedisTemplate stringRedisTemplate;private String name;private static final String KEY_PREFIX = "lock:";public SimpleRedisLock(String s, StringRedisTemplate stringRedisTemplate) {this.name = s;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean tryLock(Long timeoutSec) {//获取线程标识和锁String key = KEY_PREFIX + name;long threadId = Thread.currentThread().getId();String value = threadId + "";//获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}@Overridepublic void unLock() {//释放锁stringRedisTemplate.delete(KEY_PREFIX + name);}
}
存在的问题:
防止误删(加一个线程标识进行校验,设置特定的value值用于校验setnx是基于key的)
加一个判断是否是自己的锁(是自己的才删)
代码实现:
根据JVM的id-key值与当前线程的UUID线程标识-value进行区分获取当前线程的身份,解决线程误删操作。
package com.hmdp.utils;import lombok.Data;
import org.springframework.data.redis.core.StringRedisTemplate;import java.util.UUID;
import java.util.concurrent.TimeUnit;@Data
public class SimpleRedisLock implements ILock {private StringRedisTemplate stringRedisTemplate;private String name;private static final String KEY_PREFIX = "lock:";private static final String ID_PREFIX = UUID.randomUUID() + "-";public SimpleRedisLock(String s, StringRedisTemplate stringRedisTemplate) {this.name = s;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean tryLock(Long timeoutSec) {//获取线程标识和锁String key = KEY_PREFIX + name;//key值String value = ID_PREFIX + Thread.currentThread().getId();//value值//获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}@Overridepublic void unLock() {//获取线程标识String value = ID_PREFIX + Thread.currentThread().getId();//value值//获取锁中的标识String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);if (value.equals(id)) {stringRedisTemplate.delete(KEY_PREFIX + name);}}
}
存在的问题:
防止误删,如果在判断结束后出现了阻塞情况,导致时间达到了TTL时间,其他的线程进入锁依然会被误删,那被误删的线程就会没有锁,导致其他的线程进入抢券,引发线程并发问题)
这里的判断与释放分成了两部分,非原子性操作
get
(校验锁归属)和 delete
(释放锁)是独立操作,期间锁可能过期并被其他线程获取。
最终解决方案:Lua脚本(解决原子性)
Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言,他的基本语法可以参考:Lua 教程 | 菜鸟教程
基于Redis的分布式锁
释放锁的业务流程是这样的:
- 1 获取锁中的线程标识。
- 2 判断是否与指定的标识一致。
- 3 如果一致则释放锁。
- 4 如果不一致则什么都不做。
代码实现:
Lua脚本的编写
--比较线程标识与所种的标识是否一致
if (redis.call('get', KEYS[1]) == ARGV[1]) then--释放锁return redis.call('del', KEYS[1])
end
return 0
调用代码的改进:
package com.hmdp.utils;import lombok.Data;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;@Data
public class SimpleRedisLock implements ILock {private static final String KEY_PREFIX = "lock:";private StringRedisTemplate stringRedisTemplate;private String name;private final String ID_PREFIX;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;this.ID_PREFIX = UUID.randomUUID() + "-";}// 释放锁的脚本(static初始化避免多次读取,这样可以优化性能)private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;static {UNLOCK_SCRIPT = new DefaultRedisScript<>();UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));UNLOCK_SCRIPT.setResultType(Long.class);}@Overridepublic boolean tryLock(Long timeoutSec) {//获取线程标识和锁String key = KEY_PREFIX + name;//key值String value = ID_PREFIX + Thread.currentThread().getId();//value值//获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}@Overridepublic void unLock() {//调用Lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT, Collections.singletonList(KEY_PREFIX + name), ID_PREFIX + Thread.currentThread().getId());}// @Override
// public void unLock() {
// //获取线程标识
// String value = ID_PREFIX + Thread.currentThread().getId();//value值
// //获取锁中的标识
// String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
// if (value.equals(id)) {
// stringRedisTemplate.delete(KEY_PREFIX + name);
// }
// }
}
Service接口的代码实现展示
package com.hmdp.service.impl;import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.SimpleRedisLock;
import com.hmdp.utils.UserHolder;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.time.LocalDateTime;@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Autowiredprivate ISeckillVoucherService seckillVoucherService;@Autowiredprivate RedisIdWorker redisIdWorker;@Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** 秒杀优惠券** @param voucherId 优惠券id* @return*/@Overridepublic Result seckillVoucher(Long voucherId) {// 1查询优惠券SeckillVoucher byId = seckillVoucherService.getById(voucherId);// 2判断时间范围if (byId.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("秒杀尚未开始");}if (byId.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("秒杀已经结束");}// 3判断库存if (byId.getStock() < 1) {return Result.fail("库存不足");}// 4实现一人一单Long userId = UserHolder.getUser().getId();//创建锁对象SimpleRedisLock simpleRedisLock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);//获取锁boolean isLock = simpleRedisLock.tryLock(1000L);//判断是否成功if (!isLock) {//获取锁失败return Result.fail("请勿重复下单");}try {//获取锁成功,开始创建订单IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);} finally {//释放锁simpleRedisLock.unLock();}}@Transactionalpublic Result createVoucherOrder(Long voucherId) {//实现一人一单Long userId = UserHolder.getUser().getId();if (query().eq("user_id", userId).eq("voucher_id", voucherId).count() > 0) {return Result.fail("用户已经购买过");}// 4扣减库存boolean update = seckillVoucherService.update().setSql("stock=stock-1")// set stock=stock-1(更新操作的条件).eq("voucher_id", voucherId)// where voucher_id=? and stock=?(判断秒杀券的id).gt("stock", 0)// where stock>0(判断修改时券是否>0).update();if (!update) {return Result.fail("库存不足");}// 5生成订单VoucherOrder voucherOrder = new VoucherOrder();// 6订单idLong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 7用户idvoucherOrder.setUserId(userId);// 8优惠券idvoucherOrder.setVoucherId(voucherId);// 9保存订单save(voucherOrder);// 10返回订单idreturn Result.ok(orderId);}
}
相关文章:
#Redis黑马点评#(四)优惠券秒杀
目录 一 生成全局id 二 添加优惠券 三 实现秒杀下单 方案一(会出现超卖问题) 方案二(解决了超卖但是错误率较高) 方案三(解决了错误率较高和超卖但是会出现一人抢多张问题) 方案四(解决一人抢多张问题“非分布式…...
https,http1,http2,http3的一些知识
温故知新,突然有人问我项目中🤔有使用http3么,一下不知从何说起,就有了这篇文章的出现。 https加密传输,ssltls https 验证身份 提供加密,混合加密 : 对称加密 非对称加密 原理:…...
《设计数据密集型应用》——阅读小记
设计数据密集型应用 这本书非常推荐看英语版,如果考过了CET-6就可以很轻松的阅读这本书。 当前计算机软件已经不是单体的时代了,分布式系统,微服务现在是服务端开发的主流,如果没有读过这本书,则强力建议读这本书。 …...
SpringCloud之Gateway基础认识-服务网关
0、Gateway基本知识 Gateway 是在 Spring 生态系统之上构建的 API 网关服务,基于 Spring ,Spring Boot 和 Project Reactor 等技术。 Gateway 旨在提供一种简单而有效的方式来对 API 进行路由,以及提供一些强大的过滤器功能,例如…...
MySQL 从入门到精通(三):日志管理详解 —— 从排错到恢复的核心利器
在 MySQL 数据库的日常运维中,日志是定位问题、优化性能、数据恢复的核心工具。无论是排查服务器启动异常,还是分析慢查询瓶颈,亦或是通过二进制日志恢复误删数据,日志都扮演着 “数据库黑匣子” 的角色。本文将深入解析 MySQL 的…...
单脉冲前视成像多目标分辨算法——论文阅读
单脉冲前视成像多目标分辨算法 1. 论文的研究目标及实际意义1.1 研究目标1.2 实际问题与产业意义2. 论文的创新方法及公式解析2.1 核心思路2.2 关键公式与模型2.2.1 单脉冲雷达信号模型2.2.2 匹配滤波输出模型2.2.3 多目标联合观测模型2.2.4 对数似然函数与优化2.2.5 MDL准则目…...
SpringBoot项目容器化进行部署,meven的docker插件远程构建docker镜像
需求:将Spring Boot项目使用容器化进行部署 前提 默认其他环境,如mysql,redis等已经通过docker部署完毕, 这里只讨论,如何制作springboot项目的镜像 要将Spring Boot项目使用docker容器进行部署,就需要将Spring Boot项目构建成一个docker镜像 一、手动…...
【金仓数据库征文】政府项目数据库迁移:从MySQL 5.7到KingbaseES的蜕变之路
摘要:本文详细阐述了政府项目中将 MySQL 5.7 数据库迁移至 KingbaseES 的全过程,涵盖迁移前的环境评估、数据梳理和工具准备,迁移实战中的数据源与目标库连接配置、迁移任务详细设定、执行迁移与过程监控,以及迁移后的质量验证、系…...
C++GO语言微服务和服务发现②
01 创建go-micro项目-查看生成的 proto文件 02 创建go-micro项目-查看生成的main文件和handler ## 创建 micro 服务 命令:micro new --type srv test66 框架默认自带服务发现:mdns。 使用consul服务发现: 1. 初始consul服务发现&…...
手机银行怎么打印流水账单(已解决)
一、中国银行 登录中国银行手机银行APP。 在首页点击“更多”,向左滑动找到并点击“助手”。 在助手页面选择“交易流水打印”。 点击“立即申请”,选择需要打印的账户和时间段。 输入接收流水账单的电子邮箱地址。 提交申请后,在“申请…...
单片机-STM32部分:10-2、逻辑分析仪
飞书文档https://x509p6c8to.feishu.cn/wiki/VrdkwVzOnifH8xktu3Bcuc4Enie 安装包如下:根据自己的系统选择,目前这个工具只有window版本哦 安装方法比较简单,都按默认下一步即可,注意不要安装到中文路径哦。 其余部分参考飞书文档…...
Scala与Go的异同教程
当瑞士军刀遇到电锯:Scala vs Go的相爱相杀之旅 各位准备秃头的程序猿们(放心,用Go和Scala不会加重你的发际线问题),今天我们来聊聊编程界的"冰与火之歌"——Scala和Go的异同。准备好瓜子饮料,我…...
【算法-哈希表】常见算法题的哈希表套路拆解
算法相关知识点可以通过点击以下链接进行学习一起加油!双指针滑动窗口二分查找前缀和位运算模拟链表 在刷题的过程中,我们会频繁遇到一些“高频套路”——而哈希表正是其中最常用也最高效的工具之一。它能帮助我们在 O(1) 的时间复杂度内完成查找、插入与…...
前端取经路——现代API探索:沙僧的通灵法术
大家好,我是老十三,一名前端开发工程师。在现代Web开发中,各种强大的API就像沙僧的通灵法术,让我们的应用具备了超乎想象的能力。本文将带你探索从离线应用到实时通信,从多线程处理到3D渲染的九大现代Web API,让你的应用获得"通灵"般的超能力。 在前端取经的第…...
深入了解 ArkTS:HarmonyOS 开发的关键语言与应用实践
随着 HarmonyOS(鸿蒙操作系统)的推出,华为为开发者提供了一套全新的开发工具和编程语言,使得跨设备、跨平台的应用开发成为可能。在这些工具中,ArkTS(Ark TypeScript)作为一种专为 HarmonyOS 设…...
Flask 调试的时候进入main函数两次
在 Flask 开启 Debug 模式时,程序会因为自动重载(reloader)的机制而启动两个进程,导致if __name__ __main__底层的程序代码被执行两次。以下说明其原理与常见解法。 Flask Debug 模式下自动重载机制 Flask 使用的底层服务器 Wer…...
Git 时光机:修改Commit信息
前言 列位看官都知道,Git 的每一次 git commit,其中会包含作者(Author)和提交者(Committer)的姓名与邮箱。有时可能会因为配置错误、切换了开发环境,或者只是单纯的手滑,导致 commi…...
DAY 21 常见的降维算法
知识点回顾: LDA线性判别PCA主成分分析t-sne降维 还有一些其他的降维方式,也就是最重要的词向量的加工,我们未来再说 作业: 自由作业:探索下什么时候用到降维?降维的主要应用?或者让ai给你出题&…...
Docker使用小结
概念 镜像( Image ) :相当于一个 root 文件系统;镜像构建时,分层存储、层层构建;容器( Container ) :镜像是静态的定义,容器是镜像运行时的实体;…...
kubectl top 查询pod连接数
在 Kubernetes 中,kubectl top 命令默认仅支持查看 Pod 或节点的 CPU/内存资源使用情况,并不直接提供 TCP 连接数的统计功能。若要获取 Pod 的 TCP 连接数,需结合其他工具和方法。以下是具体实现方案: 1. 直接进入容器查看 TCP 连…...
Kubernetes生产实战(十七):负载均衡流量分发管理实战指南
在Kubernetes集群中,负载均衡是保障应用高可用、高性能的核心机制。本文将从生产环境视角,深入解析Kubernetes负载均衡的实现方式、最佳实践及常见问题解决方案。 一、Kubernetes负载均衡的三大核心组件 1)Service资源:集群内流…...
Git 分支指南
什么是 Git 分支? Git 分支是仓库内的独立开发线,你可以把它想象成一个单独的工作空间,在这里你可以进行修改,而不会影响主分支(或 默认分支)。分支允许开发者在不影响项目实际版本的情况下,开…...
自动泊车技术—相机模型
一、相机分类及特性 传感器类型深度感知原理有效工作范围环境适应性功耗水平典型成本区间数据丰富度单目相机运动视差/几何先验1m~∞光照敏感1-2W5−5−502D纹理中双目相机立体匹配 (SGM/SGBM算法)0.3m~20m纹理依赖3-5W50−50−3002D稀疏深度多摄像头系统多视角三角测量0.1m~5…...
程序代码篇---esp32视频流处理
文章目录 前言一、ESP32摄像头设置1.HTTP视频流(最常见)2.RTSP视频流3.MJPEG流 二、使用OpenCV读取视频流1. 读取HTTP视频流2. 读取RTSP视频流 三、使用requests库读取MJPEG流四、处理常见问题1. 连接不稳定或断流2. 提高视频流性能2.1降低分辨率2.2跳过…...
数据结构与算法分析实验12 实现二叉查找树
实现二叉查找树 1、二叉查找树介绍2.上机要求3.上机环境4.程序清单(写明运行结果及结果分析)4.1 程序清单4.1.1 头文件 TreeMap.h 内容如下:4.1.2 实现文件 TreeMap.cpp 文件内容如下:4.1.3 源文件 main.cpp 文件内容如下: 4.2 实现展效果示5…...
深入浅出之STL源码分析2_类模版
1.引言 我在上面的文章中讲解了vector的基本操作,然后提出了几个问题。 STL之vector基本操作-CSDN博客 1.刚才我提到了我的编译器版本是g 11.4.0,而我们要讲解的是STL(标准模板库),那么二者之间的关系是什么&#x…...
Docker、Docker-compose、K8s、Docker swarm之间的区别
1.Docker docker是一个运行于主流linux/windows系统上的应用容器引擎,通过docker中的镜像(image)可以在docker中构建一个独立的容器(container)来运行镜像对应的服务; 例如可以通过mysql镜像构建一个运行mysql的容器,既可以直接进入该容器命…...
【Linux】线程的同步与互斥
目录 1. 整体学习思维导图 2. 线程的互斥 2.1 互斥的概念 2.2 见一见数据不一致的情况 2.3 引入锁Mutex(互斥锁/互斥量) 2.3.1 接口认识 2.3.2 Mutex锁的理解 2.3.3 互斥量的封装 3. 线程同步 3.1 条件变量概念 3.2 引入条件变量Cond 3.2.1 接口认识 3.2.2 同步的…...
C++发起Https连接请求
需要下载安装openssl //stdafx.h #pragma once #include<iostream> #include <openssl/ssl.h> #include <openssl/err.h> #include <iostream> #include <string>#pragma comment(lib, "libssl.lib") #pragma comment(lib, "lib…...
Linux 内核链表宏的详细解释
🔧 Linux 内核链表结构概览 Linux 内核中的链表结构定义在头文件 <linux/list.h> 中。核心结构是: struct list_head {struct list_head *next, *prev; }; 它表示一个双向循环链表的节点。链表的所有操作都围绕这个结构体展开。 🧩 …...
[架构之美]Spring Boot集成MyBatis-Plus高效开发(十七)
[架构之美]Spring Boot集成MyBatis-Plus高效开发(十七) 摘要:本文通过图文代码实战,详细讲解Spring Boot整合MyBatis-Plus全流程,涵盖代码生成器、条件构造器、分页插件等核心功能,助你减少90%的SQL编写量…...
游戏引擎学习第270天:生成可行走的点
回顾并为今天的内容定下基调 今天的计划虽然还不完全确定,可能会做一些内存分析,也有可能暂时不做,因为目前并没有特别迫切的需求。最终我们会根据当下的状态随性决定,重点是持续推动项目的进展,无论是 memory 方面还…...
批量统计PDF页数,统计图像属性
软件介绍: 1、支持批量统计PDF、doc\docx、xls\xlsx页数 2、支持统计指定格式文件数量(不填格式就是全部) 3、支持统计JPG、JPEG、PNG图像属性 4、支持统计多页TIF页数、属性 5、支持统计PDF、JPG画幅 统计图像属性 「托马斯的文件助手」…...
QT Creator配置Kit
0、背景:qt5.12.12vs2022 记得先增加vs2017编译器 一、症状: 你是否有以下症状? 1、用qt新建的工程,用qmake,可惜能看见的只有一个pro文件? 2、安装QT Creator后,使用MSVC编译显示no c com…...
[架构之美]IntelliJ IDEA创建Maven项目全流程(十四)
[架构之美]IntelliJ IDEA创建Maven项目全流程(十四) 摘要:本文将通过图文结合的方式,详细讲解如何使用IntelliJ IDEA快速创建Maven项目,涵盖环境配置、项目初始化、依赖管理及常见问题解决方案。适用于Java开发新手及…...
SpringBoot学习(上) , SpringBoot项目的创建(IDEA2024版本)
目录 1. SpringBoot介绍 SpringBoot特点 2. SpringBoot入门 2.1 创建SpringBoot项目 Spring Initialize 第一步: 选择创建项目 第二步: 选择起步依赖 第三步: 查看启动类 2.2 springboot父项目 2.3 测试案例 2.3.1 数据库 2.3.2 生成代码 1. SpringBoot介绍 Spring B…...
《Python星球日记》 第51天:神经网络基础
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、引言:走进神经网络的世界二、神经元与激活函数1. 神经元&#x…...
MindSpore框架学习项目-ResNet药物分类-模型评估
目录 4.模型评估 4.1模型预测 4.1.1加载模型 4.1.2通过传入图片路径进行推理 单张图片推理代码解释 4.2图片推理 4.2.1构造可视化推理结果函数 可视化推理结果函数代码解释 4.2.2进行单张推理 参考内容: 昇思MindSpore | 全场景AI框架 | 昇思MindSpore社区…...
Visual Studio Code 前端项目开发规范合集【推荐插件】
文章目录 前言代码格式化工具(Prettier)1、下载 prettier 相关依赖:2、安装 Vscode 插件(Prettier):3、配置 Prettier(.prettierrc.cjs): 代码规范工具(ESLin…...
uniapp-商城-48-后台 分类数据添加修改弹窗bug
在第47章的操作中,涉及到分类的添加、删除和更新功能,但发现uni-popup组件存在bug。该组件的函数接口错误导致在小程序中出现以下问题:1. 点击修改肉类名称时,回调显示为空,并报错“setVal is not defined”࿰…...
OpenLayers 精确经过三个点的曲线绘制
OpenLayers 精确经过三个点的曲线绘制 根据您的需求,我将提供一个使用 OpenLayers 绘制精确经过三个指定点的曲线解决方案。对于三个点的情况,我们可以使用 二次贝塞尔曲线 或 三次样条插值,确保曲线精确通过所有控制点。 实现方案 下面是…...
uniapp小程序中实现无缝衔接滚动效果
组件滚动通知只能实现简单的滚动效果,不能实现滚动内容中的字进行不同颜色的更改,下面实现一个无缝衔接的滚动动画,可以根据自己的需要进行艺术化的更改需要滚动的内容,也可以自定义更改滚动速度。 <template><view cla…...
【Docker 新手入门指南】第四章:镜像加速
【Docker 新手入门指南】系列文章目录 【Docker 新手入门指南】第一章:前言【Docker 新手入门指南】第二章:架构概述【Docker 新手入门指南】第三章:快速安装【Docker 新手入门指南】第四章:镜像加速 文章目录 🚀【Doc…...
k8s删除pv和pvc后,vg存储没释放分析
原因是pv对应的lvm没删除 pv如下: local-068e2cac-22de-40f3-af90-efd151d043c8 100Gi RWO Retain Released sase-ops/alertmanager-kube-prometheus-stack-alertmanager-db-alertmanager-kube-prometheus-stack-alertmanager-0 …...
Ubuntu 22.04(WSL2)使用 Docker 安装 Zipkin 和 Skywalking
Ubuntu 22.04(WSL2)使用 Docker 安装 Zipkin 和 Skywalking 分布式追踪工具在现代微服务架构中至关重要,它们帮助开发者监控请求在多个服务之间的流动,识别性能瓶颈和潜在错误。本文将指导您在 Ubuntu 22.04(WSL2 环境…...
【DLF】基于语言的多模态情感分析
作者提出的不足 模态平等处理导致冗余与冲突 问题:现有MSA方法对所有模态(语言、视觉、音频)平等处理,忽略模态间贡献差异(如语言为主导模态)。后果:跨模态交互引入冗余信息(如视觉和音频中与情感无关的噪声),甚至模态对间双向信息传递(…...
window 显示驱动开发-线性伸缩空间段
线性伸缩空间段类似于线性内存空间段。 但是,伸缩空间段只是地址空间,不能容纳位。 若要保存位,必须分配系统内存页,并且必须重定向地址空间范围以引用这些页面。 内核模式显示微型端口驱动程序(KMD)必须实…...
[Linux网络_71] NAT技术 | 正反代理 | 网络协议总结 | 五种IO模型
目录 1.NAT技术 NAPT 2.NAT和代理服务器 3.网线通信各层协议总结 补充说明 4.五种 IO 模型 1.什么是IO?什么是高效的IO? 2.有那些IO的方式?这么多的方式,有那些是高效的? 异步 IO 🎣 关键缺陷类比…...
免费5个 AI 文字转语音工具网站!
一个爱代码的设计师在运营,不定时分享干货、学习方法、效率工具和AIGC趋势发展。个人网站:tomda.top 分享几个好用的文字转语音、语音转文字的在线工具,麻烦需要的朋友保存。 01. ChatTTS 中英文智能转换,语音自然流畅,在线免费…...
【入门】数字走向II
描述 输入整数N,输出相应方阵。 输入描述 一个整数N。( 0 < n < 10 ) 输出描述 一个方阵,每个数字的场宽为3。 #include <bits/stdc.h> using namespace std; int main() {int n;cin>>n;for(int in;i>1;i--){for(…...