Becomin' Charles

算法 | LNMP | Flutter | Mac

Becomin' Charles

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 的思想,也可以给我们在服务实现时候,很多的启发。此外,这种策略的学习和理解,也是理解更复杂的网络协议的基础。

SOCKS 代理协议是网络上使用非常普遍的一种协议,最近因为想要自己搭建一个穿透局域网安全网关的代理,所以,顺便仔细学习了一下 SOCKS 的相关资料,还算有点意思,特此记录一下。

SOCKS 协议的故事

话说,一个叫做 David Koblas 的系统管理员,他在 MIPS Computer Systems 公司工作。就是这个人发明了 SOCKS 代理协议。MIPS 公司在 1992 年,被一家叫做 Silicon Graphics 的公司给控制了(应该是收购了吧),这家公司后来把自己的品牌名改为了 SGI(一家很高端的软硬件制造商,2009年4月破产了)。也就是在这一年 1992 年,David 在 Usenix Security Symposium 安全研讨会上,发布了一篇论文,名字就叫 SOCKS,将此协议公之于众。

最早,整个互联网采用的是盲目信任的方式进行连接和组织的。当时,专家们在聚会的时候,讨论的都是怎么让网络更加简单和高效,一台主机在网络上到底怎么才能更可靠的被别的主机所连接。一直到 1988 年,一个叫做 Morris 的蠕虫,给整个互联网一记重拳,后来大家讨论将一个子网络接入到互联网的时候,安全也成了一个必须要讨论的重要话题。

不过,提升网络的安全性可没有什么容易的办法,想出来的大部分办法都是通过减少内网服务器向公网暴露的机会,来最小化攻击的概率,也就是我们常喜欢用的“最小原则”,如果没有必要,就不进行授权。很多网络在接入互联网的时候,都选择了单一出入口的方式。将本地的子网络,置于防火墙的保护之后,再接入到互联网,如此一来就极大减少了被攻击的机会,网络的安全性自然就提高了。

但是本地网络置于防火墙之后,再访问外部网络的资源就会非常不便。为了解决这个问题,人们想出了各种方案。内外网隔离(笨拙,使用极其不便,但是维护成本很低),单一机器授权(只有一台机器可以和外网进行双向访问,使用仍然非常不便,而且维护成本高昂,因为有很多用户的访问权限要分配和收回),还有安全路由器(常见的一种策略是允许所有的出口流量,但是对进口流量禁止所有 1024 以下端口的访问,这种策略的问题是,一旦路由被攻破,整个网络就置于威胁之下)。

OSI 7层网络模型

在这些方案之外,代理防火墙(Proxy Firewall)解决方案就被提出来了。路由器是在 OSI 模型的“网络层”进行安全过滤,可以降低客户端的成本,但是不够完善,维护起来也比较困难,估计当年的硬件不像现在可以随意修改配置吧。代理防火墙的方案,平衡了使用的便捷和维护的复杂度,是一种折衷。

代理防火墙工作在 OSI 模型的“会话层”,也就是“传输层”和“应用层”中间的地方。著名的 SOCKS 就是这样一种“解决方案”。它是一种非常轻薄的解决方案,在客户端,提供了一套开发类库,叫 SOCKS 库,对照着标准 socket 的 API,提供了五个 API 函数,名字跟 socket 的一样,只是用 R 作为前缀。在服务器端,提供了一个叫做 sockd 的伺服软件,这个软件部署在防火墙系统所在的一台主机上,通过简单的配置文件就可以完成应用层的过滤,允许或拒绝哪些目的地址和端口被接入,是非常容易维护的,给网络管理员带来了极大的方便。

在 1992 年公布之前,SOCKS 解决方案已经在 MIPS 内部使用了长达三年之久,没有遇到明显的问题和瓶颈,所以 David 认为这是一个久经烤验的成熟方案。

SOCKS 的版本发展

上面的故事里,我们可以看到 SOCKS 最初提出的时候,其性质是一个解决方案,包含一个类库和一个服务端后台伺服程序。但是,NEC 公司的李英达?(Ying-da Lee),看到了它的优美之处,把它提炼出来,发展成了一种协议,变得更加通用,而且,大家都可以根据协议提出自己的实现。这位李同学,提出了 SOCKS 协议的第四个版本。也是流传非常广泛的版本。

从李同学开始,SOCKS 的定位也变得非常明确,就是在防火墙服务器上,提供一种 TCP 会话的转发,允许用户可以透明的穿透防火墙的阻拦。这种协议的优势在于,它完全独立于应用层的协议,可以用在很多的场景,telnet,http,ftp 都不在话下,并且可以在 TCP 会话开始之前,完成访问权限的检查,之后只要做来回往复的转发即可。而且由于此协议完全不关心应用层的协议,所以应用层通信可以加密,保护自己通信的内容不被代理所看到。

