Service registration and discovery
What is Service Discovery and Registration
- Service registration: register the module information that provides a service (usually the IP and port of the service) to a public rental price (for example: zookeeper, consul, etcd)
- Service discovery: The newly registered service module can be discovered by other callers in time, and automatic discovery can be realized whether it is a service addition or a service deletion
Where is the service registration and discovery application
We know that with the rapid development of the Internet, the number of client requests is increasing rapidly. Then the pressure on the server will increase. So what solution do we have to solve this problem?
Web1.0
In the era of web 1.0, all client calls are a server. All services are also in one project (pictured below). In the initial stage of the Internet, it can meet the traffic at that time. But with the continuous increase of clients, a single server can no longer meet such a large amount of traffic, so how do we deal with it?
Web2.0
Through the egg and basket theory, when all the eggs are put in one basket, but there are more and more eggs, we can only add more baskets. Putting eggs in different baskets will solve this problem.
In this way, we have entered the era of web2.0. That is to say, the server cluster we have tried, all clients will distribute all requests to the machines in the cluster through different algorithms through a load balancing. This can load more client requests.
The era of microservices
With the advancement of the times, our business has changed from a single website browsing to a variety of APPs. The increase of users means that our functions become diversified. Then the single service cluster deployment method used in Web2.0 will make the project very complicated. It caused a lot of trouble to our development and maintenance. So again according to the egg basket theory, our solution is to put different eggs in different baskets . To put it simply, it is to split a single service to eliminate the business dimension into different services, and the resources in these services need to be isolated from each other.
Microservices requirements for service discovery registration
We can imagine that an e-commerce service can be divided into microservices such as users, commodities, orders, and inventory. If there are only 5 microservices, we may think that the relationship is not very complicated, and we can use configuration files or other methods to solve the call configuration between services and services. Then, as the business becomes more and more complex, user microservices will be split into independent microservices such as users, registration, and members. At this time, the number of microservices will become very large. So what about the mistakes of so many microservices' fingertips and more complex mutual calls? At this time, service registration and discovery can solve these problems.
It is conceivable that for user services, I only need to register a service named "User" with the service center, and then upload my ip and port to the registration center. Then the caller can simply find the User service based on the name of the service. The caller does not care how many machines are in this service, which will be solved by the registry. So what's the benefit of doing this:
- The number of servers in a microservice can be increased at any time
- Heartbeat detection can be done through a unified registry to ensure service availability
- No need to maintain complex service names and corresponding IP ports, etc.
- It can be a unified registration and discovery system, a unified early warning or alarm mechanism, and problems can be found in time
This is why there is a service discovery registration module in each microservice framework.
How to register with service discovery in Kratos
Kratos will not explain too much in this article. It is an open source microservice framework for station B. In this frame there is an interface:
type Registrar interface {
// 注册实例
Register(ctx context.Context, service *ServiceInstance) error
// 反注册实例
Deregister(ctx context.Context, service *ServiceInstance) error
}
type Discovery interface {
// 根据 serviceName 直接拉取实例列表
GetService(ctx context.Context, serviceName string) ([]*ServiceInstance, error)
// 根据 serviceName 阻塞式订阅一个服务的实例列表信息
Watch(ctx context.Context, serviceName string) (Watcher, error)
}
As long as our objects implement these two interfaces, we can use service registration and discovery in the framework.
Of course, most mainstream service discovery components such as consul, etcd, kubernetes, zookeeper, etc. have been implemented by default in Kratos.
So how do we use it?
service registration
In service registration, we only need to create a Registrar object and inject this object into Kratos to realize service discovery. Of course this service needs to have a unique name.
The following code uses consul as an example.
import (
consul "github.com/go-kratos/kratos/contrib/registry/consul/v2"
"github.com/hashicorp/consul/api"
)
// new consul client
client, err := api.NewClient(api.DefaultConfig())
if err != nil {
panic(err)
}
// new reg with consul client
reg := consul.New(client)
app := kratos.New(
// service-name
kratos.Name(Name),
kratos.Version(Version),
kratos.Metadata(map[string]string{}),
kratos.Logger(logger),
kratos.Server(
hs,
gs,
),
// with registrar
kratos.Registrar(reg),
)
core code
The following is the core code of consul in Kratos as service discovery. We see that the first method is some processing and judgment of the service. Note that this service can include the http protocol and the grpc protocol. Finally, an object is formed, and the ServiceRegister method is called. In the method, you can see that a put request is actually sent to consul and a service is registered.
This put request is the interface of the registration service exposed by consul to the outside world. In fact, Kratos encapsulates a layer of this interface, and automatically registers the service by reading the configuration.
// Register register service instance to consul
func (c *Client) Register(_ context.Context, svc *registry.ServiceInstance, enableHealthCheck bool) error {
addresses := make(map[string]api.ServiceAddress)
checkAddresses := make([]string, 0, len(svc.Endpoints))
for _, endpoint := range svc.Endpoints {
raw, err := url.Parse(endpoint)
if err != nil {
return err
}
addr := raw.Hostname()
port, _ := strconv.ParseUint(raw.Port(), 10, 16)
checkAddresses = append(checkAddresses, net.JoinHostPort(addr, strconv.FormatUint(port, 10)))
addresses[raw.Scheme] = api.ServiceAddress{Address: endpoint, Port: int(port)}
}
asr := &api.AgentServiceRegistration{
ID: svc.ID,
Name: svc.Name,
Meta: svc.Metadata,
Tags: []string{fmt.Sprintf("version=%s", svc.Version)},
TaggedAddresses: addresses,
}
if len(checkAddresses) > 0 {
host, portRaw, _ := net.SplitHostPort(checkAddresses[0])
port, _ := strconv.ParseInt(portRaw, 10, 32)
asr.Address = host
asr.Port = int(port)
}
if enableHealthCheck {
for _, address := range checkAddresses {
asr.Checks = append(asr.Checks, &api.AgentServiceCheck{
TCP: address,
Interval: fmt.Sprintf("%ds", c.healthcheckInterval),
DeregisterCriticalServiceAfter: fmt.Sprintf("%ds", c.deregisterCriticalServiceAfter),
Timeout: "5s",
})
}
}
if c.heartbeat {
asr.Checks = append(asr.Checks, &api.AgentServiceCheck{
CheckID: "service:" + svc.ID,
TTL: fmt.Sprintf("%ds", c.healthcheckInterval*2),
DeregisterCriticalServiceAfter: fmt.Sprintf("%ds", c.deregisterCriticalServiceAfter),
})
}
err := c.cli.Agent().ServiceRegister(asr)
if err != nil {
return err
}
if c.heartbeat {
go func() {
time.Sleep(time.Second)
err = c.cli.Agent().UpdateTTL("service:"+svc.ID, "pass", "pass")
if err != nil {
log.Errorf("[Consul]update ttl heartbeat to consul failed!err:=%v", err)
}
ticker := time.NewTicker(time.Second * time.Duration(c.healthcheckInterval))
defer ticker.Stop()
for {
select {
case <-ticker.C:
err = c.cli.Agent().UpdateTTL("service:"+svc.ID, "pass", "pass")
if err != nil {
log.Errorf("[Consul]update ttl heartbeat to consul failed!err:=%v", err)
}
case <-c.ctx.Done():
return
}
}
}()
}
return nil
}
func (a *Agent) serviceRegister(service *AgentServiceRegistration, opts ServiceRegisterOpts) error {
r := a.c.newRequest("PUT", "/v1/agent/service/register")
r.obj = service
r.ctx = opts.ctx
if opts.ReplaceExistingChecks {
r.params.Set("replace-existing-checks", "true")
}
_, resp, err := a.c.doRequest(r)
if err != nil {
return err
}
defer closeResponseBody(resp)
if err := requireOK(resp); err != nil {
return err
}
return nil
}
service discovery
Reference article:
Deep dive into service registration and discovery
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。