IT男们,珍惜生命吧
今天在微博上听说百度地图的一个IT男去世了~中山大学,刚毕业的~~
悲催的IT男们,珍惜生命吧,多出去走走,运动运动,珍惜身边的家人、朋友,不要总窝在电脑前边~~

今天在微博上听说百度地图的一个IT男去世了~中山大学,刚毕业的~~
悲催的IT男们,珍惜生命吧,多出去走走,运动运动,珍惜身边的家人、朋友,不要总窝在电脑前边~~

熟悉 k8s 的同学都会知道,Kubernetes 的网络定义了一套容器网络 API 接口 CNI(Container Network Interface),这样我们就可以使用任意的第三方网络插件,给我们提供了多样化的网络解决方案。每个解决方案,都有自己的实现方式,相应的,当出现一些网络问题时,也需要我们有能力去分析定位和解决问题。
先介绍一下我们大概的业务背景:
我们的服务部署在 360 搜索自建的 k8s 云平台上,有 4 个主力集群,业务对外提供 service 的方式为 基于 iptables 模式的 kube-proxy, 通过 nodePort 的方式,指定多台 external IP (也称为边缘节点),前边挂载 lvs 的形式。
从网上找了个图,比较类似了,只是把左边的 Worker node 多部署几台,前边通过 lvs 实现负载均衡即可。

在某次业务问题排查中,定位到通过某个集群的 lvs vip 访问业务时,有一定的几率产生超时,于是就有了此次的踩坑之旅。
先用一个脚本查看访问 vip 的超时情况,发现是链接阶段超时:
TIME_OUT_FMT='\ntime_namelookup: %{time_namelookup}s\n time_connect: %{time_connect}s\n time_appconnect: %{time_appconnect}s\n time_pretransfer: %{time_pretransfer}s\n time_redirect: %{time_redirect}s\n time_starttransfer: %{time_starttransfer}s\n'
for n in {1..20} ;
do
echo $n;
# 这里 url 用业务的 vip 地址
curl -w "$TIME_OUT_FMT" -o /dev/null -s 'https://fukun.org'
done
根据之前另外一个集群的经验,怀疑是边缘节点容量不足导致的,但是查看边缘节点的网卡丢包情况,并未出现异常,不过还是先对此集群的边缘节点进行了扩容,但扩容之后并没有好转。
此时由于对业务任然在产生影响,为了止损,所以快速的通过 ingress-nginx 部署了一个直接对外的支持 HTTPS 的服务,切换到 ingress 后,业务检测正常,此时是下午6、7点钟,已经不是流量高峰,当到第二天流量早高峰时,由于 ingress 服务之前主要是内部业务之间调用,并未启用 HTTPS,虽然单机的 QPS 6800+ 相比之前的 6000+ 并没有增加多少,但是 nginx 活跃连接数却从之前的单机 700 飙升到了单机 8000, 此时 ingress 服务虽然还能用,但对延迟已经有影响了,对于内部大部分 100ms 级别的请求来说,超时的影响已经比较严重了。所以此时只好先快速的把流量切到其他机房,避免 ingress 影响扩大。

