Redis 分布式锁+秒杀异步优化

文章目录

  • 问题
  • 思路
  • setnx实现
    • 锁误删问题和解决方案
    • Redis Lua脚本
      • 问题引出
      • 解决方案
  • setnx实现的问题
  • Redission
    • 快速入门
    • redission可重入锁原理
  • 秒杀优化(异步优化)
    • 异步秒杀思路
    • 秒杀资格判断
    • Redis消息队列

问题

比如我们两个机器都部署了我们项目,这里nginx使用轮询的方式去做负载均衡
在这里插入图片描述
这里用postman测试
在这里插入图片描述
出问题了:两个请求都会进入到synchronized,这两个请求是同一个用户同一个商品,按理来说是一人一单不能同时进入synchronized代码块
但是我们采用了分布式,我们请求1进入的8081端口,请求2进入的8082端口
相同的代码,部署在不同的机器上,synchronized锁不住其他机器/端口上的代码
这样的话还是实现不了一人一单
在这里插入图片描述
原理图
一个JVM一个锁监视器,只能保证请求该JVM的线程互斥,保证不了其他JVM的,所以分布式情况下也可能两个线程(userid相同)进入同步代码块,导致线程安全问题
在这里插入图片描述

思路

在JVM外单起一个服务,所有节点都会找这个服务去获取锁,这样的话就可以实现不同JVM同一代码块的锁
在这里插入图片描述
下面是一些实现方案
方案对应上文提到的服务
在这里插入图片描述
下面我们主要讲redis的解决方案

setnx实现

set lock thread1 NX EX 10
NX代表set命令是setnx(不存在可以添加返回1,存在可以失败返回0)
EX(expire)后面代表过期时间为了防止
setnx lock thread1
expire lock 10
两个命令之间redis宕机导致锁设置了但是没有过期时间
我们使用set lock thread1 NX EX 10来同时设置key和过期时间

在这里插入图片描述
初版Java代码
锁对象以及一些方法

public interface ILock {  /**  * 尝试获取锁  * @param timeoutSec 锁持有的超时时间,过期后自动释放  * @return true代表获取锁成功;false代表获取锁失败  */  boolean tryLock(long timeoutSec);  /**  * 释放锁  */  void unlock();  
}  public class SimpleRedisLock implements ILock {private String name;private RedisTemplate redisTemplate;public SimpleRedisLock(String name, RedisTemplate redisTemplate) {this.name = name;this.redisTemplate = redisTemplate;}private static final String KEY_PREFIX = "lock:";@Override  public boolean tryLock(long timeoutSec) {Long ThreadId = BaseContext.getCurrent().getId();Boolean flag = redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX+ name, ThreadId+"", timeoutSec, TimeUnit.SECONDS);
//        如果 flag 为 true,返回 true;如果 flag 为 false 或 null,返回 false。return BooleanUtil.isTrue(flag);}@Override  public void unlock() {redisTemplate.delete(KEY_PREFIX+ name);}  
}  

一人一单测试代码
分布式锁(悲观锁)解决一人一单的问题,查后insert语句,乐观锁解决查后update语句

因为insert原来没有数据所以不可以用乐观锁,而update可以

		Long userId = UserHolder.getUser().getId();//确保锁的针对同一个用户SimpleRedisLock lock = new SimpleRedisLock("order:"+userId, redisTemplate);boolean isLock = lock.tryLock(1200); // 获取锁if (!isLock) {// 获取锁失败, 返回或者重试return Result.fail("不允许重复下单");//新增这块用的同步锁(分布式),而查询减票是乐观锁}try{// 获取代理对象(事务)IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();  // 拿到当前对象的代理对象,其实就是IVoucherOrderService这个接口的代理对象,返回的是Object,做个强转return proxy.createVoucherOrder(voucherId);  // 如果报错了是因为我们的接口中没有这个方法,那我们就在接口中创建一下这个方法就行}finally {lock.unlock();// 释放锁}

锁误删问题和解决方案

初始线程阻塞可能释放其他线程的锁
在这里插入图片描述
解决方法:释放锁的时候看是不是自己获取的锁
释放锁时观察是否是自己获取的锁

在这里插入图片描述
解决释放的锁不是自身原先获取的锁

  • 在获取锁时存入线程标识(可以用UUID+ThreadId表示)
  • 在释放锁时先获取锁中的线程序示,判断是否与当前线程标识一致
    1.如果一致则释放锁
    2.如果不一致则不释放锁

