慢接口分析与优化总结

news2024/11/24 7:04:46

文章目录

  • 1. 慢接口优化的意义
  • 2. 接口耗时构成
  • 3. 优化技巧
    • 3.1. 内部代码逻辑
      • 异步执行[异步思想]
      • 并行优化
      • 拒绝阻塞等待
      • 预分配与循环使用[池化思想]
      • 线程池合理设计
      • 锁粒度避免过粗
      • 优化程序结构
    • 3.2. 缓存
      • 恰当引入缓存[空间换时间思想]
      • 缓存延迟优化
      • 提前初始化缓存[预取思想]
    • 3.3. 数据库 DB
      • 批量读写[批量思想]
      • 索引
        • SQL 没加索引
        • 索引没生效
        • 索引设计不合理
      • 慢 SQL 优化
        • SQL 语句优化
        • 其他优化思路
      • 避免大事务问题
      • 深分页问题
        • 游标记录法
        • 延迟关联法
    • 3.4. 调用下游服务
      • 批量调用
      • 拒绝无效等待
    • 3.5. 网络
      • 流量控制
      • 压缩传输内容
    • 3.6. 服务器资源

本文参考:

  1. 从11s到170ms!看看人家的接口优化技巧
  2. MySQL进阶-----order by优化和group by优化

1. 慢接口优化的意义

在工作的过程中,很多时候不只是完成产品的业务需求,技术需求也是需要关注到的点。那其实在系统发展的过程中,业务复杂度增加、业务数据量提升,经常就会出现一些请求响应时间变长的接口,这都会给用户带来不好的体验。“慢接口优化”应运而生,能够显著提升页面响应速度,提高系统的并发能力。

在这里插入图片描述

2. 接口耗时构成

首先画一张架构图,看下接口从 Client 到 Server 经历了哪些步骤,我们又可以针对各个步骤做哪些方面的优化:

在这里插入图片描述

下面的优化技巧就针对步骤涉及到的不同方面进行优化。

3. 优化技巧

3.1. 内部代码逻辑

异步执行[异步思想]

对于一些非核心业务逻辑,如果这部分逻辑执行时间长且不影响主流程,可以让其异步执行,主流程先返回,从而降低接口耗时。或者是主流程线程继续执行,不关心异步执行的逻辑。

比方说:用户领取红包以后写入不同运营人群是发的人群异步消息,数据库插入完成以后同步至 ES 是发的异步消息

具体来说,有如下几种实现异步的方式:

  • 使用消息队列,将非核心业务逻辑作为消息放入消息队列执行,主业务逻辑发完消息就继续执行;甚至可以直接将原先定时任务的刷数接口,通过 DTS 数据传输工具,将其从 hive 底表的单条记录转换为 MQ 消息,完成异步化改造
  • 使用异步线程或者线程池,执行主流程以外的逻辑,比如 Kafka/Zookeeper/Tomcat 等组件中有专门负责网络 IO 的异步线程,异步地从发送队列中拉取请求发送;或者是程序内部需要用到飞书导出的 API,启动时起一个异步定时线程池,定期地拉取飞书 token
  • 使用事件监听机制(观察者模式),主流程触发事件,监听者监听对应的事件异步执行自定义逻辑,比如 Spring 中的 EventListener 监听器、Zookeeper 中的 watcher 监听者
  • 部署定时任务或者微服务,在主流程以外的实例中部署,异步地执行非核心业务逻辑,部署资源消耗较大

并行优化

在设计接口的时候,如果使用串行逻辑,一个程序块执行完再执行另一个程序块,任务只能顺序执行,接口耗时较大,整体吞吐量收到限制。如果观察到程序块之间没有严格的时序关系,比方说多次 RPC 远程调用下游,则可以将远程调用改为并行执行。常用的做法就是通过线程池提交多次 RPC 任务返回 Future 函数,然后在主程序中 get 这多个并行任务。n 次 RPC 调用理论上就可以提高 n 倍的吞吐量。

如果并行处理过程中还需要访问共享资源,需要进行并发控制,比方说多次并行的结果放入同一个 List 中,可以加锁,或者通过 CAS 算法,或者直接使用线程安全的容器,比如 SynchronizedList、ConcurrentHashMap 等。