SOCKSv4 开始,这个协议得到了非常广泛的应用,所以,广大群众对这个协议进行了扩展,就有了 SOCKSv4a 版本,这个版本只对 v4 进行了比较小的改动,主要是允许在协议头里填充域名,代替IP地址,用以防止客户端不能正确解析出目标IP的场景。(举个例子,某个局域网有一台服务器,域名是 test.oa.com,与之对应的 IP 地址也是内网的,这种情况下,外部用户是很难知道到底 IP 地址是什么的,因为没有登记在公网的 DNS 服务里面)到这里后,SOCKS 就成了一种非常通用的双向通信的轻量级代理协议了。也有了大量的标准实现。得到了海量的需要网络连接的应用程序的支持,成为了“电路级网关”(circuit-level gateway)的事实标准。

现在更加流行的 SOCKSv5 版本就是一个更加完善的版本了,主要增加了:

  • 强力的身份验证方案
  • 验证方法的协商机制
  • 地址解析的代理
  • UDP协议应用的代理支持

SOCKS 协议的内容

SOCKSv4的内容

上面的插图展示了SOCKS协议第四版的内容,可以看见,这里展示的是前两个包体,客户端发起,服务器回复,连接成功后,后续的操作就透明了。

SOCKSv4a 对第 4 版的扩展,非常小,就是增加了一个域名,只要在 IP 那个字段,前三个字节都填 0x00,就会由服务器来负责解析正确的地址,并利用回包空闲的两个字段来返回正确的 IP 地址和端口号码。

到了现在普遍使用的 SOCKSv5,协议变复杂了很多。首先,因为有强力的验证,而且支持多种验证方法,就有了一个协商验证方法的过程,然后,进行身份验证,最后再进行通信指令。

SOCKS5 的协商过程

上面的图,展示了 SOCKS5 连接的协商过程。协议的具体内容,对应着上面的握手环节,增加了几种不同的包体:

SOCKS5 的协议包体结构

SOCKS 协议的不足

从上面的 SOCKS5 的时序图上,我们可以看到,由于承担了额外的验证协商的功能,导致 SOCKS5 在建立的时候,需要额外消耗多达三次握手,如果不需要验证身份,也需要两次握手,这就增加连接时候的延迟。

另外,因为 SOCKS5 协议本身完全不关注应用层的内容,所以,客户端和目标服务器的通信,加密完全依赖客户端和服务器的通信协议,如果服务器使用的是 HTTP 协议,那么通过代理走的流量,就相当于是明文在内网传输。安全性上不是很高。

不过,我们可以通过在外面包裹一层 TLS 来解决这个问题,就形成了 SOCKS5 over TLS 的解决方案,有效加密了通信的内容。TLS 负责加密连接,SOCKS5 负责代理协议控制。

互联网上已经公布了 SOCKSv6 的草案,主要内容就是SOCKS5的问题修复,第一个就是多次握手已经不太适用于移动互联网和卫星通信等网络环境,需要被优化。客户端会尽可能多的发送信息给服务器,并且要求创建 socket 之前,不等待验证的结论;连接请求模仿 TCP Fast Open 的语义,具体就是在连接请求的包体里,带上一部分载荷,连同第一个SYN包一同发给服务器;根据选项可以不向后兼容;可选支持 0-RTT 的验证方法(一个IP包体到达服务器再返回客户端的过程消耗,叫 1-RTT)。

不过,目前来说,还没有得到广大软件厂商的支持。而且据说 TLS 1.3 也在积极探索 0-RTT 的解决方案,不过也还在协议草案的早期。

参考文献:

以前,我也注意到,不在现有项目中引入框架是有原因的,而且,尤其不能选用 Yii 框架。

“继承”噩梦

你所有的 controller,都继承自 CController,其又继承自 CBaseController,这个又继承自 CComponent。

所有你的 model,都继承自 CActiveRecord 或者 CFormModel,它们又继承自 CModel,这个 CModel 也继承自 CComponent。

这两者的继承链条,都包含了静态变量,并且执行了其他很多类的静态方法。这两个因素造成调试过程变得极为困难和冗长。

全局状态

有多种形式的全局状态。PHP 程序员熟知的就是全局变量。但是这并不是唯一的一种形式。类中的静态变量,也是一种全局状态,它(总是)能够造成看起来没有关系的类实例神秘地交互。