先看个图,从 Ipv6 的相关资讯可以看到,各大互联网公司都在推进 Ipv6 的部署和支持,这离不开工信部信通院(信息通信研究院)的大力推进。
Ipv6 的"好处"就不多说了,如果使用 Ipv6,每个设备都可以分配一个固定的地址,定位起来就方便多了,想想就很美(kong)好(bu)吧。
网络设施和软件的改进我们暂且不谈,就来谈下如何保障业务访问的连通性。
一谈到 Ipv6 ,大家印象里访问一个 Ipv6 的网站的过程是这样的:
通过 DNS 的 AAAA 解析获取到了域名对应的 Ipv6 的地址,然后就会通过 Ipv6 的地址访问网站,如果访问不通,业务就会挂掉,导致用户访问失败。
如果这个网址只支持 Ipv6 ,那就是这种现象,如果网址本身支持双栈,即同时支持 Ipv4 和 Ipv6 ,那么在某些环境下,会启用 Happy Eyeballs 算法,进行快速回退,使用 Ipv4 进行访问。
这个算法的提出是在2011年,最初推广 Ipv6 的时候,为了避免让大家担心使用 Ipv6 后如果网络不通,用户会流失。
今天在学习 极客时间的系列课程-《 Linux性能优化实战》时,通过Docker运行了一个容器,需要借助perf来分析性能瓶颈,正常启动后,在容器内通过apt-get install -y linux-tools安装好了perf工具,但是启动时,却提示以下信息:
┌─Error:───────────────────────────────────────────────────────────┐
│No permission to enable cycles event. │
│ │
│You may not have permission to collect system-wide stats. │
│ │
│Consider tweaking /proc/sys/kernel/perf_event_paranoid, │
│which controls use of the performance events system by │
│unprivileged users (without CAP_SYS_ADMIN). │
│ │
│The current value is 3: │
│ │
│ -1: Allow use of (almost) all events by all users │
│>= 0: Disallow raw tracepoint access by users without CAP_IOC_LOCK│
│>= 1: Disallow CPU event access by users without CAP_SYS_ADMIN │
│>= 2: Disallow kernel profiling by users without C │
│ │
│ │
│Press any key... │
└──────────────────────────────────────────────────────────────────┘
提示没有权限,需要更改 /proc/sys/kernel/perf_event_paranoid 文件,然后尝试执行:
$ echo 0 > /proc/sys/kernel/perf_event_paranoid
bash: /proc/sys/kernel/perf_event_paranoid: Read-only file system
解决办法:
需要使用 –privileged 参数,所以必须要重新启动容器,重新安装相关package.
启动容器时:docker run --privileged xxx 即可.
再次执行perf top -g -p 1385
查看分析效果


相关阅读:
通过前边四章,我们了解了HTTP/2的特性,以及如何在Go中利用HTTP/2的相关特性进行一些开发工作。本章作为一个收尾,我来谈一些自己对HTTP/2的理解,以及HTTP/2的应用前景展望,个人观点,不一定对,欢迎大家留言讨论。
先回顾下:
HTTP/2通过多种多种技术手段(如:多路复用,头部压缩,优先级等),极大的优化了HTTP的C/S双端的数据交互体验,解决很多以往HTTP/1.1协议本身不能解决的问题。
先来看下带宽和延迟对页面加载的影响,数据来源:HTTP/2 is here, let’s optimize! - Velocity SC 2015


相关阅读:
上篇文章我们了解了如何在HTTP/2 server端进行Header信息的发送,同时保持连接不断开。这次我们在这个基础上,实现自动下发PUSH。
先来实现一个最简单的Server Push的例子, 我们在上次的demo基础上继续改进
package main
import (
"html/template"
"log"
"net/http"
)
func main() {
http.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-custom-header", "custom header")
w.WriteHeader(http.StatusNoContent)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
select {}
})
// 用于push的 handler
http.HandleFunc("/crt", func(w http.ResponseWriter, r *http.Request) {
tpl := template.Must(template.ParseFiles("server.crt"))
tpl.Execute(w, nil)
})
// 请求该Path会触发Push
http.HandleFunc("/push", func(w http.ResponseWriter, r *http.Request) {
pusher, ok := w.(http.Pusher)
if !ok {
log.Println("not support server push")
} else {
err := pusher.Push("/crt", nil)
if err != nil {
log.Printf("Failed for server push: %v", err)
}
}
w.WriteHeader(http.StatusOK)
})
log.Println("start listen on 8080...")
log.Fatal(http.ListenAndServeTLS(":8080", "server.crt", "server.key", nil))
}
以上代码添加了两个Hanlder,一个是 /crt,返回我们的证书内容,这个是用来给做客户端push的内容。另一个是 /push,请求该链接时,我们会将 /crt 的内容主动 push 到客户端。
GO服务启动后,我们通过h2c来访问下/push :
先在一个终端通过 h2c start -d 启动进行输出显示,然后另外开一个终端窗口发起请求 h2c connect localhost:8080 和 h2c get /push :


