程式设计师如何优雅地解决线上问题?

2022-08-01 12:05:51

身为一个程式设计师,遇到线上问题那都是家常便饭的事儿。

如果你在深夜看到一群同事围在一起,他们是在共同探讨什么哲学问题么?非也,他们一定是遇到了线上BUG。

线上问题只要影响到了核心业务流程那便是事故,所以一旦事故发生,无论你在约会,还是周末打游戏,甚至是在睡觉,只要接到了来自公司的电话,那只能赶紧连上公司网路加班了。

image-20220731171413404

 

BUG分类

线上问题是复杂多变的,我们一般将bug分为系统级别和业务级别bug。

系统级别bug

业务部署在整套系统上执行,一旦出现系统级别bug则业务会被严重拖垮。如CPU爆满、服务不可用、甚至伺服器宕机等都属于系统级别的bug。

如果是CPU100%,那是由哪个执行绪,哪个类,甚至是哪个方法导致的?

若是业务流程正常但是部分服务效能拉跨,那么如何快速定位到问题在哪儿?

因为是线上发生的事儿,所以重点在于如何迅速解决

以下分享我最常用的一些问题排查工具。

 

linux定位工具

1.CPU高负载,甚至100%?

perf工具

perf是linux的效能分析工具,核心作用之一就是用来检视热点函数的分布情况。

用它可以生成火焰图检视到函数的资源占用情况,函数的呼叫栈越深火焰就越高。所以对于异常的函数一眼就能看出。

image-20220729174819116

如上图通过呼叫栈你可以看出Monitor管程在反复呼叫enter和wait,这种情况下就可以判断出该程式已经发生死锁且存在效能问题。假设有大量执行绪请求这段程式码,那么CPU资源将被迅速打满!

在著名的「713B站事故」里技术团队在事故发生时就用到了当前工具生成了火焰图,快速地分析出了事故的根因也就是导致CPU100%的lua热点函数。

 

2.某一进程存在异常嫌疑,想快速知道它的状态?

ps命令

我们专案部署的伺服器里在跑的进程老多了,java进程、nginx进程、redis、讯息伫列进程等等。

举个例子,假设在某一流量高峰期系统监控到整个服务效能下降5倍,业务被严重拖垮,在确定没有业务层面bug的情况下大概率就是因为服务效能达到瓶颈了。如何确定瓶颈在哪儿?

大部分情况下通过系统告警就可以知道大概问题所在。如发生讯息堆积我们就该怀疑讯息生产者和消费者的状态,这个时候就要具体去检视讯息伫列这一进程。

可以使用一些轻量级的linux命令,如ps

[root@linuxfancy ~]# ps -ef | grep queuejob
root       1303      1  0 Apr17 ?        00:00:00 /usr/sbin/queuejob
root       3260   3087  0 Apr17 ?        00:00:00 /usr/bin/queuejob /bin/sh -c exec -l /bin/bash -c "env GNOME_SHELL_SESSION_MODE=classic gnome-session --session gnome-classic"
root      24174  19508  0 11:39 pts/0    00:00:00 grep --color=auto ssh
[root@linux265 ~]# ps -aux | grep queueA
root       1303  0.0  0.0  82468  1204 ?        Ss   Apr17   0:00 /usr/sbin/queueA
root       3260  0.0  0.0  52864   572 ?        Ss   Apr17   0:00 /usr/bin/queueA /bin/sh -c exec -l
root      24188  0.0  0.0 112652   956 pts/0    S+   11:39   0:00 grep --color=auto ssh

该命令还可以用于对进程的资源使用情况进行排序:

[root@linuxfancy ~]# ps aux | sort -nk 3
[root@linuxfancy ~]# ps aux | sort -rnk 4 

 

3.我想知道记忆体&磁碟的使用情况?

vmstat命令

vmstat是Virtual Meomory Statistics(虚拟记忆体统计)的缩写。

它是一个用于监控记忆体和磁碟使用情况的工具,但是也可以用来检视CPU的一些指标,如中断次数等。使用它可以检视记忆体使用的详细资讯和磁碟的读/写情况。

image-20220731155906985

以上表头栏位的说明如下:

Procs(进程):

r: 执行伫列中进程数量

b: 等待IO的进程数量

Memory(记忆体):

swpd: 使用虚拟记忆体大小

free: 可用记忆体大小

buff: 用作缓冲的记忆体大小

cache: 用作快取的记忆体大小

Swap(交换):

si: 每秒从交换区写到记忆体的大小

so: 每秒写入交换区的记忆体大小IO:(现在的Linux版本块的大小为1024bytes)bi: 每秒读取的块数bo: 每秒写入的块数

System(系统):

in: 每秒中断数,包括时钟中断

cs: 每秒上下文切换数

CPU(以百分比表示)

us: 用户进程执行时间(user time)

sy: 系统进程执行时间(system time)

id: 空闲时间(包括IO等待时间),中央处理器的空闲时间

wa: IO等待时间

从以上命令就可以很清晰地看出伺服器的各方面效能情况。除此之外还有以下命令也可以在排查或者调优中使用:

image-20220731155528178

 

业务级别bug

如何定位到业务bug?

出现了业务bug那就纯纯的是开发或测试的锅了。

bug确定后第一步一定是先看纪录档,只要你写需求的时候纪录档打的全,一般出现了问题纪录档或者告警都会第一时间推播。