why use UUID?
ThreadId的规律是每个JVM创建线程就会自增赋值,这样的话可能JVM1和JVM2相同的线程id同时进入代码,单用ThreadId可能会导致误删的情况
所以这里使用UUID作为表示,由于这里UUID是static属性,所有对象公用一个,只能表示JVM的唯一性,而不能标识JVM中的线程,所以释放锁的时候比对ThreadId
static UUID标识JVM,ThreadID标识JVM里面的线程,保证线程唯一性

	private static final String KEY_PREFIX = "lock:";private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";@Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标识String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁Boolean success = redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId+"", timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}@Overridepublic void unlock() {// 获取线程标识String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁中的标识String id = (String) redisTemplate.opsForValue().get(KEY_PREFIX + name);// 判断标识是否一致if (threadId.equals(id)) {// 释放锁redisTemplate.delete(KEY_PREFIX + name);}}

Redis Lua脚本

问题引出

由于我们判断和释放锁不是原子性操作
如果我们判断后发生了阻塞(JVM垃圾回收),锁超时释放,然后另一个线程获取
之后线程1恢复,因为已经判断,所以还是会删除锁,导致并发问题
在这里插入图片描述

解决方案

保证我们判断锁是否为自己的和释放锁的操作为原子的即可

Lua脚本类似于mysql中的事务保证了多个操作的原子性,redis中的事务不同于mysql事务达不到这种效果,所以采用Lua脚本来实现

为什么使用 Lua 脚本?

  • 原子性:Lua 脚本在 Redis 中执行时是原子的,即脚本在执行期间不会被其他命令中断。
    Lua 脚本在执行期间,Redis 不会处理其他命令,确保脚本的原子性。但长时间运行的脚本可能导致 Redis 阻塞,需谨慎使用。

  • 减少网络开销:将多个操作合并为一个脚本,减少客户端与服务器之间的通信次数。

  • 复杂操作:Lua 脚本支持条件判断、循环等复杂逻辑,适合处理需要多个步骤的操作。

  • Redis 会缓存加载的脚本,通过 SCRIPT LOAD 加载的脚本会一直保留,直到服务器重启或使用 SCRIPT FLUSH 清除。

  1. 使用脚本我们需要先编写一个脚本
  • 在 Lua 脚本中,可以通过 redis.call() 或 redis.pcall() 调用 Redis 命令。

  • redis.call():执行命令,出错时抛出异常。

  • redis.pcall():执行命令,出错时返回错误信息。

EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey myvalue
KEYS[1] 是键参数,ARGV[1] 是值参数。
  • 1 表示有 1 个键参数。

用法

  • EVAL:执行 Lua 脚本
EVAL "return 'Hello, Redis!'" 0

其中,“return ‘Hello, Redis!’” 是 Lua 脚本,0 表示没有键参数。

  • SCRIPT LOAD:加载脚本到 Redis,返回 SHA1 校验和。
SCRIPT LOAD "return 'Hello, Redis!'"

返回的 SHA1 值可用于后续的 EVALSHA 命令。

  • EVALSHA:通过 SHA1 值执行已加载的脚本。(此时已经缓存了)
EVALSHA <SHA1> 0

Lua脚本实现释放分布式锁

-- 这里的 KEYS[1] 就是传入的key,这里的 ARGV[1] 就是当前传递的参数值  
-- 获取当前的值,判断是否与当前传递的参数一致  
if (redis.call('GET', KEYS[1]) == ARGV[1]) then  -- 一致,则删除  return redis.call('DEL', KEYS[1])  
end  
-- 不一致,则返回0  
return 0  

setnx实现的问题

setnx实现分布式锁问题
在这里插入图片描述
不可重入:方法A调用方法B,方法A和方法B都需要获取到该锁资源,若是不可冲入锁,会导致死锁,A执行不完不能释放锁,B拿不到锁导致A执行不完
不可重试:直接返回了false,应为可充实
超时释放:最好可以动态的超时释放
主从一致性:从节点变为主节点时候没有同步到锁的信息,然后其他线程就可以进入了

Redission

