A practical tutorial on Go Building microservices (iii.)

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.
Original Address
Reprint please specify the original and translation address

In the third section, we're going to let our accountservice do something useful.

    • Declaring an account structure
    • A storage that embeds a key-value pair to store the account structure
    • The serialization structure is JSON and is used for our Accounts/{accountid} HTTP Service

Source

All the code in this blog can be obtained from the branch P3.

git checkout P3

Declaring an account structure

In our project, create a model folder in the Accountservice folder

mkdir model

Under the Model folder, create a file with the name Account.go and write the following code:

package modeltype Account struct {    Id string `json:"id"`    Name string `json:"name"`}

This declares the account, which contains the ID and name. The case of the first letter indicates the scope (uppercase =public, lowercase = in-package call).
In the declaration, we also used the built-in Json.marshal function to support parameter serialization.

Embedding a key-value pair for storage

Here, we will use the BOLTDB to store the key-value pairs. This package is simple, quick and easy to integrate. We can use go get to get this package

go get github.com/boltdb/boltdb/b

After that, in the/goblog/accountservice folder, create a folder dbclient, boltclient.go the file in Dbclient. To make mocking easier, we first declare an interface that is used to define the method that the implementation needs to follow.

package dbclientimport (        "github.com/callistaenterprise/goblog/accountservice/model")type IBoltClient interface {        OpenBoltDb()        QueryAccount(accountId string) (model.Account, error)        Seed()}

In the same file, we implement this interface. Define a bolt that is encapsulated first. The structure of a pointer to a DB

// Real implementationtype BoltClient struct {        boltDB *bolt.DB}

Here is the implementation of OPENBOLTDB (), and we will then add the remaining two functions.

func (bc *BoltClient) OpenBoltDb() {        var err error        bc.boltDB, err = bolt.Open("accounts.db", 0600, nil)        if err != nil {                log.Fatal(err)        }}

This part of the code may seem a little strange, but we are actually binding a method function to a struct. Our structure implicitly implements one of the three methods.
We need a "bolt client" instance in some places. Let's declare where it will be used, create/goblog/accountservice/service/handlers.go, and create an instance of our struct:

package service    import (          "github.com/callistaenterprise/goblog/accountservice/dbclient"  )    var DBClient dbclient.IBoltClient

Update Main.go to open the database when he starts:

