English | 中文
A naming service resolves the target service name to one concrete network node (Node) when the client makes a call.
Naming plugins register through @register_plugin when their module is imported. import trpc_sdk.naming registers the built-in plugins. Import a custom plugin's module from the service entry so the decorator runs.
The client selects a node in one of two modes: one Selector plugin chosen by target, or no target, using the default TrpcSelector chain (discovery, routing, load balancing, and circuit breaking). The two modes are described below.
The client selects a node during select_node. When target is set, it must be scheme://endpoint:
schemeis the registered name of aPluginType.NAMING_SELECTORplugin.endpointis the string after://. It is passed through unchanged toselector.select(endpoint, opt).- If
targethas no://, the framework raisesClientRouteError.
When
targetis omitted, the client constructsTrpcSelectordirectly and does not look up a plugin namedtrpcin the plugin table.TrpcSelectoritself is not registered with@register_plugin, sotarget: trpc://...fails because the plugin cannot be found.
| scheme | Plugin | Behavior |
|---|---|---|
ip, http, https |
IpSelector |
Parse endpoint as host:port, with no DNS lookup. Several addresses separated by commas are accepted, and one is picked at random. |
dns |
DNSSelector |
Parse endpoint as host:port (comma-separated addresses, one picked at random), then resolve the host with DNS. |
dsn |
DSNSelector |
Write the whole string after :// into Node.address. When target is dsn://user:passwd@tcp(ip:port)/db, Node.address is user:passwd@tcp(ip:port)/db. |
Once a client service has
target,discovery,loadbalance, andcircuitbreakeron that service are ignored. Those three apply only whentargetis omitted and the call goes through theTrpcSelectorchain.
The selector below is a StaticSelector backed by a static dictionary. It returns a fixed (ip, port) for the service name it receives. report does nothing. The built-in IpSelector.report is empty as well.
from trpc_sdk.abc import BaseSelector
from trpc_sdk.abc import Node
from trpc_sdk.abc import PluginType
from trpc_sdk.abc import SelectorOption
from trpc_sdk.plugin import register_plugin
@register_plugin(PluginType.NAMING_SELECTOR, "static")
class StaticSelector(BaseSelector):
"""Map a service name to one fixed host:port."""
_nodes = {
"service1": ("127.0.0.1", 8000),
"service2": ("127.0.0.1", 8001),
}
async def select(self, service_name: str, opt: SelectorOption) -> Node:
ip, port = self._nodes[service_name]
return Node(service_name=service_name, ip=ip, port=port)
async def report(self, node: Node, cost: int, code: int) -> bool:
return TrueA few implementation details:
- How
Nodefills its address: WhenNodeis constructed with bothipandport, initialization fillsnode.addressas"ip:port". To leave the address unsplit, asDSNSelectordoes, pass onlyaddress. - Arguments to
report:costis the call duration.codeis the result code of this call. On success the framework passes0. On failure it passes the error code. - Configuration: The framework does not decode the YAML
pluginssection into a naming plugin. After the service entry imports the module above, the client only needstargetin YAML:
client:
service:
- name: trpc.test.helloworld.Greeter
target: static://service1service1 after :// is passed to StaticSelector.select as service_name, and the call is routed to 127.0.0.1:8000.
When the client configuration has no target, the framework uses the configured client service name (YAML name) as the target. TrpcSelector.select then picks a node in this order:
- Resolve each plugin name. Start from the process-wide default. If this call's
SelectorOptionsets an override, that override wins. - Try
discovery.select_node. Call the discovery plugin'sselect_nodefirst. The built-inIpDiscovery.select_nodealways returnsNone, so the later steps still run. If a custom discovery returns aNodehere, listing, route filtering, and load balancing are skipped. - Run the standard pipeline. Call
discovery.listfor the candidate nodes, thenservice_router.filter, thenloadbalance.selectto pick one node. - Attach the circuit breaker. Store the chosen circuit-breaker instance in
node.metadata["circuitbreaker"]. When the RPC ends,TrpcSelector.reportawaits that circuit breaker'sreport.
The four roles in the chain use these default plugins:
| Role | Plugin type (PluginType) |
Default registered name | Built-in class |
|---|---|---|---|
| discovery | NAMING_DISCOVERY |
IPDiscovery |
IpDiscovery |
| load balance | NAMING_LOADBALANCE |
RandomLB |
RandomLoadBalance |
| circuit breaker | NAMING_CIRCUIT_BREAKER |
NoopCB |
NoopCircuitBreaker |
| service router | NAMING_SERVICE_ROUTER |
NoopSR |
NoopServiceRouter |
The built-in
IpDiscovery.listtreats the service name ashost:portand resolves it with DNS, ignoringnamespace. To look up a node list by a logical service name, replace it with a custom discovery plugin.
Change the default sub-plugins for every client in the process from code:
from trpc_sdk.naming import set_default_circuitbreaker
from trpc_sdk.naming import set_default_discovery
from trpc_sdk.naming import set_default_loadbalance
from trpc_sdk.naming import set_default_servicerouter
set_default_discovery("static_discovery")
set_default_loadbalance("RandomLB")
set_default_circuitbreaker("NoopCB")
set_default_servicerouter("NoopSR")A single client service can override discovery, loadbalance, and circuitbreaker in YAML. Client YAML has no servicerouter field. Set the service router with set_default_servicerouter, or with Options.set_service_router on the call:
client:
service:
- name: trpc.test.helloworld.Greeter
discovery: static_discovery
loadbalance: RandomLB
circuitbreaker: NoopCB
namespace: DevelopmentA custom BaseDiscovery looks like this:
from typing import Any
from typing import List
from typing import Optional
from trpc_sdk.abc import BaseDiscovery
from trpc_sdk.abc import Node
from trpc_sdk.abc import PluginType
from trpc_sdk.plugin import register_plugin
@register_plugin(PluginType.NAMING_DISCOVERY, "static_discovery")
class StaticDiscovery(BaseDiscovery):
"""Return one fixed node for a known service name."""
_nodes = {
"trpc.test.helloworld.Greeter": ("127.0.0.1", 8000),
}
async def list(self, service_name: str, namespace: str) -> List[Node]:
ip, port = self._nodes[service_name]
return [Node(service_name=service_name, ip=ip, port=port)]
async def select_node(self, service_name: str, opt: Any) -> Optional[Node]:
return NoneThe other three plugins in the chain:
- Load balance (
BaseLoadBalance): Implementselect(service_name, node_list, namespace). The built-inRandomLoadBalancepicks one node uniformly at random from a non-empty list. An empty list fails immediately. - Service router (
BaseServiceRouter): Implementfilter(service_name, nodes, opt). The built-inNoopServiceRouterreturns the incoming list unchanged. To drop some nodes, return a filtered new list, or the original list after you change it. An empty list means there is no usable node, and the later load balance fails. - Circuit breaker (
BaseCircuitBreaker): Implementavailableandreport. The currentTrpcSelectorselection path does not callavailable. After the call,TrpcSelector.reportforwards the node, the cost, and the result code to the circuit breaker'sreport. The built-inNoopCircuitBreaker.reportis empty. A custom circuit breaker records state there.
The framework also provides ResolveSelector, which is not registered in the plugin table. It extracts a service name from a DSN string, asks another selector that is already instantiated to resolve an address, and splices that address back into the original DSN string.