404频道

学习笔记

限流的方式有多种,每种都有其应用场景。

限制请求的方式包括:

  1. 丢弃请求
  2. 放在队列中,等有令牌后再请求
  3. 走降级逻辑

计数器

我之前设计的流控系统,以每秒为单位,如果一秒内超过固定的QPS,则将请求进行降级处理。该算法已经在生产环境中平稳运行了很久,也确实满足了业务的需求。

计数器流控算法简单粗暴,有一个缺点,即流控的单位为秒,但一秒的请求很可能是不均匀的,不能进行更细粒度的控制,也不允许流量存在某种程度的突发。

漏桶算法

请求先进入漏桶中,漏桶以一定的速度出水,当水流的速度过大时会直接溢出。

漏桶大小:起到缓冲的作用

漏桶的出水速度:该值固定

令牌桶算法

令牌桶算法相比漏桶算法而言,允许请求存在某种程度的突发,常用于网络流量整形和速率限制。

系统会恒定的速度往令牌桶中注入令牌,如果令牌桶中的令牌满后就不再增加。新请求来临时,会拿走一个令牌,如果没有令牌就会限制该请求。

这里的请求可以代表一个网络请求,或者网络的一个字节。

涉及到的变量:

  1. 网络请求平均速率r:每隔1/r秒向令牌桶中放入一个令牌,1秒共放入r个令牌
  2. 令牌桶的最大大小:令牌桶慢后,再放入的令牌会直接丢弃

令牌相当于操作系统中信号量机制。

业界较为出名的流控工具当属Guava中的RateLimiter,基于令牌桶算法实现。

在实际的代码实现中,并不一定需要一个固定的线程来定期往令牌桶中放入令牌,而是在请求到来时,直接计算得出当前是否还有令牌。比如下面的python代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time


class TokenBucket(object):

# rate是令牌发放速度,capacity是桶的大小
def __init__(self, rate, capacity):
self._rate = rate
self._capacity = capacity
self._current_amount = 0
self._last_consume_time = int(time.time())

# token_amount是发送数据需要的令牌数
def consume(self, token_amount):
increment = (int(time.time()) - self._last_consume_time) * self._rate # 计算从上次发送到这次发送,新发放的令牌数量
self._current_amount = min(
increment + self._current_amount, self._capacity) # 令牌数量不能超过桶的容量
if token_amount > self._current_amount: # 如果没有足够的令牌,则不能发送数据
return False
self._last_consume_time = int(time.time())
self._current_amount -= token_amount
return True

ref

15行Python代码,帮你理解令牌桶算法

iowait和load一样,都是非常容易让人产生误解的系统指标。

iowait表示cpu空闲且有未完成的io请求的时间,iowait高并不能反映出磁盘是系统的性能瓶颈。iowait高的时候cpu正处于空闲状态,没有任务可以执行。此时存在已经发出的磁盘io,此时的cpu空闲状态称之为iowait。本质上,iowait是一种特殊的cpu空闲状态。

iowait状态的cpu是运行在pid为0的idle线程上。

cpu此时之所以进入睡眠状态,是因为进程处于睡眠状态,在等待某个特定的事件(比如网络数据,io操作完成等)。

iowait仅能反应磁盘io的指标,并不能反应其他io设备的指标,比如网络丢包。

在io wait的进程处于不可中断状态,通过top命令可以看到进程状态为

由此可见,iowait包含的信息量非常少,仅凭iowait升高不能判断出系统io有问题。要想判断系统io有问题,还需要使用iostat等命令来查看系统的svctm、util、avgqu-sz等指标。

case 1

http://linuxperf.com/wp-content/uploads/2015/02/iowait1.png

仅cpu的繁忙程度变化的情况下,会影响到iowait的值。

case 2

http://linuxperf.com/wp-content/uploads/2015/02/iowait.png

在cpu繁忙程序不变的情况下,发起io请求的时间不同也会影响到iowait的值。

seccomp是secure computing mode的缩写,是Linux内核中的一个安全计算工具,机制用于限制应用程序可以使用的系统调用,增加系统的安全性。可以理解为系统调用的防火墙,利用BPF来规律系统调用。

在/proc/${pid}/status文件中的Seccomp字段可以看到进程的Seccomp。

prctl

下面程序使用prctl来设置程序的seccomp为strict模式,仅允许read、write、_exit和sigreturn四个系统调用。当调用未在seccomp白名单中的系统调用后,应用程序会被kill。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>         /* printf */
#include <sys/prctl.h> /* prctl */
#include <linux/seccomp.h> /* seccomp's constants */
#include <unistd.h> /* dup2: just for test */

int main() {
printf("step 1: unrestricted\n");

// Enable filtering
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
printf("step 2: only 'read', 'write', '_exit' and 'sigreturn' syscalls\n");

// Redirect stderr to stdout
dup2(1, 2);
printf("step 3: !! YOU SHOULD NOT SEE ME !!\n");

// Success (well, not so in this case...)
return 0;
}

执行上述程序后会输出如下内容:

1
2
3
step 1: unrestricted
step 2: only 'read', 'write', '_exit' and 'sigreturn' syscalls
Killed

基于BPF的seccomp

上述基于prctl系统调用的seccomp机制不够灵活,在linux 3.5之后引入了基于BPF的可定制的系统调用过滤功能。

需要先安装依赖包:yum install libseccomp-dev

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
33
34
35
36
37
#include <stdio.h>   /* printf */
#include <unistd.h> /* dup2: just for test */
#include <seccomp.h> /* libseccomp */

int main() {
printf("step 1: unrestricted\n");

// Init the filter
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_KILL); // default action: kill

// setup basic whitelist
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);

// setup our rule
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup2), 2,
SCMP_A0(SCMP_CMP_EQ, 1),
SCMP_A1(SCMP_CMP_EQ, 2));

// build and load the filter
seccomp_load(ctx);
printf("step 2: only 'write' and dup2(1, 2) syscalls\n");

// Redirect stderr to stdout
dup2(1, 2);
printf("step 3: stderr redirected to stdout\n");

// Duplicate stderr to arbitrary fd
dup2(2, 42);
printf("step 4: !! YOU SHOULD NOT SEE ME !!\n");

// Success (well, not so in this case...)
return 0;
}

输入如下内容:

1
2
3
4
step 1: unrestricted
step 2: only 'write' and dup2(1, 2) syscalls
step 3: stderr redirected to stdout
Bad system call

docker中的应用

通过如下方式可以查看docker是否启用seccomp:

1
2
# docker info --format "{{ .SecurityOptions }}"
[name=seccomp,profile=default]

docker每个容器默认都设置了一个seccomp profile,启用的系统调用可以从default.json中看到。

docker会将seccomp传递给runc中的sepc.linux.seccomp。

可以通过—security-opt seccomp=xxx来设置docker的seccomp策略,xxx为json格式的文件,其中定义了seccomp规则。

也可以通过--security-opt seccomp=unconfined来关闭docker引入默认的seccomp规则的限制。

ref

题图为金山岭长城,明代著名抗倭名将戚继光从南方调任至此修筑,为明长城之精华,

资源

1.GoAccess

http://rt.goaccess.io/?20180926071813&ref=hpimg

一款开源的实时分析nginx日志的工具,并拥有一个比较强大的dashboard。

2.Wayne

https://raw.githubusercontent.com/wiki/Qihoo360/wayne/image/dashboard-ui.png

360开源的kubernetes的多集群管理平台。

3.MacKey

一个分享KeyNote模版的网站,每个KeyNote模版都带有动画和图片截图。

4.Nomad