Redisson 是一个用于 Java 的 Redis 客户端,它不仅提供了对 Redis 数据库的简单 API 接口,还提供了许多高级功能,旨在简化分布式应用程序的开发。它又以下的特性:

  1. 丰富的数据结构:提供了多种高级数据结构,如映射、集合、列表等,兼容 Java 集合框架。

  2. 分布式执行:支持分布式任务处理,实现高并发的任务执行。

  3. 分布式锁:确保在分布式环境下对共享资源的安全访问。

  4. 对象映射:自动序列化和反序列化 Java 对象,简化数据存取。

  5. 反应式编程支持:适合高并发和低延迟的应用程序。

  6. 高可用性:支持 Redis Sentinel 和 Redis Cluster,确保稳定运行。

快速入门

导入依赖

<dependency>  <groupId>org.redisson</groupId>  <artifactId>redisson</artifactId>  <version>3.13.6</version>  
</dependency>  

配置redisson的配置类

@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient(){// 配置Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379") //redis://192.168.150.101:6379.setPassword("123456");// 创建RedissonClient对象return Redisson.create(config);}
}

使用Redisson

@Resource  
private RedissonClient redissonClient;  @Test  
void testRedisson() throws InterruptedException {  // 获取锁(可重入),指定锁的名称  RLock lock = redissonClient.getLock("anyLock");  // 尝试获取锁,参数分别是:获取锁的最大等待时间(单位是时间尝试),锁自动释放的时间,时间单位  boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS); // 无参的话就是不等待,30秒自动释放  // 判断释放锁获取成功  if (isLock) {  try {  System.out.println("执行业务");  } finally {  // 释放锁  lock.unlock();  }  }  
}  

redission可重入锁原理

未完待续

秒杀优化(异步优化)

异步秒杀思路

先回顾一下
当用户发起请求,此时会请求nginx,nginx会访问到tomcat,而tomcat中的程序,会进行串行操作,分成如下几个步骤

1、查询优惠卷2、判断秒杀库存是否足够3、查询订单4、校验是否是一人一单5、扣减库存6、创建订单

在这六步操作中,又有很多操作是要去操作数据库的,而且还是一个线程串行执行, 这样就会导致我们的程序执行的很慢,所以我们需要异步程序执行,那么如何加速呢?
在这里插入图片描述
优化方案:
我们将耗时比较短的逻辑判断放入到redis中,比如是否库存足够,是否一人一单,只要这种逻辑可以完成,就意味着我们是一定可以下单完成的,我们只需要进行快速的逻辑判断,根本就不用等下单逻辑走完,我们直接给用户返回成功
再在后台开一个线程,后台线程慢慢的去执行queue里边的消息

  • 第一个难点是我们怎么在redis中去快速校验一人一单,还有库存判断

  • 第二个难点是由于我们校验和tomct下单是两个线程,那么我们如何知道到底哪个单他最后是否成功,或者是下单完成,为了完成这件事我们在redis操作完之后,我们会将一些信息返回给前端,同时也会把这些信息丢到异步queue中去,后续操作中,可以通过这个id来查询我们tomcat中的下单逻辑是否完成了。

在这里插入图片描述

秒杀资格判断

redis中去做秒杀资格的判断
需要有两个数据1.库存2.用户是否购买过该优惠券
信息1库存可以直接存库存信息
信息2可以是value为set存储购买过该优惠券的用户id进行比对判断
在这里插入图片描述

整体思路:当用户下单之后,判断库存是否充足只需要导redis中去根据key找对应的value是否大于0即可,如果不充足,则直接结束,如果充足,继续在redis中判断用户是否可以下单,如果set集合中没有这条数据,说明他可以下单,如果set集合中没有这条记录,则将userId和优惠卷存入到redis中,并且返回0,整个过程需要保证是原子性的,我们可以使用lua来操作

当以上判断逻辑走完之后,我们可以判断当前redis中返回的结果是否是0 ,如果是0,则表示可以下单,则将之前说的信息存入到到queue中去,然后返回,然后再来个线程异步的下单,前端可以通过返回的订单id来判断是否下单成功。
在这里插入图片描述
优化代码

  1. 首先,在添加优惠券的同时,我们需要将该优惠券及其库存保存到redis中,方便我们之后在redis中快速判断优惠券库存是否充足。对添加优惠券方法做修改如下。
