这是我参与更文挑战的第23天,活动详情查看: 更文挑战

只有发现问题,才能解决问题。

起源

网站卡顿,主要表现在首页首次加载长期Loading、页面跳转可以明显看到浏览器跳转进度条缓慢移动。

(我也没办法,我也不知道,不要冲一个前些天刚从后端转过来的前端er发什么火![/可怜])

寻找前端性能定位工具

查书籍吧,查到一本名为《精通软件性能测试与LoadRunner实战》。书中第13节介绍了一些工具,其中包括HttpWatch,Page Speed,Dyna Trace,Yslow等。通过这些工具可以获取系统前端的性能指标相关信息。对解决问题不知道有没有用,但肯定能发现问题

一个一个学吧!

HttpWatch简介

HttpWatch,网页数据分析工具,可集成在浏览器工具栏中,功能主要有网页摘要、Cookie管理、缓存管理、消息头发送/接收、字符查询、POST数据、目录管理功能、报告输出等。能够在显示网页的同时显示网页请求和返回的日志信息。目前有基础版Basic Edition和专业版Professional Edition。

官网地址:https://www.httpwatch.com/

下载地址:https://www.httpwatch.com/download/

没关系,先去Chrome应用市场下一个扩展插件试一下,结果:

You have installed the HttpWatch for Chrome extension but the HttpWatch software is not installed or is out of date. Click the button below to download HttpWatch for Windows.

image.png

安装软件,基础版,Go….

image.png

安装完成后还有一个帮助文档:

image.png

基础使用

点击浏览器扩展插件,浏览器弹出:“httpWatch”已开始调试此浏览器。

image.png

注意:要测试的网站或页面必须在本标签页下输入与变动,否则HttpWatch侦听不到

比如我们输入”juejin.cn”,测试截图情况如下图所示:

image.png

上方的各列表示含义分别是:

Started: 表示记录URL的起始时间

Time:从请求发出直到最后的结果所耗费的时间

Sent:发送请求时传送的字节

Received:系统响应结果接收的字节

Method:请求过程中使用的方法,如GET, POST

Result:系统返回的请求结果,状态码

Type:已图标形式提现下载的数据类型,可通过鼠标移动查看文本提示信息

URL:显示当前请求的页面URL地址。

想看某条具体请求的具体时间情况,即下方的Time Chart,结果:Not Available For Basic Edition!

image.png

借用一张书中的图,一个请求的Time Chart应该是以下样子:

image.png

各个阶段的含义:

Blocked:阻塞时间,包括任何预处理时间(比如缓存查找)和等待网络连接可用花费的时间。个人理解相当于Chrome Network分析中的Queueing与Stalled。

DNS Lookup:DNS解析主机名到IP地址所花费的时间。

Connect:创建一个TCP连接到Web服务器所需的时间,如果是HTTPS,则还包括SSL握手过程。

Send:发送请求到服务器所需的时间

Wait:等待从服务器得到响应消息的时间,包括由于网络延迟和请求Web服务器的时间,这个值基本反应了后台接口的响应速度。

Receive:接受响应消息的时间,该值取决于内容返回的大小、网络带宽和是否使用了HTTP压缩。

TTFB:从浏览器发出请求到服务器返回第一个字节所耗费的时间,包括TCP连接耗时,发送请求时间,接收第一个字节的响应消息时间。

Network:是一个HTTP请求在网络消息传输上耗费的时间。

分析与结论

通过对自己的网站进行分析,发现除接口请求时长大外,打包出来的jscss文件也非常的大。

有两个方案:

  1. 使用ugliyjs或者gzip进行压缩
  2. 使用cdn缩小文件传输时长

当然两个方案可以同时进行。

这是我参与更文挑战的第22天,活动详情查看: 更文挑战

不知不觉22天了,每天更文对自己来说真是一个挑战,从被掏空到主动学习

既然我们知道了为什么,我们就可以知道怎么做

尤大说,要看文档,要看文档,要看文档。

官方文档

setup

文档将setuprefwatch等都介绍在“组合API”一节,个人对这个目前并没有太理解,等应用有了深刻理解再另写吧!

