小菜成长之路:警惕沦为 API 调用侠 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
div class="inner" id="node_sidebar"> 请不要在回答技术问题时复制粘贴 AI 生成的内容
fasionchan
V2EX    程序员

小菜成长之路:警惕沦为 API 调用侠

  fasionchan 2020-06-15 09:00:27 +08:00 11754 次点击
这是一个创建于 1947 天前的主题,其中的信息可能已经有所发展或是发生改变。

小菜(化名)在某互联网公司担任运维工程师,负责公司后台业务的运维保障工作。由于自己编程经验不多,平时有不少工作需要开发协助。

听说 Python很火,能快速开发一些运维脚本,小菜也加入 Python大军学起来。 Python语言确实简单,小菜很快就上手了,觉得自己应对运维开发工作已经绰绰有余,便不再深入研究。

背景

这天老板给小菜派了一个数据采集任务,要实时统计服务器 TCP连接数。需求背景是这样的:开发同事需要知道服务的连接数以及不同状态连接的比例,以便判断服务状态。

因此,小菜需要开发一个脚本,定期采集并报告 TCP连接数,提交数据格式定为 json

{ "LISTEN": 4, "ESTABLISHED": 100, "TIME_WAIT": 10 } 

作为运维工程师,小菜当然知道怎么查看系统 TCP连接。Linux系统中有两个命令可以办到, netstatss

$ netstat -nat Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 127.0.0.1:8388 0.0.0.0:* LISTEN tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 192.168.56.3:22 192.168.56.1:54983 ESTABLISHED tcp6 0 0 :::22 :::* LISTEN 
$ ss -nat State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 127.0.0.1:8388 0.0.0.0:* LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:* LISTEN 0 128 0.0.0.0:22 0.0.0.0:* ESTAB 0 0 192.168.56.3:22 192.168.56.1:54983 LISTEN 0 128 [::]:22 [::]:* 

小菜还知道 ss命令比 netstat命令要快,但至于为什么,小菜就不知道了。

小菜很快找到老板,提出了自己的解决方案:写一个 Python程序,调用 ss命令采集 TCP连接信息,然后再逐条统计。

老板告诉小菜,线上服务器很多都是最小化安装,并不能保证每台机器上都有 ss或者 netstat命令。

老板还告诉小菜,程序开发要学会 站在巨人的肩膀上。动手写代码前,先调研一番,看是否有现成的解决方案。 切忌重复造轮子,浪费时间不说,可能代码质量还差,效果也不好。

最后老板给小菜指了条明路,让他回去再看看 psutilpsutil是一个 Python第三方包,用于采集系统性能数据,包括: CPU、内存、磁盘、网卡以及进程等等。临走前,老板还叮嘱小菜,完成工作后花点时间研究下这个库。

psutil 方案

小菜搜索 psutil发现,原来有这么顺手的第三方库,喜出望外!他立马装好 psutil,准备开干:

$ pip install psutil 

导入 psutil后,一个函数调用就可以拿到系统所有连接,连接信息非常丰富:

>>> import psutil >>> for conn in psutil.net_connections('tcp'): ... print(conn) ... sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='192.168.56.3', port=22), raddr=addr(ip='192.168.56.1', port=54983), status='ESTABLISHED', pid=None) sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.1', port=8388), raddr=(), status='LISTEN', pid=None) sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='0.0.0.0', port=22), raddr=(), status='LISTEN', pid=None) sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.53', port=53), raddr=(), status='LISTEN', pid=None) sconn(fd=-1, family=<AddressFamily.AF_INET6: 10>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='::', port=22), raddr=(), status='LISTEN', pid=None) 

小菜很满意,感觉不用花多少时间就可搞定数据采集需求了,准时下班有望!噼里啪啦,很快小菜就写下这段代码:

import psutil from collections import defaultdict # 遍历每个连接,按连接状态累加 stats = defaultdict(int) for conn in psutil.net_connections('tcp'): stats[conn.status] += 1 # 遍历每种状态,输出连接数 for status, count in stats.items(): print(status, count) 

小菜接着在服务器上测试这段代码,功能完全正常:

ESTABLISHED 1 LISTEN 4 

小菜将数据采集脚本提交,并按既定节奏逐步发布到生产服务器上。开发同事很快就看到小菜采集的数据,都夸小菜能力不错,需求完成得很及时。小菜也很高兴,感觉 Python没白学。如果用其他语言开发,说不定现在还在加班加点呢!Life is short, use Python! 果然没错!

小菜愈发自信,早就把老板的话抛到脑后了。 psutil这个库这么好上手,有啥好深入研究的?

内存悲剧

