什么网站专做二手名表/免费网站统计代码
👨🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:Redis:原理速成+项目实战——Redis实战6(封装缓存工具(高级写法)&&缓存总结)
📚订阅专栏:Redis:原理速成+项目实战
希望文章对你们有所帮助
这篇文章写了很久。我自己在边实现、边用jmeter来测试、边根据结果来优化我的代码,对于那些线程并发的问题,我大致是可以靠自己来解决,但是为了写好这篇文章,为了做好线程并发问题的分析,我在独立实现完之后,还是按黑马程序员的进度走了一下,他埋坑的地方其实都是线程并发问题的坑,我也自己掉一掉,并在这篇文章中进行总结。
文章中会涉及一些java面试常见的问题:常量池、Spring代理失效。如果没有印象大家可以专门找一下这些面经去了解一下。
聊到电商,一定离不开秒杀,而Redis在整个秒杀的业务中的作用是非常巨大的,接下来将会利用Redis实现全局ID,并实现秒杀,并且解决超卖问题、实现一人一单,逐渐优化业务。
优惠券秒杀
- 全局唯一ID
- Redis实现全局唯一ID
- 优惠券秒杀下单
- 添加优惠券
- 实现秒杀下单
- 库存超卖问题
- 库存超卖问题分析
- 乐观锁解决超卖
- 实现一人一单功能
- 集群下的线程并发安全问题
全局唯一ID
每个店铺都可以发布优惠券(代金券),当用户抢购的时候,就会生成订单并且保存到tb_voucher_order这张表中:
可以发现,我们的主键ID没有使用自增长,这是因为如果使用数据库自增ID就会存在一些问题:
1、ID的规律性太明显,容易让别人猜测到信息
2、受单表数据量的限制(订单可能数据非常大,可能会分多表进行存储,但表的自增长相互之间不受影响,所以不同表之间可能会出现ID相同的情况,也就是说这种时候会违背ID的唯一性,这显然是不可以的)
而全局ID生成器,是一种分布式系统下用来生成全部唯一ID的工具,一般满足以下特性:
1、唯一性
2、高可用
3、高性能
4、递增性
5、安全性
除了第5点,Redis及其数据结构已经可以直接满足前4点的要求了,为了增加ID的安全性,不要直接使用Redis自增的数值,而是拼接一些其他信息,最终我们将ID组成定义为64位的二进制数,分别是1位符号位,31位时间戳,32位序列号:
1、符号位:1bit,永远为0
2、时间戳:31bit,以秒为单位,可以用69年
3、序列号:32bit,秒内的计数器,支持每秒产生2^32个不同的ID,这是用来处理相同秒内(时间戳相同)的多个业务
这样的结构是可以大幅度提高安全性的,不同时间下的ID一定不同,相同时间的情况下,也会因为32位的序列号而导致ID不同。
Redis实现全局唯一ID
我们在utils包下创建RedisIdWorker类:
@Component
public class RedisIdWorker {@Resourceprivate StringRedisTemplate stringRedisTemplate;/*** 开始时间戳由main函数运行得到*/public static final long BEGIN_TIMESTAMP = 1704499200L;/*** 序列号的位数*/public static final int COUNT_BITS = 32;public long nextId(String keyPrefix){//获得当前时间LocalDateTime now = LocalDateTime.now();long nowSecond = now.toEpochSecond(ZoneOffset.UTC);//生成时间戳long timestamp = nowSecond - BEGIN_TIMESTAMP;/*** 接下来生成序列号* 我们的key的设置除了加上icr表示是自增长的,还需要在最后拼接一个日期字符串* 这是因为我们的序列号上限是2^32,并不大,如果每天的key都是一样的,这是很有可能超过上限的* 在后面拼接一个日期字符串,可以保证每一天的key都是不一样的,而且一天内也基本不可能到达2^32的上限* 这样做还有一个好处,我们以后可以根据每天或者每月来查看value值,起到统计效果*///获取当前日期,精确到天String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));//ID自增长,这里最好用基本类型而不是包装类,因为后面还会做运算long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);//拼接并返回,这里灵活用位运算return timestamp << COUNT_BITS | count;}public static void main(String[] args) {//定义时间为2024年1月1日00:00:00LocalDateTime time = LocalDateTime.of(2024, 1, 6, 0, 0, 0);//将时间变成变成秒数的形式long second = time.toEpochSecond(ZoneOffset.UTC);//在这里运行出来的时间作为BEGIN_TIMESETAMPSystem.out.println(second);}
}
编写测试代码:
@Resourceprivate RedisIdWorker redisIdWorker;//性能池private ExecutorService es = Executors.newFixedThreadPool(500);@Testvoid testIdWorker() throws InterruptedException {//因为线程池是异步的,因此我们要用CountDownLatch去截断,这样才能正常计时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();};//将任务提交300次,并进行计时long begin = System.currentTimeMillis();for (int i = 0; i < 300; i++) {es.submit(task);}latch.await();//等待所有的countDown结束long end = System.currentTimeMillis();System.out.println("time = " + (end - begin));}
运行后可以发现,id各不重复,估计id生成的花费时间差不多只有2秒(id的打印也是会花时间的)
打开Redis客户端,可以发现我成功的生成了3万条的id:
优惠券秒杀下单
每个店铺都可以发布优惠券,分为平价券和特价券,平价券可以任意购买,而特价券需要秒杀抢购,表关系如下:
1、tb_voucher:优惠券基本信息(金额,规则等)
上面的type可以表示标识出是平价券还是特价券,如果是特价券我们也需要一些特定的信息,因此我们会专门拓展出一张表。
2、tb_seckill_voucher:优惠券库存、开始抢购时间、结束抢购时间(特价券需要此表)
添加优惠券
在VoucherController中提供一个接口,调用就可以实现添加秒杀优惠券:
虽然我们传入的参数只有Voucher,但是它也同样可以用来保存需要秒杀的券:
真正的添加不是客户来做的,要给后台来做,我们可以使用postman:
可以发现我们的数据库中已经存储了这个秒杀券:
实现秒杀下单
点击限时抢购,查看请求URL:
说明 | |
---|---|
请求方式 | POST |
请求路径 | voucher-order/seckill/{id} |
请求参数 | id,优惠券id |
返回值 | 订单id |
下单的时候我们需要判断2点:
1、秒杀是否开始或结束
2、库存是否充足
业务流程:
controller:
serviceimpl:
/*** <p>* 服务实现类* </p>** @author 王雄俊* @since 2024-01-06*/
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {//注入秒杀优惠券的service@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@Override@Transactionalpublic Result seckillVoucher(Long voucherId) {//查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);//判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("秒杀尚未开始");}//判断秒杀是否结束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("秒杀已经结束");}//判断库存是否充足if (voucher.getStock() < 1) {return Result.fail("库存不足");}//扣减库存,用mybatis-plus来写boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();//where条件if (!success){return Result.fail("库存不足");}System.out.println("啊啊啊啊啊");//创建订单,需要订单id、用户id、代金券idVoucherOrder voucherOrder = new VoucherOrder();long orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);Long userId = UserHolder.getUser().getId();//用户Id去ThreadLocal中取voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);save(voucherOrder);//返回订单IDreturn Result.ok(orderId);}
}
这边实现了最基础的订单秒杀,但是它存在很多问题
库存超卖问题
既然是秒杀,那每秒钟很可能会有成千上万的用户进行访问,那么这就对我们的并行化要求非常高,线程安全问题肯定是很重要的,上面的代码肯定是会存在线程安全问题的,我们可以用jmeter来做测试,为了方便我们到时候观察测试结果,我们去数据库手动把优惠券数量调回100,接着在jmeter中用200个线程来进行抢购:
这里设置的请求头则表示200个线程全部都由这一个用户来执行:
运行后可以看到有些请求成功,有些请求失败:
预期是有100个线程失败的,但是打开聚合报告可以发现失败的线程数量不到一半:
说明有些线程意外成功了,打开数据库,发现票数为-9,说明发生了超卖:
这会给商家带来损失。
库存超卖问题分析
假设库存容量为1(相当于一种临界资源),高并发的时候可能出现的异常情况:
也就是说,我们在某一时段会同时有多个线程查询库存的时候,得到的库存量为1,这时候都会进行扣减操作,造成超卖。
针对这种线程安全问题,常见解决方法就是直接加锁,可以分为悲观锁和乐观锁:
悲观锁:认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行。(Synchronized、Lock等)
乐观锁:认为线程安全问题不一定会发生,因此不加锁,只是在更新数据时去判断有没有其它线程对数据做了修改。(如果没有修改,那就是安全的;如果已经被其他线程修改说明发生了安全问题,此时可以重试或异常)
显然乐观锁的性能会好很多,但是实现起来会更复杂,我们要处理好关键的一点,那就是更新数据的时候,该如何去判断有没有其它线程对数据做了修改。
乐观锁的实现方式有2种方法(其实思想相同):
1、版本号法:
给数据增加一个字段version,初始值为1,每次我们要修改库存量之前都需要先查询库存量与版本号,然后线程执行SQL语句,执行SQL语句必须要确定数据库中的这条数据的版本号就是查询出来的版本号,如果不相同说明有其他线程修改了数据,导致当前数据的版本号与之前查询的不一样:
2、CAS法
上面的方法加一个版本号其实是一种标识,但是我们不一定要借助version,实际上我们可以直接依靠库存量来做标识,在对数据库进行修改的时候,我们要首先判断当前数据的库存量与之前线程查询出来的库存量是否相同,不相同则说明发生线程安全问题,不能修改:
乐观锁解决超卖
我们选用CAS法来解决超卖,根据上述思想,我们只需要在SQL语句那增加一个判断库存量的条件:
测试一下上面的代码,先把数据库做还原,把订单数据删光,并还原stock为100,然后测试jmeter,可以发现jmeter中显示大量的失败,数据库中也显示没有超卖:
超卖问题确实没有出现了,但是这显然是不合常理的,200个线程抢100张票,票居然只能卖出20张。这说明乐观锁有弊端。
我们对于乐观锁的分析,是拿stock=1的情况来说的,所以当线程查询出来的stock与数据库的stock不一致的时候,足以说明票已经卖完了。
假设stock=100,当线程查询出来的stock与数据库的stock不一致的时候,并不能说明票卖完了,理论上库存量大概率不为0,该线程还是应该要能够实现买票操作,但全都因为查询的stock与数据库不一致导致有大量线程买票失败。
传统乐观锁太谨慎了!我们应该要对其进行改进!
我们不再判断查询条件,而只需要查询数据库中的stock是否大于0:
再次打开jmeter进行测试,异常率50%,解决了上述问题:
但是这不代表乐观锁就是完美的,很显然代码逻辑中要操作数据库,大量的线程就会给数据库带来压力,仅仅使用乐观锁在更高并发的场景下还是不太够的。
实现一人一单功能
我们在jmeter中的测试,200个线程全部都由一个用户来执行,因此打开订单表,我们可以发现订单全部被同一个用户买了:
商家做优惠券就是为了吸引更多的用户,一人多单可能会导致商家变相亏本。
其实思路是很简单的,我们只需要判断当前尝试抢优惠券的线程,其用户id在订单表中是否已经存在了,如果存在则不允许下单:
我们在库存修改的代码之前加上这一部分逻辑:
//一人一单Long userId = UserHolder.getUser().getId();//查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();//判断是否存在if (count > 0){return Result.fail("您已购买过一次!");}
再次测试jmeter:
数据库显示这个用户买了10张优惠券,一人多单的问题有所缓解,但依旧存在:
这是因为上面的那一串逻辑还是存在了并发安全问题,在某一时刻还是会有很多的线程(同一个用户)进入了这部分逻辑,判断了count为0,因此进行了删减库存的操作。
这里我们肯定也要加锁,由于这一串逻辑并没有涉及到修改数据库的操作,所以我们只能加悲观锁。
@Overridepublic Result seckillVoucher(Long voucherId) {//查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);//判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("秒杀尚未开始");}//判断秒杀是否结束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("秒杀已经结束");}//判断库存是否充足if (voucher.getStock() < 1) {return Result.fail("库存不足");}//返回订单IDreturn createVoucherOrder(voucherId);}@Transactional //事务回滚放到这个函数public Result createVoucherOrder(Long voucherId) {//一人一单Long userId = UserHolder.getUser().getId();/*** userId值一样的,我们用同一把锁,但是每个请求一来,我们的id对象都是全新的* Long类型会存在这个问题,所以我们要用toString方法* 但是toString方法其实是对long类型new了一个字符串,所以每调用一个toString都是一个全新对象* 所以要加上intern()方法,从常量池中返回字符串的规范表示*/synchronized (userId.toString().intern()) {//查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();//判断是否存在if (count > 0) {return Result.fail("您已购买过一次!");}//扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock", 0).update();if (!success) {return Result.fail("库存不足");}//创建订单,需要订单id、用户id、代金券idVoucherOrder voucherOrder = new VoucherOrder();long orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);save(voucherOrder);//返回订单IDreturn Result.ok(orderId);}}
需要注意一个细节,上面代码还是会发生并发安全问题:
我们这边的整个函数已经是被Spring托管了,所以事务的提交会在函数执行完毕之后,也就是说我们会先释放锁,再提交事务,当我们事务还没有提交完成,修改数据还没写入数据库,却又有其他线程进来了,再次发生线程并发问题。
所以,锁的范围太小了,我们应该要把整个函数都锁起来:
但,依旧有问题!直接调用createVoucherOrder方法是不行的,因为它相当于调用了this.createVoucherOrder,然而当前类并不是代理对象,这会导致Sping代理失效!
所以我们要先获得当前对象的代理对象,然后再去调用这个函数:
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
需要引入依赖:
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId>
</dependency>
并且在启动类中需要暴露代理对象:
运行项目,打开jmeter进行测试:
完美解决!
注意我没有在createVoucherOrder这个函数上面直接加锁,不然所有进行操作的线程都串行执行实在太影响效率了!
集群下的线程并发安全问题
现在已经通过加锁解决一人一单问题安全,但是这只能解决单机情况的,集群模式依旧不行,在这里试着模拟一下集群的方式来进行测试。
1、将服务启动2份,端口分别为8081与8082:
重启形成2个机子的集群:
2、修改nginx的conf目录下的nginx.conf文件,配置反向代理、负载均衡:
最后重新加载一下Nginx并重启:
最后访问网址,并连续刷新2次:
http://localhost:8080/api/voucher/list/1
查看后台可以发现两个启动服务都可以接受到信息,因为api(8080)包括了8081与8082,访问是以轮转的方式进行的:
这样就实现了负载均衡。
测试大家只需要在锁那里打个断点,并且在postman里面分别抢券(都用同一个用户)来进行优惠券抢购,可以发现只用1个用户信息,数据库中却少了2张券,说明又一次发生了并发问题。
从头分析一下:
1、对于一个服务中的2个线程,可能发生下面的并发问题:
2、我们解决方法是加锁:
之所以这样能实现,是因为我们锁住的对象是userId.toString().intern(),也就是从这台Tomcat常量池中取出userId.toString(),同一个userId之间肯定是相同的,因此可以锁住,防止并发。
3、但如果我们部署另外一台Tomcat,这是锁的锁监视器,其监视的内容和之前锁中的监视器内容是不一样的,那么新Tomcat的线程获取锁就会成功(获取的userId.toString()是不一样的,不理解的可以去看toString方法的源码),并成功的操作数据库,因此才会造成线程并行问题。
如下图,线程1、3发生了线程安全问题:
因此我们只能保证单个JVM下的线程安全,却无法保证集群中多个JVM的线程安全,我们需要在集群中加锁,也就是分布式锁,将在后续讲解。
相关文章:

Redis:原理速成+项目实战——Redis实战7(优惠券秒杀+细节解决超卖、一人一单问题)
👨🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习 🌌上期文章:Redis:原理速成项目实战——Redis实战6(封装缓存工具(高级写法)&&缓存总…...

【刷题笔记3】
笔记3 输出小数位数控制。(自动四舍五入,不够就自动补0) double a123.456; cout<<fixed<<setprecision(2)<<a;递归题目的记录 (1):n*m的棋盘格子(n为横向的格子数…...

YOLOv8优化策略:轻量化改进 | 华为Ghostnet,超越谷歌MobileNet | CVPR2020
🚀🚀🚀本文改进:Ghost bottleneck为堆叠Ghost模块 ,与YOLOV8建立轻量C2f_GhostBottleneck 🚀🚀🚀YOLOv8改进专栏:http://t.csdnimg.cn/hGhVK 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 1.Ghostnet介绍 论文: https://arxiv.org/pdf/1911.11907.…...

格雷希尔G65系列快速接头满足汽车减震器的气压、油压测试要求
当汽车经过不平路面时,汽车减震器可以抑制弹簧吸震后因反弹带来的震荡和来自路面的冲击,为乘客带来平稳舒适的行车体验。减震器在出厂之前,需要模拟汽车的真实行驶环境,在模拟当中需要对它们进行气压和油压的轮番测试。 客户的测试…...

php中常用的几个安全函数
1. mysql_real_escape_string() 这个函数对于在PHP中防止SQL注入攻击很有帮助,它对特殊的字符,像单引号和双引号,加上了“反斜杠”,确保用户的输入在用它去查询以前已经是安全的了。但你要注意你是在连接着数据库的情况下使用这个…...

【K8S 云原生】Kurbernets集群的调度策略
目录 一、Kubernetes的list-watch机制 1、List-watch 2、创建pod的过程: 二、scheduler调度的过程和策略: 1、简介 2、预算策略:predicate 3、优先策略: 3.1、leastrequestedpriority: 3.2、balanceresourceal…...

vue-office 支持多种文件(docx、excel、pdf)预览的vue组件库
一、文档链接 https://gitcode.com/mirrors/501351981/vue-office/overview?utm_sourcecsdn_github_accelerator&isLogin1 二、安装 #docx文档预览组件 npm install vue-office/docx vue-demi0.13.11#excel文档预览组件 npm install vue-office/excel vue-demi0.13.11#…...

如何使用GaussDB创建脱敏策略(MASKING POLICY)
目录 一、前言 二、GaussDB中的脱敏策略 1、数据脱敏的定义 2、创建脱敏策略的语法说明 三、在GaussDB中如何创建数据脱敏策略(示例) 1、创建脱敏策略的一般步骤 2、GaussDB数据库中创建脱敏策略的完整示例 1)开启安全策略开关,以初识用户omm登录…...

【Golang map并发报错】panic: assignment to entry in nil map
go并发写map[string]interface{}数据的时候,报错:panic: assignment to entry in nil map 多个key同时操作一个map时,如: test[key1] 1 test[key2] "a" test[key3] true 就会遇到并发nil值报错,什么…...

【GO语言依赖】Go语言依赖管理简述
在运行环境中,遭遇报错,显示找不到函数 经过研究后发现需要进行依赖管理,进行如下操作后解决: 起源 最早的时候,Go所依赖的所有的第三方库都放在GOPATH这个目录下面。这就导致了同一个库只能保存一个版本的代码。如…...

论文阅读记录SuMa SuMa++
首先是关于SuMa的阅读,SuMa是一个完整的激光SLAM框架,核心在于“基于面元(surfel)”的过程,利用3d点云转换出来的深度图和法向量图来作为输入进行SLAM的过程,此外还改进了后端回环检测的过程,利用提出的面元的概念和使…...

性能分析与调优: Linux 内存观测工具
目录 一、实验 1.环境 2.vmstat 3.PSI 4.swapon 5.sar 6.slabtop 7.numstat 8.ps 9.top 10.pmap 11.perf 12.bpftrace 二、问题 1.接口读写报错 2.slabtop如何安装 3.numactl如何安装 4.numad启动服务与关闭NUMA 5. perf如何安装 6. kernel-lt-doc与kern…...

【ARM 嵌入式 编译系列 3.4 -- 查看所依赖库文件的路径 详细介绍】
文章目录 问题背景库文件路径查看库文件路径信息打印显示连接标准库不使用标准库 libgcc.a问题背景 在自己构建的 Makefle系统中对 cortex-m33 代码编译时,在链接阶段总是报出下面问题 ... arm-none-eabi-ld: cannot find libgcc.a: No such file or directory arm-none-eab…...

分布式锁3: zk实现分布式锁3 使用临时顺序节点+watch监听实现阻塞锁
一 zk实现分布式锁 1.1 使用临时顺序节点 的问题 接上一篇文章,每个请求要想正常的执行完成,最终都是要创建节点,如果能够避免争抢必然可以提高性能。这里借助于zk的临时序列化节点,实现分布式锁 1. 主要修改了构造方法和lock方…...

google drive api
1.创建oauth2 json 文件 https://developers.google.com/drive/api/quickstart/pythoncchttps://developers.google.com/drive/api/quickstart/python这里要注意quickstart的code会经常更新,有可能之前的版本不能用了 比方说下面这个包 from google.oauth2.crede…...

3_代理模式(动态代理JDK原生和CGLib)
一.代理模式 1.概念 代理模式(Proxy Pattern )是指为其他对象提供一种代理,以控制对这个对象的访问,属于结构型模式。 在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的…...

Linux的权限(1)
目录 操作系统的"外壳"程序 外壳程序是什么? 为什么存在外壳程序? 外壳程序怎么运行操作? 权限 什么是权限? 权限的本质? Linux中的(人)用户权限? su和su -的区别…...

数据安全保障的具体措施有哪些
随着信息化时代的到来,数据已经成为企业和社会发展的重要资产。然而,数据安全问题也日益突出,如何保障数据的安全性、完整性和可用性成为了亟待解决的问题。以下将详细探讨数据安全保障的各个方面,以期为企业和社会提供更好的数据…...

浅谈标签及应用场景
一、标签的定义 标签是根据业务场景的需求,通过对目标对象(包含静态、动态特性),运用抽象、归纳、推理等算法得到的高度精炼的特征标识,用于差异化管理与决策。标签由标签名称和标签值组成,打在目标对象上…...

Linux动态分配IP与正向解析DNS
目录 一、DHCP分配 1. 动态分配 1.1 服务端服务安装 1.2 修改服务端dhcp配置 1.3 修改客户端dhcp,重启查询网卡信息 2. 根据mac固定分配 2.1 修改服务器端dhcp服务配置 2.2 客户端自动获取,查看网卡信息 二、时间同步 1. 手动同步 2. 自动同…...

pyspark 使用udf 进行预测,发现只起了一个计算节点
PySpark UDF 只使用一个计算节点的问题 原因分析 默认的并行度设置 PySpark在执行UDF(用户定义函数)时,默认可能不会利用所有可用的计算节点。这是因为UDF通常在单个节点上执行,并且如果没有正确设置分区,可能会导致数…...

mysql触发器的简单使用
mysql触发器 触发器是一个特殊的存储过程,在事件delete、insert、update发生时自动执行一条或多条SQL语句(执行多条SQL语句需要用begin、end 包裹起来) 创建触发器 创建触发器的四大必要条件 唯一的触发器名称触发器关联的表触发器响应的…...

全志T113开发板Qt远程调试
1引言 通常情况下工程师在调试Qt程序时,需要频繁制作镜像烧录到核心板来测试Qt程序是否完善,这样的操作既费时又费力。这时我们可以通过QtCreator设备功能,定义设备后,在x86_64虚拟机上交叉编译qt程序,将程序远程部署到…...

学习使用php、js脚本关闭当前页面窗口的方法
学习使用php、js脚本关闭当前页面窗口的方法 前言方法一:使用JavaScript代码方法二:通过http头文件来实现方法三:使用服务器端脚本来实现 前言 在开发web应用程序时,我们通常需要在不同的网页之间进行导航。通常情况下࿰…...

python 人脸检测与人脸识别
安装库文件: pip install dlib face_recognition import dlib import face_recognition import cv2 from PIL import Image, ImageDraw# 判断运行环境 cpu or gpu def check_env():print(dlib.DLIB_USE_CUDA)print(dlib.cuda.get_num_devices())# 判断人脸在图片当中的位置 def…...

RT-Thread: ulog 日志 讲解和使用
说明:记录 RT-Thread: ulog 日志功能和使用流程。 官网资料链接: https://docs.rt-thread.org/#/rt-thread-version/rt-thread-standard/programming-manual/ulog/ulog 1.ulog 简介 日志的定义:日志是将软件运行的状态、过程等信息&#x…...

git ssh key 配置
一、Profile Settings-->SSH Keys 我们点击这里会有详情的文档介绍生成sshkey。 ssh-keygen -t rsa -b 2048 -C "邮箱" --回车... 将生成的id_rsa.pub粘贴到如下保存 git config --global user.name "用户名" git config --global user.email "邮…...

MongoDB聚合:$documents
$documents阶段可以根据输入值返回字面意义的文档。 语法 { $documents: <表达式> }$documents接受可解析为对象数组的任何有效表达式,包括: 系统变量,如 $$NOW 或 $$SEARCH_META $let 表达式 $lookup 表达式作用域中的变量 没有…...

程序员英语 - 英文会议常用句型
相信大部分程序员都会有如下经历: 产品经理(BA)们在和外系统聊集成方案时或者给用户解决某个问题时发现搞不定了,这个时候就会拉上程序员一起上会参与讨论或者排查问题,但程序员们英文又不好,上了会又听不懂…...

UV贴图和展开初学者指南
在线工具推荐: 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 介绍 这正是本文的主题——UV贴图——登上舞台的时候。大多数 3D 建…...