vue3新增了一个setup组件选项,在组件创建之前执行,setup 中不可使用 this,(文档中说“应该避免使用this”),因为setup 的调用发生在 datacomputedmethods 被解析之前,所以它们无法在 setup 中被获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default {
components: { ... },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
console.log(props) // { user: '' }
return {} // 这里返回的任何内容都可以用于组件的其余部分
}
// 组件的“其余部分”
}

ref

ref: 响应式变量。如下所示,如此可使得变量counter变为一个在任何地方起作用的响应式变量。

1
2
3
import { ref } from 'vue'
const counter = ref(0)
console.log(counter) // { value: 0 }

将值封装在一个对象中的理由,文档中给了一个很生动的Gif动画,非常有意思

全局/外部watch函数

不知道算不算vue3的新特性,不过我之前没有使用过这一函数,姑且罗列出来。

1
2
3
4
5
6
7
import { ref, watch } from 'vue'

const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
// 监听counter,当counter发生修改时,执行回调函数。

全局/外部computed属性

1
2
3
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)

Teleport

经过文档阅读,我的理解是Teleport允许给组件的部分DOM指定父组件,比如将组件的一部分渲染至页面的html/body下。

1
2
3
4
5
6
7
8
9
10
11
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
</teleport>

片段

简单来说,组件中的<template><div>..</div></template>,其中<div></div>可以不写了。

自定义事件

允许给emit事件编写验证函数。

这是我参与更文挑战的第18天,活动详情查看: 更文挑战

事情起源

因为生产环境与开发环境总有些差异,最普遍的是数据量不同。这导致在测试环境下发现不了的某些卡顿问题会发生在生产环境中,尤其小团队中基本是没有测试岗位的,大多生产环境的卡顿问题是上线以后才会暴露出来。

问题来了:

  1. 如何在线上系统运行的情况下定位问题代码?
  2. 如何避免修改原代码来定位问题代码?

最基本的定位方法是:定位到卡顿的接口,通过在各个方法请求前与请求后打印时间戳来计算各方法的响应时间。

这个基本方法有什么问题呢?

  1. 如果开发人员无权获取生产环境怎么重现。
  2. 如果调用方法很多的情况下,通过响应时间戳定位效率太低。

Arthas工具

Arthas官网

Arthas是阿里巴巴开源的一款Java诊断工具。Arthas除了解决我们上述定位接口卡顿问题,还能解决一下问题[1]:

  1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到JVM的实时运行状态?
  7. 怎么快速定位应用的热点,生成火焰图?
  8. 怎样直接从JVM内查找某个类的实例?

几个常用的命令:

watch:方法执行数据观测

monitor:方法执行监控

trace:方法内部调用路径,并输出方法路径上的每个节点上耗时

stack:输出当前方法被调用的调用路径

tt:方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测

arthas还可以生成火焰图,我们本文不做详述。

启动arthas

下载arthas后,命令打开对应文件夹,使用以下命令,即可启动arthas:

1
java -jar arthas-boot.jar

image.png

trace命令

这一节来看看怎么用trace命令获取各个方法method的执行时间。

trace在线文档

trace最基本的使用方法是监听方法调用路径和各个方法的耗时:

1
trace class-pattern method-pattern

如图所示,我们监听类com.xxxx.productmanage.ProductManageControllerindex()方法/接口:

1
trace com.xxxx.productmanage.ProductManageController index

效果如图:

image.png

我们请求index()方法对应的接口:浏览器刷新指定页面。

查看命令行输出:

image.png

可以清晰的看到各个方法的执行时间(貌似是命令行语言设置问题导致有些符号异常),如此,我们便可以轻松的定位到方法的执行时间。

深入

可以看到上述命令只能看到当前类中各个方法的执行时间,如果要深入调用的其他类对应方法又该怎么办呢?

比如我们要进一步监听上述耗时较长的getCompanys方法内部调用。

方法1

1
trace - E class1|class2 method1|method2

如上述定位问题,我们执行:

1
trace -E com.xxxxx.ProductManageController|com.xxxx.ProductManageService index|getCompanys

输出结果为:

1
2
Press Q or Ctrl+C to abort.
Affect(class count: 2 , method count: 2) cost in 147 ms, listenerId: 6

