-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: define Breaker and implement Google SRE circuit breaker.
- Loading branch information
1 parent
7a01e11
commit 905ab09
Showing
18 changed files
with
819 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Complete configurations: https://golangci-lint.run/usage/configuration/ | ||
|
||
linters: | ||
enable: | ||
- asasalint | ||
- asciicheck | ||
- bidichk | ||
- bodyclose | ||
- contextcheck | ||
- durationcheck | ||
- errcheck | ||
- errname | ||
- errorlint | ||
- exportloopref | ||
- gochecknoglobals | ||
- gochecknoinits | ||
- gocritic | ||
- godot | ||
- gofmt | ||
- gofumpt | ||
- goimports | ||
- gomnd | ||
- gosec | ||
- gosimple | ||
- govet | ||
- ineffassign | ||
- interfacer | ||
- misspell | ||
- nakedret | ||
- nilerr | ||
- nilnil | ||
- noctx | ||
- nolintlint | ||
- prealloc | ||
- predeclared | ||
- promlinter | ||
- reassign | ||
- revive | ||
- rowserrcheck | ||
- sqlclosecheck | ||
- staticcheck | ||
- stylecheck | ||
- tenv | ||
- testableexamples | ||
- thelper | ||
- tparallel | ||
- unconvert | ||
- unparam | ||
- unused | ||
- usestdlibvars | ||
- wastedassign | ||
|
||
linters-settings: | ||
gosec: | ||
excludes: | ||
- G404 # Use of weak random number generator (math/rand instead of crypto/rand) | ||
- G501 # Blocklisted import crypto/md5: weak cryptographic primitive | ||
- G401 #Use of weak cryptographic primitive | ||
|
||
revive: | ||
rules: | ||
- name: unexported-return | ||
disabled: true | ||
|
||
output: | ||
sort-results: true | ||
|
||
issues: | ||
exclude-rules: | ||
- path: "_test\\.go" | ||
linters: | ||
- gochecknoglobals |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,42 @@ | ||
# breaker | ||
Circuit Breaker | ||
# What is this? | ||
|
||
Circuit Breaker in Go. | ||
|
||
# Why use it? | ||
|
||
A grace way to Handling Overload in client-side. | ||
|
||
# How does it work? | ||
|
||
There are only one implementation of Circuit Breaker, it is from [Google SRE](https://sre.google/sre-book/handling-overload). | ||
|
||
# How to use it? | ||
|
||
The abstract of Breaker interface is clear, it only cares about: | ||
|
||
- the dependency is available or not | ||
|
||
Not care about: | ||
|
||
- specific errors | ||
- fallback strategies | ||
- telemetry | ||
|
||
There are some examples to show how to use it: | ||
|
||
- Use Circuit Breaker to protect your service (e.g. [example/simple/breaker.go](example/simple/breaker.go)) | ||
- Handle specific errors (e.g. [example/acceptableerror/breaker.go](example/acceptableerror/breaker.go)) | ||
- Add fallback strategies (e.g. [example/fallback/breaker.go](example/fallback/breaker.go)) | ||
- Add telemetry middleware (e.g. [example/telemetry/breaker.go](example/telemetry/breaker.go)) | ||
|
||
# Benchmark | ||
|
||
```bash | ||
❯ go test -bench=. -benchmem | ||
goos: darwin | ||
goarch: arm64 | ||
pkg: github.com/chenyanchen/breaker | ||
BenchmarkGoogleBreaker_Do-8 5794507 249.1 ns/op 0 B/op 0 allocs/op | ||
PASS | ||
ok github.com/chenyanchen/breaker 1.658s | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package breaker | ||
|
||
import "errors" | ||
|
||
type Breaker interface { | ||
Do(func() error) error | ||
} | ||
|
||
var ErrServiceUnavailable = errors.New("circuit breaker is open") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package acceptableerror | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
|
||
"github.com/chenyanchen/breaker" | ||
"github.com/chenyanchen/breaker/example" | ||
) | ||
|
||
type breakerContentService struct { | ||
breaker breaker.Breaker | ||
|
||
contentService example.ContentService | ||
} | ||
|
||
func NewBreakerContentService(breaker breaker.Breaker, contentService example.ContentService) example.ContentService { | ||
return &breakerContentService{ | ||
breaker: breaker, | ||
contentService: contentService, | ||
} | ||
} | ||
|
||
func (s *breakerContentService) GetContent(ctx context.Context, req *example.GetContentRequest) (*example.GetContentResponse, error) { | ||
var resp *example.GetContentResponse | ||
getContentFn := func() (err error) { | ||
resp, err = s.contentService.GetContent(ctx, req) | ||
return err | ||
} | ||
|
||
// handle acceptable errors | ||
getContentFn = handleAcceptableErrors(getContentFn, example.ErrContentNotFound) | ||
|
||
err := s.breaker.Do(getContentFn) | ||
return resp, err | ||
} | ||
|
||
func handleAcceptableErrors(fn func() error, acceptableErrors ...error) func() error { | ||
return func() error { | ||
err := fn() | ||
if err == nil { | ||
return nil | ||
} | ||
|
||
for _, target := range acceptableErrors { | ||
if errors.Is(err, target) { | ||
// TODO: do something, like log | ||
return nil | ||
} | ||
} | ||
|
||
return err | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/chenyanchen/breaker" | ||
"github.com/chenyanchen/breaker/example" | ||
"github.com/chenyanchen/breaker/example/simple" | ||
) | ||
|
||
func main() { | ||
contentService := &contentService{} | ||
|
||
breakerContentService := simple.NewBreakerContentService(breaker.NewGoogleBreaker(), contentService) | ||
|
||
response, err := breakerContentService.GetContent(context.Background(), &example.GetContentRequest{}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
fmt.Println("response:", response) | ||
} | ||
|
||
type contentService struct{} | ||
|
||
func (*contentService) GetContent(context.Context, *example.GetContentRequest) (*example.GetContentResponse, error) { | ||
return &example.GetContentResponse{}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package fallback | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/chenyanchen/breaker" | ||
"github.com/chenyanchen/breaker/example" | ||
) | ||
|
||
type breakerContentService struct { | ||
breaker breaker.Breaker | ||
|
||
contentService example.ContentService | ||
|
||
defaultContent *example.GetContentResponse | ||
} | ||
|
||
func NewBreakerContentService( | ||
breaker breaker.Breaker, | ||
contentService example.ContentService, | ||
defaultContent *example.GetContentResponse, | ||
) example.ContentService { | ||
return &breakerContentService{ | ||
breaker: breaker, | ||
contentService: contentService, | ||
defaultContent: defaultContent, | ||
} | ||
} | ||
|
||
func (s *breakerContentService) GetContent(ctx context.Context, req *example.GetContentRequest) (*example.GetContentResponse, error) { | ||
var resp *example.GetContentResponse | ||
err := s.breaker.Do(func() (err error) { | ||
resp, err = s.contentService.GetContent(ctx, req) | ||
return err | ||
}) | ||
if err == nil { | ||
return resp, nil | ||
} | ||
|
||
// do fallback strategy | ||
if s.defaultContent != nil { | ||
return s.defaultContent, nil | ||
} | ||
|
||
return resp, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
module github.com/chenyanchen/breaker/example | ||
|
||
go 1.21.3 | ||
|
||
require ( | ||
github.com/chenyanchen/breaker v0.0.1 | ||
go.opentelemetry.io/otel/metric v1.19.0 | ||
) | ||
|
||
require go.opentelemetry.io/otel v1.19.0 // indirect | ||
|
||
replace github.com/chenyanchen/breaker => ../ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= | ||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | ||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= | ||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= | ||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | ||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= | ||
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= | ||
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= | ||
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= | ||
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= | ||
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package example | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
) | ||
|
||
type ContentService interface { | ||
GetContent(ctx context.Context, req *GetContentRequest) (*GetContentResponse, error) | ||
} | ||
|
||
type ( | ||
GetContentRequest struct{} | ||
GetContentResponse struct{} | ||
) | ||
|
||
var ErrContentNotFound = errors.New("content not found") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package simple | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/chenyanchen/breaker" | ||
|
||
"github.com/chenyanchen/breaker/example" | ||
) | ||
|
||
type breakerContentService struct { | ||
breaker breaker.Breaker | ||
|
||
contentService example.ContentService | ||
} | ||
|
||
func NewBreakerContentService(breaker breaker.Breaker, contentService example.ContentService) example.ContentService { | ||
return &breakerContentService{ | ||
breaker: breaker, | ||
contentService: contentService, | ||
} | ||
} | ||
|
||
func (s *breakerContentService) GetContent(ctx context.Context, req *example.GetContentRequest) (*example.GetContentResponse, error) { | ||
var resp *example.GetContentResponse | ||
err := s.breaker.Do(func() (err error) { | ||
resp, err = s.contentService.GetContent(ctx, req) | ||
return err | ||
}) | ||
return resp, err | ||
} |
Oops, something went wrong.