拒绝阻塞等待

当接口需要调用调用下游的 RPC 接口(或者是前端调用后端接口),但是下游接口需要 10s 甚至更多才能返回,我们的主程序又通常都是同步阻塞等待下游 RPC返回,这会极大拖慢接口的耗时。甚至在某些有接口调用时间阈值限制的场景下会被动断开连接,给用户造成不好的体验。之前我们的项目中就存在前端组件调用后端接口,如果后端接口 15s 内没返回就会被组件强制断开连接,最终导致前端超时获取不到数据的情况。这里可以后端接口先返回,等后端逻辑处理完以后,再通过消息触发前端查询(前端架构可以感知消息),这样就可以将前端阻塞等待的时间缩小。

还可以参考 CompletableFuture 中的 whenComplete 方法也有回调机制,不需要主流程在这里阻塞等待,等前面的操作执行完会自动执行回调操作;还有网络 IO 中的 NIO 模型,read()/write() 在非阻塞的模式下,IO 没有数据时接口直接返回,主线程可以继续去遍历其他已经有就绪事件的 IO。

预分配与循环使用[池化思想]

「线程池」就是池化思想的集大成者,它可以帮助我们管理线程,避免增加线程和销毁线程的资源消耗。每次需要用到线程的时候都去创建一个新的线程,会增加耗时,也会增大资源开销,那么线程池就是很好的复用了已经创建的线程(其中核心线程被创建出来一般不会被回收,当然想回收也可以通过参数 allowCoreThreadTimeOut 配置)。

使用到线程池的场景也很多,包括 MySQL 的 HikariCP 数据库连接池、 HttpClient 的连接池等。

这体现的是分配完以后可以循环使用的思想,也出现在 Http 网络连接中,通过配置 Keep-Alive 长连接,每次 Http 请求可以复用前面三次握手后建立的 TCP 连接,避免资源的频繁销毁创建。

线程池合理设计

在前面池化思想中就提到过「线程池」,线程池可以帮忙并行处理任务,提高接口响应速度;帮我们管理线程,避免创建和销毁线程的资源开销;甚至可以起到削峰的作用,因为其本身就自带有一个阻塞队列。

合理的设计线程池,可以让我们更高效地处理任务,但是如果线程池设计的不合理,会极大影响我们的接口效率,甚至拖垮接口的响应时间。我们可以重点关注线程池七个参数中的这几个参数:核心线程数、最大线程数、阻塞队列

线程池设计可以注意下面几点:

  • 如果核心线程数过小,不能够达到很好的并行效果。如果核心线程数太大,需要考虑机器资源是否足够,避免占用太多的 CPU/IO 资源劣化系统稳定性;此外,还需要考虑下游的承载能力,核心线程的大量请求是否会打挂下游
  • 阻塞队列不宜过小,因为这样子起不到缓冲削峰的作用,线程池也会更容易创建大量的非核心线程,增大线程池创建开销。阻塞队列也不可过大,如果核心线程处理速度不足,大量的任务阻塞在队列中如果不能及时处理,前端的请求就会发生超时,并且阻塞队列过大还容易导致 OOM
  • 线程池还需要注意「隔离」,不同线程池各司其职,不然上述的问题就会互相扰乱,核心业务的稳定性也会被边缘业务拖垮

锁粒度避免过粗

锁本质是为了让代码块“串行化”,那么在串行化的代码块中加入定制的逻辑,就可以实现效率上的“做一次”或者是正确性上的”避免并发问题“。但是如果加锁的力度过粗,是很影响接口性能的。

不管你是synchronized加锁还是redis分布式锁,只需要在共享临界资源加锁即可,不涉及共享资源的,就不必要加锁。这就好像你上卫生间,不用把整个家都锁住,锁住卫生间门就可以了。

比如,在业务代码中,有一个ArrayList因为涉及到多线程操作,所以需要加锁操作,假设刚好又有一段比较耗时的操作(代码中的slowNotShare方法)不涉及线程安全问题。反例加锁,就是一锅端,全锁住:

//不涉及共享资源的慢方法
private void slowNotShare() {
    try {
        TimeUnit.MILLISECONDS.sleep(100);
    } catch (InterruptedException e) {
    }
}

//错误的加锁方法
public int wrong() {
    long beginTime = System.currentTimeMillis();
    IntStream.rangeClosed(1, 10000).parallel().forEach(i -> {
        //加锁粒度太粗了,slowNotShare其实不涉及共享资源
        synchronized (this) {
            slowNotShare();
            data.add(i);
        }
    });
    log.info("cosume time:{}", System.currentTimeMillis() - beginTime);
    return data.size();
}

正例:

public int right() {
    long beginTime = System.currentTimeMillis();
    IntStream.rangeClosed(1, 10000).parallel().forEach(i -> {
        slowNotShare();//可以不加锁
        //只对List这部分加锁
        synchronized (data) {
            data.add(i);
        }
    });
    log.info("cosume time:{}", System.currentTimeMillis() - beginTime);
    return data.size();
}

优化程序结构

优化程序逻辑、程序代码,是可以节省耗时的。比如,你的程序创建过多不必要的对象、或者程序逻辑混乱,多次重复查数据库、又或者你的实现逻辑算法不是最高效的,等等。

我举个简单的例子:复杂的逻辑条件,有时候调整一下顺序,就能让你的程序更加高效。

假设业务需求是这样:如果用户是会员,并且第一次登陆时,需要发一条感谢短信。如果没有经过思考,代码直接这样写了

if(isUserVip && isFirstLogin){
    sendSmsMsg();
}

假设有5个请求过来,isUserVip判断通过的有3个请求,isFirstLogin通过的只有1个请求。那么以上代码,isUserVip执行的次数为5次,isFirstLogin执行的次数也是3次,如下:

在这里插入图片描述

如果调整一下isUserVipisFirstLogin的顺序:

if(isFirstLogin && isUserVip){
    sendMsg();
}

isFirstLogin执行的次数是5次,isUserVip执行的次数是1次:

在这里插入图片描述

本质上就是漏斗结构,往下钻满足条件的请求不断变小的过程。

【注意】不过有时候程序结构的调整也需要考虑逻辑判断背后服务的负载能力:

假设业务需求是这样的:用户在具有开卡资格以后(当前状态未开卡为普通用户),普通用户和会员用户会展示不同的文案

考虑漏斗模型和接口的时效性要求,我原先的判断流程图如下,Redis 判断是否具有开卡资格,进入分支以后才会使用 RPC,Redis 中拥有开卡资格的人是较少的。但是这里 Redis 判断用的是我们业务的集群,承接的却是进入业务的全站流量,这部分量级是很夸张的,Redis 使用率直线飙升到 70% 以上,会影响业务的稳定性,在大促场景下甚至会直接打挂业务集群。

在这里插入图片描述

最终调整程序流程图如下,由 RPC 人群服务承接全站的流量,该服务具有上千台机器,服务的稳定性不会有问题。

这其实也是“性能”和“稳定性”的权衡,在追求一味时效性的同时,也需要合理评估服务集群的承载能力。

在这里插入图片描述

3.2. 缓存

恰当引入缓存[空间换时间思想]

对于一些非实时更新的数据(更新频率可能是小时级、天级甚至月级),我们往往可以将它们放入缓存,避免频繁读取数据库;有时候也可以避免调用外部服务的时间过长,提高接口响应速度;分布式缓存如 Redis 的承载能力也远大于 Mysql 等关系型数据库,也常用来做大流量的提前分流治理。

前面的缓存包括分布式缓存、本地缓存:

  • 分布式缓存,如 Redis、Memcached

    省钱卡包红包领取数据、日级更新数据接口的结果都可以使用分布式缓存,通过缓存过期、job定时任务刷新、监听 binlog/领域事件、主动更新等方式,保证缓存的时效性,以及缓存与数据库的一致性。

  • 本地缓存,如 JVM 本地缓存、Map 缓存,甚至可以延伸到类中的一个字段变量

    相比于分布式缓存,本地缓存建议存放数据量极小且不经常更新甚至不更新的数据,比如销售行业信息、销售部门信息等