Hashicorp公司开源的集群调度工具,该公司另一款较为出名的产品为Vagrant。

5.registrator

该服务部署在宿主机上,自动将docker的容器注册到服务注册中心中,如consul、etcd等。

6.CNI-Genie

华为开源的容器网络解决方案,CNI(Container Network Interface)仅支持加载一个插件,该插件可以同时一次加载多个网络插件,在容器中可以同时存在多个网络解决方案的ip。

7.stress-ng

Linux下有一个命令行的压测测试工具stress,可以用来测试cpu、内存、io等,stress-ng提供了更丰富的选项。

8.Resilience4j

java版的开源熔断工具Hystrix宣布停止开发,并推荐了Resilience4j工具,该工具灵感来自于Hystrix,主要为java 8和函数式编程设计的自动熔断工具。

9.Standard Go Project Layout

我刚开始写go的时候,一度被golang的源码目录结构所困惑,这个项目提供了一个标准的goalng目录结构的用法,很多开源项目都是按照这个标准组织的。

10.dive

https://github.com/wagoodman/dive/blob/master/.data/demo.gif

docker images不是一个单独的文件存储在宿主机上,而是采用分层设计,以便于多个镜像之间复用相同的层数据。dive可以用来分析docker image的每一层的具体组成。

11.Swoole

php号称是世界上最好的编程语言之一,但最为人诟病的是其网络模型是同步模型,导致其性能一直上不去。Swoole可以实现类似于Golang中的goroutine同步编程模型来实现异步的功能。

精彩文章

1.知乎社区核心业务 Golang 化实践

本文记录了知乎内部使用golang来重构python的实践经验,用来解决python编程语言的运行效率低和维护成本高的问题。

2.如何在Docker内部使用gdb调试器

本文记录了一些docker关于权限相关的技术实现。

3.ofo剧中人:我不愿谢幕

以记者的角度记录了OFO的发家、辉煌、衰败,曾有过彷徨与迷茫,曾有过野性与嚣张,但最终还是要倒在资本面前。

大家都在吐槽OFO押金退不了的事情,看到一个评论中的不错的点子,可以在OFO的退押金页面增加广告位,毕竟流量就是金钱,退押金页面的流量也是流量,反正押金也退不了,不如借此来一波,至少比在公众号中卖蜂蜜要好的多。

一个生动的细节是,有黑摩的司机不爽共享单车影响他们生意,砸ofo的车。ofo后期转化了一批相当数量的司机当修车师傅,化干戈为玉帛。

上述操作还是非常犀利的,说白了还是利益在作怪。

一个正常的tcp server在处理请求时会经过如下的系统调用:socket() bind() listen() accept() read() write() close()。一个请求在被应用程序读取之前,可能处于SYN_RCVD和ESTABLISHED两种状态。

第一个队列:SYN_RCVD状态是server端接收到了client端的SYN包,server端会将该连接放到半连接队列中,并向客户端发送SYN+ACK包,此时连接处于半连接状态。通常该队列被称为半连接队列。

第二个队列:ESTABLISHED状态为已经完成了三次握手,但是server端的应用程序还未调用accept系统调用的情况。通常该队列被称为全连接队列。

这两种情况下都需要操作系统提供相应队列来保存连接状态。

backlog用来设置这两个队列的最大值,但在不同的操作系统中有不同的含义,下面的说明以linux操作系统为准。

其中第一个维护SYN_RCVD状态的队列使用内核参数net.ipv4.tcp_max_syn_backlog来控制,如果队列超过这一阈值,连接会被拒绝。该值默认为1000.

第二个维护ESTABLISHED状态的队列,该队列的长度由应用程序调用listen系统调用时指定。

内核参数

  1. net.ipv4.tcp_max_syn_backlog
1
2
tcp_max_syn_backlog (integer; default: see below; since Linux 2.2)
The maximum number of queued connection requests which have still not received an acknowledgement from the connecting client. If this number is exceeded, the kernel will begin dropping requests. The default value of 256 is increased to 1024 when the memory present in the system is adequate or greater (>= 128Mb), and reduced to 128 for those systems with very low memory (<= 32Mb). It is recommended that if this needs to be increased above 1024, TCP_SYNQ_HSIZE in include/net/tcp.h be modified to keep TCP_SYNQ_HSIZE*16<=tcp_max_syn_backlog, and the kernel be recompiled.

用来设置 syn 队列的大小,通常也会称为半连接队列。该参数的默认值一般为 1024。如果 syn 队列满,此时 syn 报文会被丢弃,无法回复 syn + ack 报文。可以通过 netstat -s 命令看到 “XX SYNs to LISTEN sockets dropped”. 的报错信息。

  1. net.ipv4.tcp_syncookies
1
tcp_syncookies (Boolean; since Linux 2.2) Enable TCP syncookies. The kernel must be compiled with CONFIG_SYN_COOKIES.  Send out syncookies when the syn backlog queue of a socket overflows.  The syncookies feature attempts to protect a socket from a SYN flood attack.  This should be used as a last resort, if at all.  This is a violation of the  TCP  protocol,  and  con‐ flicts  with other areas of TCP such as TCP extensions.  It can cause problems for clients and relays.  It is not recommended as a tuning mechanism for heavily loaded servers to help with overloaded or misconfigured conditions.  For recommended alternatives see tcp_max_syn_backlog, tcp_synack_retries, and tcp_abort_on_overflow.

因为 syn 队列的存在,当客户端一直在发送 syn 包,但是不回 ack 报文时,一旦服务端的队列超过 net.ipv4.tcp_max_syn_backlog 设置的大小就会存在队列溢出的问题,从而导致服务端无法响应客户端的请求,这就是 syn flood 攻击。

为了防止 syn flood 攻击,引入了 syn cookies 机制,该机制并非 tcp 协议的一部分。原理参见:深入浅出TCP中的SYN-Cookies

一旦开启了 syn cookies 机制后,即使 syn 队列满,仍可以对新建的连接回复 syn + ack 报文,但是不需要进入队列。

因为 syn cookies 存在部分缺陷,只有当 syn 队列满时该特性才会生效。

  1. net.ipv4.tcp_abort_on_overflow

在三次握手完成后,该连接会进入到 ESTABLISHED 状态,并将该连接放入到用户程序队列中。若该队列已满,默认会将该连接重新设置为 SYN_ACK 状态,相当于是服务端没有接收到客户端的 syn + ack 报文,后续可以利用客户端的重传机制重新接收报文。

一旦开启了 net.ipv4.tcp_abort_on_overflow 选项后,会直接发送 RST 报文给到客户端,客户端会终止该连接,并报错 104 Connection reset by peer

  1. net.core.somaxconn

全连接队列的最大值,该配置为全局默认配置。单个 socket 的全连接队列的长度选择为 Min(backlog, somaxconn)。

内核源码

在整个内核模块中主要涉及到 listen() 和 accept() 系统调用,listen() 系统调用的作用是申请和初始化半连接队列和全连接队列。队列位于内核代码的 include/net/inet_connection_sock.h 中的如下位置:

1
2
3
4
5
6
struct inet_connection_sock {
/* inet_sock has to be the first member! */
struct inet_sock icsk_inet;
struct request_sock_queue icsk_accept_queue; // 队列
struct inet_bind_bucket *icsk_bind_hash;
}

如何参看

查看 tcp 状态为 SYN_RECV 的链接即为半连接状态的请求:netstat -napt | grep SYN_RECV。也可以通过 netstat -s | grep 'SYNs to LISTEN' 查看。

