cgroups 介绍与使用

cgroup 比较有趣的地方是它没有提供任何的系统调用接口,所以你不能用 API Call 的方式使用 cgroup,实际上 cgroup 实现了 linux 虚拟文件系统 vfs,所以类似我们熟悉的 btfrs, ext4, 因此可以用类似文件系统的方式进行操作。 比如用 mount 命令看一下 linux 上挂载了哪些设备: # mount -t cgroup /dev/sda2 on / type ext4 (rw,relatime) cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd) cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio) cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma) cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot) 可以看到, 第一行是磁盘 sda2 挂载在根目录 /, 它的类型是 ext4 后面几行是 cgroup 挂载在了目录 /sys/fs/cgroup/,类型是 cgroup 如果你的内核比较新的话,将看不到上面那些 cgroup 的行,而是只能看到最后这一行 cgroup2,这是因为新版本的内核使用了 cgroup v2 。 另外类似于 “net_cls”, “rdma” 这些都是 cgroup 子系统的名字,详见本文结尾的附录。 知道了上面这些,那么我们就能用操作文件系统的方式使用 cgroup 了,正好我有两台 linux VM, 一个 Ubuntu Server 22.04,内核 5.15, 默认使用了 cgroup v2 另一个是 Ubuntu Server 20.04, 内核 5.4,使用 cgroup v1 cgroup 和 cgroup2 有很多不一样的地方,具体见参考资料 1。本文所有例子都基于 cgroup v2。 ...

2023-03-22 · Me

Latency numbers every programmer should know

这期水一篇文章。 网上有很多人流传 Jeff Dean 的这个 Latency numbers,我看过很多遍但总是记不住,干脆把它抄下来算了。 L1 cache reference ......................... 0.5 ns Branch mispredict ............................ 5 ns L2 cache reference ........................... 7 ns Mutex lock/unlock ........................... 25 ns Main memory reference ...................... 100 ns Compress 1K bytes with Zippy ............. 3,000 ns = 3 µs Send 2K bytes over 1 Gbps network ....... 20,000 ns = 20 µs SSD random read ........................ 150,000 ns = 150 µs Read 1 MB sequentially from memory ..... 250,000 ns = 250 µs Round trip within same datacenter ...... 500,000 ns = 0.5 ms Read 1 MB sequentially from SSD* ..... 1,000,000 ns = 1 ms Disk seek ........................... 10,000,000 ns = 10 ms Read 1 MB sequentially from disk .... 20,000,000 ns = 20 ms Send packet CA->Netherlands->CA .... 150,000,000 ns = 150 ms 如果对上面的纳秒、毫秒还没有很直观的概念的话,有人给出了下面这样一个有意思的对比: ...

2022-10-02 · Me

使用 Kubernetes 遇到的一些问题和解决思路

update on 2022-05-21 今天在 homelab 的 k8s 集群上发生了同样的情况,我想删除一个 namespace,再确认已经把 namespace 里面所有其他资源都删除的情况下,namespace 始终是 Terminating, 找了很多资料,方法也众说纷纭 。 最后通过看 api-server log 发现原来又是 Unable to authenticate the request due to an error: x509: certificate has expired or is not yet valid root cause 还是我更新 cert 的时候又漏了某些步骤。 事情的起因是 k8s 的 cert 过期了,在目录 /etc/kubernetes/pki/ 下面的这些 cert 都与 k8s 的核心服务息息相关,因此 cert 过期了,整个 k8s 集群就停止服务了。 这个集群是 kubernetes 1.14, 因此需要运行几个命令完成更新,而 1.15 版本以上这个过程简化了不少。 由于之前已经 renew cert 两次了,因此正常按部就班几个操作就完事了,但是这个因为一点小疏忽,加上系统死机重启了一次,花了很多时间去恢复各种服务。 本文记录 debug 的过程中遇到的一些症状,以及后来发现的解决方法,为以后遇到类似问题提供思路。 Node 重启后 kubelet 没运行 前面提到,可能是因为 cert 过期后触发某些 bug 导致 Master Node 不能 ssh(之前 renew cert 没有类似问题),所以只能去机房按电源开关重启了。 ...

2022-04-27 · Me

Makefile 的几个语法坑

Makefile 和 Bash script 在使用的过程中有很多奇奇怪怪的坑,本文做一下纪录。 首先,有两个文件,一个叫 envs,里面定义了一个环境变量,比如 $ cat envs export GOPROXY="test.local" 第二个文件就是 Makefile ,假如我这样写 test: source ./envs echo ${GOPROXY} 所以,总的目标是,我希望在 Makefile 中导入另一个文件中事先定义好的环境变量。 然而这样的写法有很多问题。 source 命令找不到 加入直接运行 make, 很有可能你会看到这样的错误 $ make source ./envs make: source: Command not found 可是在 terminal 里面明明可以用 source 命令啊? 于是,第一个坑出现: source is a (non-POSIX) shell builtin, not an executable program on any conventional UNIX-like system. If source is changed to ., make changes its strategy; instead of trying to find and execute a program, it just passes the rule body to the system shell. ...

