【案例实战】业务稳定性运行之全链路混合压测

1.全链路压测开展步骤

(1)什么是全链路压测

全链路压测是指基于真实业务场景,通过模拟海量的用户请求,对整个后台服务进行压力测试,从而评估整个系统的性能水平。

在这里插入图片描述

  • 对应用程序的整个技术栈进行完整的压力和性能测试,覆盖了整个应用程序的各个部分,包括前端、后端、服务、数据库等。
  • 模拟真实用户交互和**系统环境(生产环境)**的情况下,评估整个应用在高负载情况下的性能表现和持续调优的过程,包括响应时间、吞吐量、资源利用率以及系统的稳定性和可扩展性等。
  • 全链路压测可以帮助发现系统的性能瓶颈和故障点,并对整个系统的性能进行全面评估和优化。

(2)全链路压测的实施步骤

  • 确定压测场景
    • 根据实际使用情况和业务需求,确定要测试的压测场景,包括用户访问路径、操作流程和数据交互等。
    • 每个场景包括一系列用户行为和操作流程,涉及到多个应用程序的不同组件。
    • 区分核心业务和非核心业务,主要精力和压测都是对核心业务进行
  • 流量数据模型准备
    • 根据压测场景,构建模拟用户行为的测试脚本。
    • 为模拟用户行为提供必要的测试数据,可以使用真实或合成的数据进行测试,以确保测试的真实性和覆盖性。
    • 做好数据隔离:不能污染正常的业务数据,整理好每个环节的数据流转。
  • 选择工具配置并发
    • 选择合适的流量录制和回放工具,根据实际情况和性能需求,配置并发用户数、请求频率和负载模型等。
    • 可以逐步增加并发负载,以模拟系统在峰值和极限负载下的性能表现。
  • 开始运行压测
    • 运行测试脚本,模拟并发用户对系统进行访问和交互。
    • 观察和记录系统在不同负载情况下的性能指标,例如响应时间、吞吐量、CPU和内存利用率等。
  • 数据分析和优化
    • 分析压测结果,并识别可能的性能问题和瓶颈点。
    • 可以根据分析结果进行调整和优化,例如调整配置参数、进行代码优化或增加系统资源等。
  • 结果评估和报告
    • 根据压测结果进行结果评估,并生成详细的测试报告,报告应包括系统的性能指标、发现的问题和建议的优化方案。
  • 压测现场还原
    • 对相关压测数据进行清理,比如缓存,队列,数据库等,避免长期占用相关资源,定时维护好相关测试数据。

(3)全链路压测实施关键技术难点

  • 流量数据模型:压测数据不真实,达不到生产环境的流量模型,最终导致压测不准确,比如只是拍脑袋想的用户访问路径。
  • 业务探针性能:全链路压测涉及到各个方法调用链的监听,会有数据采集上报,离不开APM调用链路追逐工具,需要降低本身的损耗。
  • 流量染色:流量请求和访问链路长,涉及业务多,需要多个系统代码改造,减少压测代码侵入性。
  • 数据隔离:避免影响线上正式用户,比如数据库,MQ,缓存,外部三方服务等。
  • 日志隔离:业务日志输出到文件或者到队列,避免压测数据采集后加入到正式的报表里面,导致运营和产品分析数据不准确。
  • 压测时间:一般在业务访问低峰进行,比如国外的话就是半夜2~6点,国外业务就根据时差进行调整。

2.数据模型准备之流量回放

(1)什么是流量回放

流量回放就是通过记录线上流量,在开发或者测试环境回放,来发现系统是否能够正常运行,降低代码变动整体系统带来的风险。

(2)流量回放工具GoReplay

  • 官网:https://goreplay.org/

  • github:https://github.com/buger/goreplay

  • GoReplay是GO语言编写的http流量复制工具,使用流程简单,支持多个系统,mac、linux、win。

  • GoReplay 不是代理,而是在后台侦听网络接口上的流量,无需更改生产基础架构,只需在与服务相同的机器上运行 GoReplay 守护程序。