全连接队列可以使用 ss -nlt | grep 8080 的方式查看 Recv-Q 的值。
也可通过如下命令来查看全连接队列的溢出情况。

1
2
$netstat -s | grep overflow
3255 times the listen queue of a socket overflowed

概念

中断由硬件产生,并发送到中断控制器,中断控制器再发送中断到CPU,CPU检测到中断信号后,会中断当前的工作,每个中断都有IRQ(中断请求),基于IRQ,CPU将中断请求分发到对应的硬件驱动上通知操作系统,操作系统会对中断进行处理。

中断控制器

常见的中断控制器有两种:可编程中断控制器8259A和高级可编程中断控制器(APIC)。传统的8259A只适合单CPU的情况,现在都是多CPU、多核心的SMP体系,所以为了充分利用SMP体系结构,把中断传递给系统上的每个CPU以便更好实现并行和提高性能,Intel引入了高级可编程中断控制器(APIC)。

光有高级可编程中断控制器的硬件支持还不够,Linux内核还必须能利用这些硬件的特质,所以只有kernel 2.4以后的版本才支持把不同的硬件中断请求(IRQs)分配到特定的CPU核心上,这个绑定技术被称为SMP IRQ Affinity。

在设置网卡中断的cpu core时,有一个限制就是,IO-APIC 有两种工作模式:logic 和 physical,在 logic 模式下 IO-APIC 可以同时分布同一种 IO 中断到8颗 CPU (core) 上(受到 bitmask 寄存器的限制,因为 bitmask 只有8位长。);在 physical 模式下不能同时分布同一中断到不同 CPU 上,比如,不能让 eth0 中断同时由 CPU0 和 CPU1 处理,这个时候只能定位 eth0 到 CPU0、eth1 到 CPU1,也就是说 eth0 中断不能像 logic 模式那样可以同时由多个 CPU 处理。

软中断和硬中断

为了解决中断处理程序执行时间过长和中断丢失的问题,Linux系统将中断分为上半部和下半部。

上半部在中断禁止模式下运行,用来快速处理中断,主要用来处理跟硬件密切相关的工作。

下半部处理上半部未完成的工作,通常以内核线程的方式运行。

以Linux接收网卡数据包为例进行说明:

网卡接收到一个数据包后,会通过硬件中断的方式通知内核新的数据到了。内核会调用中断处理程序进行处理。

上半部将网卡中的数据写入到内存中,并更新一下硬件寄存器的状态,最后发送一个软中断信号,通知下半部进一步的处理。

下半部被软中断信号唤醒后,从内存中读到数据,按照网络协议栈对数据进行解析和处理,并发送给应用程序。

上面所说的上半部即硬中断,下半部即软中断,但一些内核自定义的事件也属于软中断,比如内核调度和RCU锁等。

硬中断:由外设产生,用来通知操作系统外设状态的变化。在处理中断的同时要关闭中断。特点为处理要尽可能的快。

软中断:为了满足实时性要求,硬中断处理时间都比较短,将时间比较长的中断放到软中断中来完成,称为下半部。int就是软中断指令,中断向量表是中断号和中断处理程序的对应表。每个CPU对应一个软中断内核线程ksoftirqd/cpu编号

1
2
3
4
5
[root@120-14-29-SH-1037-B07 ~]# ps -ef | grep ksoft
root 3 2 0 2016 ? 01:47:12 [ksoftirqd/0]
root 21 2 0 2016 ? 00:47:51 [ksoftirqd/1]
root 26 2 0 2016 ? 00:47:34 [ksoftirqd/2]
root 31 2 0 2016 ? 00:47:46 [ksoftirqd/3]

中断嵌套:硬中断可以嵌套,即新的硬中断可以打断正在执行的中断,但同种中断不可以。软中断不可嵌套,但相同类型中断可在不同的cpu上执行。

相关命令

mpstat

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
33
# 显示cpu处理的中断数量
[root@103-17-164-sh-100-k07 ~]# mpstat -I SUM 1
Linux 3.10.0-327.10.1.el7.x86_64 (103-17-164-sh-100-k07.yidian.com) 09/09/2017 _x86_64_ (4 CPU)

05:27:59 PM CPU intr/s
05:28:00 PM all 61274.00
05:28:01 PM all 61712.00
05:28:02 PM all 62315.00
05:28:03 PM all 59280.00
^C
Average: all 61145.25

# 显示每个核处理的中断数量
[root@103-17-164-sh-100-k07 ~]# mpstat -I SUM 1 -P ALL
Linux 3.10.0-327.10.1.el7.x86_64 (103-17-164-sh-100-k07.yidian.com) 09/09/2017 _x86_64_ (4 CPU)

05:30:30 PM CPU intr/s
05:30:31 PM all 61446.00
05:30:31 PM 0 40489.00
05:30:31 PM 1 6839.00
05:30:31 PM 2 6935.00
05:30:31 PM 3 7185.00

# 显示更详细的信息
[root@120-14-31-SH-1037-B07 ~]# mpstat -P ALL 1
Linux 3.10.0-327.el7.x86_64 (120-14-31-SH-1037-B07.yidian.com) 09/10/2017 _x86_64_ (4 CPU)

01:15:09 AM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
01:15:10 AM all 6.35 0.00 11.64 0.00 0.00 7.67 0.00 0.00 0.00 74.34
01:15:10 AM 0 5.05 0.00 11.11 0.00 0.00 0.00 0.00 0.00 0.00 83.84
01:15:10 AM 1 6.38 0.00 12.77 0.00 0.00 9.57 0.00 0.00 0.00 71.28
01:15:10 AM 2 8.70 0.00 10.87 0.00 0.00 14.13 0.00 0.00 0.00 66.30
01:15:10 AM 3 6.32 0.00 12.63 0.00 0.00 7.37 0.00 0.00 0.00 73.68

lspci

可以来查看网卡型号,驱动等信息,内容较多

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
33
34
35
36
37
[root@103-17-164-sh-100-k07 ~]# lspci -vvv | more
00:00.0 Host bridge: Intel Corporation Xeon E7 v2/Xeon E5 v2/Core i7 DMI2 (rev 04)
Subsystem: Super Micro Computer Inc Device 0668
Control: I/O- Mem- BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-
Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
Interrupt: pin A routed to IRQ 0
Capabilities: [90] Express (v2) Root Port (Slot-), MSI 00
DevCap: MaxPayload 128 bytes, PhantFunc 0
ExtTag- RBE+
DevCtl: Report errors: Correctable- Non-Fatal- Fatal- Unsupported-
RlxdOrd- ExtTag- PhantFunc- AuxPwr- NoSnoop-
MaxPayload 128 bytes, MaxReadReq 128 bytes
DevSta: CorrErr- UncorrErr- FatalErr- UnsuppReq- AuxPwr- TransPend-
LnkCap: Port #0, Speed 5GT/s, Width x4, ASPM not supported, Exit Latency L0s <64ns, L1 <16us
ClockPM- Surprise+ LLActRep+ BwNot+
LnkCtl: ASPM Disabled; RCB 64 bytes Disabled- CommClk-
ExtSynch- ClockPM- AutWidDis- BWInt- AutBWInt-
LnkSta: Speed unknown, Width x0, TrErr- Train- SlotClk- DLActive- BWMgmt- ABWMgmt-
RootCtl: ErrCorrectable- ErrNon-Fatal- ErrFatal- PMEIntEna- CRSVisible-
RootCap: CRSVisible-
RootSta: PME ReqID 0000, PMEStatus- PMEPending-
DevCap2: Completion Timeout: Range BCD, TimeoutDis+, LTR-, OBFF Not Supported ARIFwd-
DevCtl2: Completion Timeout: 50us to 50ms, TimeoutDis-, LTR-, OBFF Disabled ARIFwd-
LnkCtl2: Target Link Speed: 2.5GT/s, EnterCompliance- SpeedDis-
Transmit Margin: Normal Operating Range, EnterModifiedCompliance- ComplianceSOS-
Compliance De-emphasis: -6dB
LnkSta2: Current De-emphasis Level: -6dB, EqualizationComplete-, EqualizationPhase1-
EqualizationPhase2-, EqualizationPhase3-, LinkEqualizationRequest-
Capabilities: [e0] Power Management version 3
Flags: PMEClk- DSI- D1- D2- AuxCurrent=0mA PME(D0+,D1-,D2-,D3hot+,D3cold+)
Status: D0 NoSoftRst- PME-Enable- DSel=0 DScale=0 PME-
Capabilities: [100 v1] Vendor Specific Information: ID=0002 Rev=0 Len=00c <?>
Capabilities: [144 v1] Vendor Specific Information: ID=0004 Rev=1 Len=03c <?>
Capabilities: [1d0 v1] Vendor Specific Information: ID=0003 Rev=1 Len=00a <?>
Capabilities: [280 v1] Vendor Specific Information: ID=0005 Rev=3 Len=018 <?>