然后再次请求接口,输出结果如下图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[arthas@18804]$ trace -E com.highmall.suppliermanage.productmanage.ProductManageController|com.highmall.suppliermanage.productmanage.ProductManageService index|getCompanys
Press Q or Ctrl+C to abort.
Affect(class count: 2 , method count: 2) cost in 147 ms, listenerId: 6
`---ts=2021-06-17 18:24:01;thread_name=XNIO-1 task-3;id=55;is_daemon=false;priority=5;TCCL=com.jfinal.server.undertow.hotswap.HotSwapClassLoader@6156496
`---[73.303623ms] com.highmall.suppliermanage.productmanage.ProductManageController:index()
+---[0.080517ms] com.xxxx.productmanage.ProductManageController:getHeader() #46
+---[0.223587ms] com.xxxx.productmanage.ProductManageController:getParaToInt() #47
+---[0.034004ms] com.xxxx.productmanage.ProductManageController:getParaToInt() #48
+---[0.029192ms] com.xxxx.productmanage.ProductManageController:getPara() #49
+---[0.030154ms] com.xxxx.productmanage.ProductManageController:getParaToInt() #50
+---[71.959859ms] com.xxxx.ProductManageService:getCompanys() #51
| `---[71.861378ms] com.xxxx.productmanage.ProductManageService:getCompanys()
| +---[38.061978ms] com.xxxx.productmanage.ProductManageService:haveManageAllCompanyRight() #157
| +---[0.083083ms] com.jfinal.kit.StrKit:notBlank() #162
| `---[32.933605ms] com.jfinal.plugin.activerecord.Db:paginate() #172
`---[0.446852ms] com.xxxx.productmanage.ProductManageController:renderAppJson() #52

截图如下:

image.png

方法2

这种方法我并没有重现,不知是操作问题还是arthas版本问题,但是原操作文档有这一部分,我们暂且保留。

需要我们打开另外一个命令行窗口,执行命令:

1
telnet localhost 3658

链接到我们正在执行的Arthas,然后执行以下命令,添加监听:

1
trace com.xxxxxx.productmanage.ProductManageService getCompanys --listenerId 1

然后保持我们原有的命令窗口,重新调用接口,查看效果:

第一个终端还是只输出了一层,IT NOT WORK FOR ME!!!

[1] Arthas(阿尔萨斯) 能为你做什么

这是我参与更文挑战的第21天,活动详情查看: 更文挑战

起源

某个页面需要下载全部数据的功能,下载数据量大,接口延迟长…..

某个页面加载初始数据量延长长,但单个检索快速,出现初始数据加载中时,检索接口返回,初始数据后续返回覆盖了检索数据的展示….

这些情况需要我们:

  1. 能够手动取消/终止请求Request。
  2. 某些页面接口同时只能有一个在请求。

现状

系统基于老哥花裤衩开源的vue-element-admin做的二次开发,其中的请求采用的是axios,其中封装的request.js关键代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
withCredentials: true, // send cookies when cross-domain requests
timeout: 500000, // request timeout
transformRequest: [function(data) {
// 对 data 进行任意转换处理
return Qs.stringify({
...data
},
// 数组的转换
{ arrayFormat: 'brackets' })
}]
})

// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent

if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)

发起请求的方法:

1
2
3
4
5
6
7
export function remoteApi(data) {
return request({
url: '/api',
method: 'post',
data
})
}

取消请求 cancelToken

其官方文档:取消:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});

axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})

// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');

修改后的请求方法

1
2
3
4
5
6
7
8
export function remoteApi(data, cancelToken) {
return request({
url: '/api',
method: 'post',
cancelToken,
data
})
}

实际请求时,创建cacelToken:

1
2
3
// 组件方法内
this.cancelToken = CancelToken.source()
remoreApi(data, this.cancelToken).then(....).catch(....).finally(....)

需要取消请求时,执行:

1
2
// 组件方法内
this.cancelToken.cancel('取消下载')

避免重复请求

避免一个接口的重复请求,以免延时长的接口返回数据覆盖另一个接口的返回数据,造成数据显示错误。思路也相对简单,使用一个全局Map存储请求标识与对应的cancelToken。请求,在发起请求前,按照请求标识,唤起对应cancelToken的cancel方法,然后再发出新请求,即可满足条件。

这是我参与更文挑战的第17天,活动详情查看: 更文挑战

本文是在掘力计划沙龙:拥抱云原生——探索云原生下的框架与存储前的学习文章。

拥抱云原生——初探Quarkus拥抱云原生——云原生概念与Quarkus基本使用两篇文章中,我们初步了解了一下云原生的概念,Quarkus的使用。了解到Quarkus是一个针对K8s的原生JAVA框架,针对GraalVM和HotSpot进行量身定制。

这里面又涉及到许多新的概念:K8S, GraalVM等。一个一个学吧,我们先来了解一个K8S——Kubernetes。

让营地比你来时更干净。——《代码整洁之道》

Kubernetes相关文档

官网:英文 中文

中文概述:Kubernetes 是什么?

其他文档:Kubernetes是什么

基础概念与特点

Kubernetes是Google开源的容器编排引擎,即K8S是一个管理容器的工具

在文档Kubernetes是什么中,概述Kubernetes的用途:

快速部署应用

快速扩展应用

无缝对接新的应用功能

节省资源,优化硬件资源的使用

其特点为:

可移植: 支持公有云,私有云,混合云,多重云(multi-cloud)

可扩展: 模块化, 插件化, 可挂载, 可组合

自动化: 自动部署,自动重启,自动复制,自动伸缩/扩展

Kubernetes 是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态系统。Kubernetes 的服务、支持和工具广泛可用。

下图[1]比较了三种系统部署方案:

container_evolution.svg

  1. 传统部署:应用程序运行在物理服务器上
  2. 虚拟部署:在物理服务器上运行多个虚拟机,应用程序运行在虚拟机上
  3. 容器部署:容器类似与VM,具有自己的文件系统,CPU,内存,进程空间等,但是轻量的(App共享操作系统),可以跨云和OS发行版本进行移植。

容器可以提供敏捷应用程序的创建和部署,持续开发、集成和部署等多个好处[1]

Kubernetes 就是管理这些容器的一个工具,有诸如扩展要求、故障转移、部署模式等多种功能,K8s提供的好处包括:服务发现和负载均衡存储编排自动部署和回滚自动完成装箱计算自我修复密钥与配置管理[1]

总结

​ K8S是一个管理容器的工具

[1] Kubernetes 是什么?](https://kubernetes.io/zh/docs/concepts/overview/what-is-kubernetes/)

2021年上半年印象中主要有这么几件事:石家庄疫情、Golang学习与写作、软考。

最近头脑发昏,碎言碎语,不成逻辑,姑妄言之,姑妄听之。

石家庄疫情

2021阳历年初,2020农历年末,石家庄出现几例新冠疫情病例,封城,封小区,大概持续有1个月左右。封闭刚开始时是不用上班工作的,老婆也因工作原因被封闭在学校里,自己在家着实“快活”了一些日子,游戏,睡觉。时间长了,领导要求远程办公,虽然比不上单位上班的强度,但是网络延迟、电脑卡顿还是很挠人。

假期远程办公就好像踩着一双舒服的运动鞋,里面漏进去几粒沙石子一般,膈人

带着情绪办公的结果就是,写出来的程序能用,但很长一段时间总提心吊胆,生怕出问题。

Golang学习与写作

之前自己购买过大概100块钱一年的腾讯云服务器,搭建博客(Hexo +Nginx + Git),还花9块钱申请了一个域名,当时域名是qisichen.club,早就过期了,写过一点儿肤浅的技术文章。

这件事没有成,现在想来,一是我本人没有什么大抱负,纯粹个人贪玩,也没有制定写作计划,纯粹走一步看一步。

所以毫无疑问,最终博客也好,写作也好完全搁置了。

今年大概3月份吧,看到掘金有一个更文活动,Go主题月,发满21篇文章可以拿一个机械键盘。之前一直想学Go,想着可以边学习边刷一下Leetcode,就刷了些文章。最后刷了部键盘,人生第一步自己的机械键盘,感谢掘金!

也就是从这时候开始,被掘金的小姐姐给的“胡萝卜”引导着,偶尔发一些文章参加些小活动。

最近的6月更文挑战更是逼自己一把。到今天已经坚持22天了,大部分都是当天写的,周末有时会发一些以前的草稿或前些天提前写好的内容。还是感谢掘金吧!

软考

软考这事得从老婆说起,她一直在准备考编,考了几次没录上,埋怨不带她学习,学习氛围不够,逼着我也参加相应的职业考试,**Wife Happy, Life Happy!**,那就考吧!

买了三本官方教材,偶尔抽空看一下,正儿八经学习了大概一周吧。今年(2021年)5月29考试,上午感觉考的还不错,都是选择题;下午计算题和论文差了一些。据说7月份出考试结果,等着就好,通过希望不大!

闲言碎语

国企不轻松

今年(2021)算是在这家国企的边缘部门待的第4年,领导原是另外一个部门转过来的,喜欢你“加班”。不要以为国企不加班,从我目前了解到的,除了行政部门,生产部门的加班都很普遍。我们目前的作息是早8点打卡上班,部门强制要求加班到晚上7点,大小周。

996

越是996,工作强度越大,越是疲惫,视野也会越来越狭窄,注重眼前,看不到远方,包括个人发展、编写的代码都能看出“赶工”的痕迹。休息时间会被挤占,本能的就会想方设法的压缩工期,一周干一件事和一天干一件事自然不是一个概念。

房子与车子

今年30岁整,无房无车。房子没买,主要是缺钱,二是因为爱人工作暂不稳定,买房位置不定。石家庄的房价和本地人收入差距较大,17年18年被炒房的翻了一倍,工资一点儿没涨。买房这事一窍不通,总担心到时候被中介或者开发商给骗了。

对私家车我提不起半点儿兴趣,一直觉得私家车属于奢侈品,使用率低,占用空间大,再舒适的车还能在上面睡觉不成。公共交通才是未来。

下半年展望

作为一个小兵,没有什么话语权,只希望:加班少点!自己写作水平能提高一下!

希望家人健康!家和万事兴!

老婆也怀孕了,马上就要有自己的宝宝,生活一定越来越好,给自己点个赞!

下图是掘金发的键盘:Happy!

image.png

某天天气好,在租的小区拍的:

1163219744.jpg

5月底初次检查:

image.png

掘金年中主题活动 | 2021 我的半程成长之路征文活动正在进行中……

这是我参与更文挑战的第16天,活动详情查看: 更文挑战

因运营人员需要排序某个产品列表,这里记录使用SortableJS实现元素排序的过程。

sortableJs官网

引入

package.json中添加:

1
"sortablejs": "1.8.4",

需要排序的组件上添加:

1
import Sortable from 'sortablejs'

添加控制标签

在需要添加拖拽效果的控件上添加id标签。vue环境下为el-table增加拖拽排序则需要添加refrow-key,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
<el-table
ref="myDragTable"
v-loading="loading"
:data="dataList"
row-key="id"
element-loading-text="商品加载中"
border
fit
style="width: 100%;"
highlight-current-row
>
....
</el-table>

其中的关键在于以下两个标签:

1
2
ref="myDragTable"
row-key="id"

不要忘记设置row-key,否则会发生拖拽后行不规则的移动。

给其中的某列添加拖拽功能标识:

1
2
3
4
5
<el-table-column align="center" label="拖动" width="45" class-name="allow">
<template>
<i class="el-icon-rank" />
</template>
</el-table-column>

该列展示效果如下图所示:

image.png

辅助数据与排序方法

在vue组件的data中需要添加两个数组来记录拖拽前与拖拽后的顺序:

1
2
3
4
5
6
7
data() {
return {
dataList: [...] // 列表展示数据
oldPList: [], // 排序用
newPList: [] // 排序用
}
}

两个排序数组的初始化应该放在什么地方呢?

setSort方法又该在什么时间点去执行呢?

应该在获取数据之后与渲染DOM之后,关键代码如下图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
methods: {
getData() {
api...({ // 请求后端数据
...
}).then(res => {
// 这里表示已从后端获取了数据
this.dataList = res.data
this.oldPList = this.dataList.map(v => v.id)
this.newPList = this.oldPList.slice()
this.$nextTick(() => {
// 表示在渲染dom之后调用setSort方法
this.setSort()
})
})
},
setSort() {
// 获取操作元素
const el = this.$refs.myDragTable.$el.querySelectorAll('.el-table__body-wrapper > table > tbody')[0]
this.sortable = Sortable.create(el, {
ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
handle: '.allow', // 仅某一列可以拖动,这样不影响其他列的点击,复制功能
setData: function(dataTransfer) {
// to avoid Firefox bug
// Detail see : https://github.com/RubaXa/Sortable/issues/1012
dataTransfer.setData('Text', '')
},
onEnd: evt => {
/*这里就是在处理拖拽后的重新排序了*/
// 找到所拖拽的行
const targetRow = this.dataList.splice(evt.oldIndex, 1)[0]
// 操作原数据,重新排列所拖拽行的位置
this.dataList.splice(evt.newIndex, 0, targetRow)

// 以下步骤为通过重新排序,将新的排序通过接口告知后端,后端数据重排
const tempIndex = this.newPList.splice(evt.oldIndex, 1)[0]
this.newPList.splice(evt.newIndex, 0, tempIndex)
// 调用接口更新排序
// updateOrder({ tabsOrder: this.newPList }).then(response => {
// this.$message.success('更新排序成功')
// })
}
})
},
}

具体接口配置信息:http://www.sortablejs.com/options.html

编写序号排序

拖拽排序适合少量数据的情况,不适合大数据量或者将一个元素从末尾拖到头部。我们还需要可以手动修改序号的方式来辅助。

image.png

这样操作人员可以很方便的将某些元素置顶或设置到末尾。

我们需要监听el-inputchange事件,请求后台,更新排序信息。

为什么是监听change事件而不是input事件呢?因为input事件在你的输入框内有任何变动时会立即请求,而change则会在你输入完毕后(比如输入完毕点击enter或者鼠标点击空白处等)

这是我参与更文挑战的第15天,活动详情查看: 更文挑战

我们的系统是基于花裤衩开源的vue-elementui-admin做的。运营人员反馈线上管理系统卡顿严重,在用户量上来之后需要优化。

卡顿现象

经过线上测试,卡顿可重现,但不可稳定重现。

卡顿现象主要表现为:

  1. 点击菜单,菜单页Tagview加载慢,可以看到上方缓慢的加载条。
  2. 页面打开后,数据加载慢。

思路

宏观上讲,卡顿分为两部分:

  1. 后端接口延迟:卡顿2的主要原因。
  2. 前端静态资源加载慢:暂不清楚,需定位。

后端接口卡顿定位

后端接口卡顿定位主要有两种,一种是通过后端拦截器记录每个接口的响应时间,一种是通过前端浏览器Network查看各请求的整体响应时间。

下图为后端记录格式:

image.png

下图为前端查看请求响应时间:

image.png

我们发现有部分接口的响应时间(Waiting-TTFB)非常长,确实需要优化,我们针对这部分接口分别进行后端的优化。

后端优化方案:

​ 可以通过优化执行语句,并发,Sql优化,缓存来达到后端接口优化的目的,这部分不多言。

前端资源加载监听

后端接口延迟会造成页面加载后,数据持续加载,但理论上不应该造成页面初始加载的卡顿。

我们认为主要的卡顿原因在前端。

通过Chrome-Network监听发现,页面卡顿时,部分接口的QueueingStalled时间非常长,但延迟并不十分稳定。查找资料吧。

Network features reference中,View the timing breakdown of a request一节中的Timing breakdown phases explained,介绍了各个概念,原文如下:

Here’s more information about each of the phases you may see in the Timing tab:

  • Queueing

. The browser queues requests when:

  • There are higher priority requests.

  • There are already six TCP connections open for this origin, which is the limit. Applies to HTTP/1.0 and HTTP/1.1 only.

  • The browser is briefly allocating space in the disk cache

  • Stalled. The request could be stalled for any of the reasons described in Queueing.

  • DNS Lookup. The browser is resolving the request’s IP address.

  • Initial connection. The browser is establishing a connection, including TCP handshakes/retries and negotiating an SSL.

  • Proxy negotiation. The browser is negotiating the request with a proxy server.

  • Request sent. The request is being sent.

  • ServiceWorker Preparation. The browser is starting up the service worker.

  • Request to ServiceWorker. The request is being sent to the service worker.

  • Waiting (TTFB). The browser is waiting for the first byte of a response. TTFB stands for Time To First Byte. This timing includes 1 round trip of latency and the time the server took to prepare the response.

  • Content Download. The browser is receiving the response.

  • Receiving Push. The browser is receiving data for this response via HTTP/2 Server Push.

  • Reading Push. The browser is reading the local data previously received.

我们重点关注其中耗时较长的Queueing,Stalled,Waiting(TTFB)以及可优化的部分。翻译如下:

Queueing

浏览器在以下情况将请求排队:

 1. 有更高优先级的请求。
 2. 已经为此源打开了六个 TCP 连接,这是限制。仅适用于 HTTP/1.0 和 HTTP/1.1。
 3. 浏览器在磁盘缓存中短暂分配空间

Stalled:当Queueing中的任何情况发生时,请求将被Stalled

**Waiting (TTFB)**:等待初始响应所用的时间,也称为第一字节时间。此时间将捕捉到服务器往返的延迟时间,以及等待服务器传送响应所用的时间。

Queueing与Stalled的区别

StalledQueuing之后的下一个状态,Stalled开始时已经出队,他们太显著的差别(是否使用proxy/ssl),他们之间没有and/or/parent/child的关系,有建议将queueing/stalled改名为postponed/awaiting socket,具体可以看看chromium issue。[2]

Queueing

优化方向:(服务器同一个源/域最多同时又6个TCP连接)

1. 减少请求数
2. 域名发散:把资源放到不同的域名上

Stalled

同源链接复用可能引发这样的问题,由于之前存在可用链接,此时浏览器希望重用之前的连接以节省资源,用之前的一个socket去发起连接,后收到服务器返回的链接已重置/不存在,再从原本可用链接中找可用链接,引发长时间等待,具体可以看看 chrome-stalled-problem-resolving-process

Stalled是从TCP连接建立完成,到真正可以传输数据之间的时间差。TCP三次握手后,发送端发送数据后,一段时间内(不同的操作系统时间段不同)接收不到服务端ACK包,就会以 某一时间间隔(时间间隔一般为指数型增长)重新发送,从重传开始到接收端正确响应的时间就是stalled阶段。而重传超过一定的次数(windows系统是5次),发送端就认为本次TCP连接已经down掉了,需要重新建立连接。 stalled阶段时TCP连接的检测过程,如果检测成功就会继续使用该TCP连接发送数据,如果检测失败就会重新建立TCP连接。所以出现stalled阶段过长,往往是丢包所致,这也意味着网络或服务端有问题。

文章[4]详细描述了作者定位一个Stalled延时长的问题。

一种可能的Stalled延迟过长的情况是:

我新开一个标签尝试访问同一个资源的时候,这次请求也会去读取这个缓存,假设之前那次请求很慢,耗时很久,那么后来这次请求因为无法获取对该缓存的操作权限就一直处于等待状态。

两种解决方案:

  1. 服务端设置Response响应头Cache-Control: no-cache。即无缓存。
  2. 给请求添加时间戳让每次请求变得唯一。

Waiting (TTFB)

建议将此值控制在200毫秒以下[3]。长TTFB会揭示两个主要问题:

  1. 客户端与服务端之间的网络条件较差;
  2. 服务器应用的响应慢

DNS Lookup

DNS查询所用的时间

  • 可优化部分
    • 不要有太多的新域名(可能递归查询绕地球一圈),参考域名收敛
    • 减少DNS解析路径(如果内部有很多DNS服务器解析)。

优化汇总

主要优化方向

  1. 减少请求数+域名发散解决Queueing时长问题
  2. 禁用缓存,或请求时间戳解决Stalled时长问题
  3. 优化服务端响应(具体见上述后端优化方法),解决TTFB时长问题

后续优化方向需要学习具体的的Chrome的请求分析来做。参考[4]与Chrome官网。

[1] 了解资源加载时序

[2] Network Resource Timing 我的请求慢在哪

[3] Chrome DevTools 的 Queueing、Stalled解析

[4] 关于请求被挂起页面加载缓慢问题的追查

这是我参与更文挑战的第14天,活动详情查看: 更文挑战

心似白云常自在,意如流水任东西。

拥抱云原生——初探Quarkus中,简单的了解了一下Quarkus。本文继续学习云原生以及Quarkus。

云原生

云原生,Cloud Native,最早于2015年的“迁移到云原生应用架构”中提出,其中总结了云原生应用的5个关键特征:

  1. 十二要素应用程序
  2. 微服务
  3. 敏捷的自助式基础设施
  4. 基于API进行服务间协作
  5. 反脆弱

谷歌2018年所发布的云原生定义为:

云原生技术使组织能够在现代化和动态的环境(例如公共云、私有云和混合云)中,构建和运行可进行容量伸缩的应用程序。其代表技术包括容器、服务网格、微服务、不可变基础设施和声明性API。

  1. 应用容器化
  2. 面向微服务架构
  3. 应用支持容器的编排调度

这些技术能够构建富有韧性、便于管理和易于观测的松耦合系统。结合强大的自动化功能,这些技术能使工程师们可以轻松、频繁且可预测地对系统做出重大变更。

后续,2020年VMware定义云原生为:

“云原生是一种利用云计算软件交付模型的优势,来构建和运行应用程序的方法。当公司使用云原生架构构建和运行应用程序时,他们能更快地将新想法推向市场,并更快地响应客户需求。….云原生应用更关注如何创建和部署应用程序。云原生更重要的方面,是能够为开发人员提供能按需访问的计算能力,以及现代化的数据和应用程序服务的能力。云原生开发要与DevOps、持续交付、微服务和容器的概念相结合。”

目前Redhat关于云原生应用的定义与理解:

云原生应用开发是根据众所周知的云计算技巧与技术构建、运行和改进应用的一种方法。

云原生应用是独立的小规模松散耦合服务的集合,旨在提供备受认可的业务价值,例如快速融合用户反馈以实现持续改进。简而言之,通过云原生应用开发,您可以加速构建新应用,优化现有应用并在云原生架构中集成。其目标是以企业需要的速度满足应用用户的需求。

云原生是一种行为方式和设计理念,究其本质,凡是能够提高云上资源利用率和应用交付效率的行为或方式都是云原生的[3]。

云原生是一种构建和运行应用程序的方法,是一套技术体系和方法论。云原生(CloudNative)是一个组合词,Cloud+Native。Cloud表示应用程序位于云中,而不是传统的数据中心;Native表示应用程序从设计之初即考虑到云的环境,原生为云而设计,在云上以最佳姿势运行,充分利用和发挥云平台的弹性+分布式优势。总而言之,符合云原生架构的应用程序应该是:采用开源堆栈(K8S+Docker)进行容器化,基于微服务架构提高灵活性和可维护性,借助敏捷方法、DevOps支持持续迭代和运维自动化,利用云平台设施实现弹性伸缩、动态调度、优化资源利用率。[4]。

图片来源:
作者:华为云开发者社区
链接:https://juejin.cn/post/6844904197859590151
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Quarkus基本使用

说完理论,我们再来点Quarkus实践。教程来源于:https://quarkus.io/guides/getting-started

注入

在Quarkus中,使用@Inject可以注入。更多信息: Contexts and Dependency Injection guide

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package org.acme.getting.started;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.annotations.jaxrs.PathParam;

@Path("/hello")
public class GreetingResource {

// 注入
@Inject
GreetingService service;

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello RESTEasy";
}

@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/greeting/{name}")
public String greeting(@PathParam String name) {
return service.greeting(name);
}
}

开发模式

运行项目的方式是执行命令:

1
./mvnw compile quarkus:dev

其中quarkus:dev表示我们以开发模式运行。开发模式下将启动热更新机制。

测试

可以使用@QuarkusTest注解测试类,@Test注解方法。然后命令行执行:./mvnw test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@QuarkusTest
public class GreetingResourceTest {

@Test
public void testHelloEndpoint() {
....
}

@Test
public void testGreetingEndpoint() {
.....
}

}

打包

执行命令:

1
./mvnw package

会生成

  1. getting-started-1.0.0-SNAPSHOT.jar- 仅包含项目的类和资源,它是 Maven 构建生成的常规工件 - 它不是可运行的 jar;
  2. quarkus-app文件夹,包括一个可执行的 quarkus-run.jar 文件。这个jar不是引用jar,不需要把它放到quarkus-app/lib/
  3. 如果想部署到容器中,应该部署整个quarkus-app目录。
  4. 如果单纯执行,可以运行命令java -jar target/quarkus-app/quarkus-run.jar
  5. 如果在配置文件中配置打包模式为fast-jar,则生成的包启动更快,占用内存更小。

image.png

image.png

[1] 什么是云原生

[2] 迁移到云原生应用架构

[3] 云原生(Cloud Native)的定义——教程

[4] 什么是云原生?这回终于有人讲明白了——华为

[5] 如何理解云原生(Cloud Native) 应用 ?——Red Hat

[6] 云原生架构概述 其中对比了SpringCloud与Kubernetes