在这里插入图片描述

  • 流量录制重放特点
    • 捕获网络指定端口流量,输出到控制台
    • 捕获网络指定端口流量,将原始流量实时重放到其他环境中
    • 捕获网络指定端口流量,并保存到文件中
    • 捕获网络指定端口流量,请求过滤指定路径流量,并保存到文件中

(3)Linux服务器安装Go环境和GoReplay

  • Go语言包下载地址:https://studygolang.com/dl
# 下载之后解压
tar -C /usr/local -zxvf go1.5.3.linux-amd64.tar.gz
  • 配置go语言环境变量
# 打开文件
vim /etc/profile
# 添加环境变量
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
# 编译生效
source /etc/profile
# 测试
go version
# go version go1.21.5 linux/386
  • GoReplay下载二进制包地址:https://github.com/buger/gor/releases
  • 下载1.3.1版本,下载之后解压
tar xvzf gor_1.3.1_x64.tar.gz 
  • 解压完压缩包后,可以从当前目录进行Gor,也可以将Gor文件复制到的PATH文件下
  • ./gor 为执行文件

(4)使用方式

  • 参数介绍
输入
--input-raw : 用于捕获 HTTP 流量时,应指定 IP 地址或界面以及应用程序端口
--input-file :接收以前使用过的文件记录
--input-tcp :如果决定将多个转发器Gor实例转发流量到它,Gor聚合实例使用
输出
--output-http :重播HTTP流量到给定的端点
--output-file :记录传入到文件的流量
--output-tcp :将传入的数据转发到另一个Gor实例
--output-stdout :用于调试,输出所有数据。
  • 监听本地服务端口 8082 流量保存到本地的文件
./gor --input-raw :8082 --output-file=requests.gor
  • 将流量从文件回放到其他服务
./gor --input-file requests.gor --output-http="http://ip:8082"
  • 将 http 的请求打印到终端
./gor --input-raw :8082 --output-stdout
  • 将 http 的请求实时转发到其他服务
./gor --input-raw :8082 --output-http="http://ip:8082"
  • 将流量放大或减少转发给其他服务端口
./gor --input-file "requests.gor|200%" --output-http="http://ip:8082"
./gor --input-file "requests.gor|20%" --output-http="http://ip:8082"
  • 过滤指定请求
./gor --input-raw :80 --http-allow-method GET --output-http http://target_server:8080
./gor --input-raw :8080 --output-http staging.com --http-allow-url /api
  • 只收集请求头中符合 api-version 为 1.0x 的请求
./gor --input-raw :8080 --output-http staging.com --http-allow-headerapi-version:^1\.0\d
  • 对指定的header或url请求进行限流或者加速
./gor --input-tcp :28020 --output-http"http://staging.com|10"# (每秒请求数限制10个以内)
./gor --input-raw :80 --output-tcp"replay.local:28020|10%" # (每秒请求数限制10%以内)
./gor --input-raw :80 --output-tcp"replay.local:28020|10%" --http-header-limiter "X-API-KEY:10%"
./gor --input-raw :80 --output-tcp"replay.local:28020|10%" --http-param-limiter "api_key:10%"
  • 将流量转发到多个站点
./gor --input-raw :80 --output-http "http://target_server:8080"--output-http "http://target_server2:8080"
  • 将相同的流量发送到多个站点,并且平分所有流量
./gor --input-raw :80 --output-http "http://staging.com" --output-http "http://dev.com"--split-output true

3.全链路压测之流量染色

(1)什么是流量染色

流量染色就是让压测流量可以被程序代码识别,方便做好数据隔离。对压测的请求增加特色的流量标识,比如请求里面增加url参数或header增加请求头。区分压测流量和真实流量,正常用户不会访问到压测数据,压测数据不会影响正式业务。染色后的压测流量,产生的数据可以再压测结束后直接清理。

(2)流量链路改造

