Becomin' Charles

算法 | LNMP | Flutter | Mac

Becomin' Charles

在运维和管理 Linux 服务器的时候,我们最常用的一个命令就是 netstat,我常用这个命令来查看当前服务器上有哪些进程正在侦听端口,主要用来诊断网络服务的工作状态。

不过,最近有一次安装好一个 Ubuntu 发型版,发现默认没有安装 netstat,觉得非常奇怪,自己手动安装后,发现 man pages 提示,netstat 命令已经是 deprecated 了,建议使用 ss 命令代替。

This program is mostly obsolete. Replacement for netstat is ss. Replacement for netstat -r is ip route. Replacement for netstat -i is ip -s link. Replacement for netstat -g is ip maddr.

netstat man pages

netstat 的用法

netstat 有许多许多参数,我一般就用一种组合,以至于后来已经想不起来为什么是这几个参数了:

1
netstat -npl

得到的结果是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN 655/systemd-resolve
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 890/sshd
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN 30790/cupsd
tcp 0 0 0.0.0.0:18025 0.0.0.0:* LISTEN 890/sshd
tcp6 0 0 :::22 :::* LISTEN 890/sshd
tcp6 0 0 ::1:631 :::* LISTEN 30790/cupsd
tcp6 0 0 :::9090 :::* LISTEN 15415/./prometheus
tcp6 0 0 :::18025 :::* LISTEN 890/sshd
udp 0 0 127.0.0.53:53 0.0.0.0:* 655/systemd-resolve
udp 0 0 0.0.0.0:631 0.0.0.0:* 30792/cups-browsed
udp 0 0 0.0.0.0:5353 0.0.0.0:* 757/avahi-daemon: r
udp 0 0 0.0.0.0:42360 0.0.0.0:* 757/avahi-daemon: r
udp6 0 0 :::58232 :::* 757/avahi-daemon: r
udp6 0 0 :::5353 :::* 757/avahi-daemon: r
Active UNIX domain sockets (only servers)
Proto RefCnt Flags Type State I-Node PID/Program name Path
unix 2 [ ACC ] STREAM LISTENING 35116 1304/gnome-session- @/tmp/.ICE-unix/1304
unix 2 [ ACC ] SEQPACKET LISTENING 1448 1/init /run/udev/control
unix 2 [ ACC ] STREAM LISTENING 34277 1270/systemd /run/user/1000/systemd/private
unix 2 [ ACC ] STREAM LISTENING 34282 1270/systemd /run/user/1000/gnupg/S.gpg-agent.ssh
unix 2 [ ACC ] STREAM LISTENING 33510 1270/systemd /run/user/1000/gnupg/S.gpg-agent
unix 2 [ ACC ] STREAM LISTENING 33511 1270/systemd /run/user/1000/pulse/native
unix 2 [ ACC ] STREAM LISTENING 33512 1270/systemd /run/user/1000/gnupg/S.gpg-agent.extra

最常用的就是这个命令组合,展示的结果有两个段落,第一个段落展示的是 TCP/UDP 协议的侦听情况,第二个段落展示的是 socks 文件的侦听情况。参数 n 的意思是展示数字格式的 IP 地址,不然会展示主机名称或者是域名,参数 p 的意思显示进程的名字(有时候显示不出来),l 的意思,是关注处于 LISTENING 状态的 socket。

通过如上命令,我们看到了系统所有打开的 socket,如果你启动一种网络服务也好,自己开发一个网络服务打开端口也好,通过这个命令都应该能看到自己打开的端口,如果看不到,应该就是没有能够正确打开端口,要好好查询是什么原因。所以这是一个很好用的调试命令。

ss 的用法

上面介绍了 netstat 的最最基本的一种用法,其他用法当然还有很多,但是先略过不表,如果想使用 ss 命令来代替 netstat 的话,我们怎样达到类似的效果呢?

1
ss -atlp

这是我自己摸索的一个参数组合,目前我背诵得还不是很流利,每次还需要看一下文档:

1
2
3
4
5
6
7
8
9
State          Recv-Q           Send-Q                      Local Address:Port                       Peer Address:Port
LISTEN 0 128 127.0.0.53%lo:domain 0.0.0.0:* users:(("systemd-resolve",pid=655,fd=13))
LISTEN 0 128 0.0.0.0:ssh 0.0.0.0:* users:(("sshd",pid=890,fd=5))
LISTEN 0 5 127.0.0.1:ipp 0.0.0.0:* users:(("cupsd",pid=30790,fd=7))
LISTEN 0 128 0.0.0.0:18025 0.0.0.0:* users:(("sshd",pid=890,fd=3))
LISTEN 0 128 [::]:ssh [::]:* users:(("sshd",pid=890,fd=6))
LISTEN 0 5 [::1]:ipp [::]:* users:(("cupsd",pid=30790,fd=6))
LISTEN 0 128 *:9090 *:* users:(("prometheus",pid=15415,fd=3))
LISTEN 0 128 [::]:18025 [::]:* users:(("sshd",pid=890,fd=4))

这是 ss 命令呈现出来的结果,可以看到,格式和 netstat 很不一样,不像 netstat 命令那么紧凑和直观。这是很多人诟病这个命令的原因之一。当然,批判这种批判的声音认为,人们只是死守了一种习惯,不愿前行。当然了,这么说也未尝不对,就拿 Charles 个人来说,就算我 2010 年参加工作,才学会 netstat 命令,那我到现在也使用了将近十年,从来没有变过,当然看得无比顺眼啦。

当然,也有一种理由是老外提出来的,说 ss 这个命令的名字不好,其实 ss 可能是 socket statistics 的意思,缩写以后,竟然只有两个字母,不太好联想,不像 netstat 那么直观。当然这是我的解释,不是老外抱怨的理由,他们抱怨的是,每每提及 ss,他们会联想起希特勒!是不是匪夷所思,我是 80 后,我这个年代的人,对这个都没有什么印象,关键我们用中文为主,估计大家看到 ss 最多联想到梯子,怎么都不会想到希特勒。这个大纳粹有一个武装部队,以前叫党卫队特别机动部队,后来改名叫武装党卫队。它的德语简称正是SS。

不说闲话了,说说几个参数,a 参数是显示所有的意思,t 参数意思是显示 TCP 协议的,l 代表正在 LISTENING 状态的,p 代表进程信息。从上面的表里,我们看到 p 参数打印的信息,组织得不如 netstat 精炼。但是更为完善一点,显示了进程名字和 PID 以及 FD。但是因为用了两重小括号,key/value 的格式,再加引号,看起来脏乱差。当然,我们可以用一些命令去格式化它,不过还是太麻烦了。

更换的原因是什么?

这可能是我最为好奇的事情。不过网上我搜索了不少的资料,基本都语焉不详。这也有点让我有点无奈。

大体上,我们能看出来,主要是 net-tools 这个包,将要被 iproute 这个包给替换。理由大概是,1,这个包太老了,2,这个包不支持很多内核新的特性(但是没有说是哪些特性),界面不够优化使用困难(对命令行不友好),3,net-tools 里面的 ifconfig 确实缺点多多,4,未来不再想维护 net-tools 了。

Luk Claes and me, as the current maintainers of net-tools, we’ve been thinking about it’s future. Net-tools has been a core part of Debian and any other linux based distro for many years, but it’s showing its age.
It doesnt support many of the modern features of the linux kernel, the interface is far from optimal and difficult to use in automatisation, and also, it hasn’t got much love in the last years.
On the other side, the iproute suite, introduced around the 2.2 kernel line, has both a much better and consistent interface, is more powerful, and is almost ten years old, so nobody would say it’s untested.
Hence, our plans are to replace net-tools completely with iproute, maybe leading the route for other distributions to follow. Of course, most people and tools use and remember the venerable old interface, so the first step would be to write wrappers, trying to be compatible with net-tools.
At the same time, we believe that most packages using net-tools should be patched to use iproute instead, while others can continue using the wrappers for some time. The ifupdown package is obviously the first candidate, but it seems that a version using iproute has been available in experimental since 2007.

