已发表的技术专栏
0 grpc-go、protobuf、multus-cni 技术专栏 总入口
1 grpc-go 源码剖析与实战 文章目录
2 Protobuf介绍与实战 图文专栏 文章目录
3 multus-cni 文章目录(k8s多网络实现方案)
4 grpc、oauth2、openssl、双向认证、单向认证等专栏文章目录)
本篇文章我们从源码的视角来分析重试机制的原理。
1、源码分析入口 |
进入grpc-go/stream.go文件中的withRetry方法里
1.func (cs *clientStream) withRetry(op func(a *csAttempt) error, onSuccess func()) error {
2. cs.mu.Lock()
3. for {
4. if cs.committed {
5. cs.mu.Unlock()
6. return op(cs.attempt)
7. }
8. a := cs.attempt
9. cs.mu.Unlock()
10. err := op(a)
11. cs.mu.Lock()
12. if a != cs.attempt {
13. continue
14. }
15. if err == io.EOF {
16. <-a.s.Done()
17. }
18. if err == nil || (err == io.EOF && a.s.Status().Code() == codes.OK) {
19. onSuccess()
20. cs.mu.Unlock()
21. return err
22. }
23. if err := cs.retryLocked(err); err != nil {
24. cs.mu.Unlock()
25. return err
26. }
27. }
28.}
withRetry主要流程说明
其实,代码实现主体思路是:
只要执行op成功后,就会执行onSuccess()函数,(切面操作)
如果传递的onSuccess函数,是bufferForRetryLocked函数,就具备了重试功能
如果传递的onSuccess函数,是commitAttemptLocked函数,就不具备重试功能
如果传递的onSuccess函数,是其他函数的,可能就会有其他功能了
注意:
withRetry属于clientStream,属于客户端的;
并没有发现服务器端也有类似的重试机制功能;
服务器端将执行结果反馈给客户端时,并没有使用重试机制。
2、重试机制withRetry实现方式的特点 |
该方法withRetry的特点:
3、当业务执行失败时,重试机制retryLocked如何处理此种情况? |
进入retryLocked方法里:
1.func (cs *clientStream) retryLocked(lastErr error) error {
2. for {
3. cs.attempt.finish(lastErr)
4. if err := cs.shouldRetry(lastErr); err != nil {
5. cs.commitAttemptLocked()
6. return err
7. }
8. cs.firstAttempt = false
9. if err := cs.newAttemptLocked(nil, nil); err != nil {
10. return err
11. }
12. if lastErr = cs.replayBufferLocked(); lastErr == nil {
13. return nil
14. }
15. }
16.}
主流程说明:
3.1、假设某一操作失败了,客户端是如何判断是不是允许重试呢? |
进入grpc-go/stream.go文件中的shouldRetry方法里:
1.// shouldRetry returns nil if the RPC should be retried; otherwise it returns
2.// the error that should be returned by the operation.
3.func (cs *clientStream) shouldRetry(err error) error {
4. unprocessed := false
5. if cs.attempt.s == nil {
6. pioErr, ok := err.(transport.PerformedIOError)
7. if ok {
8. // Unwrap error.
9. err = toRPCErr(pioErr.Err)
10. } else {
11. unprocessed = true
12. }
13. if !ok && !cs.callInfo.failFast {
14. return nil
15. }
16. }
17. if cs.finished || cs.committed {
18. return err
19. }
20. if cs.attempt.s != nil {
21. <-cs.attempt.s.Done()
22. unprocessed = cs.attempt.s.Unprocessed()
23. }
24. if cs.firstAttempt && unprocessed {
25. return nil
26. }
27. if cs.cc.dopts.disableRetry {
28. return err
29. }
30. pushback := 0
31. hasPushback := false
32. if cs.attempt.s != nil {
33. //------省略掉跟Trailer相关的代码,暂不分析
34. }
35. var code codes.Code
36. if cs.attempt.s != nil {
37. code = cs.attempt.s.Status().Code()
38. } else {
39. code = status.Convert(err).Code()
40. }
41. rp := cs.methodConfig.retryPolicy
42. if rp == nil || !rp.retryableStatusCodes[code] {
43. return err
44. }
45. if cs.retryThrottler.throttle() {
46. return err
47. }
48. if cs.numRetries+1 >= rp.maxAttempts {
49. return err
50. }
51. var dur time.Duration
52. if hasPushback {
53. dur = time.Millisecond * time.Duration(pushback)
54. cs.numRetriesSincePushback = 0
55. } else {
56. fact := math.Pow(rp.backoffMultiplier, float64(cs.numRetriesSincePushback))
57. cur := float64(rp.initialBackoff) * fact
58. if max := float64(rp.maxBackoff); cur > max {
59. cur = max
60. }
61. dur = time.Duration(grpcrand.Int63n(int64(cur)))
62. cs.numRetriesSincePushback++
63. }
64. t := time.NewTimer(dur)
65. select {
66. case <-t.C:
67. cs.numRetries++
68. return nil
69. case <-cs.ctx.Done():
70. t.Stop()
71. return status.FromContextError(cs.ctx.Err()).Err()
72. }
73.}
shouldRetry方法:
主要流程说明:
本方法就做了一件事,就是从不同的角度来分析,允不允许进行重试
3.2、当本次操作失败了,客户端如何重试前几步的操作呢? |
进入grpc-go/stream.go文件中的replayBufferLocked方法里:
1.func (cs *clientStream) replayBufferLocked() error {
2. a := cs.attempt
3. for _, f := range cs.buffer {
4. if err := f(a); err != nil {
5. return err
6. }
7. }
8. return nil
9.}
第3-7行:依次执行存储在cs.buffer里的函数,就是将以前执行成功的业务逻辑,再重新执行一次
4、假设客户端发送数据阶段失败时,整体模拟一遍,看看重试机制是如何处理的? |
看完前面的分析后,可能不是很理解整个流程,现在我们从头模拟一次:
假设发送数据失败了,发送数据函数为op代码如下:
op := func(a *csAttempt) error {
err := a.sendMsg(m, hdr, payload, data)
m, data = nil, nil
return err
}
err = cs.withRetry(op, func() { cs.bufferForRetryLocked(len(hdr)+len(payload), op) })
模拟流程如下:
客户端执行流的创建,创建流的函数为op, 在withRetry方法里的第10行执行op, 执行成功后,通过withRetry方法里的onSuccess()函数,即bufferForRetryLocked方法,将创建流的函数op存储到切片cs.buffer里
接下来,客户端开始调用传输数据的函数op,
在withRetry方法里的第10行执行op, 假设执行失败后,开始执行第23行retryLocked方法
进入shouldRetry方法里,判断是否允许重试
当执行某个操作成功时,将当前操作缓存起来,如依次存储到切片里;
等执行某个操作失败时,进行重试:
先对缓存里的函数依次执行一次,
最后再执行刚才执行失败的操作
下一篇文章
元数据相关介绍?