当前位置: 首页 > news >正文

redis优化秒杀和消息队列

redis优化秒杀

  • 1. 异步秒杀思路
    • 1.1 在redis存入库存和订单信息
    • 1.2 具体流程图
  • 2. 实现
    • 2.1 总结
  • 3. Redis的消息队列
    • 3.1 基于list实现消息队列
    • 3.2 基于PubSub实现消息队列
    • 3.3 基于stream实现消息队列
      • 3.3.1 stream的单消费模式
      • 3.3.2 stream的消费者组模式
    • 3.4 基于stream消息队列实现异步秒杀

本文为学习redis时做的笔记,学习内容来自 黑马程序员Redis入门到实战教程,该教程是循序渐进的,所以不是一上来就讲完最后的解决方案了,请耐心看完

1. 异步秒杀思路

image.png
这是我们原本的秒杀思路,其中的流程都要经过mysql数据库,而mysql数据库的并发性能不是很好,而且为了避免线程安全问题,还加入了分布式锁,所以整个流程的性能不好,现在我们要去优化它。

我们可以把这整个流程比作一个餐馆点菜的过程,前台点菜并将菜品写在小票上,给顾客一份,后厨一份,后厨根据小票的内容依次做菜

根据这个例子,我们的流程也可以分为两个部分:

  1. 第一部分是判断秒杀资格(判断秒杀库存和校验一人一单)
  2. 第二部分是减库存创建订单流程

两个部分各自为一个线程,主线程判断秒杀资格,如果用户有资格,就开启一个独立线程完成耗时较久的第二部分

image.png
同时,我们也要优化判断秒杀资格的性能,将库存和订单存入redis中,如果判断用户有资格,先将优惠券id,用户id、订单id保存在阻塞队列中,并将订单id返回给用户,用户可以通过这个订单id完成支付操作,虽然此时还没有创建订单,但是在队列中迟早会创建,之后开启独立线程读取队列中的信息,完成下单。
现在的业务流程变成了在redis中判断秒杀资格,保存信息在队列中并返回订单id,性能和吞吐量大大提高

1.1 在redis存入库存和订单信息

现在讨论这两个东西需要什么样的结构去存储

image.png
因为库存只是一个数值,我们使用redis中的string类型去存储,key是优惠券的id,value是库存

到时只需判断库存是否大于0,如果用户有资格,库存要减一,相当于在redis中预减库存

image.png
因为需要一个优惠券id(key)能存很多用户id,而且用户id不能重复,所以订单信息我们使用set结构存储

到时只需看value中是否有该用户id来判断该用户是否下过单

1.2 具体流程图

image.png
为了保证过程的原子性,需要用到lua脚本,通过执行lua脚本后的结果来返回异常信息或者订单id,这样创建订单的时效性就没有那么强了,完全可以照着数据库能承受的范围去执行写的操作,用户只需要订单id就能完成支付操作

2. 实现

需求:
image.png

  1. 新增秒杀优惠券的同时,将优惠券信息保存在redis中

修改添加秒杀优惠券的方法,将优惠券信息保存在redis中

@Override
@Transactional
public void addSeckillVoucher(Voucher voucher) {
// 保存优惠券
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());
}

添加一个秒杀优惠券

{"title": "120元代金券","beginTime": "2023-11-01T01:11:11","actualValue": 12000,"shopId": 1,"subTitle": "周一至周五均可使用","payValue": 10000,"stock": 100,"endTime": "2024-11-01T01:11:11","type": 1,"rules": "全场通用\\n无需预约\\n可无限叠加\\不兑现、不找零\\n仅限堂食"
}

image.png

  1. 基于lua脚本,判断秒杀库存、一人一单、决定用户是都抢购成功

写lua脚本

  • 先写参数列表,再组合成key,最后写逻辑
-- 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. 脚本业务
-- 判断库存是否充足
if (tonumber(redis.call('get',stockKey)) <= 0) then-- 库存不足return 1
end
-- 判断用户是否下单
if (redis.call('sismember',orderKey,userId) == 1) then-- 用户下过单return 2
end-- 扣减库存
redis.call('incrby',stockKey,-1);
-- 下单,保存用户id到set集合
redis.call('sadd',orderKey,userId);