func main() {        fmt.Printf("Starting %v\n", appName)        initializeBoltClient()                 // NEW        service.StartWebServer("6767")}// Creates instance and calls the OpenBoltDb and Seed funcsfunc initializeBoltClient() {        service.DBClient = &dbclient.BoltClient{}        service.DBClient.OpenBoltDb()        service.DBClient.Seed()}

Our microservices now create a database at startup. However, we also need to refine the code before running:

Seed some accounts at startup

Open Boltclient to add the following code:

Start seeding Accountsfunc (BC *boltclient) Seed () {initializebucket () seedaccounts ()}//creates an "Acc Ountbucket "in our boltdb. It would overwrite any existing bucket of the same name.func (BC *boltclient) initializebucket () {bc.boltDB.Update ( Func (TX *bolt. TX) Error {_, err: = Tx. Createbucket ([]byte ("Accountbucket")) if err! = Nil {return FMT. Errorf ("Create Bucket failed:%s", err)} return nil})}//Seed (n) Make-Believe Acco UNT objects into the acountbucket bucket.func (BC *boltclient) seedaccounts () {total: = + for I: = 0; I & Lt Total i++ {//Generate a key 10000 or larger key: = StrConv. Itoa (10000 + i)//Create An instance for our account struct ACC: = model. account{Id:key, Name: "Person_" + StrConv.              Itoa (i),}  Serialize the struct to JSON jsonbytes, _: = json. Marshal (ACC)//Write the data to the Accountbucket Bc.boltDB.Update (func (TX *bolt. TX) Error {b: = tx. Bucket ([]byte ("Accountbucket")) Err: = B.put ([]byte (Key), jsonbytes) return ERR})} FMT. Printf ("Seeded%v fake accounts...\n", total)}

Want to know how the update function of the bolt API works. You can refer to Boltdb's documentation

Now let's add the query function:

Func (BC *boltclient) queryaccount (AccountId string) (model. Account, error) {//Allocate a empty account instance we'll let json.        Unmarhal populate for us in a bit. Account: = model. account{}//Read an object from the bucket using Boltdb.view err: = Bc.boltDB.View (func (TX *bolt. TX) Error {//Read the bucket from the DB b: = Tx.                Bucket ([]byte ("Accountbucket"))//Read The value identified by our accountId supplied as []byte Accountbytes: = B.get ([]byte (AccountId)) if accountbytes = = Nil {return FMT. Errorf ("No account found for" + accountId)}//Unmarshal the returned bytes into the Accou NT struct we created at//the top of the function JSON.                Unmarshal (Accountbytes, &account)//Return nil to indicate nothing went wrong, e.g no error return nil})//If There were an error, return the error if err! = Nil {return model.        account{}, err}//Return the account struct and nil as error. Return account, nil}

Annotations make it easier for you to understand. This function will search for BOLTDB with a provided accountid, then return an account structure or error

Now you can try running:

> go run *.goStarting accountserviceSeeded 100 fake accounts...2017/01/31 16:30:59 Starting HTTP service at 6767

Provide account service via HTTP

Let's modify the/accounts/{accountid} route in/service/routes.go so that he returns a seeded account struct. Open Routes.go replace func with the Getaccount function (w http. Responsewriter, R *http. Request). We will then create the Getaccount function:

Route{        "GetAccount",             // Name        "GET",                    // HTTP method        "/accounts/{accountId}",  // Route pattern        GetAccount,},

After that, update service/handlers.go. Add the Getaccount function:

var DBClient dbclient.IBoltClientfunc 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)        // If err, return a 404    if err != nil {        w.WriteHeader(http.StatusNotFound)        return    }        // If found, marshal into JSON, write headers and content    data, _ := json.Marshal(account)    w.Header().Set("Content-Type", "application/json")    w.Header().Set("Content-Length", strconv.Itoa(len(data)))    w.WriteHeader(http.StatusOK)    w.Write(data)}

This getaccount function conforms to the handler function format defined in gorilla. So when Gorilla finds a request/accounts/{accountid}, it is routed to the Getaccount function. Let's run for a try:

> go run *.goStarting accountserviceSeeded 100 fake accounts...2017/01/31 16:30:59 Starting HTTP service at 6767

Use Curl to request this API. Remember, we joined 100 accounts.id starting from 10000.

> curl http://localhost:6767/accounts/10000{"id":"10000","name":"Person_0"}

Yes, our microservices now answer JSON data over HTTP.

Performance

Let's look at memory and CPU usage separately:

Memory utilization after startup

2.1MB, good. Added 300kb after adding embedded boltdb and some other routing code, compared to the initial consumption. Let's test 1K req/s with Gatling. Now we're really returning the real account data and serializing it to JSON.

Memory usage under stress testing

31.2MB. Adding an embedded database does not consume too much resources, compared to the simple Return data service in chapter two

Performance and CPU Usage

1k req/s with a single core of about 10%. Boltdb and JSON serialization do not add too much consumption. By the way, take a look at the above Java program, with three times times more CPU resources

The average response time is still less than 1ms.
Let's try a bigger stress test, 4k req/s. (You may need to increase the OS level to handle the maximum number of requests)

Memory Usage 4k REQ/S

About 12MB grows about 4 times times. Memory growth is likely due to a go run or gorilla increase in the number of internal Goroutine to process requests concurrently.

4k REQ/S Performance

CPU usage is approximately 30%. When we run on a 16GB Ram/core i7 notebook, io or file access will become a bottleneck before the CPU.

The average delay rose to 1ms,95% 's request under 3MS. We saw an increase in latency, but I think the accountservice performance is good.

Summarize

We will discuss unit test in the next article. We will use Goconvey mocking Boltdb client at the same time.

Thank you , please.

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.