通过纪录档我们可以定位到bug对应程式码的位置,但这仅仅是第一步,因为你只知道哪里出了问题,并不知道程式码出了什么问题(除非一眼就能看出)。

所以下一步,看资料,资料是业务应用的核心。若通过纪录档和页面表现检视到你的主流程是没有问题的,那么下一步就是要确定表的资料是否有问题,资料存在bug的表现会是各方面的,可能是用户反馈,也可能是流程错误,这要取决于你表的设计。

切记!!线上资料是重中之重,当你决定要修复资料,在处理之前一定要做好备份,这样起码可以保证事情不会变的更糟。一般情况下修改线上资料这种活都需要你写好SQL,然后经过leader审批再交给DBA来操作,一定不要干出删库跑路这种事哟。

假设验证了你资料是OK的,那么问题就极大可能出现在了程式码层面

当代程式设计师最难过的瞬间无非就是有一个非常紧急的线上bug需要你来解决,但是摆在你面前的却是一堆屎山程式码!!

修改业务bug最重要的是要将bug点修改掉并且保证其它业务还能正常执行,这是牵一发而动全身的事情,否则bug只会越改越多。

所以平时应该预知到这些风险,做好程式码设计。总结一下定位业务bug的正确步骤:

image-20220731163701459

 

方案设计

程式码设计

一般公司都有自己的程式码设计规范。比如由外到里包装程式码,每一个方法都要有对应的职责,并且一个方法不要超过100行,一个类不要超过1000行程式码等。清晰的结构可以让你和他人更好地review程式码,避免看起来一头雾水。

一般写业务逻辑有两种方式,一种就是简洁明了的线性逻辑,另一种就是通过封装程式码来减少程式码耦合提高内聚性,也就是我们说的设计模式的使用。两种方式各有优缺点,但是工作多年了咱写的程式码也不能直里直气的,多少得带点」艺术「对吧?推荐一下我经常使用但是也不会特别复杂的设计模式。

设计模式

工厂模式

这是最常使用的设计模式之一。

工厂模式分为简单工厂模式、工厂方法模式和抽象工厂模式。我们这里讲解简单工厂模式,因为后两个都是以其为基础做改进的。

其结构如下:

通过定义一个用以建立物件的介面, 让子类决定范例化哪个类。

所以其实质就是由一个工厂类根据传入的引数,动态决定应该建立哪一个产品类(这些产品类继承自一个父类别或介面)的范例。

其包含以下角色:

  • 工厂(Creator)角色: 工厂类的建立产品类的方法可以被外界直接呼叫,建立所需的产品物件。

  • 抽象产品(Product)角色:它负责描述所有范例所共有的公共介面。

  • 具体产品(Concrete Product)角色:建立目标,所有建立的物件都是充当这个角色的某个具体类的范例。

image-20220731152210768

 

当遇到需要根据某个前提条件建立不同的类实现时, 可以使用工厂模式。

 

装饰者模式

它是在不必改变原类结构和继承体系的情况下,动态地扩充套件一个物件的功能。通过建立一个包装物件来实现对功能的扩充套件,动态的给一个物件新增一些额外的职责。

所以装饰者模式分为主体和装饰者。

其包含角色如下:

  • 主体(Main):业务主体逻辑、栏位等。

  • 主体具体实现类(MainComponent):主体具体的实现类。

  • 装饰者(Decorator):要做的装饰扩充套件逻辑介面。

  • 装饰者具体实现类(DecoratorComponent):扩充套件逻辑的具体实现类。

image-20220731154251894

以上两种设计模式都有着」高扩充套件性「的特点,我们应该根据业务灵活设计介面,避免需求迭代导致的一坨坨又臭又长的程式码。但是设计模式切勿用来炫技,一些较为冷门或者复杂的设计模式不推荐使用,否则当一套程式码只有你能维护时,那将会是非常痛苦的。。

image-20220731171435913

当然了这也能够体现出你在公司的不可替代性!

 

架构设计

系统高效能 & 高可用

  • 使用快取:快取的作用是为了系统的读能力。将用户经常存取的资料扔到快取里面可以有效地提高存取速度并且减少资料库的压力。

image-20220731135840288

  • 服务降级 & 限流:若短时间内流量激增影响到伺服器效能,可考虑降级边缘业务以保证核心业务的可用性和效能。

image-20220731135954949

  • 分散式系统 & 服务拆分:将整个系统拆分成不同的业务模组再部署到对应的伺服器中,服务之间通过中介软体通讯,可以有效地避免

和减少单一服务故障对整体系统的影响。

image-20220731140137660

 

  • 高可用架构:重要性不言而喻。同城多活、异地多活的架构部署可以保证单机房挂掉的情况下流量可以迅速切换到其他机房让核心业务不受影响。可谓是防止系统宕机必备良药啊!

image-20220731142619051

做好事故复盘

都说小事故伤身,大事故提桶。。一般发生事故后写一张事故单是不可避免的。除了详细描述好事故发生的经过,背锅人,解决方案,后续的事故跟进也是一系列流程的事,多则需要数周去跟进。其实事故的发生对于团队的技术发展和成型往往起着积极推进作用,所以对于每一个团队来说事故一定是不可避免的。每次事故发生我们都要思考如何完善系统,打破技术壁垒。并且遇到事儿也不要慌,如果是大问题,那么首先背锅的一定是leader!

其实呢一般公司最喜欢的是能快速解决问题的员工,即便这些问题可能是由你创造的。