(Yii 框架)对全局状态的使用是其核心机制。在代码中,你到处都能看到静态方法的调用,而且,Yii 的配置文件,离了全局状态就没法工作。

每次你调用 Yii::app()的时候,你都在访问或者改变它。

这使得统一测试你的 Yii 应用,成为了不可能。而调试工作,则变成了练习在你的整个项目中使用 grep。

紧耦合

当你使用 Yii 创建一个应用的时候,如果不启动整个框架,你是不可能只运行其中一部分的。大多数时候,取决于你最终在你的代码里用了静态方法。

每次你在你的代码里添加了一个静态方法的调用,这段代码就跟那个静态方法的类耦合起来了。这就是紧耦合。

你可能已经注意到了(我希望你注意到了),有其他的方法可以实现一样的效果,使用 new 操作符。这是另一个方法让你的代码跟某个特定的类耦合起来。没有接口。

无论 Yii 项目的配置文件看起来有多吓人,这个配置文件的设计仍然是用心良苦的。这是在本已经一团糟的代码里,再度引入外部代码并代替已经存在的组件的伤害最小的一种方式。

然而,它将(Yii 框架)缺少接口和存在耦合带来的问题带到了聚光灯下。

很多程序员想要替换的组件之一是 CUrlManager,大多数是因为你能够传递额外的参数(给它)的方式的原因。

OOP 中的接口,明确了两个实例间的约定。由你来定义实例的能力,能被别人调用的方法。如果一个大的代码里,没有接口的话,你就不得不猜测,哪些方法是必须的,哪些不是。

在 Yii 组件里面,问题甚至更加严重,因为使用了静态调用和深度继承。上文提到的 CUrlManager 继承了 CApplicationCompnent,它又继承了 CComponent。而 CUrlManager 的同一个文件里,还定义了 CUrlRule 和 CBaseUrlRule。

当你编写一个他们的替代品的时候,你必须写一些代码,将他们插入到配置文件中,并在你自己的 Application 中测试,使用这种方式,你才知道下一个需要你填写的方法是什么。

基本上来说,这是一种“保存,然后看看什么东西爆掉了”的研发方法。

这绝不是 MVC!

Yii 并没有实现 MVC 或者任何受 MVC 启发的设计模式。它叫做“MVC”的东西,实际上可以用“ActiveRecord-Template-Logic”模式来描述。

Yii 框架的作者,实现了一组 active record 和表单封装,而不是一个恰当的模型层,这导致应用的逻辑必须写在 controller 里面。

另一方面,你有被美化的模板,而不是包含展示逻辑的适当视图实例。这在一定程度上被小部件的使用所缓解,但小部件反而遭受单一职责原则(SRP)的侵犯,因为它们被迫包含部分展示逻辑并执行部分渲染。剩余的展示逻辑又一次最终在控制器中。

更糟糕的是,“控制器”还必须处理授权问题。这通常意味着,每当你更改访问方案时,你将不得不检查每一个CController的实例,以确定是否也需要更改。

这不是MVC。这是一个混乱,名字是从MVC设计模式中借来的,随意贴在一些组件上。
所有的小事情…

框架也有一些小问题,不值得单独成章:

定义一个文件中多个类:

这很快就会变得令人烦恼,因为有些类被硬塞到完全不相关的文件名中。这意味着,调试往往需要使用搜索。

随意添加的“模块”:

看起来,这些模块是在事后添加到框架中的。这就是为什么,当你需要设置默认模块时,你将不得不在配置文件参数中设置,该参数被称为’defaultController’。

网站 502,看 log

发现 mysql storage engine error 28,不明白原因

google 被提醒磁盘满了

du -sm * 查看哪个目录占空间大,发现无法执行,因为 /tmp 目录计算不出来

ls /tmp wc -l 发现计算不了,主要 ls 执行不了

怀疑文件过多,google 找方法,因为 ls 默认排序导致

ls -f /tmp
ls -U /tmp
ls –sort=None /tmp

等方法,发现文件有 145万个

都是 sess_xxxx 为 PHP 的 session 文件

删空 sess 文件,处理 sess 文件的自动清理问题

全部做完发现,磁盘仍然是满的

du -sm *

发现是 log 过大

查看 log 是因为 WP 报错导致,原因是数据库 user 没有权限

授权后,问题解决,但是牵扯了更多的问题,有待解决

  1. wordpress 主从同步方案的缺陷
  2. 服务器监控报警

Yii 2.x 最底层的抽象是 Object,但是,窃以为,最重要的基础抽象,仍然是 Component。这点与 Yii 1.x 相比,并没有本质的变化,就是,我仍然没有参透,为什么要多出来一个 Object 的抽象,表面上看,这个 Object 比 Component 少了不少东西,如果没有必要的话,显然继承 Object 会更加合算一点,但是这么一点好处的话,简直微不足道。

