feat: registry.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package polaris
|
||||
|
||||
import (
|
||||
polarisKratos "github.com/go-kratos/kratos/contrib/registry/polaris/v2"
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
|
||||
polarisApi "github.com/polarismesh/polaris-go/api"
|
||||
@@ -10,8 +9,8 @@ import (
|
||||
conf "github.com/tx7do/kratos-bootstrap/api/gen/go/conf/v1"
|
||||
)
|
||||
|
||||
// NewPolarisRegistry 创建一个注册发现客户端 - Polaris
|
||||
func NewPolarisRegistry(c *conf.Registry) *polarisKratos.Registry {
|
||||
// NewRegistry 创建一个注册发现客户端 - Polaris
|
||||
func NewRegistry(c *conf.Registry) *Registry {
|
||||
if c == nil || c.Polaris == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -44,7 +43,7 @@ func NewPolarisRegistry(c *conf.Registry) *polarisKratos.Registry {
|
||||
}
|
||||
}
|
||||
|
||||
reg := polarisKratos.NewRegistry(provider, consumer)
|
||||
reg := New(provider, consumer)
|
||||
|
||||
return reg
|
||||
}
|
||||
@@ -18,6 +18,6 @@ func TestNewPolarisRegistry(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
reg := NewPolarisRegistry(&cfg)
|
||||
reg := NewRegistry(&cfg)
|
||||
assert.NotNil(t, reg)
|
||||
}
|
||||
53
registry/polaris/go.mod
Normal file
53
registry/polaris/go.mod
Normal file
@@ -0,0 +1,53 @@
|
||||
module github.com/tx7do/kratos-bootstrap/registry/polaris
|
||||
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.3
|
||||
|
||||
replace (
|
||||
github.com/armon/go-metrics => github.com/hashicorp/go-metrics v0.4.1
|
||||
|
||||
github.com/tx7do/kratos-bootstrap/api => ../../api
|
||||
github.com/tx7do/kratos-bootstrap/registry => ../registry
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-kratos/kratos/v2 v2.8.4
|
||||
github.com/polarismesh/polaris-go v1.6.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tx7do/kratos-bootstrap/api v0.0.20
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/polarismesh/specification v1.5.5-alpha.1 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.64.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
|
||||
google.golang.org/grpc v1.72.2 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
1008
registry/polaris/go.sum
Normal file
1008
registry/polaris/go.sum
Normal file
File diff suppressed because it is too large
Load Diff
92
registry/polaris/options.go
Normal file
92
registry/polaris/options.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package polaris
|
||||
|
||||
import "time"
|
||||
|
||||
type options struct {
|
||||
// required, namespace in polaris
|
||||
Namespace string
|
||||
|
||||
// required, service access token
|
||||
ServiceToken string
|
||||
|
||||
// optional, protocol in polaris. The Default value is nil, it means use protocol config in service
|
||||
Protocol *string
|
||||
|
||||
// service weight in polaris. The Default value is 100, 0 <= weight <= 10000
|
||||
Weight int
|
||||
|
||||
// service priority. Default value is 0. The smaller the value, the lower the priority
|
||||
Priority int
|
||||
|
||||
// To show service is healthy or not. The Default value is True.
|
||||
Healthy bool
|
||||
|
||||
// Heartbeat enable .Not in polaris. The Default value is True.
|
||||
Heartbeat bool
|
||||
|
||||
// To show service is isolated or not. Default value is False.
|
||||
Isolate bool
|
||||
|
||||
// TTL timeout. if the node needs to use heartbeat to report, required. If not set,server will throw ErrorCode-400141
|
||||
TTL int
|
||||
|
||||
// optional, Timeout for a single query. Default value is global config
|
||||
// Total is (1+RetryCount) * Timeout
|
||||
Timeout time.Duration
|
||||
|
||||
// optional, retry count. Default value is global config
|
||||
RetryCount int
|
||||
}
|
||||
|
||||
// Option is polaris option.
|
||||
type Option func(o *options)
|
||||
|
||||
// WithNamespace with a Namespace option.
|
||||
func WithNamespace(namespace string) Option {
|
||||
return func(o *options) { o.Namespace = namespace }
|
||||
}
|
||||
|
||||
// WithServiceToken with ServiceToken option.
|
||||
func WithServiceToken(serviceToken string) Option {
|
||||
return func(o *options) { o.ServiceToken = serviceToken }
|
||||
}
|
||||
|
||||
// WithProtocol with a Protocol option.
|
||||
func WithProtocol(protocol string) Option {
|
||||
return func(o *options) { o.Protocol = &protocol }
|
||||
}
|
||||
|
||||
// WithWeight with a Weight option.
|
||||
func WithWeight(weight int) Option {
|
||||
return func(o *options) { o.Weight = weight }
|
||||
}
|
||||
|
||||
// WithHealthy with a Healthy option.
|
||||
func WithHealthy(healthy bool) Option {
|
||||
return func(o *options) { o.Healthy = healthy }
|
||||
}
|
||||
|
||||
// WithIsolate with an Isolate option.
|
||||
func WithIsolate(isolate bool) Option {
|
||||
return func(o *options) { o.Isolate = isolate }
|
||||
}
|
||||
|
||||
// WithTTL with TTL option.
|
||||
func WithTTL(TTL int) Option {
|
||||
return func(o *options) { o.TTL = TTL }
|
||||
}
|
||||
|
||||
// WithTimeout with a Timeout option.
|
||||
func WithTimeout(timeout time.Duration) Option {
|
||||
return func(o *options) { o.Timeout = timeout }
|
||||
}
|
||||
|
||||
// WithRetryCount with RetryCount option.
|
||||
func WithRetryCount(retryCount int) Option {
|
||||
return func(o *options) { o.RetryCount = retryCount }
|
||||
}
|
||||
|
||||
// WithHeartbeat with a Heartbeat option.
|
||||
func WithHeartbeat(heartbeat bool) Option {
|
||||
return func(o *options) { o.Heartbeat = heartbeat }
|
||||
}
|
||||
349
registry/polaris/registry.go
Normal file
349
registry/polaris/registry.go
Normal file
@@ -0,0 +1,349 @@
|
||||
package polaris
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/registry"
|
||||
|
||||
"github.com/polarismesh/polaris-go/api"
|
||||
"github.com/polarismesh/polaris-go/pkg/config"
|
||||
"github.com/polarismesh/polaris-go/pkg/model"
|
||||
)
|
||||
|
||||
var (
|
||||
_ registry.Registrar = (*Registry)(nil)
|
||||
_ registry.Discovery = (*Registry)(nil)
|
||||
)
|
||||
|
||||
// _instanceIDSeparator . Instance id Separator.
|
||||
const _instanceIDSeparator = "-"
|
||||
|
||||
// Registry is polaris registry.
|
||||
type Registry struct {
|
||||
opt options
|
||||
provider api.ProviderAPI
|
||||
consumer api.ConsumerAPI
|
||||
}
|
||||
|
||||
func New(provider api.ProviderAPI, consumer api.ConsumerAPI, opts ...Option) (r *Registry) {
|
||||
op := options{
|
||||
Namespace: "default",
|
||||
ServiceToken: "",
|
||||
Protocol: nil,
|
||||
Weight: 0,
|
||||
Priority: 0,
|
||||
Healthy: true,
|
||||
Heartbeat: true,
|
||||
Isolate: false,
|
||||
TTL: 0,
|
||||
Timeout: 0,
|
||||
RetryCount: 0,
|
||||
}
|
||||
for _, option := range opts {
|
||||
option(&op)
|
||||
}
|
||||
return &Registry{
|
||||
opt: op,
|
||||
provider: provider,
|
||||
consumer: consumer,
|
||||
}
|
||||
}
|
||||
|
||||
func NewRegistryWithConfig(conf config.Configuration, opts ...Option) (r *Registry) {
|
||||
provider, err := api.NewProviderAPIByConfig(conf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
consumer, err := api.NewConsumerAPIByConfig(conf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return New(provider, consumer, opts...)
|
||||
}
|
||||
|
||||
// Register the registration.
|
||||
func (r *Registry) Register(_ context.Context, serviceInstance *registry.ServiceInstance) error {
|
||||
ids := make([]string, 0, len(serviceInstance.Endpoints))
|
||||
for _, endpoint := range serviceInstance.Endpoints {
|
||||
// get url
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get host and port
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// port to int
|
||||
portNum, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// medata
|
||||
var rmd map[string]string
|
||||
if serviceInstance.Metadata == nil {
|
||||
rmd = map[string]string{
|
||||
"kind": u.Scheme,
|
||||
"version": serviceInstance.Version,
|
||||
}
|
||||
} else {
|
||||
rmd = make(map[string]string, len(serviceInstance.Metadata)+2)
|
||||
for k, v := range serviceInstance.Metadata {
|
||||
rmd[k] = v
|
||||
}
|
||||
rmd["kind"] = u.Scheme
|
||||
rmd["version"] = serviceInstance.Version
|
||||
}
|
||||
// Register
|
||||
service, err := r.provider.Register(
|
||||
&api.InstanceRegisterRequest{
|
||||
InstanceRegisterRequest: model.InstanceRegisterRequest{
|
||||
Service: serviceInstance.Name + u.Scheme,
|
||||
ServiceToken: r.opt.ServiceToken,
|
||||
Namespace: r.opt.Namespace,
|
||||
Host: host,
|
||||
Port: portNum,
|
||||
Protocol: r.opt.Protocol,
|
||||
Weight: &r.opt.Weight,
|
||||
Priority: &r.opt.Priority,
|
||||
Version: &serviceInstance.Version,
|
||||
Metadata: rmd,
|
||||
Healthy: &r.opt.Healthy,
|
||||
Isolate: &r.opt.Isolate,
|
||||
TTL: &r.opt.TTL,
|
||||
Timeout: &r.opt.Timeout,
|
||||
RetryCount: &r.opt.RetryCount,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
instanceID := service.InstanceID
|
||||
|
||||
if r.opt.Heartbeat {
|
||||
// start heartbeat report
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second * time.Duration(r.opt.TTL))
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
<-ticker.C
|
||||
|
||||
err = r.provider.Heartbeat(&api.InstanceHeartbeatRequest{
|
||||
InstanceHeartbeatRequest: model.InstanceHeartbeatRequest{
|
||||
Service: serviceInstance.Name + u.Scheme,
|
||||
Namespace: r.opt.Namespace,
|
||||
Host: host,
|
||||
Port: portNum,
|
||||
ServiceToken: r.opt.ServiceToken,
|
||||
InstanceID: instanceID,
|
||||
Timeout: &r.opt.Timeout,
|
||||
RetryCount: &r.opt.RetryCount,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
ids = append(ids, instanceID)
|
||||
}
|
||||
// need to set InstanceID for Deregister
|
||||
serviceInstance.ID = strings.Join(ids, _instanceIDSeparator)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deregister the registration.
|
||||
func (r *Registry) Deregister(_ context.Context, serviceInstance *registry.ServiceInstance) error {
|
||||
split := strings.Split(serviceInstance.ID, _instanceIDSeparator)
|
||||
for i, endpoint := range serviceInstance.Endpoints {
|
||||
// get url
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get host and port
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// port to int
|
||||
portNum, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Deregister
|
||||
err = r.provider.Deregister(
|
||||
&api.InstanceDeRegisterRequest{
|
||||
InstanceDeRegisterRequest: model.InstanceDeRegisterRequest{
|
||||
Service: serviceInstance.Name + u.Scheme,
|
||||
ServiceToken: r.opt.ServiceToken,
|
||||
Namespace: r.opt.Namespace,
|
||||
InstanceID: split[i],
|
||||
Host: host,
|
||||
Port: portNum,
|
||||
Timeout: &r.opt.Timeout,
|
||||
RetryCount: &r.opt.RetryCount,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetService return the service instances in memory according to the service name.
|
||||
func (r *Registry) GetService(_ context.Context, serviceName string) ([]*registry.ServiceInstance, error) {
|
||||
// get all instances
|
||||
instancesResponse, err := r.consumer.GetAllInstances(&api.GetAllInstancesRequest{
|
||||
GetAllInstancesRequest: model.GetAllInstancesRequest{
|
||||
Service: serviceName,
|
||||
Namespace: r.opt.Namespace,
|
||||
Timeout: &r.opt.Timeout,
|
||||
RetryCount: &r.opt.RetryCount,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serviceInstances := instancesToServiceInstances(instancesResponse.GetInstances())
|
||||
|
||||
return serviceInstances, nil
|
||||
}
|
||||
|
||||
// Watch creates a watcher according to the service name.
|
||||
func (r *Registry) Watch(ctx context.Context, serviceName string) (registry.Watcher, error) {
|
||||
return newWatcher(ctx, r.opt.Namespace, serviceName, r.consumer)
|
||||
}
|
||||
|
||||
type Watcher struct {
|
||||
ServiceName string
|
||||
Namespace string
|
||||
Ctx context.Context
|
||||
Cancel context.CancelFunc
|
||||
Channel <-chan model.SubScribeEvent
|
||||
ServiceInstances []*registry.ServiceInstance
|
||||
}
|
||||
|
||||
func newWatcher(ctx context.Context, namespace string, serviceName string, consumer api.ConsumerAPI) (*Watcher, error) {
|
||||
watchServiceResponse, err := consumer.WatchService(&api.WatchServiceRequest{
|
||||
WatchServiceRequest: model.WatchServiceRequest{
|
||||
Key: model.ServiceKey{
|
||||
Namespace: namespace,
|
||||
Service: serviceName,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w := &Watcher{
|
||||
Namespace: namespace,
|
||||
ServiceName: serviceName,
|
||||
Channel: watchServiceResponse.EventChannel,
|
||||
ServiceInstances: instancesToServiceInstances(watchServiceResponse.GetAllInstancesResp.GetInstances()),
|
||||
}
|
||||
w.Ctx, w.Cancel = context.WithCancel(ctx)
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Next returns services in the following two cases:
|
||||
// 1.the first time to watch and the service instance list is not empty.
|
||||
// 2.any service instance changes found.
|
||||
// if the above two conditions are not met, it will block until context deadline exceeded or canceled
|
||||
func (w *Watcher) Next() ([]*registry.ServiceInstance, error) {
|
||||
select {
|
||||
case <-w.Ctx.Done():
|
||||
return nil, w.Ctx.Err()
|
||||
case event := <-w.Channel:
|
||||
if event.GetSubScribeEventType() == model.EventInstance {
|
||||
// this always true, but we need to check it to make sure EventType not change
|
||||
if instanceEvent, ok := event.(*model.InstanceEvent); ok {
|
||||
// handle DeleteEvent
|
||||
if instanceEvent.DeleteEvent != nil {
|
||||
for _, instance := range instanceEvent.DeleteEvent.Instances {
|
||||
for i, serviceInstance := range w.ServiceInstances {
|
||||
if serviceInstance.ID == instance.GetId() {
|
||||
// remove equal
|
||||
if len(w.ServiceInstances) <= 1 {
|
||||
w.ServiceInstances = w.ServiceInstances[0:0]
|
||||
continue
|
||||
}
|
||||
w.ServiceInstances = append(w.ServiceInstances[:i], w.ServiceInstances[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle UpdateEvent
|
||||
if instanceEvent.UpdateEvent != nil {
|
||||
for i, serviceInstance := range w.ServiceInstances {
|
||||
for _, update := range instanceEvent.UpdateEvent.UpdateList {
|
||||
if serviceInstance.ID == update.Before.GetId() {
|
||||
w.ServiceInstances[i] = instanceToServiceInstance(update.After)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle AddEvent
|
||||
if instanceEvent.AddEvent != nil {
|
||||
w.ServiceInstances = append(w.ServiceInstances, instancesToServiceInstances(instanceEvent.AddEvent.Instances)...)
|
||||
}
|
||||
}
|
||||
return w.ServiceInstances, nil
|
||||
}
|
||||
}
|
||||
return w.ServiceInstances, nil
|
||||
}
|
||||
|
||||
// Stop close the watcher.
|
||||
func (w *Watcher) Stop() error {
|
||||
w.Cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func instancesToServiceInstances(instances []model.Instance) []*registry.ServiceInstance {
|
||||
serviceInstances := make([]*registry.ServiceInstance, 0, len(instances))
|
||||
for _, instance := range instances {
|
||||
if instance.IsHealthy() {
|
||||
serviceInstances = append(serviceInstances, instanceToServiceInstance(instance))
|
||||
}
|
||||
}
|
||||
return serviceInstances
|
||||
}
|
||||
|
||||
func instanceToServiceInstance(instance model.Instance) *registry.ServiceInstance {
|
||||
metadata := instance.GetMetadata()
|
||||
// Usually, it won't fail in kratos if register correctly
|
||||
kind := ""
|
||||
if k, ok := metadata["kind"]; ok {
|
||||
kind = k
|
||||
}
|
||||
return ®istry.ServiceInstance{
|
||||
ID: instance.GetId(),
|
||||
Name: instance.GetService(),
|
||||
Version: metadata["version"],
|
||||
Metadata: metadata,
|
||||
Endpoints: []string{fmt.Sprintf("%s://%s:%d", kind, instance.GetHost(), instance.GetPort())},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user