V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
fasionchan
V2EX  ›  程序员

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

  fasionchan · Jun 15, 2020 · 12143 views
This topic created in 2141 days ago, the information mentioned may be changed or developed.

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

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

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

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

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

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

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

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

如果一开始可以预知后面的事情,我们多半就不会按着文中这个路线走,甚至是评估是否有替代监控手段,完全绕开。一开始其实思路很直接,psutil 已经做了大量的基础工作,拿来用就是了。只是开源解决方案有不少带着玩具意味,一遇到规模较大的场景就撑不住。没有办法,选择用它,就只能去解决它带来的问题。
hxysnail
    83
hxysnail  
   Jun 17, 2020
受教了,文章写得真不错! ps: 向小菜和老板献上我的膝盖
JavaIO
    84
JavaIO  
   Jun 17, 2020
写的不错
willww64
    85
willww64  
   Jun 18, 2020
@fasionchan 嗯嗯,都是根据各自情况采用不同方法。我个人比较偏好用已有命令解决问题,所以我面临这样的问题可能会先尝试推动系统统一化、标准化。当然如果老大不听我的,推不动的情况下,那也只能采取跟你们一样的办法了。
About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   846 Online   Highest 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 206ms · UTC 21:21 · PVG 05:21 · LAX 14:21 · JFK 17:21
♥ Do have faith in what you're doing.