第七十章 【重磅加餐】一线大厂如何产生海量请求压测
第1集 Jmeter单接口压测问题点和流量漏斗模型介绍
简介: Jmeter单接口压测问题点和流量漏斗模型介绍
-
压测工具选择
- Jmeter
- LoadLunner
- Apache AB
-
问题点
-
常用的Jmeter压测工具,进行单接口压测是没问题的,可以得出基于某个机器配置下接口的吞吐量
-
但是实际线上业务,用户不可能只访问某个接口
-
更多的是多个接口业务联动,有可能某个接口出现瓶颈导致其他接口出现问题
-
例子
1、轮播图列表接口【4核8G,QPS 3万】2、分类列表接口【4核8G,QPS 5万】3、视频详情页接口【4核8G,QPS 4万】4、加入购物车接口【4核8G,QPS 2万】5、创建订单接口【4核8G,QPS 8千】6、注册接口【4核8G,QPS 1万】7、用户信息查询接口【4核8G,QPS 1万】8、推荐视频拉链接口【4核8G,QPS 1K】
- 某天你的网站突然火了上了热门,每天几百万的用户访问
-
-
最高的时候每秒有3千用户访问,但后台却挂了。。。。。
明明单机QPS都很高足够支撑这个访问量级了,却还出现问题
分析
- 单个接口支撑是没问题的,但是忽略了某些低QPS的接口,如【推荐视频】接口
- 由于这些接口性能不足,导致系统CPU、内存、链接被耗尽了
- 从而连锁反应导致其他接口RT响应超时、GC频繁、线程池耗尽
什么是流量漏斗模型(别名:流量模型)
- 谁会用:公司产品经理、运营、公司CTO、CEO
- 那我们研发工程师为啥要用?
- 举个例子大家就明白
- 互联网产品其本身就是一个虚拟的漏斗,用户的行为路径有很多,举个淘宝、JD这电商例子
- 首页->查看商品->添加购物车->注册->登录->下单->支付->确认收货
-
最终怎么办
-
需要推测常规用户的访问流量模型 和 随着流量增大 和服务器性能关联指标变化情况
-
比如100万个用户进来
- 有多少是新用户会进行注册
- 有多少是新用户会进入详情页
- 有多少用户是会加入购物车
- 有多少用户是会支付订单
- …
-
知道解决方式后,又带来新的问题,怎么记录用户访问链路【流量模型和增加大流量】?
-
第2集 带你走进流量模型-流量记录重放技术
简介: 带你走进流量模型-流量记录重放技术
- 需要解决的问题
- 记录用户访问链路【流量模型和增加大流量】后各个指标的变化情况,CPU、RT、GC 、内存、带宽等
- 混合链路压测的要点
- 需要真实的用户访问分布数据(流量模型)
- 支持成倍的扩大缩放相关流量数据
- 敏感数据脱敏处理,压测脏数据隔离
- 隔离数据包括不限,日志、Redis、MQ、数据库等
- 敏感信息脱敏:手机号、session信息、联系方式等
-
工具
-
Nginx访问日志(基于HTTP协议)
- 需要二次开发程序读取协议或者使用三方工具
- 成本高,复用性相对弱
-
TCP Copy(基于TCP协议)
- 记录最原始的流量,支持多种协议
- 学习成本高,使用相对复杂
-
GoReplay(基于HTTP协议)
- 仅支持HTTP协议
- 成本低,上手快
-
第3集 流量重放GoReplay介绍和依赖环境讲解
简介: 流量重放GoReplay介绍和依赖环境讲解
- 什么是 流量重放GoReplay
- 地址
- 官网:https://goreplay.org/
- github:https://github.com/buger/goreplay
- GO语言编写的http流量复制工具
- 使用流程简单,支持多个系统,mac、linux、win
- GoReplay 不是代理,而是在后台侦听网络接口上的流量
- 无需更改生产基础架构,只需在与服务相同的机器上运行 GoReplay 守护程序
- 使用者:腾讯、京东、阿里等一线大厂
- 地址
-
流量录制重放特点
- 捕获网络指定端口流量,输出到控制台
- 捕获网络指定端口流量,将原始流量实时重放到其他环境中
- 捕获网络指定端口流量,并保存到文件中
- 捕获网络指定端口流量,请求过滤指定路径流量,并保存到文件中
-
机器和环境选择
- 机器:Nginx所在机器,入口流量
- 安装Go环境
- Go语言是Google开发的具有良好并发能力的编程语言
- Go语言别名Golang
第4集 阿里云Linux服务器安装Go环境和GoReplay实战
简介: 阿里云Linux服务器安装Go环境和GoReplay实战
-
阿里云Nginx机器:112.74.55.160
-
Golang环境安装
- 安装包在本章本集资料里面
- 解压到指定目录
tar -C /usr/local -zxvf go1.5.3.linux-amd64.tar.gz
- 添加PATH环境变量
# 1.打开文件 vim /etc/profile# 2.添加环境变量 export GOROOT=/usr/local/go export PATH=$PATH:$GOROOT/bin# 3.编译生效source /etc/profile
- 测试
输入 go version 出现版本号即为成功。
GoReplay安装
- 安装包在本章本集资料里面
- 解压工具
tar xvzf gor_1.3.1_x64.tar.gz
- 解压完压缩包后,可以从当前目录进行Gor,也可以将Gor文件复制到的PATH文件下
- 测试
./gor
第5集 GoReplay流量录制和重放功能讲解
简介: GoReplay流量录制和重放功能讲解
-
重放机器准备
- 阿里云Nginx机器:112.74.55.160
- Docker安装Nginx,8080端口作为映射
docker run --name class_nginx -p 8080:80 -d nginx:1.21.6
-
参数
-
输入
- –input-raw : 用于捕获 HTTP 流量时,应指定 IP 地址或界面以及应用程序端口
- –input-file :接收以前使用过的文件记录
- –input-tcp :如果决定将多个转发器Gor实例转发流量到它,Gor聚合实例使用
-
可用输出:
–output-http :重播HTTP流量到给定的端点
–output-file :记录传入到文件的流量
–output-tcp :将传入的数据转发到另一个Gor实例
–output-stdout :用于调试,输出所有数据。
-
-
案例测试
#1.捕获网络流量,表示监听80端口发生的所有网络活动记录到stdout
gor --input-raw :80 --output-stdout#2.重放,将原始流量重放到其他环境中,同一个服务器但是端口不同,多个亦可
gor --input-raw :80 --output-http="http://127.0.0.1:8080"#3.捕抓流量请求并保存到文件中,实际会保存为requests.gor文件名
gor --input-raw :80 --output-file requests.gor#4.从保存下来的流量文件中提取流量向某个端口输出
gor --input-file requests_0.gor --output-http="http://127.0.0.1:8080"#5.请求过滤指定路径流量,使用该机制,只记录/account-server/api路径下的请求
gor --input-raw :80 --output-http "http://127.0.0.1:8080" --http-allow-url /account-server/api
第6集 GoReplay多倍速度-循环重放流量实战
简介: GoReplay多倍循环重放流量实战
- 多倍重放录制流量
#流量录制的监听命令gor --input-raw :80 --output-file requests.gorgor --input-file requests_0.gor --output-http "http://127.0.0.1:8080"# --input-file 从文件中获取请求数据,重放的时候5倍速度重放; 比如10秒20条请求,扩大2倍后就是5秒就跑完20条请求# --input-file-loop 无限循环,而不是读完这个文件就停止# --output-http 发送请求到 http://xdclass.net# --stats --output-http-stats 每 5 秒输出一次 TPS 数据gor --input-file "requests_0.gor|200%" --input-file-loop --output-http "http://127.0.0.1:8080" --stats --output-http-statsgor --input-file "requests_0.gor|2000%" --output-http "http://127.0.0.1:8080"gor --input-file "requests_0.gor|5000%" --input-file-loop --output-http "http://127.0.0.1:8080" --stats --output-http-statsgor --input-file "requests_0.gor|6000%" --input-file-loop --output-http "http://127.0.0.1:80" --stats --output-http-stats
第7集 基于Gor录制短链平台基础流量模型数据
简介: 基于Gor录制短链平台基础流量模型数据
-
开启录制
gor --input-raw :80 --output-file requests.gor
-
注意事情
-
各个微服务可以单节点进行部署,方便压测观察数据
-
并非大规模全链路压测,环境和数据没做隔离
-
-
基础数据访问
- 因为我们还没上线,所以直接PostMan访问用于模拟正常用户的链路
- 公司
- 没上线,灰度发布,录制部分流量(产品、运营经理会进行推广的)
- 已经上线版本迭代,直接录制线上流量模型即可的
- 增删改查接口
- 有些新增、删除 是具备唯一性,则会新增错误
- 主要是查询为主,多数业务都 二八原则
- 流量角度:少数接口承接了80%的流量,多数接口承接了20%的流量
- 接口类型:查询类型接口承接了80%的流量,新增/更新/删除 承接了20%的流量
第8集 多倍扩大Gor回放流量模型-压测短链平台
简介: 多倍扩大Gor回放流量模型-压测短链平台
- 流量回放
- 10倍回放
- 50倍回放
- N倍回放(取决机器配置)
gor --input-file "requests_0.gor|300000%" --input-file-loop --output-http "http://127.0.0.1:80" --stats --output-http-stats
-
观察结果
- 分钟调用次数:RT响应、CPU、内存、带宽占用、GC次数、接口成功率等指标
第七十一章 短链平台基础接口补充开发
第1集 账号服务个人信息查询基础接口开发
简介: 账号服务个人信息查询基础接口开发
-
账号微服务接口补充开发
- 查看个人信息模块
/*** 查看个人信息* @return*/@GetMapping("detail")public JsonData detail(){JsonData jsonData = accountService.detail();return jsonData;}@Overridepublic AccountDO detail(Long accountNo) {return accountMapper.selectOne(new QueryWrapper<AccountDO>().eq("account_no", accountNo));}@Datapublic class AccountVO {private Long accountNo;/*** 头像*/private String headImg;/*** 手机号*/private String phone;/*** 邮箱*/private String mail;/*** 用户名*/private String username;/*** 认证级别,DEFAULT,REALNAME,ENTERPRISE,访问次数不一样*/private String auth;@JsonProperty("createTime")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date gmtCreate;}
第2集 惰性更新流量包前端展示和短链过期时间
简介: 惰性更新流量包前端展示和短链过期时间
-
问题点
- 流量包是用的时候才去检查更新,那如果用户昨天用了但今天没用
- 查看的总的流量包时候,还是旧的流量包
-
解决方式
- 利用当前时间和流量包更新时间进行判断
-
流量包惰性更新涉及的接口
- 分页查看流量包列表
- 查看流量包详情
-
编码实现
private TrafficVO beanProcess(TrafficDO trafficDO) {TrafficVO trafficVO = new TrafficVO();BeanUtils.copyProperties(trafficDO, trafficVO);//惰性更新,前端显示问题,根据更新时间进行判断是否是最新的//今天日期String todayStr = TimeUtil.format(new Date(), "yyyy-MM-dd");String trafficUpdateDate = TimeUtil.format(trafficDO.getGmtModified(), "yyyy-MM-dd");//日期不一样,则未更新,dayUsed需要为0if (!todayStr.equalsIgnoreCase(trafficUpdateDate)) {trafficVO.setDayUsed(0);}return trafficVO;}
- 短链服务expired过期时间处理 前端入参 expired字段
@Data
public class ShortLinkAddRequest {/*** 组*/private Long groupId;/*** 短链标题*/private String title;/*** 原生url*/private String originalUrl;/*** 域名id*/private Long domainId;/*** 域名类型*/private String domainType;/*** 过期时间*/@JsonFormat(locale = "zh",timezone = "GMT+8",pattern="yyyy-MM-dd HH:mm:ss")private Date expired;
}
- 访问
低于1980的则是永久有效,毫秒
1980-01-01 00:00:00 -> 315504000000L
第3集 数据库 Too many connection异常解决
简介: 数据库 Too many connection异常解决
-
异常信息
- Mysql数据库报错,too many connection
- 结果就是导致无法连接数据库,微服务或者数据库客户端连接超时
-
原因分析
- MySQL默认的连接为100个,系统自带的连接数太小,连接的线程超过系统配置导致出现错误
- 可以通过部署多几个MysqlServer实例,物理分库
#查看当前连接数 show full processlist;#查看最大连接数 show variables like "max_connections";set global max_connections=1000
- mysql的连接数保持时间-默认 28800(8个小时)
#查看连接睡眠时间,默认是 28800,相对较少调整这个,不能太短,也不能过长; show global variables like 'wait_timeout' wait_timeout解释: 当一个客户端连接到MySQL数据库后,如果客户端不自己断开,也不做任何操作,MySQL数据库会将这个连接保留"wait_timeout"这么长时间(单位是s,默认是28800s,也就是8小时),超过时间之后,MySQL数据库为了节省资源,就会断开这个连接
- 程序没及时关闭连接,产生过多sleep进程
- MySQL默认的连接为100个,系统自带的连接数太小,连接的线程超过系统配置导致出现错误
-
注意
- 上述是临时修改,重启mysql会失效
- 永久修改可以通过修改mysql的配置/etc/my.cnf配置文件
- 搜索下mysql配置文件修改博文
第4集 Flink-IP解析地理位置信息-百度地图api案例
简介: Flink根据IP解析地理位置信息-百度地图api案例
-
本章内容都是跳动比较大,缝缝补补,根据大家遇到的问题补充的知识点
-
IP信息转换为地理位置信息,
- 百度地图文档 :https://lbsyun.baidu.com/index.php?title=webapi/ip-api
- 离线解决方案:纯真IP库 GeoLite2 埃文科技 ip2region
-
代码
@Slf4j public class AsyncLocationRequestFunction extends RichAsyncFunction<ShortLinkWideDO,String> {//private static final String IP_PARSE_URL = "https://restapi.amap.com/v3/ip?ip=%s&output=json&key=4f6e1b4212a5fdec6198720f261892bd";private static final String IP_PARSE_URL = "http://api.map.baidu.com/location/ip?ak=ot37gt3nQm27omNBBqFHygbxVUafTl2V&ip=%s";private CloseableHttpAsyncClient httpAsyncClient;@Overridepublic void timeout(ShortLinkWideDO input, ResultFuture<String> resultFuture) throws Exception {resultFuture.complete(Collections.singleton(null));}@Overridepublic void open(Configuration parameters) throws Exception {this.httpAsyncClient = createAsyncHttpClient();}@Overridepublic void close() throws Exception {if(httpAsyncClient!=null){httpAsyncClient.close();}}@Overridepublic void asyncInvoke(ShortLinkWideDO shortLinkWideDO, ResultFuture<String> resultFuture) throws Exception {String ip = shortLinkWideDO.getIp();String url = String.format(IP_PARSE_URL,ip);HttpGet httpGet = new HttpGet(url);Future<HttpResponse> future = httpAsyncClient.execute(httpGet, null);CompletableFuture<ShortLinkWideDO> completableFuture = CompletableFuture.supplyAsync(new Supplier<ShortLinkWideDO>() {@Overridepublic ShortLinkWideDO get() {try {HttpResponse response = future.get();int statusCode = response.getStatusLine().getStatusCode();if (statusCode == HttpStatus.SC_OK) {HttpEntity entity = response.getEntity();String result = EntityUtils.toString(entity, "UTF-8");JSONObject locationObj = JSON.parseObject(result);//String city = locationObj.getString("city");//String province = locationObj.getString("province");String city = locationObj.getJSONObject("content").getJSONObject("address_detail").getString("city");String province = locationObj.getJSONObject("content").getJSONObject("address_detail").getString("province");shortLinkWideDO.setProvince(province);shortLinkWideDO.setCity(city);return shortLinkWideDO;}} catch (InterruptedException | ExecutionException | IOException e) {log.error("ip解析错误,value={},msg={}", shortLinkWideDO, e.getMessage());}shortLinkWideDO.setProvince("-");shortLinkWideDO.setCity("-");return shortLinkWideDO;}});completableFuture.thenAccept(new Consumer<ShortLinkWideDO>() {@Overridepublic void accept(ShortLinkWideDO shortLinkWideDO) {resultFuture.complete(Collections.singleton(JSON.toJSONString(shortLinkWideDO)));}});// completableFuture.thenAccept( (dbResult) -> { // resultFuture.complete(Collections.singleton(JSON.toJSONString(shortLinkWideDO))); // });}private CloseableHttpAsyncClient createAsyncHttpClient() {try {RequestConfig requestConfig = RequestConfig.custom()//返回数据的超时时间.setSocketTimeout(20000)//连接上服务器的超时时间.setConnectTimeout(10000)//从连接池中获取连接的超时时间.setConnectionRequestTimeout(1000).build();ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor();PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);//设置连接池最大是500个连接connManager.setMaxTotal(500);//MaxPerRoute是对maxtotal的细分,每个主机的并发最大是300,route是指域名connManager.setDefaultMaxPerRoute(300);CloseableHttpAsyncClient httpClient = HttpAsyncClients.custom().setConnectionManager(connManager).setDefaultRequestConfig(requestConfig).build();httpClient.start();return httpClient;} catch (IOReactorException e) {log.error("初始化 CloseableHttpAsyncClient异常:{}",e.getMessage());return null;}} }
第七十二章 短链平台-前后端联调实战和Bug修复
第1集 短链平台-前端业务功能效果演示
简介: 短链平台-前端业务功能效果演示
- 效果和功能测试
第2集 短链平台-前端技术栈环境搭建和结构讲解
简介: 短链平台-前端页面技术栈环境搭建
-
前端代码(本章本集资料)
-
开发环境说明
-
编辑器
- Vscode( 自己下载即可,或者看HTML课程里面有安装包)
-
框架(确保大版本一致,如果之前安装了旧版node和npm,搜索博文重新升级下)
-
Node
- 版本 v16.15.0
- 【安装包】 作者有偿提供。
-
Npm版本 8.5.5
-
其他版本
Vue3版本:3.2.13Ant-Design-Vue 版本:3.2.2EchartJS版本:5.3.2
-
-
-
下载代码-安装环境,如何启动?
-
导入VSCode
-
构建启动
npm installnpm run serve
-
核心目录结构讲解
- 依赖组件模块
- 请求接口地址
- 页面组件
- 后端api域名
- 前后端联调注意事项
- 前端详情启动端口不要冲突了
- 后端接口协议要和课程保持一致,不然前端代码识别不了
- 失败情况:前端 http , 后端https
- 失败情况:前端 https, 后端http
- 成功:前端http、后端http
- 成功:前端https、后端https
- 后端API地址记得修改(课程的后端api地址不一定可用,会改动,可以用自己的)
第3集 前后端联调-前端不能识别雪花算法id解决方案
简介: 前后端联调-前端不能识别雪花算法id解决
- 问题
- 雪花算法生成的id作为主键时,因为其长度为19位
- 而前端JS一般能处理16位,如果不处理的话在前端会造成精度丢失,最后两位会变成00
-
后端 解决方式
- 直接把id类型改为String就行,使用JackSon包的注解
- 对应的实体类主键属性加入注解@JsonSerialize
@JsonSerialize(using = ToStringSerializer.class) @TableId private Long id;
前端 解决方式
- 前端使用 json-bigint 模块进行处理,一般都是用axios数据请求
npm install json-bigint#代码封装
axios.defaults.transformResponse = [function (data) {const json = JSONBIG({storeAsString: true})const res = json.parse(data)return res}
]或 axios.defaults.transformResponse = [function (data) {const json = JsonBigint({storeAsString:true})const res = json.parse(data)return res}
]axios.create({baseURL: 'http://*******.com',timeout: 5000,timeoutErrorMessage: '请求时间过长,请联系后端或者优化请求',
})
第4集 短链平台-前端Vue3打包发布阿里云OSS实战
简介: 短链平台-前端Vue3打包发布阿里云OSS实战
-
前端发布有多个方式
- Nginx文件服务器
- 云厂商文件服务器+CDN:阿里云OSS
-
Vue项目发布上线
-
打包编译
npm run build
-
上传阿里云OSS
-
配置域名和默认静态页面
-
第5集 订单服务-主动关单查询支付平台业务处理
简介: 订单服务-主动关单查询支付平台业务处理
- 需求
- 用户下单支付成功了,但是回调通知失败了,导致本地订单数据库未支付成功
- 延迟队列订单超时,取消订单前需要向第三方平台查询订单是否支付完成
-
- 接收微信推送的支付成功消息后,我们需要做啥?
- 更新订单状态
- 调用账号服务发放流包
- 接收微信推送的支付成功消息后,我们需要做啥?
- 编码实战
/*** //延迟消息的时间 需要比订单过期 时间长一点,这样就不存在查询的时候,用户还能支付成功* <p>* //查询订单是否存在,如果已经支付则正常结束* //如果订单未支付,主动调用第三方支付平台查询订单状态* //确认未支付,本地取消订单* //如果第三方平台已经支付,主动的把订单状态改成已支付,造成该原因的情况可能是支付通道回调有问题,然后触发支付后的动作,如何触发?RPC还是?** @param eventMessage*/@Overridepublic boolean closeProductOrder(EventMessage eventMessage) {String outTradeNo = eventMessage.getBizId();Long accountNo = eventMessage.getAccountNo();ProductOrderDO productOrderDO = productOrderManager.findByOutTradeNoAndAccountNo(outTradeNo, accountNo);if (productOrderDO == null) {//订单不存在log.warn("订单不存在");return true;}if (productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.PAY.name())) {//已经支付log.info("直接确认消息,订单已经支付:{}", eventMessage);return true;}//未支付,需要向第三方支付平台查询状态if (productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.NEW.name())) {//向第三方查询状态PayInfoVO payInfoVO = new PayInfoVO();payInfoVO.setPayType(productOrderDO.getPayType());payInfoVO.setOutTradeNo(outTradeNo);payInfoVO.setAccountNo(accountNo);// 需要向第三方支付平台查询状态String payResult = payFactory.queryPayStatus(payInfoVO);if (StringUtils.isBlank(payResult)) {//如果为空,则未支付成功,本地取消订单productOrderManager.updateOrderPayState(outTradeNo, accountNo, ProductOrderStateEnum.CANCEL.name(), ProductOrderStateEnum.NEW.name());log.info("未支付成功,本地取消订单:{}", eventMessage);} else {//支付成功,主动把订单状态更新成支付log.warn("支付成功,但是微信回调通知失败,需要排查问题:{}", eventMessage);productOrderManager.updateOrderPayState(outTradeNo, accountNo, ProductOrderStateEnum.PAY.name(), ProductOrderStateEnum.NEW.name());//触发支付成功后的逻辑,Map<String, Object> content = new HashMap<>(4);content.put("outTradeNo", outTradeNo);content.put("buyNum", productOrderDO.getBuyNum());content.put("accountNo", accountNo);content.put("product", productOrderDO.getProductSnapshot());//构建消息EventMessage payEventMessage = EventMessage.builder().bizId(outTradeNo).accountNo(accountNo).messageId(outTradeNo).content(JsonUtil.obj2Json(content)).eventMessageType(EventMessageType.PRODUCT_ORDER_PAY.name()).build();//如果key不存在,则设置成功,返回trueBoolean flag = redisTemplate.opsForValue().setIfAbsent(outTradeNo, "OK", 3, TimeUnit.DAYS);if (flag) {rabbitTemplate.convertAndSend(rabbitMQConfig.getOrderEventExchange(),rabbitMQConfig.getOrderUpdateTrafficRoutingKey(), payEventMessage);return false;}}}return true;}
第6集 缝缝补补-短链平台Bug修复
简介: 缝缝补补-短链平台Bug修复
- 修复一
- 分页某个分组的查找短链,未加排序
- 修复二
- 数据可视化服务-升序
- 修复三
- Flink服务TimeUtil时间格式化
修复四
- 修复Agent空判断
修复五
- 查看我的订单-降序