Yii 1.x 的 CComponent 的回顾

我们先来回顾一下 Yii 1.x 的 CComponent 到底提供了一些什么样的核心抽象呢?

  1. 属性的抽象,支持 getter 和 setter
  2. 事件,以及配套的事件处理器
  3. Behaviour

所有 Yii 框架的对象几乎都会继承 CComponent,也即在 Yii 框架里,几乎所有的对象,潜在都会支持这几个特性。属性,我们在 Object 的文章里,已经谈及了,这个抽象,可以说是面向对象程序设计的基础,其实是对成员变量的一层封装,在成员变量基础上,提供了更灵活的可见性控制。不过,Yii 1.x 的属性实现,是有点奇怪的,这个怪异性我本来是无感知的,看完了 Yii 2.x 的实现后,我才明白它怪在哪里。1.x 的属性,在判定 hasProperty 的时候,只承认有 getter 或者 setter 方法的属性是属性,如果用公有成员变量的话,不被承认为属性。其实,我们从效果上看,公有成员变量,其实看起来就比较像是一个读写都开放的属性。使用 __set 和 __get 的魔术方法,无非是想让一个属性,用起来更像是一个公有成员变量那样,哪怕它并不存在实体,但是,一个公有成员变量真的存在时候,哪怕它用起来像是一个属性,但是竟然不被承认是属性,这确实有点奇怪。

Yii 1.x 的事件机制,我认为实现得并不好,其主要原理是,使用 CComponent 类的一个私有成员变量 _e 来记录事件处理器,所以,就预示着一个特点,就是事件的绑定,必须在对象创建之后。这就带来了极大的局限性,什么局限性呢,限制了事件绑定的位置。例如,我想要捕获一个事件,必须在产生这个事件的对象创建之后,这个事件发生之前去绑定我的处理器。按照我们一般的经验,这个,最好就是在对象创建刚刚完毕的时候,其实也就只能写在 __construct 函数的最后一行。如弱你要不想把事件绑定给写到对象类里面,那就更难用了。这么实现,一个缺点,是会把事件的绑定,散布到各个类的实现里面,另一个,就是当目标对象的类,不能修改的时候,会很痛苦,比如,是某个类库或者别的团队维护的类,那就很难看了。只能继承它,然后覆盖构造函数,但是如果对方的模块没有提供替换目标类的方法,你基本就束手无策了,所以,这种设计,对整个应用全局的代码设计,都提出了非常高的要求,极大的降低了易用性。

此外,还有一个比较难看的点是,事件的附着句柄,是一个以 on 前缀的方法名,也就是你想出发一个叫 afterBuy 的事件的时候,你必须在你订单类里面,定义一个方法,叫 onAfterBuy,而这个方法可能除了触发这个事件外,别无其他业务了,所以,如果事件比较多,就会在你的类实现里面,出现很多的只有一行的方法。至少从视觉上是比较难看的。

第三个东西,就是 Behaviour,这个我特意没有翻译成中文,因为字面意思是“行为”,但是这两个字,是无从表达这个东西的实质的。在 Yii 框架的文档里,会提到这个东西的别名,是 Mixin,字面意思是“混入”,但是,这个也比较难以说明问题。从现象上理解,就是一个类可以调用本不属于自己的成员方法,通过 __call 这样的 Magic Method 来实现的,利用了 PHP 语言动态的特性做到的。但是,这个Behaviour 也好,Mixin 也好,到底带来了什么好处,这也是我在整个应用框架过程中,所不能体会的一个点。这个 Behaviour 的实现,如果我没猜错,我觉得,也是有一定的问题的,实现得并不那么方便,因为,每个具体的对象,通过一个 behaviours() 方法来注册 Behaviour,但是,并不是每个品种的对象,都支持这个特性的。事实上,我搜了一下,在 Yii 1.x 里面,只有 7 个类,缺省激发了 Behaviour 机制。

它们是:CModule,CApplication,CController,CConsoleCommand,CActiveRecord,CFormModel,CApplicationComponent。这个机制,一般,都在 __construct 方法里面被激活,但是,也有例外的,比如 CApplicationComponent 在 init 方法里被激活,确实比较奇怪。除了这 7 个类,以及它们的子类,Yii 1.x 框架里,别的对象,虽然也预置了这个机制,却不是默认被激活的。

