github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/service_config.go (about) 1 /* 2 * 3 * Copyright 2017 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package grpc 20 21 import ( 22 "encoding/json" 23 "errors" 24 "fmt" 25 "reflect" 26 "strconv" 27 "strings" 28 "time" 29 30 "github.com/hxx258456/ccgo/grpc/codes" 31 "github.com/hxx258456/ccgo/grpc/internal" 32 internalserviceconfig "github.com/hxx258456/ccgo/grpc/internal/serviceconfig" 33 "github.com/hxx258456/ccgo/grpc/serviceconfig" 34 ) 35 36 const maxInt = int(^uint(0) >> 1) 37 38 // MethodConfig defines the configuration recommended by the service providers for a 39 // particular method. 40 // 41 // Deprecated: Users should not use this struct. Service config should be received 42 // through name resolver, as specified here 43 // https://github.com/grpc/grpc/blob/master/doc/service_config.md 44 type MethodConfig = internalserviceconfig.MethodConfig 45 46 type lbConfig struct { 47 name string 48 cfg serviceconfig.LoadBalancingConfig 49 } 50 51 // ServiceConfig is provided by the service provider and contains parameters for how 52 // clients that connect to the service should behave. 53 // 54 // Deprecated: Users should not use this struct. Service config should be received 55 // through name resolver, as specified here 56 // https://github.com/grpc/grpc/blob/master/doc/service_config.md 57 type ServiceConfig struct { 58 serviceconfig.Config 59 60 // LB is the load balancer the service providers recommends. The balancer 61 // specified via grpc.WithBalancerName will override this. This is deprecated; 62 // lbConfigs is preferred. If lbConfig and LB are both present, lbConfig 63 // will be used. 64 LB *string 65 66 // lbConfig is the service config's load balancing configuration. If 67 // lbConfig and LB are both present, lbConfig will be used. 68 lbConfig *lbConfig 69 70 // Methods contains a map for the methods in this service. If there is an 71 // exact match for a method (i.e. /service/method) in the map, use the 72 // corresponding MethodConfig. If there's no exact match, look for the 73 // default config for the service (/service/) and use the corresponding 74 // MethodConfig if it exists. Otherwise, the method has no MethodConfig to 75 // use. 76 Methods map[string]MethodConfig 77 78 // If a retryThrottlingPolicy is provided, gRPC will automatically throttle 79 // retry attempts and hedged RPCs when the client’s ratio of failures to 80 // successes exceeds a threshold. 81 // 82 // For each server name, the gRPC client will maintain a token_count which is 83 // initially set to maxTokens, and can take values between 0 and maxTokens. 84 // 85 // Every outgoing RPC (regardless of service or method invoked) will change 86 // token_count as follows: 87 // 88 // - Every failed RPC will decrement the token_count by 1. 89 // - Every successful RPC will increment the token_count by tokenRatio. 90 // 91 // If token_count is less than or equal to maxTokens / 2, then RPCs will not 92 // be retried and hedged RPCs will not be sent. 93 retryThrottling *retryThrottlingPolicy 94 // healthCheckConfig must be set as one of the requirement to enable LB channel 95 // health check. 96 healthCheckConfig *healthCheckConfig 97 // rawJSONString stores service config json string that get parsed into 98 // this service config struct. 99 rawJSONString string 100 } 101 102 // healthCheckConfig defines the go-native version of the LB channel health check config. 103 type healthCheckConfig struct { 104 // serviceName is the service name to use in the health-checking request. 105 ServiceName string 106 } 107 108 type jsonRetryPolicy struct { 109 MaxAttempts int 110 InitialBackoff string 111 MaxBackoff string 112 BackoffMultiplier float64 113 RetryableStatusCodes []codes.Code 114 } 115 116 // retryThrottlingPolicy defines the go-native version of the retry throttling 117 // policy defined by the service config here: 118 // https://github.com/grpc/proposal/blob/master/A6-client-retries.md#integration-with-service-config 119 type retryThrottlingPolicy struct { 120 // The number of tokens starts at maxTokens. The token_count will always be 121 // between 0 and maxTokens. 122 // 123 // This field is required and must be greater than zero. 124 MaxTokens float64 125 // The amount of tokens to add on each successful RPC. Typically this will 126 // be some number between 0 and 1, e.g., 0.1. 127 // 128 // This field is required and must be greater than zero. Up to 3 decimal 129 // places are supported. 130 TokenRatio float64 131 } 132 133 func parseDuration(s *string) (*time.Duration, error) { 134 if s == nil { 135 return nil, nil 136 } 137 if !strings.HasSuffix(*s, "s") { 138 return nil, fmt.Errorf("malformed duration %q", *s) 139 } 140 ss := strings.SplitN((*s)[:len(*s)-1], ".", 3) 141 if len(ss) > 2 { 142 return nil, fmt.Errorf("malformed duration %q", *s) 143 } 144 // hasDigits is set if either the whole or fractional part of the number is 145 // present, since both are optional but one is required. 146 hasDigits := false 147 var d time.Duration 148 if len(ss[0]) > 0 { 149 i, err := strconv.ParseInt(ss[0], 10, 32) 150 if err != nil { 151 return nil, fmt.Errorf("malformed duration %q: %v", *s, err) 152 } 153 d = time.Duration(i) * time.Second 154 hasDigits = true 155 } 156 if len(ss) == 2 && len(ss[1]) > 0 { 157 if len(ss[1]) > 9 { 158 return nil, fmt.Errorf("malformed duration %q", *s) 159 } 160 f, err := strconv.ParseInt(ss[1], 10, 64) 161 if err != nil { 162 return nil, fmt.Errorf("malformed duration %q: %v", *s, err) 163 } 164 for i := 9; i > len(ss[1]); i-- { 165 f *= 10 166 } 167 d += time.Duration(f) 168 hasDigits = true 169 } 170 if !hasDigits { 171 return nil, fmt.Errorf("malformed duration %q", *s) 172 } 173 174 return &d, nil 175 } 176 177 type jsonName struct { 178 Service string 179 Method string 180 } 181 182 var ( 183 errDuplicatedName = errors.New("duplicated name") 184 errEmptyServiceNonEmptyMethod = errors.New("cannot combine empty 'service' and non-empty 'method'") 185 ) 186 187 func (j jsonName) generatePath() (string, error) { 188 if j.Service == "" { 189 if j.Method != "" { 190 return "", errEmptyServiceNonEmptyMethod 191 } 192 return "", nil 193 } 194 res := "/" + j.Service + "/" 195 if j.Method != "" { 196 res += j.Method 197 } 198 return res, nil 199 } 200 201 // TODO(lyuxuan): delete this struct after cleaning up old service config implementation. 202 type jsonMC struct { 203 Name *[]jsonName 204 WaitForReady *bool 205 Timeout *string 206 MaxRequestMessageBytes *int64 207 MaxResponseMessageBytes *int64 208 RetryPolicy *jsonRetryPolicy 209 } 210 211 // TODO(lyuxuan): delete this struct after cleaning up old service config implementation. 212 type jsonSC struct { 213 LoadBalancingPolicy *string 214 LoadBalancingConfig *internalserviceconfig.BalancerConfig 215 MethodConfig *[]jsonMC 216 RetryThrottling *retryThrottlingPolicy 217 HealthCheckConfig *healthCheckConfig 218 } 219 220 func init() { 221 internal.ParseServiceConfigForTesting = parseServiceConfig 222 } 223 func parseServiceConfig(js string) *serviceconfig.ParseResult { 224 if len(js) == 0 { 225 return &serviceconfig.ParseResult{Err: fmt.Errorf("no JSON service config provided")} 226 } 227 var rsc jsonSC 228 err := json.Unmarshal([]byte(js), &rsc) 229 if err != nil { 230 logger.Warningf("grpc: parseServiceConfig error unmarshaling %s due to %v", js, err) 231 return &serviceconfig.ParseResult{Err: err} 232 } 233 sc := ServiceConfig{ 234 LB: rsc.LoadBalancingPolicy, 235 Methods: make(map[string]MethodConfig), 236 retryThrottling: rsc.RetryThrottling, 237 healthCheckConfig: rsc.HealthCheckConfig, 238 rawJSONString: js, 239 } 240 if c := rsc.LoadBalancingConfig; c != nil { 241 sc.lbConfig = &lbConfig{ 242 name: c.Name, 243 cfg: c.Config, 244 } 245 } 246 247 if rsc.MethodConfig == nil { 248 return &serviceconfig.ParseResult{Config: &sc} 249 } 250 251 paths := map[string]struct{}{} 252 for _, m := range *rsc.MethodConfig { 253 if m.Name == nil { 254 continue 255 } 256 d, err := parseDuration(m.Timeout) 257 if err != nil { 258 logger.Warningf("grpc: parseServiceConfig error unmarshaling %s due to %v", js, err) 259 return &serviceconfig.ParseResult{Err: err} 260 } 261 262 mc := MethodConfig{ 263 WaitForReady: m.WaitForReady, 264 Timeout: d, 265 } 266 if mc.RetryPolicy, err = convertRetryPolicy(m.RetryPolicy); err != nil { 267 logger.Warningf("grpc: parseServiceConfig error unmarshaling %s due to %v", js, err) 268 return &serviceconfig.ParseResult{Err: err} 269 } 270 if m.MaxRequestMessageBytes != nil { 271 if *m.MaxRequestMessageBytes > int64(maxInt) { 272 mc.MaxReqSize = newInt(maxInt) 273 } else { 274 mc.MaxReqSize = newInt(int(*m.MaxRequestMessageBytes)) 275 } 276 } 277 if m.MaxResponseMessageBytes != nil { 278 if *m.MaxResponseMessageBytes > int64(maxInt) { 279 mc.MaxRespSize = newInt(maxInt) 280 } else { 281 mc.MaxRespSize = newInt(int(*m.MaxResponseMessageBytes)) 282 } 283 } 284 for i, n := range *m.Name { 285 path, err := n.generatePath() 286 if err != nil { 287 logger.Warningf("grpc: parseServiceConfig error unmarshaling %s due to methodConfig[%d]: %v", js, i, err) 288 return &serviceconfig.ParseResult{Err: err} 289 } 290 291 if _, ok := paths[path]; ok { 292 err = errDuplicatedName 293 logger.Warningf("grpc: parseServiceConfig error unmarshaling %s due to methodConfig[%d]: %v", js, i, err) 294 return &serviceconfig.ParseResult{Err: err} 295 } 296 paths[path] = struct{}{} 297 sc.Methods[path] = mc 298 } 299 } 300 301 if sc.retryThrottling != nil { 302 if mt := sc.retryThrottling.MaxTokens; mt <= 0 || mt > 1000 { 303 return &serviceconfig.ParseResult{Err: fmt.Errorf("invalid retry throttling config: maxTokens (%v) out of range (0, 1000]", mt)} 304 } 305 if tr := sc.retryThrottling.TokenRatio; tr <= 0 { 306 return &serviceconfig.ParseResult{Err: fmt.Errorf("invalid retry throttling config: tokenRatio (%v) may not be negative", tr)} 307 } 308 } 309 return &serviceconfig.ParseResult{Config: &sc} 310 } 311 312 func convertRetryPolicy(jrp *jsonRetryPolicy) (p *internalserviceconfig.RetryPolicy, err error) { 313 if jrp == nil { 314 return nil, nil 315 } 316 ib, err := parseDuration(&jrp.InitialBackoff) 317 if err != nil { 318 return nil, err 319 } 320 mb, err := parseDuration(&jrp.MaxBackoff) 321 if err != nil { 322 return nil, err 323 } 324 325 if jrp.MaxAttempts <= 1 || 326 *ib <= 0 || 327 *mb <= 0 || 328 jrp.BackoffMultiplier <= 0 || 329 len(jrp.RetryableStatusCodes) == 0 { 330 logger.Warningf("grpc: ignoring retry policy %v due to illegal configuration", jrp) 331 return nil, nil 332 } 333 334 rp := &internalserviceconfig.RetryPolicy{ 335 MaxAttempts: jrp.MaxAttempts, 336 InitialBackoff: *ib, 337 MaxBackoff: *mb, 338 BackoffMultiplier: jrp.BackoffMultiplier, 339 RetryableStatusCodes: make(map[codes.Code]bool), 340 } 341 if rp.MaxAttempts > 5 { 342 // TODO(retry): Make the max maxAttempts configurable. 343 rp.MaxAttempts = 5 344 } 345 for _, code := range jrp.RetryableStatusCodes { 346 rp.RetryableStatusCodes[code] = true 347 } 348 return rp, nil 349 } 350 351 func min(a, b *int) *int { 352 if *a < *b { 353 return a 354 } 355 return b 356 } 357 358 func getMaxSize(mcMax, doptMax *int, defaultVal int) *int { 359 if mcMax == nil && doptMax == nil { 360 return &defaultVal 361 } 362 if mcMax != nil && doptMax != nil { 363 return min(mcMax, doptMax) 364 } 365 if mcMax != nil { 366 return mcMax 367 } 368 return doptMax 369 } 370 371 func newInt(b int) *int { 372 return &b 373 } 374 375 func init() { 376 internal.EqualServiceConfigForTesting = equalServiceConfig 377 } 378 379 // equalServiceConfig compares two configs. The rawJSONString field is ignored, 380 // because they may diff in white spaces. 381 // 382 // If any of them is NOT *ServiceConfig, return false. 383 func equalServiceConfig(a, b serviceconfig.Config) bool { 384 aa, ok := a.(*ServiceConfig) 385 if !ok { 386 return false 387 } 388 bb, ok := b.(*ServiceConfig) 389 if !ok { 390 return false 391 } 392 aaRaw := aa.rawJSONString 393 aa.rawJSONString = "" 394 bbRaw := bb.rawJSONString 395 bb.rawJSONString = "" 396 defer func() { 397 aa.rawJSONString = aaRaw 398 bb.rawJSONString = bbRaw 399 }() 400 // Using reflect.DeepEqual instead of cmp.Equal because many balancer 401 // configs are unexported, and cmp.Equal cannot compare unexported fields 402 // from unexported structs. 403 return reflect.DeepEqual(aa, bb) 404 }