https://serverfault.com/questions/633087/where-is-the-statement-of-deprecation-of-ifconfig-on-linux

也有从原理层面分析的:现在的 netstat 和 ifconfig 命令,都是通过读写 /proc 目录下的虚拟文件来完成任务的,这个东西在小型业务系统上,是没问题的,但是在大规模系统里,可能会伤害系统的性能之类的。相比之下,ss 和 ip 两个命令,使用的是 Linux 内核的 netlink sockets 特性。有着根本上的不同。虽然,老命令也可以用新原理重写,但是其实并没有人那么做,主要因为不同程序员团体的一些 political issues ,大家意见不合……

当然,深层次的还有,我们使用这样的调试命令,本质上还是希望获知内核的状态的,其实,内核已经改变了 networking 模块的整个原理,另一方面我还要求命令像从前那样去展示信息,展示层面的格式和真实原理已经背离,所以,从长远看,替代这两个命令才是必然。

结论

咱们这些做技术的,也还是要与时俱进比较好,虽然,以前的那些命令熟悉,好用,手到擒来,甚至无法忘记,但是新的还是要保持学习。很多发型版已经默认不带有 net-tools 包了,虽然仍然可以手动安装回来,但是,这背后的态度已经很明确了。另一方面,我们做技术,也要谨防自己的大脑僵化,还是要保持对新事物的好奇心和热情。

Algorithm

这次跟大家分享的题目是我在练习使用回溯法时候的一道练习题:

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:

1
2
3
4
5
6
7
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]

以下是我的解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func generateParenthesis(num int) []string {

result := []string{}

//约束条件:左括号的数量,消耗快于右括号的消耗数量
//结束条件:右括号数量消耗完毕
//单步前进:消耗一个左括号或者右括号
var put func(left int, right int, str string)

put = func(left int, right int, str string) {
if right == 0 {
result = append(result, str)
} else {
if left > 0 {
put(left - 1, right, str + "(")
}
if right > left {
put(left, right - 1, str + ")")
}
}
}

put(num, num, "")
return result
}

因为是回溯法的练习题,所以,一开始我就知道了应该用回溯法去解这道题目。回溯法是一种很基础的算法,经过一周的练习,我感觉,回溯法本质上就是一种穷举法的应用。一般,我们可能想到的穷举法,都是循环,两重循环,有两个下标,可以按部就班,没有重复和遗漏地从头穷举到尾。这种类型的穷举法,我们光凭直觉就可以写出来,并且写正确,能不能跑出来,就不一定了,因为效率问题嘛~

回溯法的穷举是另一种类型,就是我们可能很直观想到怎么去穷举这种问题,但是很难用代码去表达出来。需要一个关键的数据结构的帮助,我们才能有效编写算法。最经典的例题,其实就是“八皇后问题”。在一个国际象棋棋盘上,放上八个皇后,不能互相攻击。给出一种解法,或者要求给出所有解法。

直觉就是,现在第一行第一格放一个皇后,然后在第二行找个位子放第二个,以此类推,直到所有八个皇后都放上去了,如果放到一半发现所有的格子都不能放了,那么上一个可能放错了,把上一个皇后换到下一个可以用的格子,继续按照原有策略试,以此类推,不断修改上一个皇后的位置,上一个的上一个的位置,一直到找到一个正确的摆法,也可以一直到找到所有的摆法。

这个思路,描述起来很简单,但是如果用代码去写,我相信对于很多同学来说,还是非常困难的。以前,我对于自己写不出八皇后这个问题的代码这件事情,一直很自责,对自己的智商感到抱歉。不过,现在,经过学习我发现,其实只是因为我没有认真去学习回溯法,以及没有认真训练,所以,我不能掌握这种方法。我不应该为自己不是最顶尖聪明的人类而感到抱歉,我只应该为自己没有早一点开始训练,感到愧疚。

这种类型的题目,就是我说的,不是那么显然地能找到按部就班没有重复和遗漏的迭代变量的题目。主要的一个原因就是,很多时候,循环的层数可能是不确定的,给代码编写带来了很大的困难。比如,“八皇后问题”,经常被写成“N皇后问题”,八皇后你可以不厌其烦写八重循环,那么N皇后怎么个写法?

前面提到的很关键的那个数据结构,就是栈。先进后出的线性数据结构。这种数据结构的长度其实是可变的。我们只要每穷举一个步骤,就把此步骤的相关数据都保存在栈里,然后下一个,出了问题,就弹出上一个,这个过程是可以重复的,所以我们就有了能力穷举尝试完所有的步骤。与上面直观感觉描述的解法相吻合了。

说起来,回溯法的核心关键,正是去使用栈。每探索一个步骤,就用栈记住一切,然后,继续下一个步骤。栈这个东西,跟另一个程序的核心特性结合很紧密,于是往往可以让我们把代码表达得很简洁。那个特性就是让人又爱又恨的“递归”。

我想,每个程序员,在学习递归的时候,都掌握过这个知识点,就是函数可以自己调用自己,形成递归,那么上一重没有完成的函数,怎么办呢,它的所有信息保存在栈上,直到下一重返回。结合回溯法,如果利用递归来使用栈,就可以把代码表达得很简洁和优美。因为递归方法使用栈,都是隐性的,可以节省大量的代码。让我们专注在算法逻辑地撰写上面,而不是去处理压栈弹出等数据结构操作。

如果大家了解深度优先搜索,就会发现,其实这个压栈的处理,也很像深度优先搜索,所以,有些人写代码,经常把回溯法的主方法取名为 dfs,就是这个原因。我们都知道,深度优先搜索,是用在图上面的搜索算法,回溯法用到的题目,其实也是一种图,只不过是一种隐式的图,图的节点和边的定义没有明确给出,扩展的规则隐含在题目里的一种图。所以,回溯法有时候等同于深度优先搜索,也不奇怪了。

上面我写的括号生成的解法,是用 Go 语言实现的,也是偶然得到的一种灵感,写得非常简洁,思路也很清晰,所以特此分享给大家。以后我也想写一个文章,专门介绍,怎么理解回溯法,以及怎么在不同的题目里面去运用,怎么在最差情况下发挥得还不错。:)

Review

本期想给大家分享的文章是:《An introduction to RabbitMQ, a broker that deals in messages

broker 字典里是经纪人的意思,其实也可以叫中介,或者中间商,消息队列本质上就是处理消息的一种中间商,这是套用了基本经济学里面的概念。

消息队列中间件,就像是一种专门处理系统间消息的中间商或者说中介。RabbitMQ 就是这样一种中间件。它实现了很多消息队列的协议,最重要的一种就是 AMQP,也就是高级消息队列协议。这种协议的概念模型关注三种实体,队列,绑定,和交换。

AMQP Models

接着文章逐一介绍了中间件系统的一些重要实体,比如发布者,消费者,绑定和交换的一些形式,比较有趣的就是 Topic 话题模式。

如果不大了解复杂的消息中间件系统,这是一篇很好的入门文章。

Tip

本周要分享的一个 Tip 是打造好自己的工作环境,看到了一篇文章,介绍了如何自定义自己的 Shell 环境,觉得非常有趣:

如何把你的终端主题改成任何你想要的样式

这篇文章介绍了如何在 Mac 上安装一个叫 Powerlevel9K 的软件,来把 zsh 和 iTerm 打造成非常赏心悦目的样子。如果还不了解什么是 zsh 和 iTerm 的话,那么我也极力推荐大家试一试。

Share

这次分享给大家的是一篇短小的知识:

Linux 系统资源管理:什么是 cgroups?

综述