重写秒杀逻辑

  • 调用lua脚本,根据返回的数字判断,并返回订单号
private static final DefaultRedisScript<Long> SECKILL_SCRIPT;//初始化脚本
static {SECKILL_SCRIPT = new DefaultRedisScript();//读取文件位置,classpath就是resourceSECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));SECKILL_SCRIPT.setResultType(Long.class);
}@Override
public Result seckillVoucher(Long voucherId) {
// 获取用户id
Long userId = UserHolder.getUser().getId();
// 1. 调用lua脚本
//不需要传key,所以传个空集合
Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString()
);
int intValue = result.intValue();//2. 判断结果是否为0
if (intValue != 0) {return Result.fail(intValue == 1 ? "库存不足" : "不要重复下单");
}
//3. 将优惠券id,用户id、订单id保存在阻塞队列中
//TODO 将优惠券id,用户id、订单id保存在阻塞队列中
long orderId = redisIdWorker.nextId("order");//4. 返回订单id
return Result.ok(orderId);
}
  1. 如果抢购成功,将优惠券id和用户id封装后存入阻塞队列
//阻塞队列
private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);

阻塞队列BlockingQueue,当一个线程尝试从一个阻塞队列中获取元素,如果队列中没有元素,这个线程就会被阻塞,当队列中有元素时就会被唤醒并获取元素

seckillVoucher中添加:

//3.2 放入阻塞队列中
orderTasks.add(order);
  1. 开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能
  • 建立线程池,当这个类初始化完线程就去执行VoucherOrderHandler类中的run方法,不断获取队列中的订单信息,去创建订单

@PostConstruct 注解效果是当前类初始化完就去执行

