trpc.group/trpc-go/trpc-go@v1.0.3/client/config.go (about) 1 // 2 // 3 // Tencent is pleased to support the open source community by making tRPC available. 4 // 5 // Copyright (C) 2023 THL A29 Limited, a Tencent company. 6 // All rights reserved. 7 // 8 // If you have downloaded a copy of the tRPC source code from Tencent, 9 // please note that tRPC source code is licensed under the Apache 2.0 License, 10 // A copy of the Apache 2.0 License is included in this file. 11 // 12 // 13 14 package client 15 16 import ( 17 "fmt" 18 "sync" 19 "time" 20 21 "trpc.group/trpc-go/trpc-go/codec" 22 "trpc.group/trpc-go/trpc-go/config" 23 "trpc.group/trpc-go/trpc-go/filter" 24 icodec "trpc.group/trpc-go/trpc-go/internal/codec" 25 "trpc.group/trpc-go/trpc-go/naming/circuitbreaker" 26 "trpc.group/trpc-go/trpc-go/naming/discovery" 27 "trpc.group/trpc-go/trpc-go/naming/loadbalance" 28 "trpc.group/trpc-go/trpc-go/naming/selector" 29 "trpc.group/trpc-go/trpc-go/naming/servicerouter" 30 "trpc.group/trpc-go/trpc-go/transport" 31 ) 32 33 // BackendConfig defines the configuration needed to call the backend service. 34 // It's empty by default and can be replaced. 35 type BackendConfig struct { 36 // Callee is the name of the backend service. 37 // The config file uses it as the key to set the parameters. 38 // Usually, it is the proto name of the callee service defined in proto stub file, 39 // and it is the same as ServiceName below. 40 Callee string `yaml:"callee"` // Name of the backend service. 41 ServiceName string `yaml:"name"` // Backend service name. 42 EnvName string `yaml:"env_name"` // Env name of the callee. 43 SetName string `yaml:"set_name"` // "Set" name of the callee. 44 45 // DisableServiceRouter, despite its inherent inappropriate and vague nomenclature, 46 // is an option for naming service that denotes the de-facto meaning of disabling 47 // out-rule routing for the source service. 48 DisableServiceRouter bool `yaml:"disable_servicerouter"` 49 Namespace string `yaml:"namespace"` // Namespace of the callee: Production/Development. 50 CalleeMetadata map[string]string `yaml:"callee_metadata"` // Set callee metadata. 51 52 Target string `yaml:"target"` // Polaris by default, generally no need to configure this. 53 Password string `yaml:"password"` // Password for authentication. 54 55 // Naming service four swordsmen. 56 // Discovery.List => ServiceRouter.Filter => Loadbalancer.Select => Circuitbreaker.Report 57 Discovery string `yaml:"discovery"` // Discovery for the backend service. 58 ServiceRouter string `yaml:"servicerouter"` // Service router for the backend service. 59 Loadbalance string `yaml:"loadbalance"` // Load balancing algorithm. 60 Circuitbreaker string `yaml:"circuitbreaker"` // Circuit breaker configuration. 61 62 Network string `yaml:"network"` // Transport protocol type: tcp or udp. 63 Timeout int `yaml:"timeout"` // Client timeout in milliseconds. 64 Protocol string `yaml:"protocol"` // Business protocol type: trpc, http, http_no_protocol, etc. 65 Transport string `yaml:"transport"` // Transport type. 66 67 // Serialization type. Use a pointer to check if it has been set (0 means pb). 68 Serialization *int `yaml:"serialization"` 69 Compression int `yaml:"compression"` // Compression type. 70 71 TLSKey string `yaml:"tls_key"` // Client TLS key. 72 TLSCert string `yaml:"tls_cert"` // Client TLS certificate. 73 // CA certificate used to validate the server cert when calling a TLS service (e.g., an HTTPS server). 74 CACert string `yaml:"ca_cert"` 75 // Server name used to validate the server (default: hostname) when calling an HTTPS server. 76 TLSServerName string `yaml:"tls_server_name"` 77 78 Filter []string `yaml:"filter"` // Filters for the backend service. 79 StreamFilter []string `yaml:"stream_filter"` // Stream filters for the backend service. 80 81 // Report any error to the selector if this value is true. 82 ReportAnyErrToSelector bool `yaml:"report_any_err_to_selector"` 83 } 84 85 // genOptions generates options for each RPC from BackendConfig. 86 func (cfg *BackendConfig) genOptions() (*Options, error) { 87 opts := NewOptions() 88 if err := cfg.setNamingOptions(opts); err != nil { 89 return nil, err 90 } 91 92 if cfg.Timeout > 0 { 93 opts.Timeout = time.Duration(cfg.Timeout) * time.Millisecond 94 } 95 if cfg.Serialization != nil { 96 opts.SerializationType = *cfg.Serialization 97 } 98 if icodec.IsValidCompressType(cfg.Compression) && cfg.Compression != codec.CompressTypeNoop { 99 opts.CompressType = cfg.Compression 100 } 101 102 // Reset the transport to check if the user has specified any transport. 103 opts.Transport = nil 104 WithTransport(transport.GetClientTransport(cfg.Transport))(opts) 105 WithStreamTransport(transport.GetClientStreamTransport(cfg.Transport))(opts) 106 WithProtocol(cfg.Protocol)(opts) 107 WithNetwork(cfg.Network)(opts) 108 opts.Transport = attemptSwitchingTransport(opts) 109 WithPassword(cfg.Password)(opts) 110 WithTLS(cfg.TLSCert, cfg.TLSKey, cfg.CACert, cfg.TLSServerName)(opts) 111 if cfg.Protocol != "" && opts.Codec == nil { 112 return nil, fmt.Errorf("codec %s not exists", cfg.Protocol) 113 } 114 for _, name := range cfg.Filter { 115 f := filter.GetClient(name) 116 if f == nil { 117 if name == DefaultSelectorFilterName { 118 // selector filter is configured 119 // need to set selector filter pos 120 opts.selectorFilterPosFixed = true 121 opts.Filters = append(opts.Filters, selectorFilter) 122 opts.FilterNames = append(opts.FilterNames, name) 123 continue 124 } 125 return nil, fmt.Errorf("client config: filter %s no registered, do not configure", name) 126 } 127 opts.Filters = append(opts.Filters, f) 128 opts.FilterNames = append(opts.FilterNames, name) 129 } 130 for _, name := range cfg.StreamFilter { 131 f := GetStreamFilter(name) 132 if f == nil { 133 return nil, fmt.Errorf("client config: stream filter %s no registered, do not configure", name) 134 } 135 opts.StreamFilters = append(opts.StreamFilters, f) 136 } 137 opts.rebuildSliceCapacity() 138 return opts, nil 139 } 140 141 // setNamingOptions sets naming related options. 142 func (cfg *BackendConfig) setNamingOptions(opts *Options) error { 143 if cfg.ServiceName != "" { 144 opts.ServiceName = cfg.ServiceName 145 } 146 if cfg.Namespace != "" { 147 opts.SelectOptions = append(opts.SelectOptions, selector.WithNamespace(cfg.Namespace)) 148 } 149 if cfg.EnvName != "" { 150 opts.SelectOptions = append(opts.SelectOptions, selector.WithDestinationEnvName(cfg.EnvName)) 151 } 152 if cfg.SetName != "" { 153 opts.SelectOptions = append(opts.SelectOptions, selector.WithDestinationSetName(cfg.SetName)) 154 } 155 if cfg.DisableServiceRouter { 156 opts.SelectOptions = append(opts.SelectOptions, selector.WithDisableServiceRouter()) 157 opts.DisableServiceRouter = true 158 } 159 if cfg.ReportAnyErrToSelector { 160 opts.shouldErrReportToSelector = func(err error) bool { return true } 161 } 162 for key, val := range cfg.CalleeMetadata { 163 opts.SelectOptions = append(opts.SelectOptions, selector.WithDestinationMetadata(key, val)) 164 } 165 if cfg.Target != "" { 166 opts.Target = cfg.Target 167 return opts.parseTarget() 168 } 169 if cfg.Discovery != "" { 170 d := discovery.Get(cfg.Discovery) 171 if d == nil { 172 return fmt.Errorf("client config: discovery %s no registered", cfg.Discovery) 173 } 174 opts.SelectOptions = append(opts.SelectOptions, selector.WithDiscovery(d)) 175 } 176 if cfg.ServiceRouter != "" { 177 r := servicerouter.Get(cfg.ServiceRouter) 178 if r == nil { 179 return fmt.Errorf("client config: servicerouter %s no registered", cfg.ServiceRouter) 180 } 181 opts.SelectOptions = append(opts.SelectOptions, selector.WithServiceRouter(r)) 182 } 183 if cfg.Loadbalance != "" { 184 balancer := loadbalance.Get(cfg.Loadbalance) 185 if balancer == nil { 186 return fmt.Errorf("client config: balancer %s no registered", cfg.Loadbalance) 187 } 188 opts.SelectOptions = append(opts.SelectOptions, selector.WithLoadBalancer(balancer)) 189 } 190 if cfg.Circuitbreaker != "" { 191 cb := circuitbreaker.Get(cfg.Circuitbreaker) 192 if cb == nil { 193 return fmt.Errorf("client config: circuitbreaker %s no registered", cfg.Circuitbreaker) 194 } 195 opts.SelectOptions = append(opts.SelectOptions, selector.WithCircuitBreaker(cb)) 196 } 197 return nil 198 } 199 200 var ( 201 // DefaultSelectorFilterName is the default name of selector filter. 202 // It can be modified if conflict exists. 203 DefaultSelectorFilterName = "selector" 204 205 defaultBackendConf = &BackendConfig{ 206 Network: "tcp", 207 Protocol: "trpc", 208 } 209 defaultBackendOptions *Options 210 211 mutex sync.RWMutex 212 configs = make(map[string]*configsWithFallback) // Key: callee. 213 options = make(map[string]*optionsWithFallback) // Key: callee. 214 ) 215 216 type configsWithFallback struct { 217 fallback *BackendConfig 218 serviceNames map[string]*BackendConfig // Key: service name. 219 } 220 221 type optionsWithFallback struct { 222 fallback *Options 223 serviceNames map[string]*Options // Key: service name. 224 } 225 226 // getDefaultOptions returns default options. 227 func getDefaultOptions() *Options { 228 mutex.RLock() 229 opts := defaultBackendOptions 230 mutex.RUnlock() 231 if opts != nil { 232 return opts 233 } 234 mutex.Lock() 235 if defaultBackendOptions != nil { 236 mutex.Unlock() 237 return defaultBackendOptions 238 } 239 opts, err := defaultBackendConf.genOptions() 240 if err != nil { 241 defaultBackendOptions = NewOptions() 242 } else { 243 defaultBackendOptions = opts 244 } 245 mutex.Unlock() 246 return defaultBackendOptions 247 } 248 249 // DefaultClientConfig returns the default client config. 250 // 251 // Note: if multiple client configs with same callee and different service name 252 // exist in trpc_go.yaml, this function will only return the last config for 253 // the same callee key. 254 func DefaultClientConfig() map[string]*BackendConfig { 255 mutex.RLock() 256 c := make(map[string]*BackendConfig) 257 for k, v := range configs { 258 c[k] = v.fallback 259 } 260 mutex.RUnlock() 261 return c 262 } 263 264 // LoadClientConfig loads client config by path. 265 func LoadClientConfig(path string, opts ...config.LoadOption) error { 266 conf, err := config.DefaultConfigLoader.Load(path, opts...) 267 if err != nil { 268 return err 269 } 270 tmp := make(map[string]*BackendConfig) 271 if err := conf.Unmarshal(tmp); err != nil { 272 return err 273 } 274 RegisterConfig(tmp) 275 return nil 276 } 277 278 // Config returns BackendConfig by callee service name. 279 func Config(callee string) *BackendConfig { 280 mutex.RLock() 281 if len(configs) == 0 { 282 mutex.RUnlock() 283 return defaultBackendConf 284 } 285 conf, ok := configs[callee] 286 if !ok { 287 conf, ok = configs["*"] 288 if !ok { 289 mutex.RUnlock() 290 return defaultBackendConf 291 } 292 } 293 mutex.RUnlock() 294 return conf.fallback 295 } 296 297 func getOptionsByCalleeAndUserOptions(callee string, opt ...Option) *Options { 298 // Each RPC call uses new options to ensure thread safety. 299 inputOpts := &Options{} 300 for _, o := range opt { 301 o(inputOpts) 302 } 303 if inputOpts.ServiceName != "" { 304 // If user passes in a service name option, use callee and service name 305 // as a combined key to retrieve client config. 306 return getOptionsByCalleeAndServiceName(callee, inputOpts.ServiceName) 307 } 308 // Otherwise use callee only. 309 return getOptions(callee) 310 } 311 312 // getOptions returns Options by callee service name. 313 func getOptions(callee string) *Options { 314 mutex.RLock() 315 if len(options) == 0 { 316 mutex.RUnlock() 317 return getDefaultOptions() 318 } 319 opts, ok := options[callee] 320 if !ok { 321 opts, ok = options["*"] 322 if !ok { 323 mutex.RUnlock() 324 return getDefaultOptions() 325 } 326 } 327 mutex.RUnlock() 328 return opts.fallback 329 } 330 331 func getOptionsByCalleeAndServiceName(callee, serviceName string) *Options { 332 mutex.RLock() 333 serviceOptions, ok := options[callee] 334 if !ok { 335 mutex.RUnlock() 336 return getOptions(callee) // Fallback to use callee as the single key. 337 } 338 opts, ok := serviceOptions.serviceNames[serviceName] 339 if !ok { 340 mutex.RUnlock() 341 return getOptions(callee) // Fallback to use callee as the single key. 342 } 343 mutex.RUnlock() 344 return opts 345 } 346 347 // RegisterConfig is called to replace the global backend config, 348 // allowing updating backend config regularly. 349 func RegisterConfig(conf map[string]*BackendConfig) error { 350 opts := make(map[string]*optionsWithFallback) 351 confs := make(map[string]*configsWithFallback) 352 for key, cfg := range conf { 353 o, err := cfg.genOptions() 354 if err != nil { 355 return err 356 } 357 opts[key] = &optionsWithFallback{ 358 fallback: o, 359 serviceNames: make(map[string]*Options), 360 } 361 opts[key].serviceNames[cfg.ServiceName] = o 362 confs[key] = &configsWithFallback{ 363 fallback: cfg, 364 serviceNames: make(map[string]*BackendConfig), 365 } 366 confs[key].serviceNames[cfg.ServiceName] = cfg 367 } 368 mutex.Lock() 369 options = opts 370 configs = confs 371 mutex.Unlock() 372 return nil 373 } 374 375 // RegisterClientConfig is called to replace backend config of single callee service by name. 376 func RegisterClientConfig(callee string, conf *BackendConfig) error { 377 if callee == "*" { 378 // Reset the callee and service name to enable wildcard matching. 379 conf.Callee = "" 380 conf.ServiceName = "" 381 } 382 opts, err := conf.genOptions() 383 if err != nil { 384 return err 385 } 386 mutex.Lock() 387 if opt, ok := options[callee]; !ok || opt == nil { 388 options[callee] = &optionsWithFallback{ 389 serviceNames: make(map[string]*Options), 390 } 391 configs[callee] = &configsWithFallback{ 392 serviceNames: make(map[string]*BackendConfig), 393 } 394 } 395 options[callee].fallback = opts 396 configs[callee].fallback = conf 397 options[callee].serviceNames[conf.ServiceName] = opts 398 configs[callee].serviceNames[conf.ServiceName] = conf 399 mutex.Unlock() 400 return nil 401 }