...

ethtool

用来查看网卡信息

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
[root@103-17-164-sh-100-k07 ~]# ethtool eth3
Settings for eth3:
Supported ports: [ FIBRE ]
Supported link modes: 1000baseT/Full
10000baseT/Full
Supported pause frame use: No
Supports auto-negotiation: Yes
Advertised link modes: 1000baseT/Full
10000baseT/Full
Advertised pause frame use: No
Advertised auto-negotiation: Yes
Speed: 10000Mb/s
Duplex: Full
Port: FIBRE
PHYAD: 0
Transceiver: external
Auto-negotiation: on
Supports Wake-on: d
Wake-on: d
Current message level: 0x00000007 (7)
drv probe link
Link detected: yes

# 可查看Ring buffer的大小
[root@103-17-164-sh-100-k07 ~]# ethtool -g eth3
Ring parameters for eth3:
Pre-set maximums:
RX: 4096
RX Mini: 0
RX Jumbo: 0
TX: 4096
Current hardware settings:
RX: 512
RX Mini: 0
RX Jumbo: 0
TX: 512

# 列出信息较多,包含网卡的统计信息,包括丢包量信息
[root@103-17-164-sh-100-k07 ~]# ethtool -S eth3 | more
NIC statistics:
rx_packets: 680955795162
tx_packets: 27260701850
rx_bytes: 248285654162670
tx_bytes: 195321924245892
rx_pkts_nic: 683081802539
tx_pkts_nic: 27260700665
rx_bytes_nic: 251132784690871
tx_bytes_nic: 195447730645152
lsc_int: 11
tx_busy: 0
non_eop_descs: 1811095697
rx_errors: 67381
tx_errors: 0
rx_dropped: 0
tx_dropped: 0
multicast: 1025690658
broadcast: 206937242
rx_no_buffer_count: 0
collisions: 0
rx_over_errors: 0
rx_crc_errors: 67302
rx_frame_errors: 0
hw_rsc_aggregated: 2358414213
hw_rsc_flushed: 232402327
fdir_match: 10634169417
fdir_miss: 669277016191
fdir_overflow: 3321
rx_fifo_errors: 0
rx_missed_errors: 2538
tx_aborted_errors: 0
tx_carrier_errors: 0
tx_fifo_errors: 0
tx_heartbeat_errors: 0
tx_timeout_count: 0
tx_restart_queue: 0
rx_long_length_errors: 80264
rx_short_length_errors: 0
tx_flow_control_xon: 1
rx_flow_control_xon: 0
tx_flow_control_xoff: 51
rx_flow_control_xoff: 0
rx_csum_offload_errors: 0
alloc_rx_page_failed: 0
alloc_rx_buff_failed: 0
rx_no_dma_resources: 0
os2bmc_rx_by_bmc: 0
os2bmc_tx_by_bmc: 0
os2bmc_tx_by_host: 0
os2bmc_rx_by_host: 0

# 查看网卡多队列的支持情况,当前网卡支持8个队列,使用了8个队列
[root@103-17-6-sh-100-j11 ~]# ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX: 0
TX: 0
Other: 1
Combined: 8
Current hardware settings:
RX: 0
TX: 0
Other: 1
Combined: 8

# 设置网卡当前使用的多队列,当前使用的网卡数量不能超过最大值8,该值跟网卡的中断数量一一对应,即/proc/interrupts中看到的eth0的中断数量
[root@103-17-6-sh-100-j11 ~]# ethtool -L eth0 combined 2
[root@103-17-6-sh-100-j11 ~]# ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX: 0
TX: 0
Other: 1
Combined: 8
Current hardware settings:
RX: 0
TX: 0
Other: 1
Combined: 2

sar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 列出网卡的接收包信息,比iftop更直观
[root@103-17-164-sh-100-k07 ~]# sar -n DEV 1
Linux 3.10.0-327.10.1.el7.x86_64 (103-17-164-sh-100-k07.yidian.com) 09/09/2017 _x86_64_ (4 CPU)

05:06:12 PM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s
05:06:13 PM eth0 0.00 0.00 0.00 0.00 0.00 0.00 0.00
05:06:13 PM eth1 0.00 0.00 0.00 0.00 0.00 0.00 0.00
05:06:13 PM eth2 0.00 0.00 0.00 0.00 0.00 0.00 0.00
05:06:13 PM eth3 77893.00 4328.00 31413.92 30134.16 0.00 0.00 46.00
05:06:13 PM lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00

# 可列出错误包的相关信息
[root@103-17-164-sh-100-k07 ~]# sar -n EDEV 1
Linux 3.10.0-327.10.1.el7.x86_64 (103-17-164-sh-100-k07.yidian.com) 09/09/2017 _x86_64_ (4 CPU)

05:07:13 PM IFACE rxerr/s txerr/s coll/s rxdrop/s txdrop/s txcarr/s rxfram/s rxfifo/s txfifo/s
05:07:14 PM eth0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
05:07:14 PM eth1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
05:07:14 PM eth2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
05:07:14 PM eth3 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
05:07:14 PM lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

