Golang倒腾一款简配的具有请求列队功能的并发受限服务器 ...

打印 上一主题 下一主题

主题 1583|帖子 1583|积分 4749


  
golang官方指南[1]给了一些代码片断,层层递进演示了信道的能力:

  
1>. 信号量
2>. 限流能力

  1. var sem = make(chan int, MaxOutstanding) 
  2.  
  3. func Serve(queue chan *Request) {
  4.     for req := range queue {
  5.         req:= req
  6.         sem <- 1   
  7.         go func() {   // 只会开启MaxOutstanding个并发协程
  8.             process(req)
  9.             <-sem
  10.         }()
  11.     }
  12. }
复制代码
上面出现了两个信道:

  
① sem 提供了限制服务端并发处理请求的信号量
② queue 提供了一个客户端请求队列,起媒介/解耦的作用

  
  
进一步指南给出了信道的另一个用法: 3>. 解多路复用

  
多路复用是网络编程中一个耳熟能详的概念,nginx redis等高性能web、内存kv都用到了这个技术 。

  
这个解多路复用是怎么理解呢?

  
离散/独立/并发的客户端请求被服务端Serve收敛之后, Serve就起到了多路复用的概念,在Request定义resultChan信道,就给每个客户端请求提供了独立获取请求效果的能力,这便是一种解多路复用。

  
  
从现实效果看这就是常见的互联网web服务器:一款具备请求列队功能的并发限流服务器

  
  
官方指南并没有完整实现客户端和服务器端工程。

  
下面是我的工程化实现, 记录实践中遇到的题目。

  并发受限服务器

  

  • 信道queue接收客户端请求,解耦客户端和服务器,天然具备列队能力

  • 信号量信道sem提供了并发受限的能力

  • 服务器处理完,向解多路复用信道req.resultChan写入响应效果。

  1. /* 实现一个有请求队列功能的并发请求受限服务器*/
  2. package main
  3. import (
  4.  "fmt"
  5.  "sync"
  6.  "time"
  7. )
  8. var sem = make(chan int, Maxoutstanding)
  9. var wg2 sync.WaitGroup
  10. func server(queue chan *Request) {
  11.  fmt.Printf("Server is already, listen req \n")
  12.  for req := range queue {
  13.  req := req
  14.  sem <- 1
  15.  wg2.Add(1)
  16.  go func() {
  17.  defer wg2.Done()
  18.  process(req)
  19.  <-sem
  20.  }()
  21.  }
  22. }
  23. func process(req *Request) {
  24.  s := sum(req.args)
  25.  req.resultChan <- s
  26. }
  27. func sum(a []int) (s int) {
  28.  for i := 1; i <= a[0]; i++ {
  29.  s += i
  30.  }
  31.  time.Sleep(time.Millisecond * 20)
  32.  return s
  33. }
复制代码
      
time.Sleep模仿服务器处理请求单次耗时20ms, 输出数字的累加,
eg: input: 100;
output: (1+100)/2*100 =5050

   
wg2 sync.WaitGroup是一个动态活跃的Goroutine计数器,注意用法和位置,wg2的作用是:等候所有请求处理完成。

  并发客户端请求

  
for循环开启并发客户端请求,

  

  • 每个请求入驻一个独立的Goroutine,独立向信道queue投递请求和接收响应

  1. package main
  2. import (
  3.  "fmt"
  4.  "sync"
  5. )
  6. type Request struct {
  7.  args       []int
  8.  resultChan chan int
  9. }
  10. var wg1 sync.WaitGroup
  11. func clients() {
  12.  fmt.Printf("start %d concurrency client request\n ", concurrencyClients)
  13.  for i := 1; i <= concurrencyClients; i++ {
  14.  r := &Request{
  15.  args:       []int{i},
  16.  resultChan: make(chan int),
  17.  }
  18.  wg1.Add(1)
  19.  go ClientReq(r)
  20.  }
  21.  wg1.Wait() 
  22. }
  23. func ClientReq(r *Request) {
  24.  defer wg1.Done()
  25.  queue <- r
  26.  go func() {
  27.  res := <-r.resultChan
  28.  fmt.Printf("current args is %d, the result is %d \n", r.args[0], res)
  29.  }()
  30. }
