Cloud Native 19 min read

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.

Tencent Cloud Middleware
Tencent Cloud Middleware
Tencent Cloud Middleware
How PolarisMesh Server Handles Service Registration: Deep Dive into the Registration Flow

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 heartbeat

Go 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);
}
RegisterFlow

performs 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.

PolarisMesh registration processing diagram
PolarisMesh registration processing diagram
JavaGoservice registrationPolarisMesh
Tencent Cloud Middleware
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.