pipe 导致的 CLOSE_WAIT
历时一周总算把导致服务大量 CLOSE_WAIT
的原因给找到了。打印任务调用栈果然的必备手段啊!
问题描述
Python 服务 A,用于接收心跳包确认其他服务是否存活。其他服务每 5 分钟向 A 发送一次心跳包;总计 < 100 个其他服务。
- 05-11 19:30 ,首次出现 Python 服务大量
CLOSE_WAIT
,至 13 日发现,总计 10k 左右CLOSE_WAIT
的 TCP 连接。05-13 15:30 通过运维平台重新部署... - 05-14 16:30 ,再次出现。19:30 手动重启。
- 其间给 Python 服务 A 添加了打印调用栈的模块 pdbx,通过运维平台重新部署
- 05-17 19:00 ,再次出现。等待打印调用栈,不小心杀掉了服务...
- 05-19 14:30 ,重现。
- 05-20 08:30 ,查找原因,解决问题。
由于不是本人负责的服务,于 16 日凭兴趣开始有限介入,协助排查。现将排查流程一一记述,给自己和大家未来排查问题提供一个借鉴。
PNG 文件格式
PNG (Portable Network Graphics) 文件格式(第二版)。PNG 文件格式由两类结构——PNG 签名(PNG Signature)
和若干数据块(chunk)
组成。
PNG 签名相当于其他文件格式中的魔数,用于声明二进制数据所代表的格式。类似的有 JAVA
class 文件的 ca fe ba be
、ELF
文件的 7f 45 4c 46
(.ELF) 。PNG 签名使用 89 50 4e 47 0d 0a 1a 0a
(.PNG....) 作为魔数。
HINT 上述的
.
仅仅是为了指代非打印字符,并非真的是点号
。
在 PNG 签名
数据之后,紧接着就是 数据块(chunk)
的数据。虽然统称数据块
,但存在不同类型的数据块(例如 IHDR, PLTE, IDAT, IEND 等)。每个 PNG 文件可以有若干连续的数据块(至少 3 个数据块),其中第一个数据块和最后一个数据块类型分别是 IHDR、IEND 。
MySQL TIMESTAMP 时间精度问题
最近一段单元测试代码总是不定时地爆炸。test pass 与 failed 的比例大约 10:1 。伪代码如下:
/**
* 表结构
* CREATE TABLE `time_0` (
* `timeout` timestamp NOT NULL
* )
*/
// part 1
jdbcTemplate.execute("UPDATE `time_0` SET `timeout`=now() WHERE `id` = xxx;");
// part 2
Date timeout = jdbcTemplate.queryForObject("SELECT `timeout` FROM `time_0` WHERE `id` = xxx;", Date.class);
Assert.assertTrue(timeout.getTime() < System.currentTimeMillis());
在绝大多数模拟中,先执行 part 1
,紧跟着执行 part 2
都能通过测试。但偶尔还是挂掉了。
Lex & Yacc 学习笔记
高级语言相较于机器语言、汇编语言,更加符合人的思考习惯。换句话说,更偏向于自然语言的风格而更偏离指令化的描述。用高级语言编写的一行代码,最终可能需要处理器执行若干条指令。如何让机器意识到高级语言代码对应的机器指令是哪些呢?当然就需要一个优秀的翻译。
无论是编译型语言还是解释型语言,总逃脱不了这样一个流程:高级语言 ➜ 目标平台的指令。所谓编译型/解释型的区别,在于其转换流程是在线的(online)还是离线的(offline)。在线的方式无法意识到后续的代码,但胜在即时反应;离线的方式可以统揽全局,进行更多的优化,但代码文本必须完整。
高级语言 ➜ 目标平台的指令,这样的流程如何实现。一般来说,划分为四个阶段:词法分析、语法分析、语义分析、目标代码生成。
本篇的主要目的,是展示“语言翻译”的几个阶段工作,以及通过 Lex & Yacc 工具演示一门自定义语言的“翻译”。
在进行第四篇(任务调度)行文描述时,就一直闹不清内核所谓的task
的概念。之前一直将其与进程(process)的概念等同视之。但这又导致了线程的概念无处安置(毕竟在计算机科学的概念中,线程作为进程的子集存在,负责程序执行)。不过,现在这个疑惑总算得到了合理的解释:我们错误地将理论和实践不加区分地混淆了。内核开发社区与学术界的合作在整个内核开发历史上并没有想象中的频繁,正相反,学术界对内核代码的贡献不到1%[1]。如果想要将进程/线程的思想代入内核,并逐一印证,那么过程将非常痛苦并最终一无所获。所谓进程/线程,在内核中只有一个概念——执行的上下文(Context of Execution),任何想要对进程/线程概念进行区分的行为都将是作茧自缚[2]。同时,task
也就是 Context of Execution
概念在实现上的表征。
如何获取运行时进程堆栈
前些天看了关于在密码学应用中使用java.lang.String
与byte[]
的相关讨论,不推荐使用java.lang.String
的重点就是其将在JVM中驻留,从而可能被窃取。但是,如何从内存中获取这些内容?JVM当然提供了一些机制,但是个人更喜欢从内核的角度来看看这个问题。
/proc/${pid}/maps
首先当然是确定进程堆栈在物理内存的位置啦。很遗憾,没有找到相关的方案。毕竟进程记录的都是虚拟线性地址,而通过内核分段、分页机制最终映射到物理内存。不过,从/proc
虚拟文件系统中,提供了进程虚拟地址映射。
address perm offset dev inode pathname
556566cb5000-556566cb6000 r-xp 00000000 fc:01 2496598 /root/ffTrace/run
556566eb5000-556566eb6000 r--p 00000000 fc:01 2496598 /root/ffTrace/run
556566eb6000-556566eb7000 rw-p 00001000 fc:01 2496598 /root/ffTrace/run
55656814f000-556568170000 rw-p 00000000 00:00 0 [heap]
7f2a95f91000-7f2a96178000 r-xp 00000000 fc:01 1835434 /lib/x86_64-linux-gnu/libc-2.27.so
7f2a96178000-7f2a96378000 ---p 001e7000 fc:01 1835434 /lib/x86_64-linux-gnu/libc-2.27.so
7f2a96378000-7f2a9637c000 r--p 001e7000 fc:01 1835434 /lib/x86_64-linux-gnu/libc-2.27.so
7f2a9637c000-7f2a9637e000 rw-p 001eb000 fc:01 1835434 /lib/x86_64-linux-gnu/libc-2.27.so
7f2a9637e000-7f2a96382000 rw-p 00000000 00:00 0
7f2a96382000-7f2a963a9000 r-xp 00000000 fc:01 1835410 /lib/x86_64-linux-gnu/ld-2.27.so
7f2a965a0000-7f2a965a2000 rw-p 00000000 00:00 0
7f2a965a9000-7f2a965aa000 r--p 00027000 fc:01 1835410 /lib/x86_64-linux-gnu/ld-2.27.so
7f2a965aa000-7f2a965ab000 rw-p 00028000 fc:01 1835410 /lib/x86_64-linux-gnu/ld-2.27.so
7f2a965ab000-7f2a965ac000 rw-p 00000000 00:00 0
7ffe2cf5e000-7ffe2cf7f000 rw-p 00000000 00:00 0 [stack]
7ffe2cfed000-7ffe2cff0000 r--p 00000000 00:00 0 [vvar]
7ffe2cff0000-7ffe2cff2000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
理解 Linux Kernel (9) - IO Multiplexing
前一篇已经对 Linux 内核网络相关的内容进行了基础性的介绍。数据从到达网卡,到最终被用户进程获取,最少经过了三个进程/硬中断的配合:网络中断负责将网络接收到的数据读取到内存并添加到 softnet_data 队列,并设置软中断通知内核进程 ksoftirqd;内核进程 ksoftirqd 被调度并处于运行状态,处理位于 softnet_data 中的 struct sock
对象,将其逐级从网络接口层逐级提升到网络层、传输层...最终添加到接收队列 sk_receive_queue
中;用户进程通过 read
、recv
、recvfrom
等命令检查并获取 sk_receive_queue
中的数据。
整个流程从概述上可以很轻松地配合进行网络数据交互,但如果要监控多个网络套接字呢?处理流程将变得复杂。我们无法预知哪个套接字能优先接收到数据。因此,最直接的办法就是轮询,在用户程序硬编码,通过设置超时时间的方式尝试获取数据。当然,这个效率就相当低下了。每次试探都需要触发系统调用(要知道这代价可是相当大的),另外超时时间的设置也是一个硬性的阻塞式消耗。
那么,有没有解决方案呢?当然有。通过用户程序硬编码式的轮询显然是陷入性能瓶颈的根源。因此内核主动提供了轮询式的系统调用(select
, poll
, epoll
)。通过将轮询逻辑下沉到内核态,系统调用就只会有一次,而且超时时间的设置也显得统一。本篇就要就 select
和 epoll
两类系统调用的实现进行探究。
【Java】API 参数误定义的后果
工程项目中,定义 API 总是一个慎之又慎的操作。不能少,不满足调用方的需求就惨了;也不能多,不然就乱套了,自己维护困难,调用方也开始了自我发挥。虽然足够慎重,但绝大多数都逃不过最终不得不“改” API 的情况。今天要讨论的是在同一个类内同方法名不同参数(入参/出参)的情况。
想要做到同方法名不同入参,很简单,就是“重载(Overload)”,日常都在使用。不再赘述!
想要做到同方法名不同出参,答案就不再那么肯定了。当然,如果问把void add(int)
API 改写成 int add(int)
,可能得到的大多数回答都是可以。