复制代码
wg1 WaitGroup的目的是确保所有的客户端请求都已经发出,之后客户端任务结束,所以此处我们新开Goroutine处理响应效果(这里又有闭包的参与)。

  工程化

  
工程化代码的先后序次,决定了代码是否死锁。
server需要处于监听状态,故先启动。

        
本处clients在主协程整体上是同步发送,假如放在clients()的反面,clients内的wg1可能会有部门请求Goroutine阻塞在信道queue, 且没法唤醒, 运行时会检测出报死锁。

   
  1. package main
  2. import (
  3.  "fmt"
  4.  "time"
  5. )
  6. var concurrencyClients = 1000
  7. var queueLength = 100
  8. var queue = make(chan *Request, queueLength) // 请求队列长度
  9. var Maxoutstanding int = 10                  // 服务器并发受限10
  10. func main() {
  11.  go server(queue)
  12.  var start = time.Now()
  13.  clients() // 确保所有的请求都已经发出去
  14.  wg2.Wait() // 确保服务器处理完所有的请求
  15.  fmt.Printf("客户端并发%d请求,服务器请求队列长度%d,服务器限流%d,总共耗时%d ms \n", concurrencyClients, queueLength, Maxoutstanding, time.Since(start).Milliseconds())
  16. }
复制代码
上面出现了3个设置变量
1>.  客户端并发请求数目concurrencyClients=100
2>.  服务器列队队列长度queueLength=100, 会作用到信道queue
3>.  服务器并发受限阈值Maxoutstanding=10

  1. start 1000 concurrency client request
  2.  Server is already, listen req 
  3. current args is 14, the result is 105 
  4. current args is 2, the result is 3 
  5. current args is 3, the result is 6 
  6. current args is 1, the result is 1 
  7. current args is 4, the result is 10 
  8. current args is 8, the result is 36 
  9. current args is 6, the result is 21 
  10. current args is 12, the result is 78 
  11. current args is 5, the result is 15 
  12. current args is 7, the result is 28 
  13. current args is 18, the result is 171 
  14. current args is 16, the result is 136 
  15. current args is 15, the result is 120 
  16. current args is 20, the result is 210 
  17. current args is 19, the result is 190 
  18. current args is 13, the result is 91 
  19. current args is 21, the result is 231 
  20. current args is 10, the result is 55 
  21. current args is 17, the result is 153 
  22. current args is 9, the result is 45 
  23. current args is 22, the result is 253 
  24. current args is 28, the result is 406 
  25. current args is 27, the result is 378 
  26. current args is 11, the result is 66 
  27. current args is 26, the result is 351 
  28. current args is 30, the result is 465 
  29. current args is 23, the result is 276 
  30. current args is 25, the result is 325 
  31. current args is 29, the result is 435 
  32. current args is 24, the result is 300 
  33. current args is 31, the result is 496 
  34. current args is 34, the result is 595 
  35. current args is 38, the result is 741 
  36. current args is 36, the result is 666 
  37. current args is 41, the result is 861 
  38. current args is 32, the result is 528 
  39. current args is 35, the result is 630 
  40. current args is 33, the result is 561 
  41. current args is 37, the result is 703 
  42. current args is 39, the result is 780 
  43. current args is 52, the result is 1378 
  44. current args is 46, the result is 1081 
  45. current args is 47, the result is 1128 
  46. current args is 49, the result is 1225 
  47. current args is 45, the result is 1035 
  48. current args is 43, the result is 946 
  49. current args is 48, the result is 1176 
  50. current args is 40, the result is 820 
  51. current args is 42, the result is 903 
  52. current args is 44, the result is 990 
  53. current args is 59, the result is 1770 
  54. current args is 55, the result is 1540 
  55. current args is 53, the result is 1431 
  56. current args is 57, the result is 1653 
  57. current args is 51, the result is 1326 
  58. current args is 54, the result is 1485 
  59. current args is 50, the result is 1275 
  60. current args is 56, the result is 1596 
  61. current args is 58, the result is 1711 
  62. current args is 60, the result is 1830 
  63. current args is 66, the result is 2211 
  64. current args is 63, the result is 2016 
  65. current args is 70, the result is 2485 
  66. current args is 62, the result is 1953 
  67. current args is 61, the result is 1891 
  68. current args is 65, the result is 2145 
  69. current args is 67, the result is 2278 
  70. current args is 64, the result is 2080 
  71. current args is 68, the result is 2346 
  72. current args is 69, the result is 2415 
  73. current args is 76, the result is 2926 
  74. current args is 77, the result is 3003 
  75. current args is 71, the result is 2556 
  76. current args is 80, the result is 3240 
  77. current args is 75, the result is 2850 
  78. current args is 74, the result is 2775 
  79. current args is 73, the result is 2701 
  80. current args is 72, the result is 2628 
  81. current args is 78, the result is 3081 
  82. current args is 81, the result is 3321 
  83. current args is 89, the result is 4005 
  84. current args is 83, the result is 3486 
  85. current args is 88, the result is 3916 
  86. current args is 82, the result is 3403 
  87. current args is 79, the result is 3160 
  88. current args is 86, the result is 3741 
  89. current args is 84, the result is 3570 
  90. current args is 90, the result is 4095 
  91. current args is 85, the result is 3655 
  92. current args is 87, the result is 3828 
  93. current args is 101, the result is 5151 
  94. current args is 92, the result is 4278 
  95. current args is 94, the result is 4465 
  96. current args is 93, the result is 4371 
  97. current args is 98, the result is 4851 
  98. current args is 91, the result is 4186 
  99. current args is 99, the result is 4950 
  100. current args is 100, the result is 5050 
  101. current args is 95, the result is 4560 
  102. current args is 96, the result is 4656 
  103. current args is 109, the result is 5995 
  104. current args is 107, the result is 5778 
  105. current args is 108, the result is 5886 
  106. current args is 102, the result is 5253 
  107. current args is 103, the result is 5356 
  108. current args is 106, the result is 5671 
  109. current args is 105, the result is 5565 
  110. current args is 104, the result is 5460 
  111. current args is 111, the result is 6216 
  112. current args is 97, the result is 4753 
  113. current args is 120, the result is 7260 
  114. current args is 112, the result is 6328 
  115. current args is 113, the result is 6441 
  116. current args is 114, the result is 6555 
  117. current args is 110, the result is 6105 
  118. current args is 119, the result is 7140 
  119. current args is 115, the result is 6670 
  120. current args is 117, the result is 6903 
  121. current args is 116, the result is 6786 
  122. current args is 118, the result is 7021 
  123. current args is 123, the result is 7626 
  124. current args is 122, the result is 7503 
  125. current args is 130, the result is 8515 
  126. current args is 121, the result is 7381 
  127. current args is 126, the result is 8001 
  128. current args is 129, the result is 8385 
  129. ......