突然有一天,其他同事紧急告诉小菜,他开发的采集脚本占用很多内存, CPU也跑到了 100%,已经开始影响线上服务了。小菜还沉浸在成功的喜悦中,收到这个反馈如同晴天霹雳,有点举手无措。

业务同事告诉小菜,受影响的机器系统连接数非常大,质疑小菜是不是脚本存在性能问题。小菜觉得很背,脚本只是调用 psutil并统计数据,怎么就摊上性能故障?脚本影响线上服务,小菜压力很大,但不知道如何是好,只能跑去找老板寻求帮助。

老板要小菜第一时间停止数据采集,降低影响。复盘故障时,老板很敏锐地问小菜,是不是用容器保存所有连接了?小菜自己并没有,但是 psutil这么做了:

>>> psutil.net_connections() [sconn(fd=-1, family=<AddressFamily.AF_INET6: 10>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='::', port=22), raddr=(), status='LISTEN', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='0.0.0.0', port=22), raddr=(), status='LISTEN', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.53', port=53), raddr=(), status='LISTEN', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_DGRAM: 2>, laddr=addr(ip='10.0.2.15', port=68), raddr=(), status='NONE', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_DGRAM: 2>, laddr=addr(ip='127.0.0.1', port=8388), raddr=(), status='NONE', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='192.168.56.3', port=22), raddr=addr(ip='192.168.56.1', port=54983), status='ESTABLISHED', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_DGRAM: 2>, laddr=addr(ip='127.0.0.53', port=53), raddr=(), status='NONE', pid=None), sconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, laddr=addr(ip='127.0.0.1', port=8388), raddr=(), status='LISTEN', pid=None)] 

psutil将采集到的所有 TCP 连接放在一个列表里返回。如果服务器上有十万个 TCP连接,那么列表里将有十万个连接对象。难怪采集脚本吃了那么多内存!

老板告诉小菜,可以用生成器加以解决。与列表不同,生成器逐个返回数据,因此不会占用太多内存。Python2rangexrange函数的区别也是一样的道理。

小菜从 pstuilfork了一个分支,并将 net_connections 函数改造成 生成器

def net_connections(): while True: if done: break # 解析一个 TCP 连接 cOnn= xxx yield conn 

代码上线后,采集脚本内存占用量果然下降了! 生成器将统计算法的空间复杂度由原来的O(n)优化为 O(1)。经过这次教训,小菜不敢再盲目自信了,他决定抽时间好好看看 psutil的源码。

源码体会

深入学习源码后,小菜发现原来psutil采集 TCP连接数的秘笈是:从/proc/net/tcp以及 /proc/net/tcp6 读取连接信息。

由此,他还进一步了解到 procfs,这是一个伪文件系统,将内核空间信息以文件方式暴露到用户空间。 /proc/net/tcp文件则是提供内核 TCP连接信息:

$ cat /proc/net/tcp sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 0: 0100007F:20C4 00000000:0000 0A 00000000:00000000 00:00000000 00000000 65534 0 18183 1 000000000000000 100 0 0 10 0 1: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 101 0 16624 1 0000000000000000 100 0 0 10 0 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 18967 1 0000000000000000 100 0 0 10 0 3: 0338A8C0:0016 0138A8C0:D6C7 01 00000000:00000000 02:00023B11 00000000 0 0 22284 4 0000000000000000 20 13 23 10 20 

小菜还注意到,连接信息看起来像个自定义类对象,但其实是一个 nametuple

# psutil.net_connections() scOnn= namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr', 'status', 'pid']) 

小菜一开始并不知道作者为啥要这么做。后来,小菜开始研究 Python源码,学习了 Python类机制后他恍然大悟。

Python自定义类的每个实例对象均需要一个 dict来保存对象属性,这也就是对象的 属性空间

如果用自定义类来实现,每个连接都需要创建一个字典,而字典又是 散列表实现的。如果系统存在成千上万的连接,开销可想而知。

小菜将学到的知识总结起来:对于 数量大属性固定的实体,没有必要用自定义类来实现,用 nametuple更合适,开销更小。由此,小菜不经由衷佩服 psutil的作者。

CPU 悲剧

后来小菜又收到业务反馈,采集脚本在高并发的服务器上, CPU使用率很高,需要再优化一下。

小菜回忆 psutil源码,很快就找到了性能瓶颈处:psutil将连接信息所有字段都解析了,而采集脚本只需要其中的 状态字段而已。

跟老板商量后,小菜决定自行读取 procfs来实现采集脚本,只解析状态字段,避免不必要的计算开销。

procfs 方案

直接读取 /proc/net/tcp,可以得到完整的 TCP连接信息:

>>> with open('/proc/net/tcp') as f: ... for line in f: ... print(line.rstrip()) ... sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 0: 0100007F:20C4 00000000:0000 0A 00000000:00000000 00:00000000 00000000 65534 0 18183 1 0000000000000000 100 0 0 10 0 1: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 101 0 16624 1 0000000000000000 100 0 0 10 0 2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 18967 1 0000000000000000 100 0 0 10 0 3: 0338A8C0:0016 0138A8C0:D6C7 01 00000000:00000000 02:0007169E 00000000 0 0 22284 3 0000000000000000 20 20 33 10 20 

其中, IP、端口、状态等字段都是以十六进制编码的。例如, st列表示状态,状态码0A表示 LISTEN。很快小菜就写下这段代码:

from collections import defaultdict stat_names = { '0A': 'LISTEN', '01': 'ESTABLISHED', # ... } # 遍历每个连接,按连接状态累加 stats = defaultdict(int) with open('/proc/net/tcp') as f: # 跳过表头行 f.readline() for line in f: st = line.strip().split()[3] stats[st] += 1 for st, count in stats.items(): print(stat_names[st], count) 

现在,小菜写代码比之前讲究多了。在统计连接数时,他并不急于将状态码解析成名字,而是按原样统计。等统计完成,他再一次性转换,这样状态码转换开销便降到最低: O(1) 而不是 O(n)

这次改进符合业务同事预期,但小菜决定好好做一遍性能测试,不打无准备之仗。他找业务同事要了一个连接数最大的 /proc/net/tcp样本,拉到本地测试。测试结果还算符合预期,采集脚本能够扛住十万连接采集压力。

性能测试中,小菜发现了一个比较奇怪的问题。同样的连接规模,把 /proc/net/tcp拉到本地跑比直接在服务器上跑要快,而本地电脑性能肯定比不上服务器。

他百思不得其解,又去找老板帮忙。老板很快指出到其中的区别,将 /proc/net/tcp拉到本地就成为普通 磁盘文件,而 procfs是内核映射出来的 伪文件,并不是磁盘文件。

他让小菜研究一下 Python文件 IO以及内核 IO子系统在处理这两种文件时有什么区别,还让小菜特别留意 IO缓冲区大小。

IO 缓冲

小菜打开一个普通的磁盘文件,发现 Python选的默认缓冲区大小是 4K(读缓存对象头 152字节):

>>> f = open('test.py') >>> f.buffer.__sizeof__() 4248 

但是如果打开的是 procfs文件, Python选的缓冲区却只有 1K,相差了 4倍呢!

>>> f = open('/proc/net/tcp') >>> f.buffer.__sizeof__() 1176 

因此,理论上 Python默认读取 procfs发生的上下文切换次数是普通磁盘文件的 4倍,怪不得会慢。

虽然小菜还不知道这种现象背后的原因,但是他已经知道怎么进行优化了。随即他决定将缓冲区设置为 1M以上,尽量避免 IO上下文切换,以空间换时间:

with open('/proc/net/tcp', buffering=1*1024*1024) as f: # ... 

经过这次优化,采集脚本在大部分服务器上运行良好,基本可以高枕无忧了。而小菜也意识到 编程语言以及 操作系统等底层基础知识的重要性,他开始制定学习计划补全计算机基础知识。

netlink 方案

后来负载均衡团队找到小菜,他们也想统计服务器上的连接信息。由于负载均衡服务器作为入口转发流量,连接数规模特别大,达到几十万,将近百万的规模。小菜决定好好进行性能测试,再视情况上线。

测试结果并不乐观,采集脚本要跑几十秒钟才完成, CPU跑到 100%。小菜再次调高 IO缓冲区,但效果不明显。小菜又测试了 ss命令,发现 ss命令要快很多。由于之前尝到了阅读源码的甜头,小菜很想到 ss源码中寻找秘密。

由于项目时间较紧,老板提醒小菜先用 strace命令追踪 ss命令的系统调用,便可快速获悉 ss的实现方式。老板演示了 strace命令的用法,很快就找到了 ss的秘密 Netlink

$ strace ss -nat ... socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_SOCK_DIAG) = 3 ... 

Netlink套接字是 Linux提供通讯机制,可用于内核与进程间、进程与进程间通讯。 Netlink下的 sock_diag子系统,提供了一种从内核获取套接字信息的新方式。

procfs不同,sock_diag 采用网络通讯的方式,内核作为服务端接收客户端进程查询请求,并以二进制数据包响应查询结果,效率更高。

这就是 ssnetstat更快的原因, ss采用 Netlink机制,而 netstat采用 procfs机制。