CGroups 是 Control Groups 的缩写,是 Linux 内核的一种机制,可以分配限制监控一组任务(进程)使用的物理资源(CPU、内存、磁盘I/O、带宽或这几种资源的组合)。

这里有几个基本概念需要知道:

  • 任务(Task)—— 一个系统进程在这个机制的语境下,称为一个任务。
  • 控制组(Control Group)—— 控制组就是一组按照某种规则分组的进程,并且关联了一组参数或者限制。控制组是层次结构化的,一个控制组可以从自己的父节点继承属性。
  • 子系统(Subsystem)—— 也叫做资源控制器,或者控制器。是一种单一资源的抽象,比如 CPU 时间,或者内存等。
  • 层次结构(Hirerarchy)—— 一种层次化的结构,可以用来附着(attach)一些子系统。因为控制组是层次结构化的,它们所形成的一棵树称作一个层次结构。

CGroups 提供了一个虚拟文件系统 /proc/cgroup,作为交互的接口,用于设置和管理各个子系统。本质上来说,CGroups 是内核附加在程序上的一系列钩子(Hooks),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。[7]

CGroups 发展的时间线

核心特性

  • 资源限制 —— 一组进程不可超过内存的使用限制,包括文件系统的 Cache
  • 优先级 —— 一组进程相对可以获得较大的 CPU 时间和 I/O 占用
  • 审计 —— 衡量一组任务的资源利用,比如,这种数据可以用来计费
  • 控制 —— 冻结或者恢复一组进程

几个概念间的关系

一个层次结构,可以附着一个或者多个子系统(来源 RedHat)

一个子系统不能附着第二个已经附着过子系统的层次结构

一个任务不能是同一个层次结构下的不同控制组的成员

fork 出来的进程严格继承父进程的控制组

CGroups 的基本使用

在实践中,系统管理员会用 CGroups 干类似这些事情[8]:

  • 隔离一个进程集合(比如:nginx的所有进程),并限制他们所消费的资源,比如绑定CPU的核。
  • 为这组进程 分配其足够使用的内存
  • 为这组进程分配相应的网络带宽和磁盘存储限制
  • 限制访问某些设备(通过设置设备的白名单)

因为是一个 /proc 目录下的伪文件系统,我们总是可以用 Shell 的命令来操作和管理 CGroups,但是更简单的办法是安装一个 libcgroup 的包[6],它提供了好几个命令行工具和相关的文档。这个包里面提供一个 cgconfig 的服务,以及 /etc/cgconfig.conf 配置文件的通过系统运维脚本 service 就可以有效实现层次结构的创建和子系统的附着,以及参数设置等工作,非常方便。具体可以参阅 RHEL 6 的指南。

CGroups 的实现方式

Linux 内核中关于 CGroups 的源码结构示意图[7]

上面是一幅 Linux 内核源码中,跟 CGroups 有关的数据结构的关系图。task_struct 结构体就是描述进程的数据结构,里面通过一个指针 cgoups 关联到一个辅助的数据结构叫 css_set,css_set 又通过一个辅助的数据结构叫 cg_cgroup_link 关联到 cgroup,至此完成了进程和 cgroup 的映射。因为一个进程可以属于多个 cgroup(必须分属不同的 Hirerarchy),一个 cgroup 也可以关联多个进程,所以,cg_cgroup_link 就是一个处理多对多关系的“表”。

cgroup 里面有 sibling,children,parent 指针,显示 cgroup 结构体,本质上是一个“树”的节点(node)数据结构。所以,cgroup 是树形的结构。有一个 root 指针,指向了树的根 cgroupfs_root。树根,其实就是我们说的“层次结构”(Hirerarchy)。

cgroup_subsys 就是我们说的“子系统”的数据结构。这是一个抽象数据结构,需要被各个子系统去分别实现,所以这里包含了很多函数指针(如 attach)。cgroup_subsys_state 存储了一些各个子系统共用的元数据。各个子系统各自的结构体,按照自己的特点再来定义各自的控制信息结构体。参考[7]

从逻辑层面看 CGroups 的内核数据结构[10]

上图引自美团技术博客的一篇文章,从逻辑结构层面,描述了进程、子系统、群组和层次结构的关系。见参考文献[10]。

结语

CGroups 是内核提供的操作系统层面的虚拟化关键技术。是 Docker 这类杀手级应用的理论基础。本文只是对作者学习此概念时候看到的一系列文章的内容进行了编纂和综述,尝试从一个门外汉的角度去对该项技术形成一定的理解。如需细致学习,还请以参考文献中的文章内容为标准。

通过研究和学习这些资料,总结出一个基本判断就是,Linux 内核提供了比较完备的虚拟化技术,即使没有 Docker 这样的系统化的应用技术,我们也可以实现对一些机器资源的灵活管理。内核本来就提供了这样的工具。通过学习完全是可以掌握的。但是,Docker 这类技术,则提供了更为友好,高效的操作接口,极大提升了工程效率,降低了学习难度,更值得在生产中推广。

参考文献:

  1. https://lwn.net/Articles/199643/ Rohit Setch 提交 patch
  2. https://lwn.net/Articles/236038/ Paul Menage 接手容器的开发
  3. https://en.wikipedia.org/wiki/Cgroups Wiki:cgroups
  4. https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
  5. https://www.kernel.org/doc/Documentation/cgroup-v2.txt
  6. RHEL 6 资源管理指南
  7. Docker背后的内核知识——CGroups资源限制
  8. Docker基础技术——Linux CGroups
  9. CGroup 的介绍、应用实例和原理描述
  10. Linux资源管理之cgroups简介

unpreview

左耳朵耗子,陈皓发起的 ARTS 打卡活动

Algorithm

给定一个排序数组,你需要在**原地**删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在**原地修改输入数组**并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定数组 nums = [1,1,2], 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, **2**。 你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, **4**。你不需要考虑数组中超出新长度后面的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func removeDuplicates(nums []int) int {

l := len(nums)

if l == 0 l == 1 {
return l
}

i := 0

for j := 1; j < l; j ++ {
if nums[j] == nums[i] {
continue
} else {
nums[i + 1] = nums[j]
i ++
}
}

return i + 1

}

这道题目是一道简单的题目,正在恢复对算法的训练,所以从简单的开始吧。以上答案是用 Go 语言完成的,我估计已经是我第四遍或者第五遍的代码了。我之前肯定刷过这道题目,但是今天仍然没有非常流畅自然地写对。还是写错了一次,然后我擦掉重写,发现这次写得比较简洁了。

思路很简单,第一个下标指向第一个位置,然后第二个下标从第二个位置开始起遍历,发现一样就继续往后跳,发现不一样,就把目标拷贝到第一个下标后面一个位置。直到第二个下标遍历完。

第一个下标 i 指向的元素,以及下标小于 i 的元素,是已经去重的。先看初始状态,i 指向 0,第一个元素,显然 i 以及 i 之前所有的元素都是去重的,因为一共只有 1 个元素,必然不会重复。

然后看一下循环,每一轮循环结束,如果 j 指向的元素,与 i 指向的相同,就把下标 j 往后 +1,如果不同,则把 j 指向的元素复制到 i + 1 指向的元素,而下标 i 也向后挪一个,如此循环结束后,仍然满足 i 以及 i 之前的元素都是不重复的。

循环结束后,i 正好指向着不重复的最后一个元素,其实每一轮结束后,i 都指向着不重复的最后一个元素。所以,最后不重复的元素的个数就是 i + 1 个。

以上使用循环不变式的方式分析了算法的正确性这个算法的时间复杂度是 O(n)。

Review

Avoiding Double Payments in a Distributed Payments System

本周 review 的文章是这一篇《在分布式系统中避免双重支付问题》,这是 Airbnb 技术博客本周的封面文章。介绍了 Airbnb 支付团队在整个系统向 SOA 架构迁移的过程中,如何构建分布式的支付系统。