缓存延迟优化

以 Redis 为例,当我们本身使用不当或者运维不合理的时候,可能导致 Redis 访问延迟变大。可能存在如下原因:

  1. 使用了复杂度过高的命令,如 sort、sunion 等

  2. 操作、存储大的 bigkey

    bigKey 的危害有如下五个方面:

    • 内存空间分配不平衡:在 Redis Cluster 中,bigKey 所在的分片会占用更多内存,数据和查询的倾斜会导致该分片的稳定性下降
    • 客户端超时阻塞:由于 Redis 执行命令是单线程,在操作 bigKey 时会比较耗时,那么客户端就会超时
    • 网络阻塞:每次获取 bigKey 产生的网络流量较大,在固定的带宽下,分配给别的服务的网络资源就会更少
    • 阻塞工作线程:主动删除 del bigKey,被动过期删除、数据迁移时,都会由于处理大 key 时间较长,会阻塞后续命令的执行
  3. 大量数据集中过期,集中过期导致 Redis 响应慢的原因与 Redis 的过期策略有关。

  4. Redis 实例内存使用达到了上限

  5. Fork 子进程导致响应变慢,Redis 生成 RDB 和 AOF 都需要父进程 fork 出一个子进程进行数据的持久化,父进程需要拷贝内存页表给子进程,因此 Redis 内存不宜设置太大,一般不超过 10G,实例越大,Fork 耗时越长,在 Fork 完成之前,整个实例都会被阻塞住。

提前初始化缓存[预取思想]

把未来可能需要的数据获取好,提前放到本地缓存中,需要的时候,直接去取就可以,而不需要实时计算,这样子就可以大幅度减少接口耗时。这样子可能在启动的时候会耗费一定的时间去预取数据,即使后面程序中并没有用到对应的数据。

比如在项目代码中实现 Spring 的 ApplicationListener 接口,监听 ContextRefreshEvent 事件,重写其中的 onApplicationEvent 方法,可以在 Spring 容器初始化以后提前读取销售的行业信息,并缓存到本地 Map 中,需要的时候直接从本地缓存中取就可以。

还比如在配置中心比如 Nacos、TCC(字节内部配置组件)、Diamond(阿里内部配置组件),会在 Spring 启动时由 BeanPostProcessor 去解析注解,根据注解中的定位信息,从远程服务器拉取对应的配置,放入本地实例的变量中,需要的时候直接用就可以。

3.3. 数据库 DB

批量读写[批量思想]

循环内的 insert、查询可能会因为数据量的增长导致接口性能急剧下降。将循环内的查询、insert 改成批量操作,可以大大减少数据库的访问时间,缩减系统与DB之间的连接、关闭等消耗。此外,也可以根据实际的业务场景,将大批次的数据操作,改成小批次的数据操作。

Demo:

// 优化前:循环入库性能低下
for(Employee employee: employeeList) {
  insert(employ);
}

// 优化后: 批量入库
batchInsert(employeeList);

索引

提到接口优化,我们第一时间都会想到 DB 的索引优化,索引优化确实是成本最小的优化,而且一般效果都不错。我们可以从下面几个维度去思考:

SQL 没加索引

开发的时候,容易疏忽忘记给 SQL 添加索引,所以写完 SQL 的时候,我们就可以顺便 explain 看下执行计划。

一般加索引就看下面的字段:

SQL 的 where 字段 + order by/group by 后面的字段

其中 order by/group by 字段也符合最左匹配原则,参考 MySQL进阶-----order by优化和group by优化

索引没生效

即使加了索引,有些索引也会失效,下面就是索引失效的十大场景:

在这里插入图片描述

索引设计不合理

索引不是越多越好,需要合理设计

  • 删除冗余和重复的索引,减少索引占用空间
  • 索引一般不超过 5 个
  • 索引不适合建立在区分度不高的字段上,比如性别字段
  • 适当使用覆盖索引,避免回表查询
  • 如果需要使用force index强制走某个索引,那就需要思考你的索引设计是否真的合理了