相关阅读:
前边两章讲了很多HTTP/2概念性的东西,看起来比较无趣,从这次开始,我们从一些实际用途开始讲起。
本次讲一个非常简单的功能,然后把其内部实现串一下。
这次要实现的功能非常简单,就是一个http2的server,对客户端的请求,只返回一个header信息,并且保持连接,以便在后续任何时候进行一些其他的响应操作。目前看起来这个场景可能没有太大作用,其实HTTP/2做为一个超文本传输协议,目前我们能想到的应用场景还都是普通的web业务,但是老外们的思路就比较广,已经把一些HTTP/2的特性在特定的场景发挥出来了,比如 Amazon的Alexa,Apple的APNS 等。这次实现的这个小功能,就是Alexa里用到的一小部分.
Amazon的avs(Alexa Voice Service)通过HTTP/2实现了全双工的传输功能,其下行功能就用到了这块,Alexa跟avs建立链接后,客户端会发起一个GET /v20160207/directives的请求,服务端接受请求后,返回一个200的头信息,并hold住链接,后续使用该链接通过Server Push功能给客户端主动发送指令。
本次开始,我们先不管Server Push,先从发送Header这个小功能开始吧。
HTTP/2在GO语言的实现中没有支持h2c,所以我们必须使用带证书的加密方式,那么首先需要有一张证书。
我们可以使用openssl自己生成一张:
openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt
然后按提示随便输入一些内容就可以得到两个文件,server.key和server.crt,其实就是相当于私钥和公钥。当然这个证书是不能在互联网上正常流通使用的,因为证书是自己签发的,没有人给你做担保,能确认这个证书跟它所标识的内容提供方是匹配的。所以我们在做请求测试的时候,需要客户端忽略证书校验才可以。
服务端GO示例的代码如下:
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-custom-header", "custom header")
w.WriteHeader(http.StatusNoContent)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
select {}
})
log.Println("start listen on 8080...")
log.Fatal(http.ListenAndServeTLS(":8080", "server.crt", "server.key", nil))
}
服务运行起来后我们在一个较新的支持HTTP/2的curl命令下执行:
curl "https://localhost:8080/header" -k -i --http2
-k 参数表示忽略证书校验,避免客户端拿到证书后校验不通过而拒绝链接-i 参数表示显示返回的header信息--http2 表示启用http/2,这个参数也可以不带,因为客户端支持的话,会优先使用http/2去链接,服务端不支持的时候降级到http/1.1
这样就实现了只返回了一个header信息,并且链接没有断开。
我们再通过前边介绍过的h2c来看下请求的效果:

可以看到返回的只有一个Header信息,并且是没有END_STREAM标记的。
本次的实践内容到这里就可以结束了,最终实现的代码很简单,但是为什么这样可以实现呢,在缺少相关资料的情况下,很难知道这样做是可以实现该目的的,那么接下来就从Go语言中对HTTP/2的实现来一探究竟吧:
在http中设置超时,或在time.Sleep等方法中需要设置一个时间段时,如果使用 整型变量 * time.Second 之类的,就会报以下错误:
t := 3
t * time.Second
// mismatched types int and time.Duration
但是如果使用 整型数字 * time.Second,就没有问题
3 * time.Second
// ok,没问题。
那怎么使用变量进行超时设置呢,解决办法就是使用 time.Duration:
t := 3
t.Duration(t) * time.Second
//ok,没有问题了
为什么会这样呢,我们可以看下time.Duration的定义
type Duration int64
其实就是一个int类型
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
而 time.Second 之类的常量也是 Druation 类型,所以,如果是变量进行相乘的计算时,两个参数必须都是time.Druation类型的;当是一个数字时,默认是可以转成time.Duration来进行计算的。

