How PolarisMesh Server Handles Service Registration: Deep Dive into the Registration Flow
This article explores PolarisMesh’s server-side registration process, detailing how client registration requests are handled, the data flow through apiserver, resource auth filter, service and storage layers, and provides concrete code examples for both Java and Go SDKs, along with MySQL storage schema.
Introduction
PolarisMesh (北极星) is Tencent's open‑source service governance platform that addresses service management, traffic control, configuration, fault tolerance, and observability for distributed and microservice architectures. It provides standard solutions and best practices for various tech stacks.
Prerequisites
Go 1.17.x or higher
VSCode or GoLand
Clone the polaris-server source from the release-v1.12.0 branch on GitHub
Clone the polaris-java source from the release-v1.10.0 branch on GitHub
Client Registration Request Model
Java SDK request object:
InstanceRegisterRequest request = new InstanceRegisterRequest();
request.setService(service);
request.setNamespace(namespace);
request.setHost(host);
request.setPort(port);
request.setToken(token); // optional when server auth is enabled
request.setVersion(version);
request.setProtocol(protocol);
request.setWeight(weight);
request.setMetadata(metadata);
request.setZone(zone);
request.setRegion(region);
request.setCampus(campus);
request.setTtl(ttl); // seconds, required for heartbeatGo SDK request struct:
type InstanceRegisterRequest struct {
Service string
Namespace string
Host string
Port int
ServiceToken string // optional when auth is enabled
Protocol *string
Weight *int
Version *string
Metadata map[string]string
Healthy *bool
Isolate *bool
TTL *int
Location *Location
Timeout *time.Duration
RetryCount *int
}Sending Registration Request (Java Example)
ProviderAPI providerAPI = DiscoveryAPIFactory.createProviderAPI();
InstanceRegisterRequest registerRequest = new InstanceRegisterRequest();
// initialize request fields …
InstanceRegisterResponse registerResp = providerAPI.registerInstance(registerRequest);The call invokes ProviderAPI.registerInstance, which ensures the TTL is set and delegates to RegisterFlow.registerInstance.
RegisterFlow in Java SDK
public InstanceRegisterResponse registerInstance(InstanceRegisterRequest req) throws PolarisException {
if (req.getTtl() == null) {
req.setTtl(DEFAULT_INSTANCE_TTL);
}
return registerFlow.registerInstance(req, this::doRegister, this::heartbeat);
} RegisterFlowperforms two main tasks: initiating the registration action and maintaining periodic heartbeats. It stores the registration state locally and schedules heartbeat tasks using a ScheduledThreadPoolExecutor.
public InstanceRegisterResponse registerInstance(InstanceRegisterRequest request, RegisterFunction registerFunction,
HeartbeatFunction heartbeatFunction) {
InstanceRegisterResponse instanceRegisterResponse = registerFunction.doRegister(request, createRegisterV2Header());
RegisterState registerState = RegisterStateManager.putRegisterState(sdkContext, request);
if (registerState != null) {
registerState.setTaskFuture(asyncRegisterExecutor.scheduleWithFixedDelay(
() -> doRunHeartbeat(registerState, registerFunction, heartbeatFunction),
request.getTtl(), request.getTtl(), TimeUnit.SECONDS));
}
return instanceRegisterResponse;
}Server‑Side Processing of Registration Requests
When the SDK sends a registration request, the server processes it through several layers:
apiserver layer : receives the gRPC request and converts it to internal data structures.
resource auth filter layer : performs permission checks before forwarding to the service layer.
service layer : validates request legality, checks resource existence, and either returns an existing instance ID or generates a new one. It then passes the request to the BatchController.
batch controller : transforms a batch of registration requests into storage‑layer models and delegates to the appropriate store plugin.
storage layer : writes instance data to MySQL (cluster mode) or BoltDB (single‑node mode). In MySQL mode, three tables are used: instance, health_check, and instance_metadata.
apiserver Implementation (Go)
func (g *DiscoverServer) RegisterInstance(ctx context.Context, in *apiservice.Instance) (*apiservice.Response, error) {
rCtx := grpcserver.ConvertContext(ctx)
rCtx = context.WithValue(rCtx, utils.StringContext("operator"), ParseGrpcOperator(ctx))
if in.GetServiceToken().GetValue() != "" {
rCtx = context.WithValue(rCtx, utils.ContextAuthTokenKey, in.GetServiceToken().GetValue())
}
grpcHeader := rCtx.Value(utils.ContextGrpcHeader).(metadata.MD)
if _, ok := grpcHeader["async-regis"]; ok {
rCtx = context.WithValue(rCtx, utils.ContextOpenAsyncRegis, true)
}
out := g.namingServer.RegisterInstance(rCtx, in)
return out, nil
}resource auth filter (Go)
func (svr *ServerAuthAbility) RegisterInstance(ctx context.Context, req *apiservice.Instance) *apiservice.Response {
authCtx := svr.collectClientInstanceAuthContext(ctx, []*apiservice.Instance{req}, model.Create, "RegisterInstance")
_, err := svr.strategyMgn.GetAuthChecker().CheckClientPermission(authCtx)
if err != nil {
resp := api.NewResponseWithMsg(convertToErrCode(err), err.Error())
return resp
}
ctx = authCtx.GetRequestContext()
ctx = context.WithValue(ctx, utils.ContextAuthContextKey, authCtx)
return svr.targetServer.RegisterInstance(ctx, req)
}service layer (Go)
func (s *Server) RegisterInstance(ctx context.Context, req *apiservice.Instance) (*apiservice.Response, error) {
// after auth, forward to naming server
return s.namingServer.RegisterInstance(ctx, req)
}Synchronous Instance Creation
func (s *Server) serialCreateInstance(
ctx context.Context, svcId string, req *apiservice.Instance, ins *apiservice.Instance) (*model.Instance, *apiservice.Response) {
// lock service, write instance, handle metadata, etc.
// omitted for brevity
return data, nil
}Asynchronous Instance Creation
func (s *Server) asyncCreateInstance(
ctx context.Context, svcId string, req *apiservice.Instance, ins *apiservice.Instance) (*model.Instance, *apiservice.Response) {
allowAsyncRegis, _ := ctx.Value(utils.ContextOpenAsyncRegis).(bool)
future := s.bc.AsyncCreateInstance(svcId, ins, !allowAsyncRegis)
if err := future.Wait(); err != nil {
if future.Code() == apimodel.Code_ExistedResource {
req.Id = utils.NewStringValue(ins.GetId().GetValue())
}
return nil, api.NewInstanceResponse(future.Code(), req)
}
return instancecommon.CreateInstanceModel(svcId, req), nil
}MySQL Storage Schema for Instances
CREATE TABLE `instance` (
`id` varchar(128) NOT NULL,
`service_id` varchar(32) NOT NULL,
`host` varchar(128) NOT NULL,
`port` int(11) NOT NULL,
`protocol` varchar(32) DEFAULT NULL,
`version` varchar(32) DEFAULT NULL,
`health_status` tinyint(4) NOT NULL DEFAULT '1',
`isolate` tinyint(4) NOT NULL DEFAULT '0',
`weight` smallint(6) NOT NULL DEFAULT '100',
`enable_health_check` tinyint(4) NOT NULL DEFAULT '0',
`cmdb_region` varchar(128) DEFAULT NULL,
`cmdb_zone` varchar(128) DEFAULT NULL,
`cmdb_idc` varchar(128) DEFAULT NULL,
`revision` varchar(32) NOT NULL,
`flag` tinyint(4) NOT NULL DEFAULT '0',
`ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
...
) ENGINE=InnoDB;
CREATE TABLE `health_check` (
`id` varchar(128) NOT NULL COMMENT 'Instance ID',
`type` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'Instance health check type',
`ttl` int(11) NOT NULL COMMENT 'TTL time',
PRIMARY KEY (`id`),
CONSTRAINT `health_check_ibfk_1` FOREIGN KEY (`id`) REFERENCES `instance` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
CREATE TABLE `instance_metadata` (
`id` varchar(128) NOT NULL COMMENT 'Instance ID',
`mkey` varchar(128) NOT NULL COMMENT 'metadata key',
`mvalue` varchar(4096) NOT NULL COMMENT 'metadata value',
`ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`, `mkey`),
KEY `mkey` (`mkey`),
CONSTRAINT `instance_metadata_ibfk_1` FOREIGN KEY (`id`) REFERENCES `instance` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;Key Takeaways
The registration flow in PolarisMesh involves coordinated actions across client SDKs, a RegisterFlow orchestrator, multiple server layers for authentication and business logic, and a pluggable storage backend. Understanding each component helps developers troubleshoot registration issues and extend the platform.
Tencent Cloud Middleware
Official account of Tencent Cloud Middleware. Focuses on microservices, messaging middleware and other cloud‑native technology trends, publishing product updates, case studies, and technical insights. Regularly hosts tech salons to share effective solutions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