通过中断可以看到网卡包含四个中断55-58,均位于cpu0上。

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
33
34
35
36
37
38
39
40
[root@103-17-164-sh-100-k07 ~]# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 44 0 0 0 IR-IO-APIC-edge timer
1: 3 0 0 0 IR-IO-APIC-edge i8042
8: 42 0 0 0 IR-IO-APIC-edge rtc0
9: 1 0 0 0 IR-IO-APIC-fasteoi acpi
12: 4 0 0 0 IR-IO-APIC-edge i8042
16: 99 0 0 0 IR-IO-APIC-fasteoi ehci_hcd:usb1
18: 0 0 0 0 IR-IO-APIC-fasteoi i801_smbus
23: 83 0 0 0 IR-IO-APIC-fasteoi ehci_hcd:usb2
37: 12019917 0 0 0 IR-PCI-MSI-edge 0000:00:1f.2
48: 0 0 0 0 DMAR_MSI-edge dmar0
55: 746827548 0 0 0 IR-PCI-MSI-edge eth3-TxRx-0
56: 2674693551 0 0 0 IR-PCI-MSI-edge eth3-TxRx-1
57: 2341522223 0 0 0 IR-PCI-MSI-edge eth3-TxRx-2
58: 3587929355 0 0 0 IR-PCI-MSI-edge eth3-TxRx-3
59: 3334 0 0 0 IR-PCI-MSI-edge eth3
61: 2 0 0 0 IR-PCI-MSI-edge ioat-msix
63: 2 0 0 0 IR-PCI-MSI-edge ioat-msix
64: 2 0 0 0 IR-PCI-MSI-edge ioat-msix
65: 2 0 0 0 IR-PCI-MSI-edge ioat-msix
66: 2 0 0 0 IR-PCI-MSI-edge ioat-msix
67: 2 0 0 0 IR-PCI-MSI-edge ioat-msix
68: 2 0 0 0 IR-PCI-MSI-edge ioat-msix
69: 2 0 0 0 IR-PCI-MSI-edge ioat-msix
NMI: 1100069 693493 635982 615953 Non-maskable interrupts
LOC: 1120358899 146726541 4134846029 168005659 Local timer interrupts
SPU: 0 0 0 0 Spurious interrupts
PMI: 1100069 693493 635982 615953 Performance monitoring interrupts
IWI: 57255892 115292160 113458706 112987848 IRQ work interrupts
RTR: 0 0 0 0 APIC ICR read retries
RES: 525229423 2791640970 427214674 1986396041 Rescheduling interrupts
CAL: 4294536344 4294488238 4294478163 4294552026 Function call interrupts
TLB: 65239533 57937650 55104990 52690662 TLB shootdowns
TRM: 0 0 0 0 Thermal event interrupts
THR: 0 0 0 0 Threshold APIC interrupts
MCE: 0 0 0 0 Machine check exceptions
MCP: 160132 160132 160132 160132 Machine check polls
ERR: 0
MIS: 0

修改中断的cpu分配

echo “2” > /proc/irq/49/smp_affinity

其中2表示cpu1, 49表示中断号。

查看软中断

1
2
3
4
5
6
7
8
9
10
11
12
[root@120-14-31-SH-1037-B07 ~]# cat /proc/softirqs
CPU0 CPU1 CPU2 CPU3
HI: 1 3 0 1
TIMER: 1795378091 3617740778 2674553229 1524071492
NET_TX: 202188392 22218135 17427628 17205883
NET_RX: 3388060179 60871361 68145291 38670323
BLOCK: 4741950 2422 1309 1489
BLOCK_IOPOLL: 0 0 0 0
TASKLET: 2102131738 3720214 3104944 1942912
SCHED: 526046585 612421231 496815061 456047989
HRTIMER: 0 0 0 0
RCU: 3147020579 4237695975 3820083676 3267816268

软中断包括10个类别,NET_RX(网络接收中断)、NET_TX(网络发送中断)

查看数据包统计

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
[root@c1-g08-120-166-30 ~]# netstat -s
Ip:
7586513384 total packets received
0 forwarded
741 with unknown protocol
0 incoming packets discarded
7586512643 incoming packets delivered
7948370396 requests sent out
Icmp:
23 ICMP messages received
0 input ICMP message failed.
ICMP input histogram:
destination unreachable: 1
echo requests: 19
echo replies: 3
46 ICMP messages sent
0 ICMP messages failed
ICMP output histogram:
destination unreachable: 9
echo request: 18
echo replies: 19
IcmpMsg:
InType0: 3
InType3: 1
InType8: 19
OutType0: 19
OutType3: 9
OutType8: 18
Tcp:
561299810 active connections openings
2005002 passive connection openings
8 failed connection attempts
282644949 connection resets received
725 connections established
7585817181 segments received
13957880471 segments send out
1742807 segments retransmited
136 bad segments received.
523811266 resets sent
Udp:
445457 packets received
3 packets to unknown port received.
0 packet receive errors
553840367 packets sent
0 receive buffer errors
0 send buffer errors
UdpLite:
InErrors: 3
TcpExt:
341304 invalid SYN cookies received
1 resets received for embryonic SYN_RECV sockets
1 ICMP packets dropped because they were out-of-window
1997421 TCP sockets finished time wait in fast timer
222787 delayed acks sent
1819 delayed acks further delayed because of locked socket
Quick ack mode was activated 178186 times
13 packets directly queued to recvmsg prequeue.
3280604069 packet headers predicted
1972740996 acknowledgments not containing data payload received
798190042 predicted acknowledgments
642444 times recovered from packet loss by selective acknowledgements
Detected reordering 23 times using FACK
Detected reordering 1618 times using SACK
59 congestion windows fully recovered without slow start
16 congestion windows partially recovered using Hoe heuristic
17089 congestion windows recovered without slow start by DSACK
9594 congestion windows recovered without slow start after partial ack
TCPLostRetransmit: 2849
4926 timeouts after SACK recovery
672451 fast retransmits
28946 forward retransmits
680 retransmits in slow start
5024 other TCP timeouts
TCPLossProbes: 1390602
TCPLossProbeRecovery: 997235
4723 SACK retransmits failed
178189 DSACKs sent for old packets
979863 DSACKs received
62 DSACKs for out of order packets received
282645553 connections reset due to unexpected data
9 connections reset due to early user close
TCPDSACKIgnoredNoUndo: 936061
TCPSpuriousRTOs: 5150
TCPSackShifted: 233309
TCPSackMerged: 733778
TCPSackShiftFallback: 1768422
IPReversePathFilter: 1
TCPRetransFail: 11
TCPRcvCoalesce: 1290965687
TCPOFOQueue: 1753072
TCPChallengeACK: 136
TCPSYNChallenge: 136
TCPSpuriousRtxHostQueues: 73
TCPAutoCorking: 272452106
TCPSynRetrans: 4538
TCPOrigDataSent: 9260789170
TCPHystartTrainDetect: 3721895
TCPHystartTrainCwnd: 64417412
TCPHystartDelayDetect: 80
TCPHystartDelayCwnd: 2579
TCPACKSkippedSynRecv: 4
IpExt:
InMcastPkts: 249744
OutMcastPkts: 83317
InBcastPkts: 226
InOctets: 12312144006244
OutOctets: 12449967070063
InMcastOctets: 22309104
OutMcastOctets: 9664620
InBcastOctets: 104240
InNoECTPkts: 7586513474
InECT0Pkts: 47

irqbalance

查看是否运行:systemctl status irqbalance

irqbalance根据系统中断负载的情况,自动迁移中断保持中断的平衡,同时会考虑到省电因素等等。 但是在实时系统中会导致中断自动漂移,对性能造成不稳定因素,在高性能的场合建议关闭。

irqbalance用于优化中断分配,它会自动收集系统数据以分析使用模式,并依据系统负载状况将工作状态置于 Performance mode 或 Power-save mode。处于Performance mode 时,irqbalance 会将中断尽可能均匀地分发给各个 CPU core,以充分利用 CPU 多核,提升性能。
处于Power-save mode 时,irqbalance 会将中断集中分配给第一个 CPU,以保证其它空闲 CPU 的睡眠时间,降低能耗。

ref

本文为极客时间专栏从0开始学习微服务的阅读笔记。

在了解微服务之前,先来了解一下单体应用。

在上学那会,做过一些企业站,技术往往是基于LAMP(Linux + Apache + MySQL + PHP),这种业务较为简单的企业站,就是单体应用。所有的业务代码都是放在一个PHP程序中,代码只需要我自己来维护就可以了,测试、上线、运维全部搞定。