很不幸Python并没有提供 Netlink API,一般人可能又要干着急了。好在小菜先前有意识地研究了部分Python源码,对 Python的运行机制有所了解。

他知道可以用 C写一个 Python扩展模块,在 C语言中调用原生系统调用。

编写 Python C扩展模块可不简单,对编程功底要求很高,必须全面掌握 Python运行机制,特别是对象内存管理。

一朝不慎可能导致程序异常退出、内存泄露等棘手问题。好在小菜已经不是当年的小菜了,他经受住了考验。

小菜的扩展模块上线后,效果非常好,顶住了百万级连接的采集压力。

一个看似简单得不能再简单的数据采集需求,背后涉及的知识可真不少,没有一定的水平还真搞不定。好在小菜成长很快,他最终还是彻底地解决了性能问题,找回了久违的信心。

内核模块方案

虽然性能问题已经彻底解决,小菜还是没有将其淡忘。

他时常想:如果可以将统计逻辑放在内核空间做,就不用在内核和进程之间传递大量连接信息了,效率应该是最高的!受限于当时的知识水平,小菜还没有能力实现这个设想。

后来小菜在研究 Linux内核时,发现可以用内核模块来扩展内核的功能,结合 procfs的工作原理,他找到了技术方案!他顺着 /proc/net/tcp在内核中的实现源码,依样画葫芦写了这个内核模块:

#include <linux/module.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <net/tcp.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Xiaocai"); MODULE_DESCRIPTION("TCP state statistics"); MODULE_VERSION("1.0"); // 状态名列表 static char *state_names[] = { NULL, "ESTABLISHED", "SYN_SENT", "SYN_RECV", "FIN_WAIT1", "FIN_WAIT2", "TIME_WAIT", "CLOSE", "CLOSE_WAIT", "LAST_ACK", "LISTEN", "CLOSING", NULL }; static void stat_sock_list(struct hlist_nulls_head *head, spinlock_t *lock, unsigned int state_counters[]) { // 套接字节点指针(用于遍历) struct sock *sk; struct hlist_nulls_node *node; // 链表为空直接返回 if (hlist_nulls_empty(head)) { return; } // 自旋锁锁定 spin_lock_bh(lock); // 遍历套接字链表 sk = sk_nulls_head(head); sk_nulls_for_each_from(sk, node) { if (sk->sk_state < TCP_MAX_STATES) { // 自增状态计数器 state_counters[sk->sk_state]++; } } // 自旋锁解锁 spin_unlock_bh(lock); } static int tcpstat_seq_show(struct seq_file *seq, void *v) { // 状态计数器 unsigned int state_counters[TCP_MAX_STATES] = { 0 }; unsigned int state; // TCP 套接字哈希槽序号 unsigned int bucket; // 先遍历 Listen 状态 for (bucket = 0; bucket < INET_LHTABLE_SIZE; bucket++) { struct inet_listen_hashbucket *ilb; // 哈希槽 ilb = &tcp_hashinfo.listening_hash[bucket]; // 遍历链表并统计 stat_sock_list(&ilb->head, &ilb->lock, state_counters); } // 遍历其他状态 for (bucket = 0; bucket < tcp_hashinfo.ehash_mask; bucket++) { struct inet_ehash_bucket *ilb; spinlock_t *lock; // 哈希槽链表 ilb = &tcp_hashinfo.ehash[bucket]; // 保护锁 lock = inet_ehash_lockp(&tcp_hashinfo, bucket); // 遍历链表并统计 stat_sock_list(&ilb->chain, lock, state_counters); } // 遍历状态输出统计值 for (state = TCP_ESTABLISHED; state < TCP_MAX_STATES; state++) { seq_printf(seq, "%-12s: %d\n", state_names[state], state_counters[state]); } return 0; } static int tcpstat_seq_open(struct inode *inode, struct file *file) { return ingle_open(file, tcpstat_seq_show, NULL); } static const struct file_operations tcpstat_file_ops = { .owner = THIS_MODULE, .open = tcpstat_seq_open, .read = seq_read, .llseek = seq_lseek, .release = single_release }; static __init int tcpstat_init(void) { proc_create("tcpstat", 0, NULL, &tcpstat_file_ops); return 0; } static __exit void tcpstat_exit(void) { remove_proc_entry("tcpstat", NULL); } module_init(tcpstat_init); module_exit(tcpstat_exit); 

内核模块编译好并加载到内核后, procfs文件系统提供了一个新文件 /proc/tcpstat,内容为统计结果:

$ cat /proc/tcpstat ESTABLISHED : 5 SYN_SENT : 0 SYN_RECV : 0 FIN_WAIT1 : 0 FIN_WAIT2 : 0 TIME_WAIT : 1 CLOSE : 0 CLOSE_WAIT : 0 LAST_ACK : 0 LISTEN : 14 CLOSING : 0 

当用户程序读取这个文件时,内核虚拟文件系统( VFS)调用小菜在内核模块中写的处理函数:遍历内核 TCP套接字完成统计并格式化统计结果。内核模块、 VFS以及套接字等知识超出专栏范围,不再赘述。

小菜在服务器上试验这个内核模块,真的快得飞起!

经验总结

小菜开始总结这次脚本开发工作中的经验教训,他列出了以下关键节点:

  1. 依靠psutil采集,没有关注 psutil实现导致性能问题;
  2. 用生成器代替列表返回连接信息,解决内存瓶颈;
  3. 直接读取 procfs文件系统,部分解决 CPU性能瓶颈;
  4. 通过调节 IO缓冲区大小,进一步降低 CPU开销;
  5. Netlink代替 procfs,彻底解决性能问题;
  6. 实验内核模块思路,终极解决方案快得飞起;

这些问题节点,一个比一个深入,没有一定功底是搞不定的。小菜从刚开始跌跌撞撞,到后来独当一面,快速成长的关键在于善于在问题中总结经验教训:

  • 程序开发完一定要做性能测试,看能够扛住多大的压力;
  • 使用任何工具,需要准确理解其背后的原理,避免误用;
  • 对编程语言以及操作系统源码要保持好奇心;
  • 计算机基础知识很重要,需要及时补全才能达到新高度;
  • 学会问题发散,举一反三;

更多章节

洞悉 Python 虚拟机运行机制,探索高效程序设计之道!

到底如何才能提升我的 Python 开发水平,向更高一级的岗位迈进? 如果你有这些问题或者疑惑,请订阅我们的专栏,阅读更多章节:

附录

更多 Python 技术文章请访问:小菜学 Python,转至 原文 可获得最佳阅读体验。

订阅更新,获取更多学习资料,请关注 小菜学编程

小菜学编程

85 条回复    2020-06-18 16:37:01 +08:00
sadfQED2
    1
sadfQED2  
   2020-06-15 09:11:30 +08:00 via Android   57
翻到最后,果然
matsuijurina
    2
matsuijurina  
   2020-06-15 09:16:29 +08:00 via Android   7
公司业务面对的全是十万百万连接的问题,老板还有空教菜鸟运维 Linux 命令怎么用
iceecream
    3
iceecream  
   2020-06-15 09:17:58 +08:00   3
虽然是软文,但是确实受教了。
fasionchan
    4
fasionchan  
OP
   2020-06-15 09:22:03 +08:00
@matsuijurina 他只有将小菜教会才能把锅交出去哈哈
fasionchan
    5
fasionchan  
OP
   2020-06-15 09:23:22 +08:00
@sadfQED2 轻拍……
flyhelan
    6
flyhelan  
   2020-06-15 09:27:28 +08:00
@iceecream 是啊。有启发就行。
sagaxu
    7
sagaxu  
   2020-06-15 09:28:50 +08:00 via Android
头一回给软文点赞
HANXIAO1996
    8
HANXIAO1996  
   2020-06-15 09:40:25 +08:00   3
我觉得软文就是欺骗人。
NoirStrike
    9
NoirStrike  
   2020-06-15 09:52:44 +08:00
教程还不错的感觉, 就是这故事有点...
fasionchan
    10
fasionchan  
OP
   2020-06-15 09:56:24 +08:00
@NoirStrike 不是故事,根据在上家公司遇到的案例改编的
neeok
    11
neeok  
   2020-06-15 09:56:39 +08:00
@matsuijurina 真实情况就是先骂一顿,然后炒掉.
p1094358629
    12
p1094358629  
   2020-06-15 10:04:03 +08:00
虽然是软文,感觉还不错
fasionchan
    13
fasionchan  
OP
   2020-06-15 10:06:54 +08:00
@neeok 别的地方我不了解,就我待过的腾讯、网易游戏、蚂蚁金服这几家,一般不会这样做。

实习生应届生大多数还是要手把手教的,但不能一直处于这个状态;这时新人学习能力和主动性就很重要,这决定他的成长速度;如果他做得足够好,很快就可以渡过新手期,独当一面。

当然了,如果基础比较差,然后态度又有问题,进展缓慢,还是会给负反馈,比如绩效垫底,但直接炒掉的我没见过
crella
    14
crella  
   2020-06-15 10:10:37 +08:00 via Android