在这里插入图片描述

  • 数据库隔离

    • 压测产生的数据需要和真实数据库的进行隔离,一般采用数据库的影子库、影子表进行隔离。

    • 具体来说,影子库是生产环境数据库的一份完整拷贝,包含与生产环境相同的表结构和数据。

    • 影子表是在影子库中创建的与生产环境表相对应的测试表,压测的数据进入影子表。

    • 生产和压测环境的隔离,通过在压测环境中使用影子库和影子表,可以避免对生产环境数据的直接修改和干扰。

    • 完整的数据环境,通过生成影子库和影子表的完整拷贝,全链路压测的时候可以在准确、真实的数据环境中进行工作。

    • 线上问题还原,当线上出现问题时,可以使用影子库和影子表进行问题还原和分析,在相同数据环境中重现问题。

  • 消息队列隔离

    • 业务产生消息到MQ后,消费者会进行消费,压测过程产生的数据不能直接投递到MQ中。

    • 一般是采用队列隔离或者消息隔离,隔离策略也是基于消息的生产者封装方法进行投递。

    • 队列隔离:创建不同的消息队列,压测的队列和正式的队列采用不同的前缀进行区分。

    • 消息隔离:消息里面增加参数,标记消息是否是压测还是正式的数据。

  • 缓存隔离

    • 缓存里面的数据隔离,对key进行区分,根据流量标识是否是压测流量,增加相关的key前缀标识。

    • 不直接操作redis,而是封装redis工具类,在工具类里面判断是否是压测流量,里面对key的读写进行操作。

(3)流量标识透传

流量标识透传是一种将请求上下文信息从发起端(如客户端)传递到目标端(如后端服务)的方案。可以在测试过程中追踪和识别请求的来源,并对不同的请求进行分类和分析。压测流量全部带标识,结合拦截器,存储在ThreadLocal里面进行不同服务直接传递。

(4)流量标识透传方案

  • HTTP Header:将请求上下文信息添加到HTTP请求的Header中。常用的Header字段包括:
    • X-Request-ID:请求标识ID,用于唯一标识每个请求。
    • X-Trace-ID:链路追踪ID,用于追踪请求在分布式系统中的路径。
    • X-Forwarded-For:客户端真实IP地址,用于透传客户端IP。
    • 其他自定义的Header字段用于传递其他请求上下文信息。

(5)跨服务器之间流量传递

在这里插入图片描述

  • 微服务里面采用了Fegin进行RPC调用,在发送请求前可以从ThreadLocal里面的进行获取相关标识符进行
  • Dubbo/GRPC都类似,将请求上下文信息绑定到线程上下文,以实现在不同服务间的透传

4.全链路混合压测环境搭建

(1)阿里云docker部署mysql

docker run -d -p 3306:3306 --name mysql --privileged=true -v /data/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=mysql8test. mysql:8.0.23
  • 新建数据库,一个正式库,一个影子库(shadow)。

在这里插入图片描述

  • 订单库 order、shadow_order