但这种单体应用要是放到稍微有点规模的互联网公司中必然是行不通的。一个公司里有很多的人,不可能公司这么多技术人员共同维护一套代码,这样子团队的写作必然是个问题。系统的健壮性也会比较差,一旦单体应用中的一个模块出问题后往往会影响到其他的模块,导致整个系统不可用,比如PHP的服务业界通常使用php-fpm来管理,而php-fpm处理连接的方式一个请求一个线程,当一部分请求因为延时高时会消耗过多的线程资源,导致其他请求没有可用的线程可以处理。

单体应用的其他缺点不再罗列,比如代码膨胀过度、发布较慢、系统高可用性差、团队协作成本高等。

为了解决单体应用的这些缺点,方法只有一个就是将单体应用拆分为多个服务即服务化,服务之间通过RPC的方式相互调用。

即服务化后,业界又提出了微服务的概念。

维基百科中有如下定义:

2014年,Martin Fowler 与 James Lewis 共同提出了微服务的概念,定义了微服务是由以单一应用程序构成的小服务,自己拥有自己的行程与轻量化处理,服务依业务功能设计,以全自动的方式部署,与其他服务使用 HTTP API 通讯。同时服务会使用最小的规模的集中管理 (例如 Docker) 能力,服务可以用不同的编程语言与数据库等元件实作。

但看这个定义,看的我一脸懵逼,实在太过抽象。其实微服务也没有一个特别明确的定义。

那么服务化和微服务之间有什么不同之处呢?

  1. 服务拆分粒度更细。
  2. 每个微服务都独立部署和维护。
  3. 微服务需要服务治理。由于微服务会将服务变多,势必需要一个服务管理平台来对微服务进行管理。

将单体应用向微服务拆分的方式可以分为纵向拆分和横向拆分。这里以一点资讯app为例来解释纵向拆分和横向拆分。

一点资讯分为信息流、正文页等模块,而这些功能都依赖于用户信息获取模块,纵向拆分即将信息流、正文页拆分为微服务,横向拆分即将信息流、正文页都依赖的用户信息获取模块拆分为单独的服务。

微服务架构

采用微服务后会带来一系列复杂的问题,新技术在解决了一部分问题的同时,总会带来一些新的问题,比如服务怎么定义接口、服务的发布方式和服务发现、服务的监控、服务的治理(包括依赖关系梳理、熔断机制等)、故障快速定位等。

那么一个标准的微服务架构应该长什么样子呢?

服务提供方在服务启动时向服务注册中心注册服务,声明自己能够提供的服务及当前服务的地址等信息。

服务调用者请求注册中心,查询所要调用服务的地址,并通过约定好的协议向服务提供者发起请求,获取到结果后按照数据协议格式将数据进行反序列化。

在整个服务的调用过程中,服务的请求耗时、调用量、调用成功与否等信息都会作为监控记录下来,服务的调用关系会通过trace系统记录下来,以便用于后续的故障定位和追踪。如果服务调用失败,则需要服务治理的方式来保证调用方的正常运行。

服务的接口定义

需要解决的问题是服务的接口有哪些?每个接口的输入什么?每个接口的输出是什么?

RESTful API

HTTP协议的接口定义通常使用该协议,常见Wiki或者Swagger的方式来管理。

IDL文件

IDL(interface description language)用于描述接口,使得不同的编程语言不同的平台服务之间可以相互通讯。常见实现包括Thrift和protobuf,protobuf作为序列化方式的一种,通常会使用gRPC进行通讯。

XML文件方式

服务提供者将接口描述信息保存在xml配置文件,服务启动后会加载配置文件,将服务暴露出去。

服务消费者将要调用的接口信息写在xml配置文件中,进程启动后加载xml配置文件。

服务提供者通常需要提供服务的超时时间等参数,而消费者端也需要该信息,为了让消费者端能看到生产者端的配置,可以将信息放在配置中心中,但这却增大了配置中心的内容,当一旦配置变化需要同步时,同步的数据会变多。

注册中心

要想实现服务之间的调用,通常会使用反向代理和服务注册中心的方法。

反向代理的方法业界一般使用较多的包括nginx、haproxy以及业界新秀envoy等。

在微服务架构中更多提及的方案为采用服务注册中心的方法,而注册中心的稳定性就显得尤其重要。

业界注册中心采用较多的方案为zookeeper、etcd、consul、eureka。这些注册中心的存储数结构要么是树状接口,要么是key-value形式,其中k-v形式的可以变更key的值以实现类似树状结构。

注册中心的设计

注册中心API及功能

注册中心需要提供以下api以供服务提供方和服务调用方使用。

  1. 服务注册接口,提供服务提供方使用
  2. 服务反注册接口,提供服务提供方以便销毁服务
  3. 心跳汇报接口,服务提供方通过心跳汇报服务是存活状态的。一旦当服务出现异常时,服务注册中心应该立即将服务从注册中心剔除。
  4. 服务订阅接口,服务调用方用于获取服务提供方的实例列表
  5. 服务变更接口,服务调用方用于获取最新可用服务。一旦注册中心探测到有新的服务实例或者实例减少,应该立即通知所有订阅该服务的服务调用者更新本地的节点信息。

注册中心存储哪些服务信息

通常可以按照“服务名-分组-节点信息”三层结构来存储,其中节点信息包括:节点ip地址、节点端口号、请求失败时重试次数、请求结果是否压缩。

分组的划分原则包括:

  1. 按照业务的核心程度
  2. 按照机房维度
  3. 线上环境、测试环境

注册中心如何工作

服务提供者注册节点
  1. 查看注册节点是否在白名单内,即是否可以向注册中心注册
  2. 查看注册的服务名、服务分组是否存在
  3. 将节点信息添加到对应的存储位置
服务提供者反注册
  1. 查看服务名、服务分组对应的服务是否存在
  2. 将节点删除
服务消费者查询节点信息
  1. 从本机内存中查找服务信息
  2. 如果有本地快照存储可以从中查找
服务消费者订阅服务变更
  1. 消费者获取到服务信息后,在本地保留cluster的sign值
  2. 每个一段时间从注册中心获取cluster的sign值,如果不一致,就从注册中心拉取服务节点,并更新内存环境和本地快照

服务框架

完整的服务框架包括:通讯框架、通讯协议、序列化和反序列化。

开源RPC框架

跟语言相关的框架:Dubbo、Motan(微博)、Tars(腾讯)、Spring Cloud

跨平台的开源RPC框架:gRPC、Thrift

服务监控

常用的开源监控软件包括:ELK、Graphite、TICK、Prometheus

服务追踪

使用分布式会话跟踪技术,利用traceid

开源方案包括OpenZipkin、jaeger等

服务治理

通过一系列的手段保证在意外情况下,服务仍然能够正常运行。

节点管理

服务调用失败可能是服务提供者自身出现了问题,也可能是网络问题导致。

1.注册中心自动摘除机制

服务提供者和注册中心之间保持心跳,当超时后注册中心自动摘除服务提供者。

2.服务消费者摘除

将服务提供者的探活机制放到消费者端,消费者在探测到服务提供者失败后自动摘除服务提供者。这种情况可以避免服务提供者和注册中心之间网络出现异常,但是服务提供者和服务消费者之间可以通讯的情况。

负载均衡

常用的包括随机算法、轮询、加权轮询算法、最少活跃调用、一致性hash算法(可以配合着静态注册中心达到比较好的效果)

自适应最优选择算法:在客户端维护一份每一个服务节点的性能统计快照,每隔一段时间去更新快照。在发起请求时,根据二八原则,将服务节点分成两部分,找出20%的那部分响应最慢的节点并降低权重。也可称为动态加权轮询算法。1分钟的更新时间间隔是个不错的选择。

个人感觉自适应最优选择算是个不错的选择,但还可以针对业务场景继续优化,比如权重进行动态调整。

