Background
In the second article of this series, I will review the source code of hystrix-go project to understand how to design a circuit breaker and how to implement it with Golang.
If you’re not familiar with circuit breaker pattern or hystrix-go project, please check my previous article about it.
Three service degradation strategies
Hystrix provides three different service degradation strategies to avoid the cascading failure happening in the entire system: timeout, maximum concurrent request numbers and request error rate.
- timeout: if the service call doesn’t return response successfully within a predefined time duration, then the fallback logic will run. This strategy is the simplest one.
- maximum concurrent request numbers: when the number of concurrent requests is beyond the threshold, then the fallback logic will handle the following request.
- request error rate:
hystrixwill record the response status of each service call, after the error rate reaches the threshold, the breaker will be open, and the fallback logic will execute before the breaker status changes back to closed.error ratestrategy is the most complex one.
This can be seen from the basic usage of hystrix as follows:
1 | import ( |
In the above usage case, you can see that timeout is set to 10 seconds, the maximum request number is 100, and the error rate threshold is 25 percentages.
In the consumer application level, that’s nearly all of the configuration you need to setup. hystrix will make the magin happen internally.
In this series of articles, I plan to show you the internals of hystrix by reviewing the source code.
Let’s start from the easy ones: max concurrent requests and timeout. Then move on to explore the complex strategy request error rate.
GoC
Based on the above example, you can see Go function is the door to the source code of hystrix, so let’s start from it as follows:
1 | func Go(name string, run runFunc, fallback fallbackFunc) chan error { |
Go function accept three parameters:
- name: the command name, which is bound to the
circuitcreated inside hystrix. - run: a function contains the normal logic which send request to the dependency service.
- fallback: a function contains the fallback logic.
Go function just wraps run and fallback with Context, which is used to control and cancel goroutine, if you’re not familiar with it then refer to my previous article. Finally it will call GoC function.
GoC function goes as follows:
1 | func GoC(ctx context.Context, name string, run runFuncC, fallback fallbackFuncC) chan error { |
I admit it’s complex, but it’s also the core of the entire hystrix project. Be patient, let’s review it bit by bit carefully.
First of all, the code structure of GoC function is as follows:

- Construct a new
Commandobject, which contains all the information for each call toGoCfunction. - Get the
circuit breakerby name (create it if it doesn’t exist) by callingGetCircuit(name)function. - Declare condition variable ticketCond and ticketChecked with
sync.Condwhich is used to communicate between goroutines. - Declare function returnTicket. What is a ticket? What does it mean by returnTicket? Let’s discuss it in detail later.
- Declare another function reportAllEvent. This function is critical to
error ratestrategy, and we can leave it for detailed review in the following articles. - Declare an instance of
sync.Once, which is another interestingsynchronization primitivesprovided by golang. - Launch two goroutines, each of which contains many logics too. Simply speaking, the first one contains the logic of sending requests to the target service and the strategy of
max concurrent request number, and the second one contains thetimeoutstrategy. - Return a
channeltype value
Let’s review each of them one by one.
command
command struct goes as follows, which embeds sync.Mutex and defines several fields:
1 | type command struct { |
Note that command object iteself doesn’t contain command name information, and its lifecycle is just inside the scope of one GoC call. It means that the statistic metrics about the service request like error rate and concurrent request number are not stored inside command object. Instead, such metrics are stored inside circuit field which is CircuitBreaker type.
CircuitBreaker
As we mentioned in the workflow of GoC function, GetCircuit(name) is called to get or create the circuit breaker. It is implemented inside circuit.go file as follows:
1 | func init() { |
The logic is very straightforward. All the circuit breakers are stored in a map object circuitBreakers with the command name as the key.
The newCircuitBreaker constructor function and CircuitBreaker struct are as follows:
1 | type CircuitBreaker struct { |
All the fields of CircuitBreaker are important to understand how the breaker works.
There are two fields that are not simple type need more analysis, include executorPool and metrics.
- executorPool: used for
max concurrent request numberstrategy, which is just this article’s topic. - metrics: used for
request error ratestrategy, which will be discussed in the next article, all right?
executorPool
We can find executorPool logics inside the pool.go file:
1 | type executorPool struct { |
It makes use of golang channel to realize max concurrent request number strategy. Note that Tickets field, which is a buffered channel with capicity of MaxConcurrentRequests is created. And in the following for loop, make the buffered channel full by sending value into the channel until reaching the capacity.
As we have shown above, in the first goroutine of GoC function, the Tickets channel is used as follows:
1 | go func() { |
Each call to GoC function will get a ticket from circuit.executorPool.Tickets channel until no ticket is left, which means the number of concurrent requests reaches the threshold. In that case, the default case will execute , and the service will be gracefully degraded with fallback logic.
On the other side, after each call to GoC is done, the ticket need to be sent back to the circuit.executorPool.Tickets, right? Do you remember the returnTicket function mentioned in above section. Yes, it is just used for this purpose. The returnTicket function defined in GoC function goes as follows:
1 | returnTicket := func() { |
It calls executorPool.Return function:
1 | // Return function in pool.go file |
The design and implementation of Tickets is a great example of golang channel in the real-world application.
In summary, the max concurrent request number strategy can be illustrated as follows:
Summary
In this article, max concurrent requests strategy in hystrix is reviewed carefully, and I hope you can learn something interesting from it.
But I didn’t cover the detailed logics inside GoC function, including sync.Cond, sync.Once and fallback logics. Let’s review them and timeout strategy together in the next article.