2021-09-26 · Me

bbolt 的设计与实现

关于 bbolt 的分析,网上已经有很多资料,本文只是对资料和源码的整理,主要是自己的学习笔记,文章最后的参考资料中有更多链接。 bbolt DB 整体组织 首先,bbolt 的一个文件是一个 DB,DB 中可以有多个 table, 每一个 table 是一个 B+ 树。而这个 table 在源码中就是 bucket, 整个 DB 就是一个大 bucket,它的子节点有多个 bucket。整体结构如图所示: 顶层 B+ 树,比较特殊,称为 root bucket,其所有叶子节点保存的都是子 bucket B+ 树根的 page id 其他 B+ 树,不妨称之为 data bucket,其叶子节点可能是正常用户数据,也可能是子 bucket B+ 树根的 page id。 这样,就清楚的知道了 bbolt 中 DB,table,和 data 是如何组织的了。 bbolt 的源码很简洁,主要功能分布在以下几个文件: bucket.go:对 bucket 操作的高层封装。包括 kv 的增删改查、子 bucket 的增删改查以及 B+ 树拆分和合并。 node.go:对 node 所存元素和 node 间关系的相关操作。节点内所存元素的增删、加载和落盘,访问孩子兄弟元素、拆分与合并的详细逻辑。 cursor.go:实现了类似迭代器的功能,可以在 B+ 树上的叶子节点上进行随意游走。 page.go: page 是磁盘上一个 4kb 页的表示,注意,相比 page,第二行提到的 node 表示的是内存里的结构。 db.go : bbolt 的主要源码 tx.go : bbolt 实现 MMVC 的主要代码。 如何加载文件到内存,成为一个DB? 假设有一个数据库文件 data,那么bbolt 的源码是如何读取这个文件,并且在在内存建立DB的呢? ...

2021-07-07 · Me

Bigtable 论文阅读笔记

最近因为工作需要用到 Bigtable,而设计一个好的数据库 Schema 对于性能至关重要,因此想找一些资料看看别人是如何根据自身业务特点设计 schema 的。 在网上找到了一篇 GCP 自己的官方文档 , 里面提到了一些 best practice,也提到了哪些坑需要避免,然而还是看的云里雾里。 比如, Row keys to avoid Row keys that start with a timestamp. This will cause sequential writes to be pushed onto a single node, creating a hotspot. If you put a timestamp in a row key, you need to precede it with a high-cardinality value like a user ID to avoid hotspotting. Row keys that cause related data to not be grouped together. Avoid row keys that cause related data to be stored in non-contiguous row ranges, which are inefficient to read together. ...

2021-06-20 · Me

Unicode 字符编码

ASCII 我们熟悉的 ASCII 码可以说是字符编码的始祖了。它规定了常用的数字、符号、英文字母与二进制之间的对应关系。 ASCII 的缺点是字符集太少了,只能表示英文和数字,无法表示像中文,日文这样的符号。因此人们就设计出了 Unicode 字符集,囊括了几乎所有人类语言文字的符号。 Unicode Unicode 是一个字符集,而不是一种编码方式。 Unicode 相当于是给人类所有的符号一个独一无二的 ID,只要大家都是用这个 ID 表示字符,就不会出现乱码的问题。 因为 Unicode 是一个字符集,因此它不存在所谓的 “用几个字节表示 unicode” 这样的问题,这是具体的编码方式需要处理的事。 Unicode 把 ID 划分成了 17 组 (Plane),每组有 65536 个字符,编号可以用 U+[XX]YYYY 这样的形式表示,每一位是一个十六进制数字,其中 XX 代表组编号,从 0 到 0x10,一共17个,YYYY 代表这一组中的字符编号,一共 65536 个。 其中第 0 组叫 Basic Multilingual Plane,简称 BMP,它是 Unicode 中最基础和最常用的一部分,码点范围是U+0000 ~ U+FFFF,包含了我们常用的英文和汉字。 UFT-8 UTF-8 是 Unicode 具体的编码方式,除此之外还要 UTF-16, UTF-32 等等。 为什么需要编码方式呢? 直接用 Unicode 的 ID 不就行了吗? 因为我们需要节省存储空间。 UTF-8 是一种变长的编码方式,它可以使用 1-4 个字节表示一个符号,编码规则如下 ...

2020-08-16 · Me

用 Grafana 展示监控状态