用 ruby 也是经常担心爆内存的问题。假如原帖场景为单核虚拟机 cpu 和 2G 内存,python 保存十万个对象也会爆内存吗?手动调用 GC 会不会好一点?

觉得把 python 读取 ps_util 的过程做成子进程,每读取 1000 个之后结束子进程,在开始下一个子进程,这样可能不会爆内存。(?)

楼上写内核模块是会提高性能,可是要是需求变化了一下,改得就麻烦多了。
fasionchan
    15
fasionchan  
OP
   2020-06-15 10:13:47 +08:00
@crella 是的,而且生成服务器内核版本跨度很大,无法做到可移植,部署也是个问题;所以内核模块只是一个技术思路储备,当时并没真正上线使用
fasionchan
    16
fasionchan  
OP
   2020-06-15 10:19:48 +08:00   2
@crella 如果有 2G 内存,python 保存几十万个对象,多半不会有问题。问题在于,业务不会给一个运维采集数据用的 agent 2G 内存的,我们当时做这个 agent,不关做数据采集还做任务执行,设置的内存红线是 100M 。实际上,我们做的时候是按照 50M 的目标去做的。如果 agent 因为设计问题占用大量内存,就会压缩业务应用的内存资源,诱发生产事件。说白了,运维系统要尽量做到对应用无感,不管是体现在业务逻辑上,还是对服务器资源的占用上。
paoqi2048
    17
paoqi2048  
   2020-06-15 10:28:02 +08:00
感谢分享
lostpg
    18
lostpg  
   2020-06-15 10:34:01 +08:00 via Android
挺有意思的软文,不知道能坚持产出多久,另外 python 来个虚拟环境呗,现在 poetry 挺好用的了。
xiangchen2011
    19
xiangchen2011  
   2020-06-15 10:45:21 +08:00
写的蛮好的,思路层层展开,“老板”不亏是老板
Arrowing
    20
Arrowing  
   2020-06-15 10:59:41 +08:00
老板牛逼!
jasamboro
    21
jasamboro  
   2020-06-15 11:04:39 +08:00
老板太忙了。。。
optional
    22
optional  
   2020-06-15 11:19:10 +08:00
性能越来越高,感觉兼容性 /通用性越来越差
MarkLeeyun
    23
MarkLeeyun  
   2020-06-15 11:20:50 +08:00
果然这种文章适合发在微信公众号上。。。
MarkLeeyun
    24
MarkLeeyun  
   2020-06-15 11:24:24 +08:00
v2ex 还是个讨论区,这么长的文章。。。。牛批。。
fasionchan
    25
fasionchan  
OP
   2020-06-15 11:27:26 +08:00
@optional 对,这两者矛盾似乎难以调和
wszgrcy
    26
wszgrcy  
   2020-06-15 11:30:39 +08:00 via Android
满足,果然是
NCZkevin
    27
NCZkevin  
   2020-06-15 11:30:48 +08:00
有意思
xuzhzzz
    28
xuzhzzz  
   2020-06-15 11:40:05 +08:00
开源方案一堆不用,下次要监控别的你又得学 python 搜一搜了啊
levelworm
    29
levelworm  
   2020-06-15 12:09:49 +08:00 via Android
感觉文章不错,越写越深。看来基础知识的确重要。不知道运维这块有没有比较系统的知识体系?
stefanaka
    30
stefanaka  
   2020-06-15 12:12:16 +08:00 via Android
prometheus node_exporter
imdong
    31
imdong  
   2020-06-15 12:16:25 +08:00 via iPhone
就这样的软文,请再给我来一打。
winnerczwx
    32
winnerczwx  
   2020-06-15 12:36:07 +08:00 via iPhone
一看标题以为是鸡汤文,结果发现有干货
longjiahui
    33
longjiahui  
   2020-06-15 12:43:22 +08:00 via iPhone
是我讨厌的标题
Justin13
    34
Justin13  
   2020-06-15 12:48:35 +08:00 via Android
看完了,小菜牛逼,老板也是好人。
dremy
    35
dremy  
   2020-06-15 12:48:55 +08:00 via iPhone
受教了,原来还能这么用
brucep
    36
brucep  
   2020-06-15 12:51:03 +08:00
写的挺好的,向你学习。
Meltdown
    37
Meltdown  
   2020-06-15 12:52:43 +08:00 via Android   3
从 Python 搞到了内核,这真的是菜吗?
F281M6Dh8DXpD1g2
    38
F281M6Dh8DXpD1g2  
   2020-06-15 12:57:15 +08:00
你们的运维工程师是怎么 justify 使用内核模块的......风险这么高的事情
而且调 os 的 api 就不是调 api 了是么
levelworm
    39
