已发表的技术专栏
0 grpc-go、protobuf、multus-cni 技术专栏 总入口
1 grpc-go 源码剖析与实战 文章目录
2 Protobuf介绍与实战 图文专栏 文章目录
3 multus-cni 文章目录(k8s多网络实现方案)
4 grpc、oauth2、openssl、双向认证、单向认证等专栏文章目录)
本篇文章先从服务器端一侧介绍保持链接的相关原理。
1、服务器端keepalive原理图 |
服务器端的keepalive功能,存在3种类型的定时器,这3种定时器主要是对三种情况进行处理:
keepalive功能是select+timer.NewTimer来实现的。
其中, timer.NewTimer设置的定时时长,是可以动态调整的。
2、服务器端keepalive源码分析 |
2.1.服务器端何时触发keepalive功能启动呢? |
在服务器端完成客户端的链接请求后,即客户端跟服务器端双方底层建立起链接了,服务器端以协程的方式,启动了keepalive功能。
服务器端一侧的方法调用链:
main.go→Serve→handleRawConn→newHTTP2Transport→NewServerTransport→newHTTP2Server
进入grpc-go/internal/transport/http2_server.go文件中的newHTTP2Server方法里:
1.func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err error) {
2. // ---省略不相关代码---如http2Server的创建 ----接收客户端的帧
3. // ---省略不相关代码---帧发送器的创建
4.
5. go t.keepalive()
6. return t, nil
7.}
第5行,就是启动keepalive功能
2.2、keepalive源码分析 |
进入grpc-go/internal/transport/http2_server.go文件中的keepalive方法里
1.func (t *http2Server) keepalive() {
2. p := &ping{}
3.
4. outstandingPing := false
5. kpTimeoutLeft := time.Duration(0)
6. prevNano := time.Now().UnixNano()
7.
8. idleTimer := time.NewTimer(t.kp.MaxConnectionIdle)
9. ageTimer := time.NewTimer(t.kp.MaxConnectionAge)
10. kpTimer := time.NewTimer(t.kp.Time)
11. defer func() {
12. idleTimer.Stop()
13. ageTimer.Stop()
14. kpTimer.Stop()
15. }()
16. for {
17. select {
18. case <-idleTimer.C:
19. t.mu.Lock()
20. idle := t.idle
21. if idle.IsZero() { // The connection is non-idle.
22. t.mu.Unlock()
23. idleTimer.Reset(t.kp.MaxConnectionIdle)
24. continue
25. }
26. val := t.kp.MaxConnectionIdle - time.Since(idle)
27. t.mu.Unlock()
28. if val <= 0 {
29. t.drain(http2.ErrCodeNo, []byte{})
30. return
31. }
32.
33. idleTimer.Reset(val)
34. case <-ageTimer.C:
35. t.drain(http2.ErrCodeNo, []byte{})
36. ageTimer.Reset(t.kp.MaxConnectionAgeGrace)
37. select {
38. case <-ageTimer.C:
39. t.Close()
40. case <-t.done:
41. }
42. return
43. case <-kpTimer.C:
44. lastRead := atomic.LoadInt64(&t.lastRead)
45.
46. if lastRead > prevNano {
47. outstandingPing = false
48. kpTimer.Reset(time.Duration(lastRead) + t.kp.Time - time.Duration(time.Now().UnixNano()))
49. prevNano = lastRead
50. continue
51. }
52. if outstandingPing && kpTimeoutLeft <= 0 {
53. t.Close()
54. return
55. }
56. if !outstandingPing {
57. if channelz.IsOn() {
58. atomic.AddInt64(&t.czData.kpCount, 1)
59. }
60. t.controlBuf.put(p)
61. kpTimeoutLeft = t.kp.Timeout
62. outstandingPing = true
63. }
64.
65. sleepDuration := minTime(t.kp.Time, kpTimeoutLeft)
66. kpTimeoutLeft -= sleepDuration
67. kpTimer.Reset(sleepDuration)
68. case <-t.done:
69. return
70. }
71. }
72.}
主要处理逻辑说明
2.2.1、当链接处于idle的时长,超过了规定的时长时,服务器端如何处理? |
服务器端处理idle时的逻辑流程图? |
可以按照下面的思路去理解,就很容易理解: 两个大的分支
好,继续分析源码:
keepalive源码重点分析:针对的是第18-33行的功能: |
2.2.2、当一个链接超过规定的运行时长时,服务器端如何处理? |
服务器端针对超过运行时长链接的处理逻辑流程图? |
优雅关闭?
给客户端发送了goAway帧,先等待一下,等待时长t.kp.MaxConnectionAgeGrace,这时间后,再关闭服务器端链接。
就是说,先让客户端处理一会,服务器端再处理。
只要超过运行时长,就必须关闭链接。
keepalive源码重点分析:针对的是第35-42行的功能: |
实际测试时,如何测试让ageTimer.C先执行,还是让先t.done执行? |
只关注服务器端,测试用例为examples/features/keepalive/server/main.go。
我本地测试是,grpc 客户端 跟grpc服务器端都是在同一个物理机上做的测试。
1.如何让keepalive,先执行第40行的t.done语句呢? |
其实,不需要修改任何代码,直接用main.go文件中的测试用例即可。
因为测试用例中,客户端接收到goAway帧后,很快就关闭了,耗时远远小于MaxConnectionAgeGrace的时长
进入grpc-go/internal/transport/http2_server.go文件中的HandleStreams方法里:
1.func (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context.Context, string) context.Context) {
2.for {
3. t.controlBuf.throttle()
4. frame, err := t.framer.fr.ReadFrame()
5. atomic.StoreInt64(&t.lastRead, time.Now().UnixNano())
6. if err != nil {
7. //---省去---不相关代码---
8. if err == io.EOF || err == io.ErrUnexpectedEOF {
9. t.Close()
10. return
11. }
12. t.Close()
13. return
14. }
第8行,接收到EOF后,就关闭连接了。
2.如何让keepalive,先执行第38行的ageTimer.C语句呢? |
方式有很多。可以修改MaxConnectionAgeGrace的参数值,如10*time.Millisecond;
var kasp = keepalive.ServerParameters{
MaxConnectionIdle: 5 * time.Second,
MaxConnectionAge: 10 * time.Second,
MaxConnectionAgeGrace: 10 * time.Millisecond,
Time: 5 * time.Second,
Timeout: 1 * time.Second,
}
这样的话,客户端正处于关闭连接状态时,而服务器端的优雅关闭时间已到期了,就主动调用第39行,t.close()关闭连接了。
2.2.3、如何来验证服务器端跟客户端还保持着链接?如果链接已经挂掉的话,服务器端如何处理 |
核心思路就是: |
判断服务器端在规定的时间段内,如5秒里,是否接收到过数据,或者说任何类型的帧;
接收到过,就表明链接存在。
没有接收到过,表明链接存在异常了。
简单的理解,就是:(或者说,整个核心思路,可以用下面的话来总结)
keepalive源码重点分析:针对的是:44-67行的功能 |
lastRead,表示的是最后一次接收到帧的时间
prevNano,表示的是接收到前一个帧的时间
若lastRead > prevNano的话,说明在接收到前一个帧后,在规定的时间内,又接收到了新的帧,时间是lastRead;即表明,服务器端跟客户端还保持着链接
第47行:outstandingPing重置为false,表示的是服务器端没有向客户端发送ping帧
第48行:重置kpTimer定时器的时长
第49行:更新prevNano时间为lastRead时间
第50行:退出case分支,继续keepalive功能
下一篇文章
客户端keepalive原理图介绍以及源码分析
点击下面的图片,返回到专栏大纲 |