On-the-Go micro-service Building (vii)-service discovery and load balancing

Source: Internet
Author: User
Tags http 200 docker swarm etcd
This is a creation in Article, where the information may have evolved or changed.

Section Seventh: Service Discovery and load balancing

Original address

Reprint please specify the original and translation address

This article will focus on the important parts of the two microservices architectures: Service discovery and load balancing. And how they helped us 2017 years of constantly demanding horizontal expansion capacity

Brief introduction

Load balancing and fame. Service discovery requires some explanation, starting with a problem:
"Service A How to request service B, if you do not know how to find B"
In other words, if you have 10 service B running on a random cluster node, someone wants to record these instances, so when a needs to contact B, at least one IP address or hostname can be used (user load balancer), or service a must be able to get the logical name of service B from a third party (server load balancer). Both of these approaches require service discovery in a microservices architecture. Simply put, service discovery is a registrar for a variety of services
If this sounds like DNS, it really is. The difference is that this service discovery is used within your cluster to help service find each other. However, DNS is usually more static and is helping outside to request your service. At the same time, DNS servers and DNS protocols are not suitable for controlling the changing environment of microservices, and containers and nodes are often increased and decreased.
Most of the service framework provides one or more choices to service discovery. By default, Spring Cloud/netflix OSS is powered by Netflix Eureka (also supports Consul, ETCD, ZooKeeper), each service is registered in the Eureka instance, Then send heartbeats to let Eureka know they're still working. Another famous is Consul, which offers many features including integrated DNS. Other well-known choices use key-value pairs to store the registration service, such as ETCD.
Here, we mainly look at the mechanism in the swarm. Also, let's take a look at using unit Test (Gock) to simulate HTTP requests, because we want to do service-to-service communication.

Two kinds of load balancing

For service implementations, we divide load balancing into two types:

    • Client: The client requests a discovery service to get the address (IP, hostname, port). From here, they can choose an address either randomly or round-robin. In order not to extract from the discovery service every time, each client saves some cache, As the discovery service is updated. Client load Balancing in the spring cloud ecosystem, the example is the Netflix ribbon. The Go-kit is similar to ETCD. The advantage of client load balancing is to remove the hub, without the bottleneck of the center, Because each service holds their own registrar. The disadvantage is that the internal service is complicated and the local registrar contains the risk of a bad path.

    • Server segment: In this model, the client relies on the load balancer to find the name of the service it wants to request. This model is usually a proxy mode because it can be used to load balance or reverse proxy. This is a little bit simple, load balancing and service discovery mechanisms are usually included in the container deployment, You do not need to install and manage these sections. Again, our service does not need to know the service registrar and the load balancer will help us. All requests through the complex equalizer will make him a bottleneck.

When we use Docker swarm services, the server-side real service (producer services) registration is completely transparent to the developer. That is, our services do not know that they are running on server-side load balancing, Docker swarm complete the entire registration/ heartbeat/de-registration.

Using Service discovery Information

Suppose you want to create a custom monitoring application that needs to request the/health path for all deployed services, and how your monitoring application knows these IP and ports. You need to get the details of the service request. For swarm to save this information, how do you get them. For client-side methods, such as Eureka, You can use the API directly, however, for services that depend on deployment, it's not easy, I can say there is a way to do it, and there are a lot of ways to deal with different situations.

Docker Remote API

I recommend using the Docker remote API to use the Docker API in your service to request information from other services to Swarm manager. After all, if you use your container to deploy the built-in service discovery mechanism, this is where you should request it. If there is a problem, Others can also write an adapter to your deployment. However, there are limitations with the Deployment API: You are tightly aware of the container's API, and you need to make sure that your app can communicate with Docker manager.

Other programmes

    • Using other service discovery mechanisms-netflix Eureka, Consul, etc. use the API of these services to register/query/heartbeat and so on. I don't like this way because it makes our services more complex and swarm can do them. I think it's anti-design mode, So don't do it normally.
    • Application-Specific token discovery: In this way, each service sends their own tokens, with IP, service name, and so on. Users can subscribe to these services while updating their registrars. We see Netflix turbine without Eureka, We will use this mechanism. This method is slightly different because it does not have to register all the services, after all, we only care about a part of the service.

Code

git checkout P7

Scaling and load Balancing

Let's see if we can start more than one Accountservice instance to implement the extension while watching us swarm automatically do load balancing requests.
In order to know which instance replies to our request, we add a new account structure and we can export the IP address. Open Account.go