CREATE TABLE `product_order` (`id` bigint NOT NULL AUTO_INCREMENT,`product_id` bigint DEFAULT NULL,`product_title` varchar(255) DEFAULT NULL,`amount` int DEFAULT NULL,`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
  • 商品库 product、shadow_product
CREATE TABLE `product` (`id` bigint NOT NULL AUTO_INCREMENT,`title` varchar(255) DEFAULT NULL,`stock` int DEFAULT NULL,`amount` int DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 分别在两个库中加入数据
INSERT INTO `product` (`id`, `title`, `stock`, `amount`) VALUES (1, '名称', 200, 10);

在这里插入图片描述

(2)阿里云docker部署RabbitMQ

docker run -d --name rabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=password -p 15672:15672 -p 5672:5672 rabbitmq:3.8.9-management

在这里插入图片描述

(3)阿里云docker部署Redis

docker run -itd --name redis -p 6379:6379 -v /mydata/redis/data:/data redis:7.0.8 --requirepass 123456

在这里插入图片描述

(4)阿里云docker部署Nacos

docker run -d -e MODE=standalone -e JVM_XMS=128m -e JVM_XMX=128m -e JVM_XMN=128m -p 8848:8848 -p 9848:9848 --restart=always --privileged=true --name nacos nacos/nacos-server:v2.2.3

在这里插入图片描述

5.全链路混合压测项目搭建

(1)需求模块划分

  • 微服务业务划分:商品服务、订单服务

在这里插入图片描述

(2)业务逻辑

在这里插入图片描述

在这里插入图片描述

  • 我们就以蓝色的用户行为链路进行测试。

(3)流量染色和RPC透传

  • 压测流量全部带标识,结合拦截器,存储在ThreadLocal里面进行不同服务直接传递。
/*** @author lixiang* @date 2024/1/6 15:50*/
@Slf4j
public class RequestInterceptor implements HandlerInterceptor {public static TransmittableThreadLocal<Integer> threadLocal = new TransmittableThreadLocal<>();@Overridepublic boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response,@NonNull Object handler) throws Exception {//前端在header中传入test_flag标识来区分是否为正式流量或者压测流量String testFlag = request.getHeader("test_flag");if(StringUtils.isNotBlank(testFlag)){//通过threadLocal传递信息log.info("压测流量,path = {}",request.getRequestURI());threadLocal.set(1);}else{log.info("正式流量,path = {}",request.getRequestURI());threadLocal.set(0);}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)  {threadLocal.remove();}
}
  • RPC调用feign透传参数
/*** @author lixiang* @date 2024/1/6 15:50*/
@Configuration
public class FeignConfig implements RequestInterceptor {@Overridepublic void apply(RequestTemplate requestTemplate) {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();assert attributes != null;HttpServletRequest request = attributes.getRequest();Enumeration<String> headerNames = request.getHeaderNames();if (headerNames != null) {while (headerNames.hasMoreElements()) {String name = headerNames.nextElement();String values = request.getHeader(name);requestTemplate.header(name, values);}}}
}

(4)缓存隔离

  • 缓存里面的数据隔离,对key进行区分,根据流量标识是否是压测流量,增加相关的key前缀标识。
    /*** 构建 key,用于区分压测和正式流量* @param key* @return*/private String buildFinalKey(String key){Integer testFlag =  RequestInterceptor.threadLocal.get();String finalKey = "";//压测流量if(testFlag !=null && testFlag ==1){finalKey = "shadow:"+key;}else {finalKey = key;}return finalKey;}

(5)消息队列隔离

  • 采用队列隔离或者消息隔离,隔离策略也是基于消息的生产者封装方法进行投递

在这里插入图片描述

		/*** 构建 routingKey的时候,用于区分压测和正式流量* @param routingKey* @return*/private String buildFinalRoutingKey(String routingKey){Integer testFlag =  RequestInterceptor.threadLocal.get();String finalKey = "";//压测流量if(testFlag !=null && testFlag ==1){finalKey = "SHADOW."+routingKey;}else {finalKey = routingKey;}return finalKey;}
  • 监听器这块用于区分不同的队列
@Slf4j
@Component
public class OrderMQListener {@RabbitListener(queuesToDeclare = { @Queue("ORDER_QUEUE") })public void orderQueue(ProductOrderDO productOrderDO, Message message, Channel channel) throws IOException {log.info("监听到正式消息:{}",message);long msgTag = message.getMessageProperties().getDeliveryTag();handleOrderMsg(productOrderDO);channel.basicAck(msgTag,false);}@RabbitListener(queuesToDeclare = { @Queue("SHADOW_ORDER_QUEUE") })public void shadowOrderQueue( ProductOrderDO productOrderDO,Message message, Channel channel) throws IOException {log.info("监听到影子消息:{}",message);long msgTag = message.getMessageProperties().getDeliveryTag();handleOrderMsg(productOrderDO);channel.basicAck(msgTag,false);}private void handleOrderMsg(ProductOrderDO productOrderDO){String type = "1".equals(productOrderDO.getType())?"正式逻辑":"压测逻辑";log.info("{}-处理订单消息",type);}
}

(6)数据源隔离

在这里插入图片描述

  • 双数据源配置类
/*** @author lixiang* @date 2024/1/6 15:50*/
@Configuration
public class DynamicDataSourceConfig {public static final String MASTER = "MASTER";public static final String SHADOW = "SHADOW";@Bean("masterDataSourceProperties")@ConfigurationProperties("spring.datasource.master")public DataSourceProperties masterDataSourceProperties() {return new DataSourceProperties();}@Bean("masterDataSource")@ConfigurationProperties(prefix = "spring.datasource.master.hikari")public HikariDataSource primaryDataSource() {return masterDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();}@Bean("shadowDataSourceProperties")@ConfigurationProperties("spring.datasource.shadow")public DataSourceProperties shadowDataSourceProperties() {return new DataSourceProperties();}@Bean("shadowDataSource")@ConfigurationProperties(prefix = "spring.datasource.shadow.hikari")public HikariDataSource secondaryDataSource() {return shadowDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();}}
  • 数据源路由配置
/*** @author lixiang* @date 2024/1/6 15:50*/
public class DynamicDataSource extends AbstractRoutingDataSource {private static final TransmittableThreadLocal<String> dataSourceContextHolder = new TransmittableThreadLocal<>();/*** 配置DataSource, defaultDataSource为主数据库*/public DynamicDataSource(DataSource defaultDataSource, Map<Object,Object> targetDataSourceMap) {super.setDefaultTargetDataSource(defaultDataSource);super.setTargetDataSources(targetDataSourceMap);super.afterPropertiesSet();}@Overrideprotected Object determineCurrentLookupKey() {return getDataSource();}public static void setDataSource(String dataSource) {dataSourceContextHolder.set(dataSource);}public static String getDataSource() {return dataSourceContextHolder.get();}public static void clearDataSource() {dataSourceContextHolder.remove();}
}
  • 数据源切面区分正式库和测试库
@Slf4j
@Aspect
@Component
public class DataSourceAspect {@Pointcut("execution(public * com.lixiang.controller..*.*(..))")public void controllerPointcut() {}@Before(value = "controllerPointcut()")public void methodBefore(JoinPoint joinPoint) {ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();assert requestAttributes != null;HttpServletRequest request = requestAttributes.getRequest();// 获取请求头String testFlag = request.getHeader("test_flag");// 通过 testFlag 判断if (StringUtils.isNotBlank(testFlag)) {System.out.println("压测流量,影子库,path = "+request.getRequestURI());DynamicDataSource.setDataSource(DynamicDataSourceConfig.SHADOW);} else {System.out.println("正式流量,正式库,path = "+request.getRequestURI());DynamicDataSource.setDataSource(DynamicDataSourceConfig.MASTER);}}
}
  • 启动主类配置数据源路由
    /*** 创建多个数据源对象* @param masterDataSource* @param shadowDataSource* @return*/@Bean@Primarypublic DynamicDataSource dataSource(DataSource masterDataSource, DataSource shadowDataSource) {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("SHADOW", shadowDataSource);targetDataSources.put("MASTER", masterDataSource);return new DynamicDataSource(masterDataSource, targetDataSources);}

6.Jmeter全链路混合压测

项目的代码我会传到资源文件中哦,大家记得去找【案例实战】业务稳定性运行之全链路混合压测这个标题的资源文件。

(1)启动项目,查看服务情况

在这里插入图片描述

(2)接口准备

ip:8082/api/product/v1/findById 查看商品详情
ip:8082/api/product/v1/list 查看商品列表
ip:8082/api/product/v1/lock 扣件商品库存
ip:8081/api/order/v1/add 下单

在这里插入图片描述

  • 我们以这个链路进行测试。

(3)配置jmeter

  • 配置100个用户,持续60s

在这里插入图片描述

  • 配置请求头,添加压测标识

在这里插入图片描述

  • 配置用户变量

在这里插入图片描述

  • 添加压测接口

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 查看报告

在这里插入图片描述

在这里插入图片描述

OK,至此全链路压测就已经完成啦,大家可以根据公司自己的业务去实施。记得给博主三连哦!

在这里插入图片描述

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

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

相关文章

C++|19.C++类与结构体对比

类和结构体 类和结构体本质上并没有太大区别。 但两者在默认上有所区别。 类默认成员变量是私有的&#xff0c;而结构体默认成员变量是公有的。 也就是说&#xff0c;对于一个类来说&#xff0c;会默认使用private去保护其内部成员变量使得无法直接访问到其内部的变量。 同时从…

外汇天眼:台北妇女轻信假投资诈骗话术,小赚1万却惨赔1500万

当今社会物价急速上涨&#xff0c;许多民众为了避免资产因通膨缩水&#xff0c;纷纷开始寻找各种能增加收入的渠道&#xff0c;因此投资理财日渐受到重视。 然而&#xff0c;诈骗集团也注意到这趋势&#xff0c;并且推出虚假的投资平台或方案&#xff0c;以各种话术行骗。 不久…

人逢三六九,运势低迷路难走

很多人都希望自己各方面的运势都能够朝着好的方向旺盛发展&#xff0c;我们的运势将会如何发展跟我们的出生时间是离不开关系的。我们的祖先为了后人着想&#xff0c;不犯他们犯过的错误&#xff0c;少走一些弯路&#xff0c;总结了多年来的生活经验&#xff0c;创造出了一句句…

在视频号上开通店铺怎么样?全新带货平台,普通人最后的电商机会

我是王路飞。 好消息&#xff1a;现在视频号上也可以开店、带货、做电商了。 坏消息&#xff1a;有一定的门槛&#xff08;尤其是资金方面&#xff09;。 视频号小店代表的是“全村的希望”&#xff08;腾讯马老板在22年底内部员工大会上的发言内容&#xff09;&#xff0c;…

Vue使用printJS导出网页为pdf、printJS导出pdf

先放几个参考链接 感谢&#xff01; Vue使用PrintJS实现页面打印功能_vue print.js 设置打印pdf的大小-CSDN博客 前台导出pdf经验汇总 &#xff08;html2canvas.js和浏览器自带的打印功能-print.js&#xff09;以及后台一些导出pdf的方法_iqc后台管理系统怎么做到导出pdf-CSD…

互联网外包公司干了2个月,技术退步明显了.......

先说一下自己的情况&#xff0c;本科毕业&#xff0c;18年通过校招进入南京某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能…

Aging:浙大学者研究发现,多吃这类抗氧化饮食,延缓衰老

撰文 | 宋文法 衰老&#xff0c;是一个复杂、多阶段、渐进的过程&#xff0c;发生在生命的整个过程。随着时间的流逝&#xff0c;人体的器官、肌肉会逐渐衰老&#xff0c;一些疾病也伴随着年龄的增长而发生&#xff0c;包括癌症、糖尿病、心血管疾病等。 衰老过程是由体内自由基…

【node link】Node命令中的node link命令的使用,还有CLI全局命令的使用,开发命令行工具必不可少的部分

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;NodeJs &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继续前进的勇气…

synchronized和lock的区别

synchronized和lock的区别 1&#xff09;synchronized是一个关键字&#xff0c;lock是一个java类&#xff1b; 2&#xff09;synchronized无法判断获取锁的状态&#xff0c;lock可以判断是否获取到了锁&#xff1b; 3&#xff09;synchronized会自动释放锁&#xff0c;lock必须…

ORPC-824,对标可替代ACPL-824/PC824等

提供隔离反馈 逻辑电路之间的接口 电平转换 DC和AC输入 SMPS中的调节反馈电路 消除接地环路 特征 电流传输比 &#xff08; CTR &#xff1a; 最低 20% 在 IF 1mA&#xff0c; VCE 5V &#xff09; 宽工作温度范围 -55~110C 高输入输出隔离电压 &#xff08; Viso 5&am…

LowB三人组(冒泡排序,插入排序,选择排序)(数据结构课设篇1,python版)(排序综合)

本篇博客主要详细讲解一下LowB三人组排序&#xff0c;为什么叫LowB三人组呢&#xff1f;因为他们的时间复杂度都为O&#xff08;n^2&#xff09;。下篇博客NB三人组&#xff08;堆排序&#xff0c;归并排序&#xff0c;快速排序&#xff09;&#xff08;数据结构课设篇2&#x…

大模型学习

大模型的参数量和显存占用估算 现在业界的大语言模型都是基于transformer模型的&#xff0c;模型结构主要有两大类&#xff1a;encoder-decoder&#xff08;代表模型是T5&#xff09;和decoder-only&#xff0c;具体的&#xff0c;decoder-only结构又可以分为Causal LM&#x…