相关阅读:
上一篇文章中介绍了HTTP/2的二进制分帧和多路复用的特性,这次来介绍下头部压缩和服务端推送。
在HTTP/1.x中,每次HTTP请求都会携带需要的header信息,这些信息以纯文本形式传递,所以每次的请求和响应,都会浪费一些带宽,如果header信息中包含cookie等之类的信息,那么浪费的带宽就更可观了。为了减少带宽开销和提升性能,HTTP/2 使用 HPACK 压缩格式压缩请求和响应标头元数据,这种格式采用两种简单但是强大的技术:
Huffman 编码对传输的header字段进行编码,从而减小了传输的大小。利用 Huffman 编码,可以在传输时对各个值进行压缩,而利用之前传输值的索引列表,我们可以通过传输索引值的方式对重复值进行编码,索引值可用于有效查询和重构完整的标头键值对。
客户端和服务端都有一个内置的静态表,部分内容如下:
静态表
+-------+-----------------------------+---------------+
| Index | Header Name | Header Value |
+-------+-----------------------------+---------------+
| 1 | :authority | |
| 2 | :method | GET |
| 3 | :method | POST |
| 4 | :path | / |
| 5 | :path | /index.html |
| 6 | :scheme | http |
| 7 | :scheme | https |
| 8 | :status | 200 |
| 9 | :status | 204 |
| 10 | :status | 206 |
| 11 | :status | 304 |
| 12 | :status | 400 |
| 13 | :status | 404 |
| 14 | :status | 500 |
| 15 | accept-charset | |
| 16 | accept-encoding | gzip, deflate |
| 17 | accept-language | |
...
| 58 | user-agent | |
| 59 | vary | |
| 60 | via | |
| 61 | www-authenticate | |
+-------+-----------------------------+---------------+
可以看到,部分静态表已经包含了value,比如 Index=2 的 :method = GET,当客户端发起请求时,如果发起的是GET请求,那么只需要在Header信息中携带一个Index=2的索引即可,服务端收到通过静态表即可查出对应的请求头信息。
在静态表中传输的Header Block是这种格式的:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 1 | Index (7+) |
+---+---------------------------+
从图中可以看到,只需要8-bit即可实现一个method的Header传输:

对于静态表中不存在value的值,或者value的值跟想传递的值不一样时,就不能只传递简单的Index了;比如对于:path的头信息,如果要请求的path不在静态表里,就需要用到 Huffman 编码 了。

最近由于做一些相关项目,需要使用到HTTP/2的一些特性,花了两天的时间看了下HTTP/2的RFC-7540 的文档,又花了一天时间看了下go语言中http server中对HTTP/2的实现,做一些笔记,记录一些心得。内容比较多,会分多篇写,具体是几篇,看情况定吧。
先来看一下什么是HTTP/2,为什么不是HTTP/1.2?HTTP/2 没有改动 HTTP 的应用语义。HTTP 方法、状态代码、URI 等概念都跟HTTP/1.1一样,但是HTTP/2在数据传输过程中做了二进制分帧(frame)处理,这点跟之前不一样,通过分帧,HTTP/2对我们的应用隐藏了其复杂性,达到了既能支持一些新特性,又能兼容之前的所有应用。所以,如果我们是跟之前一样,做一些普通的web应用,对HTTP/2的使用跟HTTP/1没有任何区别。但如果我们希望能利用到HTTP/2的一些新特性,就需要对它有一些更深入的了解。
HTTP/2最革命性的原因就在于这个二进制分帧了,要了解二进制分帧在客户端和服务端传输的过程,需要了解三个概念:
HTTP/2里边,这些概念的关系是这样的:
简言之,HTTP/2 将 HTTP 协议通信分解为二进制编码Frame的交换,这些Frame对应着特定Stream中的Message。所有这些都在一个 TCP 连接内复用。这是 HTTP/2 协议所有其他功能和性能优化的基础。
639 posts found