@Override
@Transactional
public void addSeckillVoucher(Voucher voucher) {// MP保存优惠券save(voucher);// 保存秒杀信息SeckillVoucher seckillVoucher = new SeckillVoucher();seckillVoucher.setVoucherId(voucher.getId());seckillVoucher.setStock(voucher.getStock());seckillVoucher.setBeginTime(voucher.getBeginTime());seckillVoucher.setEndTime(voucher.getEndTime());seckillVoucherService.save(seckillVoucher);// 保存秒杀库存到Redis中stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY + voucher.getId(), voucher.getStock().toString());
}
  1. redis判断采用lua脚本,代码如下。
-- 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

redis如何操作执行lua脚本?
1.写好lua脚本在同一目录
2.在需要执行的类中定义并读取lua脚本
3.在执行处使用redistemplate.execute执行并传参,参数是lua里面定义的
在这里插入图片描述

 //提前初始化脚本,避免每次去执行脚本时单独去创建脚本对象private static final DefaultRedisScript<Long> SECKILL_SCRIPT;static {SECKILL_SCRIPT = new DefaultRedisScript<>();SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));//加载lua脚本SECKILL_SCRIPT.setResultType(Long.class);//设置返回值}/*** 使用lua脚本完成扣减资格判断* @param voucherId* @return*/@Override
public Result seckillVoucher(Long voucherId) {//获取用户Long userId = UserHolder.getUser().getId();//生成订单idlong orderId = redisIdWorker.nextId("order");// 1.执行lua脚本  判断是否满足扣减资格(看缓存里面是否有 重复订单)Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),//lua脚本里面没有代表key的参数  这里传入空参voucherId.toString(), userId.toString(), String.valueOf(orderId)  //根据lua脚本传入多个参数);int r = result.intValue();//将long类型转换为int  再去判断// 2.判断结果是否为0if (r != 0) {// 2.1.不为0 ,代表没有购买资格return Result.fail(r == 1 ? "库存不足" : "不能重复下单");   //r == 2 代表不能重复下单}//TODO 保存阻塞队列  待完成// 3.返回订单idreturn Result.ok(orderId);
}

Redis消息队列

抢购成功,将优惠券id和用户id封装后存入阻塞队列,然后开启线程池去阻塞队列里拿东西执行

BlockingQueue:阻塞队列对象,当一个线程从阻塞队列get值的时候,如果没有元素就会阻塞

1、创建阻塞队列 ,将 voucherOrder 对象放到队列当中

    /*** 初始化阻塞队列*/private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024*1024);// 保存阻塞队列  VoucherOrder voucherOrder = new VoucherOrder();// 2.3.订单idvoucherOrder.setId(orderId);// 2.4.用户idvoucherOrder.setUserId(userId);// 2.5.代金券idvoucherOrder.setVoucherId(voucherId);// 2.6.放入阻塞队列orderTasks.add(voucherOrder);

2.异步下单
视频

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@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);}@PostConstruct //注解含义:在当前类初始化完毕后执行private void init(){SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}//创建阻塞队列private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024*1024);//创建线程池(单线程线程池)private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();//内部类,规定线程执行逻辑private class VoucherOrderHandler implements Runnable{@Overridepublic void run() {while (true){try {// 1.获取队列中的订单信息VoucherOrder voucherOrder = orderTasks.take();// 2.创建订单handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error("处理订单异常",e);}}}}private void handleVoucherOrder(VoucherOrder voucherOrder) {// 1.获取用户Long userId = voucherOrder.getUserId();// 2.创建锁对象RLock lock = redissonClient.getLock("lock:order:" + userId);// 3.获取锁boolean isLock = lock.tryLock();// 4.若获取锁失败if (!isLock) {log.error("不允许重复下单");return;}// 获取锁成功 (理论上没有问题,lua脚本已经判断过了,这里再加锁只是兜底)try {//通过代理对象调用proxy.createVoucherOrder(voucherOrder);} finally {lock.unlock();}}@Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {boolean success = seckillVoucherService.update().setSql("stock = stock - 1")  //使用MP,设置sql语句.eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0).update();save(voucherOrder);}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());// 2.判断结果是否为0int r = result.intValue();if(r!=0){// 2.1.不为0,代表没有购物资格return Result.fail(r==1?"库存不足":"不能重复下单");}// 2.2 为0,有购买资格,先创建订单,再将订单信息添加到阻塞队列VoucherOrder voucherOrder = new VoucherOrder();// 2.3 获取订单id(Redis全局唯一id)long orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);// 2.4将订单信息存入阻塞队列,任务结束orderTasks.add(voucherOrder);//3.获取代理对象,方便后序线程使用,可以放在成员变量或者是voucherOrder里面proxy = (IVoucherOrderService) AopContext.currentProxy();// 4.返回订单idreturn Result.ok(orderId);}}

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

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