他们构建了一个类库,名叫 Orpheus 这是古希腊的一个神“俄尔浦斯” 的名字。这个类库主要原理,是利用 Java 的 lamda 演算,封装了一个严格要求幂等性的类库,将一次分布式事务拆分成 Pre-RPC,RPC 和 Post-RPC 三个阶段,将分布式事务中,本地数据库事务和 RPC 分隔,并在强幂等性要求下工作,从而保障系统的最终一致性。

文章介绍了这么实现的原因和带有的问题,以及团队如此选择的 trade-off。是一篇高质量的设计思想文章,衷心向大家推荐。

Tip

想给大家分享的一个技巧点,就是在使用 Mac 的时候,经常需要使用 XCode 的命令行工具,比如 homebrew,比如我现在用 Visual Studio Code 做 Go 语言的 IDE,都需要用到这个命令行工具。怎么安装呢?

1
xcode-select --install

在 Mac 的 Shell 上执行上述命令,就可以激活 XCode 的 command line tools 的安装过程了。

Share

这个礼拜分享的文章,是眼前刚遇到的一个问题,虽然不是技术文章,但是也跟技术人息息相关。

《技术人走上管理岗位的困惑》

[ez-toc]

今天,老板跟我说,有一个年轻的同事要离职了,我听了很痛心,他是一个很不错的小伙子,老板对他寄予了很高的期望,他从我的团队调到了另一个研发团队,过去的目的是抓住那个团队的业务,把整个团队管理起来,但是实际上,他并没有达到预期,自己也待得很不爽,所以想要离开公司了。

我反思了一下,这本质上管理者的责任,没有能够有效地培养和引导,没有给人才建立有效的成长阶梯导致了人才的流失,非常遗憾。技术人的晋升和成长,或者说,不光是技术人,其他的岗位可能也是雷同的,应该说,专业型领导者的晋升和培养,是一个系统性的难题。这是需要管理者和候选人本人的双重努力才能完成的一个艰难蜕变。

管理意识问题

管理意识问题,可能是技术人晋升的最大障碍之一,我看到不少人因为这个问题,停留在原地无法寸进。

管理价值的迷思。很多技术人觉得,我就是一个专家,管理这种事情,充满了繁琐,不是我所喜欢的,甚至是我所厌恶的。所以,他就很少投入精力在这个领域,而是投入到自己喜爱的领域去。

其实,就我个人看来,技术人,特指程序员这个岗位,大体上有这么几个发展方向:技术专家,业务专家,综合管理。技术专家可能是大多数程序员所向往的一个发展方向,也是一条光荣的荆棘路,可以说,是非常困难的一条道路。不过呢,有问题的地方在于,虽然大多数人喜欢,但是,大多数人都不知道如何成为技术专家,以及想要成为技术专家的话,需要满足怎样的成长轨迹。成长轨迹,其实包含了方向和速度两个层面。方向层面,各有所好,容易忽略的是成长速度的问题。这个也是这条道路特别困难的原因,我见过不少工作了8年,10年,最后水平连高级程序员都算不上的人,非常可惜,只能是处在一个压力很大的境地了。

业务专家的话,是一条相对比较普遍的道路,如果技术人能够觉醒这个意识的话,是非常有利的。我们的世界错综复杂,其实需要海量的业务专家来推动整个社会的发展和进步,所以这种类型的技术人,永远都会有一碗饭吃,只要觉醒了这个意识,认真建立自己的领域知识体系,很容易建立竞争壁垒。遗憾的是,注意到这个问题的人比较少。

最后是综合管理,可能就是一般人嘴里说的“技术转管理”的那个“管理”。这样的人,技术有一定深度,业务比较精熟,最后,真正的特长就是掌握了管理能力。其实,这个领域是我认为的真正蓝海。因为明白这个道理的人实在太少了,又有很多同学很轻视这个方面,就更是留下了广阔的空间。未来这个地方的缺口一定是很大的。这个方向上,常见的问题,就是找不到管理的价值,无法正确理解管理的本质。

就我的理解来说,管理是一个很高级的技能。现在的世界越来越复杂,如果我们想要构建更大规模的系统,光靠一些散兵游勇是做不到的,一定要靠有能力的团队来完成。但是散兵游勇如何变成团队,就必须有高效的领导者。但是领导者是非常难以培养的,大家都是从管理去入手练习的。管理者这个角色,只是一个路径,领导者才是培养的目标。注意我特别区分了管理和领导的区别,也即 Manager 和 Leader 的区别。

成长路径的问题

这个就是管理者层面的问题了。就我们公司五年的发展轨迹看,这里一直是很严重的问题。早期业务冲刺,很难顾及到这个方面,现在这个问题很凸显,但是因为早期积累不足,也很难一蹴而就。创业公司的另一个麻烦就是,创业者自己本来可能就是基层的同学,在摸爬滚打过程中成长,也难有什么体系化,科学化的认识。意识到的时候,补起来很痛苦。所以这是很多创业公司活下来后,无法做大做强的重要瓶颈之一。

很多技术同学,走上管理岗位,都是被动的。就像我说的,公司要想在一个业务方面,有序开展生产,必须要有一个个层级的协调人,也就是基层和中层管理者。怎么找到他们呢?就是从既有的团队里拉起来一个,要求他充当这个角色。这本来是一个机遇,但是前一个章节说的,管理意识是一个很难跨越的障碍,不少同学都是因为技术过硬和业务精熟被挑出来的,但是挑出来后,他们可能对管理的价值并不由衷认同。

其实这时候,就需要团队的领导者或者管理者有意识地去培养,尝试去激发他们的管理意识,培养他们的热情,去认同这项工作的价值,然后言传身教一些具体的方法。管理职责和标准动作,都可以通过培训完成,但是怎么从管理过度到领导,就必须另一个领导者来启发和引领。这里最重要的一个环节,就是个人意识的激发和觉醒。也是我们做得最差的一个,基本都是放养,然后很多人无法完成转变,最后流失。

一旦意识觉醒以后,到底怎么继续往前,怎么去到更高的境界?这就是一个我自己也没有想清楚的问题了。目前,只能是修行在个人了。我的想法很不成熟也没法进一步去阐述了。

风险的问题

另一个制约技术人走上管理岗位的问题,就是风险问题。没有哪一家公司是长盛不衰的,也没有哪一种业务是一定可以保持增长和存活的。那么技术转了管理后,必然分出来很大一部分精力从事管理和领导的任务。这时候,其技术的成长去必然不能兼顾。再加上原来的思维定势,再次应聘的时候,往往还是退回纯技术岗位,又因为整个行业的问题都是类似的,寻找管理者和领导者的时候,往往无法鉴别,只能退回技术去找寻候选人,哪怕并不需要,于是,又变成了技术专家的面试。

其实很多公司需要的是领导者,但是面试都是挑技术专家。这很奇怪,但是现状就是如此。这种现状,给每个从事管理的人,制造了很高的门槛,不得不兼顾个人的技术成长,也是他们轻视管理的原因。就好像高考指挥棒在那里摆着,素质教育就没法开展。每个管理者都背负着巨大的降级风险,甚至失业风险。

背负这样的后顾之忧,成长就必然慢,供应就必然更加短缺,这是一个完整的负向激励闭环。非常地可惜。我自己的成长也同样置身于这种阴霾之下。

解决之道

以我目前的见识和眼光,我也看不到什么太好的办法。只能尝试去谈谈自己的看法。我觉得,这个其实是一个系统的问题,可能需要更多的人去认识到人的价值,领导者的价值,然后,为整个行业去打造这样一种环境,认同不同类型的人才的价值,才能系统性地去打开一个正向激励的循环。这里面,优秀的领军企业,应该承担好自己的社会责任。