levelworm  
   2020-06-15 12:57:49 +08:00 via Android
@Meltdown 我也觉得非常牛逼。。。
fasionchan
    40
fasionchan  
OP
   2020-06-15 13:02:38 +08:00
@liprais 当时内核模块方案没有在线上用,只是作为一个备用技术思路,技术研究性质的
shino996
    41
shino996  
   2020-06-15 13:10:02 +08:00 via iPhone
直接翻到最下面, 果然有我想看到的东西
peachpeach
    42
peachpeach  
   2020-06-15 13:11:23 +08:00 via iPhone
作为嵌入式汪,这篇文章确实不错。

应该让程序猿们都了解一下底层的一些东西。
efaun
    43
efaun  
   2020-06-15 13:13:14 +08:00
如果一台机器连 netstat 都没有,大概率也不会有 py 吧
fasionchan
    44
fasionchan  
OP
   2020-06-15 13:16:27 +08:00
@efaun 是的,所以我们的 agent 自带 py 运行时
sudoy
    45
sudoy  
   2020-06-15 13:17:13 +08:00
不错,有帮助
dumbass
    46
dumbass  
   2020-06-15 13:44:17 +08:00
这篇教程写的生动有趣哦
redford42
    47
redford42  
   2020-06-15 13:52:12 +08:00
卧槽...这个小菜大概比我厉害三倍。
qW7bo2FbzbC0
    48
qW7bo2FbzbC0  
   2020-06-15 14:04:35 +08:00
同时面对 cent6 和 7 时,用 lsof -i |grep listen 似乎是个更好的方案吧,ss 和 netstat 在不同版本上速度是不一样的,有时候 ss 快有时候 netstat 快,而且慢的时候很慢,平均都不如 lsof 方案快
fasionchan
    49
fasionchan  
OP
   2020-06-15 14:11:48 +08:00
ss netstat 慢可能是 DNS 反解引起的,lsof 需要遍历 proc 下每个进程的每个 fd,按理应该不如 ss 或 netstat 快
fhsan
    50
fhsan  
   2020-06-15 14:41:32 +08:00
your pics, now mine
janwarlen
    51
janwarlen  
   2020-06-15 14:54:42 +08:00
文章质量不错,思路清晰
KuroNekoFan
    52
KuroNekoFan  
   2020-06-15 14:55:52 +08:00
意思就是别用 python 吗(
Huelse
    53
Huelse  
   2020-06-15 15:20:25 +08:00
文章挺生动的
说实话,我用 python 曾多次写着写着就被迫去改 Lib 里的代码
mmrx
    54
mmrx  
   2020-06-15 15:58:17 +08:00
软文又怎么了
只要有东西,有人感兴趣,这个帖子就没白发。
adfew1234
    55
adfew1234  
   2020-06-15 16:14:12 +08:00
写的不错,故事精彩,代码读起来也不错
fasionchan
    56
fasionchan  
OP
   2020-06-15 16:42:20 +08:00
@mmrx 哈哈,这或许就是软文硬写?
yyt6801
    57
yyt6801  
   2020-06-15 17:00:44 +08:00
看完了,虽是软文,不过挺不错、学习了。 ps:这老板真心厉害...
c4fun
    58
c4fun  
   2020-06-15 18:21:02 +08:00
不错的软文+硬文,比较适合给运维和运维开发团队看。还有这个老板真的厉害,都这么大的并发量了还有精力研究技术。
qianProgrammer
    59
qianProgrammer  
   2020-06-15 18:26:53 +08:00
学习了,还有这老板可真厉害...
fasionchan
    60
fasionchan  
OP
   2020-06-15 19:43:46 +08:00
@KuroNekoFan python 确实有不少弱点,还是要看具体场景,对于性能比较敏感的模块,我现在慢慢转用 Go 来实现,感觉在开发效率与执行效率间找到了一个更好的平衡
Licsber
    61
Licsber  
   2020-06-15 20:00:13 +08:00
虽说是软文 但是还不错
27
    62
27  
   2020-06-15 20:00:16 +08:00
这老板居然什么都会,现实中还真没见过。。
plko345
    63
plko345  
   2020-06-15 20:27:10 +08:00
NB, 关注了, 话说这样的需求应该不少吧, 就没有现成的方案吗?
fengjianxinghun
    64
fengjianxinghun  
   2020-06-15 21:30:52 +08:00
这个软件硬写还行。
fengjianxinghun
    65
fengjianxinghun  
   2020-06-15 21:32:50 +08:00   1
@plko345 bcc-ebpf
qwerthhusn
    66
qwerthhusn  
   2020-06-15 22:12:16 +08:00
太长了,没看。但是俺不是 API 调用侠,我是数据库读写侠
cnrting
    67
cnrting  
   2020-06-15 22:39:55 +08:00 via iPhone
太长了,翻到生成器那里就知道这八成是个软文,于是我滑倒末尾~~~
felixlong
    68
felixlong  
   2020-06-15 22:47:18 +08:00
@fengjianxinghun 想请教一个问题,你运营这种公众号教人学编程真的赚的到钱吗?
heart4lor
    69
heart4lor  
   2020-06-15 23:05:46 +08:00
上 v2 以来第一次关注软文微信……这篇文章真的很不错!
seakingii
    70
seakingii  
   2020-06-15 23:10:01 +08:00
文章不错。
20150517
    71
20150517  
   2020-06-15 23:14:21 +08:00
正常老板只会说 6 个字:明天不用来了
puilu
    72
puilu  
   2020-06-15 23:18:28 +08:00
果然没让我失望,看了开头就猜到是广告,拉到底果然是
levelworm
    73
levelworm  
   2020-06-15 23:31:33 +08:00
@20150517 不会吧,都招进来了肯定要先培养一下的。实在扶不起来再砍人。
Cy86
    74
Cy86  
   2020-06-15 23:38:54 +08:00
从头看到尾, 受益了, 难得有文章给带学习思路走的
emric
    75
emric  
   2020-06-15 23:45:06 +08:00
虽然是广告,但是文章质量不错。
xxxy
    76
xxxy  
   2020-06-16 07:48:00 +08:00
文章质量不错
fasionchan
    77
fasionchan  
OP
   2020-06-16 09:53:12 +08:00
@fengjianxinghun 大佬说的这个是个好东西,有空我也研究下
zclHIT
    78
zclHIT  
   2020-06-16 14:19:24 +08:00
看了一眼标题,立刻翻到最后,果然 :)
willww64
    79