相关文章

机器学习中的距离度量与优化方法:从曼哈顿距离到梯度下降

目录 前言一、曼哈顿距离(Manhattan Distance)&#xff1a;二、切比雪夫距离 (Chebyshev Distance)&#xff1a;三、 闵可夫斯基距离(Minkowski Distance)&#xff1a;小结四、余弦距离(Cosine Distance)五、杰卡德距离(Jaccard Distance)六、交叉验证方法6.1 HoldOut Cross-v…

HTML 嵌入标签对比:小众(<embed>、<object>) 与 <iframe> 的优缺点及使用场景和方式

需求背景 在网页开发中&#xff0c;嵌入外部资源预览&#xff08;如视频、PDF、地图或其他网页&#xff09;是常见的需求。HTML 提供了多种标签来实现这一功能&#xff0c;其中 <embed>、<object> 和 <iframe> 是最常用的三种。本文将对比它们的优缺点&…

未来七轴机器人会占据主流?深度解析具身智能方向当前六轴机器人和七轴机器人的区别,七轴力控机器人发展会加快吗?

六轴机器人和七轴机器人在设计、功能和应用场景上存在明显区别。六轴机器人是工业机器人的传统架构&#xff0c;而七轴机器人则在多自由度和灵活性方面进行了增强。 本文将在理解这两者的区别以及为何六轴机器人仍然是市场主流&#xff0c;从多个方面进行深入解读六轴和七轴区…

C++基础精讲-07

文章目录 1. const对象2. 指向对象的指针3. 对象数组4. c中const常见用法总结4.1 修饰常量4.2 修饰指针4.3 修饰函数参数4.4 修饰函数返回值4.5 修饰成员函数4.6 const对象 5. 赋值运算符函数&#xff08;补充&#xff09;5.1 概念5.2 默认赋值运算符函数局限5.3 解决办法 1. c…

软件测试之接口测试用例设计

1.接口测试用例设计简介 我们对系统的需求分析完成之后&#xff0c;即可设计对应的接口测试用例&#xff0c;然后用接口测试用例进行接口测试。接口测试用例的设计也需要用到黑盒测试方法&#xff0c;其与功能测试用例设计的方法类似&#xff0c;接口测试用例设计中还需要增加…

(2)VTK C++开发示例 --- 绘制多面锥体

文章目录 1. 概述2. CMake链接VTK3. main.cpp文件4. 演示效果 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;VTK开发 &#x1f448; 1. 概述 VTK C开发示例程序&#xff1b; 使用C 和VTK绘制一个多面锥体。 环境说明系统ubuntu22.04、windows11cmake3.22、3.2…

公司内部自建知识共享的方式分类、详细步骤及表格总结,分为开源(对外公开)和闭源(仅限内部),以及公共(全员可访问)和内部(特定团队/项目组)四个维度

以下是公司内部自建知识共享的方式分类、详细步骤及表格总结&#xff0c;分为开源&#xff08;对外公开&#xff09;和闭源&#xff08;仅限内部&#xff09;&#xff0c;以及公共&#xff08;全员可访问&#xff09;和内部&#xff08;特定团队/项目组&#xff09;四个维度&am…

DeepSeek使用001:Word中配置DeepSeek AI的V3和R1模型

文章目录 Word中配置DeepSeek大模型1、勾选开发工具2、信任中心设置3、添加DeepSeek-V3模型4、获取API KEY5、添加DeepSeek-R1模型6、新建组7、测试使用 Word中配置DeepSeek大模型 1、勾选开发工具 打开【选项】 选择【自定义功能区】 2、信任中心设置 打开【信任中心】&…

Spark-SQL核心编程语言

利用IDEA开发spark-SQL 创建spark-SQL测试代码 自定义函数UDF 自定义聚合函数UDAF 强类型的 Dataset 和弱类型的 DataFrame 都提供了相关的聚合函数&#xff0c; 如 count()&#xff0c; countDistinct()&#xff0c;avg()&#xff0c;max()&#xff0c;min()。除此之外&…

从图像“看出动作”