我简单查了一下维基百科,这个 Mixin 的思想,本质上,还是提供了一种抽象的颗粒度,比类更加细小,复用程度更高。另一方面,Mixin 的机制,可以部分弥补没有多重继承的烦恼,而用不着引入多重继承的复杂性。其实,PHP 在语言层面提供了支持,就是一个叫 traits 的特性,比框架带的这个 Mixin 强很多,但是,这个在 PHP 很高的版本才出现的,在语言版本没有更广泛的扩散前,还是框架自己实现比较好。

这个东西实现的缺点,和事件机制,有点异曲同工。都是绑定点必须在目标类实现里面去写,才比较合适,一旦不能改目标类实现,就会浑身都蛋疼了。在整个 Yii 1.x 使用经验中,我用到这个特性的机会少之又少,第一,是没太看懂这个东西的本质是什么,另一个是,这个东西也确实用起来没那么方便,假使回到从前我能有现在对这个特性的领悟,说不定我能够用起来吧,无聊的感慨啊~

Yii 2.x 的 Component 实现

在了解历史的基础上,在回到现实,可能就多了很多的便利。我们现在来看看 Yii 2.x 提供的核心抽象有哪些改进。

从整个 Component 提供的公有成员函数列表来看,到了 2.x Yii 框架的核心理念,并没有什么巨大的变化。仍然是三个主流的东西,属性、事件、Behaviour。

第一个不同,是 Component 不再是最底层的抽象了,底下还有 Object,所以 Component 是继承自 Object 的。但是,我们仔细看 Component 的方法实现,就会发现,Component 几乎没有从Object 继承任何东西回来。当然,还是有一丁点东西的,就是对象生命周期的概念,是继承过来了,因为 Component 里面没有构造函数和 init。除此以外,Object 提供的其他特性,Component 都涵盖了,而且完全覆盖了父类的方法,真是一种奇怪的思路(与其这样,再写一遍构造不是好了么,这么玩,仅仅为了 DRY 么?),但是我参悟也不是很深,也不便过多去评价这个。

属性,作为组件的第一个抽象特性,有了一些变化。第一个是上文提到的,公有成员是否被认定为属性的问题。在 2.x 里面得到了统一,公有成员变量,是被认定为属性的。第二个是,上文提到了一个 Mixin 的东西,在 2.x 里面,Mixin 这个特性被进一步向语言提供的 traits 的能力对其了,不光是方法可以被“混入”,现在属性也可以被“混入”了,也就是你可以通过一个 Behaviour,来向一个对象注入属性了。这已经比较逼近 traits 的能力了。而且,“混入”的属性,在进行 hasProperty 判定的时候,也会被判定为是 true。

事件,有了比较大的变化。第一个,是取消了使用方法来做事件句柄的做法,这样,你不必为了出发一个事件,来定义一个 on 前缀的方法,可以在随便一个字符串作为事件的名字去绑定事件,另一方面想要触发事件时候,可以用 trigger 方法随意触发任意事件了。整体来说,更加灵活了。但是,事件的处理器,仍然是用对象的私有成员变量 _events 来存储的,这就使得那个事件绑定点的问题,仍然存在。不过,还是部分被解决了,且看下文。

Behaviour,如果说,Component 里面变化最大的东西,莫过于是 Behaviour 了。从 PHP 5.4.0 开始,官方语言层面加入了对 Traits 的支撑。可见,这个东西在面向对象里面,渐渐成为了一种趋势了,大家虽然憎恨多重继承的复杂性,但是对与比继承更细粒度的代码复用,还是有所渴求的,所以这个轻量级的 Traits,被直接写入了语言。而 Yii 2.x,我觉得也是在深化这种复用形式的功能。一个是引入了除方法外,属性的“混入”,第二,将 Behaviour 的绑定,全面深化到 Component 层面,也即,这个特性,缺省在所有 Yii 2.x 的对象里都打开了,不像 1.x 那样,只有 7 个类是打开的。它被更加深入的当成一种代码复用手段,被全面地鼓励使用了。

通过 Behaviour,解决了我说的那个问题,必须把事件绑定给写到目标对象类的实现里面,这次不用了,你可以写到一个 Behaviour 里面,然后通过配置文件的形式去“混入”到目标对象里面,就可以达到目的,但是,仍然没有解决那个必须找到对象构造点的问题,却用另一些新的东西给解决了,比如DI,比如Service Locator等,且看其他文章的介绍了。

在使用 nginx 的时候,配置 gzip 模块,可以让服务器的伺服更加高效,对于文本类型的数据,传输量可以压缩一半左右。

官方建议我们创建一个叫 conf.d 的文件夹,使用 include 语句,将 gzip 的配置文件插入到 http 区段。

阅读全文 »