C++服务开发入门指南
  • 序言
  • 前言
  • 一、一个简单的服务
    • 1 什么是服务
    • 2 服务可以用来做什么
    • 3 简单服务框架
  • 二、网络通信服务框架
    • 1 网络服务的基本概念
    • 2 增加监听端口
    • 3 处理客户端会话
    • 小结
  • 三、添加基础模块
    • 1 日志模块
    • 2 定时器
    • 3 事件机制
    • 4 线程池
    • 5 线程安全
    • 小结
  • 四、一个聊天服务
    • 1 需求描述及分析
    • 2 概要设计
    • 3 创建服务项目
    • 4 ClientUser实现
    • 5 RoomMgr实现
    • 6 ChatRoom实现
    • 7 RoomIDMgr实现
    • 小结
  • 五、测试、迭代及重构
    • 1 测试
    • 2 迭代
    • 3 重构
    • 4 版本号
  • 六、架构设计
    • 1 单点服务
    • 2 分布式服务
  • 七、部署及发布
    • 1 部署环境
    • 2 编译环境
    • 3 部署服务
    • 4 发布服务
  • 八、线上问题处理
    • 1 线上问题
    • 2 问题处理
  • 九、程序员的职业规划
    • 职业规划
Powered by GitBook
On this page
  • 1 运行问题
  • 2 业务逻辑出错
  1. 八、线上问题处理

1 线上问题

服务程序从发布开始,就面向线上用户提供服务了。

此后遇到的问题,可以统称为线上问题。

线上问题可以分为运行问题和业务问题。

运行问题包括内存泄露,宕机,卡死等;业务问题主要是业务逻辑出错,表现就是各种各样了。

1 运行问题

1.1 内存泄露

内存泄露是指随着服务的运行,程序占用的内存不断增长。

内存泄露发生的原因,主要是在堆上分配的内存空间没有在合适的时机回收,即new和delete不匹配,活着malloc和free没有对应。

内存泄漏的点有时候很容易确认,更多的时候却非常隐蔽,不容易排查。

如果能够确认A版本没有内存泄漏问题,而B版本出现了,则仔细排查AB版本之间的差异是最有效的方式。仔细梳理内存分配和回收,如果实在无法通过逻辑确认,在怀疑的地方增加日志,并进行压测或线上运行,通过日志信息进一步确认。

内存泄漏一般短时间不会造成太大影响,业务运行都表现正常。但服务一般都是长时间运行的,持续的内存增长到达上限后,可能引发其他异常或宕机。

因此,内存泄漏是必须要重视的问题,发生后需尽快查明原因,避免潜在的更严重的风险。

1.2 宕机

宕机是指服务异常中止运行。

宕机通常是由于内存的非法访问引起,可能是数组越界访问、野指针调用等。

服务宕机时,有可能生成dump文件,如果能够通过dump文件查找到宕机时的调用堆栈,确认是哪行代码引发的宕机,对于排查问题是极有帮助的。

有时候生成的dump文件无法定位,或者没有生成dump文件,就只能通过日志文件分析,在宕机前执行到哪个业务流程,大概在什么地方有问题,再进行细致排查。

宕机会造成用户内存状态的丢失,其影响程度跟整体后端服务体系有关,有可能影响严重,也可能无关痛痒。

但宕机是严重的开发问题,发现后需尽快解决。

1.3 卡死

卡死是指服务由于某些原因,程序在某个流程中间卡住,无法按照正常逻辑继续执行。

引发卡死的情形通常有死锁、进入死循环,活着sleep超长的时间等。

服务卡死后,无法继续执行,由于很多情况下前端需要等待服务的反馈才能继续执行,这可能导致用户前端也处于卡死或无响应状态。而服务卡死后,开发和运维又很难像宕机一样即时感知到,有时候卡死造成的影响比宕机还大。

2 业务逻辑出错

业务逻辑出错的情形是多种多样的,很多时候是边界测试不到位,在一些很极端的情况下发生逻辑错误。

这些情况在测试中不容易遇到,在灰度发布时也没有触发,直到线上全网发布,用户基数变大,偶然变成必然,运行后才会出现。

下面列举一些在项目中真实发生的过的案例。

2.1 特殊的用户昵称

用户昵称中出现了特殊字符,在使用用户昵称拼装json数据时,导致json解析出现问题。

这类错误很容易复现,因此找到问题原因,并解决也不是很复杂。

这类问题麻烦在于,在出现之前,很难想到昵称中的特殊字符会影响json字符串的解析。

这需要有相应的经验,针对这类问题完善自动化检测机制,对于不在系统控制的可变字符做适配性验证。

2.2 有极限的计时器

问题表现是在独立线程中的日志模块,在服务运行一段时间后,不再输出到文件,服务内存占用在一直升高。

经过代码分析发现,在毫秒级时间比较的时候,使用了clock()函数。

clock()函数可以获取程序启动后,到函数调用时经历的毫秒数,返回值类型clock_t是长整形,最大表示数据为2147483647,windows系统下最大表示24.8天。

当程序运行24天后,clock取值超过上限,时间比较出错,导致预期的逻辑无法执行,日志一直缓存在内容中,无法落盘。

服务端尽量不要使用clock()和getTickCount函数,取而代之应可以使用64位的函数getTickCount64,或使用C++11 chrono提供的高精度时间获取函数。

这类问题初始运行正常,只有运行很长时间(24天)后,才会触发异常逻辑,问题发现后很好解决,只能通过代码规范,禁止某些函数使用,或在这类函数出现后保持高度警惕。

2.3 碰撞的请求处理

问题表现A、B两个用户的订单串了:A支付的订单,商品给了B。

审查代码后发现,在支付相关消息处理时,复用了某条TCP连接,但缺少了必要的数据校验。当用户A和B同时发送请求的时候,A的请求先被处理,B的请求到达后,A的请求正好处理完,结果用于回复了B用户。

这类问题,在没有请求碰撞的情况下,逻辑正常;只有在AB两天请求在同一毫秒内到达时会触发。

这种问题在QA测试时极难发现,在单台服务灰度发布时也不容易遇到,而在全平台大量线上用户并发请求的场景下,偶然的事件就变成了必然。

这里也为我们带来一些启示:能在线上运行很多年,且稳定不出异常的程序,即使代码再难看,也有其价值。而再优秀的设计与实现,也只有经历线上业务的考验,才能证明自身。

2.4 被覆盖的协议

服务发布后,发现上个版本迭代的某些功能失效了,代码分析发现,是pb协议文件被覆盖导致。

pb协议文件,是proto文件通过google的转换工具,生成的C++代码文件。

在代码合并的时候,这部分的修改通常不会被特别重视。代码review更多关注业务逻辑代码的改变。

QA测试的时候,通常会针对当前版本的修改进行校验,以及可能影响到的模块和业务,通常情况下很难每次迭代,全面遍历验证。

而灰度测试的时候,用户量较少,且影响功能不是核心逻辑,也为收到用户反馈。

但是正式上线的时候,用户基数变大,很快就得到反馈,回退版本后,修正再次发布。

线上问题多种多样,有些很好解决,有些却不容易定位;有些影响很小,有些造成的后果很严重。

我们不仅要解决线上问题,还需要在线上问题发生时,做好稳妥应对,尽量缩小影响范围,减少经济损失。

问题解决后,还需要及时复盘,确认问题发生的原因,以及如何做才能避免类似的问题再次发生,或完善测试工具,或优化业务流程,或改进编程规范。最终目标是减少问题发生。

Previous八、线上问题处理Next2 问题处理

Last updated 2 years ago