运维或者 SRE 部门经常会弄一个大屏幕展示各种系统状态,看上去很好玩,于是我也用类似的开源软件监控一下家里的主机。 整个过程非常简单,主要是安装三个软件 Node exporter,Prometheus,Grafana。 Node exporter 既然要展示系统状态,那么第一步就是要获得系统的状态数据,比如 CPU 使用率,内存使用率,网络流量等。 Prometheus 官方提供了一个使用 go 语言编写的程序 node_exporter,直接下载项目主页上 release 里的二进制即可。node_exporter 最好直接安装在物理主机上,因为这样才能采集到最准确的数据。 运行 node_exporter 以后,会自动启动一个 http server 并且监听 9100 端口,如果有 client 过来访问, server 返回主机的监控信息。比如: $ curl http://localhost:9100/metrics node_network_transmit_packets_total{device="veth126cb08"} 28859 node_network_transmit_packets_total{device="veth1276a16"} 1383 node_network_transmit_packets_total{device="veth749c501"} 1.108492e+06 返回信息的格式是符合 Prometheus 定义的标准的,因此 Prometheus 能够处理并以简单的图标的形式展现这些数据。 看到这里大家应该不难想到,如果我自己写一个程序 HelloWorld,并且把程序的状态按照一定的格式导出,那么同样可以通过 Prometheus + Grafana 展现。 Prometheus Prometheus 是一个功能齐全的数据库,还提供了 PromSQL 语言方便用户查询,以及一个简单的网页前端。 最简单快捷的方式当然是启动一个容器,唯一需要注意的是把配置文件 prometheus.yml 挂载到容器的 /etc/prometheus/ 目录下。 $ docker run -d -p 9090:9090 \ -v /home/prometheus/:/etc/prometheus/ prom/prometheus 配置文件中需要在 scrape_configs 部分添加 noder exporter 的 IP 地址和端口。 ...

2020-02-02 · Me

CPU affinity

CPU affinity – CPU 亲和性,指进程更希望运行在哪个 CPU core 上。 指定 core 有什么好处呢? 比如,可以自己决定哪些程序可以独占 CPU 资源,保证这个程序性能的最大化; 指定 CPU 以后可以提高 Cache 的命中率,常用于一些对性能非常高要求的程序,例如 nginx。 命令行指令 taskset 在 Linux 系统中,我们可以用 taskset 命令指定一个进程运行在哪个核心上。 比如我们写一个程序用 while(1) 制造死循环,那么运行这个程序的时候 CPU 会飙到 100% 用以下这条命令运行这个程序 taskset -c 3 ./a.out 意思是把 a.out 运行在从 0 开始数起的第 3 个核心上。 于是,用 htop 命令查看,会看到第 4 个核 CPU 使用率是 100%。 编程的 API 那么在程序的代码里怎么用呢? 先来看看 glibc 提供的系统 API #include <sched.h> int sched_setaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask); int sched_getaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask); void CPU_CLR(int cpu, cpu_set_t *set); int CPU_ISSET(int cpu, cpu_set_t *set); void CPU_SET(int cpu, cpu_set_t *set); void CPU_ZERO(cpu_set_t *set); nginx 的 config 文件中,可以为每个工作进程绑定CPU ...

2019-09-15 · Me

How TCP backlog works in Linux

原文: http://veithen.io/2014/01/01/how-tcp-backlog-works-in-linux.html 当一个应用程序使用 listen() 系统调用把一个 socket fd 设置成 LISTEN 状态时,也需要指定一个 backlog 值。通常我们可以认为这个 backlog 代表这个 socket fd 可以接受最大的连接请求数。 #include <sys/types.h> #include <sys/socket.h> int listen(int sockfd, int backlog); 因为 TCP 的三次握手,在 server 端 accept() 系统调用返回,并且tcp 状态在变成 ESTABLISHED 之前,会有一个短暂的 SYN RECEIVED 状态。那么这个状态的 tcp 链接应该放在哪个 queue 里面呢? 单个 queue,其大小就是 listen() 参数 backlog。当一个 SYN 包到达时,server 返回一个 SYN/ACK 给 client,并且把这个链接放入 queue。当 client 的 ACK 到达时,TCP 的状态变成 ESTABLISHED。这就意味着这一个 queue 有两种不同的状态:SYN RECEIVED 和 ESTABLISHED。只有在 ESTABLISHED 状态的链接才能被 accept() 返回给用户程序。 两个 queue,未完成的链接在 SYN queue,建立好的链接在 accept queue。也就是说 SYN REVEIVED 状态的链接会先被放到 SYN queue 里面,当它变成 ESTABLISHED 状态时再被移动到另一个 queue。所以,accept() 系统调用实现起来就简单了,它只从 accept queue 中获取链接返回给应用程序。这时,listen() 系统调用的 backlog 参数决定了 accept queue 的大小。 ...

2019-08-15 · Me