从我自身来说,还是要在公司努力提升影响力,扩散领导力的价值。要增加跟底下一线的管理者的接触,增加言传身教的机会,让他们能够意识到这个问题,并有意识地去培养自己。同时考虑到现在整个环境的现状,还是要制造一些机会,和提供一切资源,帮助他们去对抗面临的风险问题。解决了后顾之忧,他们才能放心学习和成长。

Algorithm

Review

Tips

这次的 Tips 是关于 Linux 服务器管理的,Charles 前两周,颇费周章,把自己动的第一台 MacBook Pro,安装成了 Linux 服务器,装的发行版是 Ubuntu 19.04 Desktop 版。

一个朋友跟我说,这么激进啊,我嘿嘿一笑。谁知道,今天折腾的时候,才发现自食苦果。最近几年,明显感觉到 Linux 的系统管理发展迅猛,我们小时候玩 Linux 学会的那些知识,眨眼全部作废了,真让人不胜唏嘘啊。

这次碰到的问题是 DNS 服务地址的设定问题。在我的记忆里,就是 /etc/resolve.conf 里面写一行 nameserver 指令配置一下就可以了。现在实际试下来才知道,根本不对。我无论是把里面改写了还是新增记录,一重启解析服务,里面的东西就全部还原了。

后来,我又想起来可以在 /etc/network/interfaces 里面修改配置,写是写好了,但是完全不起作用,我用 ifdown enp3s0 && ifup enp3s0 命令重启网络,还是报错无法解析。

实在不行,去官方看文档,竟然发现 Ubuntu 19.04 的服务器管理文档根本没有发布!果然,在服务器选择方面,还是不应该太激进,还是要选可靠一点的版本。现在也不可能重装了,算了吧。再给我一次重来的机会,我一定会选择 LTS。