willww64  
   2020-06-16 15:38:59 +08:00
直接用 ss 命令效率如何呢?
fasionchan
    80
fasionchan  
OP
   2020-06-16 15:44:37 +08:00
@willww64 直接用 ss 性能其实还行,略低一点点而已,只是当时有不少服务器没有装这个工具
willww64
    81
willww64  
   2020-06-16 16:36:43 +08:00
@fasionchan 感谢回复!非常抱歉,文章前面是昨天看的,今天看了剩下的部分,忘了文章前面提到了很多服务器系统是最小化安装,没有装 ss 之类的工具。。。
文章里使用的技术和表达的意思我理解并受用了。不过我想偏个题请教一下,服务器上不安装 ss 这类工具的具体考虑是什么呢?安全吗?开发替代脚本甚至内核模块,虽然最终优化了 CPU 和内存使用,但中间还是引起了一些问题,因此引入的成本个人觉得还是大了点。
fasionchan
    82
fasionchan  
OP
   2020-06-16 16:53:02 +08:00
@willww64 早年间的最小化安装主要是节省磁盘空间,那时很多发行版也没包含 ss,现在应该大部分服务器都会有 ss 命令了。

依赖外部命令其实也有一些问题,因为不确定一台服务器是否安装了这个命令,也不知道这个命令的版本是什么,有些命令不同版本输出格式还不一样,这在服务器数量大(当时大概 3 万台),版本杂的场景是一个噩梦。因此,我们更喜欢直接调 API 或系统调用,虽然也可能有版本差异,胜在拿到的数据是格式化的,规避了解析数据导致的一堆问题。

如果一开始可以预知后面的事情,我们多半就不会按着文中这个路线走,甚至是评估是否有替代监控手段,完全绕开。一开始其实思路很直接,psutil 已经做了大量的基础工作,拿来用就是了。只是开源解决方案有不少带着玩具意味,一遇到规模较大的场景就撑不住。没有办法,选择用它,就只能去解决它带来的问题。
hxysnail
    83
hxysnail  
   2020-06-17 09:10:06 +08:00
受教了,文章写得真不错! ps: 向小菜和老板献上我的膝盖
JavaIO
    84
JavaIO  
   2020-06-17 09:24:16 +08:00
写的不错
willww64
    85
willww64  
   2020-06-18 16:37:01 +08:00
@fasionchan 嗯嗯,都是根据各自情况采用不同方法。我个人比较偏好用已有命令解决问题,所以我面临这样的问题可能会先尝试推动系统统一化、标准化。当然如果老大不听我的,推不动的情况下,那也只能采取跟你们一样的办法了。
关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1162 人在线   最高记录 6679       Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 35ms UTC 23:28 PVG 07:28 LAX 16:28 JFK 19:28
Do have faith in what you're doing.
ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86