服务路由

用于限定服务消费者可选择服务提供者节点的范围。

应用场景:分组调用(组的划分可以按照机房等维度)、灰度发布、流量切换(比如机房故障后,用于机房之间的流量调度)、读写分离(读接口部署在一起,写接口部署在一起)。

规则的写法

A.条件路由

  • 某个ip的消费者仅访问某个ip的服务提供者
  • 排除某个服务节点(所有的服务消费者都不能访问某个服务提供者)
  • 白名单和黑名单
  • 机房级别的隔离(可以基于ip地址的规则来做)
  • 读写分离(所有get类方法仅访问某些节点等)

B.脚本路由

使用脚本语言的形式来描述

路由的获取方式

  • 本地配置:存储在服务消费者本地
  • 配置中心配置,可以修改规则后动态下发

服务容错

手段包括超时、重试、双发、熔断等。

双发的思路为服务调用者在发起一次服务调用后,在给定的时间内(该时间要比服务超时的时间短)没有得到结果,再发起另外一个请求。

熔断的思路为在某一个时间内如果服务调用失败次数超过一定值,则触发熔断,不再向服务提供者发起请求。

断路器中的状态包括:Closed、Open、Half Open(半打开状态,用于探测后端服务是否已经正常)

Hystrix是最出名的熔断器,计算服务调用的失败率是通过滑动窗口来实现,滑动窗口内包含10个桶,每个桶为1秒内的服务调用情况。

FailOver,失败自动切换。消费者调用失败后,自动从可以节点中选择下一个节点重新调用,可设置失败的次数。通常适合只读的场景。

FailBack,失败通知。调用失败后,不能重试,而是根据失败的信息来决定后续的执行策略。通常用于写场景。

FailCache,失败缓存。调用失败后,隔一段时间后再重试。

FailFast,快速失败。调用失败后不再重试。

如何识别服务节点是否存活

如果注册中心为zk,在服务节点变化的时候,注册中心会向服务调用方推送服务节点变化的通知。但当网络抖动的时候,可能会存在节点的状态频繁变化的问题,导致服务消费者频繁收到节点变更通知,或者导致注册中心获取到的服务节点过少。可通过以下手段来避免该问题。

服务端故障时的应对策略

故障包括:集群故障、单个idc故障、单机故障

集群故障

比如触发bug、突发流量等

限流:限制超出接口阈值的部分请求

降级:一种思路为通过开关来实现。具体可以分为多种等级:一级降级对业务影响比较小,可以设置为自动降级;二级降级对业务有一定影响,设置为手工降级;三级降级需要谨慎操作。

单idc故障

基于dns的流量切换:延时稍高,比较适合入口流量。

基于rpc的分组流量切换:将之前单个机房内访问的流量切换为多个机房访问。

单机故障

可以通过自动重启服务的手段来解决。但要避免一次性重启服务过多的问题。

动态注册中心的保护机制

  1. 心跳开关保护机制,给注册中心设置一个开关,当开发打开时,即使网络频繁抖动,注册中心也不会通知消费者节点变更,后者设置一定的百分比打开该开关。正常情况下该开关可以不打开。
  2. 服务节点摘除保护机制,设定一个阈值比例,在出现网络抖动的情况下,注册中心也不会将超过这个阈值的节点给下掉,防止一下子下掉过多节点。该机制正常情况下,应该开启。

静态注册中心的保护机制

在服务消费者端来判断是否服务提供者是否存活。服务消费者调用某一个节点失败超过一定次数就将节点标记为不可用。并隔一段时间后再去探测该节点是否存活。

注册中心的服务正常情况下不改变的,只有当服务在发布的时候才去修改注册中心中的节点。注册中心中的节点变化后仍然通知服务消费者,只是在网络出现抖动的时候,不再去通知。

个人感觉这个思路更靠谱。

服务治理平台

1.服务管理 包括服务上下线、节点添加和删除、服务查询、服务节点查询等

2.服务治理 限流、降级、切流量

3.服务监控 可以包含服务的tracing监控等

4.问题定位

5.日志查询

6.服务运维 发布部署和扩缩容

配置中心

业内采用的开源配置中心包括:

  • Spring Cloud 功能相对较弱,变更配置需要通过git操作
  • Apollo 对Spring Boot的支持比较好

进阶内容

做好容量规划

包括容量评估和调度决策两方面。

压测服务的单机最大容量可以采用区间加权的方式来计算,比如0 ~ 10ms区间权重为1,10 ~ 50ms区间权重为2,500ms以上权重为32,通过累加的方式计算出单机的权重,从而评估出单机最大容量。(该评估容量的方式非常实用)

