You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 lines
5.0 KiB

1 year ago
1 year ago
  1. package s3api
  2. import (
  3. "fmt"
  4. "github.com/gorilla/mux"
  5. "github.com/seaweedfs/seaweedfs/weed/filer"
  6. "github.com/seaweedfs/seaweedfs/weed/glog"
  7. "github.com/seaweedfs/seaweedfs/weed/pb"
  8. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  9. "github.com/seaweedfs/seaweedfs/weed/pb/s3_pb"
  10. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  11. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  12. "net/http"
  13. "sync"
  14. "sync/atomic"
  15. )
  16. type CircuitBreaker struct {
  17. sync.RWMutex
  18. Enabled bool
  19. counters map[string]*int64
  20. limitations map[string]int64
  21. }
  22. func NewCircuitBreaker(option *S3ApiServerOption) *CircuitBreaker {
  23. cb := &CircuitBreaker{
  24. counters: make(map[string]*int64),
  25. limitations: make(map[string]int64),
  26. }
  27. err := pb.WithFilerClient(false, 0, option.Filer, option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  28. content, err := filer.ReadInsideFiler(client, s3_constants.CircuitBreakerConfigDir, s3_constants.CircuitBreakerConfigFile)
  29. if err == filer_pb.ErrNotFound {
  30. glog.Infof("s3 circuit breaker not configured")
  31. return nil
  32. }
  33. if err != nil {
  34. return fmt.Errorf("read S3 circuit breaker config: %v", err)
  35. }
  36. return cb.LoadS3ApiConfigurationFromBytes(content)
  37. })
  38. if err != nil {
  39. glog.Infof("s3 circuit breaker not configured correctly: %v", err)
  40. }
  41. return cb
  42. }
  43. func (cb *CircuitBreaker) LoadS3ApiConfigurationFromBytes(content []byte) error {
  44. cbCfg := &s3_pb.S3CircuitBreakerConfig{}
  45. if err := filer.ParseS3ConfigurationFromBytes(content, cbCfg); err != nil {
  46. glog.Warningf("unmarshal error: %v", err)
  47. return fmt.Errorf("unmarshal error: %v", err)
  48. }
  49. if err := cb.loadCircuitBreakerConfig(cbCfg); err != nil {
  50. return err
  51. }
  52. return nil
  53. }
  54. func (cb *CircuitBreaker) loadCircuitBreakerConfig(cfg *s3_pb.S3CircuitBreakerConfig) error {
  55. //global
  56. globalEnabled := false
  57. globalOptions := cfg.Global
  58. limitations := make(map[string]int64)
  59. if globalOptions != nil && globalOptions.Enabled && len(globalOptions.Actions) > 0 {
  60. globalEnabled = globalOptions.Enabled
  61. for action, limit := range globalOptions.Actions {
  62. limitations[action] = limit
  63. }
  64. }
  65. cb.Enabled = globalEnabled
  66. //buckets
  67. for bucket, cbOptions := range cfg.Buckets {
  68. if cbOptions.Enabled {
  69. for action, limit := range cbOptions.Actions {
  70. limitations[s3_constants.Concat(bucket, action)] = limit
  71. }
  72. }
  73. }
  74. cb.limitations = limitations
  75. return nil
  76. }
  77. func (cb *CircuitBreaker) Limit(f func(w http.ResponseWriter, r *http.Request), action string) (http.HandlerFunc, Action) {
  78. return func(w http.ResponseWriter, r *http.Request) {
  79. if !cb.Enabled {
  80. f(w, r)
  81. return
  82. }
  83. vars := mux.Vars(r)
  84. bucket := vars["bucket"]
  85. rollback, errCode := cb.limit(r, bucket, action)
  86. defer func() {
  87. for _, rf := range rollback {
  88. rf()
  89. }
  90. }()
  91. if errCode == s3err.ErrNone {
  92. f(w, r)
  93. return
  94. }
  95. s3err.WriteErrorResponse(w, r, errCode)
  96. }, Action(action)
  97. }
  98. func (cb *CircuitBreaker) limit(r *http.Request, bucket string, action string) (rollback []func(), errCode s3err.ErrorCode) {
  99. //bucket simultaneous request count
  100. bucketCountRollBack, errCode := cb.loadCounterAndCompare(s3_constants.Concat(bucket, action, s3_constants.LimitTypeCount), 1, s3err.ErrTooManyRequest)
  101. if bucketCountRollBack != nil {
  102. rollback = append(rollback, bucketCountRollBack)
  103. }
  104. if errCode != s3err.ErrNone {
  105. return
  106. }
  107. //bucket simultaneous request content bytes
  108. bucketContentLengthRollBack, errCode := cb.loadCounterAndCompare(s3_constants.Concat(bucket, action, s3_constants.LimitTypeBytes), r.ContentLength, s3err.ErrRequestBytesExceed)
  109. if bucketContentLengthRollBack != nil {
  110. rollback = append(rollback, bucketContentLengthRollBack)
  111. }
  112. if errCode != s3err.ErrNone {
  113. return
  114. }
  115. //global simultaneous request count
  116. globalCountRollBack, errCode := cb.loadCounterAndCompare(s3_constants.Concat(action, s3_constants.LimitTypeCount), 1, s3err.ErrTooManyRequest)
  117. if globalCountRollBack != nil {
  118. rollback = append(rollback, globalCountRollBack)
  119. }
  120. if errCode != s3err.ErrNone {
  121. return
  122. }
  123. //global simultaneous request content bytes
  124. globalContentLengthRollBack, errCode := cb.loadCounterAndCompare(s3_constants.Concat(action, s3_constants.LimitTypeBytes), r.ContentLength, s3err.ErrRequestBytesExceed)
  125. if globalContentLengthRollBack != nil {
  126. rollback = append(rollback, globalContentLengthRollBack)
  127. }
  128. if errCode != s3err.ErrNone {
  129. return
  130. }
  131. return
  132. }
  133. func (cb *CircuitBreaker) loadCounterAndCompare(key string, inc int64, errCode s3err.ErrorCode) (f func(), e s3err.ErrorCode) {
  134. e = s3err.ErrNone
  135. if max, ok := cb.limitations[key]; ok {
  136. cb.RLock()
  137. counter, exists := cb.counters[key]
  138. cb.RUnlock()
  139. if !exists {
  140. cb.Lock()
  141. counter, exists = cb.counters[key]
  142. if !exists {
  143. var newCounter int64
  144. counter = &newCounter
  145. cb.counters[key] = counter
  146. }
  147. cb.Unlock()
  148. }
  149. current := atomic.LoadInt64(counter)
  150. if current+inc > max {
  151. e = errCode
  152. return
  153. } else {
  154. current := atomic.AddInt64(counter, inc)
  155. f = func() {
  156. atomic.AddInt64(counter, -inc)
  157. }
  158. if current > max {
  159. e = errCode
  160. return
  161. }
  162. }
  163. }
  164. return
  165. }