你知道是什么职位么?英文缩写=ui,ue,fe,rd,op,db,pm
ui 是什么职位? ui 就是用户设计
ue 是什么职位? ue用户体验
fe 是什么职位? fe前端研发
rd是什么职位? rd程序员
op是什么职位? op运维
db是什么职位? db数据库
pm是什么职位? pm产品经理
bd是什么职位? bd商务拓展

ui 是什么职位? ui 就是用户设计
ue 是什么职位? ue用户体验
fe 是什么职位? fe前端研发
rd是什么职位? rd程序员
op是什么职位? op运维
db是什么职位? db数据库
pm是什么职位? pm产品经理
bd是什么职位? bd商务拓展

熟悉 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