//线程池
private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
//类加载完就执行
@PostConstruct
private void init() {SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
}class VoucherOrderHandler implements Runnable {@Overridepublic void run() {while (true) {try {//1.获取队列中的订单信息VoucherOrder voucherOrder = orderTasks.take();//2.创建订单handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error("订单异常:" + e);}}}
}
  • 执行handleVoucherOrder方法,获取锁,最后执行创建订单方法

注1:在这个方法中不能使用threadlocal去获取用户信息,因为是异步下单,这是一个子线程,不是主线程,没有用户的信息,所以从订单中获取用户id

注2:与上同理,在这个子线程中也无法获取到代理对象,将代理对象设置为成员变量,再从主线程中获取到代理对象

private void handleVoucherOrder(VoucherOrder voucherOrder) {
Long userId = voucherOrder.getUserId();
//创建锁对象
RLock redisLock = redissonClient.getLock("lock:order:" + userId);
//获取锁方法
boolean lockFlag = redisLock.tryLock();
//判断是否获取成功
if (!lockFlag) {log.error("不要重复下单");return;
}//事务方法执行起来可能会出现异常,但最后都要释放锁,所以try-catch起来
try {proxy.createVoucherOrder(voucherOrder);
} finally {redisLock.unlock();
}
}
  • 创建订单逻辑
@Transactional
public void createVoucherOrder(VoucherOrder voucherOrder) {
//5.一人一单
Long userId = UserHolder.getUser().getId();
//查询
Integer count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
//判断订单是否存在
if (count > 0) {log.error("用户已经购买过一次");return;
}//6.扣减库存
boolean success = seckillVoucherService.update().setSql("stock = stock-1")
.eq("voucher_id", voucherOrder.getVoucherId())
.gt("stock", 0)
.update();
if (!success) {log.error("不要重复下单");return;
}
save(voucherOrder);
}

2.1 总结

整个流程是:

  • 主线程:

发送请求进入seckillVoucher方法,先判断用户是否有秒杀的资格(通过lua脚本),然后创建订单(将用户id、优惠券id、订单id放进订单里),将订单放入阻塞队列

  • 子线程:

在类初始化的时候去执行线程池,线程池的任务是是不断地从队列中获取订单信息,然后去创建订单。
创建订单先获取锁,再判断一人一单,减库存,最后执行添加订单方法

完整代码:


/*** <p>* 服务实现类* </p>** @author 虎哥* @since 2021-12-22*/
@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Autowiredprivate ISeckillVoucherService seckillVoucherService;@Autowiredprivate RedisIdWorker redisIdWorker;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate RedissonClient redissonClient;private IVoucherOrderService proxy;private static final DefaultRedisScript<Long> SECKILL_SCRIPT;//初始化脚本static {SECKILL_SCRIPT = new DefaultRedisScript();//读取文件位置,classpath就是resourceSECKILL_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();//类加载完就执行@PostConstructprivate void init() {SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}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) {Long userId = voucherOrder.getUserId();//创建锁对象RLock redisLock = redissonClient.getLock("lock:order:" + userId);//获取锁方法boolean lockFlag = redisLock.tryLock();//判断是否获取成功if (!lockFlag) {log.error("不要重复下单");return;}//事务方法执行起来可能会出现异常,但最后都要释放锁,所以try-catch起来try {proxy.createVoucherOrder(voucherOrder);} finally {redisLock.unlock();}}@Overridepublic Result seckillVoucher(Long voucherId) {// 获取用户idLong userId = UserHolder.getUser().getId();// 1. 调用lua脚本//不需要传key,所以传个空集合Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString());int intValue = result.intValue();//2. 判断结果是否为0if (intValue != 0) {return Result.fail(intValue == 1 ? "库存不足" : "不要重复下单");}//3. 将优惠券id,用户id、订单id保存在阻塞队列中//3.1.创建订单VoucherOrder order = new VoucherOrder();//3.1.1 订单idlong orderId = redisIdWorker.nextId("order");order.setId(orderId);//3.1.2 用户idorder.setUserId(userId);//3.1.3 优惠券idorder.setVoucherId(voucherId);//3.2 放入阻塞队列中orderTasks.add(order);//3.3 获取当前的代理对象(事物)proxy = (IVoucherOrderService) AopContext.currentProxy();//4. 返回订单idreturn Result.ok(orderId);}@Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {//5.一人一单Long userId = voucherOrder.getUserId();//查询Integer count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();//判断订单是否存在if (count > 0) {log.error("用户已经购买过一次");return;}//6.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock-1").eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0).update();if (!success) {log.error("不要重复下单");return;}save(voucherOrder);}}

测试:
image.png

image.png
image.png

现在我们的阻塞队列使用的是jvm的内存,将来有无数的请求进来,内存可能会满,而且如果服务重启或者宕机,订单信息就消失了,可能会出现一些问题,接下来学习一下redis的消息队列

3. Redis的消息队列

消息队列,字面意思就是存放消息的队列,最简单的消息队列包含三个角色

  • 生产者:发送消息到消息队列
  • 消息队列:存储和管理消息,也称为消息代理
  • 消费者:从消息队列获取消息并处理消息

image.png

市面上有很多的消息队列的产品,但是搭建他们也是需要成本的,既然我们已经搭建起了redis集群,为了减少成本,可以使用redis提供的三种不同的方式

  • list结构:基于list结构模拟消息队列
  • PubSub:订阅发布,基本的点对点消息模型
  • Stream:比较完善的消息队列模型

3.1 基于list实现消息队列

Redis的list数据结构是一个双向链表,很容易模拟出队列效果。
队列是入口和出口不在一边,因此我们可以利用: LPUSH 结合 RPOP、或者 RPUSH 结合LPOP来实现。
不过要注意的是,当队列中没有消息时RPOP或LPOP操作会返回null,并不像VM的阻塞队列那样会阻塞并等待消息。因此这里应该使用BRPOP或者BLPOP来实现阻塞效果。
image.png

优点:

  • 利用Redis存储,不受限于JVM内存上限
  • 基于Redis的持久化机制,数据安全性有保证
  • 可以满足消息有序性

缺点:

  • 无法避免消息丢失
  • 只支持单消费者

3.2 基于PubSub实现消息队列

Pubsub(发布订阅)是Redis2.0版本引入的消息传递模型。顾名思义,消费者可以订阅一个或多个channel,生产者向对应channel发送消息后,所有订阅者都能收到相关消息

  • SUBSCRIBE channel[channell : 订阅一个或多个频道
  • PUBLISH channel msg : 向一个频道发送消息
  • PSUBSCRIBE pattern[pattern]: 订与pattern格式匹配的所有频道

image.png

优点:

  • 采用发布订阅模型,支持多生产、多消费

缺点:

  • 不支持数据持久化
  • 无法避免消息丢失
  • 消息堆积有上限,超出时数据丢失

3.3 基于stream实现消息队列

3.3.1 stream的单消费模式

Stream是Redis5.0引入的一种新数据类型,可以实现一个功能非常完善的消息队列

  1. 发送消息的命令:

image.png

  • 中间的内容都是可选。红框标注的基本可以不用管;绿框用来设置消息队列的最大消息数量;黄框用来设置消息的id,*代表redis自动生成;蓝框是队列中的消息内容

示例:
xadd users * name zhuyi love lvhan
返回id
image.png

  1. 查看队列中消息数量的命令

xlen key
示例:
image.png

  1. 读取队列中消息的命令

image.png

  • [COUNT count] 是每次读取消息的最大数量
  • [BLOCK milliseconds] 当没消息时,是否阻塞及阻塞时长
  • STREAMS key 要从哪个队列开始读,key就是队列名
  • ID 起始id,只返回大于该id的消息。0:代表从第一个消息开始,$:代表从最新的消息开始

示例:
从第一个消息开始读
image.png
因为消息已经读过,没有最新的消息,所以读不出来
image.png

特点:

  • 消息可回溯
  • 一个消息可以被多个消费者读取
  • 可以阻塞读取
  • 有消息漏读的风险。使用$,在读一条消息的时候,有超过一条以上的消息进入队列,只会读取最后一条

3.3.2 stream的消费者组模式

消费者组,将多个消费者划分到一个组中,监听同一个队列,有以下好处:

  • 消息分流:队列中的消息会分流给组内的不同消费者,而不是重复消费,从而加快消息处理的速度。如果想让一个消息被多个消费者消费,可以多加几个组
  • 消息标识:消费者组会维护一个标识,记录最后一个被处理的消息,哪怕消费者宕机重启,还会从标识之后读取消息,确保每一个消息都会被消费
  • 消息确认:消费者获取消息后,消息处于pending(待处理)状态,并存入一个pending-list。当处理完成后需要通过XACK来确认消息,标记消息为已处理,才会从pending-list移除,保证所有的消息只要被获取到了,就能至少被消费一次
  1. 创建消费者组:

image.png

  • key:队列名称
  • groupName:消费者组名称
  • ID:起始ID标识,$代表队列中最新的消息,0则代表队列中第一个消息
  • MKSTREAM:队列不存在时自动创建队列

其他常见命令:
image.png

示例:
image.png

  1. 从消费者组读取消息

image.png

  • group:消费组名称
  • consumer:消费者名称,如果消费者不存在,会自动创建一个消费者
  • count:本次查询的最大数量
  • BLOCK millisecond:当没有消息时最长等待时间
  • NOACK:无需手动ACK,获取到消息后自动确认
  • STREAMS key:指定队列名称
  • ID:获取消息的起始id:1. “>”:从下一个未消费的消息开始。2. 其他:根据指定id从pending-list中获取已消费但未确认的消息,例如0,是从pending-list中的第一个消息开始

示例:
image.png

  1. 确认消息

我们获取到消息消费后,一定要确认它,把他从pending-list中移除
image.png

  • key:队列名称
  • group:消费组名称
  • ID:获取消息的起始id:1. “>”:从下一个未消费的消息开始。2. 其他:根据指定id从pending-list中获取已消费但未确认的消息,例如0,是从pending-list中的第一个消息开始

示例:
image.png

消费者监听消息的基本思路:
image.png
特点:

  • 消息可回溯
  • 可以多消费者争抢消息,加快消费速度
  • 可以阻塞读取
  • 没有消息漏读的风险
  • 有消息确认机制,保证消息至少被消费一次

如果你的公司业务比较庞大,对消息队列要求比较严格,还是建议使用更专业的消息队列,如rabbitmq等,但如果是中小型公司,对消息队列需要没那么大,redis的stream就已经能满足需求了

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

image.png

  1. 创建一个Stream类型的消息队列,定义为stream.orders,这里直接在客户端完成了
XGROUP CREATE stream.orders g1 0 mkstream
  1. 修改之前的秒杀下单lua脚本,在认定有抢购资格后,直接向stream.orders中添加消息,内容包括voucherId、userId、orderId

修改lua脚本:
主要添加了一个订单id的参数,在业务的最后向队列发送消息

-- 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. 脚本业务
-- 判断库存是否充足
if (tonumber(redis.call('get',stockKey)) <= 0) then-- 库存不足return 1
end
-- 判断用户是否下单
if (redis.call('sismember',orderKey,userId) == 1) then-- 用户下过单return 2
end-- 扣减库存
redis.call('incrby',stockKey,-1);
-- 下单,保存用户id到set集合
redis.call('sadd',orderKey,userId);
-- 发送消息
redis.call('xadd','stream.orders','*','voucherId',voucherId,'userId',userId,'id',orderId)
return 0

修改一下调用lua脚本的逻辑:
新添加一个订单id的参数

// 1. 调用lua脚本
//不需要传key,所以传个空集合
Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString(),String.valueOf(orderId)
);
int intValue = result.intValue();
  1. 项目启动时,开启一个线程任务,尝试获取stream.orders中的消息,完成下单

在原有的线程任务逻辑上修改,从消息队列中获取订单信息,判断一下订单信息是否为空,如果为空,说明没有消息,继续下一次循环,如果有,去解析数据,拿到订单,通过以前写过的createVoucherOrder()方法来创建订单,最后一定要确认消息,将消息从pending-list中移除。
如果在执行时出现了错误或者服务宕机,通过handlePendingList()方法处理pending-list中已消费但未确认的订单,这里如果出现异常,就不用再调用这个方法了

private class VoucherOrderHandler implements Runnable {@Overridepublic void run() {while (true) {try {// 1.获取消息队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 >List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create("stream.orders", ReadOffset.lastConsumed()));// 2.判断订单信息是否为空if (list == null || list.isEmpty()) {// 如果为null,说明没有消息,继续下一次循环continue;}// 解析数据MapRecord<String, Object, Object> record = list.get(0);Map<Object, Object> value = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);// 3.创建订单handleVoucherOrder(voucherOrder);// 4.确认消息 XACKstringRedisTemplate.opsForStream().acknowledge("stream.orders", "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 BLOCK 2000 STREAMS s1 0List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1),StreamOffset.create("stream.orders", ReadOffset.from("0")));// 2.判断订单信息是否为空if (list == null || list.isEmpty()) {// 如果为null,说明没有异常消息,结束循环break;}// 解析数据MapRecord<String, Object, Object> record = list.get(0);Map<Object, Object> value = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);// 3.创建订单handleVoucherOrder(voucherOrder);// 4.确认消息 XACKstringRedisTemplate.opsForStream().acknowledge("stream.orders", "g1", record.getId());} catch (Exception e) {log.error("处理订单异常", e);}}}
}

XACK确认消息:
stringRedisTemplate.opsForStream().acknowledge("s1", "g1", record.getId());

相关文章:

redis优化秒杀和消息队列

redis优化秒杀 1. 异步秒杀思路1.1 在redis存入库存和订单信息1.2 具体流程图 2. 实现2.1 总结 3. Redis的消息队列3.1 基于list实现消息队列3.2 基于PubSub实现消息队列3.3 基于stream实现消息队列3.3.1 stream的单消费模式3.3.2 stream的消费者组模式 3.4 基于stream消息队列…...

arm-eabi-gcc 和 arm-none-eabi-gcc 都是基于 GCC 的交叉编译器

arm-eabi-gcc 和 arm-none-eabi-gcc 都是基于 GCC 的交叉编译器&#xff0c;用于编译 ARM 架构的嵌入式系统。它们的命名规则如下&#xff1a; arm 表示目标架构是 ARM。eabi 表示嵌入式应用程序二进制接口&#xff08;Embedded Application Binary Interface&#xff09;&…...

《大话设计模式》(持续更新中)

《大话设计模式》 序 为什么要学设计模式第0章 面向对象基础什么是对象&#xff1f;什么是类&#xff1f;什么是构造方法&#xff1f;什么是重载&#xff1f;属性与字段有什么区别&#xff1f;什么是封装&#xff1f;什么是继承&#xff1f;什么是多态&#xff1f;抽象类的目的…...

人工智能原理复习--绪论

文章目录 人工智能原理概述图灵测试人工智能的研究方法符号主义连接主义行为主义总结 人工智能原理概述 人工智能是计算机科学基础理论研究的重要组成部分 现代人工智能一般认为起源于美国1956你那夏季的达特茅斯会议&#xff0c;在这次会议上&#xff0c;John McCarthy第一次…...

[网络] 字节一面~ 2. HTTP 2 与 HTTP 1.x 有什么区别

头部压缩 在 HTTP2 当中&#xff0c;如果你发出了多个请求&#xff0c;并且它们的头部(header)是相同的&#xff0c;那么 HTTP2 协议会帮你消除同样的部分。(其实就是在客户端和服务端维护一张索引表来实现)二进制格式 HTTP1.1 采用明文的形式 HTTP/2 全⾯采⽤了⼆进制格式&…...

自己动手实现一个深度学习算法——八、深度学习

深度学习是加深了层的深度神经网络。 1.加深网络 1&#xff09;向更深的网络出发 创建一个如下图所示的网络结构的CNN 这个网络的层比之前实现的网络都更深。这里使用的卷积层全都是33 的小型滤波器&#xff0c;特点是随着层的加深&#xff0c;通道数变大&#xff08;卷积…...

js闭包的必要条件及创建和消失(生命周期)

>创建闭包的必要条件&#xff1a; 1.函数嵌套 2.内部函数引用外部函数的变量 3.将内部函数作为返回值返回 >闭包是什么&#xff1f; 就是可以访问外部函数&#xff08;作用域&#xff09;中变量的内部函数 > 闭包是什么时候产生的&#xff1f; - 当调用外部函数…...

鸿蒙开发-ArkTS 语言-基础语法

[写在前面: 文章多处用到gif动图&#xff0c;如未自动播放&#xff0c;请点击图片] 1. 初识 ArkTS 语言 ArkTS 是 HarmonyOS 优选主力开发语言。ArkTS 是基于 TypeScript (TS) 扩展的一门语言&#xff0c;继承了 TS 的所有特性&#xff0c;是TS的超集。 主要是扩展了以下几个方…...

GPT实战系列-GPT训练的Pretraining,SFT,Reward Modeling,RLHF

GPT实战系列-GPT训练的Pretraining&#xff0c;SFT&#xff0c;Reward Modeling&#xff0c;RLHF 文章目录 GPT实战系列-GPT训练的Pretraining&#xff0c;SFT&#xff0c;Reward Modeling&#xff0c;RLHFPretraining 预训练阶段Supervised FineTuning &#xff08;SFT&#x…...

电子学会C/C++编程等级考试2022年03月(三级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:和数 给定一个正整数序列,判断其中有多少个数,等于数列中其他两个数的和。 比如,对于数列1 2 3 4, 这个问题的答案就是2, 因为3 = 2 + 1, 4 = 1 + 3。 时间限制:10000 内存限制:65536输入 共两行,第一行是数列中数的个数…...

理解 JUnit, JaCoCo 到 SonarQube 的过程及 Maven 配置

Java 项目需要产生单元测试及代码覆盖率的话一直都是走的 JUnit 单元测试&#xff0c;JaCoCo 基于测试产生测试覆盖率&#xff0c;然后送到 SonarQube 去展示这条路子。当然 SonarQube 还可以帮我们进行代码的静态分析。但对其中的具体使用及过程知晓的并不深&#xff0c;基本就…...

人工智能关键技术决定机器人产业的前途

人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;是指让计算机或机器具有类似于人类的智能和学习能力的技术。人工智能技术与机器人技术的结合将改变传统的机器人行业格局&#xff0c;就像智能手机对传统手机的颠覆一样。本文从人工智能技术的发展趋势、…...

2023华为ICT网络初赛试题回顾

所有题目都只能用来学习交流&#xff0c;禁止用于非法不公平的使用&#xff0c;如有侵权&#xff0c;该文章立刻删除。 1、某机房没有合适长度的网线&#xff0c;现需手工制作一个568B标准的双纹线&#xff0c;那么应按照以下哪一线序进行制作? A.绿白,绿&#xff0c;蓝&#…...

Hands-on Machine Learning with Scikit-Learn,Keras TensorFlow

读书记录(缓慢更新) 目录 Part 1. The Fundamentals of Machine Learning The Content of The Machine Learning Landscape The Machine Learning Landscape Part 1. The Fundamentals of Machine Learning The Content of The Machine Learning Landscape Part 1. The F…...

242. 有效的字母异位词

这篇文章会收录到 :算法通关村第十二关-白银挑战字符串经典题目-CSDN博客 242. 有效的字母异位词 描述 : 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意&#xff1a;若 s 和 t 中每个字符出现的次数都相同&#xff0c;则称 s 和 t …...

TUP通信——与多个客户端同时通信

一&#xff0c;概括&#xff1a;可以通过多线程思想每加一个客户端由线程池中的主线程交给一个子线程管理 二&#xff0c;案例 &#xff08;1&#xff09;&#xff0c;线程池 &#xff08;2&#xff09;&#xff0c;服务端 &#xff08;3&#xff09;&#xff0c;客户端...

基于helm的方式在k8s集群中部署gitlab - 备份恢复(二)

接上一篇 基于helm的方式在k8s集群中部署gitlab - 部署&#xff08;一&#xff09;&#xff0c;本篇重点介绍在k8s集群中备份gitlab的数据&#xff0c;并在虚拟机上部署相同版本的gitlab&#xff0c;然后将备份的数据进行还原恢复 文章目录 1. 备份2. 恢复到虚拟机上的gitlab2.…...

B树与B+树的对比

B树&#xff1a; m阶B树的核心特性&#xff1a; 树中每个节点至多有m棵子树&#xff0c;即至多含有m-1个关键字根节点的子树数属于[2, m]&#xff0c;关键字数属于[1, m-1]&#xff0c;其他节点的子树数属于 [ ⌈ m 2 ⌉ , m ] [\lceil \frac{m}{2}\rceil, m] [⌈2m​⌉,m]&am…...

关键路径-STL版/拓扑排序 关键路径【数据结构】

关键路径-STL版 题目描述 给定有向图无环的边信息&#xff0c;求每个顶点的最早开始时间、最迟开始时间。 输入 第一行图的顶点总数 第二行边的总数 第三行开始&#xff0c;每条边的时间长度&#xff0c;格式为源结点 目的结点 长度 输出 第一行&#xff1a;第个顶点的最早…...

最新AI创作系统ChatGPT系统运营源码,支持GPT-4图片对话能力,上传图片并识图理解对话,支持DALL-E3文生图

一、AI创作系统 SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧&#xff01;本系统使用NestjsVueTypescript框架技术&#xff0c;持续集成AI能力到本系统。支持OpenAI DALL-E3文生图&#xff0c;…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问&#xff08;基础概念问题&#xff09; 1. 请解释Spring框架的核心容器是什么&#xff1f;它在Spring中起到什么作用&#xff1f; Spring框架的核心容器是IoC容器&#…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖

在Vuzix M400 AR智能眼镜的助力下&#xff0c;卢森堡罗伯特舒曼医院&#xff08;the Robert Schuman Hospitals, HRS&#xff09;凭借在无菌制剂生产流程中引入增强现实技术&#xff08;AR&#xff09;创新项目&#xff0c;荣获了2024年6月7日由卢森堡医院药剂师协会&#xff0…...