慢 SQL 优化

SQL 语句优化

在这里插入图片描述

其他优化思路

当 SQL 语句本身无法提供更好的优化效果,可以考虑优化整体查询架构,复杂/大表场景可以

  • 区分冷热数据分档
  • 分库分表
  • 迁移 ES/Hive 等。

避免大事务问题

为保证数据库多个操作的原子性,在涉及到数据库修改的时候,我们常会用 Java Spring 中的 @Transactional 声明式事务,

@Transactional
public int createUser(User user){
    // insert
    userDao.save(user);
  	// update
    passCertDao.updateFlag(user.getPassId());
    return user.getUserId();
}

但是如果事务中穿插着其他 RPC/MQ 等非 DB 操作,耗时会比较大,从而出现大事务问题

@Transactional
public int createUser(User user){
    // insert
    userDao.save(user);
  	// 远程调用
  	RPC/MQ
  	// update
    passCertDao.updateFlag(user.getPassId());
    return user.getUserId();
}

大事务的危害:

  1. 并发情况下,数据库连接资源容易耗尽
  2. 锁定数据较多,容易造成大量阻塞和锁超时,进而接口超时
  3. 执行时间长,容易造成主从延迟
  4. 回滚所需要的时间变长

那么大事务又是如何产生的呢?

  1. 单个事务操作数据库操作较多
  2. 事务中存在 RPC/MQ 等非 DB 耗时操作
  3. 大量的锁竞争

我们可以通过如下方案规避大事务:

  • 查询操作,尽可能放到事务外
  • RPC/MQ 等非 DB 操作放到事务外面,我有一篇文章也专门讲了 @Transactional 声明式事务回调编程,可以尽可能使代码结构改动缩小
  • 事务中避免处理太多的数据

深分页问题

一个典型的深分页例子如下:

select id, name from account where create_time > '2024-10-07' limit 100000, 10;

假设基于 create_time 建立了二级索引 idx_create_time。

limit 100000, 10 offset = 100000 足够深了,MySQL 执行计划会扫描整个二级索引树,拿出满足 create_time > ‘2024-10-07’ 条件的 100000 + 10 行,每行逐个回表拿出主键索引树上的所有字段,即 100000 + 10 条记录的字段并选出 id, name,然后丢弃前 100000 条记录。这么多次回表显然是无法接受的。

可以通过“游标记录法”和“延迟关联法”来优化深分页问题。

游标记录法

记录上次查询满足条件的记录最大 id,比方说就是等于 100000,则 SQL 语句可以修改为:

select id, name from account where id > 100000 limit 10

这样子可以直接在主键索引树上扫描,where 条件直接过滤了,只会取出来 10 条记录,减少了回表次数。但前提是:需要有一种自增的字段(这里是 id,或者已经 order by 的字段)

延迟关联法

可以理解成把二级索引树上的条件转移到主键索引树上来,减少回表次数。优化 SQL 语句如下:

select acct1.id, acct1.name from account acct1 INNER JOIN (select a.id from account a where a.create_time > '2024-10-07' limit 100000, 10) AS acct2 on acct1.id = acct2.id;

先通过二级索引树 idx_create_time 上的覆盖索引查询到满足条件的主键 ID(不需要回表),然后与原表内连接,这样相当于也是直接走了主键索引,减少了回表次数。

3.4. 调用下游服务

批量调用

这个思想同 DB 的批量读写,将多次 RPC 调用转换为批量查询,能有效减少网络建连、通信的耗时。

拒绝无效等待

当下游服务接口发生抖动的时候,若当前主程序一直等待,可能会一直拿不到接口返回从而无法响应。超时控制可以让程序在一定时间内必须返回,当下游服务劣化的时候不会影响当前业务的稳定性。

3.5. 网络

流量控制

通过限流算法、降级措施,避免不必要的流量进入,保障接口的正常响应。

压缩传输内容

Http 通常采用 gzip 压缩算法压缩 HTML、JavaScript、CSS 等文件,但是当传输内容仍然较大的时候,前后端可以联合协商传输的 JSON 结构,减少不必要字段的传输。甚至在调用中间件的时候,也可以将操作的内容压缩,比如 Redis 存储 value 的时候,可以提前使用 GZIP 将 value 压缩,然后设置 key - value, get 的时候解压缩对应的内容。