调度策略可以根据水位线来做决定,一条是安全线,一条是致命线。当水位线处于致命线需要立即扩容,当水位线回到安全线以上时可以进行缩容。(水位线的方式很赞

缩容的思路是采用逐步缩容,每隔5分钟判断一次水位线是否在致命线以上,并按照10%、30%的比例进行缩容。

为了防止水位线的抖动,可以一分钟采集一个点,当5个点中的3个点都满足条件时才进行缩容。

多机房部署

1.主从架构

所有的写请求都发给主机房,主机房更新本机房的缓存和数据库,其他机房的缓存和数据库从主机房同步。主机房出现问题后,就没法更新了。

2.独立机房架构

缓存层,每个机房都有写请求,每个机房的写请求通过消息同步组件将写请求同步给另外一个机房。数据库mysql只有一个主库,另外机房的mysql同步数据到该机房。

以上缓存消息同步组件的实现大概如下:

包括两个模块reship和collector,reship负责将本机房的写请求发一份给别的机房,collector负责从别的机房读取写请求,并发给本机房的处理服务器。

可以通过消息队列和rpc调用来实现reship和collector的通讯。

3.多机房的数据一致性

可通过消息对账机制来保证一致性,原理为通过一个单独的程序后面来验证是否一个写请求是否已经被所有的机房都处理,如果没有处理,则重新发送写请求。

混合云部署

公有云上为了安全往往不部署数据库

DevOps实践

持续集成:确保每一次代码的merge request都通过,分为四个阶段:build(开发分支代码的编译与单元测试)、package(开发分支代码打包成docker镜像)、deploy(开发分支代码部署到测试环境)、test(集成测试)。包括了代码检查和单元测试环节。

持续交付:代码merge request到develop分支后,develop分支的代码能够在生产环境中测试通过,并进行小流量灰度验证,可以随时上线。分为五个阶段:build(develop分支的代码编译与单元测试)、package(develop分支代码打包)、deploy、test、canary(develop分支代码的小流量灰度验证)。

持续部署:合并develop代码到master分支,并打包成docker镜像,并可随时上线。包括:build、package、clear、production。

Service Mesh

Service Mesh技术以轻量级网络代理的方式与应用代码部署在一起,主要有两个关键点技术。

SideCar用于转发服务之间的调用,在服务消费者端SideCar用于将请求转发到服务提供者端的SideCar中,在服务提供者端的SideCar在接收到请求后转发给本机上的服务提供者。

SideCar实现方式有基于ipstables的网络拦截和直接转发请求两种方式。

Control plane用于基于SideCar的服务调用的治理,用来取代微服务中需要服务框架干的事情,包括服务发现、负载均衡、请求路由、故障处理、安全认证、监控上报、日志记录、配额限制等。

Istio

Pilot主要用于流量控制,包括Rules API(提供API,用于流量控制)、Envoy API(给Envoy提供API,获取服务注册信息、流量控制信息等)、抽象模型(对服务注册信息、流量控制进行抽象)、平台适配层(适配k8s、Mesos等多个平台,将平台特定的注册信息转换成平台无关的抽象模型)。

Pilot的流量控制功能包括服务发现和负载均衡、请求路由、超时重试、故障注入。

Mixer实现策略控制和监控日志收集等功能,理论上Envoy发起的每次请求都会发送到Mixer,可以异步发送。

Mixer的策略控制包括对服务的访问频率限制和访问控制。

Citadel用于保证服务之间的安全,需要Envoy的配合。Citadel中存储了秘钥和证书,通过Pilot将授权策略和安全命名信息分发给Envoy,Envoy和Envoy之间通过双向TLS证书来进行通讯,由Mixer来管理授权和审计。

ref

微服务架构技术栈选型手册

Nagle算法为了避免网络中存在太多的小数据包,尽可能发送大的数据包。定义为在任意时刻,最多只有一个未被确认的小段。小段为小于MSS尺寸的数据块,未被确认是指数据发出去后未收到对端的ack。

Nagle算法是在网速较慢的时代的产物,目前的网络环境已经不太需要该机制,该算法在linux系统中默认关闭。

延时ACK机制: 在接收到对端的报文后,并不会立即发送ack,而是等待一段时间发送ack,以便将ack和要发送的数据一块发送。当然ack不能无限延长,否则对端会认为包超时而造成报文重传。linux采用动态调节算法来确定延时的时间。

可以举例来描述一下,client连续向server端发送两个小于MSS的数据包。client发送第一个数据包,根据Nagle算法,此时没有未确认的数据段,该数据包可以直接发送。server端接收到数据包后,由于延时ACK机制,并不会立即发送ack,而是需要等到延时ack机制超时后再发送第二个数据包。此时client端由于Nagle算法, 存在一个未被确认的数据包,不能向server端发送第二个数据包。

在延时要求尽量小的情况下,并不适合用Nagle算法,比如SSH会话。可以通过设置TCP_NODELAY来完成。

java 8u131之后的版本开始支持容器特性,之前的版本中并不支持容器相关的特性。

java基础知识

JVM默认的最大堆内存大小为系统内存的1/4,可以使用参数-XX:MaxRAMFraction=1表示将所有可用内存作为最大堆。

cgroup的限制在docker中能够看到,通过查看/sys/fs/cgroup目录下的文件可以获取。

JVM的用户地址空间分为JVM数据区和direct memory。JVM数据区由heap、stack等组成,GC是操作的这一片内存。direct memory是额外划分出来的一片内存空间,需要手工管理内存的申请和释放。

direct memory使用Unsafe.allocateMemoryUnsafe.setMemory来申请和设置内存,是直接使用了C语言中的malloc来申请内存。由jvm参数MaxDirectMemorySize来限制direct memory可使用的内存大小。

java < 8u131

没有对容器的任何支持,对cpu和内存的限制需要通过jvm的参数来配置。

java中并不能看到内存资源的限制,会存在使用内存超过限制而被OOM的问题。可通过在程序中设置-Xmx来解决该问题。

JVM GC(垃圾对象回收)对Java程序执行性能有一定的影响。默认的JVM使用公式“ParallelGCThreads = (ncpus <= 8) ? ncpus : 3 + ((ncpus * 5) / 8)” 来计算做并行GC的线程数,其中ncpus是JVM发现的系统CPU个数。一旦容器中JVM发现了宿主机的CPU个数(通常比容器实际CPU限制多很多),这就会导致JVM启动过多的GC线程,直接的结果就导致GC性能下降。Java服务的感受就是延时增加,TP监控曲线突刺增加,吞吐量下降。

显式的传递JVM启动参数-XX:ParallelGCThreads告诉JVM应该启动几个并行GC线程。它的缺点是需要业务感知,为不同配置的容器传不同的JVM参数。

java9 and java >= 8u131

增加了XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap参数来检查内存限制。JVM中可以看到cgroup中的内存限制。

可以根据容器中的cpu限制来动态设置GC线程数,不再需要单独设置-XX:ParallelGCThreads

java10

jvm参数XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap已经默认开启,但新增加-XX:-UseContainerSupport参数来更好支持容器,支持内存和cpu。

在开启-XX:-UseContainerSupport的同时,XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap会被关闭。

ref

题图为公司楼下公园的杨树林。时光易逝弹指间,又到一年叶落时。

资源

1.runV

基于 hypervisor 的 OCI runtime

2.operator-sdk

operator机制利用CRD机制增强了kubernetes的灵活性,但operator的编写代码很多模式都是固定的,该项目提供了更高层次的抽象。

3.orchestrator

用来管理mysql的集群拓扑和故障自动转移的工具。

4.Tars

https://github.com/TarsCloud/Tars/blob/master/docs/images/tars_jiaohu.png

腾讯开源的RPC框架,在腾讯内部已经有多年的使用历史,目前支持多种语言。

5.ngx_http_dyups_module

nginx module,可以提供Restful API的形式来动态修改upstream,而不用重新reload nginx。

6.Dragonfly

https://github.com/alibaba/Dragonfly/raw/master/docs/images/logo/dragonfly-linear.png

阿里巴巴开源的基于P2P的容器镜像分发系统。

7.SonarQube

开源的代码检查和扫描工具,支持多种语言,并提供了友好的web界面用来查看分析结果。

8.QUIC

QUIC是Google开发的基于UDP的传输层协议,提供了像TCP一样的数据可靠性,但降低了数据的传输延时,并具有灵活的拥塞控制和流量控制。

9.OpenMessaging

阿里巴巴发起的分布式消息的应用开发标准,目前github上的star数还较少。

10.nsenter

nsenter是一个命令行工具,用来进入到进程的linux namespace中。

docker提供了exec命令可以进入到容器中,nsenter具有跟docker exec差不多的执行效果,但是更底层,特别是docker daemon进程异常的时候,nsenter的作用就显示出来了,因此可以用于排查线上的docker问题。

精彩文章

1.为何程序员永远是高薪行业

从记者的视角来了解阿里云的历史。

2.Harbor传奇(1)- Harbor前世

3.蚂蚁金服 Service Mesh 实践探索

4.美团容器平台架构及容器技术实践

美团内部的容器平台HULK已经从第一代的自研升级为第二代的基于kubernetes的容器管理平台。由此可以反映出kubernetes在容器管理领域的地位。

5.Serverless:后端小程序的未来

Serverless是未来软件架构的一个演进方向,包括BasS(Backend as a Service,后端即服务)和FaaS(Functions as a Service,函数即服务)两个组成部分。

BaaS包括对象存储、数据库、消息队列等服务,并以API的形式提供应用依赖的后端服务。

FaaS中的运行是通过事件触发的方式,代码执行完成后即运行结束,因此代码必须是无状态的。FaaS平台负责服务的自动扩容,并可做到按照服务的使用资源付费,以节省大量开支。

Serverless给开发人员带来了非常大的便利性,但同时也软件跟云平台绑定特别紧密。

图书

1.《奈飞文化手册:“硅谷重要文件”的深度解读

https://images-na.ssl-images-amazon.com/images/I/51UmLKXW9%2BL._SX366_BO1,204,203,200_.jpg

Netflix公司的技术文化一直非常被业界推崇,可以从Netflix OSS已经开源的软件项目,很多的开源项目在社区也有不错的影响力,本书值得每一位技术从业者一读。

精彩句子

我们要求大家做出的任何举动,出发点都是以对客户和公司最有利为出发点,而不是试图证明自己正确。

- 奈飞文化手册:“硅谷重要文件”的深度解读

0%