这是 《精通正则表达式》第 3 版 的读书笔记。
技术图书的主要使命是传播专业知识,专业知识分为框架性知识和具体知识。框架性知识需要通过系统的阅读和学习掌握,而大量的具体知识,则主要通过日常生活的积累以及随用随查的学习来填充。
完整的正则表达式由两种字符构成,特殊字符,元字符,另外一种就是普通文本字符。
完整的正则表达式由小的构建模块单元 building block unit 构成,每个单元都很简单,不过他们能够以无穷多种方式组合,所以可以提供无限的可能。
字符组
匹配若干字符之一,使用中括号 [ea]
匹配 a 或者 e
gr[ae]y
表示匹配 gray 或者 grey
元字符 | 名称 | 匹配对象 |
---|---|---|
. | 点 | 单个任意字符 |
[abc] | 字符组 | 列出的字符 |
[^abc] | 排除字符组 | 未列出的字符 |
^ | 行起始 | |
$ | 行尾 | |
\< |
单词起始 | |
\> |
单词结束 | |
| |
竖线 | 或 |
() | 小括号 | 限制竖线的作用范围 |
其他元字符
元字符 | 描述 |
---|---|
\d | [0-9] |
\D | [^0-9] |
\s | [ \t\n\x0b\r\f] |
\S | 非空格 |
\w | 单词字符 |
\W | 非单词字符 |
量词,定义了元素可以发生的频率
元字符 | 次数下限 | 次数上限 | 含义 |
---|---|---|---|
* | 0 | 无 | 字符出现任意多次,或者不出现 {0,} |
? | 0 | 1 | 字符出现 0 次或者 1 次 ,{0,1} |
+ | 1 | 无 | >=1, {1,} |
{X} |
匹配 X 个字符 | ||
{X,Y} |
匹配 >=X 且 <=Y |
正则表达式可以使用多个括号,使用 \1
, \2
, \3
等来匹配括号的内容
([a-z])([0-9])\1\2
其中的 \1
代表的就是 [a-z]
匹配的内容,而 \2
就代表 [0-9]
匹配的内容
匹配引号内的字符串
"[^"]*"
两端引号用来匹配字符串开头和结尾的引号,中间 [^"]
用来匹配除引号之外的任何字符,*
用来表示任意数量的非引号字符
匹配 URL
grep, 和 egrep 的历史
正则表达式的流派
正则表达式的处理方式
Perl 中的例子
if ($line =~ m/^Subject: (.*)/i) {
$subject = $1
}
取邮件标题的正则,内建在程序内,隐藏了正则表达式的预处理,匹配,应用,返回结果,减轻了常见任务的难度。
由普通函数和方法来提供
Java 中处理正则,Sun 提供了 java.util.regex
包来在 Java 中更加方便的使用正则。
import java.util.regex.*;
Pattern r = Pattern.compile("^Subject: (.*)", Pattern.CASE_INSENSITIVE);
Matcher m = r.matcher(line);
if (m.find()) {
subject = m.group(1);
}
Perl 隐藏了绝大部分细节, Java 则暴露了一些正则的细节,编译正则表达式到 Pattern 对象,将正则和匹配的文本联系到一起,得到 Matcher 对象,在应用正则之前,检查是否存在匹配,返回结果,如果存在匹配,则捕获括号内的子表达式文本。
Java 也提供了函数式处理的例子, Pattern 类提供了静态方法
if (! Pattern.matches("\\s*", line)) {
// 如果 line 不是空行
}
函数包装一个隐式的正则表达式,返回一个 Boolean。
Sun 也会把正则表达式整合到 Java 的其他部分,比如 String 类中 matches
函数
if (! line.matches("\\s*")) {
// line 不为空行
}
String 中的方法不适合在对时间要求很高的循环中使用。
Python 中的处理,Python 也使用面向对象的方法
import re
r = re.compile("^Subject: (.*)", re.IGNORECASE)
m = r.search(line)
if m:
subject = m.group(1)
这个例子和 Java 中的非常类似。
从左往右匹配,左侧的结果优先于右侧
标准量词 ?, *, +, {m,n}
都是优先匹配 greedy 的。例如 a?
中的 a
,[0-9]+
中的 [0-9]
,在匹配成功之前,进行尝试的次数是有上限和下限的,规则 2 表明,尝试总是获得最长的匹配。
标准匹配量词的结果可能并非所有可能中最长的,但是它们总是尝试匹配尽可能多的字符,直到匹配上限为止。如果最终结果并非该表达式的所有可能中最长的,原因肯定是匹配字符过多导致匹配失败。
举例, \b\w+s\b
来匹配包含 s
的字符串,比如 regexes
,\w+
完全能够匹配整个单词,但如果 \w+
来匹配整个单词 s
就无法匹配,为了完成匹配, \w+
必须匹配 regexes
,最后把 s\b
留出来。
NFA 称为“表达式主导”引擎,对应的 DFA 称为 “文本主导” 引擎。
NFA 引擎中,每一个子表达式都是独立的,子表达式之间不存在内在的联系,子表达式和正则表达式的控制结构(多选分支、括号以及匹配量词)的层次关系控制了整个匹配过程。NFA 引擎是正则表达式主导,编写正则的人有充分的机会来实现期望的结果
DFA 在扫描字符串时,会记录“当前有效”的所有匹配。比如正则
to(nite|knight|night)
来匹配文本
after ... tonight ...
当文本扫描到 t^onight
时,记录可能的匹配 t^o(nite|knight|night)
接下来扫描每一个字符都会更新可能的匹配序列,比如扫描到 toni^ght ...
时,可能的匹配就是 to(ni^te|knight|ni^ght)
。此时 knight 就已经无法匹配。当扫描到 g 时只有一个匹配,等完成 h 和 t 的扫描之后,引擎发线匹配完成,报告成功。
一般情况下,文本主导的 DFA 引擎要快一些,NFA 正则表达式引擎,因为需要对同样的文本尝试不同的表达式匹配,可能会产生不同的分支浪费时间。
NFA 匹配的过程中,目标文本中的某个字符串可能会被正则表达式中不同部分重复检查。相反,DFA 引擎是确定性,目标文本中的每个字符只会检查一遍。
这两种技术,都有对应的正式名字:非确定型有穷自动机 NFA,和 确定型有穷自动机 DFA。
粗略分为三类
部分程序及其所使用的正则引擎
引擎 | 程序 |
---|---|
DFA | 大多数版本的 awk, egrep, flex, lex, MySQL |
传统型 NFA | GNU Emacs, Java, 大多数版本的 grep, less, more, .NET ,Perl, PHP,Python, Ruby, 大多数版本的 sed, vi |
POSIX NFA | mawk, GUN Emacs 明确指定时使用 |
DFA/NFA 混合 | GNU awk, GNU grep/egrep, Tcl |
匹配 IPv4 的地址,用点号分开的四个数组,0-255
[01]?[d\d?|2[0-4][d|25[0-5]
这个表达式能够匹配 0 到 255 之间的数,然后重复 4 遍。这样这个表达式会异常复杂,通常情况下,更合适的做法是不依赖正则完成全部的工作,使用
^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\$
来匹配,然后将匹配的数字拿出来,使用其他程序来进行验证。
匹配括号的内容,或许会想到 \bfoo\([^)])
为了匹配括号
`(.*)` 括号及括号内任何字符
`([^)]*)` 从一个开括号到最近的闭括号
`([^()]*)` 从一个开括号到最近的闭括号,但是不容许其中包含开括号
对于文本
var = foo(bar(this), 3.7) + 2 * (that - 1);
第一个正则会匹配 (bar(this), 3.7) + 2 * (that - 1)
, 而第二个正则表达式只会匹配 (bar(this)
, 而第三个表达式能够匹配 (this)
,但是如果想要匹配 foo 后面的括号,则无能为力,所以三个表达式都不合格。
通过 java.util.regex
使用正则非常简单,一个接口一个 exception
java.util.regex.Pattern
java.util.regex.Matcher
java.util.regex.MatchResult
java.util.regex.PatternSyntaxException
通过 Pattern 构造编译正则表达式,通过正则匹配构建 Matcher 对象。
Java 的正则对性能影响非常大,如果在程序中大量使用正则,一定要对正则进行一定优化。
Pattern.compile()
预先编译正则表达式(X|Y|Z)
会显著降低正则的匹配速度,优先考虑 ac(cd|ef)
而不是 (abcd|abef)
常用的 ping,tracert,nslookup 一般用来判断主机的网络连通性,其实 Linux 下有一个更好用的网络联通性判断工具,它可以结合ping nslookup tracert 来判断网络的相关特性,这个命令就是 mtr。mtr 全称 my traceroute,是一个把 ping 和 traceroute 合并到一个程序的网络诊断工具。
traceroute 默认使用 UDP 数据包探测,而 mtr 默认使用ICMP报文探测,ICMP在某些路由节点的优先级要比其他数据包低,所以测试得到的数据可能低于实际情况。
Debian/Ubuntu/Linux Mint 下
sudo apt install mtr-tiny
sudo apt install mtr # with GUI
macOS 下:
brew install mtr
简单使用,查看本地到 google.com 的路由连接情况:
mtr google.com
输出参数解释:
使用 mtr -r google.com
来打印报告,如果不使用 -r
or --report
参数 mtr 会不断动态运行。使用 report 选项, mtr 会向 google.com 主机发送 10 个 ICMP 包,然后直接输出结果。通常情况下 mtr 需要几秒钟时间来输出报告。mtr 报告由一系列跳数组成,每一跳意味着数据包通过节点或者路由器来达到目的主机。
一般情况下 mtr 前几跳都是本地 ISP,后几跳属于服务商比如 Google 数据中心,中间跳数则是中间节点,如果发现前几跳异常,需要联系本地 ISP 服务提供上,相反如果后几跳出现问题,则需要联系服务提供商,中间几跳出现问题,则两边无法完全解决问题。
使用 -s
来指定ping数据包的大小
mtr -s 100
100 bytes 数据包会用来发送,测试,如果设置为负数,则每一次发送的数据包的大小都会是一个随机数。
默认使用 -r
参数来生成报告,只会发送10个数据包,如果想要自定义数据包数量,可以使用 -c
参数
mtr -c 100 google.com
使用 -n
选项来让 mtr 只输出 IP,而不对主机 host name 进行解释
mtr -n github.com
查看路由:
mtr -rn 1.1.1.1
在晚上或者 VPS 交流的时候经常能看到别人用可视化的方式展示路由跳转,其实都是使用的 best trace 这样一个软件。
官网地址: https://www.ipip.net/download.html
对于 Windows,Mac 和 Android 页面上都有相应的GUI客户端,Linux 下可使用命令行:
wget http://cdn.ipip.net/17mon/besttrace4linux.zip
unzip besttrace4linux.zip
chmod +x besttrace32
sudo ./besttrace -q 1 www.google.com
如果下载地址失效了,去官网上找最新的即可。
Instagram 的 ID 生成策略是经过精心设计过的。1 每一秒 Instagram 都会收到无数用户上传的照片,在内部使用 [[PostgreSQL]] 分片存储到不同的服务器上。
这就产生了一个问题,要设计一个唯一 ID 生成方法用来标记系统中发布的每一张图片。
系统唯一 ID 需要满足如下条件:
所以最后 ID 的构成:
Instagram 在他的API文档和网站地址栏中对同一个 Post,使用了两种不一样的ID。在他们的 API 文档中,ids 类似于 908540701891980503_1639186
, 但是在网站浏览器中则使用的是 ybyPRoQWzX
这样的ID。如果查看几组 ids,能够确性这两种类型的 ids 之间存在关联,Instagram 内部也不可能为同一个帖子存储两套 ids。
在仔细看一下数字的ID,908540701891980503
显然下划线分割的后面为作者的id,前面18位的id 被转为 ybyPRoQWzX
10 位的长度。URL 中的ID信息的密度显然要比数字的id要高。
如果做一个测试,将数字id由base10转为 base64,就得到
9:0:8:5:4:0:7:0:1:8:9:1:9:8:0:5:0:3_10
50:27:50:15:17:40:16:22:51:23_64
这样两组数据,如果将 base64 的数组和10位的id比较,得到
number | letter |
---|---|
50 | y |
27 | b |
50 | y |
15 | P |
17 | R |
40 | o |
16 | Q |
22 | W |
51 | z |
23 | X |
如果对该映射重新排序,则得到
number | letter |
---|---|
15 | P |
16 | Q |
17 | R |
22 | W |
23 | X |
27 | b |
40 | o |
50 | y |
50 | y |
51 | z |
看到该映射,很容易联想到由 A-Z
和 a-z
的关系
number | letter |
---|---|
00 | A |
01 | B |
02 | C |
03 | D |
07 | H |
08 | I |
09 | J |
10 | K |
11 | L |
13 | N |
15 | P |
16 | Q |
17 | R |
20 | U |
22 | W |
23 | X |
24 | Y |
26 | a |
27 | b |
29 | d |
32 | g |
34 | i |
36 | k |
37 | l |
40 | o |
42 | q |
43 | r |
47 | v |
50 | y |
51 | z |
56 | 4 |
62 | - |
63 | _ |
这个表和标准 base64 的加密表几乎一致,除了将 +
和 /
变为了 -
和 _
。这个变动大概因为 /
在URLs中是特殊字符,而 +
号在 URL 中是特殊的请求字符。
知道了两种ids之间的关系,联系一些已知的事实可以知道
Instagram 在全平台使用唯一的ID,在 Web 端是 https://instagram.com/p/ybyPRoQWzX
, 而这个 ID 能够被转成数字ID,因此我们也知道这个 ID 也是唯一的。
更加惊奇的是数字ID也能够被用于请求 Instagram 内部的接口,比如查看 Instagram 网站的请求,可以发现请求特定用户的 Posts 的接口为
https://instagram.com/<username>/media/?max_id=<numeric id>
比如说 https://instagram.com/gitamba/media/?max_id=915362118751716223_7985735
而事实上,省略了后面的用户ID 也能更能够照样获得结果。而后面加任意内容都会被忽略
https://instagram.com/gitamba/media/?max_id=915398248830305252_whatever
这个请求的到内容和之前的内容一样。
如果知道 Instagram 产生 posts 的唯一ID 的原理 那么可以从 ID 中获取更多信息。在 Instagram 的文章中,没有提及内部的 epoch 是什么时候,但是可以通过计算来获取一个比较准确的值。
post id (base 10) | known post created time (unix time) |
---|---|
908540701891980503 | 1422526513s |
936303077400215759 | 1425836046s |
188449103402467464 | 1336684905s |
上面是三个已知发布时间的IDs, post 的id 是64bit ,如果转成 64 bits
post id (base 10) | post id (base 2) |
---|---|
908540701891980503 | 0000110010011011110010001111010001101000010000010110110011010111 |
936303077400215759 | 0000110011111110011010101011000000101010100010111000000011001111 |
188449103402467464 | 0000001010011101100000010111011000001010100010111000000010001000 |
截取前41bits,表示的从 Instagram 内部 epoch 开始流逝的时间,毫秒
first 41 bits of post id | time since Instagram epoch |
---|---|
00001100100110111100100011110100011010000 | 108306491600ms |
00001100111111100110101010110000001010101 | 111616024661ms |
00000010100111011000000101110110000010101 | 22464883733ms |
最后在用创建post 的时间 unix time 来减去从 id 中获取的时间,得到
created time | time since Instagram epoch | Instagram epoch (unix time) |
---|---|---|
1422526513s | 108306491600ms | 1314220021.400s |
1425836046s | 111616024661ms | 1314220021.339s |
1336684905s | 22464883733ms | 1314220021.267s |
如果计算一切都正确,得到的值应该是相等的。微小的误差来自,创建 post 的时间是秒,而 id中获取的时间是毫秒。
经过一系列计算,大致可以知道 Instagram epoch 时间开始于 1314220021 ,也就是 9:07pm UTC on Wednesday, August 24, 2011 看起来是一个随机的 epoch,但猜测可能是 Instagram 内部将 id 由自增长模式转变为现在模式的时间。
id in base 10 | id in base64 | converted to chars |
---|---|---|
936303077400215759 | 51:62:26:43:00:42:34:56:03:15 | z-arAqi4DP |
908540701891980503 | 50:27:50:15:17:40:16:22:51:23 | ybyPRoQWzX |
283455759575646671 | 15:47:02:24:11:51:02:56:07:15 | PvCYLzC4HP |
205004325645942831 | 11:24:20:37:36:23:34:56:00:47 | LYUlkXi4Av |
188449103402467464 | 10:29:32:23:24:10:34:56:02:08 | KdgXYKi4CI |
409960495 | 24:27:56:00:47 | Yb4Av |
167566273 | 09:63:13:47:01 | J_NvB |
iperf 命令是一个网络性能测试工具。iperf 可以测试 TCP 和 UDP 带宽质量。iperf 可以测量最大 TCP 带宽,具有多种参数和 UDP 特性。iperf 可以报告带宽,延迟抖动和数据包丢失。利用 iperf 这一特性,可以用来测试一些网络设备如路由器,防火墙,交换机等的性能。
iperf 在内存中运行,不会涉及到文件系统。iperf 存在非常多的版本,Windows,Linux,Android,iOS 都有对应的版本。可以到官网下载
Linux 下安装:
sudo apt-get install iperf
其他系统到参考官网
iperf 是一个命令行工具,当然如果搜索也能发现 GUI 的工具,比如 jperf, xjerf 等等。不过还是推荐使用命令行。
iperf 使用非常简单的 C/S 架构,client 使用 -c
参数,服务端使用 -s
。
宽带测试通常采用 UDP 模式,首先以链路理论带宽作为数据发送速率进行测试,从客户端到服务器之间的链路理论带宽为 100 Mbps,先使用 -b 100M
测试,然后根据测试结果,以实际带宽测试
服务端:
iperf -u -s # UDP 模式
客户端第一种模式
iperf -u -c server_address -b 100M -t 60
在 UDP 模式下,以 100Mbps 为数据发送速率,客户端到 IP 上传带宽测试,测试时间 60 秒。
客户端同时发起 30 个线程连接,以 5Mbps 为数据发送速率
iperf -u -c server_address -b 5M -P 30 -t 60
或者客户端直接进行上下行带宽测试
iperf -u -c server_address -b 100M -d -t 60
如果不加 -u
则使用 TCP 模式
iperf -s
客户端持续 60 秒
iperf -c server_address -t 60
使用 -p port
来指定服务端端口,默认是 5201
使用 -i interval
参数来表示间隔时间
iperf -c 192.168.2.105(server address) -i 2
表示间隔 2 秒打印结果
iperf 自身存在很多个版本,Ubuntu 默认源中可能有一个叫做 iperf, 也有一个叫做 iperf3, 这两个是不同版本的 iperf, 在使用 iperf 的时候要确保版本一致。不同的版本有不同的架构和不同的特性。
然而这两个版本 iperf3 是不兼容 iperf 2.x 的。可以这么理解 iperf 是内存中的网络性能测试工具,而 iperf3 是从头还是编写的一套新程序,目标是简化代码量,并且设计为工具库,可以嵌入到其他工具中。1
两者不同的区别
服务端:
功能 | iperf | iperf3 |
---|---|---|
使用默认端口 | iperf -s | iperf3 -s |
守护模式下,使用 TCP window | iperf -s -w 32M -D | iperf3 -s -D |
Start UDP server on port 5003, and give 1 sec interval reports. Note that for iperf3 the -u option is passed to the server from the client. | iperf -i1 -u -s -p 5003 | iperf3 -s -p 5003 |
客户端:
功能 | iperf / iperf3 |
---|---|
执行 30 秒测试,每秒发送结果 | iperf/iperf3 -c server_address -i 1 -t 30 |
执行逆向从服务端到本地测试 | iperf/iperf3 -c server_address -i 1 -t 10 -R |
使用 4 个并发,32M TCP buffer | iperf/iperf3 -c server_address -i 1 -t 20 -w 32M -P 4 |
测试 200Mbps UDP | iperf/iperf3 -c server_address -u -i 1 -b 200M |
iperf3 增加了一些额外的功能,比如 -i
模式可以提供 TCP retransit (中继)结果,并且 verbose 模式可以打印更多有效的信息,比如 CPU 利用率等等。
更多的使用可以参考这里
更多 iperf 2.0.5 和 iperf 2.0.8+ 和 iperf 3.1.5+ 的区别可以在这里 查看。
使用
在 Android 手机上可以使用一款叫做 Magic iPerf 的工具来进行测试。
或者使用 Termux 来直接使用命令测试。
MD5 全称为 Message-Digest Algorithm ,一般用来保证信息传输的完整一致,MD5 的结果是固定长度 128bit 。MD5 算法最开始是为密码学而设计,但是后来被发现有很大的缺陷,所以后来就用来校验文件的完整性(无意识而造成的不完整,比如下载)。MD5 在非密码学领域也有其他用途,比如在一个数据库中快速检查一个 key 是否存在。
密码学的 hash 方法一个最基本的要求是,计算机难以通过计算来找到两个不同的内容拥有同一个 hash 值,MD5 无法满足该基本条件。MD5 算法无法防止碰撞,无法用于安全性验证,对于安全性高的资料,建议使用其他算法。
MD5 是 Ronald Rivest 在 1991 年设计用来代替 MD4, 具体的说明定义在 RFC 1321 中。
MD5 的输入不定长,输出定长 128 bits。MD5 计算方式可以分为几个步骤:
再得到 N*512 个数据组之后,初始 A B C D 四个 32 bit 长度的变量,里面依次存放十六进制
A: 01 23 45 67 B: 89 ab cd ef C: fe dc ba 98 D: 76 54 32 10
接下来定义四个线性函数,每一个方法接受三个 32 位的 word 然后产生一个 32 位的 word
F(X,Y,Z) = (XY) | (not(X) Z) if X then Y else Z G(X,Y,Z) = (XZ) | (Y not(Z)) if Z then X else Y H(X,Y,Z) = X xor Y xor Z I(X,Y,Z) = Y xor (X | not(Z))
伪代码:
主要分成这几部分,先针对第一个分组,处理 16 个子分组,通过初始 A B C D 计算得到新的 A B C D,然后将新的 A B C D 加到原来的 A B C D 中。如此往复直到计算完所有的分组,最终得到的 ABCD 就是 MD5 的值。
/* Process each 16-word block. */
For i = 0 to N/16-1 do
/* Copy block i into X. */
For j = 0 to 15 do
Set X[j] to M[i*16+j].
end /* of loop on j */
/* 另存一份 */
AA = A
BB = B
CC = C
DD = D
/* Round 1. */
/* Let [abcd k s i] denote the operation
a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
/* Do the following 16 operations. */
[ABCD 0 7 1] [DABC 1 12 2] [CDAB 2 17 3] [BCDA 3 22 4]
[ABCD 4 7 5] [DABC 5 12 6] [CDAB 6 17 7] [BCDA 7 22 8]
[ABCD 8 7 9] [DABC 9 12 10] [CDAB 10 17 11] [BCDA 11 22 12]
[ABCD 12 7 13] [DABC 13 12 14] [CDAB 14 17 15] [BCDA 15 22 16]
/* Round 2. */
/* Let [abcd k s i] denote the operation
a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
/* Do the following 16 operations. */
[ABCD 1 5 17] [DABC 6 9 18] [CDAB 11 14 19] [BCDA 0 20 20]
[ABCD 5 5 21] [DABC 10 9 22] [CDAB 15 14 23] [BCDA 4 20 24]
[ABCD 9 5 25] [DABC 14 9 26] [CDAB 3 14 27] [BCDA 8 20 28]
[ABCD 13 5 29] [DABC 2 9 30] [CDAB 7 14 31] [BCDA 12 20 32]
/* Round 3. */
/* Let [abcd k s t] denote the operation
a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
/* Do the following 16 operations. */
[ABCD 5 4 33] [DABC 8 11 34] [CDAB 11 16 35] [BCDA 14 23 36]
[ABCD 1 4 37] [DABC 4 11 38] [CDAB 7 16 39] [BCDA 10 23 40]
[ABCD 13 4 41] [DABC 0 11 42] [CDAB 3 16 43] [BCDA 6 23 44]
[ABCD 9 4 45] [DABC 12 11 46] [CDAB 15 16 47] [BCDA 2 23 48]
/* Round 4. */
/* Let [abcd k s t] denote the operation
a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
/* Do the following 16 operations. */
[ABCD 0 6 49] [DABC 7 10 50] [CDAB 14 15 51] [BCDA 5 21 52]
[ABCD 12 6 53] [DABC 3 10 54] [CDAB 10 15 55] [BCDA 1 21 56]
[ABCD 8 6 57] [DABC 15 10 58] [CDAB 6 15 59] [BCDA 13 21 60]
[ABCD 4 6 61] [DABC 11 10 62] [CDAB 2 15 63] [BCDA 9 21 64]
/* Then perform the following additions. (That is increment each
of the four registers by the value it had before this block
was started.) */
A = A + AA
B = B + BB
C = C + CC
D = D + DD
end /* of loop on i */
最后每一轮更新之后进入下一轮更新,最后会在 ABCD 中保存 4 * 32 位最后 Hash 的结果。
128 位的 MD5 一般表示为 32 位的十六进制数。
CyanogenMod 中有一段检查文件 MD5 的代码:
/*
* Copyright (C) 2012 The CyanogenMod Project
*
* * Licensed under the GNU GPLv2 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl-2.0.txt
*/
package com.cyanogenmod.updater.utils;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5 {
private static final String TAG = "MD5";
public static boolean checkMD5(String md5, File updateFile) {
if (TextUtils.isEmpty(md5) || updateFile == null) {
Log.e(TAG, "MD5 string empty or updateFile null");
return false;
}
String calculatedDigest = calculateMD5(updateFile);
if (calculatedDigest == null) {
Log.e(TAG, "calculatedDigest null");
return false;
}
Log.v(TAG, "Calculated digest: " + calculatedDigest);
Log.v(TAG, "Provided digest: " + md5);
return calculatedDigest.equalsIgnoreCase(md5);
}
public static String calculateMD5(File updateFile) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "Exception while getting digest", e);
return null;
}
InputStream is;
try {
is = new FileInputStream(updateFile);
} catch (FileNotFoundException e) {
Log.e(TAG, "Exception while getting FileInputStream", e);
return null;
}
byte[] buffer = new byte[8192];
int read;
try {
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
String output = bigInt.toString(16);
// Fill to 32 chars
output = String.format("%32s", output).replace(' ', '0');
return output;
} catch (IOException e) {
throw new RuntimeException("Unable to process file for MD5", e);
} finally {
try {
is.close();
} catch (IOException e) {
Log.e(TAG, "Exception on closing MD5 input stream", e);
}
}
}
}
nodejs wrapper: https://github.com/Level/levelup
在分布式系统存在多个 Shard 的场景中, 同时在各个 Shard 插入数据时, 怎么给这些数据生成全局的 unique ID? 在单机系统中 (例如一个 MySQL 实例), unique ID 的生成是非常简单的, 直接利用 MySQL 自带的自增 ID 功能就可以实现.
但在一个存在多个 Shards 的分布式系统 (例如多个 MySQL 实例组成一个集群, 在这个集群中插入数据), 这个问题会变得复杂, 所生成的全局的 unique ID 要满足以下需求:
在要满足前面 6 点要求的场景中, 怎么来生成全局 unique ID 呢?
数据库单表,使用 auto increment
来生成唯一全局递增ID。
优势是无需额外附加操作,定长增长,单表结构中唯一性,劣势是高并发下性能不佳,生产的上限是数据库服务器单机的上限,水平扩展困难,分布式数据库下,无法保证唯一性。
如果没有上面这些限制, 问题会相对简单, 例如: 直接利用 UUID.randomUUID() 接口来生成 unique ID (http://www.ietf.org/rfc/rfc4122.txt). 但这个方案生成的 ID 有 128 bits, 另外, 生成的 ID 中也没有带 Timestamp 一般编程语言中自带 UUID 实现, Java 中 UUID.randomUUID().toString()
产生的ID 不依赖数据库实现。
优势是,本地生成ID,无需远程调用,全局唯一,水平扩展能力好。劣势是,ID 有 128 bits 长,占空间大,生成字符串类型,索引效率低,生成的 ID 中没有带 Timestamp 无法保证时间递增。
Flickr 的做法1 是使用 MySQL 的自增ID, 和 replace into 语法。但他这个方案 ID 中没有带 Timestamp, 生成的 ID 不能按时间排序
创建64位自增ID,首先创建表
CREATE TABLE `Tickets64` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`stub` char(1) NOT NULL default '',
PRIMARY KEY (`id`),
UNIQUE KEY `stub` (`stub`)
) ENGINE=MyISAM
SELECT * from Tickets64
假设表中有一行
+-------------------+------+
| id | stub |
+-------------------+------+
| 72157623227190423 | a |
+-------------------+------+
那么如果需要产生一个新的全局 64 bits 的ID,只要执行 SQL:
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
SQL 返回的ID就是要产生的全局唯一ID。使用 REPLACE INTO
代替 INSERT INTO
的好处是避免表行数太多。 stub 要设为唯一索引。
Flickr 内部运行两台 ticket servers,通过两台机器做主备和负载均衡。
TicketServer1:
auto-increment-increment = 2
auto-increment-offset = 1
TicketServer2:
auto-increment-increment = 2
auto-increment-offset = 2
Twitter 利用 Zookeeper 实现一个全局的 ID 生成服务 Snowflake: https://github.com/twitter/snowflake
Snowflake 生成的 unique ID 的组成 (由高位到低位):
一共 63 bits ,其中最高位是 0
unique ID 生成过程:
41 bits 的 Timestamp: 每次要生成一个新 ID 的时候, 都会获取一下当前的 Timestamp, 然后分两种情况生成 sequence number:
- 如果当前的 Timestamp 和前一个已生成 ID 的 Timestamp 相同 (在同一毫秒中), 就用前一个 ID 的 sequence number + 1 作为新的 sequence number (12 bits); 如果本毫秒内的所有 ID 用完, 等到下一毫秒继续 (**这个等待过程中, 不能分配出新的 ID**)
- 如果当前的 Timestamp 比前一个 ID 的 Timestamp 大, 随机生成一个初始 sequence number (12 bits) 作为本毫秒内的第一个 sequence number
10 bits 的机器号, 在 ID 分配 Worker 启动的时候, 从一个 Zookeeper 集群获取 (保证所有的 Worker 不会有重复的机器号)
整个过程中, 只有在 Worker 启动的时候会对外部有依赖 (需要从 Zookeeper 获取 Worker 号), 之后就可以独立工作了, 做到了去中心化.
异常情况讨论:
在获取当前 Timestamp 时, 如果获取到的时间戳比前一个已生成 ID 的 Timestamp 还要小怎么办? Snowflake 的做法是继续获取当前机器的时间, 直到获取到更大的 Timestamp 才能继续工作 (在这个等待过程中, 不能分配出新的 ID)
从这个异常情况可以看出, 如果 Snowflake 所运行的那些机器时钟有大的偏差时, 整个 Snowflake 系统不能正常工作 (偏差得越多, 分配新 ID 时等待的时间越久)
从 Snowflake 的官方文档 (https://github.com/twitter/snowflake/#system-clock-dependency) 中也可以看到, 它明确要求 “You should use NTP to keep your system clock accurate”. 而且最好把 NTP 配置成不会向后调整的模式. 也就是说, NTP 纠正时间时, 不会向后回拨机器时钟.
下面是 Snowflake 的其他变种, Instagram 产生 ID 的方法也借鉴 Snowflake
代码地址:https://github.com/boundary/flake
变化:
ID 长度扩展到 128 bits:
由于它用 48 bits 作为 Worker ID, 和 Mac 地址的长度一样, 这样启动时不需要和 Zookeeper 通讯获取 Worker ID. 做到了完全的去中心化
基于 Erlang ,这样做的目的是用更多的 bits 实现更小的冲突概率,这样就支持更多的 Worker 同时工作。同时, 每毫秒能分配出更多的 ID。
源代码:https://github.com/SawdustSoftware/simpleflake
Simpleflake 的思路是取消 Worker 号, 保留 41 bits 的 Timestamp, 同时把 sequence number 扩展到 22 bits;
Simpleflake 的特点:
Simpleflake 的问题就是 sequence number 完全随机生成, 会导致生成的 ID 重复的可能. 这个生成 ID 重复的概率随着每秒生成的 ID 数的增长而增长.
所以, Simpleflake 的限制就是每秒生成的 ID 不能太多 (最好小于 100次/秒, 如果大于 100次/秒的场景, Simpleflake 就不适用了, 建议切换回 Snowflake).
Instagram 参考 Flickr 的方案,结合 Twitter 的经验,利用 PostgreSQL 数据库的特性,实现了一个更加简单可靠的 ID 生成服务。 Instagram 的分布式存储方案: 把每个 Table 划分为多个逻辑分片 (logic Shard), 逻辑分片的数量可以很大, 例如 2000 个逻辑分片。然后制定一个规则, 规定每个逻辑分片被存储到哪个数据库实例上面; 数据库实例不需要很多. 例如, 对有 2 个 PostgreSQL 实例的系统 (instagram 使用 PostgreSQL); 可以使用奇数逻辑分片存放到第一个数据库实例, 偶数逻辑分片存放到第二个数据库实例的规则
每个 Table 指定一个字段作为分片字段 (例如, 对用户表, 可以指定 uid 作为分片字段)
插入一个新的数据时, 先根据分片字段的值, 决定数据被分配到哪个逻辑分片 (logic Shard) 然后再根据 logic Shard 和 PostgreSQL 实例的对应关系, 确定这条数据应该被存放到哪台 PostgreSQL 实例上
Instagram 在设计ID时考虑了如下因素:
Instagram unique ID 的组成:
假设2011年9月9号下午 5 点钟, epoch 开始于 2011 年 1 月1 号,那么已经有 1387263000 毫秒经过,那么前 41 bits 是
id = 1387263000 <<(64-41)
接下来13位由分片ID决定,假设按照 user ID 来分片,有 2000 个逻辑分片,如果用户的ID 是 31341 , 那么分片 ID 是 31341%2000 -> 1341
,所以接下来的13位是:
id |= 1341 <<(63-41-13)
最后,每个表自增来填补剩下的 bits,假设已经为表生成了 5000 个 IDs,那么下一个值是 5001,然后取模 1024
id |= (5001 % 1024)
sequence number 利用 PostgreSQL 每个 Table 上的 auto-increment sequence 来生成。如果当前表上已经有 5000 条记录, 那么这个表的下一个 auto-increment sequence 就是 5001 (直接调用 PL/PGSQL 提供的方法可以获取到) 然后把 这个 5001 对 1024 取模就得到了 10 bits 的 sequence number。
instagram 这个方案的优势在于:
利用 logic Shard 号来替换 Snowflake 使用的 Worker 号, 就不需要到中心节点获取 Worker 号了. 做到了完全去中心化
另外一个附带的好处就是, 可以通过 ID 直接知道这条记录被存放在哪个 logic Shard 上。同时, 今后做数据迁移的时候, 也是按 logic Shard 为单位做数据迁移的, 所以这种做法也不会影响到今后的数据迁移
MongoDB 的 ObjectID2 采用 12 个字节的长度,将时间戳编码在内。
http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/ ↩
https://docs.mongodb.com/manual/reference/method/ObjectId/#objectid ↩
Flower 是一个基于 Web 的监控和管理工具,可以用在 Celery 集群的监控和管理。和 Celery 配合使用非常不错。
Flower 可以查看 Celery 队列中 task 的数量,可以用来监控 worker 的状态并进行简单的管理,比如调整 worker 的 pool size 和 autoscale 设置,可以用来查看当前处理的 tasks, 等等。
使用 pip 安装 Flower
pip install flower
或者安装开发版
pip install https://github.com/mher/flower/zipball/master
直接使用下面命令开启本地监听 http://localhost:5555
flower -A proj --port=5555
或者从 Celery 中开启
celery flower -A proj --address=127.0.0.1 --port=5555
如果要暴露给外网访问可以 --address=0.0.0.0
:
celery flower -A worker --address=0.0.0.0 --port=5555 --basic_auth=name:password
使用 --basic_auth
来开启 HTTP 简单验证
zip 是一个非常常见的压缩工具,很多平台包括 Unix, VMS, MSDOS, OS/2, Windows 9x/NT/XP, Minix, Atari, Macintosh, Amiga, and Acorn RISC OS 等都有应用。zip 结合了打包和压缩。
参数非常多
zip options archive inpath infile
zip [-aABcdDeEfFghjklLmoqrRSTuvVwXyz!@$] [--longoption ...] [-b path] [-n suffixes] [-t date] [-tt date] [zipfile [file ...]] [-xi list]
-h
帮助-d
把压缩文件解到指定目录下-m
将文件压缩后,删除源文件-q
安静模式,压缩时不显示执行的执行过程-r
将执行的目录下所有子目录及文件一同处理-S
包含系统文件和隐藏文件-n
不覆盖已有的文件-o
覆盖已存在的文件且不要求用户确认-x
文件列表 解压缩文件,但不包括指定的 file 文件-v
查看压缩文件目录,但不解压-t
测试文件有无损坏,但不解压-j
不重建文档的目录结构,把所有文件解压到同一目录下将文件及文件夹打包到 dest.zip
中。
zip -r dest.zip path/to/folder file
zip dest.zip file1 file2
压缩时排除某些目录
zip -r dest.zip folder -x *.git*
使用密码压缩文件
zip -r -P password dest.zip file folder
解压文件
unzip dest.zip
解压文件到特定文件夹 -d
参数指定
unzip dest.zip -d /path/to/folder
不解压查看压缩包内部内容
unzip -l dest.zip
unzip -v dest.zip
在 Linux 下经常遇到 zip 乱码的情况呢,其实因为 Windows 或者其他系统下生成默认使用 GBK/GB2312 编码,而在 Linux 下默认为 UTF8,所以可以使用 unzip 的 -O
参数,这个参数 只有 unzip 1.6
及以上版本才有。
unzip -O cp936 file.zip -d /path/to/folder
Linux 下经常遇到 tar.gz tar.bz2 这样的压缩包,这个时候可能就需要使用到 tar 这个命令。
下面是一次 vim 的组内介绍的大纲。记录一下。
大纲
Normal mode
Insert mode
Visual mode
Command mode
i 进入 insert mode,在光标为之前进入插入模式
I 行首非空字符前插入 , I 等同于 `^i`
s 删除光标下字符,并进入 insert mode, 等同于 `cl`
S 删除光标所在一行,并进入 insert mode 行首 , 等同于 `^C`
a 光标之后进入 insert mode
A 光标移动到行尾并进入 insert mode , 等同于 `$a`
o 在光标下一行插入一行,并进入 insert mode , 等同于 `A<CR>`
O 在光标上一行插入新行,并进入 insert mode , O 等同于 `ko`
C 删除光标后到行尾内容,并进入 insert mode , C 其实等同于 `c$`
cc 删除行,并进入插入模式
Esc 退出 Insert 模式
Example: this is a text for testing mode.
h,j,k,l
k
^
h < > l
v
j
H,M,L
w,W,e,E,b,B
%
0,^,$
gg, G
fx,Fx,tx,Tx,
'', g; move cursor to last edit change
Example: This is a sentence for testing moving with a word and WO-RD test.
p 光标之后粘贴 (p)aste
P paste before the cursor
yy 复制当前行
y Yank 复制。Example: yw (yank word) 光标停留到词第一个字母上 yw 复制单词
y0 copy the data from cursor to begining of the line
y$ copy the data from cursor to end of the line
x 删除光标下单个字符,将其放到粘贴板,剪切
X 向前删除一个字符,相当于 Backspace
dd 删除光标所在一行,并把该行复制
dw 删除光标所在词 (d)elete (w)ord
d0 删除光标到该行最前
d^ 删除光标到行首非空白字符
d$ 删除光标到该行最后
J 删除光标所在行的换行符
r,R 替换
Operator + Motion = Action
dw delete word
da' delete what in ' and include '
Example:
This is an 'example' for "word-to-delete".
add quote to this "word"
/pattern - 正向搜索,从光标处开始向文件末搜索
?pattern - 反向搜索,从光标处开始向文件首搜索
n - 下一个,往下执行搜索命令
N - 上一个
* - Word under cursor - forward (bounded)
# - Word under cursor - backward (bounded)
:n1, n2s/p1/p2/g - 将第 n1 至 n2 行中所有 p1 均用 p2 替代
:g/p1/s//p2/g - 将文件中所有 p1 均用 p2 替换
:s/p1/p2/g - 将当前行中所有 p1 均用 p2 替代
:%s/old/new/g 全局替换
:'<,'>s/old/new/g 选中之后替换
:g/^$/d 删除所有空行
Example:
dit, di', di", di), di}, di],
yi), yi]
vi', vit
ciw, ciw)
qw 开启 record macro, 保存到寄存器 w
q 结束录制
Example:
int a = 1
int b = 2
int c = a+b
print a
print b
print c
共有 9 大类寄存器
寄存器访问 “a 寄存器前加 “
其他操作
:reg a 查看寄存器
"" noname buffer last dcsxy
"_ blackhole register
"% filename register
"/ last search register
": last command
insert mode <C-r> a insert text in register a
"ap Normal mode paste text in register a
surround.vim
cs"' change surround " to '
cs'<q> change surround ' to <q>
ds' delete surround '
ysiw" add " to word
yss) add ) to entire line
Example:
"Hello world!" and other strings
vimutor
更快的编码效率