我只能照着 18.04 LTS 版本的文档去查找和阅读了,得知在 18.04 里面引入了 netplan (https://netplan.io/)这个套件来管理网络,号称用更加人性化的配置方式来管理整套网络了。怎么个人性化呢,就是选择了人类更容易读写的 YAML,好吧。

但是,我一尝试,发现 Desktop 版,根本没有安装过 netplan,虽然说配置文件的目录布局都有了,但是竟然软件没装,apt 来安装,发现——不能解析!气死我了:

1
2
# 观看 resolv.conf 的文档,顺藤摸瓜找到用 resolvectl 命令可以动态设置 DNS
resolvectl dns enp3s0 192.168.1.1

总算是把 DNS 给设置出来了,然后使用 apt install netplan 安装好 netplan 网络管理包。然后,我们来看看现在的 Ubuntu 的网络管理。这里发生了很多的变化,我都没法理解的,先记录在这里把。比如第一点,关于以太网设备的名字,以前我们熟悉的那套 eth0,eth1 之类的,被称作 Linux kernel 风格。现在你用 ifconfig 命令看到的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enp3s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
inet 192.168.1.9 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::cabc:c8ff:fe8d:9d3 prefixlen 64 scopeid 0x20<link>
ether c8:bc:c8:8d:09:d3 txqueuelen 1000 (Ethernet)
RX packets 29433 bytes 2466486 (2.4 MB)
RX errors 0 dropped 17848 overruns 0 frame 0
TX packets 7051 bytes 1450602 (1.4 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device interrupt 16

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 15196 bytes 1083107 (1.0 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 15196 bytes 1083107 (1.0 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

以上,我的以太网卡的名字变成了 enp3s0 ,这个名字的来源还不太懂。然后我发现,现在官方文档推荐用 ip a 命令来查看网卡了了。

1
2
3
4
5
6
7
8
9
10
11
12
13
○ → ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether c8:bc:c8:8d:09:d3 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.9/24 brd 192.168.1.255 scope global enp3s0
valid_lft forever preferred_lft forever
inet6 fe80::cabc:c8ff:fe8d:9d3/64 scope link
valid_lft forever preferred_lft forever

以上,命令的结果是这样的。[1]

在 netplan 里,配置文件要好写得多:

Share

参考文献:

  1. https://help.ubuntu.com/lts/serverguide/network-configuration.html

今天跟一个朋友约定,一起开始认真学习算法。于是翻出了买了多年,但是从未看过的《算法导论》。认认真真看起来。记得多年前,就一直在这本书上受挫,这是除了《代码大全》外,另一本归入“买了永远不会看”分类的书。我现在的想法是,我看完这本书,并认真完成所有的练习,是否肯定可以获得一定程度上的收获呢?哪怕是花去人生中的很多年,也一定要完成。

  1. 给出一个真实世界的例子,其中包含着下列的某种计算问题:排序,确定多矩阵相乘的最佳顺序,或者找出凸壳。

这是1.1节的第一个练习题,如果认真看题目,并思考的话,会发觉,这其实是一个相当困难的题目。反正我的第一个直觉就是,我不可能想出来真实世界中有什么地方是要用到多矩阵相乘,或者找出凸壳这种东西的。对于矩阵的认识,我停留在大学时代学过的线性代数的残留记忆的水平,约等于是一无所知。当年也是如此,学习线性代数,也只是当成一门数学来学习,从来没有想到过,这门学科可以用在真实生活中的什么地方。而美国随便一本计算机教材的第一章,第一节,要求学生思考真实生活中什么地方用到这个数学的知识,正是以把知识和生活紧密结合为导向,向学生传递知识,如果学生真的认真思考,很难想见这些学生会没有成就,学生对此问题的理解会不深。

而反思自己,无论是学习线性代数的时候,还是学习算法的时候,我从来没有想到过,某个数学概念和或者某个算法跟真实生活的联系,也难怪,学过了就忘记,要是记住了,那才真的是奇怪。

要说真实世界用到排序的例子,那是相当简单的,我决定每个人至少能说出个十种八种。我就说一个我最近生活中常常碰到的,就是在12306购买火车票的时候,我最常用的,就是按照发车时间顺序排序,或者在使用手机查询列车时刻表的时候,按照列车行程时间排序,以便我买到车程最短的列车。

  1. 除了运行速度以外,在真实世界问题背景中,还可以用那些效率指标?

关于算法效率指标,我马上就能想到的还有一个,就是算法耗费的内存。我们常说时间换空间,或者空间换时间,就是说这二者是一对矛盾,所以,衡量算法效率,除了时间,就是空间。也就是存储。

  1. 选择你原来见过的某种数据结构,讨论一下其长处和局限性。

先选一个最简单的,就是数组。这是一种线性的数据结构。其长处是按照下标读取元素的速度相当快而且简单。缺点是,一旦需要数组元素变动,操作就会极为复杂,比如在中间插入一个元素,后面所有的原素都要跟着挪动位置。另外一个缺点就是数组的原素个数是固定的。数组的空间是静态的。

最近又提起了兴趣去折腾 VPS,买好一台新的 VPS 服务器后,第一件事情就是登上去设置环境,当然,SSH 登录必不可少,这也是远程操作一台服务器的先决条件。不过 SSH 服务器,默认不是按照最优的方式去配置的。所以,我打算自己总结一下 SSH 服务的最佳实践。

安装 ssh 服务器

如果购买的是一般云计算的 VPS 服务器,当然是没有这个过程了,不可能没有安装 ssh 服务器的。如果想把自己淘汰的旧电脑变成一台 Linux Server 的话,可能就会遇到这个问题。例如,我昨天把自己 2009 年的一台 Macbook Pro 安装上了 Linux,我选择了使用最为广泛的 Ubuntu,选择了最新的 19.04 版本,主要希望它能对硬件有比较好的兼容性。

一般来说,在个人电脑上,最好的选择是安装 Desktop 版本,因为你毕竟有屏幕、键盘等输入输出设备,往往还有无线网卡,装成 Server 的话,想让它兼容你电脑上的各种硬件是很痛苦的,但是如果装成 Desktop 的话,想把它改造成一个提供服务的 Server 是很简单的。所以,我们一般都安装 Desktop 版本。

比较邪门的就是 Ubuntu 的 19.04 Desktop,竟然连 ssh server 都没有装,只有一个 ssh-client。

1
2
3
4
5
6
7
8
9
# 1.我觉得 apt-get 一点不好用,装个 aptitude 包管理器
apt-get install aptitude
# 2.检查 ssh 安装状态
aptitude search ssh
# 3.安装 ssh server
aptitude install openssh-server
# 4.检查服务状态
systemctl status ssh
# 5.到这里基本安装完毕了,以上在 Ubuntu 19.04 上测试过

禁止 root 用户直接登录服务器

新的 VPS 分配的时候,都是默认设置 root 用户,并且设法把密码发送给管理员知道。root 用户是 Linux 系统权限最高的用户,一旦泄漏了,后果不堪设想,黑客可以使用 root 的权限完全控制一台服务器。

比较好的做法是,禁止 root 账户直接登录到服务器上,因为如果允许 root 登录,就可能让黑客通过攻击直接得到 root 的权限。

比较好的实践方式是,设定一个非 root 的账户,用于日常登录使用,需要的时候,使用 sudo 临时取得权限,或在本地 shell 登录到 root 帐号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用 adduser 添加一个用户
adduser charles
# 使用 passwd 创建密码
passwd charles
# 使用 usermod 改变这个用户的 shell 为自己最喜欢的 zsh
usermod -s /bin/zsh charles
# 登录用户
su charles
# 进入用户的 home 目录
cd
# 创建 .ssh 目录
mkdir .ssh
# 创建公钥文件,用于注册可以登录的客户端
touch .ssh/authorized_keys
# 为了让这个用户得到执行 root 权限的能力,加到 sudoers 里面
gpasswd -a charles wheel
# 将一些环境变量带到 sudo 的环境(高级)
visudo

上面的代码,在系统里建立了一个新的用户,给这个用户创建一个密码,密码可以用于使用 sudo 命令。

然后使用 gpasswd 将用户加到 wheel 用户组,在 CentOS 上,就可以让用户得到 sodu 权限。 多年来我积累了一套自己使用非常习惯的 shell 配置,也建议读者这么做,可以去看看我的做法 https://github.com/charlestang/env.git 就会明白我在说什么。实现这套配置,环境变量少不了,比如 vim 的配置问题。

你可能会发现,sudo 后面使用 vim 的话,很多高级配置都无法生效。其他的命令也可能有这个问题。产生的原因其实是在一个用户的环境下设置的环境变量,使用 sudo 的时候,就会失去。可以通过代码片段的 visudo 命令,打开配置文件,指定要代入到 sudo 场景下的环境变量。

使用 sudo 会带来很多的不变,但是,作为一个运维人员,还是要养成不使用 root 的好习惯。各种小问题都是可以克服的。

使用基于公钥验证的登录

一般来说,购买一台服务器,新分配的时候都是默认用户名密码登录的,国内的阿里云、腾讯云都有这样的,国外的 Linode,前两天体验的 Vultr 也是。

但是,密码验证这件事情,在密码学里叫 PSK,pre-shared key,预先共享的。也就是至少有两方知道这个密码。而且,在公有云,是云平台设定的第一个密码,所以云平台是知道的。

更安全的方式,是使用非对称加密的方式,也就是基于公钥验证的登录。首先要在本地创建一个公私钥对,然后,把公钥注册到服务器的 authorized_keys 文件里。

1
2
3
# 在自己的电脑上创建 RSA 的公私钥对
ssh-keygen -b 4096 -t rsa
# 这个命令会在你的 ~/.ssh/ 目录创建 id_rsa 和 id_rsa.pub(公钥)文件

插图节选自《HTTPS权威指南》

关于非对称密钥的长度问题,我看到 CentOS 7 的文档里面介绍,默认的 RSA 的长度是 2048 位,文档里认为足够了,而且建议的最短长度是 1024 位。上面的例子里,我特意用了 4096 位,哈哈,希望能用 30 年……插图选自一本技术书籍,内容是2012年,权威机构对密码长度和对抗强度的一个估算。

公钥文件配置在服务器的非 root 用户的 ~/.ssh/authorized_keys 文件里面,内容贴进去就可以了。一般来说,服务器缺省设置,都是支持使用公私钥验证进行登录的。下面是对/etc/ssh/sshd_config的配置;

1
2
3
4
5
6
7
# 开启公钥验证
PubkeyAuthentication yes
# 不允许 root 帐号登录
PermitRootLogin no
# 不允许使用密码验证登录
PasswordAuthentication no
ChallengeResponseAuthentication no

更换SSH服务的默认端口

SSH 服务的默认端口是 22,这个端口已经是全网皆知的了,也是网络上自动化工具攻击的首要目标,希望自己的服务器安全的话,就首先要把这个端口给更换掉。

推荐使用 10000 号以上的端口,设置的时候,可以这样:

1
2
3
4
5
6
7
# 1. 先新增一个监听的端口号,先不要删除 22 端口
Port 22
Port 10022
# 2. 重启 sshd 服务
> systemctl restart sshd
# 3. 退出登录,然后用自己的电脑去测试能否连接新的端口号
# 4. 如果登录成功,删除 Port 22,然后再次重启 sshd 服务

为什么按照上面的顺序操作?很多云服务器,都有默认的安全组或者防火墙配置,极有可能,默认情况下,除了 22 端口,其他都访问不了。所以,不要忙着取消 22 端口,而是先新增一个,测试一下联通性。不然,可能你退出了,就连不回来了。虽然,也不会有什么大的后果,但是会带来不少的麻烦。

优化SSH服务器端的性能

有一些常见的缺省设置,可能导致SSH服务的连接缓慢,简单调整参数,就可以使连接速度变快:

1
2
3
4
# 关闭 DNS,默认情况下,服务器会解析连接上来的服务器的域名(Hostname)
UseDNS no
# 这种验证方式也是拖慢连接速度的常见问题,其实在 CentOS 7中此项默认已经是 no 了
GSSAPIAuthentication no

总结

感觉还有很多的细小的点没有总结全,不过这个帖子可以放着,慢慢总结吧。

我发现,每隔一段时间,运维 Linux 服务器的方法,就会变迁一次,害得我总是要重复学习这件事情,真是太不友好了。Linux 服务器运维的方法不是一种半衰期很长的技巧么?世道都变了啊……

Ubuntu 桌面系统初始化

这两天安装了一个 Ubuntu 19.04 Desktop 到我的最老的 Macbook Pro 上面,打算当成家庭的 Server 使用的。

1
2
3
4
5
6
7
8
9
# 1. 说实在的,我就看不出来这个新版的 apt 命令有什么好用的
# 当然,底层的命令 apt-get 和 apt-cache 更难用
apt install aptitude
# 2. 替换掉 vim-tiny,不知道这么多年过去了,为什么还是这样
aptitude remove vim-tiny
aptitude install vim
# 3. 桌面版连个 netstat 命令也没有,装一下(推荐使用 ss 命令代替)
# 不是梯子的 ss,我没写错,就是 ss 命令
aptitude install net-tools

CentOS 检查系统已注册服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 注意:从 CentOS 7 开始,已经不推荐使用 chkconfig 了
# 检查有哪些注册了的服务(SysV 流派的系统服务,迟早会被新的方式取代的一种)
chkconfig --list
# 关闭这种流派的服务:在2,3,4,5四个run level下关闭名叫 agentwatch 的服务
chkconfig --level 2345 agentwatch off
# 删除指定名字的服务:删除名叫 agentwatch 的服务
chkconfig --del agentwatch
# 检查有哪些注册了的服务(systemd 流派的系统服务,CentOS 7+)
systemctl list-units
# 只列出 service 类型的
systemctl list-units --type service
# 禁用服务:禁用一个名叫 aegis.service 和 agentwatch.service 的服务
systemctl disable aegis
systemctl disable agentwatch
systemctl status agentwatch
rm /etc/init.d/agentwatch
rm /etc/systemd/system/aliyun.service
rm /usr/sbin/aliyun-service
rm /usr/sbin/aliyun-service.backup
# 如果手动暴力删除了一些 service 的配置文件
systemctl reset-failed
systemctl list-units --type service
# 执行上述两个命令,发现没有残留了

CentOS 上安装支持 BBR 的 kernel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看当前的发型版本
cat /etc/*release*
# 导入新的 repo,参见 http://elrepo.org/tiki/tiki-index.php
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
# 安装与 CentOS 7 对应的 rpm 包
yum install https://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
# 检索对应的 kernel 包
yum --enablerepo=elrepo-kernel search kernel-ml
# 安装正确的 kernel 包
yum --enablerepo=elrepo-kernel install -y kernel-ml.x86_64
# 确认安装了哪些 kernel 包
rpm -qa grep kernel
# 查看目前的配置
egrep ^menuentry /etc/grub2.cfg cut -f 2 -d "'"
# 这步骤之后,机器需要重启,然后确认现在内核的版本号
uname -a
# 启用 BBR
echo 'net.core.default_qdisc=fq' tee -a /etc/sysctl.conf
echo 'net.ipv4.tcp_congestion_control=bbr' tee -a /etc/sysctl.conf
sysctl -p
# 确认 BBR 是否启动
lsmod grep bbr

在 Linode 上申请了一台 CentOS 7

最近,腾讯云审核非常严格,于是我又购买了一台 Linode 的服务器来玩,以防万一,按照以前我的性格,我会选 Debian 9 发型版的,但是最近比较偏爱 CentOS,就选择了 CentOS 7,其实,CentOS 8 也出来了,不过真的鬼使神差还是选了 7。

按照我一贯的做法,先要更换 SSH 的端口号的,主要是为了安全,感觉在互联网上非常奇怪,出现一台新的开放端口的机器后,常用端口就立刻会被不断攻击,所以,我习惯的做法是立刻把各种要命的服务都改成冷门的端口号。

没想到,Linode 上生成的 CentOS 7 实例机器,竟然默认了非常多的安全设置,让我完全没有想到。

首先是 SELinux,我挣扎了半小时,还是放弃了,这么多年来没有打起勇气搞明白这东西,感觉实在太麻烦了。

编辑 /etc/selinux/config 文件,将 SELinux 切换到 disabled,然后重启服务器。可以彻底关闭 SELinux,然后我在 56000 端口开启 SSH,没想到还是连不上,iptables -L 发现,竟然还有 iptables,当我尝试 systemctl stop iptables 的时候,发现告诉我,系统没有 iptables.service 的 unit,这就让我摸不着头脑了。

网上继续搜索,发现,CentOS 7 默认使用的是 firewalld 这个服务,真是让人晕头转向,又简单研究了一下 firewalld 的用法。

1
2
3
4
5
6
7
8
# 检测 firewalld 是否开启,显示 running 就是开启着
firewall-cmd --state
# 列出当前开启了哪些服务,发现有 dhcpv6-client 和 ssh
firewall-cmd --list-service
# 然后加入你要增加的端口
firewall-cmd --permanent --service=ssh --add-port=56000/tcp
# 重新加载配置
firewall-cmd --reload

这里我 Link 一篇文档,非常赞,以备查阅《Understanding Firewalld in Multi-Zone Configurations》。这篇文档详细介绍了 firewalld 的原理,以及操作范例,看完基本都明白了,很不错。

在这台 CentOS 上,我想使用 yum 源来安装 ss-libev,没想到遇到了很大的阻碍,以前我在阿里云的 CentOS 实例上,轻松可以 yum install 的东西,在这里竟然遇到了很大的阻碍,折腾了两三天竟然还没有装成功,Linode 的默认实例配置里,添加了 ss-libev 的源后,发现缺少两个关键的依赖,libsodium 和 libmbedlts 两个东西,我尝试手动编译安装了 libsodium 发现并不行,因为 rpm 是个体系,手动编译不能补充这个依赖,它只认自己数据库里面的数据。

这下有点麻烦,于是我想到了换源,是不是 Linode 服务器提供的 yum 源有问题,尝试换成 163 的源,然而不管用,再尝试换成阿里云的源(我先尝试了这个,拷过来是不行的,因为可能是私有的域名或者只对国内 IP 开放更新的),换阿里云的办法是:

1
2
3
4
5
6
7
8
# 首先是备份原有的文件
cd /etc/yum.repos.d
mv CentOS-Base.repo CentOS-Base.repo.linode.backup
# 然后是下载阿里云的 repo 文件代替
curl -o CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
# 然后是更新缓存
yum makecache
yum update

然后搞笑的是,我更换了阿里云的源发现,竟然仍然无法正常用 yum 安装 ss-libev,还是那两个依赖解决不了,实在无语了暂时还没搞明白怎么回事。然后我打算放弃治疗手动安装算了,libev 的 GitHub 官方页面推荐使用 snapcraft.io 来安装,所以我顺便看了一眼怎么安装 snapcraft.io,第一个步骤是给 yum 增加 epel repository。

1
sudo yum install epel-release

然后我灵机一动,难道这就是我苦寻的遗失的源?一试,果不其然,那我也不用编译安装了,直接成功 yum install 了。到此,我又把阿里云的源换回去了,说实在不怎么信任阿里云。万一参杂点私货呢?对吧。

这里再记录一下,怎么用 Screen 去后台长期运行一个程序:

1
2
3
screen -dmS SessionName /bin/cmd -o options
# -d -m 这两个参数组合起来的意思是,不要启动 screen,而是直接 detach 状态执行命令
# -S 的意思是给这个 Session 取个名字

我这里 Link 一篇文档以备查阅《使用 Screen 管理你的远程会话》。

低配置的服务器临时增加 swap 来编译

今天在以前买的腾讯云服务器上编译 PHP ,发现竟然因为内存不够被杀掉了,得增加 swap 文件来,解决内存不够无法编译的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 首先检查一下现在系统的内存,单位用 MB
free -m

# 看到服务器没有配置任何 swap,total = 0
# 看看是否有已经定义的 swap 文件
swapon -s

# 看到没有任何文件
# 然后来格式化一个 swap 分区,512M
dd if=/dev/zero of=/swapfile bs=1024 count=512k

# 然后在上面创建一个 swap 空间
mkswap /swapfile

# 激活 swap,然后使用上面的 swapon -s 检查是否成功
swapon /swapfile

# vi /etc/fstab,在里面最后一行配置
/swapfile swap swap defaults 0 0

# 修改文件权限
chown root:root /swapfile
chmod 0600 /swapfile

# 最后用 free -m 看看 swap 的 total 增加了没有
# 看看操作系统依赖 swap 的频繁度
cat /proc/sys/vm/swappiness

# 默认值是 60,太频繁使用 swap 会拖慢速度,毕竟内存更快
sysctl vm.swappiness=10

# 改低这个参数,done

使用上面的步骤,就完成了 swap 的添加,然后再次尝试编译,成功了。

以上内容引自:《How To Add Swap on CentOS 6》

这个过程的逆过程就比较简单了:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 关闭 swap
swapoff -a

# 确认
swapof -s

# 看到没有文件了
vi /etc/fstab

# 去掉关于 swap 的配置
rm /swapfile

# 就彻底删掉了 swapfile

在 Ubuntu 20.04 上安装 MySQL

其实毫无难度的,就是用 apt install mysql-server 就可以了。我这里想说的是,装完了以后,怎么登录进去呢?整个安装过程是没有交互式的。

你可以 sudo mysql,直接用 root 权限调用客户端,就会以 root 身份登录了,不需要密码。

1
mysql -uroot -p

首次安装好数据库后,不需要密码,直接按回车就可以登录进去了。值得一提的是,现在创建用户和密码的方法变了。

1
create user admin@localhost identified by '123@qwe';

上面的用户定义了一个本地用户,密码是 123@qwe,我以前喜欢用Grant语句直接授权和创建用户和密码,现在似乎是不行了。

1
grant all privileges on db_name.* to admin@localhost;

上面的语句是授权用户访问一个数据库的语句,privileges 关键字不是必须的,可以省略。

然后需要执行:

1
flush privileges;

关于这一点,腾讯有一片文章介绍得不错:

《如何在 Ubuntu 20.04 上安装 MySQL》

如何在 Ubuntu 20.04 上安装 PHP

也说个简单的做法,就是用 apt install php-fpm,如果你是安装 php 这个包,你会发现海量的依赖,会把整个 apache 2 都给带出来。因为默认是这样的。

不过 nginx 伺服静态文件要好一点,所以,现在流行 LNMP 多一点,你可能不想安装 apache 2,那么你就应该安装 php-fpm 这个包。

使用 apt 的好处是,以后升级的时候,简单一点。如果没有逼到非要自己编译,最好不要自己编译,实在麻烦而且无趣,当然并不难。

首次在 CentOS 服务器上安装 MySQL

安装服务器比较简单:

1
yum install mysql-server.x86_64

装完服务器后,服务器默认是不启动的,使用 systemctl 命令进行启动

1
systemctl start mysqld.service

然后,root 的初始密码是什么呢?

1
mysql -uroot -p

你会发现,MySQL 初始并未设置密码,比较安全的方式是,你登录成功后,马上设置一个新的密码,使用命令:

1
2
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';
FLUSH PRIVILEGES;

即可完成 root 密码的设定。

最近我遇到一个难题,我在阿里云买了一台服务器,用它搭建了一个“获取全世界互联网信息的服务”。我家用的是电信宽带,使用起来非常流畅,但是莫名其妙的是,在公司就不能很好地运作。公司用的也是电信的宽带服务,是写字楼园区专供的企业电信宽带服务。

跟公司的 IT 抱怨很久,他们也没法解决,说我对服务器发出的请求,是从离开公司端口后开始丢包的,只能去投诉运营商,这个借口真好,原因是什么也没法查,解决当然也无法解决啦,好,不去管这些烦心事情了。

后来我同事告诉我一个神奇的工具,以及解决方案,终于曲线救国了。我把阿里云服务器上的服务,桥接到我家里,然后在公司的时候,我就连接到我自己家里的服务器。为了保持家里和阿里云的可靠连接,我需要搭建一条可靠的隧道。这里就用到了一种ARQ协议。我以前不知道什么是ARQ,今天看了一下维基百科,觉得还是很有意思的,本文就介绍一下什么是ARQ。

要记住:重要的事情说三遍

ARQ 的全称是 Automatic Repeat reQuest,不要问我为什么选择这三个大写字母,我也觉得选择很奇怪,估计大写缩写有一定的规则吧,我并不了解。但是从字面意义上看,就是说,把请求自动再发一次。就好像,你跟别人说话,那人没听到,你就再说一遍,一直到他听到为止,这就好像是我们开玩笑的:重要的事情说三遍一样。只是一个比喻。

从这个名字里,我们可以了解到另外一件事情,ARQ 虽然被称为是一种协议,但是定义里看更像一种策略,或者一种解决方案建议,具体怎么去实现,还是有多种办法的,也有很多的变体,后面会说到这个问题。

ARQ,也可以是 Automatic Repeat Query 的缩写,是一种在数据传输时,使用确认(_Acknoledgements,就是我们常说的ACK,接收方发送一个消息,告诉发送方,自己是否正确接到了一个包体_)和超时(_Timeouts,在收到一个确认消息之前,等待的一个确定的时间段_)机制,在不可靠的网络上,实现可靠的数据传输的错误控制方法。如果发送方在超时之前没有收到确认,通常会重新传输相应的包体,直到收到确认或者重试超过一定的次数。

维基百科 Automatic Repeat Request

祭出此图:ARQ 一般在数据链路(Data Link)和传输(Transport)层实现

ARQ 的常见策略

ARQ 协议有很多种常见的策略:

  • 停止并等待 ARQ(Stop-and-wait ARQ):这是最为简陋的一种ARQ实现方式,用于远程通信的两端,保证消息传送没有缺失和按序到达。一句话概括这种策略,就是每说一句话,都要等到确认才说第二句,否则就一直重复上一句。很显然,这种方式就是串行发送消息帧,而且要等待确认才继续,一帧内容传送就要最短等待一个往返消耗的时间,有时候还要加上超时,所以效率非常低下,速度也非常慢,实现起来很简单。这种协议很容易出问题,比如,接收方的确认消息丢失了,或者确认太慢了,导致了发送方等待过久,发送方就会重发,结果接收方就收到两个一样的帧,这时候,接收方就没法确认,到底是上一帧重复了,还是又来了一个新的帧。发送方也可能会收到两个确认,无法识别是重复确认同一帧还是不同。于是,这种协议会在头里加上一个比特(bit)的序号,取值只有 0 或者 1,接收方收到的时候,是按照 010101…… 这样的交错顺序的,否则就是重复帧,简单丢弃就可以了,发送方收到的确认也带序号,采用同样策略处理。所以,这种 ARQ 也叫做“翻转比特协议”(alternating bit protocol);
  • 后退N帧 ARQ(Go-Back-N ARQ):这种协议相对上面一种来说,其实利用了统筹的思想,这种策略下,发送方在等待超时的间歇,可以继续发送数据帧,可以连续发出的帧数量,叫窗口大小,显然窗口大小的设定和超时时间,数据传输速度匹配,是比较优化的选择。同样,所有发送的帧,都要带有序号,接收方严格按照序号收纳数据帧,先收 1,再收 2,如果这时候 3 丢了,来了一个 4,那么接收方会丢弃。同时,接收方每收到一个帧,都要确认一个序号,就是自己收到的有效的最大序号,在这个例子里,接收方一直确认 2。发送方在发完一个窗口所有的帧以后,检查最大的有效确认,然后从最大的有效确认后面一个开始重发,比如上面的例子里,发送方已经发到 8 了,但是因为最大的有效确认只有 2,那么必须从 3 开始重新发送。其实,TCP 协议,使用的就是这种 ARQ 策略(变体)。从这个机制的原理可以看出来,这个协议的特点,有一个滑动窗口,但是这个窗口只对发送方来说存在,接收方只是一帧一帧处理数据,所以,仍然存在很多的浪费,一些信息会反复发送多次。如果把收到的不想要的帧,先缓存起来,说不定将来会用到,就可以提高效率,这就引出了另一种策略;
  • 选择性重发/拒绝 ARQ (Selective Repeat/Reject ARQ):其实理解了上一种,这种就很容易了,就是把收到的非预期帧缓存起来,然后告诉发送方自己缺哪一帧,服务器不会连续重发,而只发送接收方缺失的帧,如果接收方续上了编号,就快速向前跳动编号,这样极大减少了数据的重复发送,提高了效率。

回退N帧 ARQ(a)和 选择性重发 ARQ(b)的原理图

总结

ARQ 是一种在数据传输过程中,错误控制的策略,保证了数据的完整性和顺序性。核心目的是在不可靠的网络上实现可靠的数据传输。比如,在短波无线电传输,GSM 网络,电报等领域都有很广泛的应用。在当今因为各种原因导致的复杂互联网环境上,某些点对点传输的服务,也变得不那么可靠,可以考虑引入 ARQ 策略。

在服务器端开发方面,ARQ 的思想,也可以给我们在服务实现时候,很多的启发。此外,这种策略的学习和理解,也是理解更复杂的网络协议的基础。