复制代码
  1. current args is 988, the result is 488566 
  2. current args is 992, the result is 492528 
  3. current args is 976, the result is 476776 
  4. current args is 984, the result is 484620 
  5. current args is 995, the result is 495510 
  6. current args is 999, the result is 499500 
  7. current args is 1000, the result is 500500 
  8. current args is 990, the result is 490545 
  9. 客户端并发1000请求,服务器请求队列长度100,服务器限流10,总共耗时2099 ms
复制代码
读者可以随意调整3个参数的大小,来感受服务器调参的魅力。

  并发客户端请求数concurrencyClients
服务器请求队列queueLength
服务器限流阈值 Maxoutstanding
耗时ms
1000
100
10
2067
1000
100
50
454
1000
100
100
210
1000
300
10
2082
1000
500
10
2071
3000
100
10
6259
5000
500
10
10516
  
完整代码传送门[2]

  
That’s All,本文根据golang有关信道的指南, 实现了一个带有请求队列功能的并发受限服务器, 巩固了信道、WaitGroup的用法。

  参考资料
  [1] 
  
golang官方指南: https://go.dev/doc/effective_go

  [2]   
完整代码传送门: https://github.com/zwbdzb/go_sample1

  本篇文字和图片均为原创,读者可结合图片探索源码, 欢迎反馈 ~。。~。欢迎添加微信号 niumabujuan 交换撕逼。 
  

  
一种基于etcd实践节点主动故障转移的思绪

  
一次sql请求,返回分页数据和总条数

  
http请求超时,底层发生了什么?

  
两将军题目和TCP三次握手

  
字节二面:你怎么理解信道是Golang中的顶级公民

  三张大图剖析HttpClient和IHttpClientFactory在DNS剖析题目上的殊途同归  

  
点“
戳“在看


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

干翻全岛蛙蛙

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表