V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fasionchan
V2EX  ›  程序员

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

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

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

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

背景

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

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

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

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

$ 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 命令。

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

最后老板给小菜指了条明路,让他回去再看看 psutil 。 psutil 是一个 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 连接,那么列表里将有十万个连接对象。难怪采集脚本吃了那么多内存!

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

小菜从 pstuil  fork 了一个分支,并将 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 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: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 采用网络通讯的方式,内核作为服务端接收客户端进程查询请求,并以二进制数据包响应查询结果,效率更高。

这就是 ss 比 netstat 更快的原因, 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 single_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
感觉文章不错,越写越深。看来基础知识的确重要。不知道运维这块有没有比较系统的知识体系?
nieqibest
    30
nieqibest  
   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 搞到了内核,这真的是菜吗?
liprais
    38
liprais  
   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
不错,有帮助
bojackhorseman
    46
bojackhorseman  
   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   ·   我们的愿景   ·   实用小工具   ·   1392 人在线   最高记录 6543   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 31ms · UTC 23:44 · PVG 07:44 · LAX 16:44 · JFK 19:44
Developed with CodeLauncher
♥ Do have faith in what you're doing.