preface

Recently, I wanted to learn some knowledge about gateway. I searched and found a project about Wukong API gateway. Well documented and enterprise-level, the decision was made: goku-api-Gateway

The problem

Before you look at the source code, you have to set a target. It’s easy to get lost by looking at the code blindly. After reading the official documentation and experimenting with it, I decided on the following goals.

  • Goku-api-gateway How to collect monitoring information? How to store it?
  • How to make efficient forwarding?
  • How does QPS limit work in distributed situations, especially the second limit?
  • How to add new filtering function conveniently?
  • Is there anything to learn?
  • Is there anything that can be improved?
  • What should the Thinking gateway provide?
  • What are the challenges facing the Thinking gateway?

Key structures of GOKU

Before looking at the code, it’s important to understand how data is abstracted in Goku-api-Gateway. This open the management background, the use of the need to set up the things set again, this piece of basic can also be. The corresponding structure is here: server/conf.

The key of

API: defines an interface forwarding, which mainly contains, request URL, forwarding URL, method, traffic policy and other information

Policy: Defines traffic restriction policies, including authentication mode, IP address blacklist and whitelist, and traffic control

The general process of processing a request

The entrance

In the outermost layer of the project there are two files: goku-ce. Go, goku-ce-admin.go. Goku-ce-admin. go is the back-end management interface and goku-ce-go is the real gateway service.

goku-ce.go

See ListenAndServe is estimated to be the Web framework that set of things, you can global search ServeHTTP. Middleware.Mapping is the handler for each API.

func main(a) {
	server := goku.New()
	
	/ / register routing handlers for server RegisterRouter (middleware server. ServiceConfig, Mapping, middleware. GetVisitCount)
	fmt.Println("Listen",server.ServiceConfig.Port)
    
    // Start the service
	err := goku.ListenAndServe(":" + server.ServiceConfig.Port,server)
    iferr ! =nil {
		log.Println(err)
	}
	log.Println("Server on " + server.ServiceConfig.Port + " stopped")
	os.Exit(0)}Copy the code

ServeHTTP

Gin is a framework that comes to mind when you see the trees in the code. When you click on it, you find that the routing tree is basically the same as gin, but the content in the nodes is a little different. Instead of one interface for a set of handlers, there is only one. Add a pointer to the Context object, which stores the forwarding address, traffic limiting policy, statistics and so on in the API. Context object is the most important object to understand the entire gateway processing. It is equivalent to the local cache of the interface information, when the route handler function is found, the local cache of the interface information is found, reducing a cache query, this idea is very good!!

Func (r *Router) ServeHTTP(w http.responsewriter, req * http.request) {//ifroot := r.trees[req.Method]; root ! // handle, ps, context, TSR := root.getValue(path);ifhandle ! = nil { handle(w, req, ps,context)return
		} else{// omit N code} // omit N code} //typeNode struct {path String wildChild bool nType nodeType maxParams Uint8 indices String children []*node Handle HANDLE PRIORITY uint32 // API forwarding address, traffic limiting policy, and statisticsCopy the code

middleware.Mapping

In Goku-ce. go it says that this is the interface handler, and the whole process is very clear, and you can see how the various filters are done by following the dots. In fact, it can be found that the whole code does not deal with the small details of high concurrency very well, and specific areas where there can be improvements will be highlighted.

func Mapping(res http.ResponseWriter, Req *http.Request,param goku.params,context * goku.context) {// Update the number of live access times go Context. VisitCount. CurrentCount. UpdateDayCount () / / verification IP whether f, s: = IPLimit (context, res, the req)if! F {res. WriteHeader (403) res. Write (byte [] (s)) / / statistical information collection go context. VisitCount. FailureCount. UpdateDayCount () go context.VisitCount.TotalCount.UpdateDayCount()return} f,s = Auth(context,res,req)if! f { res.WriteHeader(403) res.Write([]byte(s)) go context.VisitCount.FailureCount.UpdateDayCount() go context.VisitCount.TotalCount.UpdateDayCount()return} // RateLimit f,s = RateLimit(context)if! f { res.WriteHeader(403) res.Write([]byte(s)) go context.VisitCount.FailureCount.UpdateDayCount() go context.VisitCount.TotalCount.UpdateDayCount()return}} statusCode,body,headers := CreateRequest(context,req,res,param)for key,values := range headers {
        for _,value := range values {
            res.Header().Set(key,value)
        }
    }
    res.WriteHeader(statusCode)
    res.Write(body)
    ifstatusCode ! = 200 { go context.VisitCount.FailureCount.UpdateDayCount() go context.VisitCount.TotalCount.UpdateDayCount() }else {
        go context.VisitCount.SuccessCount.UpdateDayCount()
        go context.VisitCount.TotalCount.UpdateDayCount()
    }
    return
}
Copy the code