&#x1f4d8; 第一部分&#xff1a;运动估计&#xff08;Motion Estimation&#xff09; &#x1f9e0; 什么是运动估计&#xff1f; 简单说&#xff1a; &#x1f449; 给你一段视频&#xff0c;计算机要“看懂”里面什么东西动了、往哪动了、有多快。 比如&#xff1a; 一…

Spring Boot 使用 SMB 协议

2025/4/14 向全栈工程师迈进&#xff01; 一、详述SMB协议 SMB&#xff08;Server Message Block&#xff09;协议是一个网络文件共享协议&#xff0c;它使得计算机可以在网络中共享文件、打印机以及其他资源。SMB 主要用于 Windows 操作系统&#xff0c;但也有其他平台&#…

Spring编程式事务(本地事务)

使用 TransactionTemplate等类和 API 手动管理事务&#xff0c;控制事务的新建、提交、回滚等过程 方式一&#xff1a;使用 TransactionTemplate&#xff08;推荐方式&#xff09; Service public class OrderService {private final TransactionTemplate transactionTemplat…

itext7 html2pdf 将html文本转为pdf

1、将html转为pdf需求分析 经常会看到爬虫有这样的需求&#xff0c;将某一个网站上的数据&#xff0c;获取到了以后&#xff0c;进行分析&#xff0c;然后将需要的数据进行存储&#xff0c;也有将html转为pdf进行存储&#xff0c;作为原始存档&#xff0c;当然这里看具体的需求…

企业级低代码平台的架构范式转型研究

在快速迭代的数字时代&#xff0c;低代码平台如同一股清流&#xff0c;悄然成为开发者们的新宠。 它利用直观易用的拖拽式界面和丰富的预制组件&#xff0c;将应用程序的开发过程简化到了前所未有的程度。通过封装复杂的编程逻辑和提供强大的集成能力&#xff0c;低代码平台让…

C++ | STL之list详解:双向链表的灵活操作与高效实践

引言 std::list 是C STL中基于双向链表实现的顺序容器&#xff0c;擅长高效插入和删除操作&#xff0c;尤其适用于频繁修改中间元素的场景。与std::vector不同&#xff0c;std::list的内存非连续&#xff0c;但提供了稳定的迭代器和灵活的元素管理。本文将全面解析std::list的…

AI运算服务器工控机特点与应用

AI运算服务器工控机是专门针对工业环境设计的计算设备&#xff0c;结合了传统工控机&#xff08;工业控制计算机&#xff09;的可靠性与AI服务器的强大算力&#xff0c;广泛应用于智能制造、边缘计算、机器视觉、自动化控制等领域。以下是其核心特点、应用场景及选型建议&#…

25/4/9 算法笔记 DBGAN+强化学习+迁移学习实现青光眼图像去模糊1

整体实验介绍 实验主要是结合DBGAN对抗网络强化学习增强迁移学习增强实现青光眼图像去模糊。今天则是先完成了DBGAN板块模型的训练。 实验背景介绍 青光眼的主要特征有&#xff1a; 视盘形态与杯盘比CDR&#xff1a;青光眼患者主要表现为视杯扩大&#xff0c;盘沿变窄。 视…

智能复盘自动化系统搭建指南—基于DeepSeek API与Apple日历的整合实践

一、系统架构设计 本方案通过iOS快捷指令实现日历数据与AI分析的自动化交互&#xff0c;核心流程包含&#xff1a; 日历事件管理 创建每日循环的"AI复盘"日历事项实现当日备注信息的动态更新 数据处理模块时间日志标准化处理多维度数据特征提取 AI交互层对接DeepSeek…

01 位运算

12days 章节结构 00 算法前导课-编程基础&#xff08;自学的视频&#xff09; 01 位运算的奇巧淫技 02 查找与排序&#xff08;上&#xff09; 03 数组、查找与排序(下) 04 多维数组与矩阵 05 字符串专题 06 基本数学问题 06 递归、DFS、剪枝、回溯等问题 07 贪心策…

HDFS Full Block Report超限导致性能下降的原因分析

文章目录 前言发现问题失败的为什么是FBR块汇报频率的变化为什么FBR会反复失败HDFS性能下降导致Yarn负载变高的形式化分析理解线程理解IO Wait理解HDFS性能下降导致Yarn负载和使用率增高 引用 前言 我们的Yarn Cluster主要用来运行一批由Airflow定时调度的Spark Job&#xff0…