├── .codecov.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── go.yml ├── .gitignore ├── .golangci.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── api ├── api.go ├── api_test.go ├── doc.go ├── init.go ├── slot_chain.go ├── tracer.go └── tracer_test.go ├── core ├── base │ ├── block_error.go │ ├── block_error_test.go │ ├── constant.go │ ├── context.go │ ├── context_test.go │ ├── entry.go │ ├── entry_test.go │ ├── metric_item.go │ ├── metric_item_test.go │ ├── resource.go │ ├── result.go │ ├── result_test.go │ ├── rule.go │ ├── slot_chain.go │ ├── slot_chain_test.go │ ├── stat.go │ └── stat_test.go ├── circuitbreaker │ ├── circuit_breaker.go │ ├── circuit_breaker_test.go │ ├── doc.go │ ├── rule.go │ ├── rule_manager.go │ ├── rule_manager_test.go │ ├── rule_test.go │ ├── slot.go │ ├── slot_test.go │ └── stat_slot.go ├── config │ ├── config.go │ ├── config_test.go │ ├── constant.go │ ├── doc.go │ └── entity.go ├── flow │ ├── doc.go │ ├── rule.go │ ├── rule_manager.go │ ├── rule_manager_test.go │ ├── rule_test.go │ ├── slot.go │ ├── slot_test.go │ ├── standalone_stat_slot.go │ ├── tc_adaptive.go │ ├── tc_adaptive_test.go │ ├── tc_default.go │ ├── tc_throttling.go │ ├── tc_throttling_test.go │ ├── tc_warm_up.go │ └── traffic_shaping.go ├── hotspot │ ├── cache │ │ ├── concurrent_cache.go │ │ ├── concurrent_lru.go │ │ ├── concurrent_lru_benchmark_test.go │ │ ├── concurrent_lru_test.go │ │ └── lru.go │ ├── concurrency_stat_slot.go │ ├── doc.go │ ├── params_metric.go │ ├── rule.go │ ├── rule_manager.go │ ├── rule_manager_test.go │ ├── rule_test.go │ ├── slot.go │ ├── slot_test.go │ ├── traffic_shaping.go │ └── traffic_shaping_test.go ├── isolation │ ├── doc.go │ ├── rule.go │ ├── rule_manager.go │ ├── rule_manager_test.go │ └── slot.go ├── log │ ├── metric │ │ ├── aggregator.go │ │ ├── aggregator_test.go │ │ ├── common.go │ │ ├── common_test.go │ │ ├── reader.go │ │ ├── searcher.go │ │ └── writer.go │ └── slot.go ├── outlier │ ├── recycler.go │ ├── recycler_test.go │ ├── retryer.go │ ├── retryer_test.go │ ├── rule.go │ ├── rule_manager.go │ ├── rule_manager_test.go │ ├── slot.go │ └── stat_slot.go ├── stat │ ├── base │ │ ├── atomic_window_wrap_array_test.go │ │ ├── bucket_leap_array.go │ │ ├── bucket_leap_array_test.go │ │ ├── doc.go │ │ ├── leap_array.go │ │ ├── leap_array_test.go │ │ ├── metric_bucket.go │ │ ├── metric_bucket_test.go │ │ ├── mutex.go │ │ ├── mutex_test.go │ │ ├── sliding_window_metric.go │ │ ├── sliding_window_metric_test.go │ │ └── stat_base_benchmark_test.go │ ├── base_node.go │ ├── node_storage.go │ ├── resource_node.go │ ├── stat_prepare_slot.go │ └── stat_slot.go ├── system │ ├── doc.go │ ├── rule.go │ ├── rule_manager.go │ ├── rule_manager_test.go │ ├── rule_test.go │ ├── slot.go │ └── slot_test.go └── system_metric │ ├── sys_metric_stat.go │ └── sys_metric_stat_test.go ├── example ├── circuitbreaker │ ├── error_count │ │ └── circuit_breaker_error_count_example.go │ ├── error_ratio │ │ └── circuit_breaker_error_ratio_example.go │ └── slow_rt_ratio │ │ └── circuit_breaker_slow_rt_ratio_example.go ├── flow │ ├── memory_adaptive │ │ └── memory_adaptive_qps_example.go │ ├── qps │ │ ├── qps_limit_example.go │ │ ├── qps_limit_example_test.go │ │ └── sentinel.yml │ └── warm_up │ │ ├── qps_warm_up_example.go │ │ ├── qps_warm_up_example_test.go │ │ └── sentinel.yml ├── hotspot_param_flow │ ├── concurrency │ │ └── hotspot_params_concurrency_example.go │ ├── qps_reject │ │ └── hotspot_params_qps_reject_example.go │ └── qps_throttling │ │ └── hotspot_params_qps_throttling_example.go ├── isolation │ └── concurrency_limitation_example.go └── outlier │ ├── README.md │ ├── hello_kitex │ ├── client │ │ └── client.go │ ├── go.mod │ ├── go.sum │ ├── handler.go │ ├── main.go │ └── setup.sh │ ├── hello_kratos │ ├── client │ │ └── client.go │ ├── go.mod │ ├── go.sum │ ├── handler.go │ ├── main.go │ └── setup.sh │ └── hello_micro │ ├── client │ └── client.go │ ├── go.mod │ ├── go.sum │ ├── handler.go │ ├── main.go │ └── setup.sh ├── exporter └── metric │ ├── empty_exporter.go │ ├── exporter.go │ └── prometheus │ └── exporter.go ├── ext └── datasource │ ├── datasource.go │ ├── datasource_test.go │ ├── doc.go │ ├── error.go │ ├── file │ ├── refreshable_file.go │ └── refreshable_file_test.go │ ├── helper.go │ ├── helper_test.go │ ├── hotspot_rule_converter.go │ ├── hotspot_rule_converter_test.go │ ├── mock.go │ ├── property.go │ └── property_test.go ├── go.mod ├── go.sum ├── logging ├── logging.go └── logging_test.go ├── pkg ├── adapters │ ├── echo │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── middleware.go │ │ ├── middleware_example_test.go │ │ ├── middleware_test.go │ │ └── option.go │ ├── fiber │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── middleware.go │ │ ├── middleware_test.go │ │ ├── middlware_example_test.go │ │ └── option.go │ ├── gear │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── middleware.go │ │ ├── middleware_example.go │ │ ├── middleware_test.go │ │ └── option.go │ ├── gin │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── middleware.go │ │ ├── middleware_example_test.go │ │ ├── middleware_test.go │ │ └── option.go │ ├── go-zero │ │ ├── doc.go │ │ ├── example │ │ ├── global_middleware.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── middleware_test.go │ │ ├── option.go │ │ ├── routing_middleware.go │ │ └── test.yml │ ├── goframe │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── middleware.go │ │ ├── middleware_example_test.go │ │ ├── middleware_test.go │ │ └── option.go │ ├── grpc │ │ ├── client.go │ │ ├── client_test.go │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── options.go │ │ ├── server.go │ │ └── server_test.go │ ├── hertz │ │ ├── client.go │ │ ├── client_test.go │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── option.go │ │ ├── server.go │ │ └── server_test.go │ ├── iris │ │ ├── go.mod │ │ ├── go.sum │ │ ├── middleware.go │ │ ├── middleware_example_test.go │ │ ├── middleware_test.go │ │ └── option.go │ ├── kitex │ │ ├── client.go │ │ ├── client_test.go │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── options.go │ │ ├── server.go │ │ └── server_test.go │ ├── kratos │ │ ├── client.go │ │ ├── go.mod │ │ ├── go.sum │ │ └── options.go │ └── micro │ │ ├── client.go │ │ ├── client_test.go │ │ ├── doc.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── options.go │ │ ├── outlier_client.go │ │ ├── server.go │ │ ├── server_test.go │ │ └── test │ │ ├── test.pb.go │ │ ├── test.pb.micro.go │ │ └── test.proto └── datasource │ ├── apollo │ ├── apollo.go │ ├── apollo_test.go │ ├── go.mod │ ├── go.sum │ └── listener.go │ ├── consul │ ├── consul.go │ ├── consul_example_test.go │ ├── consul_test.go │ ├── demo │ │ └── datasource_consul_example.go │ ├── doc.go │ ├── go.mod │ ├── go.sum │ └── option.go │ ├── etcdv3 │ ├── demo │ │ └── datasource_etcdv3_example.go │ ├── doc.go │ ├── etcdv3.go │ ├── etcdv3_example.go │ ├── etcdv3_test.go │ ├── go.mod │ └── go.sum │ ├── k8s │ ├── .gitignore │ ├── Makefile │ ├── PROJECT │ ├── README.md │ ├── api │ │ └── v1alpha1 │ │ │ ├── circuitbreakerrules_types.go │ │ │ ├── flowrules_types.go │ │ │ ├── groupversion_info.go │ │ │ ├── hotspotrules_types.go │ │ │ ├── isolationrules_types.go │ │ │ ├── systemrules_types.go │ │ │ └── zz_generated.deepcopy.go │ ├── config │ │ ├── crd │ │ │ └── bases │ │ │ │ ├── datasource.sentinel.io_circuitbreakerrules.yaml │ │ │ │ ├── datasource.sentinel.io_flowrules.yaml │ │ │ │ ├── datasource.sentinel.io_hotspotrules.yaml │ │ │ │ ├── datasource.sentinel.io_isolationrules.yaml │ │ │ │ └── datasource.sentinel.io_systemrules.yaml │ │ └── samples │ │ │ ├── datasource_v1alpha1_circuitbreakerrules.yaml │ │ │ ├── datasource_v1alpha1_flowrules.yaml │ │ │ ├── datasource_v1alpha1_hotspotrules.yaml │ │ │ ├── datasource_v1alpha1_isolationrules.yaml │ │ │ └── datasource_v1alpha1_systemrules.yaml │ ├── controllers │ │ ├── circuitbreakerrules_controller.go │ │ ├── flowrules_controller.go │ │ ├── hotspotrules_controller.go │ │ ├── isolationrules_controller.go │ │ └── systemrules_controller.go │ ├── go.mod │ ├── go.sum │ ├── k8s.go │ ├── log_adapter.go │ └── log_adapter_test.go │ └── nacos │ ├── demo │ └── datasource_nacos_example.go │ ├── doc.go │ ├── go.mod │ ├── go.sum │ ├── nacos.go │ └── nacos_test.go ├── tests ├── api │ └── api_entry_integration_test.go ├── benchmark │ ├── circuitbreaker │ │ └── circuitbreaker_benchmark_test.go │ ├── entry │ │ ├── entry_benchmark_test.go │ │ ├── entry_comparison_benchmark_test.go │ │ └── entry_comparison_concurrency_benchmark_test.go │ ├── env_init.go │ ├── flow │ │ └── flow_benchmark_test.go │ ├── hotspot │ │ └── hotspot_benchmark_test.go │ ├── isolation │ │ └── isolation_slot_benchmark_test.go │ ├── load_rules_benchmark_test.go │ └── memory │ │ └── memory_test.go ├── core │ └── circuitbreaker │ │ └── circuitbreaker_slot_integration_test.go └── testdata │ ├── config │ ├── sentinel.yml │ └── sentinel.yml.2 │ ├── extension │ ├── SystemRule.json │ ├── SystemRule2.json │ ├── SystemRule3.json │ └── helper │ │ ├── CircuitBreakerRule.json │ │ ├── FlowRule.json │ │ ├── HotSpotParamFlowRule.json │ │ ├── IsolationRule.json │ │ └── SystemRule.json │ └── metric │ ├── app1-metrics.log.2020-02-14 │ ├── app1-metrics.log.2020-02-14.12 │ ├── app1-metrics.log.2020-02-14.32 │ ├── app1-metrics.log.2020-02-14.idx │ ├── app1-metrics.log.2020-02-15 │ ├── app1-metrics.log.2020-02-16 │ └── app1-metrics.log.2020-02-16.100 └── util ├── atomic.go ├── atomic_test.go ├── auto_recover.go ├── file.go ├── file_test.go ├── math.go ├── math_test.go ├── safe.go ├── string.go ├── string_test.go ├── time.go ├── time_test.go ├── time_ticker.go └── uuid.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "example/.*" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 16 | 17 | ## Issue Description 18 | 19 | Type: *bug report* 20 | 21 | ### Describe what happened 22 | 23 | 24 | ### Describe what you expected to happen 25 | 26 | 27 | ### How to reproduce it (as minimally and precisely as possible) 28 | 29 | 1. 30 | 2. 31 | 3. 32 | 33 | ### Tell us your environment 34 | 35 | 36 | ### Anything else we need to know? 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 16 | 17 | ## Issue Description 18 | 19 | Type: *feature request* 20 | 21 | ### Describe what feature you want 22 | 23 | ### Additional context 24 | 25 | Add any other context or screenshots about the feature request here. 26 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ### Describe what this PR does / why we need it 7 | 8 | 9 | ### Does this pull request fix one issue? 10 | 11 | 12 | 13 | ### Describe how you did it 14 | 15 | 16 | ### Describe how to verify it 17 | 18 | 19 | ### Special notes for reviews -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # JetBrains template 2 | .idea 3 | *.iml 4 | out/ 5 | 6 | # File-based project format 7 | *.iws 8 | 9 | # JIRA plugin 10 | atlassian-ide-plugin.xml 11 | 12 | # Editor-based Rest Client 13 | .idea/httpRequests 14 | 15 | ### Go template 16 | # Binaries for programs and plugins 17 | *.exe 18 | *.exe~ 19 | *.dll 20 | *.so 21 | *.dylib 22 | 23 | # Test binary, built with `go test -c` 24 | *.test 25 | 26 | # Output of the go coverage tool, specifically when used with LiteIDE 27 | *.out 28 | 29 | # Dependency directories (remove the comment below to include it) 30 | vendor/ 31 | 32 | ### macOS template 33 | .DS_Store 34 | .AppleDouble 35 | .LSOverride 36 | 37 | # Icon must end with two \r 38 | Icon 39 | 40 | # Thumbnails 41 | ._* 42 | 43 | # Files that might appear in the root of a volume 44 | .DocumentRevisions-V100 45 | .fseventsd 46 | .Spotlight-V100 47 | .TemporaryItems 48 | .Trashes 49 | .VolumeIcon.icns 50 | .com.apple.timemachine.donotpresent 51 | 52 | # Directories potentially created on remote AFP share 53 | .AppleDB 54 | .AppleDesktop 55 | Network Trash Folder 56 | Temporary Items 57 | .apdisk 58 | 59 | # VSCode 60 | .vscode 61 | 62 | # log 63 | *.log 64 | 65 | # coverage file 66 | coverage.html 67 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Options for analysis running. 2 | run: 3 | # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ 4 | skip-dirs-use-default: true 5 | skip-dirs: 6 | - example 7 | skip-files: 8 | - ".*\\.pb\\.go$" 9 | # output configuration options 10 | output: 11 | format: colored-line-number 12 | # Refer to https://golangci-lint.run/usage/linters 13 | linters-settings: 14 | govet: 15 | # Disable analyzers by name. 16 | # Run `go tool vet help` to see all analyzers. 17 | disable: 18 | - stdmethods 19 | linters: 20 | disable-all: true 21 | enable: 22 | - goimports 23 | - gofmt 24 | - misspell 25 | - govet 26 | - ineffassign 27 | - staticcheck 28 | issues: 29 | exclude-use-default: true -------------------------------------------------------------------------------- /api/slot_chain.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package api 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | "github.com/alibaba/sentinel-golang/core/circuitbreaker" 20 | "github.com/alibaba/sentinel-golang/core/flow" 21 | "github.com/alibaba/sentinel-golang/core/hotspot" 22 | "github.com/alibaba/sentinel-golang/core/isolation" 23 | "github.com/alibaba/sentinel-golang/core/log" 24 | "github.com/alibaba/sentinel-golang/core/stat" 25 | "github.com/alibaba/sentinel-golang/core/system" 26 | ) 27 | 28 | var globalSlotChain = BuildDefaultSlotChain() 29 | 30 | func GlobalSlotChain() *base.SlotChain { 31 | return globalSlotChain 32 | } 33 | 34 | func BuildDefaultSlotChain() *base.SlotChain { 35 | sc := base.NewSlotChain() 36 | sc.AddStatPrepareSlot(stat.DefaultResourceNodePrepareSlot) 37 | 38 | sc.AddRuleCheckSlot(system.DefaultAdaptiveSlot) 39 | sc.AddRuleCheckSlot(flow.DefaultSlot) 40 | sc.AddRuleCheckSlot(isolation.DefaultSlot) 41 | sc.AddRuleCheckSlot(hotspot.DefaultSlot) 42 | sc.AddRuleCheckSlot(circuitbreaker.DefaultSlot) 43 | 44 | sc.AddStatSlot(stat.DefaultSlot) 45 | sc.AddStatSlot(log.DefaultSlot) 46 | sc.AddStatSlot(flow.DefaultStandaloneStatSlot) 47 | sc.AddStatSlot(hotspot.DefaultConcurrencyStatSlot) 48 | sc.AddStatSlot(circuitbreaker.DefaultMetricStatSlot) 49 | return sc 50 | } 51 | -------------------------------------------------------------------------------- /api/tracer.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package api 16 | 17 | import ( 18 | "github.com/pkg/errors" 19 | 20 | "github.com/alibaba/sentinel-golang/core/base" 21 | "github.com/alibaba/sentinel-golang/logging" 22 | ) 23 | 24 | // TraceError records the provided error to the given SentinelEntry. 25 | func TraceError(entry *base.SentinelEntry, err error) { 26 | defer func() { 27 | if e := recover(); e != nil { 28 | logging.Error(errors.Errorf("%+v", e), "Failed to api.TraceError()") 29 | return 30 | } 31 | }() 32 | if entry == nil || err == nil { 33 | return 34 | } 35 | 36 | entry.SetError(err) 37 | } 38 | 39 | func TraceCallee(entry *base.SentinelEntry, address string) { 40 | defer func() { 41 | if e := recover(); e != nil { 42 | logging.Error(errors.Errorf("%+v", e), "Failed to api.TraceCallee()") 43 | return 44 | } 45 | }() 46 | if entry == nil || address == "" { 47 | return 48 | } 49 | entry.SetPair("address", address) 50 | } 51 | -------------------------------------------------------------------------------- /api/tracer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package api 16 | 17 | import ( 18 | "errors" 19 | "testing" 20 | "time" 21 | 22 | "github.com/alibaba/sentinel-golang/core/base" 23 | "github.com/alibaba/sentinel-golang/util" 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | var ( 28 | testRes = base.NewResourceWrapper("a", base.ResTypeCommon, base.Inbound) 29 | ) 30 | 31 | func TestTraceErrorToEntry(t *testing.T) { 32 | util.SetClock(util.NewMockClock()) 33 | 34 | type args struct { 35 | entry *base.SentinelEntry 36 | err error 37 | } 38 | te := errors.New("biz error") 39 | tests := []struct { 40 | name string 41 | args args 42 | want error 43 | }{ 44 | { 45 | name: "TestTraceErrorToEntry", 46 | args: args{ 47 | entry: nil, 48 | err: nil, 49 | }, 50 | want: te, 51 | }, 52 | } 53 | 54 | ctx := &base.EntryContext{ 55 | Resource: testRes, 56 | Input: nil, 57 | } 58 | tests[0].args.entry = base.NewSentinelEntry(ctx, testRes, nil) 59 | tests[0].args.err = te 60 | 61 | for _, tt := range tests { 62 | t.Run(tt.name, func(t *testing.T) { 63 | TraceError(tt.args.entry, tt.args.err) 64 | util.Sleep(time.Millisecond * 10) 65 | assert.Equal(t, tests[0].args.entry.Context().Err(), tt.want) 66 | }) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /core/base/block_error_test.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNewBlockError(t *testing.T) { 10 | type args struct { 11 | opts []BlockErrorOption 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want *BlockError 17 | }{ 18 | { 19 | name: "normal", 20 | args: struct{ opts []BlockErrorOption }{opts: []BlockErrorOption{ 21 | WithBlockType(BlockTypeFlow), 22 | WithBlockMsg("test"), 23 | WithRule(new(MockRule)), 24 | WithSnapshotValue("snapshot"), 25 | }}, 26 | want: NewBlockError(WithBlockType(BlockTypeFlow), 27 | WithBlockMsg("test"), 28 | WithRule(new(MockRule)), 29 | WithSnapshotValue("snapshot")), 30 | }, 31 | } 32 | for _, tt := range tests { 33 | t.Run(tt.name, func(t *testing.T) { 34 | got := NewBlockError(tt.args.opts...) 35 | assert.Equal(t, got.blockType, BlockTypeFlow) 36 | assert.Equal(t, got.blockMsg, "test") 37 | assert.Equal(t, got.rule, new(MockRule)) 38 | assert.Equal(t, got.snapshotValue, "snapshot") 39 | }) 40 | } 41 | } 42 | 43 | type MockRule struct { 44 | Id string `json:"id"` 45 | } 46 | 47 | func (m *MockRule) String() string { 48 | return "mock rule" 49 | } 50 | 51 | func (m *MockRule) ResourceName() string { 52 | return "mock resource" 53 | } 54 | -------------------------------------------------------------------------------- /core/base/constant.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package base 16 | 17 | // global variable 18 | const ( 19 | TotalInBoundResourceName = "__total_inbound_traffic__" 20 | 21 | DefaultMaxResourceAmount uint32 = 10000 22 | 23 | DefaultSampleCount uint32 = 2 24 | DefaultIntervalMs uint32 = 1000 25 | 26 | // default 10*1000/500 = 20 27 | DefaultSampleCountTotal uint32 = 20 28 | // default 10s (total length) 29 | DefaultIntervalMsTotal uint32 = 10000 30 | 31 | DefaultStatisticMaxRt = int64(60000) 32 | ) 33 | -------------------------------------------------------------------------------- /core/base/context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package base 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestEntryContext_IsBlocked(t *testing.T) { 24 | ctx := NewEmptyEntryContext() 25 | assert.False(t, ctx.IsBlocked(), "empty context with no result should indicate pass") 26 | ctx.RuleCheckResult = NewTokenResultBlocked(BlockTypeUnknown) 27 | assert.True(t, ctx.IsBlocked(), "context with blocked request should indicate blocked") 28 | } 29 | -------------------------------------------------------------------------------- /core/base/entry_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package base 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | var flag = 0 24 | 25 | func exitHandlerMock(entry *SentinelEntry, ctx *EntryContext) error { 26 | flag += 1 27 | return nil 28 | } 29 | 30 | func TestSentinelEntry_WhenExit(t *testing.T) { 31 | flag = 0 32 | sc := NewSlotChain() 33 | ctx := sc.GetPooledContext() 34 | entry := NewSentinelEntry(ctx, nil, sc) 35 | entry.WhenExit(exitHandlerMock) 36 | entry.Exit() 37 | assert.True(t, flag == 1) 38 | 39 | entry.Exit() 40 | assert.True(t, flag == 1) 41 | } 42 | -------------------------------------------------------------------------------- /core/base/metric_item_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package base 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestMetricItemFromFatStringLegal(t *testing.T) { 24 | line1 := "1564382218000|2019-07-29 14:36:58|/foo/*|4|9|3|0|25|0|2|1" 25 | item1, err := MetricItemFromFatString(line1) 26 | assert.NoError(t, err) 27 | assert.Equal(t, uint64(1564382218000), item1.Timestamp) 28 | assert.Equal(t, uint64(4), item1.PassQps) 29 | assert.Equal(t, uint64(9), item1.BlockQps) 30 | assert.Equal(t, uint64(3), item1.CompleteQps) 31 | assert.Equal(t, uint64(0), item1.ErrorQps) 32 | assert.Equal(t, uint64(25), item1.AvgRt) 33 | assert.Equal(t, "/foo/*", item1.Resource) 34 | assert.Equal(t, int32(1), item1.Classification) 35 | } 36 | 37 | func TestMetricItemFromFatStringIllegal(t *testing.T) { 38 | line1 := "1564382218000|2019-07-29 14:36:58|foo|baz|4|9|3|0|25|0|2|1" 39 | _, err := MetricItemFromFatString(line1) 40 | assert.Error(t, err, "Error should occur when parsing malformed line") 41 | 42 | line2 := "1564382218000|2019-07-29 14:36:58|foo|-3|9|3|0|25|0|2|1" 43 | _, err = MetricItemFromFatString(line2) 44 | assert.Error(t, err, "Error should occur when parsing malformed line") 45 | } 46 | -------------------------------------------------------------------------------- /core/base/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package base 16 | 17 | import "fmt" 18 | 19 | // ResourceType represents classification of the resources 20 | type ResourceType int32 21 | 22 | const ( 23 | ResTypeCommon ResourceType = iota 24 | ResTypeWeb 25 | ResTypeRPC 26 | ResTypeAPIGateway 27 | ResTypeDBSQL 28 | ResTypeCache 29 | ResTypeMQ 30 | ) 31 | 32 | // TrafficType describes the traffic type: Inbound or Outbound 33 | type TrafficType int32 34 | 35 | const ( 36 | // Inbound represents the inbound traffic (e.g. provider) 37 | Inbound TrafficType = iota 38 | // Outbound represents the outbound traffic (e.g. consumer) 39 | Outbound 40 | ) 41 | 42 | func (t TrafficType) String() string { 43 | switch t { 44 | case Inbound: 45 | return "Inbound" 46 | case Outbound: 47 | return "Outbound" 48 | default: 49 | return fmt.Sprintf("%d", t) 50 | } 51 | } 52 | 53 | // ResourceWrapper represents the invocation 54 | type ResourceWrapper struct { 55 | // global unique resource name 56 | name string 57 | // resource classification 58 | classification ResourceType 59 | // Inbound or Outbound 60 | flowType TrafficType 61 | } 62 | 63 | func (r *ResourceWrapper) String() string { 64 | return fmt.Sprintf("ResourceWrapper{name=%s, flowType=%s, classification=%d}", r.name, r.flowType, r.classification) 65 | } 66 | 67 | func (r *ResourceWrapper) Name() string { 68 | return r.name 69 | } 70 | 71 | func (r *ResourceWrapper) Classification() ResourceType { 72 | return r.classification 73 | } 74 | 75 | func (r *ResourceWrapper) FlowType() TrafficType { 76 | return r.flowType 77 | } 78 | 79 | func NewResourceWrapper(name string, classification ResourceType, flowType TrafficType) *ResourceWrapper { 80 | return &ResourceWrapper{name: name, classification: classification, flowType: flowType} 81 | } 82 | -------------------------------------------------------------------------------- /core/base/rule.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package base 16 | 17 | import "fmt" 18 | 19 | type SentinelRule interface { 20 | fmt.Stringer 21 | 22 | ResourceName() string 23 | } 24 | -------------------------------------------------------------------------------- /core/circuitbreaker/slot.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package circuitbreaker 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | ) 20 | 21 | const ( 22 | RuleCheckSlotOrder = 5000 23 | ) 24 | 25 | var ( 26 | DefaultSlot = &Slot{} 27 | ) 28 | 29 | type Slot struct { 30 | } 31 | 32 | func (s *Slot) Order() uint32 { 33 | return RuleCheckSlotOrder 34 | } 35 | 36 | func (b *Slot) Check(ctx *base.EntryContext) *base.TokenResult { 37 | resource := ctx.Resource.Name() 38 | result := ctx.RuleCheckResult 39 | if len(resource) == 0 { 40 | return result 41 | } 42 | if passed, rule := checkPass(ctx); !passed { 43 | msg := "circuit breaker check blocked" 44 | if result == nil { 45 | result = base.NewTokenResultBlockedWithCause(base.BlockTypeCircuitBreaking, msg, rule, nil) 46 | } else { 47 | result.ResetToBlockedWithCause(base.BlockTypeCircuitBreaking, msg, rule, nil) 48 | } 49 | } 50 | return result 51 | } 52 | 53 | func checkPass(ctx *base.EntryContext) (bool, *Rule) { 54 | breakers := getBreakersOfResource(ctx.Resource.Name()) 55 | for _, breaker := range breakers { 56 | passed := breaker.TryPass(ctx) 57 | if !passed { 58 | return false, breaker.BoundRule() 59 | } 60 | } 61 | return true, nil 62 | } 63 | -------------------------------------------------------------------------------- /core/circuitbreaker/stat_slot.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package circuitbreaker 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | ) 20 | 21 | const ( 22 | StatSlotOrder = 5000 23 | ) 24 | 25 | var ( 26 | DefaultMetricStatSlot = &MetricStatSlot{} 27 | ) 28 | 29 | // MetricStatSlot records metrics for circuit breaker on invocation completed. 30 | // MetricStatSlot must be filled into slot chain if circuit breaker is alive. 31 | type MetricStatSlot struct { 32 | } 33 | 34 | func (s *MetricStatSlot) Order() uint32 { 35 | return StatSlotOrder 36 | } 37 | 38 | func (c *MetricStatSlot) OnEntryPassed(_ *base.EntryContext) { 39 | // Do nothing 40 | return 41 | } 42 | 43 | func (c *MetricStatSlot) OnEntryBlocked(_ *base.EntryContext, _ *base.BlockError) { 44 | // Do nothing 45 | return 46 | } 47 | 48 | func (c *MetricStatSlot) OnCompleted(ctx *base.EntryContext) { 49 | res := ctx.Resource.Name() 50 | err := ctx.Err() 51 | rt := ctx.Rt() 52 | for _, cb := range getBreakersOfResource(res) { 53 | cb.OnRequestComplete(rt, err) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/config/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | ) 21 | 22 | const testDataBaseDir = "../../tests/testdata/config/" 23 | 24 | func TestLoadFromYamlFile(t *testing.T) { 25 | type args struct { 26 | filePath string 27 | } 28 | tests := []struct { 29 | name string 30 | args args 31 | wantErr bool 32 | }{ 33 | { 34 | name: "TestLoadFromYamlFile", 35 | args: args{ 36 | filePath: testDataBaseDir + "sentinel.yml", 37 | }, 38 | wantErr: false, 39 | }, 40 | { 41 | name: "TestLoadFromYamlFile", 42 | args: args{ 43 | filePath: testDataBaseDir + "sentinel.yml.1", 44 | }, 45 | wantErr: true, 46 | }, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | if err := loadGlobalConfigFromYamlFile(tt.args.filePath); (err != nil) != tt.wantErr { 51 | t.Errorf("loadGlobalConfigFromYamlFile() error = %v, wantErr %v", err, tt.wantErr) 52 | } 53 | }) 54 | } 55 | } 56 | 57 | func TestOverrideFromSystemEnv(t *testing.T) { 58 | tests := []struct { 59 | name string 60 | wantErr bool 61 | }{ 62 | { 63 | name: "TestOverrideFromSystemEnv", 64 | wantErr: false, 65 | }, 66 | } 67 | err := loadGlobalConfigFromYamlFile(testDataBaseDir + "sentinel.yml") 68 | if err != nil { 69 | t.Errorf("Fail to initialize data.") 70 | } 71 | _ = os.Setenv(AppNameEnvKey, "app-name") 72 | _ = os.Setenv(AppTypeEnvKey, "1") 73 | _ = os.Setenv(LogDirEnvKey, testDataBaseDir+"sentinel.yml.2") 74 | _ = os.Setenv(LogNamePidEnvKey, "true") 75 | 76 | for _, tt := range tests { 77 | t.Run(tt.name, func(t *testing.T) { 78 | if err := overrideItemsFromSystemEnv(); (err != nil) != tt.wantErr { 79 | t.Errorf("overrideItemsFromSystemEnv() error = %v, wantErr %v", err, tt.wantErr) 80 | } 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /core/config/constant.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | const ( 18 | // UnknownProjectName represents the "default" value 19 | // that indicates the project name is absent. 20 | UnknownProjectName = "unknown_go_service" 21 | 22 | ConfFilePathEnvKey = "SENTINEL_CONFIG_FILE_PATH" 23 | AppNameEnvKey = "SENTINEL_APP_NAME" 24 | AppTypeEnvKey = "SENTINEL_APP_TYPE" 25 | LogDirEnvKey = "SENTINEL_LOG_DIR" 26 | LogNamePidEnvKey = "SENTINEL_LOG_USE_PID" 27 | 28 | DefaultConfigFilename = "sentinel.yml" 29 | DefaultAppType int32 = 0 30 | 31 | DefaultMetricLogFlushIntervalSec uint32 = 1 32 | DefaultMetricLogSingleFileMaxSize uint64 = 1024 * 1024 * 50 33 | DefaultMetricLogMaxFileAmount uint32 = 8 34 | DefaultSystemStatCollectIntervalMs uint32 = 1000 35 | DefaultLoadStatCollectIntervalMs uint32 = 1000 36 | DefaultCpuStatCollectIntervalMs uint32 = 1000 37 | DefaultMemoryStatCollectIntervalMs uint32 = 150 38 | DefaultWarmUpColdFactor uint32 = 3 39 | ) 40 | -------------------------------------------------------------------------------- /core/config/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package config provides general configuration mechanism. 16 | package config 17 | -------------------------------------------------------------------------------- /core/flow/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package flow implements the flow shaping control. 16 | // 17 | // flow module is based on QPS statistic metric 18 | // 19 | // The TrafficShapingController consists of two part: TrafficShapingCalculator and TrafficShapingChecker 20 | // 21 | // 1. TrafficShapingCalculator calculates the actual traffic shaping token threshold. Currently, Sentinel supports two token calculate strategy: Direct and WarmUp. 22 | // 2. TrafficShapingChecker performs checking logic according to current metrics and the traffic shaping strategy, then yield the token result. Currently, Sentinel supports two control behavior: Reject and Throttling. 23 | // 24 | // Besides, Sentinel supports customized TrafficShapingCalculator and TrafficShapingChecker. User could call function SetTrafficShapingGenerator to register customized TrafficShapingController and call function RemoveTrafficShapingGenerator to unregister TrafficShapingController. 25 | // There are a few notes users need to be aware of: 26 | // 27 | // 1. The function both SetTrafficShapingGenerator and RemoveTrafficShapingGenerator is not thread safe. 28 | // 2. Users can not override the Sentinel supported TrafficShapingController. 29 | package flow 30 | -------------------------------------------------------------------------------- /core/flow/slot_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package flow 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/alibaba/sentinel-golang/core/base" 21 | "github.com/alibaba/sentinel-golang/core/stat" 22 | "github.com/alibaba/sentinel-golang/logging" 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func Test_FlowSlot_StandaloneStat(t *testing.T) { 27 | slot := &Slot{} 28 | statSLot := &StandaloneStatSlot{} 29 | res := base.NewResourceWrapper("abc", base.ResTypeCommon, base.Inbound) 30 | resNode := stat.GetOrCreateResourceNode("abc", base.ResTypeCommon) 31 | ctx := &base.EntryContext{ 32 | Resource: res, 33 | StatNode: resNode, 34 | Input: &base.SentinelInput{ 35 | BatchCount: 1, 36 | }, 37 | RuleCheckResult: nil, 38 | Data: nil, 39 | } 40 | 41 | slot.Check(ctx) 42 | 43 | r1 := &Rule{ 44 | Resource: "abc", 45 | TokenCalculateStrategy: Direct, 46 | ControlBehavior: Reject, 47 | // Use standalone statistic, using single-bucket-sliding-windows 48 | StatIntervalInMs: 20000, 49 | Threshold: 100, 50 | RelationStrategy: CurrentResource, 51 | } 52 | _, e := LoadRules([]*Rule{r1}) 53 | if e != nil { 54 | logging.Error(e, "") 55 | t.Fail() 56 | return 57 | } 58 | 59 | for i := 0; i < 50; i++ { 60 | ret := slot.Check(ctx) 61 | if ret != nil { 62 | t.Fail() 63 | return 64 | } 65 | statSLot.OnEntryPassed(ctx) 66 | } 67 | assert.True(t, getTrafficControllerListFor("abc")[0].boundStat.readOnlyMetric.GetSum(base.MetricEventPass) == 50) 68 | } 69 | -------------------------------------------------------------------------------- /core/flow/standalone_stat_slot.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package flow 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | "github.com/alibaba/sentinel-golang/logging" 20 | "github.com/pkg/errors" 21 | ) 22 | 23 | const ( 24 | StatSlotOrder = 3000 25 | ) 26 | 27 | var ( 28 | DefaultStandaloneStatSlot = &StandaloneStatSlot{} 29 | ) 30 | 31 | type StandaloneStatSlot struct { 32 | } 33 | 34 | func (s *StandaloneStatSlot) Order() uint32 { 35 | return StatSlotOrder 36 | } 37 | 38 | func (s StandaloneStatSlot) OnEntryPassed(ctx *base.EntryContext) { 39 | res := ctx.Resource.Name() 40 | for _, tc := range getTrafficControllerListFor(res) { 41 | if !tc.boundStat.reuseResourceStat { 42 | if tc.boundStat.writeOnlyMetric != nil { 43 | tc.boundStat.writeOnlyMetric.AddCount(base.MetricEventPass, int64(ctx.Input.BatchCount)) 44 | } else { 45 | logging.Error(errors.New("nil independent write statistic"), "Nil statistic for traffic control in StandaloneStatSlot.OnEntryPassed()", "rule", tc.rule) 46 | } 47 | } 48 | } 49 | } 50 | 51 | func (s StandaloneStatSlot) OnEntryBlocked(ctx *base.EntryContext, blockError *base.BlockError) { 52 | // Do nothing 53 | } 54 | 55 | func (s StandaloneStatSlot) OnCompleted(ctx *base.EntryContext) { 56 | // Do nothing 57 | } 58 | -------------------------------------------------------------------------------- /core/flow/tc_adaptive.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | import ( 4 | "github.com/alibaba/sentinel-golang/core/system_metric" 5 | "github.com/alibaba/sentinel-golang/logging" 6 | ) 7 | 8 | // MemoryAdaptiveTrafficShapingCalculator is a memory adaptive traffic shaping calculator 9 | // 10 | // adaptive flow control algorithm 11 | // If the watermark is less than Rule.MemLowWaterMarkBytes, the threshold is Rule.LowMemUsageThreshold. 12 | // If the watermark is greater than Rule.MemHighWaterMarkBytes, the threshold is Rule.HighMemUsageThreshold. 13 | // Otherwise, the threshold is ((watermark - MemLowWaterMarkBytes)/(MemHighWaterMarkBytes - MemLowWaterMarkBytes)) * 14 | // 15 | // (HighMemUsageThreshold - LowMemUsageThreshold) + LowMemUsageThreshold. 16 | type MemoryAdaptiveTrafficShapingCalculator struct { 17 | owner *TrafficShapingController 18 | lowMemUsageThreshold int64 19 | highMemUsageThreshold int64 20 | memLowWaterMark int64 21 | memHighWaterMark int64 22 | } 23 | 24 | func NewMemoryAdaptiveTrafficShapingCalculator(owner *TrafficShapingController, r *Rule) *MemoryAdaptiveTrafficShapingCalculator { 25 | return &MemoryAdaptiveTrafficShapingCalculator{ 26 | owner: owner, 27 | lowMemUsageThreshold: r.LowMemUsageThreshold, 28 | highMemUsageThreshold: r.HighMemUsageThreshold, 29 | memLowWaterMark: r.MemLowWaterMarkBytes, 30 | memHighWaterMark: r.MemHighWaterMarkBytes, 31 | } 32 | } 33 | 34 | func (m *MemoryAdaptiveTrafficShapingCalculator) BoundOwner() *TrafficShapingController { 35 | return m.owner 36 | } 37 | 38 | func (m *MemoryAdaptiveTrafficShapingCalculator) CalculateAllowedTokens(_ uint32, _ int32) float64 { 39 | var threshold float64 40 | mem := system_metric.CurrentMemoryUsage() 41 | if mem == system_metric.NotRetrievedMemoryValue { 42 | logging.Warn("[MemoryAdaptiveTrafficShapingCalculator CalculateAllowedTokens]Fail to load memory usage") 43 | return float64(m.lowMemUsageThreshold) 44 | } 45 | if mem <= m.memLowWaterMark { 46 | threshold = float64(m.lowMemUsageThreshold) 47 | } else if mem >= m.memHighWaterMark { 48 | threshold = float64(m.highMemUsageThreshold) 49 | } else { 50 | threshold = (float64(m.highMemUsageThreshold-m.lowMemUsageThreshold)/float64(m.memHighWaterMark-m.memLowWaterMark))*float64(mem-m.memLowWaterMark) + float64(m.lowMemUsageThreshold) 51 | } 52 | return threshold 53 | } 54 | -------------------------------------------------------------------------------- /core/flow/tc_adaptive_test.go: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/alibaba/sentinel-golang/core/system_metric" 7 | "github.com/alibaba/sentinel-golang/util" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestMemoryAdaptiveTrafficShapingCalculator_CalculateAllowedTokens(t *testing.T) { 12 | tc1 := &MemoryAdaptiveTrafficShapingCalculator{ 13 | owner: nil, 14 | lowMemUsageThreshold: 1000, 15 | highMemUsageThreshold: 100, 16 | memLowWaterMark: 1024, 17 | memHighWaterMark: 2048, 18 | } 19 | system_metric.SetSystemMemoryUsage(100) 20 | assert.True(t, util.Float64Equals(tc1.CalculateAllowedTokens(0, 0), float64(tc1.lowMemUsageThreshold))) 21 | system_metric.SetSystemMemoryUsage(1024) 22 | assert.True(t, util.Float64Equals(tc1.CalculateAllowedTokens(0, 0), float64(tc1.lowMemUsageThreshold))) 23 | system_metric.SetSystemMemoryUsage(1536) 24 | assert.True(t, util.Float64Equals(tc1.CalculateAllowedTokens(0, 0), 550)) 25 | system_metric.SetSystemMemoryUsage(2048) 26 | assert.True(t, util.Float64Equals(tc1.CalculateAllowedTokens(0, 0), 100)) 27 | system_metric.SetSystemMemoryUsage(3072) 28 | assert.True(t, util.Float64Equals(tc1.CalculateAllowedTokens(0, 0), 100)) 29 | } 30 | -------------------------------------------------------------------------------- /core/flow/tc_default.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package flow 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | ) 20 | 21 | type DirectTrafficShapingCalculator struct { 22 | owner *TrafficShapingController 23 | threshold float64 24 | } 25 | 26 | func NewDirectTrafficShapingCalculator(owner *TrafficShapingController, threshold float64) *DirectTrafficShapingCalculator { 27 | return &DirectTrafficShapingCalculator{ 28 | owner: owner, 29 | threshold: threshold, 30 | } 31 | } 32 | 33 | func (d *DirectTrafficShapingCalculator) CalculateAllowedTokens(uint32, int32) float64 { 34 | return d.threshold 35 | } 36 | 37 | func (d *DirectTrafficShapingCalculator) BoundOwner() *TrafficShapingController { 38 | return d.owner 39 | } 40 | 41 | type RejectTrafficShapingChecker struct { 42 | owner *TrafficShapingController 43 | rule *Rule 44 | } 45 | 46 | func NewRejectTrafficShapingChecker(owner *TrafficShapingController, rule *Rule) *RejectTrafficShapingChecker { 47 | return &RejectTrafficShapingChecker{ 48 | owner: owner, 49 | rule: rule, 50 | } 51 | } 52 | 53 | func (d *RejectTrafficShapingChecker) BoundOwner() *TrafficShapingController { 54 | return d.owner 55 | } 56 | 57 | func (d *RejectTrafficShapingChecker) DoCheck(resStat base.StatNode, batchCount uint32, threshold float64) *base.TokenResult { 58 | metricReadonlyStat := d.BoundOwner().boundStat.readOnlyMetric 59 | if metricReadonlyStat == nil { 60 | return nil 61 | } 62 | curCount := float64(metricReadonlyStat.GetSum(base.MetricEventPass)) 63 | if curCount+float64(batchCount) > threshold { 64 | msg := "flow reject check blocked" 65 | return base.NewTokenResultBlockedWithCause(base.BlockTypeFlow, msg, d.rule, curCount) 66 | } 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /core/hotspot/cache/concurrent_cache.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cache 16 | 17 | // ConcurrentCounterCache cache the hotspot parameter 18 | type ConcurrentCounterCache interface { 19 | // Add add a value to the cache, 20 | // Updates the "recently used"-ness of the key. 21 | Add(key interface{}, value *int64) 22 | 23 | // If the key is not existed in the cache, adds a value to the cache then return nil. And updates the "recently used"-ness of the key 24 | // If the key is already existed in the cache, do nothing and return the prior value 25 | AddIfAbsent(key interface{}, value *int64) (priorValue *int64) 26 | 27 | // Get returns key's value from the cache and updates the "recently used"-ness of the key. 28 | Get(key interface{}) (value *int64, isFound bool) 29 | 30 | // Remove removes a key from the cache. 31 | // Return true if the key was contained. 32 | Remove(key interface{}) (isFound bool) 33 | 34 | // Contains checks if a key exists in cache 35 | // Without updating the recent-ness. 36 | Contains(key interface{}) (ok bool) 37 | 38 | // Keys returns a slice of the keys in the cache, from oldest to newest. 39 | Keys() []interface{} 40 | 41 | // Len returns the number of items in the cache. 42 | Len() int 43 | 44 | // Purge clears all cache entries. 45 | Purge() 46 | } 47 | -------------------------------------------------------------------------------- /core/hotspot/cache/concurrent_lru_benchmark_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cache 16 | 17 | import ( 18 | "strconv" 19 | "testing" 20 | ) 21 | 22 | const CacheSize = 50000 23 | 24 | func Benchmark_LRU_AddIfAbsent(b *testing.B) { 25 | c := NewLRUCacheMap(CacheSize) 26 | for a := 1; a <= CacheSize; a++ { 27 | val := new(int64) 28 | *val = int64(a) 29 | c.Add(strconv.Itoa(a), val) 30 | } 31 | b.ResetTimer() 32 | for i := 0; i < b.N; i++ { 33 | for j := 1000; j <= 1001; j++ { 34 | newVal := new(int64) 35 | *newVal = int64(j) 36 | prior := c.AddIfAbsent(strconv.Itoa(j), newVal) 37 | if *prior != int64(j) { 38 | b.Fatal("error!") 39 | } 40 | } 41 | } 42 | } 43 | 44 | func Benchmark_LRU_Add(b *testing.B) { 45 | c := NewLRUCacheMap(CacheSize) 46 | for a := 1; a <= CacheSize; a++ { 47 | val := new(int64) 48 | *val = int64(a) 49 | c.Add(strconv.Itoa(a), val) 50 | } 51 | b.ResetTimer() 52 | for i := 0; i < b.N; i++ { 53 | for j := CacheSize - 100; j <= CacheSize-99; j++ { 54 | newVal := new(int64) 55 | *newVal = int64(j) 56 | c.Add(strconv.Itoa(j), newVal) 57 | } 58 | } 59 | } 60 | 61 | func Benchmark_LRU_Get(b *testing.B) { 62 | c := NewLRUCacheMap(CacheSize) 63 | for a := 1; a <= CacheSize; a++ { 64 | val := new(int64) 65 | *val = int64(a) 66 | c.Add(strconv.Itoa(a), val) 67 | } 68 | b.ResetTimer() 69 | for i := 0; i < b.N; i++ { 70 | for j := CacheSize - 100; j <= CacheSize-99; j++ { 71 | val, found := c.Get(strconv.Itoa(j)) 72 | if *val != int64(j) || !found { 73 | b.Fatal("error") 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /core/hotspot/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package hotspot provides implementation of "hot-spot" (frequent) parameter flow control. 16 | package hotspot 17 | -------------------------------------------------------------------------------- /core/hotspot/params_metric.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package hotspot 16 | 17 | import "github.com/alibaba/sentinel-golang/core/hotspot/cache" 18 | 19 | const ( 20 | ConcurrencyMaxCount = 4000 21 | ParamsCapacityBase = 4000 22 | ParamsMaxCapacity = 20000 23 | ) 24 | 25 | // ParamsMetric carries real-time counters for frequent ("hot spot") parameters. 26 | // 27 | // For each cache map, the key is the parameter value, while the value is the counter. 28 | type ParamsMetric struct { 29 | // RuleTimeCounter records the last added token timestamp. 30 | RuleTimeCounter cache.ConcurrentCounterCache 31 | // RuleTokenCounter records the number of tokens. 32 | RuleTokenCounter cache.ConcurrentCounterCache 33 | // ConcurrencyCounter records the real-time concurrency. 34 | ConcurrencyCounter cache.ConcurrentCounterCache 35 | } 36 | -------------------------------------------------------------------------------- /core/hotspot/slot.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package hotspot 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | "github.com/alibaba/sentinel-golang/util" 20 | ) 21 | 22 | const ( 23 | RuleCheckSlotOrder = 4000 24 | ) 25 | 26 | var ( 27 | DefaultSlot = &Slot{} 28 | ) 29 | 30 | type Slot struct { 31 | } 32 | 33 | func (s *Slot) Order() uint32 { 34 | return RuleCheckSlotOrder 35 | } 36 | 37 | func (s *Slot) Check(ctx *base.EntryContext) *base.TokenResult { 38 | res := ctx.Resource.Name() 39 | batch := int64(ctx.Input.BatchCount) 40 | 41 | result := ctx.RuleCheckResult 42 | tcs := getTrafficControllersFor(res) 43 | for _, tc := range tcs { 44 | arg := tc.ExtractArgs(ctx) 45 | if arg == nil { 46 | continue 47 | } 48 | r := canPassCheck(tc, arg, batch) 49 | if r == nil { 50 | continue 51 | } 52 | if r.Status() == base.ResultStatusBlocked { 53 | return r 54 | } 55 | if r.Status() == base.ResultStatusShouldWait { 56 | if nanosToWait := r.NanosToWait(); nanosToWait > 0 { 57 | // Handle waiting action. 58 | util.Sleep(nanosToWait) 59 | } 60 | continue 61 | } 62 | } 63 | return result 64 | } 65 | 66 | func canPassCheck(tc TrafficShapingController, arg interface{}, batch int64) *base.TokenResult { 67 | return canPassLocalCheck(tc, arg, batch) 68 | } 69 | 70 | func canPassLocalCheck(tc TrafficShapingController, arg interface{}, batch int64) *base.TokenResult { 71 | return tc.PerformChecking(arg, batch) 72 | } 73 | -------------------------------------------------------------------------------- /core/hotspot/slot_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package hotspot 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | "github.com/stretchr/testify/mock" 20 | ) 21 | 22 | type TrafficShapingControllerMock struct { 23 | mock.Mock 24 | } 25 | 26 | func (m *TrafficShapingControllerMock) PerformChecking(arg interface{}, batchCount int64) *base.TokenResult { 27 | retArgs := m.Called(arg, batchCount) 28 | return retArgs.Get(0).(*base.TokenResult) 29 | } 30 | 31 | func (m *TrafficShapingControllerMock) BoundParamIndex() int { 32 | retArgs := m.Called() 33 | return retArgs.Int(0) 34 | } 35 | 36 | func (m *TrafficShapingControllerMock) BoundMetric() *ParamsMetric { 37 | retArgs := m.Called() 38 | return retArgs.Get(0).(*ParamsMetric) 39 | } 40 | 41 | func (m *TrafficShapingControllerMock) BoundRule() *Rule { 42 | retArgs := m.Called() 43 | return retArgs.Get(0).(*Rule) 44 | } 45 | 46 | func (m *TrafficShapingControllerMock) Replace(r *Rule) { 47 | _ = m.Called(r) 48 | return 49 | } 50 | 51 | func (m *TrafficShapingControllerMock) ExtractArgs(ctx *base.EntryContext) []interface{} { 52 | _ = m.Called() 53 | ret := []interface{}{ctx.Input.Args[m.BoundParamIndex()]} 54 | return ret 55 | } 56 | -------------------------------------------------------------------------------- /core/isolation/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package isolation provides implementation of concurrency limiting (semaphore isolation). 16 | package isolation 17 | -------------------------------------------------------------------------------- /core/isolation/rule.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package isolation 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | ) 21 | 22 | // MetricType represents the target metric type. 23 | type MetricType int32 24 | 25 | const ( 26 | // Concurrency represents concurrency (in-flight requests). 27 | Concurrency MetricType = iota 28 | ) 29 | 30 | func (s MetricType) String() string { 31 | switch s { 32 | case Concurrency: 33 | return "Concurrency" 34 | default: 35 | return "Undefined" 36 | } 37 | } 38 | 39 | // Rule describes the isolation policy (e.g. semaphore isolation). 40 | type Rule struct { 41 | // ID represents the unique ID of the rule (optional). 42 | ID string `json:"id,omitempty"` 43 | // Resource represents the target resource definition. 44 | Resource string `json:"resource"` 45 | // MetricType indicates the metric type for checking logic. 46 | // Currently Concurrency is supported for concurrency limiting. 47 | MetricType MetricType `json:"metricType"` 48 | Threshold uint32 `json:"threshold"` 49 | } 50 | 51 | func (r *Rule) String() string { 52 | b, err := json.Marshal(r) 53 | if err != nil { 54 | // Return the fallback string 55 | return fmt.Sprintf("{Id=%s, Resource=%s, MetricType=%s, Threshold=%d}", r.ID, r.Resource, r.MetricType.String(), r.Threshold) 56 | } 57 | return string(b) 58 | } 59 | 60 | func (r *Rule) ResourceName() string { 61 | return r.Resource 62 | } 63 | -------------------------------------------------------------------------------- /core/isolation/slot.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package isolation 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | "github.com/alibaba/sentinel-golang/logging" 20 | "github.com/pkg/errors" 21 | ) 22 | 23 | const ( 24 | RuleCheckSlotOrder = 3000 25 | ) 26 | 27 | var ( 28 | DefaultSlot = &Slot{} 29 | ) 30 | 31 | type Slot struct { 32 | } 33 | 34 | func (s *Slot) Order() uint32 { 35 | return RuleCheckSlotOrder 36 | } 37 | 38 | func (s *Slot) Check(ctx *base.EntryContext) *base.TokenResult { 39 | resource := ctx.Resource.Name() 40 | result := ctx.RuleCheckResult 41 | if len(resource) == 0 { 42 | return result 43 | } 44 | if passed, rule, snapshot := checkPass(ctx); !passed { 45 | msg := "concurrency exceeds threshold" 46 | if result == nil { 47 | result = base.NewTokenResultBlockedWithCause(base.BlockTypeIsolation, msg, rule, snapshot) 48 | } else { 49 | result.ResetToBlockedWithCause(base.BlockTypeIsolation, msg, rule, snapshot) 50 | } 51 | } 52 | return result 53 | } 54 | 55 | func checkPass(ctx *base.EntryContext) (bool, *Rule, uint32) { 56 | statNode := ctx.StatNode 57 | batchCount := ctx.Input.BatchCount 58 | curCount := uint32(0) 59 | for _, rule := range getRulesOfResource(ctx.Resource.Name()) { 60 | threshold := rule.Threshold 61 | if rule.MetricType == Concurrency { 62 | if cur := statNode.CurrentConcurrency(); cur >= 0 { 63 | curCount = uint32(cur) 64 | } else { 65 | curCount = 0 66 | logging.Error(errors.New("negative concurrency"), "Negative concurrency in isolation.checkPass()", "rule", rule) 67 | } 68 | if curCount+batchCount > threshold { 69 | return false, rule, curCount 70 | } 71 | } 72 | } 73 | return true, nil, curCount 74 | } 75 | -------------------------------------------------------------------------------- /core/log/slot.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package log 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | ) 20 | 21 | const ( 22 | StatSlotOrder = 2000 23 | ) 24 | 25 | var ( 26 | DefaultSlot = &Slot{} 27 | ) 28 | 29 | type Slot struct { 30 | } 31 | 32 | func (s *Slot) Order() uint32 { 33 | return StatSlotOrder 34 | } 35 | 36 | func (s *Slot) OnEntryPassed(_ *base.EntryContext) { 37 | 38 | } 39 | 40 | func (s *Slot) OnEntryBlocked(ctx *base.EntryContext, blockError *base.BlockError) { 41 | // TODO: write sentinel-block.log here 42 | } 43 | 44 | func (s *Slot) OnCompleted(_ *base.EntryContext) { 45 | 46 | } 47 | -------------------------------------------------------------------------------- /core/outlier/rule.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package outlier 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/circuitbreaker" 19 | ) 20 | 21 | type RecoveryCheckFunc func(address string) bool 22 | 23 | // Rule encompasses the fields of outlier ejection rule. 24 | type Rule struct { 25 | *circuitbreaker.Rule 26 | 27 | // Whether to enable active detection mode for recovery. 28 | // Enabling active detection mode will disable passive detection. 29 | EnableActiveRecovery bool 30 | 31 | // An upper limit on the percentage of nodes to be excluded from the 32 | // service's load balancing pool. 33 | MaxEjectionPercent float64 34 | 35 | // The initial value of the time interval (in ms) to resume detection. 36 | RecoveryIntervalMs uint32 37 | 38 | // The time interval (in seconds) for node recycling。 39 | RecycleIntervalS uint32 40 | 41 | // Maximum number of recovery attempts allowed during recovery detection. 42 | MaxRecoveryAttempts uint32 43 | 44 | // RecoveryCheckFunc is used to determine whether a node is healthy in 45 | // the active recovery mode. 46 | RecoveryCheckFunc RecoveryCheckFunc 47 | } 48 | -------------------------------------------------------------------------------- /core/outlier/slot.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package outlier 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | "github.com/alibaba/sentinel-golang/core/circuitbreaker" 20 | ) 21 | 22 | const ( 23 | RuleCheckSlotOrder = 6000 24 | ) 25 | 26 | var ( 27 | DefaultSlot = &Slot{} 28 | ) 29 | 30 | type Slot struct { 31 | } 32 | 33 | func (s *Slot) Order() uint32 { 34 | return RuleCheckSlotOrder 35 | } 36 | 37 | func (s *Slot) Check(ctx *base.EntryContext) *base.TokenResult { 38 | resource := ctx.Resource.Name() 39 | result := ctx.RuleCheckResult 40 | if len(resource) == 0 { 41 | return result 42 | } 43 | 44 | filterNodes, outlierNodes, halfOpenNodes := checkAllNodes(ctx) 45 | result.SetFilterNodes(filterNodes) 46 | result.SetHalfOpenNodes(halfOpenNodes) 47 | 48 | if len(outlierNodes) != 0 { 49 | rule := getOutlierRuleOfResource(resource) 50 | if rule.EnableActiveRecovery && len(retryerCh) < capacity { 51 | retryerCh <- task{outlierNodes, resource} 52 | } 53 | if len(recyclerCh) < capacity { 54 | recyclerCh <- task{outlierNodes, resource} 55 | } 56 | } 57 | return result 58 | } 59 | 60 | func checkAllNodes(ctx *base.EntryContext) (filters []string, outliers []string, halfs []string) { 61 | resource := ctx.Resource.Name() 62 | nodeBreaks := getNodeBreakersOfResource(resource) 63 | rule := getOutlierRuleOfResource(resource) 64 | nodeCount := len(nodeBreaks) 65 | for address, breaker := range nodeBreaks { 66 | if breaker.TryPass(ctx) { 67 | if !rule.EnableActiveRecovery && breaker.CurrentState() == circuitbreaker.HalfOpen { 68 | halfs = append(halfs, address) 69 | } 70 | continue 71 | } 72 | outliers = append(outliers, address) 73 | if len(filters) < int(float64(nodeCount)*rule.MaxEjectionPercent) { 74 | filters = append(filters, address) 75 | } 76 | } 77 | return filters, outliers, halfs 78 | } 79 | -------------------------------------------------------------------------------- /core/outlier/stat_slot.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package outlier 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | "github.com/alibaba/sentinel-golang/logging" 20 | ) 21 | 22 | const ( 23 | StatSlotOrder = 6000 24 | ) 25 | 26 | var ( 27 | DefaultMetricStatSlot = &MetricStatSlot{} 28 | ) 29 | 30 | // MetricStatSlot records metrics for outlier ejection on invocation completed. 31 | // MetricStatSlot must be filled into slot chain if outlier ejection is alive. 32 | type MetricStatSlot struct { 33 | } 34 | 35 | func (s *MetricStatSlot) Order() uint32 { 36 | return StatSlotOrder 37 | } 38 | 39 | func (c *MetricStatSlot) OnEntryPassed(_ *base.EntryContext) { 40 | // Do nothing 41 | return 42 | } 43 | 44 | func (c *MetricStatSlot) OnEntryBlocked(_ *base.EntryContext, _ *base.BlockError) { 45 | // Do nothing 46 | return 47 | } 48 | 49 | func (c *MetricStatSlot) OnCompleted(ctx *base.EntryContext) { 50 | res := ctx.Resource.Name() 51 | err := ctx.Err() 52 | nodeBreakers := getNodeBreakersOfResource(res) 53 | if address, ok := ctx.GetPair("address").(string); !ok || address == "" { 54 | logging.Warn("[Outlier] Failed to get valid address", "resourceName", res) 55 | } else { 56 | if _, ok2 := nodeBreakers[address]; !ok2 { 57 | addNodeBreakerOfResource(res, address) 58 | nodeBreakers = getNodeBreakersOfResource(res) 59 | } 60 | breaker := nodeBreakers[address] 61 | breaker.OnRequestComplete(ctx.Rt(), err) 62 | if err == nil { 63 | recycler := getRecyclerOfResource(res) 64 | recycler.recover(address) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /core/stat/base/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package stat/base provides fundamental data structures of statistics. 16 | package base 17 | -------------------------------------------------------------------------------- /core/stat/base/mutex.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package base 16 | 17 | import ( 18 | "sync" 19 | "sync/atomic" 20 | "unsafe" 21 | ) 22 | 23 | const mutexLocked = 1 << iota 24 | 25 | // The mutex which supports try-locking. 26 | type mutex struct { 27 | sync.Mutex 28 | } 29 | 30 | // TryLock acquires the lock only if it is free at the time of invocation. 31 | func (tl *mutex) TryLock() bool { 32 | return atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&tl.Mutex)), 0, mutexLocked) 33 | } 34 | -------------------------------------------------------------------------------- /core/stat/base/mutex_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package base 16 | 17 | import ( 18 | "runtime" 19 | "sync" 20 | "testing" 21 | "time" 22 | 23 | "github.com/alibaba/sentinel-golang/util" 24 | ) 25 | 26 | func Test_Mutex_TryLock(t *testing.T) { 27 | var m mutex 28 | m.Lock() 29 | util.Sleep(time.Second) 30 | if m.TryLock() { 31 | t.Error("TryLock get lock error") 32 | } 33 | m.Unlock() 34 | if !m.TryLock() { 35 | t.Error("TryLock get lock error") 36 | } 37 | m.Unlock() 38 | } 39 | 40 | func utTriableMutexConcurrent(t *testing.T) { 41 | m := &mutex{} 42 | cnt := int32(0) 43 | wg := &sync.WaitGroup{} 44 | wg.Add(1000) 45 | for i := 0; i < 1000; i++ { 46 | go func(tm *mutex, wgi *sync.WaitGroup, cntPtr *int32, t *testing.T) { 47 | for { 48 | if tm.TryLock() { 49 | *cntPtr = *cntPtr + 1 50 | tm.Unlock() 51 | wgi.Done() 52 | break 53 | } else { 54 | runtime.Gosched() 55 | } 56 | } 57 | }(m, wg, &cnt, t) 58 | } 59 | wg.Wait() 60 | //fmt.Println("count=", cnt) 61 | if cnt != 1000 { 62 | t.Error("count error concurrency") 63 | } 64 | } 65 | 66 | func Test_Mutex_TryLock_Concurrent(t *testing.T) { 67 | utTriableMutexConcurrent(t) 68 | } 69 | 70 | func Benchmark_Mutex_TryLock(b *testing.B) { 71 | for n := 0; n < b.N; n++ { 72 | utTriableMutexConcurrent(nil) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /core/stat/node_storage.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stat 16 | 17 | import ( 18 | "sync" 19 | 20 | "github.com/alibaba/sentinel-golang/core/base" 21 | "github.com/alibaba/sentinel-golang/logging" 22 | ) 23 | 24 | type ResourceNodeMap map[string]*ResourceNode 25 | 26 | var ( 27 | inboundNode = NewResourceNode(base.TotalInBoundResourceName, base.ResTypeCommon) 28 | 29 | resNodeMap = make(ResourceNodeMap) 30 | rnsMux = new(sync.RWMutex) 31 | ) 32 | 33 | // InboundNode returns the global inbound statistic node. 34 | func InboundNode() *ResourceNode { 35 | return inboundNode 36 | } 37 | 38 | // ResourceNodeList returns the slice of all existing resource nodes. 39 | func ResourceNodeList() []*ResourceNode { 40 | rnsMux.RLock() 41 | defer rnsMux.RUnlock() 42 | 43 | list := make([]*ResourceNode, 0, len(resNodeMap)) 44 | for _, v := range resNodeMap { 45 | list = append(list, v) 46 | } 47 | return list 48 | } 49 | 50 | func GetResourceNode(resource string) *ResourceNode { 51 | rnsMux.RLock() 52 | defer rnsMux.RUnlock() 53 | 54 | return resNodeMap[resource] 55 | } 56 | 57 | func GetOrCreateResourceNode(resource string, resourceType base.ResourceType) *ResourceNode { 58 | node := GetResourceNode(resource) 59 | if node != nil { 60 | return node 61 | } 62 | rnsMux.Lock() 63 | defer rnsMux.Unlock() 64 | 65 | node = resNodeMap[resource] 66 | if node != nil { 67 | return node 68 | } 69 | 70 | if len(resNodeMap) >= int(base.DefaultMaxResourceAmount) { 71 | logging.Warn("[GetOrCreateResourceNode] Resource amount exceeds the threshold", "maxResourceAmount", base.DefaultMaxResourceAmount) 72 | } 73 | node = NewResourceNode(resource, resourceType) 74 | resNodeMap[resource] = node 75 | return node 76 | } 77 | 78 | func ResetResourceNodeMap() { 79 | rnsMux.Lock() 80 | defer rnsMux.Unlock() 81 | resNodeMap = make(ResourceNodeMap) 82 | } 83 | -------------------------------------------------------------------------------- /core/stat/resource_node.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stat 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | "github.com/alibaba/sentinel-golang/core/config" 20 | ) 21 | 22 | type ResourceNode struct { 23 | BaseStatNode 24 | 25 | resourceName string 26 | resourceType base.ResourceType 27 | } 28 | 29 | // NewResourceNode creates a new resource node with given name and classification. 30 | func NewResourceNode(resourceName string, resourceType base.ResourceType) *ResourceNode { 31 | return &ResourceNode{ 32 | BaseStatNode: *NewBaseStatNode(config.MetricStatisticSampleCount(), config.MetricStatisticIntervalMs()), 33 | resourceName: resourceName, 34 | resourceType: resourceType, 35 | } 36 | } 37 | 38 | func (n *ResourceNode) ResourceType() base.ResourceType { 39 | return n.resourceType 40 | } 41 | 42 | func (n *ResourceNode) ResourceName() string { 43 | return n.resourceName 44 | } 45 | -------------------------------------------------------------------------------- /core/stat/stat_prepare_slot.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stat 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/core/base" 19 | ) 20 | 21 | const ( 22 | PrepareSlotOrder = 1000 23 | ) 24 | 25 | var ( 26 | DefaultResourceNodePrepareSlot = &ResourceNodePrepareSlot{} 27 | ) 28 | 29 | type ResourceNodePrepareSlot struct { 30 | } 31 | 32 | func (s *ResourceNodePrepareSlot) Order() uint32 { 33 | return PrepareSlotOrder 34 | } 35 | 36 | func (s *ResourceNodePrepareSlot) Prepare(ctx *base.EntryContext) { 37 | node := GetOrCreateResourceNode(ctx.Resource.Name(), ctx.Resource.Classification()) 38 | // Set the resource node to the context. 39 | ctx.StatNode = node 40 | } 41 | -------------------------------------------------------------------------------- /core/system/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package system provides implementation of adaptive system protection. 16 | package system 17 | -------------------------------------------------------------------------------- /core/system_metric/sys_metric_stat_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package system_metric 16 | 17 | import ( 18 | "sync" 19 | "testing" 20 | "time" 21 | 22 | "github.com/alibaba/sentinel-golang/util" 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestCurrentLoad(t *testing.T) { 27 | defer currentLoad.Store(NotRetrievedLoadValue) 28 | 29 | cLoad := CurrentLoad() 30 | assert.True(t, util.Float64Equals(NotRetrievedLoadValue, cLoad)) 31 | 32 | v := float64(1.0) 33 | currentLoad.Store(v) 34 | cLoad = CurrentLoad() 35 | assert.True(t, util.Float64Equals(v, cLoad)) 36 | } 37 | 38 | func TestCurrentCpuUsage(t *testing.T) { 39 | defer currentCpuUsage.Store(NotRetrievedCpuUsageValue) 40 | 41 | cpuUsage := CurrentCpuUsage() 42 | assert.Equal(t, NotRetrievedCpuUsageValue, cpuUsage) 43 | 44 | v := float64(0.3) 45 | currentCpuUsage.Store(v) 46 | cpuUsage = CurrentCpuUsage() 47 | assert.True(t, util.Float64Equals(v, cpuUsage)) 48 | } 49 | 50 | func Test_getProcessCpuStat(t *testing.T) { 51 | wg := &sync.WaitGroup{} 52 | wg.Add(1) 53 | go func() { 54 | i := 0 55 | wg.Done() 56 | for i < 10000000000 { 57 | i++ 58 | if i == 1000000000 { 59 | i = 0 60 | } 61 | } 62 | }() 63 | wg.Wait() 64 | 65 | got, err := getProcessCpuStat() 66 | if err != nil { 67 | t.Error(err) 68 | } 69 | assert.True(t, int(got) == 0) 70 | time.Sleep(time.Millisecond * 200) 71 | 72 | got, err = getProcessCpuStat() 73 | if err != nil { 74 | t.Error(err) 75 | } 76 | assert.True(t, int(got) > 0) 77 | time.Sleep(time.Millisecond * 200) 78 | 79 | got, err = getProcessCpuStat() 80 | if err != nil { 81 | t.Error(err) 82 | } 83 | assert.True(t, int(got) > 0) 84 | time.Sleep(time.Millisecond * 200) 85 | } 86 | -------------------------------------------------------------------------------- /example/flow/qps/qps_limit_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | "math/rand" 21 | "testing" 22 | "time" 23 | 24 | sentinel "github.com/alibaba/sentinel-golang/api" 25 | "github.com/alibaba/sentinel-golang/core/base" 26 | "github.com/alibaba/sentinel-golang/core/flow" 27 | "github.com/alibaba/sentinel-golang/util" 28 | ) 29 | 30 | func Benchmark_qps(b *testing.B) { 31 | for i := 0; i < b.N; i++ { 32 | doTest() 33 | } 34 | } 35 | 36 | func doTest() { 37 | // We should initialize Sentinel first. 38 | err := sentinel.InitDefault() 39 | if err != nil { 40 | log.Fatalf("Unexpected error: %+v", err) 41 | } 42 | 43 | _, err = flow.LoadRules([]*flow.Rule{ 44 | { 45 | Resource: "some-test", 46 | Threshold: 100, 47 | TokenCalculateStrategy: flow.Direct, 48 | ControlBehavior: flow.Reject, 49 | }, 50 | }) 51 | if err != nil { 52 | log.Fatalf("Unexpected error: %+v", err) 53 | return 54 | } 55 | 56 | for i := 0; i < 10; i++ { 57 | go func() { 58 | for { 59 | e, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound)) 60 | if b != nil { 61 | // Blocked. We could get the block reason from the BlockError. 62 | time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) 63 | } else { 64 | // Passed, wrap the logic here. 65 | fmt.Println(util.CurrentTimeMillis(), "passed") 66 | time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) 67 | 68 | // Be sure the entry is exited finally. 69 | e.Exit() 70 | } 71 | 72 | } 73 | }() 74 | } 75 | time.Sleep(time.Second * 5) 76 | } 77 | -------------------------------------------------------------------------------- /example/flow/qps/sentinel.yml: -------------------------------------------------------------------------------- 1 | version: "v1" 2 | sentinel: 3 | app: 4 | name: sentinel-go-demo 5 | log: 6 | metric: 7 | maxFileCount: 7 -------------------------------------------------------------------------------- /example/flow/warm_up/qps_warm_up_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | "math/rand" 21 | "testing" 22 | "time" 23 | 24 | "github.com/alibaba/sentinel-golang/util" 25 | 26 | "github.com/alibaba/sentinel-golang/core/flow" 27 | 28 | sentinel "github.com/alibaba/sentinel-golang/api" 29 | "github.com/alibaba/sentinel-golang/core/base" 30 | ) 31 | 32 | func Benchmark_qps(b *testing.B) { 33 | for i := 0; i < b.N; i++ { 34 | doTest() 35 | } 36 | } 37 | 38 | func doTest() { 39 | // We should initialize Sentinel first. 40 | err := sentinel.InitDefault() 41 | if err != nil { 42 | log.Fatalf("Unexpected error: %+v", err) 43 | } 44 | 45 | _, err = flow.LoadRules([]*flow.Rule{ 46 | { 47 | Resource: "some-test", 48 | Threshold: 100, 49 | TokenCalculateStrategy: flow.WarmUp, 50 | ControlBehavior: flow.Reject, 51 | WarmUpPeriodSec: 10, 52 | }, 53 | }) 54 | if err != nil { 55 | log.Fatalf("Unexpected error: %+v", err) 56 | return 57 | } 58 | for i := 0; i < 10; i++ { 59 | go func() { 60 | for { 61 | e, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound)) 62 | if b != nil { 63 | // Blocked. We could get the block reason from the BlockError. 64 | time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) 65 | } else { 66 | // Passed, wrap the logic here. 67 | fmt.Println(util.CurrentTimeMillis(), "passed") 68 | time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond) 69 | 70 | // Be sure the entry is exited finally. 71 | e.Exit() 72 | } 73 | 74 | } 75 | }() 76 | } 77 | time.Sleep(time.Second * 5) 78 | } 79 | -------------------------------------------------------------------------------- /example/flow/warm_up/sentinel.yml: -------------------------------------------------------------------------------- 1 | version: "v1" 2 | sentinel: 3 | app: 4 | name: sentinel-go-demo 5 | log: 6 | metric: 7 | maxFileCount: 7 -------------------------------------------------------------------------------- /example/isolation/concurrency_limitation_example.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "math/rand" 19 | "os" 20 | "time" 21 | 22 | sentinel "github.com/alibaba/sentinel-golang/api" 23 | "github.com/alibaba/sentinel-golang/core/config" 24 | "github.com/alibaba/sentinel-golang/core/isolation" 25 | "github.com/alibaba/sentinel-golang/logging" 26 | ) 27 | 28 | func main() { 29 | cfg := config.NewDefaultConfig() 30 | // for testing, logging output to console 31 | cfg.Sentinel.Log.Logger = logging.NewConsoleLogger() 32 | err := sentinel.InitWithConfig(cfg) 33 | if err != nil { 34 | logging.Error(err, "fail") 35 | os.Exit(1) 36 | } 37 | logging.ResetGlobalLoggerLevel(logging.DebugLevel) 38 | ch := make(chan struct{}) 39 | 40 | r1 := &isolation.Rule{ 41 | Resource: "abc", 42 | MetricType: isolation.Concurrency, 43 | Threshold: 12, 44 | } 45 | _, err = isolation.LoadRules([]*isolation.Rule{r1}) 46 | if err != nil { 47 | logging.Error(err, "fail") 48 | os.Exit(1) 49 | } 50 | 51 | for i := 0; i < 15; i++ { 52 | go func() { 53 | for { 54 | e, b := sentinel.Entry("abc", sentinel.WithBatchCount(1)) 55 | if b != nil { 56 | logging.Info("[Isolation] Blocked", "reason", b.BlockType().String(), "rule", b.TriggeredRule(), "snapshot", b.TriggeredValue()) 57 | time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond) 58 | } else { 59 | logging.Info("[Isolation] Passed") 60 | time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond) 61 | e.Exit() 62 | } 63 | } 64 | }() 65 | } 66 | <-ch 67 | } 68 | -------------------------------------------------------------------------------- /example/outlier/hello_kitex/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "time" 7 | 8 | "github.com/cloudwego/kitex-examples/hello/kitex_gen/api" 9 | "github.com/cloudwego/kitex-examples/hello/kitex_gen/api/hello" 10 | "github.com/cloudwego/kitex/client" 11 | etcd "github.com/kitex-contrib/registry-etcd" 12 | 13 | sentinel "github.com/alibaba/sentinel-golang/api" 14 | "github.com/alibaba/sentinel-golang/core/circuitbreaker" 15 | "github.com/alibaba/sentinel-golang/core/outlier" 16 | "github.com/alibaba/sentinel-golang/pkg/adapters/kitex" 17 | ) 18 | 19 | func initOutlierClient() hello.Client { 20 | resolver, err := etcd.NewEtcdResolver([]string{"127.0.0.1:2379"}) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | c, err := hello.NewClient("example.helloworld", 25 | client.WithResolver(kitex.OutlierClientResolver(resolver)), 26 | client.WithMiddleware(kitex.SentinelClientMiddleware( 27 | kitex.WithEnableOutlier(func(ctx context.Context) bool { 28 | return true 29 | }))), 30 | ) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | return c 35 | } 36 | 37 | func main() { 38 | c := initOutlierClient() 39 | err := sentinel.InitDefault() 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | _, err = outlier.LoadRules([]*outlier.Rule{ 44 | { 45 | Rule: &circuitbreaker.Rule{ 46 | Resource: "example.helloworld", 47 | Strategy: circuitbreaker.ErrorCount, 48 | RetryTimeoutMs: 3000, 49 | MinRequestAmount: 1, 50 | StatIntervalMs: 1000, 51 | Threshold: 1.0, 52 | }, 53 | EnableActiveRecovery: true, 54 | MaxEjectionPercent: 1.0, 55 | RecoveryIntervalMs: 2000, 56 | MaxRecoveryAttempts: 5, 57 | }, 58 | }) 59 | if err != nil { 60 | log.Fatal(err) 61 | } 62 | passCount, testCount := 0, 200 63 | req := &api.Request{Message: "Bob"} 64 | for i := 0; i < testCount; i++ { 65 | resp, err := c.Echo(context.Background(), req) 66 | log.Println(resp, err) 67 | if err == nil { 68 | passCount++ 69 | } 70 | time.Sleep(500 * time.Millisecond) 71 | } 72 | log.Printf("Results: %d out of %d requests were successful\n", passCount, testCount) 73 | } 74 | -------------------------------------------------------------------------------- /example/outlier/hello_kitex/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/cloudwego/kitex-examples/hello/kitex_gen/api" 10 | ) 11 | 12 | type HelloImpl struct { 13 | id int 14 | startTime time.Time 15 | } 16 | 17 | // If the simulated node crashes, then Echo returns correct directly, otherwise Echo needs to simulate a business error 18 | func (s *HelloImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) { 19 | message := fmt.Sprintf("Welcome %s,I am node%d", req.Message, s.id) 20 | if *nodeCrashFlag { 21 | return &api.Response{Message: message}, nil 22 | } 23 | faultStartTime := s.startTime.Add(5 * time.Second).Add(time.Duration(s.id) * 5 * time.Second) 24 | faultEndTime := faultStartTime.Add(20 * time.Second) 25 | currentTime := time.Now() 26 | // If currentTime is in the time range of the business error 27 | if currentTime.After(faultStartTime) && currentTime.Before(faultEndTime) { 28 | return nil, errors.New("internal server error") 29 | } 30 | return &api.Response{Message: message}, nil 31 | } 32 | 33 | func getIDWithAddress(address string) int { 34 | return int(address[len(address)-1] - '0') 35 | } 36 | -------------------------------------------------------------------------------- /example/outlier/hello_kitex/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | api "github.com/cloudwego/kitex-examples/hello/kitex_gen/api/hello" 10 | "github.com/cloudwego/kitex/pkg/rpcinfo" 11 | "github.com/cloudwego/kitex/server" 12 | etcd "github.com/kitex-contrib/registry-etcd" 13 | ) 14 | 15 | var addressFlag = flag.String("server_address", ":8000", "Set the listen address for server") 16 | var nodeCrashFlag = flag.Bool("node_crash", false, "Set the flag for whether to simulate node crash") 17 | 18 | const serviceName = "example.helloworld" 19 | const etcdAddr = "127.0.0.1:2379" 20 | 21 | func main() { 22 | flag.Parse() 23 | etcdReg, err := etcd.NewEtcdRegistry([]string{etcdAddr}) 24 | addr, err := net.ResolveTCPAddr("tcp", *addressFlag) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | svr := api.NewServer( 29 | &HelloImpl{getIDWithAddress(*addressFlag), time.Now()}, 30 | server.WithServiceAddr(addr), 31 | server.WithRegistry(etcdReg), 32 | server.WithServerBasicInfo( 33 | &rpcinfo.EndpointBasicInfo{ 34 | ServiceName: serviceName, 35 | }), 36 | ) 37 | err = svr.Run() 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/outlier/hello_kitex/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | node_crash=${1:-false} 4 | node_count=${2:-9} # node_count can only be 1 to 9 5 | 6 | start_process() { 7 | for ((i = 1; i <= node_count; i++)); do 8 | port=900$i 9 | echo "Starting: go run . --server_address=:$port --node_crash=$node_crash" 10 | go run . --server_address=:$port --node_crash=$node_crash & 11 | pids[$i]=$! 12 | done 13 | } 14 | 15 | stop_process() { 16 | for ((i = 1; i <= node_count; i++)); do 17 | sleep 5 18 | kill ${pids[$i]} 19 | port=900$i 20 | pgrep -f "hello_kitex --server_address=:$port" | xargs kill 21 | echo "Killed process ${pids[$i]}" 22 | done 23 | } 24 | 25 | restart_process() { 26 | for ((i = 1; i <= node_count; i++)); do 27 | sleep 5 28 | port=900$i 29 | echo "Restarting: go run . --server_address=:$port --node_crash=$node_crash" 30 | go run . --server_address=:$port --node_crash=$node_crash & 31 | done 32 | } 33 | 34 | if [ "$node_crash" = "true" ]; then 35 | start_process 36 | sleep 5 37 | stop_process 38 | restart_process 39 | else 40 | start_process 41 | fi 42 | 43 | wait 44 | -------------------------------------------------------------------------------- /example/outlier/hello_kratos/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | pb "github.com/go-kratos/examples/helloworld/helloworld" 10 | "github.com/go-kratos/kratos/v2/middleware/recovery" 11 | "github.com/go-kratos/kratos/v2/transport" 12 | "github.com/go-kratos/kratos/v2/transport/grpc" 13 | "github.com/go-kratos/kratos/v2/transport/http" 14 | ) 15 | 16 | type server struct { 17 | pb.UnimplementedGreeterServer 18 | id int 19 | startTime time.Time 20 | } 21 | 22 | // NewServer inits http server and grpc server 23 | func NewServer() (transport.Server, transport.Server) { 24 | httpSrv := http.NewServer( 25 | http.Address(*httpAddressFlag), 26 | http.Middleware(recovery.Recovery()), 27 | ) 28 | grpcSrv := grpc.NewServer( 29 | grpc.Address(*grpcAddressFlag), 30 | grpc.Middleware(recovery.Recovery()), 31 | ) 32 | s := &server{id: getIDWithAddress(*grpcAddressFlag), startTime: time.Now()} 33 | pb.RegisterGreeterServer(grpcSrv, s) 34 | pb.RegisterGreeterHTTPServer(httpSrv, s) 35 | return grpcSrv, httpSrv 36 | } 37 | 38 | func getIDWithAddress(address string) int { 39 | return int(address[len(address)-1] - '0') 40 | } 41 | 42 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (resp *pb.HelloReply, err error) { 43 | message := fmt.Sprintf("Welcome %s,I am node%d", in.Name, s.id) 44 | if *nodeCrashFlag { 45 | return &pb.HelloReply{Message: message}, nil 46 | } 47 | faultStartTime := s.startTime.Add(5 * time.Second).Add(time.Duration(s.id) * 5 * time.Second) 48 | faultEndTime := faultStartTime.Add(20 * time.Second) 49 | currentTime := time.Now() 50 | // If currentTime is in the time range of the business error 51 | if currentTime.After(faultStartTime) && currentTime.Before(faultEndTime) { 52 | return nil, errors.New("internal server error") 53 | } 54 | return &pb.HelloReply{Message: message}, nil 55 | } 56 | -------------------------------------------------------------------------------- /example/outlier/hello_kratos/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | 7 | etcdregitry "github.com/go-kratos/kratos/contrib/registry/etcd/v2" 8 | "github.com/go-kratos/kratos/v2" 9 | etcdclient "go.etcd.io/etcd/client/v3" 10 | ) 11 | 12 | var httpAddressFlag = flag.String("http_server_address", ":8000", "Set the listen address for http server") 13 | var grpcAddressFlag = flag.String("grpc_server_address", ":9000", "Set the listen address for grpc server") 14 | var nodeCrashFlag = flag.Bool("node_crash", false, "Set the flag for whether to simulate node crash") 15 | 16 | const serviceName = "example.helloworld" 17 | const etcdAddr = "127.0.0.1:2379" 18 | 19 | func main() { 20 | flag.Parse() 21 | client, err := etcdclient.New(etcdclient.Config{ 22 | Endpoints: []string{etcdAddr}, 23 | }) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | etcdReg := etcdregitry.New(client) 28 | grpcSrv, httpSrv := NewServer() 29 | app := kratos.New( 30 | kratos.Name(serviceName), 31 | kratos.Server( 32 | httpSrv, 33 | grpcSrv, 34 | ), 35 | kratos.Registrar(etcdReg), 36 | ) 37 | err = app.Run() 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/outlier/hello_kratos/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | node_crash=${1:-false} 4 | node_count=${2:-9} # node_count can only be 1 to 9 5 | 6 | start_process() { 7 | for ((i = 1; i <= node_count; i++)); do 8 | http_port=800$i 9 | grpc_port=900$i 10 | echo "Starting: go run . --grpc_server_address=:$grpc_port --http_server_address=:$http_port --node_crash=$node_crash" 11 | go run . --grpc_server_address=:$grpc_port --http_server_address=:$http_port --node_crash=$node_crash & 12 | pids[$i]=$! 13 | done 14 | } 15 | 16 | stop_process() { 17 | for ((i = 1; i <= node_count; i++)); do 18 | sleep 5 19 | kill ${pids[$i]} 20 | port=900$i 21 | pgrep -f "hello_kratos --grpc_server_address=:$port" | xargs kill 22 | echo "Killed process ${pids[$i]}" 23 | done 24 | } 25 | 26 | restart_process() { 27 | for ((i = 1; i <= node_count; i++)); do 28 | sleep 5 29 | http_port=800$i 30 | grpc_port=900$i 31 | echo "Restarting: go run . --grpc_server_address=:$grpc_port --http_server_address=:$http_port --node_crash=$node_crash" 32 | go run . --grpc_server_address=:$grpc_port --http_server_address=:$http_port --node_crash=$node_crash & 33 | done 34 | } 35 | 36 | if [ "$node_crash" = "true" ]; then 37 | start_process 38 | sleep 5 39 | stop_process 40 | restart_process 41 | else 42 | start_process 43 | fi 44 | 45 | wait 46 | -------------------------------------------------------------------------------- /example/outlier/hello_micro/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "time" 7 | 8 | "github.com/micro/go-micro/v2" 9 | "github.com/micro/go-micro/v2/client" 10 | "github.com/micro/go-micro/v2/client/selector" 11 | "github.com/micro/go-micro/v2/registry" 12 | "github.com/micro/go-micro/v2/registry/etcd" 13 | 14 | sentinel "github.com/alibaba/sentinel-golang/api" 15 | "github.com/alibaba/sentinel-golang/core/circuitbreaker" 16 | "github.com/alibaba/sentinel-golang/core/outlier" 17 | microAdapter "github.com/alibaba/sentinel-golang/pkg/adapters/micro" 18 | proto "github.com/alibaba/sentinel-golang/pkg/adapters/micro/test" 19 | ) 20 | 21 | const serviceName = "example.helloworld" 22 | const etcdAddr = "127.0.0.1:2379" 23 | const version = "latest" 24 | 25 | func initOutlierClient() client.Client { 26 | etcdReg := etcd.NewRegistry(registry.Addrs(etcdAddr)) 27 | sel := selector.NewSelector( 28 | selector.Registry(etcdReg), 29 | selector.SetStrategy(selector.RoundRobin), 30 | ) 31 | srv := micro.NewService( 32 | micro.Name(serviceName), 33 | micro.Version(version), 34 | micro.Selector(sel), 35 | micro.WrapClient(microAdapter.NewClientWrapper( 36 | microAdapter.WithEnableOutlier(func(ctx context.Context) bool { 37 | return true 38 | }))), 39 | ) 40 | return srv.Client() 41 | } 42 | 43 | func main() { 44 | c := initOutlierClient() 45 | err := sentinel.InitDefault() 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | _, err = outlier.LoadRules([]*outlier.Rule{ 50 | { 51 | Rule: &circuitbreaker.Rule{ 52 | Resource: serviceName, 53 | Strategy: circuitbreaker.ErrorCount, 54 | RetryTimeoutMs: 3000, 55 | MinRequestAmount: 1, 56 | StatIntervalMs: 1000, 57 | Threshold: 1.0, 58 | }, 59 | EnableActiveRecovery: false, 60 | MaxEjectionPercent: 1.0, 61 | RecoveryIntervalMs: 2000, 62 | MaxRecoveryAttempts: 5, 63 | }, 64 | }) 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | passCount, testCount := 0, 200 69 | req := c.NewRequest(serviceName, "Test.Ping", &proto.Request{}, 70 | client.WithContentType("application/json")) 71 | for i := 0; i < testCount; i++ { 72 | rsp := &proto.Response{} 73 | err = c.Call(context.Background(), req, rsp) 74 | log.Println(rsp, err) 75 | if err == nil { 76 | passCount++ 77 | } 78 | time.Sleep(500 * time.Millisecond) 79 | } 80 | log.Printf("Results: %d out of %d requests were successful\n", passCount, testCount) 81 | } 82 | -------------------------------------------------------------------------------- /example/outlier/hello_micro/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | proto "github.com/alibaba/sentinel-golang/pkg/adapters/micro/test" 10 | ) 11 | 12 | var nodeCrash = false // Set the flag for whether to simulate node crash 13 | 14 | type TestHandler struct { 15 | id int 16 | startTime time.Time 17 | } 18 | 19 | func (s *TestHandler) Ping(ctx context.Context, req *proto.Request, rsp *proto.Response) error { 20 | if nodeCrash { 21 | rsp.Result = fmt.Sprintf("Welcome, I am node%d", s.id) 22 | return nil 23 | } 24 | faultStartTime := s.startTime.Add(5 * time.Second).Add(time.Duration(s.id) * 5 * time.Second) 25 | faultEndTime := faultStartTime.Add(20 * time.Second) 26 | currentTime := time.Now() 27 | // If currentTime is in the time range of the business error 28 | if currentTime.After(faultStartTime) && currentTime.Before(faultEndTime) { 29 | rsp.Result = "" 30 | return errors.New("internal server error") 31 | } 32 | rsp.Result = fmt.Sprintf("Welcome, I am node%d", s.id) 33 | return nil 34 | } 35 | 36 | func getIDWithAddress(address string) int { 37 | return int(address[len(address)-1] - '0') 38 | } 39 | -------------------------------------------------------------------------------- /example/outlier/hello_micro/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/micro/go-micro/v2" 7 | "github.com/micro/go-micro/v2/logger" 8 | "github.com/micro/go-micro/v2/registry" 9 | "github.com/micro/go-micro/v2/registry/etcd" 10 | 11 | pb "github.com/alibaba/sentinel-golang/pkg/adapters/micro/test" 12 | ) 13 | 14 | const serviceName = "example.helloworld" 15 | const etcdAddr = "127.0.0.1:2379" 16 | const version = "latest" 17 | 18 | func main() { 19 | etcdReg := etcd.NewRegistry(registry.Addrs(etcdAddr)) 20 | srv := micro.NewService() 21 | srv.Init( 22 | micro.Name(serviceName), 23 | micro.Version(version), 24 | micro.Registry(etcdReg), 25 | ) 26 | if err := pb.RegisterTestHandler(srv.Server(), &TestHandler{ 27 | getIDWithAddress(srv.Server().Options().Address), 28 | time.Now(), 29 | }); err != nil { 30 | logger.Fatal(err) 31 | } 32 | if err := srv.Run(); err != nil { 33 | logger.Fatal(err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/outlier/hello_micro/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | node_crash=${1:-false} 4 | node_count=${2:-9} # node_count can only be 1 to 9 5 | 6 | start_process() { 7 | for ((i = 1; i <= node_count; i++)); do 8 | port=900$i 9 | echo "Starting: go run . --server_address=:$port" 10 | go run . --server_address=:$port & 11 | pids[$i]=$! 12 | done 13 | } 14 | 15 | stop_process() { 16 | for ((i = 1; i <= node_count; i++)); do 17 | sleep 5 18 | kill ${pids[$i]} 19 | port=900$i 20 | pgrep -f "hello_micro --server_address=:$port" | xargs kill 21 | echo "Killed process ${pids[$i]}" 22 | done 23 | } 24 | 25 | restart_process() { 26 | for ((i = 1; i <= node_count; i++)); do 27 | sleep 5 28 | port=900$i 29 | echo "Restarting: go run . --server_address=:$port" 30 | go run . --server_address=:$port & 31 | done 32 | } 33 | 34 | sed -E -i 's/nodeCrash = (true|false)/nodeCrash = '$node_crash'/' handler.go 35 | if [ "$node_crash" = "true" ]; then 36 | start_process 37 | sleep 5 38 | stop_process 39 | restart_process 40 | else 41 | start_process 42 | fi 43 | 44 | wait 45 | -------------------------------------------------------------------------------- /exporter/metric/empty_exporter.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import "net/http" 4 | 5 | type EmptyExporter struct { 6 | } 7 | 8 | func (e *EmptyExporter) HTTPHandler() http.Handler { 9 | return nil 10 | } 11 | 12 | type emptyMetric struct { 13 | } 14 | 15 | func (e emptyMetric) Register() error { 16 | return nil 17 | } 18 | 19 | func (e emptyMetric) Unregister() bool { 20 | return false 21 | } 22 | 23 | func (e emptyMetric) Reset() { 24 | return 25 | } 26 | 27 | func newEmptyExporter() *EmptyExporter { 28 | return &EmptyExporter{} 29 | } 30 | 31 | type emptyCounter struct { 32 | emptyMetric 33 | } 34 | 35 | func (e emptyCounter) Add(value float64, labelValues ...string) { 36 | return 37 | } 38 | 39 | func (e *EmptyExporter) NewCounter(name, desc string, labelNames []string) Counter { 40 | return emptyCounter{} 41 | } 42 | 43 | type emptyGauge struct { 44 | emptyMetric 45 | } 46 | 47 | func (e emptyGauge) Set(value float64, labelValues ...string) { 48 | return 49 | } 50 | 51 | func (e *EmptyExporter) NewGauge(name, desc string, labelNames []string) Gauge { 52 | return &emptyGauge{} 53 | } 54 | 55 | type emptyHistogram struct { 56 | emptyMetric 57 | } 58 | 59 | func (e emptyHistogram) Observe(value float64, labelValues ...string) { 60 | return 61 | } 62 | 63 | func (e *EmptyExporter) NewHistogram(name, desc string, buckets []float64, labelNames []string) Histogram { 64 | return &emptyHistogram{} 65 | } 66 | -------------------------------------------------------------------------------- /ext/datasource/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package ext/datasource provides interfaces and helper classes of dynamic data-source. 16 | package datasource 17 | -------------------------------------------------------------------------------- /ext/datasource/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package datasource 16 | 17 | type Code uint32 18 | 19 | const ( 20 | OK = iota 21 | ConvertSourceError = 1 22 | UpdatePropertyError = 2 23 | HandleSourceError = 3 24 | ) 25 | 26 | func NewError(code Code, desc string) Error { 27 | return Error{ 28 | code: code, 29 | desc: desc, 30 | } 31 | } 32 | 33 | type Error struct { 34 | code Code 35 | desc string 36 | } 37 | 38 | func (e Error) Code() Code { 39 | return e.code 40 | } 41 | 42 | func (e Error) Error() string { 43 | return e.desc 44 | } 45 | -------------------------------------------------------------------------------- /ext/datasource/hotspot_rule_converter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package datasource 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func Test_parseSpecificItems(t *testing.T) { 24 | t.Run("Test_parseSpecificItems", func(t *testing.T) { 25 | source := make([]SpecificValue, 6) 26 | s1 := SpecificValue{ 27 | ValKind: KindInt, 28 | ValStr: "10010", 29 | Threshold: 100, 30 | } 31 | s2 := SpecificValue{ 32 | ValKind: KindInt, 33 | ValStr: "10010aaa", 34 | Threshold: 100, 35 | } 36 | s3 := SpecificValue{ 37 | ValKind: KindString, 38 | ValStr: "test-string", 39 | Threshold: 100, 40 | } 41 | s4 := SpecificValue{ 42 | ValKind: KindBool, 43 | ValStr: "true", 44 | Threshold: 100, 45 | } 46 | s5 := SpecificValue{ 47 | ValKind: KindFloat64, 48 | ValStr: "1.234", 49 | Threshold: 100, 50 | } 51 | s6 := SpecificValue{ 52 | ValKind: KindFloat64, 53 | ValStr: "1.2345678", 54 | Threshold: 100, 55 | } 56 | source[0] = s1 57 | source[1] = s2 58 | source[2] = s3 59 | source[3] = s4 60 | source[4] = s5 61 | source[5] = s6 62 | 63 | got := parseSpecificItems(source) 64 | assert.True(t, len(got) == 5) 65 | assert.True(t, got[10010] == 100) 66 | assert.True(t, got[true] == 100) 67 | assert.True(t, got[1.234] == 100) 68 | assert.True(t, got[1.23400] == 100) 69 | assert.True(t, got["test-string"] == 100) 70 | assert.True(t, got[1.23457] == 100) 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /ext/datasource/mock.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package datasource 16 | 17 | import "github.com/stretchr/testify/mock" 18 | 19 | type MockPropertyHandler struct { 20 | mock.Mock 21 | } 22 | 23 | func (m *MockPropertyHandler) isPropertyConsistent(src interface{}) bool { 24 | args := m.Called(src) 25 | return args.Bool(0) 26 | } 27 | 28 | func (m *MockPropertyHandler) Handle(src []byte) error { 29 | args := m.Called(src) 30 | return args.Error(0) 31 | } 32 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/fsnotify/fsnotify v1.4.7 7 | github.com/google/uuid v1.1.1 8 | github.com/pkg/errors v0.9.1 9 | github.com/prometheus/client_golang v1.16.0 10 | github.com/shirou/gopsutil/v3 v3.21.6 11 | github.com/stretchr/testify v1.8.0 12 | go.uber.org/multierr v1.5.0 13 | gopkg.in/yaml.v2 v2.4.0 14 | ) 15 | 16 | require ( 17 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/go-ole/go-ole v1.2.4 // indirect 22 | github.com/golang/protobuf v1.5.3 // indirect 23 | github.com/google/go-cmp v0.6.0 // indirect 24 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 25 | github.com/pmezard/go-difflib v1.0.0 // indirect 26 | github.com/prometheus/client_model v0.3.0 // indirect 27 | github.com/prometheus/common v0.42.0 // indirect 28 | github.com/prometheus/procfs v0.10.1 // indirect 29 | github.com/rogpeppe/go-internal v1.13.1 // indirect 30 | github.com/stretchr/objx v0.4.0 // indirect 31 | github.com/tklauser/go-sysconf v0.3.6 // indirect 32 | github.com/tklauser/numcpus v0.2.2 // indirect 33 | go.uber.org/atomic v1.6.0 // indirect 34 | golang.org/x/sys v0.21.0 // indirect 35 | golang.org/x/tools v0.22.0 // indirect 36 | google.golang.org/protobuf v1.30.0 // indirect 37 | gopkg.in/yaml.v3 v3.0.1 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /pkg/adapters/echo/doc.go: -------------------------------------------------------------------------------- 1 | // This package provides Sentinel middleware for echo. 2 | package echo 3 | -------------------------------------------------------------------------------- /pkg/adapters/echo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/adapters/echo 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 7 | github.com/alibaba/sentinel-golang v1.0.2 8 | github.com/go-ole/go-ole v1.2.5 // indirect 9 | github.com/labstack/echo/v4 v4.1.17 10 | github.com/stretchr/testify v1.7.0 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/adapters/echo/middleware.go: -------------------------------------------------------------------------------- 1 | package echo 2 | 3 | import ( 4 | "net/http" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/base" 8 | "github.com/labstack/echo/v4" 9 | ) 10 | 11 | // SentinelMiddleware returns new echo.HandlerFunc. 12 | // Default resource name pattern is {httpMethod}:{apiPath}, such as "GET:/api/:id". 13 | // Default block fallback is to return 429 (Too Many Requests) response. 14 | // 15 | // You may customize your own resource extractor and block handler by setting options. 16 | func SentinelMiddleware(opts ...Option) echo.MiddlewareFunc { 17 | options := evaluateOptions(opts) 18 | return func(next echo.HandlerFunc) echo.HandlerFunc { 19 | return func(c echo.Context) (err error) { 20 | resourceName := c.Request().Method + ":" + c.Path() 21 | if options.resourceExtract != nil { 22 | resourceName = options.resourceExtract(c) 23 | } 24 | entry, blockErr := sentinel.Entry( 25 | resourceName, 26 | sentinel.WithResourceType(base.ResTypeWeb), 27 | sentinel.WithTrafficType(base.Inbound), 28 | ) 29 | if blockErr != nil { 30 | if options.blockFallback != nil { 31 | err = options.blockFallback(c) 32 | } else { 33 | // default error response 34 | err = c.JSON(http.StatusTooManyRequests, "Blocked by Sentinel") 35 | } 36 | return err 37 | } 38 | defer entry.Exit() 39 | 40 | err = next(c) 41 | return err 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pkg/adapters/echo/middleware_example_test.go: -------------------------------------------------------------------------------- 1 | package echo 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "github.com/labstack/echo/v4/middleware" 6 | ) 7 | 8 | func Example() { 9 | r := echo.New() 10 | r.Use(middleware.Logger()) 11 | r.Use( 12 | SentinelMiddleware( 13 | // customize resource extractor if required 14 | // method_path by default 15 | WithResourceExtractor(func(ctx echo.Context) string { 16 | if res, ok := ctx.Get("X-Real-IP").(string); ok { 17 | return res 18 | } 19 | return "" 20 | }), 21 | // customize block fallback if required 22 | // abort with status 429 by default 23 | WithBlockFallback(func(ctx echo.Context) error { 24 | return ctx.JSON(400, map[string]interface{}{ 25 | "err": "too many requests; the quota used up", 26 | "code": 10222, 27 | }) 28 | }), 29 | ), 30 | ) 31 | 32 | r.GET("/test", func(c echo.Context) error { 33 | return nil 34 | }) 35 | r.Logger.Fatal(r.Start(":1323")) 36 | } 37 | -------------------------------------------------------------------------------- /pkg/adapters/echo/option.go: -------------------------------------------------------------------------------- 1 | package echo 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | ) 6 | 7 | type ( 8 | Option func(*options) 9 | options struct { 10 | resourceExtract func(echo.Context) string 11 | blockFallback func(echo.Context) error 12 | } 13 | ) 14 | 15 | func evaluateOptions(opts []Option) *options { 16 | optCopy := &options{} 17 | for _, opt := range opts { 18 | opt(optCopy) 19 | } 20 | 21 | return optCopy 22 | } 23 | 24 | // WithResourceExtractor sets the resource extractor of the web requests. 25 | func WithResourceExtractor(fn func(ctx echo.Context) string) Option { 26 | return func(opts *options) { 27 | opts.resourceExtract = fn 28 | } 29 | } 30 | 31 | // WithBlockFallback sets the fallback handler when requests are blocked. 32 | func WithBlockFallback(fn func(ctx echo.Context) error) Option { 33 | return func(opts *options) { 34 | opts.blockFallback = fn 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pkg/adapters/fiber/doc.go: -------------------------------------------------------------------------------- 1 | // This package provides Sentinel middleware for fiber. 2 | package fiber 3 | -------------------------------------------------------------------------------- /pkg/adapters/fiber/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/adapters/fiber 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/StackExchange/wmi v1.2.1 // indirect 7 | github.com/alibaba/sentinel-golang v1.0.2 8 | github.com/gofiber/fiber/v2 v2.37.1 9 | github.com/stretchr/testify v1.5.1 10 | ) 11 | -------------------------------------------------------------------------------- /pkg/adapters/fiber/middleware.go: -------------------------------------------------------------------------------- 1 | package fiber 2 | 3 | import ( 4 | "net/http" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/base" 8 | "github.com/gofiber/fiber/v2" 9 | ) 10 | 11 | // SentinelMiddleware returns new gin.HandlerFunc 12 | // Default resource name is {method}:{path}, such as "GET:/api/users/:id" 13 | // Default block fallback is returning 429 code 14 | // Define your own behavior by setting options 15 | func SentinelMiddleware(opts ...Option) fiber.Handler { 16 | options := evaluateOptions(opts) 17 | return func(ctx *fiber.Ctx) error { 18 | resourceName := ctx.Route().Method + ":" + string(ctx.Context().Path()) 19 | 20 | if options.resourceExtract != nil { 21 | resourceName = options.resourceExtract(ctx) 22 | } 23 | 24 | entry, entryErr := sentinel.Entry( 25 | resourceName, 26 | sentinel.WithResourceType(base.ResTypeWeb), 27 | sentinel.WithTrafficType(base.Inbound), 28 | ) 29 | 30 | if entryErr != nil { 31 | if options.blockFallback != nil { 32 | return options.blockFallback(ctx) 33 | } else { 34 | return ctx.SendStatus(http.StatusTooManyRequests) 35 | } 36 | } 37 | 38 | defer entry.Exit() 39 | return ctx.Next() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/adapters/fiber/middlware_example_test.go: -------------------------------------------------------------------------------- 1 | package fiber 2 | 3 | import "github.com/gofiber/fiber/v2" 4 | 5 | func Example() { 6 | app := fiber.New() 7 | app.Use( 8 | SentinelMiddleware( 9 | // customize resource extractor if required 10 | // method_path by default 11 | WithResourceExtractor(func(ctx *fiber.Ctx) string { 12 | return ctx.GetReqHeaders()["X-Real-IP"] 13 | }), 14 | // customize block fallback if required 15 | // abort with status 429 by default 16 | WithBlockFallback(func(ctx *fiber.Ctx) error { 17 | return ctx.Status(400).JSON(struct { 18 | Error string `json:"error"` 19 | Code int `json:"code"` 20 | }{ 21 | "too many request; the quota used up", 22 | 10222, 23 | }) 24 | })), 25 | ) 26 | 27 | app.Get("/test", func(ctx *fiber.Ctx) error { return nil }) 28 | _ = app.Listen(":8080") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/adapters/fiber/option.go: -------------------------------------------------------------------------------- 1 | package fiber 2 | 3 | import "github.com/gofiber/fiber/v2" 4 | 5 | type ( 6 | Option func(*options) 7 | options struct { 8 | resourceExtract func(*fiber.Ctx) string 9 | blockFallback func(*fiber.Ctx) error 10 | } 11 | ) 12 | 13 | func evaluateOptions(opts []Option) *options { 14 | optCopy := &options{} 15 | for _, opt := range opts { 16 | opt(optCopy) 17 | } 18 | 19 | return optCopy 20 | } 21 | 22 | // WithResourceExtractor sets the resource extractor of the web requests. 23 | func WithResourceExtractor(fn func(*fiber.Ctx) string) Option { 24 | return func(opts *options) { 25 | opts.resourceExtract = fn 26 | } 27 | } 28 | 29 | // WithBlockFallback sets the fallback handler when requests are blocked. 30 | func WithBlockFallback(fn func(ctx *fiber.Ctx) error) Option { 31 | return func(opts *options) { 32 | opts.blockFallback = fn 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/adapters/gear/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | This package provides Sentinel middleware for Gear. 3 | 4 | Users may register SentinelMiddleware to the Gear router, like. 5 | 6 | import ( 7 | sentinelPlugear "github.com/sentinel-group/sentinel-go-adapters/gear" 8 | "github.com/teambition/gear" 9 | ) 10 | 11 | r := gear.NewRouter() 12 | 13 | r.Use(sentinelPlugear.SentinelMiddleware()) 14 | 15 | The plugear extracts "HttpMethod:Router" as the resource name by default (e.g. GET:/foo/:id). 16 | Users may provide customized resource name extractor when creating new 17 | SentinelMiddleware (via options). 18 | 19 | Fallback logic: the plugear will return "429 Too Many Requests" status code 20 | if current request is blocked by Sentinel rules. Users may also 21 | provide customized fallback logic via WithBlockFallback(handler) options. 22 | */ 23 | package gear 24 | -------------------------------------------------------------------------------- /pkg/adapters/gear/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/adapters/gear 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 7 | github.com/alibaba/sentinel-golang v1.0.2 8 | github.com/go-ole/go-ole v1.2.5 // indirect 9 | github.com/stretchr/testify v1.7.0 10 | github.com/teambition/gear v1.24.0 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/adapters/gear/middleware.go: -------------------------------------------------------------------------------- 1 | package gear 2 | 3 | import ( 4 | "net/http" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/base" 8 | "github.com/teambition/gear" 9 | ) 10 | 11 | // SentinelMiddleware returns new gear.Middleware 12 | // Default resource name is {method}:{path}, such as "GET:/api/users/:id" 13 | // Default block fallback is returning 429 code 14 | // Define your own behavior by setting options 15 | func SentinelMiddleware(opts ...Option) gear.Middleware { 16 | options := evaluateOptions(opts) 17 | return func(ctx *gear.Context) (err error) { 18 | resourceName := ctx.Method + ":" + gear.GetRouterPatternFromCtx(ctx) 19 | 20 | if options.resourceExtract != nil { 21 | resourceName = options.resourceExtract(ctx) 22 | } 23 | 24 | entry, blockErr := sentinel.Entry( 25 | resourceName, 26 | sentinel.WithResourceType(base.ResTypeWeb), 27 | sentinel.WithTrafficType(base.Inbound), 28 | ) 29 | 30 | if blockErr != nil { 31 | if options.blockFallback != nil { 32 | err = options.blockFallback(ctx) 33 | } else { 34 | err = ctx.End(http.StatusTooManyRequests, []byte("Blocked by Sentinel")) 35 | } 36 | return err 37 | } 38 | 39 | defer entry.Exit() 40 | return err 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/adapters/gear/middleware_example.go: -------------------------------------------------------------------------------- 1 | package gear 2 | 3 | import ( 4 | "github.com/teambition/gear" 5 | ) 6 | 7 | func Example() { 8 | app := gear.New() 9 | 10 | router := gear.NewRouter() 11 | router.Use( 12 | SentinelMiddleware( 13 | // customize resource extractor if required 14 | // method_path by default 15 | WithResourceExtractor(func(ctx *gear.Context) string { 16 | return ctx.GetHeader("X-Real-IP") 17 | }), 18 | // customize block fallback if required 19 | // abort with status 429 by default 20 | WithBlockFallback(func(ctx *gear.Context) error { 21 | return ctx.JSON(400, map[string]interface{}{ 22 | "err": "too many request; the quota used up", 23 | "code": 10222, 24 | }) 25 | }), 26 | ), 27 | ) 28 | router.Get("/test", func(c *gear.Context) error { 29 | return nil 30 | }) 31 | app.UseHandler(router) 32 | _ = app.Listen(":0") 33 | } 34 | -------------------------------------------------------------------------------- /pkg/adapters/gear/option.go: -------------------------------------------------------------------------------- 1 | package gear 2 | 3 | import ( 4 | "github.com/teambition/gear" 5 | ) 6 | 7 | type ( 8 | Option func(*options) 9 | options struct { 10 | resourceExtract func(*gear.Context) string 11 | blockFallback func(*gear.Context) error 12 | } 13 | ) 14 | 15 | func evaluateOptions(opts []Option) *options { 16 | optCopy := &options{} 17 | for _, opt := range opts { 18 | opt(optCopy) 19 | } 20 | 21 | return optCopy 22 | } 23 | 24 | // WithResourceExtractor sets the resource extractor of the web requests. 25 | func WithResourceExtractor(fn func(*gear.Context) string) Option { 26 | return func(opts *options) { 27 | opts.resourceExtract = fn 28 | } 29 | } 30 | 31 | // WithBlockFallback sets the fallback handler when requests are blocked. 32 | func WithBlockFallback(fn func(ctx *gear.Context) error) Option { 33 | return func(opts *options) { 34 | opts.blockFallback = fn 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pkg/adapters/gin/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | This package provides Sentinel middleware for Gin. 3 | 4 | Users may register SentinelMiddleware to the Gin server, like. 5 | 6 | import ( 7 | sentinelPlugin "github.com/sentinel-group/sentinel-go-adapters/gin" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | r := gin.New() 12 | r.Use(sentinelPlugin.SentinelMiddleware()) 13 | 14 | The plugin extracts "HttpMethod:FullPath" as the resource name by default (e.g. GET:/foo/:id). 15 | Users may provide customized resource name extractor when creating new 16 | SentinelMiddleware (via options). 17 | 18 | Fallback logic: the plugin will return "429 Too Many Requests" status code 19 | if current request is blocked by Sentinel rules. Users may also 20 | provide customized fallback logic via WithBlockFallback(handler) options. 21 | */ 22 | package gin 23 | -------------------------------------------------------------------------------- /pkg/adapters/gin/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/adapters/gin 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 7 | github.com/alibaba/sentinel-golang v1.0.2 8 | github.com/gin-gonic/gin v1.7.0 9 | github.com/go-ole/go-ole v1.2.5 // indirect 10 | github.com/stretchr/testify v1.7.0 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/adapters/gin/middleware.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "net/http" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/base" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | // SentinelMiddleware returns new gin.HandlerFunc 12 | // Default resource name is {method}:{path}, such as "GET:/api/users/:id" 13 | // Default block fallback is returning 429 code 14 | // Define your own behavior by setting options 15 | func SentinelMiddleware(opts ...Option) gin.HandlerFunc { 16 | options := evaluateOptions(opts) 17 | return func(c *gin.Context) { 18 | resourceName := c.Request.Method + ":" + c.FullPath() 19 | 20 | if options.resourceExtract != nil { 21 | resourceName = options.resourceExtract(c) 22 | } 23 | 24 | entry, err := sentinel.Entry( 25 | resourceName, 26 | sentinel.WithResourceType(base.ResTypeWeb), 27 | sentinel.WithTrafficType(base.Inbound), 28 | ) 29 | 30 | if err != nil { 31 | if options.blockFallback != nil { 32 | options.blockFallback(c) 33 | } else { 34 | c.AbortWithStatus(http.StatusTooManyRequests) 35 | } 36 | return 37 | } 38 | 39 | defer entry.Exit() 40 | c.Next() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/adapters/gin/middleware_example_test.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func Example() { 8 | r := gin.New() 9 | r.Use( 10 | SentinelMiddleware( 11 | // customize resource extractor if required 12 | // method_path by default 13 | WithResourceExtractor(func(ctx *gin.Context) string { 14 | return ctx.GetHeader("X-Real-IP") 15 | }), 16 | // customize block fallback if required 17 | // abort with status 429 by default 18 | WithBlockFallback(func(ctx *gin.Context) { 19 | ctx.AbortWithStatusJSON(400, map[string]interface{}{ 20 | "err": "too many request; the quota used up", 21 | "code": 10222, 22 | }) 23 | }), 24 | ), 25 | ) 26 | 27 | r.GET("/test", func(c *gin.Context) {}) 28 | _ = r.Run(":0") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/adapters/gin/option.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | type ( 8 | Option func(*options) 9 | options struct { 10 | resourceExtract func(*gin.Context) string 11 | blockFallback func(*gin.Context) 12 | } 13 | ) 14 | 15 | func evaluateOptions(opts []Option) *options { 16 | optCopy := &options{} 17 | for _, opt := range opts { 18 | opt(optCopy) 19 | } 20 | 21 | return optCopy 22 | } 23 | 24 | // WithResourceExtractor sets the resource extractor of the web requests. 25 | func WithResourceExtractor(fn func(*gin.Context) string) Option { 26 | return func(opts *options) { 27 | opts.resourceExtract = fn 28 | } 29 | } 30 | 31 | // WithBlockFallback sets the fallback handler when requests are blocked. 32 | func WithBlockFallback(fn func(ctx *gin.Context)) Option { 33 | return func(opts *options) { 34 | opts.blockFallback = fn 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pkg/adapters/go-zero/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | This package provides Sentinel integration for go-zero. 3 | 4 | go-zero provides both zrpc and rest modules. 5 | 6 | For go-zero/zrpc, user can call `AddUnaryInterceptors` and `AddStreamInterceptors` 7 | on zrpc.RpcServer or `WithUnaryClientInterceptor` on zrpc.ClientOption to add grpc interceptors. 8 | 9 | For go-zero/rest, there are two kinds of middlewares. 10 | The first one is the global middleware, which can be applied to rest.Server via: 11 | 12 | import ( 13 | sgz "github.com/sentinel-go/pkg/adapters/go-zero" 14 | ) 15 | server := rest.MustNewServer(c.RestConf) 16 | server.Use(sgz.SentinelMiddleware()) 17 | 18 | The plugin extracts service FullMethod as the resource name by default. 19 | Users may provide customized resource name extractor when creating new 20 | Sentinel interceptors (via options). 21 | 22 | Fallback logic: the plugin will return the BlockError by default 23 | if current request is blocked by Sentinel rules. Users may also 24 | provide customized fallback logic via WithXxxBlockFallback(handler) options. 25 | 26 | The second one is the routing middleware, 27 | which is registered to specific routes via by go-zero automatically. 28 | Therefore, it is recomended to create routing middleware based on the generated templates. 29 | An example with Sentinel based on go-zero template is provided in `routing_middleware`. 30 | 31 | The calling order is first global ones, then routing ones. 32 | */ 33 | package go_zero 34 | -------------------------------------------------------------------------------- /pkg/adapters/go-zero/global_middleware.go: -------------------------------------------------------------------------------- 1 | package go_zero 2 | 3 | import ( 4 | "net/http" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/base" 8 | "github.com/zeromicro/go-zero/rest" 9 | ) 10 | 11 | // SentinelMiddleware returns new echo.HandlerFunc. 12 | // Default resource name pattern is {httpMethod}:{apiPath}, such as "GET:/api/:id". 13 | // Default block fallback is to return 429 (Too Many Requests) response. 14 | // 15 | // You may customize your own resource extractor and block handler by setting options. 16 | func SentinelMiddleware(opts ...Option) rest.Middleware { 17 | options := evaluateOptions(opts) 18 | return func(next http.HandlerFunc) http.HandlerFunc { 19 | return func(w http.ResponseWriter, r *http.Request) { 20 | resourceName := r.Method + ":" + r.URL.Path 21 | if options.resourceExtract != nil { 22 | resourceName = options.resourceExtract(r) 23 | } 24 | entry, blockErr := sentinel.Entry( 25 | resourceName, 26 | sentinel.WithResourceType(base.ResTypeWeb), 27 | sentinel.WithTrafficType(base.Inbound), 28 | ) 29 | if blockErr != nil { 30 | if options.blockFallback != nil { 31 | status, msg := options.blockFallback(r) 32 | http.Error(w, msg, status) 33 | } else { 34 | // default error response 35 | http.Error(w, "Blocked by Sentinel", http.StatusTooManyRequests) 36 | } 37 | return 38 | } 39 | defer entry.Exit() 40 | 41 | next(w, r) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pkg/adapters/go-zero/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/adapters/go-zero 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/alibaba/sentinel-golang v1.0.2 7 | github.com/stretchr/testify v1.7.0 8 | github.com/zeromicro/go-zero v1.3.0 9 | ) 10 | 11 | require ( 12 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 13 | github.com/beorn7/perks v1.0.1 // indirect 14 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/go-logr/logr v1.2.2 // indirect 17 | github.com/go-logr/stdr v1.2.2 // indirect 18 | github.com/go-ole/go-ole v1.2.5 // indirect 19 | github.com/golang-jwt/jwt/v4 v4.2.0 // indirect 20 | github.com/golang/protobuf v1.5.2 // indirect 21 | github.com/google/uuid v1.3.0 // indirect 22 | github.com/justinas/alice v1.2.0 // indirect 23 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 24 | github.com/openzipkin/zipkin-go v0.4.0 // indirect 25 | github.com/pkg/errors v0.9.1 // indirect 26 | github.com/pmezard/go-difflib v1.0.0 // indirect 27 | github.com/prometheus/client_golang v1.11.0 // indirect 28 | github.com/prometheus/client_model v0.2.0 // indirect 29 | github.com/prometheus/common v0.26.0 // indirect 30 | github.com/prometheus/procfs v0.6.0 // indirect 31 | github.com/shirou/gopsutil v3.20.11+incompatible // indirect 32 | github.com/spaolacci/murmur3 v1.1.0 // indirect 33 | go.opentelemetry.io/otel v1.3.0 // indirect 34 | go.opentelemetry.io/otel/exporters/jaeger v1.3.0 // indirect 35 | go.opentelemetry.io/otel/exporters/zipkin v1.3.0 // indirect 36 | go.opentelemetry.io/otel/sdk v1.3.0 // indirect 37 | go.opentelemetry.io/otel/trace v1.3.0 // indirect 38 | go.uber.org/automaxprocs v1.4.0 // indirect 39 | golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect 40 | google.golang.org/grpc v1.43.0 // indirect 41 | google.golang.org/protobuf v1.27.1 // indirect 42 | gopkg.in/yaml.v2 v2.4.0 // indirect 43 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 44 | ) 45 | -------------------------------------------------------------------------------- /pkg/adapters/go-zero/option.go: -------------------------------------------------------------------------------- 1 | package go_zero 2 | 3 | import "net/http" 4 | 5 | type ( 6 | Option func(*options) 7 | options struct { 8 | resourceExtract func(r *http.Request) string 9 | blockFallback func(r *http.Request) (int, string) 10 | } 11 | ) 12 | 13 | func evaluateOptions(opts []Option) *options { 14 | optCopy := &options{} 15 | for _, opt := range opts { 16 | opt(optCopy) 17 | } 18 | 19 | return optCopy 20 | } 21 | 22 | // WithResourceExtractor sets the resource extractor of the web requests. 23 | func WithResourceExtractor(fn func(r *http.Request) string) Option { 24 | return func(opts *options) { 25 | opts.resourceExtract = fn 26 | } 27 | } 28 | 29 | // WithBlockFallback sets the fallback handler when requests are blocked. 30 | func WithBlockFallback(fn func(r *http.Request) (int, string)) Option { 31 | return func(opts *options) { 32 | opts.blockFallback = fn 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/adapters/go-zero/routing_middleware.go: -------------------------------------------------------------------------------- 1 | package go_zero 2 | 3 | /* 4 | This is an example based on go-zero routing middleware tempalte, 5 | which is generated by goctl via 6 | 7 | goctl api go -api xxx.api 8 | 9 | The xxx.api file defines routing middlewares by 10 | 11 | @server( 12 | middleware: SentinelRoute 13 | ) 14 | 15 | For more information, please refer to go-zero's documentation. 16 | */ 17 | 18 | import ( 19 | "net/http" 20 | 21 | sentinel "github.com/alibaba/sentinel-golang/api" 22 | "github.com/alibaba/sentinel-golang/core/base" 23 | ) 24 | 25 | type SentinelRouteMiddleware struct { 26 | } 27 | 28 | func NewSentinelRouteMiddleware() *SentinelRouteMiddleware { 29 | return &SentinelRouteMiddleware{} 30 | } 31 | 32 | func (m *SentinelRouteMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { 33 | return func(w http.ResponseWriter, r *http.Request) { 34 | resourceName := r.Method + ":" + r.URL.Path 35 | entry, blockErr := sentinel.Entry( 36 | resourceName, 37 | sentinel.WithResourceType(base.ResTypeWeb), 38 | sentinel.WithTrafficType(base.Inbound), 39 | ) 40 | if blockErr != nil { 41 | http.Error(w, "Blocked by Sentinel", http.StatusTooManyRequests) 42 | return 43 | } 44 | defer entry.Exit() 45 | next(w, r) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/adapters/go-zero/test.yml: -------------------------------------------------------------------------------- 1 | Name: greet-api 2 | Host: 0.0.0.0 3 | Port: 8888 -------------------------------------------------------------------------------- /pkg/adapters/goframe/doc.go: -------------------------------------------------------------------------------- 1 | // Package goframe provides Sentinel middleware for GoFrame. 2 | // 3 | // Users may register SentinelMiddleware to the GoFrame server, like: 4 | // 5 | // import ( 6 | // sentinelPlugin "github.com/your-repo/goframe-sentinel-adapter" 7 | // "github.com/gogf/gf/v2/frame/g" 8 | // "github.com/gogf/gf/v2/net/ghttp" 9 | // ) 10 | // 11 | // s := g.Server() 12 | // s.Use(ghttp.MiddlewareHandlerFunc(sentinelPlugin.SentinelMiddleware())) 13 | // 14 | // The plugin extracts "HttpMethod:FullPath" as the resource name by default (e.g. GET:/foo/:id). 15 | // Users may provide a customized resource name extractor when creating new SentinelMiddleware (via options). 16 | // 17 | // Fallback logic: the plugin will return "429 Too Many Requests" status code if the current request is blocked by Sentinel rules. 18 | // Users may also provide customized fallback logic via WithBlockFallback(handler) options. 19 | package goframe 20 | -------------------------------------------------------------------------------- /pkg/adapters/goframe/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/adapters/goframe 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/alibaba/sentinel-golang v1.0.4 7 | github.com/gogf/gf/v2 v2.6.4 8 | github.com/stretchr/testify v1.8.2 9 | ) 10 | 11 | require ( 12 | github.com/BurntSushi/toml v1.2.0 // indirect 13 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 14 | github.com/beorn7/perks v1.0.1 // indirect 15 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 16 | github.com/clbanning/mxj/v2 v2.7.0 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/fatih/color v1.15.0 // indirect 19 | github.com/fsnotify/fsnotify v1.7.0 // indirect 20 | github.com/go-logr/logr v1.2.4 // indirect 21 | github.com/go-logr/stdr v1.2.2 // indirect 22 | github.com/go-ole/go-ole v1.2.4 // indirect 23 | github.com/golang/protobuf v1.4.3 // indirect 24 | github.com/google/uuid v1.1.1 // indirect 25 | github.com/gorilla/websocket v1.5.0 // indirect 26 | github.com/grokify/html-strip-tags-go v0.0.1 // indirect 27 | github.com/magiconair/properties v1.8.6 // indirect 28 | github.com/mattn/go-colorable v0.1.13 // indirect 29 | github.com/mattn/go-isatty v0.0.20 // indirect 30 | github.com/mattn/go-runewidth v0.0.15 // indirect 31 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 32 | github.com/olekukonko/tablewriter v0.0.5 // indirect 33 | github.com/pkg/errors v0.9.1 // indirect 34 | github.com/pmezard/go-difflib v1.0.0 // indirect 35 | github.com/prometheus/client_golang v1.9.0 // indirect 36 | github.com/prometheus/client_model v0.2.0 // indirect 37 | github.com/prometheus/common v0.15.0 // indirect 38 | github.com/prometheus/procfs v0.2.0 // indirect 39 | github.com/rivo/uniseg v0.4.4 // indirect 40 | github.com/shirou/gopsutil/v3 v3.21.6 // indirect 41 | github.com/tklauser/go-sysconf v0.3.6 // indirect 42 | github.com/tklauser/numcpus v0.2.2 // indirect 43 | go.opentelemetry.io/otel v1.14.0 // indirect 44 | go.opentelemetry.io/otel/sdk v1.14.0 // indirect 45 | go.opentelemetry.io/otel/trace v1.14.0 // indirect 46 | golang.org/x/net v0.17.0 // indirect 47 | golang.org/x/sys v0.13.0 // indirect 48 | golang.org/x/text v0.13.0 // indirect 49 | google.golang.org/protobuf v1.23.0 // indirect 50 | gopkg.in/yaml.v2 v2.3.0 // indirect 51 | gopkg.in/yaml.v3 v3.0.1 // indirect 52 | ) 53 | -------------------------------------------------------------------------------- /pkg/adapters/goframe/middleware.go: -------------------------------------------------------------------------------- 1 | package goframe 2 | 3 | import ( 4 | "github.com/alibaba/sentinel-golang/api" 5 | "github.com/alibaba/sentinel-golang/core/base" 6 | "github.com/gogf/gf/v2/net/ghttp" 7 | "net/http" 8 | ) 9 | 10 | // SentinelMiddleware returns new ghttp.HandlerFunc 11 | // Default resource name is {method}:{path}, such as "GET:/api/users/:id" 12 | // Default block fallback is returning 429 status code 13 | // Define your own behavior by setting options 14 | func SentinelMiddleware(opts ...Option) ghttp.HandlerFunc { 15 | options := evaluateOptions(opts) 16 | return func(r *ghttp.Request) { 17 | resourceName := r.Method + ":" + r.URL.Path 18 | 19 | if options.resourceExtract != nil { 20 | resourceName = options.resourceExtract(r) 21 | } 22 | 23 | entry, err := api.Entry( 24 | resourceName, 25 | api.WithResourceType(base.ResTypeWeb), 26 | api.WithTrafficType(base.Inbound), 27 | ) 28 | 29 | if err != nil { 30 | if options.blockFallback != nil { 31 | options.blockFallback(r) 32 | } else { 33 | r.Response.WriteHeader(http.StatusTooManyRequests) 34 | r.Response.Writeln("Too Many Requests") 35 | } 36 | return 37 | } 38 | 39 | defer entry.Exit() 40 | 41 | r.Middleware.Next() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/adapters/goframe/middleware_example_test.go: -------------------------------------------------------------------------------- 1 | package goframe 2 | 3 | import ( 4 | "github.com/gogf/gf/v2/frame/g" 5 | "github.com/gogf/gf/v2/net/ghttp" 6 | ) 7 | 8 | func Example() { 9 | s := g.Server() 10 | s.Use( 11 | SentinelMiddleware( 12 | // customize resource extractor if required 13 | WithResourceExtractor(func(r *ghttp.Request) string { 14 | if res, ok := r.Header["X-Real-IP"]; ok && len(res) > 0 { 15 | return res[0] 16 | } 17 | return "" 18 | }), 19 | // customize block fallback if required 20 | WithBlockFallback(func(r *ghttp.Request) { 21 | r.Response.WriteHeader(400) 22 | r.Response.WriteJson(map[string]interface{}{ 23 | "err": "too many requests; the quota used up", 24 | "code": 10222, 25 | }) 26 | }), 27 | ), 28 | ) 29 | 30 | s.Group("/", func(group *ghttp.RouterGroup) { 31 | group.GET("/test", func(r *ghttp.Request) { 32 | r.Response.Write("hello sentinel") 33 | }) 34 | }) 35 | 36 | s.SetPort(8199) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/adapters/goframe/option.go: -------------------------------------------------------------------------------- 1 | package goframe 2 | 3 | import "github.com/gogf/gf/v2/net/ghttp" 4 | 5 | type ( 6 | Option func(*options) 7 | options struct { 8 | resourceExtract func(*ghttp.Request) string 9 | blockFallback func(*ghttp.Request) 10 | } 11 | ) 12 | 13 | func evaluateOptions(opts []Option) *options { 14 | optCopy := &options{} 15 | for _, opt := range opts { 16 | opt(optCopy) 17 | } 18 | 19 | return optCopy 20 | } 21 | 22 | // WithResourceExtractor sets the resource extractor of the web requests. 23 | func WithResourceExtractor(fn func(*ghttp.Request) string) Option { 24 | return func(opts *options) { 25 | opts.resourceExtract = fn 26 | } 27 | } 28 | 29 | // WithBlockFallback sets the fallback handler when requests are blocked. 30 | func WithBlockFallback(fn func(r *ghttp.Request)) Option { 31 | return func(opts *options) { 32 | opts.blockFallback = fn 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/adapters/grpc/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | This package provides Sentinel integration for gRPC. 3 | 4 | For server/client side, users may create a gRPC server/client with Sentinel interceptor. 5 | A sample of server side: 6 | 7 | import ( 8 | sentinelPlugin "github.com/sentinel-group/sentinel-go-adapters/grpc" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | // Create with Sentinel interceptor 13 | s := grpc.NewServer(grpc.UnaryInterceptor(sentinelPlugin.NewUnaryServerInterceptor())) 14 | 15 | The plugin extracts service FullMethod as the resource name by default. 16 | Users may provide customized resource name extractor when creating new 17 | Sentinel interceptors (via options). 18 | 19 | Fallback logic: the plugin will return the BlockError by default 20 | if current request is blocked by Sentinel rules. Users may also 21 | provide customized fallback logic via WithXxxBlockFallback(handler) options. 22 | */ 23 | package grpc 24 | -------------------------------------------------------------------------------- /pkg/adapters/grpc/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/adapters/grpc 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 7 | github.com/alibaba/sentinel-golang v1.0.2 8 | github.com/go-ole/go-ole v1.2.5 // indirect 9 | github.com/stretchr/testify v1.5.1 10 | google.golang.org/grpc v1.34.0 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/adapters/grpc/server.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/base" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | // NewUnaryServerInterceptor creates the unary server interceptor wrapped with Sentinel entry. 12 | func NewUnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor { 13 | options := evaluateOptions(opts) 14 | return func( 15 | ctx context.Context, 16 | req interface{}, 17 | info *grpc.UnaryServerInfo, 18 | handler grpc.UnaryHandler, 19 | ) (interface{}, error) { 20 | // method as resource name by default 21 | resourceName := info.FullMethod 22 | if options.unaryServerResourceExtract != nil { 23 | resourceName = options.unaryServerResourceExtract(ctx, req, info) 24 | } 25 | entry, blockErr := sentinel.Entry( 26 | resourceName, 27 | sentinel.WithResourceType(base.ResTypeRPC), 28 | sentinel.WithTrafficType(base.Inbound), 29 | ) 30 | if blockErr != nil { 31 | if options.unaryServerBlockFallback != nil { 32 | return options.unaryServerBlockFallback(ctx, req, info, blockErr) 33 | } 34 | return nil, blockErr 35 | } 36 | defer entry.Exit() 37 | 38 | res, err := handler(ctx, req) 39 | if err != nil { 40 | sentinel.TraceError(entry, err) 41 | } 42 | return res, err 43 | } 44 | } 45 | 46 | // NewStreamServerInterceptor creates the unary stream interceptor wrapped with Sentinel entry. 47 | func NewStreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor { 48 | options := evaluateOptions(opts) 49 | return func( 50 | srv interface{}, 51 | ss grpc.ServerStream, 52 | info *grpc.StreamServerInfo, 53 | handler grpc.StreamHandler, 54 | ) error { 55 | // method as resource name by default 56 | resourceName := info.FullMethod 57 | if options.streamServerResourceExtract != nil { 58 | resourceName = options.streamServerResourceExtract(srv, ss, info) 59 | } 60 | entry, blockErr := sentinel.Entry( 61 | resourceName, 62 | sentinel.WithResourceType(base.ResTypeRPC), 63 | sentinel.WithTrafficType(base.Inbound), 64 | ) 65 | if blockErr != nil { // blocked 66 | if options.streamServerBlockFallback != nil { 67 | return options.streamServerBlockFallback(srv, ss, info, blockErr) 68 | } 69 | return blockErr 70 | } 71 | defer entry.Exit() 72 | 73 | err := handler(srv, ss) 74 | if err != nil { 75 | sentinel.TraceError(entry, err) 76 | } 77 | return err 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pkg/adapters/hertz/client.go: -------------------------------------------------------------------------------- 1 | package hertz 2 | 3 | import ( 4 | "context" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/base" 8 | "github.com/cloudwego/hertz/pkg/app/client" 9 | "github.com/cloudwego/hertz/pkg/protocol" 10 | ) 11 | 12 | // SentinelClientMiddleware returns new client.Middleware 13 | // Default resource name is {method}:{path}, such as "GET:/api/users" 14 | // Default block fallback is returning blockError 15 | // Define your own behavior by setting serverOptions 16 | func SentinelClientMiddleware(opts ...ClientOption) client.Middleware { 17 | options := newClientOptions(opts) 18 | return func(next client.Endpoint) client.Endpoint { 19 | return func(ctx context.Context, req *protocol.Request, resp *protocol.Response) (err error) { 20 | resourceName := options.resourceExtract(ctx, req, resp) 21 | entry, blockErr := sentinel.Entry( 22 | resourceName, 23 | sentinel.WithResourceType(base.ResTypeWeb), 24 | sentinel.WithTrafficType(base.Outbound), 25 | ) 26 | if blockErr != nil { 27 | return options.blockFallback(ctx, req, resp, blockErr) 28 | } 29 | 30 | defer entry.Exit() 31 | err = next(ctx, req, resp) 32 | if err != nil { 33 | sentinel.TraceError(entry, err) 34 | return err 35 | } 36 | return nil 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg/adapters/hertz/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | This package provides Sentinel integration for hertz 3 | 4 | For server side, users may create a Hertz Server with Sentinel middleware. 5 | A simple of server side: 6 | 7 | import ( 8 | sentinelPlugin "github.com/sentinel-go/pkg/adapters/hertz" 9 | "github.com/cloudwego/hertz/pkg/app/server" 10 | ) 11 | 12 | h := server.New() 13 | h.Use(sentinelPlugin.SentinelServerMiddleware()) 14 | 15 | For client side, users may create a Hertz Client with Sentinel middleware. 16 | A simple of client side: 17 | 18 | import ( 19 | sentinelPlugin "github.com/sentinel-go/pkg/adapters/hertz" 20 | "github.com/cloudwego/hertz/pkg/app/client" 21 | ) 22 | 23 | client, _ := client.NewClient() 24 | client.Use(sentinelPlugin.SentinelClientMiddleware()) 25 | 26 | The plugin extracts service FullMethod as the resource name by default. 27 | Users may provide customized resource name extractor when creating new 28 | Sentinel middlewares (via options). 29 | 30 | Fallback logic: the plugin will stop by default if 31 | current request is blocked by Sentinel rules. Users may also provide 32 | customized fallback logic via WithClientBlockFallback(handler) options 33 | for client side. 34 | 35 | the plugin will return "429 Too Many Requests" status code 36 | if current request is blocked by Sentinel rules. Users may also 37 | provide customized fallback logic via WithServerBlockFallback(handler) 38 | options for server side. 39 | */ 40 | package hertz 41 | -------------------------------------------------------------------------------- /pkg/adapters/hertz/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/adapters/hertz 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/alibaba/sentinel-golang v1.0.4 7 | github.com/cloudwego/hertz v0.2.0 8 | github.com/stretchr/testify v1.8.0 9 | ) 10 | -------------------------------------------------------------------------------- /pkg/adapters/hertz/server.go: -------------------------------------------------------------------------------- 1 | package hertz 2 | 3 | import ( 4 | "context" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/base" 8 | "github.com/cloudwego/hertz/pkg/app" 9 | ) 10 | 11 | // SentinelServerMiddleware returns new app.HandlerFunc 12 | // Default resource name is {method}:{path}, such as "GET:/api/users/:id" 13 | // Default block fallback is returning 429 code 14 | // Define your own behavior by setting serverOptions 15 | func SentinelServerMiddleware(opts ...ServerOption) app.HandlerFunc { 16 | options := newServerOptions(opts) 17 | return func(c context.Context, ctx *app.RequestContext) { 18 | resourceName := options.resourceExtract(c, ctx) 19 | 20 | entry, err := sentinel.Entry( 21 | resourceName, 22 | sentinel.WithResourceType(base.ResTypeWeb), 23 | sentinel.WithTrafficType(base.Inbound), 24 | ) 25 | if err != nil { 26 | options.blockFallback(c, ctx) 27 | return 28 | } 29 | defer entry.Exit() 30 | ctx.Next(c) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/adapters/iris/middleware.go: -------------------------------------------------------------------------------- 1 | package iris 2 | 3 | import ( 4 | "net/http" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/base" 8 | "github.com/kataras/iris/v12" 9 | ) 10 | 11 | func SentinelMiddleware(opts ...Option) iris.Handler { 12 | options := evaluateOptions(opts) 13 | return func(c iris.Context) { 14 | resourceName := c.Request().Method + ":" + c.Request().URL.String() 15 | 16 | if options.resourceExtract != nil { 17 | resourceName = options.resourceExtract(c) 18 | } 19 | 20 | entry, err := sentinel.Entry( 21 | resourceName, 22 | sentinel.WithResourceType(base.ResTypeWeb), 23 | sentinel.WithTrafficType(base.Inbound), 24 | ) 25 | 26 | if err != nil { 27 | if options.blockFallback != nil { 28 | options.blockFallback(c) 29 | } else { 30 | c.StatusCode(http.StatusTooManyRequests) 31 | c.StopExecution() 32 | } 33 | return 34 | } 35 | 36 | defer entry.Exit() 37 | c.Next() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg/adapters/iris/middleware_example_test.go: -------------------------------------------------------------------------------- 1 | package iris 2 | 3 | import ( 4 | "github.com/kataras/iris/v12" 5 | ) 6 | 7 | func Example() { 8 | r := iris.New() 9 | r.Use( 10 | SentinelMiddleware( 11 | // customize resource extractor if required 12 | // method_path by default 13 | WithResourceExtractor(func(ctx iris.Context) string { 14 | return ctx.GetHeader("X-Real-IP") 15 | }), 16 | // customize block fallback if required 17 | // abort with status 429 by default 18 | WithBlockFallback(func(ctx iris.Context) { 19 | ctx.StatusCode(400) 20 | ctx.JSON(map[string]interface{}{ 21 | "err": "too many request; the quota used up", 22 | "code": 10222, 23 | }) 24 | ctx.StopExecution() 25 | }), 26 | ), 27 | ) 28 | 29 | r.Get("/test", func(c iris.Context) {}) 30 | r.Run(iris.Addr(":9999")) 31 | } 32 | -------------------------------------------------------------------------------- /pkg/adapters/iris/option.go: -------------------------------------------------------------------------------- 1 | package iris 2 | 3 | import ( 4 | "github.com/kataras/iris/v12" 5 | ) 6 | 7 | type ( 8 | Option func(*options) 9 | options struct { 10 | resourceExtract func(iris.Context) string 11 | blockFallback func(iris.Context) 12 | } 13 | ) 14 | 15 | func evaluateOptions(opts []Option) *options { 16 | optCopy := &options{} 17 | for _, opt := range opts { 18 | opt(optCopy) 19 | } 20 | 21 | return optCopy 22 | } 23 | 24 | // WithResourceExtractor sets the resource extractor of the web requests. 25 | func WithResourceExtractor(fn func(iris.Context) string) Option { 26 | return func(opts *options) { 27 | opts.resourceExtract = fn 28 | } 29 | } 30 | 31 | // WithBlockFallback sets the fallback handler when requests are blocked. 32 | func WithBlockFallback(fn func(ctx iris.Context)) Option { 33 | return func(opts *options) { 34 | opts.blockFallback = fn 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pkg/adapters/kitex/client_test.go: -------------------------------------------------------------------------------- 1 | package kitex 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/cloudwego/kitex-examples/hello/kitex_gen/api" 9 | "github.com/cloudwego/kitex-examples/hello/kitex_gen/api/hello" 10 | "github.com/cloudwego/kitex/client" 11 | "github.com/stretchr/testify/assert" 12 | 13 | sentinel "github.com/alibaba/sentinel-golang/api" 14 | "github.com/alibaba/sentinel-golang/core/flow" 15 | ) 16 | 17 | const FakeErrorMsg = "fake error for testing" 18 | 19 | func TestSentinelClientMiddleware(t *testing.T) { 20 | bf := func(ctx context.Context, req, resp interface{}, blockErr error) error { 21 | return errors.New(FakeErrorMsg) 22 | } 23 | c, err := hello.NewClient("hello", 24 | client.WithMiddleware(SentinelClientMiddleware(WithBlockFallback(bf)))) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | err = sentinel.InitDefault() 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | req := &api.Request{} 33 | t.Run("success", func(t *testing.T) { 34 | _, err := flow.LoadRules([]*flow.Rule{ 35 | { 36 | Resource: "hello:echo", 37 | Threshold: 1.0, 38 | TokenCalculateStrategy: flow.Direct, 39 | ControlBehavior: flow.Reject, 40 | }, 41 | }) 42 | assert.Nil(t, err) 43 | _, err = c.Echo(context.Background(), req) 44 | assert.NotNil(t, err) 45 | assert.NotEqual(t, FakeErrorMsg, err.Error()) 46 | t.Run("second fail", func(t *testing.T) { 47 | _, err = c.Echo(context.Background(), req) 48 | assert.NotNil(t, err) 49 | assert.Equal(t, FakeErrorMsg, err.Error()) 50 | }) 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/adapters/kitex/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | This package provides Sentinel integration for Kitex. 4 | 5 | For server side, users may append a Sentinel middleware to Kitex service, like: 6 | 7 | import ( 8 | sentinelPlugin "github.com/alibaba/sentinel-golang/pkg/adapters/kitex" 9 | ) 10 | srv := hello.NewServer(new(HelloImpl),server.WithMiddleware(SentinelServerMiddleware())) 11 | 12 | The plugin extracts service name and service method as the resource name by default. 13 | Users may provide customized resource name extractor when creating new 14 | Sentinel middleware (via WithResourceExtract options). 15 | 16 | Fallback logic: the plugin will return the BlockError by default 17 | if current request is blocked by Sentinel rules. Users may also 18 | provide customized fallback logic via WithBlockFallback(handler) options. 19 | */ 20 | package kitex 21 | -------------------------------------------------------------------------------- /pkg/adapters/kitex/options.go: -------------------------------------------------------------------------------- 1 | package kitex 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cloudwego/kitex/pkg/rpcinfo" 7 | "github.com/cloudwego/kitex/pkg/rpcinfo/remoteinfo" 8 | ) 9 | 10 | type Option struct { 11 | F func(o *options) 12 | } 13 | 14 | type options struct { 15 | ResourceExtract func(ctx context.Context, req, resp interface{}) string 16 | BlockFallback func(ctx context.Context, req, resp interface{}, blockErr error) error 17 | EnableOutlier func(ctx context.Context) bool 18 | } 19 | 20 | func DefaultBlockFallback(ctx context.Context, req, resp interface{}, blockErr error) error { 21 | return blockErr 22 | } 23 | 24 | func DefaultResourceExtract(ctx context.Context, req, resp interface{}) string { 25 | ri := rpcinfo.GetRPCInfo(ctx) 26 | return ri.To().ServiceName() + ":" + ri.To().Method() 27 | } 28 | 29 | func DefaultEnableOutlier(ctx context.Context) bool { 30 | return false 31 | } 32 | 33 | func newOptions(opts []Option) *options { 34 | o := &options{ 35 | ResourceExtract: DefaultResourceExtract, 36 | BlockFallback: DefaultBlockFallback, 37 | EnableOutlier: DefaultEnableOutlier, 38 | } 39 | o.Apply(opts) 40 | return o 41 | } 42 | 43 | func (o *options) Apply(opts []Option) { 44 | for _, op := range opts { 45 | op.F(o) 46 | } 47 | } 48 | 49 | // WithResourceExtract sets the resource extractor 50 | func WithResourceExtract(f func(ctx context.Context, req, resp interface{}) string) Option { 51 | return Option{F: func(o *options) { 52 | o.ResourceExtract = f 53 | }} 54 | } 55 | 56 | // WithBlockFallback sets the fallback handler 57 | func WithBlockFallback(f func(ctx context.Context, req, resp interface{}, blockErr error) error) Option { 58 | return Option{func(o *options) { 59 | o.BlockFallback = f 60 | }} 61 | } 62 | 63 | // WithEnableOutlier sets whether to enable outlier ejection 64 | func WithEnableOutlier(f func(ctx context.Context) bool) Option { 65 | return Option{func(o *options) { 66 | o.EnableOutlier = f 67 | }} 68 | } 69 | 70 | func ServiceNameExtract(ctx context.Context) string { 71 | rpcInfo := rpcinfo.GetRPCInfo(ctx) 72 | return rpcInfo.To().ServiceName() 73 | } 74 | 75 | func CalleeAddressExtract(ctx context.Context) string { 76 | rpcInfo := rpcinfo.GetRPCInfo(ctx) 77 | remote := remoteinfo.AsRemoteInfo(rpcInfo.To()) 78 | if remote == nil || remote.Address() == nil { 79 | return "" 80 | } 81 | return remote.Address().String() 82 | } 83 | -------------------------------------------------------------------------------- /pkg/adapters/kitex/server.go: -------------------------------------------------------------------------------- 1 | package kitex 2 | 3 | import ( 4 | "context" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/base" 8 | "github.com/cloudwego/kitex/pkg/endpoint" 9 | ) 10 | 11 | // SentinelServerMiddleware returns new server.Middleware 12 | // Default resource name is {service's name}:{method} 13 | // Default block fallback is returning blockError 14 | // Define your own behavior by setting serverOptions 15 | func SentinelServerMiddleware(opts ...Option) func(endpoint.Endpoint) endpoint.Endpoint { 16 | options := newOptions(opts) 17 | return func(next endpoint.Endpoint) endpoint.Endpoint { 18 | return func(ctx context.Context, req, resp interface{}) error { 19 | resourceName := options.ResourceExtract(ctx, req, resp) 20 | entry, blockErr := sentinel.Entry( 21 | resourceName, 22 | sentinel.WithResourceType(base.ResTypeRPC), 23 | sentinel.WithTrafficType(base.Inbound), 24 | ) 25 | if blockErr != nil { 26 | return options.BlockFallback(ctx, req, resp, blockErr) 27 | } 28 | defer entry.Exit() 29 | err := next(ctx, req, resp) 30 | if err != nil { 31 | sentinel.TraceError(entry, err) 32 | } 33 | return err 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pkg/adapters/kitex/server_test.go: -------------------------------------------------------------------------------- 1 | package kitex 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "strings" 7 | "testing" 8 | "time" 9 | 10 | sentinel "github.com/alibaba/sentinel-golang/api" 11 | "github.com/alibaba/sentinel-golang/core/flow" 12 | "github.com/cloudwego/kitex-examples/hello/kitex_gen/api" 13 | "github.com/cloudwego/kitex-examples/hello/kitex_gen/api/hello" 14 | "github.com/cloudwego/kitex/client" 15 | "github.com/cloudwego/kitex/pkg/rpcinfo" 16 | "github.com/cloudwego/kitex/server" 17 | "github.com/stretchr/testify/assert" 18 | ) 19 | 20 | // HelloImpl implements the last service interface defined in the IDL. 21 | type HelloImpl struct{} 22 | 23 | // Echo implements the HelloImpl interface. 24 | func (s *HelloImpl) Echo(ctx context.Context, req *api.Request) (resp *api.Response, err error) { 25 | resp = &api.Response{Message: req.Message} 26 | return 27 | } 28 | 29 | func TestSentinelServerMiddleware(t *testing.T) { 30 | bf := func(ctx context.Context, req, resp interface{}, blockErr error) error { 31 | return errors.New(FakeErrorMsg) 32 | } 33 | srv := hello.NewServer(new(HelloImpl), 34 | server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: "hello"}), 35 | server.WithMiddleware(SentinelServerMiddleware( 36 | WithBlockFallback(bf), 37 | ))) 38 | go srv.Run() 39 | defer srv.Stop() 40 | time.Sleep(1 * time.Second) 41 | 42 | c, err := hello.NewClient("hello", client.WithHostPorts(":8888")) 43 | assert.Nil(t, err) 44 | 45 | err = sentinel.InitDefault() 46 | assert.Nil(t, err) 47 | req := &api.Request{} 48 | t.Run("success", func(t *testing.T) { 49 | _, err = flow.LoadRules([]*flow.Rule{ 50 | { 51 | Resource: "hello:echo", 52 | Threshold: 1.0, 53 | TokenCalculateStrategy: flow.Direct, 54 | ControlBehavior: flow.Reject, 55 | }, 56 | }) 57 | assert.Nil(t, err) 58 | _, err := c.Echo(context.TODO(), req) 59 | assert.Nil(t, err) 60 | 61 | t.Run("second fail", func(t *testing.T) { 62 | _, err := c.Echo(context.TODO(), req) 63 | assert.Error(t, err) 64 | assert.True(t, strings.Contains(err.Error(), FakeErrorMsg)) 65 | }) 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /pkg/adapters/kratos/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/adapters/kitex 2 | 3 | go 1.18 4 | 5 | replace github.com/alibaba/sentinel-golang => ../../../ 6 | 7 | require ( 8 | github.com/alibaba/sentinel-golang v1.0.4 9 | github.com/go-kratos/kratos/v2 v2.8.0 10 | ) 11 | 12 | require ( 13 | github.com/beorn7/perks v1.0.1 // indirect 14 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 15 | github.com/go-ole/go-ole v1.2.6 // indirect 16 | github.com/go-playground/form/v4 v4.2.0 // indirect 17 | github.com/golang/protobuf v1.5.4 // indirect 18 | github.com/google/uuid v1.4.0 // indirect 19 | github.com/kr/text v0.2.0 // indirect 20 | github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect 21 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 22 | github.com/pkg/errors v0.9.1 // indirect 23 | github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect 24 | github.com/prometheus/client_golang v1.16.0 // indirect 25 | github.com/prometheus/client_model v0.3.0 // indirect 26 | github.com/prometheus/common v0.42.0 // indirect 27 | github.com/prometheus/procfs v0.10.1 // indirect 28 | github.com/shirou/gopsutil/v3 v3.23.6 // indirect 29 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 30 | github.com/tklauser/go-sysconf v0.3.11 // indirect 31 | github.com/tklauser/numcpus v0.6.1 // indirect 32 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 33 | golang.org/x/sys v0.21.0 // indirect 34 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect 35 | google.golang.org/grpc v1.61.1 // indirect 36 | google.golang.org/protobuf v1.33.0 // indirect 37 | gopkg.in/yaml.v2 v2.4.0 // indirect 38 | gopkg.in/yaml.v3 v3.0.1 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /pkg/adapters/kratos/options.go: -------------------------------------------------------------------------------- 1 | package kratos 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/go-kratos/kratos/v2/transport" 8 | ) 9 | 10 | type Option struct { 11 | F func(o *options) 12 | } 13 | 14 | type options struct { 15 | ResourceExtract func(ctx context.Context, req interface{}) string 16 | BlockFallback func(ctx context.Context, req interface{}, blockErr error) (interface{}, error) 17 | EnableOutlier func(ctx context.Context) bool 18 | } 19 | 20 | func DefaultResourceExtract(ctx context.Context, req interface{}) string { 21 | if v, ok := transport.FromClientContext(ctx); ok { 22 | return v.Operation() 23 | } 24 | panic("operation is empty") 25 | } 26 | 27 | func DefaultBlockFallback(ctx context.Context, req interface{}, blockErr error) (interface{}, error) { 28 | return nil, blockErr 29 | } 30 | 31 | func DefaultEnableOutlier(ctx context.Context) bool { 32 | return false 33 | } 34 | 35 | func newOptions(opts []Option) *options { 36 | o := &options{ 37 | ResourceExtract: DefaultResourceExtract, 38 | BlockFallback: DefaultBlockFallback, 39 | EnableOutlier: DefaultEnableOutlier, 40 | } 41 | o.Apply(opts) 42 | return o 43 | } 44 | 45 | func (o *options) Apply(opts []Option) { 46 | for _, op := range opts { 47 | op.F(o) 48 | } 49 | } 50 | 51 | // WithResourceExtract sets the resource extractor 52 | func WithResourceExtract(f func(ctx context.Context, req interface{}) string) Option { 53 | return Option{F: func(o *options) { 54 | o.ResourceExtract = f 55 | }} 56 | } 57 | 58 | // WithBlockFallback sets the fallback handler 59 | func WithBlockFallback(f func(ctx context.Context, req interface{}, blockErr error) (interface{}, error)) Option { 60 | return Option{func(o *options) { 61 | o.BlockFallback = f 62 | }} 63 | } 64 | 65 | // WithEnableOutlier sets whether to enable outlier ejection 66 | func WithEnableOutlier(f func(ctx context.Context) bool) Option { 67 | return Option{func(o *options) { 68 | o.EnableOutlier = f 69 | }} 70 | } 71 | 72 | func ServiceNameExtract(ctx context.Context) string { 73 | if v, ok := transport.FromClientContext(ctx); ok { 74 | res := v.Endpoint() 75 | if strings.HasPrefix(res, "discovery:///") { 76 | return strings.TrimPrefix(res, "discovery:///") 77 | } 78 | } 79 | panic("resource name is empty") 80 | } 81 | -------------------------------------------------------------------------------- /pkg/adapters/micro/client_test.go: -------------------------------------------------------------------------------- 1 | package micro 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "log" 7 | "testing" 8 | 9 | proto "github.com/alibaba/sentinel-golang/pkg/adapters/micro/test" 10 | 11 | sentinel "github.com/alibaba/sentinel-golang/api" 12 | "github.com/alibaba/sentinel-golang/core/base" 13 | "github.com/alibaba/sentinel-golang/core/flow" 14 | "github.com/alibaba/sentinel-golang/core/stat" 15 | "github.com/alibaba/sentinel-golang/util" 16 | "github.com/micro/go-micro/v2/client" 17 | "github.com/micro/go-micro/v2/client/selector" 18 | "github.com/micro/go-micro/v2/registry/memory" 19 | "github.com/stretchr/testify/assert" 20 | ) 21 | 22 | func TestClientLimiter(t *testing.T) { 23 | // setup 24 | r := memory.NewRegistry() 25 | s := selector.NewSelector(selector.Registry(r)) 26 | 27 | c := client.NewClient( 28 | // set the selector 29 | client.Selector(s), 30 | // add the breaker wrapper 31 | client.Wrap(NewClientWrapper( 32 | // add custom fallback function to return a fake error for assertion 33 | WithClientBlockFallback( 34 | func(ctx context.Context, request client.Request, blockError *base.BlockError) error { 35 | return errors.New(FakeErrorMsg) 36 | }), 37 | )), 38 | ) 39 | 40 | req := c.NewRequest("sentinel.test.server", "Test.Ping", &proto.Request{}, client.WithContentType("application/json")) 41 | 42 | err := sentinel.InitDefault() 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | rsp := &proto.Response{} 48 | 49 | t.Run("success", func(t *testing.T) { 50 | var _, err = flow.LoadRules([]*flow.Rule{ 51 | { 52 | Resource: req.Method(), 53 | Threshold: 1.0, 54 | TokenCalculateStrategy: flow.Direct, 55 | ControlBehavior: flow.Reject, 56 | }, 57 | }) 58 | assert.Nil(t, err) 59 | err = c.Call(context.TODO(), req, rsp) 60 | // No server started, the return err should not be nil 61 | assert.NotNil(t, err) 62 | assert.NotEqual(t, FakeErrorMsg, err.Error()) 63 | assert.True(t, util.Float64Equals(1.0, stat.GetResourceNode(req.Method()).GetQPS(base.MetricEventPass))) 64 | 65 | t.Run("second fail", func(t *testing.T) { 66 | err := c.Call(context.TODO(), req, rsp) 67 | assert.EqualError(t, err, FakeErrorMsg) 68 | assert.True(t, util.Float64Equals(1.0, stat.GetResourceNode(req.Method()).GetQPS(base.MetricEventPass))) 69 | }) 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /pkg/adapters/micro/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | This package provides Sentinel integration for go-micro. 3 | 4 | For server side, users may append a Sentinel handler wrapper to go-micro service, like: 5 | 6 | import ( 7 | sentinelPlugin "github.com/sentinel-go/pkg/adapters/micro" 8 | ) 9 | 10 | // Append a Sentinel handler wrapper. 11 | micro.NewService(micro.WrapHandler(sentinelPlugin.NewHandlerWrapper())) 12 | 13 | The plugin extracts service method as the resource name by default. 14 | Users may provide customized resource name extractor when creating new 15 | Sentinel handler wrapper (via options). 16 | 17 | Fallback logic: the plugin will return the BlockError by default 18 | if current request is blocked by Sentinel rules. Users may also 19 | provide customized fallback logic via WithXxxBlockFallback(handler) options. 20 | */ 21 | package micro 22 | -------------------------------------------------------------------------------- /pkg/adapters/micro/outlier_client.go: -------------------------------------------------------------------------------- 1 | package micro 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/micro/go-micro/v2/client" 8 | "github.com/micro/go-micro/v2/client/selector" 9 | "github.com/micro/go-micro/v2/registry" 10 | 11 | sentinel "github.com/alibaba/sentinel-golang/api" 12 | "github.com/alibaba/sentinel-golang/core/base" 13 | ) 14 | 15 | func WithSelectOption(entry *base.SentinelEntry) client.CallOption { 16 | return client.WithSelectOption(selector.WithFilter( 17 | func(old []*registry.Service) (new []*registry.Service) { 18 | filterNodes := entry.Context().FilterNodes() 19 | halfNodes := entry.Context().HalfOpenNodes() 20 | if len(halfNodes) != 0 { 21 | fmt.Println("Half Filter Pre: ", printNodes(old[0].Nodes)) 22 | new = getRemainingNodes(old, halfNodes, true) 23 | fmt.Println("Half Filter Post: ", printNodes(new[0].Nodes)) 24 | } else { 25 | fmt.Println("Filter Pre: ", printNodes(old[0].Nodes)) 26 | new = getRemainingNodes(old, filterNodes, false) 27 | fmt.Println("Filter Post: ", printNodes(new[0].Nodes)) 28 | } 29 | return new 30 | }, 31 | )) 32 | } 33 | 34 | func WithCallWrapper(entry *base.SentinelEntry) client.CallOption { 35 | return client.WithCallWrapper(func(f1 client.CallFunc) client.CallFunc { 36 | return func(ctx context.Context, node *registry.Node, req client.Request, 37 | rsp interface{}, opts client.CallOptions) error { 38 | err := f1(ctx, node, req, rsp, opts) 39 | sentinel.TraceCallee(entry, node.Address) 40 | if err != nil { 41 | sentinel.TraceError(entry, err) 42 | } 43 | return err 44 | } 45 | }) 46 | } 47 | 48 | func getRemainingNodes(old []*registry.Service, filters []string, flag bool) []*registry.Service { 49 | nodesMap := make(map[string]struct{}) 50 | for _, node := range filters { 51 | nodesMap[node] = struct{}{} 52 | } 53 | for _, service := range old { 54 | nodesCopy := make([]*registry.Node, 0) 55 | for _, ep := range service.Nodes { 56 | nodesCopy = append(nodesCopy, ep) 57 | } 58 | service.Nodes = make([]*registry.Node, 0) 59 | for _, ep := range nodesCopy { 60 | if _, ok := nodesMap[ep.Address]; ok == flag { 61 | service.Nodes = append(service.Nodes, ep) 62 | } 63 | } 64 | } 65 | return old 66 | } 67 | 68 | func printNodes(nodes []*registry.Node) (res []string) { 69 | for _, v := range nodes { 70 | res = append(res, v.Address) 71 | } 72 | return 73 | } 74 | -------------------------------------------------------------------------------- /pkg/adapters/micro/server.go: -------------------------------------------------------------------------------- 1 | package micro 2 | 3 | import ( 4 | "context" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/base" 8 | "github.com/micro/go-micro/v2/server" 9 | ) 10 | 11 | // NewHandlerWrapper returns a Handler Wrapper with Alibaba Sentinel breaker 12 | func NewHandlerWrapper(sentinelOpts ...Option) server.HandlerWrapper { 13 | return func(h server.HandlerFunc) server.HandlerFunc { 14 | return func(ctx context.Context, req server.Request, rsp interface{}) error { 15 | opts := evaluateOptions(sentinelOpts) 16 | resourceName := req.Method() 17 | if opts.serverResourceExtract != nil { 18 | resourceName = opts.serverResourceExtract(ctx, req) 19 | } 20 | entry, blockErr := sentinel.Entry( 21 | resourceName, 22 | sentinel.WithResourceType(base.ResTypeRPC), 23 | sentinel.WithTrafficType(base.Inbound), 24 | ) 25 | if blockErr != nil { 26 | if opts.serverBlockFallback != nil { 27 | return opts.serverBlockFallback(ctx, req, blockErr) 28 | } 29 | return blockErr 30 | } 31 | defer entry.Exit() 32 | err := h(ctx, req, rsp) 33 | if err != nil { 34 | sentinel.TraceError(entry, err) 35 | } 36 | return err 37 | } 38 | } 39 | } 40 | 41 | func NewStreamWrapper(sentinelOpts ...Option) server.StreamWrapper { 42 | return func(stream server.Stream) server.Stream { 43 | opts := evaluateOptions(sentinelOpts) 44 | resourceName := stream.Request().Method() 45 | if opts.serverResourceExtract != nil { 46 | resourceName = opts.streamServerResourceExtract(stream) 47 | } 48 | entry, blockErr := sentinel.Entry( 49 | resourceName, 50 | sentinel.WithResourceType(base.ResTypeRPC), 51 | sentinel.WithTrafficType(base.Inbound), 52 | ) 53 | if blockErr != nil { 54 | if opts.serverBlockFallback != nil { 55 | return opts.streamServerBlockFallback(stream, blockErr) 56 | } 57 | 58 | stream.Send(blockErr) 59 | return stream 60 | } 61 | 62 | entry.Exit() 63 | return stream 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /pkg/adapters/micro/test/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package proto; 3 | option go_package = "tests/adapter/micro/proto;proto"; 4 | 5 | service Test { 6 | rpc Ping(Request) returns (Response) {} 7 | } 8 | 9 | 10 | message Request {} 11 | 12 | message Response { 13 | string result = 1; 14 | } -------------------------------------------------------------------------------- /pkg/datasource/apollo/apollo_test.go: -------------------------------------------------------------------------------- 1 | package apollo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/apolloconfig/agollo/v4/env/config" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | // must run an apollo server before test 11 | // this would be failed for CICD test. so it's disabled for now 12 | //func TestConfig(t *testing.T) { 13 | // 14 | // var resultConfig string 15 | // 16 | // propertyHandler := datasource2.NewDefaultPropertyHandler( 17 | // func(src []byte) (interface{}, error) { 18 | // return string(src), nil 19 | // }, 20 | // func(data interface{}) error { 21 | // s := data.(string) 22 | // fmt.Println(s) 23 | // resultConfig = s 24 | // return nil 25 | // }, 26 | // ) 27 | // c := &config.AppConfig{ 28 | // AppID: "SampleApp", 29 | // Cluster: "DEV", 30 | // IP: "http://localhost:8080", 31 | // NamespaceName: "application", 32 | // IsBackupConfig: true, 33 | // Secret: "1dc9532d02cd47f0bb26ee39d614ef8d", 34 | // } 35 | // datasource, err := NewDatasource( 36 | // c, "timeout", 37 | // WithLogger(&log.DefaultLogger{}), 38 | // WithPropertyHandlers(propertyHandler), 39 | // ) 40 | // assert.Nil(t, err) 41 | // err = datasource.Initialize() 42 | // assert.Nil(t, err) 43 | // assert.Equal(t, "123", resultConfig) 44 | // select {} 45 | //} 46 | 47 | func TestEmptyKey(t *testing.T) { 48 | c := &config.AppConfig{ 49 | AppID: "SampleApp", 50 | Cluster: "DEV", 51 | IP: "http://localhost:8080", 52 | NamespaceName: "application", 53 | IsBackupConfig: true, 54 | Secret: "1dc9532d02cd47f0bb26ee39d614ef8d", 55 | } 56 | _, err := NewDatasource(c, "") 57 | assert.Equal(t, ErrEmptyKey, err) 58 | } 59 | 60 | func TestMissConfig(t *testing.T) { 61 | _, err := NewDatasource(nil, "test") 62 | assert.Equal(t, ErrMissConfig, err) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/datasource/apollo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/datasource/apollo 2 | 3 | go 1.18 4 | 5 | replace github.com/alibaba/sentinel-golang => ../../../ 6 | 7 | require ( 8 | github.com/alibaba/sentinel-golang v1.0.4 9 | github.com/apolloconfig/agollo/v4 v4.0.9 10 | github.com/pkg/errors v0.9.1 11 | github.com/stretchr/testify v1.8.0 12 | ) 13 | 14 | require ( 15 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 16 | github.com/beorn7/perks v1.0.1 // indirect 17 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 18 | github.com/davecgh/go-spew v1.1.1 // indirect 19 | github.com/fsnotify/fsnotify v1.5.1 // indirect 20 | github.com/go-ole/go-ole v1.2.4 // indirect 21 | github.com/golang/protobuf v1.5.3 // indirect 22 | github.com/google/uuid v1.1.2 // indirect 23 | github.com/hashicorp/hcl v1.0.0 // indirect 24 | github.com/magiconair/properties v1.8.5 // indirect 25 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 26 | github.com/mitchellh/mapstructure v1.4.2 // indirect 27 | github.com/pelletier/go-toml v1.9.4 // indirect 28 | github.com/pmezard/go-difflib v1.0.0 // indirect 29 | github.com/prometheus/client_golang v1.16.0 // indirect 30 | github.com/prometheus/client_model v0.3.0 // indirect 31 | github.com/prometheus/common v0.42.0 // indirect 32 | github.com/prometheus/procfs v0.10.1 // indirect 33 | github.com/shirou/gopsutil/v3 v3.21.6 // indirect 34 | github.com/spf13/afero v1.6.0 // indirect 35 | github.com/spf13/cast v1.4.1 // indirect 36 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 37 | github.com/spf13/pflag v1.0.5 // indirect 38 | github.com/spf13/viper v1.9.0 // indirect 39 | github.com/stretchr/objx v0.4.0 // indirect 40 | github.com/subosito/gotenv v1.2.0 // indirect 41 | github.com/tklauser/go-sysconf v0.3.6 // indirect 42 | github.com/tklauser/numcpus v0.2.2 // indirect 43 | go.uber.org/atomic v1.7.0 // indirect 44 | go.uber.org/multierr v1.6.0 // indirect 45 | golang.org/x/sys v0.21.0 // indirect 46 | golang.org/x/text v0.7.0 // indirect 47 | google.golang.org/protobuf v1.30.0 // indirect 48 | gopkg.in/ini.v1 v1.64.0 // indirect 49 | gopkg.in/yaml.v2 v2.4.0 // indirect 50 | gopkg.in/yaml.v3 v3.0.1 // indirect 51 | ) 52 | -------------------------------------------------------------------------------- /pkg/datasource/apollo/listener.go: -------------------------------------------------------------------------------- 1 | package apollo 2 | 3 | import ( 4 | "github.com/apolloconfig/agollo/v4/storage" 5 | ) 6 | 7 | type customChangeListener struct { 8 | ds *apolloDatasource 9 | } 10 | 11 | func (c *customChangeListener) OnChange(event *storage.ChangeEvent) { 12 | for key, value := range event.Changes { 13 | if c.ds.propertyKey == key { 14 | c.ds.handle([]byte(value.NewValue.(string))) 15 | } 16 | } 17 | } 18 | 19 | func (c *customChangeListener) OnNewestChange(event *storage.FullChangeEvent) { 20 | for key, value := range event.Changes { 21 | if c.ds.propertyKey == key { 22 | c.ds.handle([]byte(value.(string))) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/datasource/consul/consul_example_test.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/hashicorp/consul/api" 9 | ) 10 | 11 | func Example_consulDataSource_CustomizeClient() { 12 | client, err := api.NewClient(&api.Config{ 13 | Address: "127.0.0.1:8500", 14 | }) 15 | if err != nil { 16 | fmt.Println("Failed to instance consul client") 17 | os.Exit(1) 18 | } 19 | ds, err := NewDataSource("property_key", 20 | // customize consul client 21 | WithConsulClient(client), 22 | // preset property handlers 23 | WithPropertyHandlers(), 24 | // reset queryOptions, defaultQueryOptions as default 25 | WithQueryOptions(&api.QueryOptions{}), 26 | ) 27 | 28 | if err != nil { 29 | fmt.Println("Failed to instance consul datasource") 30 | os.Exit(1) 31 | } 32 | 33 | if err := ds.Initialize(); err != nil { 34 | fmt.Println("Failed to initialize consul datasource") 35 | os.Exit(1) 36 | } 37 | } 38 | 39 | func Example_consulDataSource_CustomizeConfig() { 40 | ds, err := NewDataSource("property_key", 41 | // customize consul config 42 | WithConsulConfig(&api.Config{ 43 | Address: "127.0.0.1:8500", 44 | }), 45 | // preset property handlers 46 | WithPropertyHandlers(), 47 | // reset queryOptions, defaultQueryOptions as default 48 | WithQueryOptions(&api.QueryOptions{ 49 | WaitIndex: 0, 50 | // override default WaitTime(5min) 51 | WaitTime: time.Second * 90, 52 | }), 53 | ) 54 | 55 | if err != nil { 56 | fmt.Println("Failed to instance consul datasource") 57 | os.Exit(1) 58 | } 59 | 60 | if err := ds.Initialize(); err != nil { 61 | fmt.Println("Failed to initialize consul datasource") 62 | os.Exit(1) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pkg/datasource/consul/doc.go: -------------------------------------------------------------------------------- 1 | // This package provides Consul dynamic data-source implementation for Sentinel. 2 | package consul 3 | -------------------------------------------------------------------------------- /pkg/datasource/consul/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/datasource/consul 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/alibaba/sentinel-golang v1.0.4 7 | github.com/hashicorp/consul/api v1.4.0 8 | github.com/stretchr/testify v1.6.1 9 | ) 10 | 11 | require ( 12 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 13 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect 14 | github.com/beorn7/perks v1.0.1 // indirect 15 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/fatih/color v1.9.0 // indirect 18 | github.com/go-ole/go-ole v1.2.5 // indirect 19 | github.com/golang/protobuf v1.4.3 // indirect 20 | github.com/google/uuid v1.1.1 // indirect 21 | github.com/hashicorp/go-cleanhttp v0.5.1 // indirect 22 | github.com/hashicorp/go-hclog v0.12.0 // indirect 23 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 24 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 25 | github.com/hashicorp/golang-lru v0.5.1 // indirect 26 | github.com/hashicorp/serf v0.8.2 // indirect 27 | github.com/mattn/go-colorable v0.1.4 // indirect 28 | github.com/mattn/go-isatty v0.0.12 // indirect 29 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 30 | github.com/mitchellh/go-homedir v1.1.0 // indirect 31 | github.com/mitchellh/mapstructure v1.1.2 // indirect 32 | github.com/pkg/errors v0.9.1 // indirect 33 | github.com/pmezard/go-difflib v1.0.0 // indirect 34 | github.com/prometheus/client_golang v1.9.0 // indirect 35 | github.com/prometheus/client_model v0.2.0 // indirect 36 | github.com/prometheus/common v0.15.0 // indirect 37 | github.com/prometheus/procfs v0.2.0 // indirect 38 | github.com/shirou/gopsutil/v3 v3.21.6 // indirect 39 | github.com/stretchr/objx v0.1.1 // indirect 40 | github.com/tklauser/go-sysconf v0.3.6 // indirect 41 | github.com/tklauser/numcpus v0.2.2 // indirect 42 | go.uber.org/atomic v1.6.0 // indirect 43 | go.uber.org/multierr v1.5.0 // indirect 44 | golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa // indirect 45 | google.golang.org/protobuf v1.23.0 // indirect 46 | gopkg.in/yaml.v2 v2.3.0 // indirect 47 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 48 | ) 49 | -------------------------------------------------------------------------------- /pkg/datasource/etcdv3/doc.go: -------------------------------------------------------------------------------- 1 | // This package provides etcd v3 dynamic data-source implementation for Sentinel. 2 | package etcdv3 3 | -------------------------------------------------------------------------------- /pkg/datasource/etcdv3/etcdv3_example.go: -------------------------------------------------------------------------------- 1 | package etcdv3 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/alibaba/sentinel-golang/ext/datasource" 9 | "github.com/coreos/etcd/clientv3" 10 | "github.com/stretchr/testify/mock" 11 | ) 12 | 13 | // New one datasource based on etcv3 client 14 | func Example_ClientWithOneDatasource() { 15 | cli, err := clientv3.New(clientv3.Config{ 16 | Endpoints: []string{"localhost:2379"}, 17 | DialTimeout: 10 * time.Second, 18 | }) 19 | if err != nil { 20 | log.Fatal(err) 21 | return 22 | } 23 | defer cli.Close() 24 | 25 | h := datasource.MockPropertyHandler{} 26 | h.On("isPropertyConsistent", mock.Anything).Return(true) 27 | h.On("Handle", mock.Anything).Return(nil) 28 | ds, err := NewDataSource(cli, "foo", &h) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | err = ds.Initialize() 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | time.Sleep(30 * time.Second) 37 | fmt.Println("Prepare to close") 38 | ds.Close() 39 | time.Sleep(120 * time.Second) 40 | } 41 | 42 | // New multi datasource based on etcv3 client 43 | func Example_ClientWithMultiDatasource() { 44 | cli, err := clientv3.New(clientv3.Config{ 45 | Endpoints: []string{"localhost:2379"}, 46 | DialTimeout: 10 * time.Second, 47 | }) 48 | if err != nil { 49 | log.Fatal("client error:", err) 50 | } 51 | defer cli.Close() 52 | 53 | h := datasource.MockPropertyHandler{} 54 | h.On("isPropertyConsistent", mock.Anything).Return(true) 55 | h.On("Handle", mock.Anything).Return(nil) 56 | ds1, err := NewDataSource(cli, "foo", &h) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | 61 | ds2, err := NewDataSource(cli, "aoo", &h) 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | 66 | err = ds1.Initialize() 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | err = ds2.Initialize() 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | time.Sleep(60 * time.Second) 76 | fmt.Println("Prepare to close ds1") 77 | // close ds1, will not recv the watch response 78 | ds1.Close() 79 | 80 | // ds2 also could recv the watch response 81 | time.Sleep(80 * time.Second) 82 | ds2.Close() 83 | 84 | time.Sleep(100 * time.Second) 85 | } 86 | -------------------------------------------------------------------------------- /pkg/datasource/etcdv3/etcdv3_test.go: -------------------------------------------------------------------------------- 1 | package etcdv3 2 | 3 | // 4 | //import "testing" 5 | // 6 | //func Test_ClientWithOneDatasource(t *testing.T) { 7 | // Example_ClientWithOneDatasource() 8 | //} 9 | // 10 | //func Test_ClientWithMultiDatasource(t *testing.T) { 11 | // Example_ClientWithMultiDatasource() 12 | //} 13 | -------------------------------------------------------------------------------- /pkg/datasource/k8s/Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Image URL to use all building/pushing image targets 3 | IMG ?= controller:latest 4 | # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) 5 | CRD_OPTIONS ?= "crd:trivialVersions=true" 6 | 7 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 8 | ifeq (,$(shell go env GOBIN)) 9 | GOBIN=$(shell go env GOPATH)/bin 10 | else 11 | GOBIN=$(shell go env GOBIN) 12 | endif 13 | 14 | # Run tests 15 | test: generate fmt vet manifests 16 | go test ./... -coverprofile cover.out 17 | 18 | # Generate manifests e.g. CRD, RBAC etc. 19 | manifests: controller-gen 20 | $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases 21 | 22 | # Run go fmt against code 23 | fmt: 24 | go fmt ./... 25 | 26 | # Run go vet against code 27 | vet: 28 | go vet ./... 29 | 30 | # Generate code 31 | generate: controller-gen 32 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 33 | 34 | # find or download controller-gen 35 | # download controller-gen if necessary 36 | controller-gen: 37 | ifeq (, $(shell which controller-gen)) 38 | @{ \ 39 | set -e ;\ 40 | CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ 41 | cd $$CONTROLLER_GEN_TMP_DIR ;\ 42 | go mod init tmp ;\ 43 | go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.5 ;\ 44 | rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ 45 | } 46 | CONTROLLER_GEN=$(GOBIN)/controller-gen 47 | else 48 | CONTROLLER_GEN=$(shell which controller-gen) 49 | endif 50 | -------------------------------------------------------------------------------- /pkg/datasource/k8s/PROJECT: -------------------------------------------------------------------------------- 1 | domain: sentinel.io 2 | repo: sentinel-go-k8s-crd-datasource 3 | resources: 4 | - group: datasource 5 | kind: CircuitBreakerRules 6 | version: v1alpha1 7 | - group: datasource 8 | kind: FlowRules 9 | version: v1alpha1 10 | - group: datasource 11 | kind: HotspotRules 12 | version: v1alpha1 13 | - group: datasource 14 | kind: SystemRules 15 | version: v1alpha1 16 | - group: datasource 17 | kind: IsolationRules 18 | version: v1alpha1 19 | version: "2" 20 | -------------------------------------------------------------------------------- /pkg/datasource/k8s/README.md: -------------------------------------------------------------------------------- 1 | # Sentinel Go Kubernetes CRD data-source 2 | 3 | Install Sentinel rule CRDs: 4 | 5 | ```shell 6 | kubectl apply -f config/crd/bases 7 | ``` 8 | 9 | ## Samples 10 | 11 | - [CRD YAML samples](./config/samples) -------------------------------------------------------------------------------- /pkg/datasource/k8s/api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains API Schema definitions for the datasource v1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=datasource.sentinel.io 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "datasource.sentinel.io", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /pkg/datasource/k8s/config/samples/datasource_v1alpha1_circuitbreakerrules.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datasource.sentinel.io/v1alpha1 2 | kind: CircuitBreakerRules 3 | metadata: 4 | name: sentinel-go-circuitbreaker-rules 5 | spec: 6 | rules: 7 | - resource: test1 8 | strategy: SlowRequestRatio 9 | statIntervalMs: 10000 10 | retryTimeoutMs: 500 11 | minRequestAmount: 10 12 | maxAllowedRtMs: 100 13 | threshold: 10 14 | - resource: test1 15 | strategy: ErrorRatio 16 | statIntervalMs: 10000 17 | retryTimeoutMs: 500 18 | minRequestAmount: 10 19 | threshold: 20 20 | - id: id3 21 | resource: test1 22 | strategy: ErrorCount 23 | statIntervalMs: 10000 24 | retryTimeoutMs: 500 25 | minRequestAmount: 10 26 | threshold: 20 -------------------------------------------------------------------------------- /pkg/datasource/k8s/config/samples/datasource_v1alpha1_flowrules.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datasource.sentinel.io/v1alpha1 2 | kind: FlowRules 3 | metadata: 4 | name: sentinel-go-flow-rules 5 | spec: 6 | rules: 7 | - resource: simple-resource 8 | threshold: 500 9 | - resource: abcde 10 | threshold: 100 11 | relationStrategy: CurrentResource 12 | controlBehavior: Throttling 13 | maxQueueingTimeMs: 500 14 | - resource: abcd 15 | threshold: 200 16 | relationStrategy: CurrentResource 17 | tokenCalculateStrategy: WarmUp 18 | controlBehavior: Reject 19 | warmUpPeriodSec: 20 20 | warmUpColdFactor: 3 21 | 22 | -------------------------------------------------------------------------------- /pkg/datasource/k8s/config/samples/datasource_v1alpha1_hotspotrules.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datasource.sentinel.io/v1alpha1 2 | kind: HotspotRules 3 | metadata: 4 | name: sentinel-go-hotspot-rules 5 | spec: 6 | rules: 7 | - resource: test1 8 | metricType: Concurrency 9 | controlBehavior: Reject 10 | paramIndex: 0 11 | threshold: 100 12 | burstCount: 20 13 | durationInSec: 10 14 | paramsMaxCapacity: 10000 15 | specificItems: 16 | - valKind: KindInt 17 | valStr: "100" 18 | threshold: 101 19 | - valKind: KindInt 20 | valStr: "200" 21 | threshold: 102 22 | - resource: test1 23 | metricType: QPS 24 | controlBehavior: Throttling 25 | paramIndex: 1 26 | threshold: 200 27 | maxQueueingTimeMs: 100 28 | paramsMaxCapacity: 20000 29 | specificItems: 30 | - valKind: KindString 31 | valStr: ximu1 32 | threshold: 201 33 | - valKind: KindString 34 | valStr: ximu2 35 | threshold: 202 36 | -------------------------------------------------------------------------------- /pkg/datasource/k8s/config/samples/datasource_v1alpha1_isolationrules.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datasource.sentinel.io/v1alpha1 2 | kind: IsolationRules 3 | metadata: 4 | name: isolationrules-sample 5 | spec: 6 | rules: 7 | - resource: test1 8 | threshold: 100 9 | - resource: test2 10 | threshold: 200 11 | -------------------------------------------------------------------------------- /pkg/datasource/k8s/config/samples/datasource_v1alpha1_systemrules.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: datasource.sentinel.io/v1alpha1 2 | kind: SystemRules 3 | metadata: 4 | name: sentinel-go-system-rules 5 | spec: 6 | # Add fields here 7 | rules: 8 | - metricType: AvgRT 9 | strategy: NoAdaptive 10 | triggerCount: 123 11 | - metricType: Concurrency 12 | strategy: NoAdaptive 13 | triggerCount: 100 -------------------------------------------------------------------------------- /pkg/datasource/k8s/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/datasource/k8s 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 7 | github.com/alibaba/sentinel-golang v1.0.2 8 | github.com/go-logr/logr v0.1.0 9 | github.com/go-ole/go-ole v1.2.4 // indirect 10 | github.com/pkg/errors v0.9.1 11 | github.com/stretchr/testify v1.5.1 12 | k8s.io/apimachinery v0.17.2 13 | k8s.io/client-go v0.17.2 14 | sigs.k8s.io/controller-runtime v0.5.0 15 | ) 16 | -------------------------------------------------------------------------------- /pkg/datasource/nacos/doc.go: -------------------------------------------------------------------------------- 1 | // This package provides Nacos dynamic data-source implementation for Sentinel. 2 | package nacos 3 | -------------------------------------------------------------------------------- /pkg/datasource/nacos/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alibaba/sentinel-golang/pkg/datasource/nacos 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/alibaba/sentinel-golang v1.0.4 7 | github.com/nacos-group/nacos-sdk-go v1.0.8 8 | github.com/pkg/errors v0.9.1 9 | github.com/stretchr/testify v1.6.1 10 | ) 11 | 12 | require ( 13 | github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect 14 | github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 // indirect 15 | github.com/beorn7/perks v1.0.1 // indirect 16 | github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect 17 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 18 | github.com/davecgh/go-spew v1.1.1 // indirect 19 | github.com/go-errors/errors v1.0.1 // indirect 20 | github.com/go-ole/go-ole v1.2.5 // indirect 21 | github.com/golang/protobuf v1.4.3 // indirect 22 | github.com/google/uuid v1.1.1 // indirect 23 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect 24 | github.com/json-iterator/go v1.1.10 // indirect 25 | github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect 26 | github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect 27 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.1 // indirect 30 | github.com/pmezard/go-difflib v1.0.0 // indirect 31 | github.com/prometheus/client_golang v1.9.0 // indirect 32 | github.com/prometheus/client_model v0.2.0 // indirect 33 | github.com/prometheus/common v0.15.0 // indirect 34 | github.com/prometheus/procfs v0.2.0 // indirect 35 | github.com/shirou/gopsutil/v3 v3.21.6 // indirect 36 | github.com/stretchr/objx v0.1.1 // indirect 37 | github.com/tklauser/go-sysconf v0.3.6 // indirect 38 | github.com/tklauser/numcpus v0.2.2 // indirect 39 | github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect 40 | go.uber.org/atomic v1.6.0 // indirect 41 | go.uber.org/multierr v1.5.0 // indirect 42 | go.uber.org/zap v1.15.0 // indirect 43 | golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa // indirect 44 | google.golang.org/protobuf v1.23.0 // indirect 45 | gopkg.in/ini.v1 v1.42.0 // indirect 46 | gopkg.in/yaml.v2 v2.3.0 // indirect 47 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 48 | ) 49 | -------------------------------------------------------------------------------- /tests/benchmark/env_init.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package benchmark 16 | 17 | import ( 18 | "log" 19 | 20 | sentinel "github.com/alibaba/sentinel-golang/api" 21 | "github.com/alibaba/sentinel-golang/core/config" 22 | "github.com/alibaba/sentinel-golang/logging" 23 | ) 24 | 25 | func InitSentinel() { 26 | // We should initialize Sentinel first. 27 | conf := config.NewDefaultConfig() 28 | // for testing, logging output to console 29 | conf.Sentinel.Log.Logger = logging.NewConsoleLogger() 30 | conf.Sentinel.Log.Metric.FlushIntervalSec = 0 31 | conf.Sentinel.Stat.System.CollectIntervalMs = 0 32 | conf.Sentinel.Stat.System.CollectMemoryIntervalMs = 0 33 | conf.Sentinel.Stat.System.CollectCpuIntervalMs = 0 34 | conf.Sentinel.Stat.System.CollectLoadIntervalMs = 0 35 | err := sentinel.InitWithConfig(conf) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/benchmark/memory/memory_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package memory 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | "strconv" 21 | "sync" 22 | "testing" 23 | 24 | sentinel "github.com/alibaba/sentinel-golang/api" 25 | "github.com/alibaba/sentinel-golang/core/base" 26 | ) 27 | 28 | func doSomethingWithSentinelWithResource(res string) { 29 | e, b := sentinel.Entry(res, sentinel.WithTrafficType(base.Inbound)) 30 | if b != nil { 31 | fmt.Println("Blocked") 32 | } else { 33 | e.Exit() 34 | } 35 | } 36 | 37 | func TestMemory_Single(t *testing.T) { 38 | err := sentinel.InitDefault() 39 | if err != nil { 40 | log.Fatalf("Unexpected error: %+v", err) 41 | } 42 | i := 0 43 | for { 44 | i++ 45 | for i := 0; i < 6000; i++ { 46 | doSomethingWithSentinelWithResource(strconv.Itoa(i)) 47 | } 48 | if i == 1 { 49 | break 50 | } 51 | } 52 | } 53 | 54 | func TestMemory_Concurrency(t *testing.T) { 55 | err := sentinel.InitDefault() 56 | if err != nil { 57 | log.Fatalf("Unexpected error: %+v", err) 58 | } 59 | 60 | // prepare statistic structure 61 | for i := 0; i < 6000; i++ { 62 | doSomethingWithSentinelWithResource(strconv.Itoa(i)) 63 | } 64 | 65 | wg := &sync.WaitGroup{} 66 | wg.Add(8) 67 | for i := 0; i < 8; i++ { 68 | go func() { 69 | c := 0 70 | for { 71 | c++ 72 | for j := 0; j < 6000; j++ { 73 | doSomethingWithSentinelWithResource(strconv.Itoa(j)) 74 | } 75 | if c == 1 { 76 | break 77 | } 78 | } 79 | wg.Done() 80 | }() 81 | } 82 | wg.Wait() 83 | } 84 | -------------------------------------------------------------------------------- /tests/testdata/config/sentinel.yml: -------------------------------------------------------------------------------- 1 | version: "v1" 2 | sentinel: 3 | app: 4 | name: "sentinel-go-demo" 5 | type: 0 6 | log: 7 | dir: "sentinel.yml" 8 | usePid: false 9 | metric: 10 | singleFileMaxSize: 52428800 11 | maxFileCount: 7 12 | flushIntervalSec: 1 -------------------------------------------------------------------------------- /tests/testdata/config/sentinel.yml.2: -------------------------------------------------------------------------------- 1 | version: "v1" 2 | sentinel: 3 | app: 4 | name: "sentinel-go-demo" 5 | type: 0 6 | log: 7 | dir: "sentinel.yml" 8 | usePid: false 9 | metric: 10 | singleFileMaxSize: 52428800 11 | maxFileCount: 7 12 | flushIntervalSec: 1 -------------------------------------------------------------------------------- /tests/testdata/extension/SystemRule.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "0", 4 | "metricType": 0, 5 | "adaptiveStrategy": 0 6 | }, 7 | { 8 | "id": "1", 9 | "metricType": 0, 10 | "adaptiveStrategy": 0 11 | }, 12 | { 13 | "id": "2", 14 | "metricType": 0, 15 | "adaptiveStrategy": 0 16 | } 17 | ] -------------------------------------------------------------------------------- /tests/testdata/extension/SystemRule2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "0", 4 | "metricType": 0, 5 | "adaptiveStrategy": 0 6 | }, 7 | { 8 | "id": "1", 9 | "metricType": 0, 10 | "adaptiveStrategy": 0 11 | }, 12 | { 13 | "id": "2", 14 | "metricType": 0, 15 | "adaptiveStrategy": 0 16 | } 17 | ] -------------------------------------------------------------------------------- /tests/testdata/extension/SystemRule3.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "0", 4 | "metricType": 0, 5 | "adaptiveStrategy": 0 6 | }, 7 | { 8 | "id": "1", 9 | "metricType": 0, 10 | "adaptiveStrategy": 0 11 | } 12 | ] -------------------------------------------------------------------------------- /tests/testdata/extension/helper/CircuitBreakerRule.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "resource": "abc", 4 | "strategy": 0, 5 | "retryTimeoutMs": 10, 6 | "minRequestAmount": 10, 7 | "statIntervalMs": 1000, 8 | "maxAllowedRtMs": 100, 9 | "threshold": 0.1 10 | }, 11 | { 12 | "resource": "abc", 13 | "strategy": 1, 14 | "retryTimeoutMs": 20, 15 | "minRequestAmount": 20, 16 | "statIntervalMs": 2000, 17 | "threshold": 0.2 18 | }, 19 | { 20 | "resource": "abc", 21 | "strategy": 2, 22 | "retryTimeoutMs": 30, 23 | "minRequestAmount": 30, 24 | "statIntervalMs": 3000, 25 | "threshold": 30 26 | } 27 | ] -------------------------------------------------------------------------------- /tests/testdata/extension/helper/FlowRule.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "resource": "abc", 4 | "threshold": 100.0, 5 | "relationStrategy": 0, 6 | "tokenCalculateStrategy": 0, 7 | "controlBehavior": 0, 8 | "refResource": "refDefault", 9 | "warmUpPeriodSec": 10, 10 | "maxQueueingTimeMs": 1000, 11 | "statIntervalInMs": 0 12 | }, 13 | { 14 | "resource": "abc", 15 | "threshold": 200.0, 16 | "relationStrategy": 1, 17 | "tokenCalculateStrategy": 0, 18 | "controlBehavior": 1, 19 | "refResource": "refDefault", 20 | "warmUpPeriodSec": 20, 21 | "maxQueueingTimeMs": 2000, 22 | "statIntervalInMs": 0 23 | }, 24 | { 25 | "resource": "abc", 26 | "threshold": 300.0, 27 | "relationStrategy": 0, 28 | "tokenCalculateStrategy": 0, 29 | "controlBehavior": 1, 30 | "refResource": "refDefault", 31 | "warmUpPeriodSec": 30, 32 | "maxQueueingTimeMs": 3000, 33 | "statIntervalInMs": 0 34 | } 35 | ] -------------------------------------------------------------------------------- /tests/testdata/extension/helper/IsolationRule.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Resource": "abc", 4 | "MetricType": 0, 5 | "Threshold": 100 6 | }, 7 | { 8 | "Resource": "abc", 9 | "MetricType": 0, 10 | "Threshold": 90 11 | }, 12 | { 13 | "Resource": "abc", 14 | "MetricType": 0, 15 | "Threshold": 80 16 | }, 17 | { 18 | "Resource": "abc", 19 | "MetricType": 0, 20 | "Threshold": 70 21 | } 22 | ] -------------------------------------------------------------------------------- /tests/testdata/extension/helper/SystemRule.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "metricType": 0, 4 | "triggerCount": 0.5, 5 | "strategy": 1 6 | }, 7 | { 8 | "metricType": 1, 9 | "triggerCount": 0.6, 10 | "strategy": 1 11 | }, 12 | { 13 | "metricType": 2, 14 | "triggerCount": 0.7, 15 | "strategy": 1 16 | }, 17 | { 18 | "metricType": 3, 19 | "triggerCount": 0.8, 20 | "strategy": 1 21 | } 22 | ] -------------------------------------------------------------------------------- /tests/testdata/metric/app1-metrics.log.2020-02-14: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/sentinel-golang/368e01c4a4a2f7b2a847aedfacf35f229745eef5/tests/testdata/metric/app1-metrics.log.2020-02-14 -------------------------------------------------------------------------------- /tests/testdata/metric/app1-metrics.log.2020-02-14.12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/sentinel-golang/368e01c4a4a2f7b2a847aedfacf35f229745eef5/tests/testdata/metric/app1-metrics.log.2020-02-14.12 -------------------------------------------------------------------------------- /tests/testdata/metric/app1-metrics.log.2020-02-14.32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/sentinel-golang/368e01c4a4a2f7b2a847aedfacf35f229745eef5/tests/testdata/metric/app1-metrics.log.2020-02-14.32 -------------------------------------------------------------------------------- /tests/testdata/metric/app1-metrics.log.2020-02-14.idx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/sentinel-golang/368e01c4a4a2f7b2a847aedfacf35f229745eef5/tests/testdata/metric/app1-metrics.log.2020-02-14.idx -------------------------------------------------------------------------------- /tests/testdata/metric/app1-metrics.log.2020-02-15: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/sentinel-golang/368e01c4a4a2f7b2a847aedfacf35f229745eef5/tests/testdata/metric/app1-metrics.log.2020-02-15 -------------------------------------------------------------------------------- /tests/testdata/metric/app1-metrics.log.2020-02-16: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/sentinel-golang/368e01c4a4a2f7b2a847aedfacf35f229745eef5/tests/testdata/metric/app1-metrics.log.2020-02-16 -------------------------------------------------------------------------------- /tests/testdata/metric/app1-metrics.log.2020-02-16.100: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/sentinel-golang/368e01c4a4a2f7b2a847aedfacf35f229745eef5/tests/testdata/metric/app1-metrics.log.2020-02-16.100 -------------------------------------------------------------------------------- /util/atomic.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import "sync/atomic" 18 | 19 | type AtomicBool struct { 20 | // default 0, means false 21 | flag int32 22 | } 23 | 24 | func (b *AtomicBool) CompareAndSet(old, new bool) bool { 25 | if old == new { 26 | return true 27 | } 28 | var oldInt, newInt int32 29 | if old { 30 | oldInt = 1 31 | } 32 | if new { 33 | newInt = 1 34 | } 35 | return atomic.CompareAndSwapInt32(&(b.flag), oldInt, newInt) 36 | } 37 | 38 | func (b *AtomicBool) Set(value bool) { 39 | i := int32(0) 40 | if value { 41 | i = 1 42 | } 43 | atomic.StoreInt32(&(b.flag), int32(i)) 44 | } 45 | 46 | func (b *AtomicBool) Get() bool { 47 | if atomic.LoadInt32(&(b.flag)) != 0 { 48 | return true 49 | } 50 | return false 51 | } 52 | -------------------------------------------------------------------------------- /util/atomic_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestAtomicBool_CompareAndSet(t *testing.T) { 24 | b := &AtomicBool{} 25 | b.Set(true) 26 | ok := b.CompareAndSet(true, false) 27 | assert.True(t, ok, "CompareAndSet execute failed.") 28 | b.Set(false) 29 | ok = b.CompareAndSet(true, false) 30 | assert.True(t, !ok, "CompareAndSet execute failed.") 31 | } 32 | 33 | func TestAtomicBool_GetAndSet(t *testing.T) { 34 | b := &AtomicBool{} 35 | assert.True(t, b.Get() == false, "default value is not false.") 36 | b.Set(true) 37 | assert.True(t, b.Get() == true, "the value is false, expect true.") 38 | } 39 | -------------------------------------------------------------------------------- /util/auto_recover.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "github.com/alibaba/sentinel-golang/logging" 19 | "github.com/pkg/errors" 20 | ) 21 | 22 | func RunWithRecover(f func()) { 23 | defer func() { 24 | if err := recover(); err != nil { 25 | logging.Error(errors.Errorf("%+v", err), "Unexpected panic in util.RunWithRecover()") 26 | } 27 | }() 28 | f() 29 | } 30 | -------------------------------------------------------------------------------- /util/file.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "errors" 19 | "io" 20 | "os" 21 | ) 22 | 23 | func FilePosition(file *os.File) (int64, error) { 24 | if file == nil { 25 | return 0, errors.New("null fd when retrieving file position") 26 | } 27 | return file.Seek(0, io.SeekCurrent) 28 | } 29 | 30 | func FileExists(name string) (b bool, err error) { 31 | if _, err := os.Stat(name); err != nil { 32 | if os.IsNotExist(err) { 33 | return false, nil 34 | } 35 | } 36 | // Propagates the error if the error is not FileNotExist error. 37 | return true, err 38 | } 39 | 40 | func CreateDirIfNotExists(dirname string) error { 41 | if _, err := os.Stat(dirname); err != nil { 42 | if os.IsNotExist(err) { 43 | return os.MkdirAll(dirname, os.ModePerm) 44 | } else { 45 | return err 46 | } 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /util/file_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestFileExists(t *testing.T) { 22 | type args struct { 23 | name string 24 | } 25 | tests := []struct { 26 | name string 27 | args args 28 | wantB bool 29 | wantErr bool 30 | }{ 31 | { 32 | "file.go", args{"file.go"}, true, false, 33 | }, 34 | { 35 | "file.gox", args{"file.gox"}, false, false, 36 | }, 37 | { 38 | "empty", args{""}, false, false, 39 | }, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | gotB, err := FileExists(tt.args.name) 44 | if (err != nil) != tt.wantErr { 45 | t.Errorf("FileExists() error = %v, wantErr %v", err, tt.wantErr) 46 | return 47 | } 48 | if gotB != tt.wantB { 49 | t.Errorf("FileExists() = %v, want %v", gotB, tt.wantB) 50 | } 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /util/math.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import "math" 18 | 19 | const precision = 0.00000001 20 | 21 | func Float64Equals(x, y float64) bool { 22 | return math.Abs(x-y) < precision 23 | } 24 | -------------------------------------------------------------------------------- /util/math_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestFloat64Equals(t *testing.T) { 24 | assert.True(t, Float64Equals(0.1, 0.099999999)) 25 | assert.False(t, Float64Equals(0.1, 0.09999999)) 26 | } 27 | -------------------------------------------------------------------------------- /util/safe.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import "unsafe" 18 | 19 | // SliceHeader is a safe version of SliceHeader used within this project. 20 | type SliceHeader struct { 21 | Data unsafe.Pointer 22 | Len int 23 | Cap int 24 | } 25 | 26 | // StringHeader is a safe version of StringHeader used within this project. 27 | type StringHeader struct { 28 | Data unsafe.Pointer 29 | Len int 30 | } 31 | -------------------------------------------------------------------------------- /util/string.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import "strings" 18 | 19 | // IsBlank checks whether the given string is blank. 20 | func IsBlank(s string) bool { 21 | return strings.TrimSpace(s) == "" 22 | } 23 | -------------------------------------------------------------------------------- /util/string_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import "testing" 18 | 19 | func TestIsBlank(t *testing.T) { 20 | type args struct { 21 | s string 22 | } 23 | tests := []struct { 24 | name string 25 | args args 26 | want bool 27 | }{ 28 | { 29 | "blank1", args{""}, true, 30 | }, 31 | { 32 | "blank2", args{" "}, true, 33 | }, 34 | { 35 | "blank3", args{" "}, true, 36 | }, 37 | } 38 | for _, tt := range tests { 39 | t.Run(tt.name, func(t *testing.T) { 40 | if got := IsBlank(tt.args.s); got != tt.want { 41 | t.Errorf("IsBlank() = %v, want %v", got, tt.want) 42 | } 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /util/time_ticker.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "sync/atomic" 19 | "time" 20 | ) 21 | 22 | var nowInMs = uint64(0) 23 | 24 | // StartTimeTicker starts a background task that caches current timestamp per millisecond, 25 | // which may provide better performance in high-concurrency scenarios. 26 | func StartTimeTicker() { 27 | atomic.StoreUint64(&nowInMs, uint64(time.Now().UnixNano())/UnixTimeUnitOffset) 28 | go func() { 29 | for { 30 | now := uint64(time.Now().UnixNano()) / UnixTimeUnitOffset 31 | atomic.StoreUint64(&nowInMs, now) 32 | time.Sleep(time.Millisecond) 33 | } 34 | }() 35 | } 36 | 37 | func CurrentTimeMillsWithTicker() uint64 { 38 | return atomic.LoadUint64(&nowInMs) 39 | } 40 | -------------------------------------------------------------------------------- /util/uuid.go: -------------------------------------------------------------------------------- 1 | // Copyright 1999-2020 Alibaba Group Holding Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "github.com/google/uuid" 19 | ) 20 | 21 | func NewUuid() string { 22 | return uuid.New().String() 23 | } 24 | --------------------------------------------------------------------------------