The answer to the question

Goku-api-gateway How to collect monitoring information? How to store it?

The monitoring information is stored directly in the Context of the interface. The problem arises when multiple nodes are deployed on the gateway, how to collect the monitoring information of each node? With the problem, go to the code, found that there is no code. This is probably a castrated version of the open source version, which can only be deployed on a single node.

How does QPS limit work in distributed situations, especially the second limit?

The code doesn’t take that into account

How to add new filtering function conveniently?

New filtering is needed, add it to the middleware.mapping function. I think we can take a page from the GIN framework, where a URI corresponds to multiple handlers, each of which is a filtering function. In this way, it is even possible to hot-plug, as long as each process provides a list of URI handlers for corresponding interface modifications.

Is there anything to learn?

The interface information is stored in the routing tree

I’ve already said that up there, so I’m not going to do that. Great idea.

Is there anything that can be improved?

In the case of high concurrency, the code requirements will be very high, there is no necessary overhead can be saved, considering the general use of the gateway this thing, the concurrency must be relatively high, so there are the following improvements.

If absolute accuracy is not required, there is no need to call time.now() every time to get the time

There are a lot of code about time judgment, in fact, do not require absolute accuracy, can be directly from the cache inside the time. Because a system call is made every time time.now() is called, the overhead is minimal. Caching is also very simple. Just set a timer that updates every second. Examples of code that could be improved.

func (l *LimitRate) UpdateDayCount() {// TODO improves l.lock.Lock() now := time.now (); // TODO improves l.lock.Lock() now := time.now ()ifnow.Day() ! = l.begin.Day(){
		l.begin = now
		l.count = 0
	}
	l.count++ 
	l.lock.Unlock()
}
Copy the code

If you can cache it, cache it. You don’t have to compute it every time

func (l *LimitRate) UpdateDayCount() {// TODO improved l.lock.Lock() now := time.now () // Begin should be fixed. The date should be calculated at initialization so that l.bigen.day () is not called every time.ifnow.Day() ! = l.begin.Day(){
		l.begin = now
		l.count = 0
	}
	l.count++ 
	l.lock.Unlock()
}
Copy the code

In high concurrency scenarios, try not to type LOG, and the LOG must have a buffer, and the buffer is full before printing

Try not to log. I don’t mean don’t log. Because printing logs to disk involves IO, there is a performance impact. If a certain amount of loss can be tolerated, the log should set a buffer and wait until the buffer is full before printing to disk.

func (l *LimitRate) DayLimit() bool {
    result := trueL.lock. Lock() now := time.now (ifnow.Day() ! = l.begin.Day(){
		l.begin = now
		l.count = 0
	}

	ifl.rate ! = 0 {t := now.hour () bh := now.hour (ifBh && t < = t < l.e nd | | (bh > l.e nd && (t < bh && t < l.e nd)) {/ / TODO one thousand has exceeded the rate a mistake that GG, application use > =if l.count == l.rate {
				result = false
			} else{l.count++}}} // TODO improves this high concurrency scenario by not printing fmt.println ("Day count:")
	fmt.Println(l.count)
	
    l.lock.Unlock()
    return result
}
Copy the code

There is a cost to enabling Goruntime and a simple operation should not open a new Goruntime

Goruntimes has a very, very good reputation for being lightweight and cheap, and it’s not a problem to drive thousands, but that doesn’t mean there’s no cost. Goruntime also has to have a structure to hold, also has to participate in scheduling, also has to queue and so on. In the code, statistics are collected by opening a Goruntime, which simply adds a lock to the counter ++, which is completely unnecessary. Here you can use channle’s approach to create a resident Goruntime dedicated to handling statistics.

func Mapping(res http.ResponseWriter, Req *http.Request,param goku.params,context * goku.context) {// Update the number of live access times go Context. VisitCount. CurrentCount. UpdateDayCount () / / verification IP whether f, s: = IPLimit (context, res, the req)if! f { res.WriteHeader(403) res.Write([]byte(s)) go context.VisitCount.FailureCount.UpdateDayCount() go context.VisitCount.TotalCount.UpdateDayCount()return}}Copy the code

What should the Thinking gateway provide?

This needs to be summarized by looking at the other gateway code.

What are the challenges facing the Thinking gateway?

Gateways, as the entry point to all apis, almost certainly present high concurrency challenges. Being the gateway to all apis also requires high availability.

conclusion

In general, the open source part of the current estimation is just stand-alone code, not what I want. Need to look at other open source gateway code and keep learning.