3.6. 服务器资源

  • FullGC 问题:大批量导出 Excel,当 Excel 过大时,就会把 JVM 卡死
  • 线程池打满也会让多余的请求处于等待状态,因此高并发场景需要限流,把多余的请求直接拒绝掉
  • 程序打开的 IO 连接也要记得及时关闭,否则也会占用 IO 资源,影响进程的切换速度

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

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

相关文章

工具函数(截取文本第一个字为图片)

const subStringToImage (params) > {const { str ,color #FFF,background #4F54FF,size 60,fontSize 20 } paramsif(str.length < 0) return console.error(字符不能为空!)const text str.slice(0, 1)const canvas document.createElement(canvas)const ctx …

github 国内文件加速下载

参看;https://www.cnblogs.com/ting1/p/18356265 在源网址前加上 https://hub.gitmirror.com/ 或https://mirror.ghproxy.com/&#xff0c;例如&#xff1a; https://hub.gitmirror.com/https://github.com/t1m0thyj/WinDynamicDesktop/releases/download/v5.4.1/WinDynamicD…

算法题总结(十)——二叉树上

#二叉树的递归遍历 // 前序遍历递归LC144_二叉树的前序遍历 class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> result new ArrayList<Integer>(); //也可以把result 作为全局变量&#xff0c;只需要一个函数即可。…

公开数据集网站分享

参考链接&#xff1a;常用的医学组织切片细胞图像数据集_细胞分割数据集-CSDN博客文章浏览阅读1.3w次&#xff0c;点赞32次&#xff0c;收藏133次。乳腺癌细胞图像数据集、血细胞图像数据集、HE染色切片、疟疾细胞图像图像识别、分类、分割_细胞分割数据集https://blog.csdn.ne…

Redis list 类型

list类型 类型介绍 列表类型 list 相当于 数组或者顺序表 list内部的编码方式更接近于 双端队列 &#xff0c;支持头插 头删 尾插 尾删。 需要注意的是&#xff0c;Redis的下标支持负数下标。 比如数组大小为5&#xff0c;那么要访问下标为 -2 的值可以理解为访问 5 - 2 3 …

Linux dlsym和直接调用函数地址解析分析

dlsym 函数是 Linux 下动态链接库&#xff08;shared library&#xff09;编程中的一个重要函数。它用于在运行时获取动态链接库中符号的地址&#xff0c;通常用于获取函数指针或变量的地址。 以下是 dlsym 函数的基本用法和示例。 1. 函数原型 void *dlsym(void *handle, c…

【超详细】基于YOLOv11的PCB缺陷检测

主要内容如下&#xff1a; 1、数据集介绍 2、下载PCB数据集 3、不同格式数据集预处理&#xff08;Json/xml&#xff09;&#xff0c;制作YOLO格式训练集 4、模型训练及可视化 5、Onnxruntime推理 运行环境&#xff1a;Python3.8&#xff08;要求>3.8&#xff09;&#xff…

ubuntu ssh远程执行k8s命令报错the connection to the server localhost:8080 was refused

修改前&#xff1a; ssh root192.168.31.167 kubectl apply -f /root/jenkinsexcute/saas.demo.api.k8s.yml --recordecho "export KUBECONFIG/etc/kubernetes/admin.conf" >> /root/.bashrc 修改后 添加一段&#xff1a;export KUBECONFIG/etc/kubernetes/a…

【专题】操作系统概述

1. 操作系统的目标和作用 操作系统的目标与应用环境有关。 在查询系统中所用的OS&#xff0c;希望能提供良好的人—机交互性&#xff1b; 对于应用于工 业控制、武器控制以及多媒体环境下的OS&#xff0c;要求其具有实时性&#xff1b; 对于微机上配置的OS&#xff0c;则更看…

什么是强基计划?

“强基计划”是中国教育部于2020年推出的一项全新的高等教育招生改革计划&#xff0c;旨在通过更加科学、公正的选拔机制&#xff0c;选拔出有志于基础学科并具备扎实学科功底、创新潜质的优秀学生&#xff0c;从而推动国家基础学科的发展&#xff0c;提升自主创新能力。与传统…

【自动驾驶】UniAD代码解析

1.参考 论文&#xff1a;https://arxiv.org/pdf/2212.10156 代码&#xff1a;https://github.com/OpenDriveLab/UniAD 2.环境配置 docs/INSTALL.md &#xff08;1&#xff09;虚拟conda环境 conda create -n uniad python3.8 -y conda activate uniad &#xff08;2&#…

微信小程序和抖音小程序的分享和广告接入代码

开发完成小程序或者小游戏之后&#xff0c;我们为什么要接入分享和广告视频功能&#xff0c;主要原因有以下几个方面。 微信小程序和抖音小程序接入分享和广告功能主要基于以下几个原因&#xff1a; 用户获取与增长&#xff1a;分享功能可以帮助用户将小程序内容传播给更多人&…

C语言刷题--有关闰年

满足以下一种即是闰年 能被4整除&#xff0c;但不能被100整除能被400整除 //输入年&#xff0c;月&#xff0c;输出该月的天数 //1月 31,28,31,30,31,30,31,31,30,31,30,31int is_leap_year(int y) {if (((y % 4 0) && (y % 100 ! 0)) || y % 400 0)return 1;return…

步进电机和步进电机驱动器详解

一、步进电机的概念 步进电机是通过步进电机配套的驱动器&#xff0c;将控制器传来的脉冲信号转换成角位移的开环电机&#xff08;没有反馈&#xff09;。 步进电机工作时的位置和速度信号不反馈给控制系统&#xff0c;如果电机工作时的位置和速度信号反馈给控制系统&#xff…

《从零开始大模型开发与微调》真的把大模型说透了!零基础入门一定要看!

2022年底&#xff0c;ChatGPT震撼上线&#xff0c;大语言模型技术迅速“席卷”了整个社会&#xff0c;人工智能技术因此迎来了一次重要进展。与大语言模型相关的研发岗薪资更是水涨船高&#xff0c;基本都是5w月薪起。很多程序员也想跟上ChatGPT脚步&#xff0c;今天给大家带来…

apache.poi读取.xls文件时The content of an excel record cannot exceed 8224 bytes

目录 问题描述版本定位&#xff1a;打印size最大的Record定位&#xff1a;RefSubRecord解决代码 问题描述 使用apache.poi读取.xls文件时有The content of an excel record cannot exceed 8224 bytes的报错。待读取的文件的内容也是通过apache.poi写入的&#xff0c;我的文件修…

【sqlmap】sqli-labs速通攻略

sqli-labs工具速通 Less-1 sqlmap -u http://127.0.0.1:8081/Less-1/?id1 --batch --dbs sqlmap -u http://127.0.0.1:8081/Less-1/?id1 --batch -D security --tables sqlmap -u http://127.0.0.1:8081/Less-1/?id1 --batch -D security -T users --columns sqlmap -u ht…

购物清单 | 双十一加购率最高好物合集,数码购物车必备!

​双十一来临&#xff0c;小伙伴们肯定已经被种草了很多很多清单&#xff0c;开始买买买了&#xff01;但是&#xff0c;作为一个数码博主&#xff0c;怎么能少了数码产品&#xff01;今天我给大家准备了一份数码人专属的购物清单&#xff0c;快来看看吧&#xff01; 运动耳机…

[水墨:创作周年纪念] 特别篇!

本篇是特别篇&#xff01;&#xff01; 个人主页水墨不写bug // _ooOoo_ // // o8888888o // // 88" . "88 …

如何方便地打出「」和『』

比起英文中的引号 ‘’和 “”&#xff0c;我更喜欢使用中文直角引号&#xff1a;「」和 『』。 此外&#xff0c;在港澳台、日本这几个地区中&#xff0c;就经常使用『』和「」&#xff1a; ​ ‍ 注意&#xff1a;不同地区的习惯可能有所不同。在汉语中『』、「」分别为双…