type Account struct {    Id string `json:"id"`    Name string `json:"name"`    //new    ServedBy string `json:"servedBy"}

Open Handlers.go, add the GetIP () function, let him output the value of Servedby:

  func getaccount (w http. Responsewriter, R *http. Request) {//Read the ' accountId ' path parameter from the Mux map var accountId = mux. Vars (R) ["AccountId"]//Read The account struct BOLTDB account, err: = Dbclient.queryaccount (AccountId) A Ccount. Servedby = GetIP ()//NEW, add this line ...} ADD this funcfunc getip () string {Addrs, err: = Net.                Interfaceaddrs () if err! = Nil {return "error"} for _, Address: = Range Addrs { Check the address type and if it is not a loopback the display it if ipnet, OK: = Address. (*net. Ipnet); OK &&!ipnet. Ip. Isloopback () {if ipnet. Ip. To4 ()! = nil {return ipnet. Ip. String ()}}} Panic ("Unable to determine local IP address (non LOOPBA CK). Exiting. ")}  

The GetIP () function should use some utils packages, as these can be reused when we need to determine the Non-loopback IP address of a running service.
Recompile and deploy our services

> ./copyall.sh

Wait until the end, enter

> docker service lsID            NAME             REPLICAS  IMAGEyim6dgzaimpg  accountservice   1/1       someprefix/accountservice

With Curl

> curl $ManagerIP:6767/accounts/10000{"id":"10000","name":"Person_0","servedBy":"10.255.0.5"}  

Now that we see the IP address of the container in the reply, we extend these services

> Docker service scale accountservice=3accountservice scaled to 3

Wait a minute to run

> docker service lsID            NAME             REPLICAS  IMAGEyim6dgzaimpg  accountservice   3/3       someprefix/accountservice

Now there are three instances, we curl several times, take a look at the resulting IP address

curl $ManagerIP:6767/accounts/10000{"id":"10000","name":"Person_0","servedBy":"10.0.0.22"}curl $ManagerIP:6767/accounts/10000{"id":"10000","name":"Person_0","servedBy":"10.255.0.5"}curl $ManagerIP:6767/accounts/10000{"id":"10000","name":"Person_0","servedBy":"10.0.0.18"}curl $ManagerIP:6767/accounts/10000{"id":"10000","name":"Person_0","servedBy":"10.0.0.22"}

We saw four requests for each instance of the Round-robin method. This swarm provides a good service because it is convenient, and we do not need to choose one from a bunch of IP addresses like the client Discovery service. And, Swarm does not send requests to nodes that have healthcheck methods but do not report their health. When you expand and shrink very often, and your services are complex and require much more time than Accountservice, this will be important.

Performance

Take a look at the delay after expansion and cpu/memory usage. Will it increase?

> docker service scale accountservice=4

CPU and Memory utilization

Gatling test (1k req/s)

CONTAINER                                    CPU %               MEM USAGE / LIMIT       accountservice.3.y8j1imkor57nficq6a2xf5gkc   12.69%              9.336 MiB / 1.955 GiB accountservice.2.3p8adb2i87918ax3age8ah1qp   11.18%              9.414 MiB / 1.955 GiB accountservice.4.gzglenb06bmb0wew9hdme4z7t   13.32%              9.488 MiB / 1.955 GiB accountservice.1.y3yojmtxcvva3wa1q9nrh9asb   11.17%              31.26 MiB / 1.955 GiB

Our four instances share these jobs, and these three new instances use less than 10MB of memory in less than req/s cases.

Performance

Gatling test of an instance

Gatling test of four instances

It's not a big difference. Because our four instances are also running on the same virtual machine hardware. If we allocate some resources to swarm, we will see a drop in latency. We see a little bit of ascension on the 95 and 99 average delays. We can say, Swarm load balancing has no negative impact on performance.

Join quotes

Remember our Java-based Quotes-service? Let's expand him and request him from Accountservice, with the service name Quotes-service. The purpose is to look at the name we only know when service discovery and load balancing are good.
Let's change the account.go first.

 type Account struct {         Id string `json:"id"`         Name string  `json:"name"`         ServedBy string `json:"servedBy"`         Quote Quote `json:"quote"`         // NEW }  // NEW struct type Quote struct {         Text string `json:"quote"`         ServedBy string `json:"ipAddress"`         Language string `json:"language"` }

We use JSON tags to convert names, from quote to Text,ipaddress to Servedby.
Change the handler.go. We add a simple getquote function to request the Http://quotes-service:8080/api/quote, and the return value is used to output the new quote structure. We requested him in the Getaccount function.
First, we handle the connection, keep-alive will have a load balancer problem, unless we change the HTTP client for go. In Handler.go, add:

var client = &http.Client{}func init() {        var transport http.RoundTripper = &http.Transport{                DisableKeepAlives: true,        }        client.Transport = transport}

The Init method ensures that the HTTP request that is sent has the appropriate header information to enable the Swarm load balancer to work properly. Under Getaccount function, add GetQuote function

func getQuote() (model.Quote, error) {        req, _ := http.NewRequest("GET", "http://quotes-service:8080/api/quote?strength=4", nil)        resp, err := client.Do(req)        if err == nil && resp.StatusCode == 200 {                quote := model.Quote{}                bytes, _ := ioutil.ReadAll(resp.Body)                json.Unmarshal(bytes, &quote)                return quote, nil        } else {                return model.Quote{}, fmt.Errorf("Some error")        }}

Nothing special, huh? Strength=4 is how much CPU is used to get the Quotes-service API. If the request is wrong, an error is returned.
We request the GetQuote function from the Getaccount function to attach the value returned by the account instance to quote.

// Read the account struct BoltDBaccount, err := DBClient.QueryAccount(accountId)account.ServedBy = getIP()// NEW call the quotes-servicequote, err := getQuote()if err == nil {        account.Quote = quote}

The HTTP request sent by unit testing

If we run Handlers_test.go's unit test, we will fail. The Getaccount function tries to request a quote, but there is no quotes service on this URL.
There are two ways we can solve this problem.
1) Extract the GetQuote function as a interface, providing a true and a false method.
2) Use the HTTP-specific mcking framework to process the sent request and return a well-written answer. The built-in Httptest package can help us Open a built-in HTTP server for unit test. But I like to use a third-party gock framework.
In Handlers_test.go, the init function is added to Testgetaccount (t *testing). This causes our HTTP client instance to be Gock fetched

func inti() {    gock.InterceptClient(client)}

The Gock DSL provides good control over the external HTTP requests and responses that are expected. In the following example, we use new (), get (), and Matchparam () to make gock look forward to http://quotes-service:8080/api/quote? strength=4 Get request, reply to HTTP 200 and JSON string.

func TestGetAccount(t *testing.T) {        defer gock.Off()        gock.New("http://quotes-service:8080").                Get("/api/quote").                MatchParam("strength", "4").                Reply(200).                BodyString(`{"quote":"May the source be with you. Always.","ipAddress":"10.0.0.5:8080","language":"en"}`)

Defer Gock. OFF () Ensure that our test will stop HTTP acquisition because of Gock. New () Turns on HTTP fetch, which could be a later test failure.
We assert that the returned quote

Convey("Then the response should be a 200", func() {        So(resp.Code, ShouldEqual, 200)        account := model.Account{}        json.Unmarshal(resp.Body.Bytes(), &account)        So(account.Id, ShouldEqual, "123")        So(account.Name, ShouldEqual, "Person_123")                // NEW!        So(account.Quote.Text, ShouldEqual, "May the source be with you. Always.")})

Run test

It means running all the tests under Accountservice.
Re-deploy with./copyall.sh, try Curl.

> go test ./...?       github.com/callistaenterprise/goblog/accountservice    [no test files]?       github.com/callistaenterprise/goblog/accountservice/dbclient    [no test files]?       github.com/callistaenterprise/goblog/accountservice/model    [no test files]ok      github.com/callistaenterprise/goblog/accountservice/service    0.011s
> curl $ManagerIP:6767/accounts/10000  {"id":"10000","name":"Person_0","servedBy":"10.255.0.8","quote":      {"quote":"You, too, Brutus?","ipAddress":"461caa3cef02/10.0.0.5:8080","language":"en"}  }

Expansion Quotes-service

> docker service scale quotes-service=2

For spring boot quotes-service, it needs to be 15-30s, not as fast as go. We curl a few times

{"id":"10000","name":"Person_0","servedBy":"10.255.0.15","quote":{"quote":"To be or not to be","ipAddress":"768e4b0794f6/10.0.0.8:8080","language":"en"}}{"id":"10000","name":"Person_0","servedBy":"10.255.0.16","quote":{"quote":"Bring out the gimp.","ipAddress":"461caa3cef02/10.0.0.5:8080","language":"en"}}{"id":"10000","name":"Person_0","servedBy":"10.0.0.9","quote":{"quote":"You, too, Brutus?","ipAddress":"768e4b0794f6/10.0.0.8:8080","language":"en"}}

We see our Servedby loop with Accountservice instances. We also see that there are two IP addresses for quote. If we do not close keep-alive, we may only see a Quote-service instance

Summarize

This article we contacted service discovery and load balancing and how to request other services using the service name
Next, we will continue to micro-service knowledge points, centralized configuration.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.