Переглянути джерело

Welcome to the world, Sentinel

master
Eric Zhao 6 роки тому
коміт
c92fea5d46
100 змінених файлів з 7934 додано та 0 видалено
  1. +16
    -0
      .circleci/config.yml
  2. +28
    -0
      .github/ISSUE_TEMPLATE.md
  3. +19
    -0
      .github/PULL_REQUEST_TEMPLATE.md
  4. +27
    -0
      .gitignore
  5. +10
    -0
      .travis.yml
  6. +73
    -0
      CODE_OF_CONDUCT.md
  7. +80
    -0
      CONTRIBUTING.md
  8. +201
    -0
      LICENSE
  9. +131
    -0
      README.md
  10. BIN
      doc/image.gif
  11. BIN
      doc/image/nolockcirclearray.gif
  12. BIN
      doc/image/slots.gif
  13. +218
    -0
      pom.xml
  14. +68
    -0
      sentinel-adapter/pom.xml
  15. +53
    -0
      sentinel-adapter/sentinel-dubbo-adapter/README.md
  16. +44
    -0
      sentinel-adapter/sentinel-dubbo-adapter/pom.xml
  17. +44
    -0
      sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractDubboFilter.java
  18. +43
    -0
      sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilter.java
  19. +35
    -0
      sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtils.java
  20. +70
    -0
      sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java
  21. +75
    -0
      sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java
  22. +3
    -0
      sentinel-adapter/sentinel-dubbo-adapter/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter
  23. +23
    -0
      sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DemoService.java
  24. +27
    -0
      sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/DemoServiceImpl.java
  25. +18
    -0
      sentinel-adapter/sentinel-dubbo-adapter/src/test/resources/spring-dubbo-consumer-filter.xml
  26. +18
    -0
      sentinel-adapter/sentinel-dubbo-adapter/src/test/resources/spring-dubbo-provider-filter.xml
  27. +38
    -0
      sentinel-adapter/sentinel-grpc-adapter/README.md
  28. +87
    -0
      sentinel-adapter/sentinel-grpc-adapter/pom.xml
  29. +141
    -0
      sentinel-adapter/sentinel-grpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcClientInterceptor.java
  30. +90
    -0
      sentinel-adapter/sentinel-grpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcServerInterceptor.java
  31. +70
    -0
      sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/FooServiceClient.java
  32. +44
    -0
      sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/FooServiceImpl.java
  33. +113
    -0
      sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcClientInterceptorTest.java
  34. +114
    -0
      sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcServerInterceptorTest.java
  35. +23
    -0
      sentinel-adapter/sentinel-grpc-adapter/src/test/proto/example.proto
  36. +18
    -0
      sentinel-adapter/sentinel-spring-boot-starter/README.md
  37. +95
    -0
      sentinel-adapter/sentinel-spring-boot-starter/pom.xml
  38. +31
    -0
      sentinel-adapter/sentinel-spring-boot-starter/src/main/java/com/alibaba/boot/sentinel/Constants.java
  39. +85
    -0
      sentinel-adapter/sentinel-spring-boot-starter/src/main/java/com/alibaba/boot/sentinel/config/SentinelWebServletAutoConfiguration.java
  40. +68
    -0
      sentinel-adapter/sentinel-spring-boot-starter/src/main/java/com/alibaba/boot/sentinel/endpoint/SentinelActuatorEndpoint.java
  41. +39
    -0
      sentinel-adapter/sentinel-spring-boot-starter/src/main/java/com/alibaba/boot/sentinel/endpoint/SentinelEndpointManagementContextConfiguration.java
  42. +93
    -0
      sentinel-adapter/sentinel-spring-boot-starter/src/main/java/com/alibaba/boot/sentinel/property/SentinelProperties.java
  43. +5
    -0
      sentinel-adapter/sentinel-spring-boot-starter/src/main/resources/META-INF/spring.factories
  44. +1
    -0
      sentinel-adapter/sentinel-spring-boot-starter/src/main/resources/META-INF/spring.provides
  45. +53
    -0
      sentinel-adapter/sentinel-spring-boot-starter/src/test/java/com/alibaba/boot/sentinel/SimpleWebApplication.java
  46. +3
    -0
      sentinel-adapter/sentinel-spring-boot-starter/src/test/resources/web-servlet.properties
  47. +22
    -0
      sentinel-adapter/sentinel-web-servlet/README.md
  48. +25
    -0
      sentinel-adapter/sentinel-web-servlet/pom.xml
  49. +88
    -0
      sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java
  50. +88
    -0
      sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonTotalFilter.java
  51. +37
    -0
      sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/DefaultUrlBlockHandler.java
  52. +27
    -0
      sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/DefaultUrlCleaner.java
  53. +38
    -0
      sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/UrlBlockHandler.java
  54. +31
    -0
      sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/UrlCleaner.java
  55. +50
    -0
      sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/WebCallbackManager.java
  56. +42
    -0
      sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/config/WebServletConfig.java
  57. +185
    -0
      sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/util/FilterUtil.java
  58. +27
    -0
      sentinel-core/pom.xml
  59. +52
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java
  60. +279
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java
  61. +141
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/Entry.java
  62. +47
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/EntryType.java
  63. +38
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/Env.java
  64. +28
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/ErrorEntryFreeException.java
  65. +145
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/Sph.java
  66. +225
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphO.java
  67. +203
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphU.java
  68. +58
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/Tracer.java
  69. +50
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/concurrent/NamedThreadFactory.java
  70. +149
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfig.java
  71. +164
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/Context.java
  72. +26
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/ContextNameDefineException.java
  73. +178
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/ContextUtil.java
  74. +33
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/NullContext.java
  75. +83
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/BaseLoggerBuilder.java
  76. +235
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEye.java
  77. +45
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeAppender.java
  78. +237
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtils.java
  79. +140
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeLogDaemon.java
  80. +332
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeRollingFileAppender.java
  81. +81
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/FastDateFormat.java
  82. +179
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatEntry.java
  83. +206
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatEntryFunc.java
  84. +190
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatLogController.java
  85. +145
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatLogger.java
  86. +113
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatLoggerBuilder.java
  87. +108
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatRollingData.java
  88. +79
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/SyncAppender.java
  89. +58
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/TokenBucket.java
  90. +101
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitExecutor.java
  91. +24
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitFunc.java
  92. +41
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitOrder.java
  93. +57
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CommandCenterLog.java
  94. +54
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java
  95. +124
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/DateFileLogHandler.java
  96. +83
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java
  97. +55
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LoggerUtils.java
  98. +53
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/RecordLog.java
  99. +100
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/ClusterNode.java
  100. +0
    -0
      sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/DefaultNode.java

+ 16
- 0
.circleci/config.yml Переглянути файл

@@ -0,0 +1,16 @@
version: 2
jobs:
build:
docker:
- image: circleci/openjdk:8-jdk

working_directory: ~/sentinel

environment:
MAVEN_OPTS: -Xmx3200m

steps:
- checkout

# Run tests
- run: mvn integration-test

+ 28
- 0
.github/ISSUE_TEMPLATE.md Переглянути файл

@@ -0,0 +1,28 @@
<!-- Here is for bug reports and feature requests ONLY!

If you're looking for help, please check our mail list and the Gitter room.
-->

## Issue Description

Type: *bug report* or *feature request*

### Describe what happened (or what feature you want)


### Describe what you expected to happen


### How to reproduce it (as minimally and precisely as possible)

1.
2.
3.

### Tell us your environment


### Anything else we need to know?




+ 19
- 0
.github/PULL_REQUEST_TEMPLATE.md Переглянути файл

@@ -0,0 +1,19 @@
<!-- Thanks for submitting a pull request! Here are some tips for you:
1. Please make sure you have read and understood the contributing guidelines: https://github.com/alibaba/Sentinel/blob/master/CONTRIBUTING.md
2. Please make sure the PR has a corresponding issue.
-->

### Describe what this PR does / why we need it


### Does this pull request fix one issue?

<!--If that, add "Fixes #xxxx" below in the next line. For example, Fixes #15. Otherwise, add "NONE" -->

### Describe how you did it


### Describe how to verify it


### Special notes for reviews

+ 27
- 0
.gitignore Переглянути файл

@@ -0,0 +1,27 @@
# IntelliJ project files
.idea/
*.iml
out
gen

# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
!/.mvn/wrapper/maven-wrapper.jar

# Eclipse
.classpath
.settings/
.project
bin/

# System related
*.DS_Store
Thumbs.db

+ 10
- 0
.travis.yml Переглянути файл

@@ -0,0 +1,10 @@
language: java

sudo: required
dist: trusty

jdk:
- oraclejdk8

after_success:
- bash <(curl -s https://codecov.io/bash)

+ 73
- 0
CODE_OF_CONDUCT.md Переглянути файл

@@ -0,0 +1,73 @@
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at sentinel@linux.alibaba.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

+ 80
- 0
CONTRIBUTING.md Переглянути файл

@@ -0,0 +1,80 @@
# Contributing to Sentinel

Welcome to Sentinel! This document is a guideline about how to contribute to Sentinel.
If you find something incorrect or missing, please leave comments / suggestions.

# Before you get started

## Code of Conduct

Please make sure to read and observe our [Code of Conduct](./CODE_OF_CONDUCT.md).

## Setting up your development environment

You should have JDK 1.8 or later installed in your system.

# Contributing

We are always very happy to have contributions, whether for typo fix, bug fix or big new features.
Please do not ever hesitate to ask a question or send a pull request.

We strongly value documentation and integration with other projects.
We are very glad to accept improvements for these aspects.

## GitHub workflow

We use the `master` branch as the development branch, which indicates that this is a unstable branch.

Here are the workflow for contributors:

1. Fork to your own
2. Clone fork to local repository
3. Create a new branch and work on it
4. Keep your branch in sync
5. Commit your changes (make sure your commit message concise)
6. Push your commits to your forked repository
7. Create a pull request

Please follow [the pull request template](./.github/PULL_REQUEST_TEMPLATE.md).
Please make sure the PR has a corresponding issue.

After creating a PR, one or more reviewers will be assigned to the pull request.
The reviewers will review the code.

Before merging a PR, squash any fix review feedback, typo, merged, and rebased sorts of commits.
The final commit message should be clear and concise.

## Open an issue / PR

We use [GitHub Issues](https://github.com/alibaba/Sentinel/issues) and [Pull Requests](https://github.com/alibaba/Sentinel/pulls) for trackers.

If you find a typo in document, find a bug in code, or want new features, or want to give suggestions,
you can [open an issue on GitHub](https://github.com/alibaba/Sentinel/issues/new) to report it.
Please follow the guideline message in the issue template.

If you want to contribute, please follow the [contribution workflow](#github-workflow) and create a new pull request.
If your PR contains large changes, e.g. component refactor or new components, please write detailed documents
about its design and usage.

Note that a single PR should not be too large. If heavy changes are required, it's better to separate the changes
to a few individual PRs.

## Code review

All code should be well reviewed by one or more committers. Some principles:

- Readability: Important code should be well-documented. Comply with our code style.
- Elegance: New functions, classes or components should be well designed.
- Testability: Important code should be well-tested (high unit test coverage).

# Community

## Contact us

### Mailing list

If you have any questions or advice, please contact sentinel@linux.alibaba.com.

### Gitter

Our Gitter room: [https://gitter.im/alibaba/Sentinel](https://gitter.im/alibaba/Sentinel).

+ 201
- 0
LICENSE Переглянути файл

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.

"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:

(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and

(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and

(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and

(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.

You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright [yyyy] [name of copyright owner]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

+ 131
- 0
README.md Переглянути файл

@@ -0,0 +1,131 @@
# Sentinel: Sentinel of Your Application

[![Travis Build Status](https://travis-ci.org/alibaba/Sentinel.svg?branch=master)](https://travis-ci.org/alibaba/Sentinel)
[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![Gitter](https://badges.gitter.im/alibaba/Sentinel.svg)](https://gitter.im/alibaba/Sentinel)

## What Does It Do?

As distributed systems become increasingly popular, the stability between services is becoming more important than ever before. Sentinel takes "flow" as breakthrough point, and works on multiple fields including **flow control**, **concurrency**, **circuit breaking** and **load protection**, to protect service stability.

Sentinel has the following features:

* **Rich applicable scenarios**:
Sentinel has been wildly used in Alibaba, and has covered almost all the core-scenarios in Double-11 Shopping Festivals in the past 10 years, such as “Second Kill” which needs to limit burst flow traffic to meet the system capacity, message peak clipping and valley fills, degrading unreliable downstream applications, etc.

* **Integrated monitor module**:
Sentinel also provides real-time monitoring function. You can see the runtime information of a single machine in real-time, and the summary runtime info of a cluster with less than 500 nodes.

* **Easy extension point**:
Sentinel provides easy-to-use extension points that allow you to quickly customize your logic, for example, custom rule management, adapting data sources, and so on.

## Documentation

See the [中文文档](https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D) for Chinese readme.

See the [Wiki](https://github.com/alibaba/Sentinel/wiki) for full documentation, examples, operational details and other information.

See the [Javadoc](https://github.com/alibaba/Sentinel/tree/master/doc) for the API.

## Quick Start

Below is a simple demo that guides new users to use Sentinel in just 3 steps. It also shows how to monitor this demo using the dashboard.

### 1.Download Library

**Note:** Sentinel requires Java 6 or later.

If your application is build in maven, just add the following code in pom.xml.

```xml
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>x.y.z</version>
</dependency>
```

If not, you can download JAR in [Maven Center Repository](https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-core).


### 2.Define Resource

Wrap code snippet via Sentinel API: `SphU.entry("RESOURCENAME")` and `entry.exit()`. In below example, it is `System.out.println("hello world");`:

```java
Entry entry = null;

try {
entry = SphU.entry("HelloWorld");
// BIZ logic being protected
System.out.println("hello world");
} catch (BlockException e) {
// handle block logic
} finally {
// make sure that the exit() logic is called
if (entry != null) {
entry.exit();
}
}
```

So far the code modification is done.

### 3.Define Rules

If we want to limit the access times of the resource, we can define rules. The following code defines a rule that limits access to the reource to 20 times per second at the maximum.

```java
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule = new FlowRule();
rule.setResource("hello world");
// set limit qps to 20
rule.setCount(20);
rules.add(rule);
FlowRuleManager.loadRules(rules);
```

### 4. Check the Result

After running the demo for a while, you can see the following records in `~/logs/csp/${appName}-metrics.log.xxx`.

```
|--timestamp-|------date time----|--resource-|p |block|s |e|rt
1529998904000|2018-06-26 15:41:44|hello world|20|0 |20|0|0
1529998905000|2018-06-26 15:41:45|hello world|20|5579 |20|0|728
1529998906000|2018-06-26 15:41:46|hello world|20|15698|20|0|0
1529998907000|2018-06-26 15:41:47|hello world|20|19262|20|0|0
1529998908000|2018-06-26 15:41:48|hello world|20|19502|20|0|0
1529998909000|2018-06-26 15:41:49|hello world|20|18386|20|0|0

p stands for incoming request, block for intercepted by rules, success for success handled, e for exception, rt for average response time (ms)
```
This shows that the demo can print "hello world" 20 times per second.

More examples and information can be found in the [How To Use](https://github.com/alibaba/Sentinel/wiki/How-to-Use) section.

The working principles of Sentinel can be found in [How it works](https://github.com/alibaba/Sentinel/wiki/How-it-works) section.

Samples can be found in the [sentinel-demo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo) module.

### 5.Start Dashboard

Sentinel also provides a simple dashboard application, on which you can monitor the clients and configure the rules in real time.

For details please refer to [Dashboard](https://github.com/alibaba/Sentinel/wiki/Dashboard).

## Trouble Shooting and Logs

Sentinel will generate logs for troubleshooting. All the information can be found in [logs](https://github.com/alibaba/Sentinel/wiki/Logs).

## Bugs and Feedback

For bug report, questions and discussions please submit [GitHub Issues](https://github.com/alibaba/sentinel/issues).

Contact us: sentinel@linux.alibaba.com

## Contributing

Contributions are always welcomed! Please see [CONTRIBUTING](./CONTRIBUTING.md) for detailed guidelines.


BIN
doc/image.gif Переглянути файл

Before After
Width: 969  |  Height: 438  |  Size: 28KB

BIN
doc/image/nolockcirclearray.gif Переглянути файл

Before After
Width: 589  |  Height: 538  |  Size: 19KB

BIN
doc/image/slots.gif Переглянути файл

Before After
Width: 1070  |  Height: 684  |  Size: 154KB

+ 218
- 0
pom.xml Переглянути файл

@@ -0,0 +1,218 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parent</artifactId>
<version>0.1.0</version>
<packaging>pom</packaging>

<name>${project.artifactId}</name>
<description>The parent project of Sentinel</description>
<url>https://github.com/alibaba/Sentinel</url>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<url>https://github.com/alibaba/Sentinel</url>
<connection>scm:git:https://github.com/alibaba/Sentinel.git</connection>
<developerConnection>scm:git:https://github.com/alibaba/Sentinel.git</developerConnection>
</scm>
<developers>
<developer>
<name>The Sentinel Project Contributors</name>
<email>sentinel-dev@linux.alibaba.com</email>
<url>https://github.com/alibaba/Sentinel</url>
</developer>
</developers>
<organization>
<name>Alibaba Group</name>
<url>https://github.com/alibaba</url>
</organization>
<issueManagement>
<system>github</system>
<url>https://github.com/alibaba/Sentinel/issues</url>
</issueManagement>

<properties>
<!-- Compile libs -->
<fastjson.version>1.2.47</fastjson.version>

<!-- Test libs -->
<junit.version>4.12</junit.version>
<mockito.version>2.18.0</mockito.version>

<!-- Build -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.source.version>1.6</java.source.version>
<java.target.version>1.6</java.target.version>
<java.encoding>UTF-8</java.encoding>
<maven.compiler.version>3.7.0</maven.compiler.version>
<maven.source.version>3.0.1</maven.source.version>
<maven.javadoc.version>3.0.1</maven.javadoc.version>
<maven.gpg.version>1.6</maven.gpg.version>
<maven.jacoco.version>0.8.1</maven.jacoco.version>
</properties>

<modules>
<module>sentinel-core</module>
<module>sentinel-extension</module>
<module>sentinel-transport</module>
<module>sentinel-adapter</module>
<module>sentinel-dashboard</module>

<module>sentinel-demo</module>
</modules>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-extension</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-adapter</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<configuration>
<source>${java.source.version}</source>
<target>${java.target.version}</target>
<encoding>${java.encoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${maven.jacoco.version}</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

<profiles>
<profile>
<id>oss</id>
<build>
<plugins>
<!-- Source -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven.source.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Javadoc -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven.javadoc.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<locale>en_US</locale>
<encoding>UTF-8</encoding>
<charset>UTF-8</charset>
<doclint>none</doclint>
</configuration>
</execution>
</executions>
</plugin>
<!-- GPG -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>${maven.gpg.version}</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<distributionManagement>
<snapshotRepository>
<id>oss</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>oss</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
</profile>
</profiles>

</project>

+ 68
- 0
sentinel-adapter/pom.xml Переглянути файл

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parent</artifactId>
<version>0.1.0</version>
</parent>
<artifactId>sentinel-adapter</artifactId>
<packaging>pom</packaging>
<name>sentinel-adapter</name>
<description>The adapters of Sentinel</description>

<modules>
<module>sentinel-web-servlet</module>
<module>sentinel-dubbo-adapter</module>
<module>sentinel-grpc-adapter</module>
<module>sentinel-spring-boot-starter</module>
</modules>

<properties>
<servlet.api.version>3.1.0</servlet.api.version>
<dubbo.version>2.5.8</dubbo.version>
<grpc.version>1.13.1</grpc.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-extension</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.api.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

+ 53
- 0
sentinel-adapter/sentinel-dubbo-adapter/README.md Переглянути файл

@@ -0,0 +1,53 @@
# Sentinel Dubbo Adapter

Sentinel Dubbo Adapter provides service consumer filter and provider filter
for [Dubbo](http://dubbo.io/) services.

To use Sentinel Dubbo Adapter, you can simply add the following dependency to your `pom.xml`:

```xml
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-dubbo-adapter</artifactId>
<version>x.y.z</version>
</dependency>
```

The Sentinel filters are **enabled by default**. Once you add the dependency,
the Dubbo services and methods will become protected resources in Sentinel,
which can leverage Sentinel's flow control and guard ability when rules are configured.
Demos can be found in [sentinel-demo-dubbo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-dubbo).

If you don't want the filters enabled, you can manually disable them. For example:

```xml
<dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/>

<dubbo:provider filter="-sentinel.dubbo.provider.filter"/>
```

For more details of Dubbo filter, see [here](https://dubbo.incubator.apache.org/#/docs/dev/impls/filter.md?lang=en-us).

## Dubbo resources

The resource for Dubbo services has two granularities: service interface and service method.

- Service interface:resourceName format is `interfaceName`,e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService`
- Service method:resourceName format is `interfaceName:methodSignature`,e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)`

## Flow control based on caller

In many circumstances, it's also significant to control traffic flow based on the **caller**.
For example, assuming that there are two services A and B, both of them initiate remote call requests to the service provider.
If we want to limit the calls from service B only, we can set the `limitApp` of flow rule as the identifier of service B (e.g. service name).

Sentinel Dubbo Adapter will automatically resolve the Dubbo consumer's *application name* as the caller's name (`origin`),
and will bring the caller's name when doing resource protection.
If `limitApp` of flow rules is not configured (`default`), flow control will take effects on all callers.
If `limitApp` of a flow rule is configured with a caller, then the corresponding flow rule will only take effect on the specific caller.

> Note: Dubbo consumer does not provide its Dubbo application name when doing RPC,
so developers should manually put the application name into *attachment* at consumer side,
then extract it at provider side. Sentinel Dubbo Adapter has implemented a filter (`DubboAppContextFilter`)
where consumer can carry application name information to provider automatically.
If the consumer does not use Sentinel Dubbo Adapter but requires flow control based on caller, developers can manually put the application name into attachment with the key `dubboApplication`.

+ 44
- 0
sentinel-adapter/sentinel-dubbo-adapter/pom.xml Переглянути файл

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<parent>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-adapter</artifactId>
<version>0.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-dubbo-adapter</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

+ 44
- 0
sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractDubboFilter.java Переглянути файл

@@ -0,0 +1,44 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.dubbo;

import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;

/**
* @author leyou
*/
abstract class AbstractDubboFilter implements Filter {

protected String getResourceName(Invoker<?> invoker, Invocation invocation) {
StringBuilder buf = new StringBuilder(64);
buf.append(invoker.getInterface().getName())
.append(":")
.append(invocation.getMethodName())
.append("(");
boolean isFirst = true;
for (Class<?> clazz : invocation.getParameterTypes()) {
if (!isFirst) {
buf.append(",");
}
buf.append(clazz.getName());
isFirst = false;
}
buf.append(")");
return buf.toString();
}
}

+ 43
- 0
sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilter.java Переглянути файл

@@ -0,0 +1,43 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.dubbo;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcException;

/**
* Puts current consumer's application name in the attachment of each invocation.
*
* @author Eric Zhao
*/
@Activate(group = "consumer")
public class DubboAppContextFilter implements Filter {

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String application = invoker.getUrl().getParameter(Constants.APPLICATION_KEY);
if (application != null) {
RpcContext.getContext().setAttachment(DubboUtils.DUBBO_APPLICATION_KEY, application);
}
return invoker.invoke(invocation);
}
}

+ 35
- 0
sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtils.java Переглянути файл

@@ -0,0 +1,35 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.dubbo;

import com.alibaba.dubbo.rpc.Invocation;

/**
* @author Eric Zhao
*/
public final class DubboUtils {

public static final String DUBBO_APPLICATION_KEY = "dubboApplication";

public static String getApplication(Invocation invocation, String defaultValue) {
if (invocation == null || invocation.getAttachments() == null) {
throw new IllegalArgumentException("Bad invocation instance");
}
return invocation.getAttachment(DUBBO_APPLICATION_KEY, defaultValue);
}

private DubboUtils() {}
}

+ 70
- 0
sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java Переглянути файл

@@ -0,0 +1,70 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.dubbo;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.SentinelRpcException;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcException;

/**
* <p>Dubbo service consumer filter for Sentinel. Auto activated by default.</p>
*
* If you want to disable the consumer filter, you can configure:
* <pre>
* &lt;dubbo:consumer filter="-sentinel.dubbo.consumer.filter"/&gt;
* </pre>
*
* @author leyou
*/
@Activate(group = "consumer")
public class SentinelDubboConsumerFilter extends AbstractDubboFilter implements Filter {

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Entry interfaceEntry = null;
Entry methodEntry = null;
try {
String resourceName = getResourceName(invoker, invocation);
ContextUtil.enter(resourceName);
interfaceEntry = SphU.entry(invoker.getInterface().getName(), EntryType.OUT);
methodEntry = SphU.entry(resourceName, EntryType.OUT);
return invoker.invoke(invocation);
} catch (BlockException e) {
throw new SentinelRpcException(e);
} catch (RpcException e) {
Tracer.trace(e);
throw e;
} finally {
if (methodEntry != null) {
methodEntry.exit();
}
if (interfaceEntry != null) {
interfaceEntry.exit();
}
ContextUtil.exit();
}
}
}

+ 75
- 0
sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java Переглянути файл

@@ -0,0 +1,75 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.dubbo;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.SentinelRpcException;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcException;

/**
* <p>Dubbo service provider filter for Sentinel. Auto activated by default.</p>
*
* If you want to disable the provider filter, you can configure:
* <pre>
* &lt;dubbo:provider filter="-sentinel.dubbo.provider.filter"/&gt;
* </pre>
*
* @author leyou
*/
@Activate(group = "provider")
public class SentinelDubboProviderFilter extends AbstractDubboFilter implements Filter {

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// Get origin caller.
String application = DubboUtils.getApplication(invocation, "");

Entry interfaceEntry = null;
Entry methodEntry = null;
try {
String resourceName = getResourceName(invoker, invocation);
String interfaceName = invoker.getInterface().getName();
ContextUtil.enter(resourceName, application);
interfaceEntry = SphU.entry(interfaceName, EntryType.IN);
methodEntry = SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments());

return invoker.invoke(invocation);
} catch (BlockException e) {
throw new SentinelRpcException(e);
} catch (RpcException e) {
Tracer.trace(e);
throw e;
} finally {
if (methodEntry != null) {
methodEntry.exit(1, invocation.getArguments());
}
if (interfaceEntry != null) {
interfaceEntry.exit();
}
ContextUtil.exit();
}
}
}

+ 3
- 0
sentinel-adapter/sentinel-dubbo-adapter/src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter Переглянути файл

@@ -0,0 +1,3 @@
sentinel.dubbo.provider.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboProviderFilter
sentinel.dubbo.consumer.filter=com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboConsumerFilter
dubbo.application.context.name.filter=com.alibaba.csp.sentinel.adapter.dubbo.DubboAppContextFilter

+ 23
- 0
sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DemoService.java Переглянути файл

@@ -0,0 +1,23 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.dubbo;

/**
* @author leyou
*/
public interface DemoService {
String sayHello(String name, int n);
}

+ 27
- 0
sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/DemoServiceImpl.java Переглянути файл

@@ -0,0 +1,27 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.dubbo.provider;

import com.alibaba.csp.sentinel.adapter.dubbo.DemoService;

/**
* @author leyou
*/
public class DemoServiceImpl implements DemoService {
public String sayHello(String name, int n) {
return "Hello " + name + ", " + n;
}
}

+ 18
- 0
sentinel-adapter/sentinel-dubbo-adapter/src/test/resources/spring-dubbo-consumer-filter.xml Переглянути файл

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

<dubbo:application name="demo-consumer"/>
<dubbo:registry address="multicast://224.5.6.7:1234"/>
<dubbo:protocol name="dubbo" port="20880"/>
<dubbo:service interface="com.alibaba.csp.sentinel.adapter.dubbo.DemoService" ref="demoServiceImp" />
<bean id="demoServiceImp" class="com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoServiceImpl"/>

<dubbo:reference id="demoService" interface="com.alibaba.csp.sentinel.adapter.dubbo.DemoService"/>

</beans>

+ 18
- 0
sentinel-adapter/sentinel-dubbo-adapter/src/test/resources/spring-dubbo-provider-filter.xml Переглянути файл

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

<dubbo:application name="demo-provider"/>
<dubbo:registry address="multicast://224.5.6.7:1234"/>
<dubbo:protocol name="dubbo" port="20880"/>
<dubbo:service interface="com.alibaba.csp.sentinel.adapter.dubbo.DemoService" ref="demoServiceImp" />
<bean id="demoServiceImp" class="com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoServiceImpl"/>

<dubbo:reference id="demoService" interface="com.alibaba.csp.sentinel.adapter.dubbo.DemoService"/>

</beans>

+ 38
- 0
sentinel-adapter/sentinel-grpc-adapter/README.md Переглянути файл

@@ -0,0 +1,38 @@
# Sentinel gRPC Adapter

Sentinel gRPC Adapter provides client and server interceptor for gRPC services.

> Note that currently the interceptor only supports unary methods in gRPC.
In some circumstances (e.g. asynchronous call), the RT metrics might not be accurate.

## Client Interceptor

Example:

```java
public class ServiceClient {

private final ManagedChannel channel;

ServiceClient(String host, int port) {
this.channel = ManagedChannelBuilder.forAddress(host, port)
.intercept(new SentinelGrpcClientInterceptor()) // Add the client interceptor.
.build();
// Init your stub here.
}
}
```

## Server Interceptor

Example:

```java
import io.grpc.Server;

Server server = ServerBuilder.forPort(port)
.addService(new MyServiceImpl()) // Add your service.
.intercept(new SentinelGrpcServerInterceptor()) // Add the server interceptor.
.build();
```


+ 87
- 0
sentinel-adapter/sentinel-grpc-adapter/pom.xml Переглянути файл

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sentinel-adapter</artifactId>
<groupId>com.alibaba.csp</groupId>
<version>0.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-grpc-adapter</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>

<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>test-compile</goal>
<goal>test-compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

+ 141
- 0
sentinel-adapter/sentinel-grpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcClientInterceptor.java Переглянути файл

@@ -0,0 +1,141 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.grpc;

import javax.annotation.Nullable;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;

import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingClientCall;
import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.Status;

/**
* <p>gRPC client interceptor for Sentinel. Currently it only works with unary methods.</p>
*
* Example code:
* <pre>
* public class ServiceClient {
*
* private final ManagedChannel channel;
*
* ServiceClient(String host, int port) {
* this.channel = ManagedChannelBuilder.forAddress(host, port)
* .intercept(new SentinelGrpcClientInterceptor()) // Add the client interceptor.
* .build();
* // Init your stub here.
* }
*
* }
* </pre>
*
* For server interceptor, see {@link SentinelGrpcServerInterceptor}.
*
* @author Eric Zhao
*/
public class SentinelGrpcClientInterceptor implements ClientInterceptor {

private static final Status FLOW_CONTROL_BLOCK = Status.UNAVAILABLE.withDescription(
"Flow control limit exceeded (client side)");

@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> methodDescriptor,
CallOptions callOptions, Channel channel) {
String resourceName = methodDescriptor.getFullMethodName();
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT);
// Allow access, forward the call.
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
channel.newCall(methodDescriptor, callOptions)) {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
super.start(new SimpleForwardingClientCallListener<RespT>(responseListener) {
@Override
public void onReady() {
super.onReady();
}

@Override
public void onClose(Status status, Metadata trailers) {
super.onClose(status, trailers);
// Record the exception metrics.
if (!status.isOk()) {
recordException(status.asRuntimeException());
}
}
}, headers);
}

@Override
public void cancel(@Nullable String message, @Nullable Throwable cause) {
super.cancel(message, cause);
// Record the exception metrics.
recordException(cause);
}
};
} catch (BlockException e) {
// Flow control threshold exceeded, block the call.
return new ClientCall<ReqT, RespT>() {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
responseListener.onClose(FLOW_CONTROL_BLOCK, new Metadata());
}

@Override
public void request(int numMessages) {

}

@Override
public void cancel(@Nullable String message, @Nullable Throwable cause) {

}

@Override
public void halfClose() {

}

@Override
public void sendMessage(ReqT message) {

}
};
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}

private void recordException(Throwable t) {
Tracer.trace(t);
}
}

+ 90
- 0
sentinel-adapter/sentinel-grpc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcServerInterceptor.java Переглянути файл

@@ -0,0 +1,90 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.grpc;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;

import io.grpc.ForwardingServerCall;
import io.grpc.ForwardingServerCallListener;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCall.Listener;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.Status;

/**
* <p>gRPC server interceptor for Sentinel. Currently it only works with unary methods.</p>
*
* Example code:
* <pre>
* Server server = ServerBuilder.forPort(port)
* .addService(new MyServiceImpl()) // Add your service.
* .intercept(new SentinelGrpcServerInterceptor()) // Add the server interceptor.
* .build();
* </pre>
*
* For client interceptor, see {@link SentinelGrpcClientInterceptor}.
*
* @author Eric Zhao
*/
public class SentinelGrpcServerInterceptor implements ServerInterceptor {

private static final Status FLOW_CONTROL_BLOCK = Status.UNAVAILABLE.withDescription(
"Flow control limit exceeded (server side)");

@Override
public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata,
ServerCallHandler<ReqT, RespT> serverCallHandler) {
String resourceName = serverCall.getMethodDescriptor().getFullMethodName();
// Remote address: serverCall.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.IN);
// Allow access, forward the call.
return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(
serverCallHandler.startCall(
new ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT>(serverCall) {
@Override
public void close(Status status, Metadata trailers) {
super.close(status, trailers);
// Record the exception metrics.
if (!status.isOk()) {
recordException(status.asRuntimeException());
}
}
}, metadata)) {};
} catch (BlockException e) {
serverCall.close(FLOW_CONTROL_BLOCK, new Metadata());
return new ServerCall.Listener<ReqT>() {};
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}

private void recordException(Throwable t) {
Tracer.trace(t);
}
}

+ 70
- 0
sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/FooServiceClient.java Переглянути файл

@@ -0,0 +1,70 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.grpc;

import java.util.concurrent.TimeUnit;

import com.alibaba.csp.sentinel.adapter.grpc.gen.FooRequest;
import com.alibaba.csp.sentinel.adapter.grpc.gen.FooResponse;
import com.alibaba.csp.sentinel.adapter.grpc.gen.FooServiceGrpc;

import io.grpc.ClientInterceptor;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

/**
* A simple wrapped gRPC client for FooService.
*
* @author Eric Zhao
*/
final class FooServiceClient {

private final ManagedChannel channel;
private final FooServiceGrpc.FooServiceBlockingStub blockingStub;

FooServiceClient(String host, int port) {
this.channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.build();
this.blockingStub = FooServiceGrpc.newBlockingStub(this.channel);
}

FooServiceClient(String host, int port, ClientInterceptor interceptor) {
this.channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.intercept(interceptor)
.build();
this.blockingStub = FooServiceGrpc.newBlockingStub(this.channel);
}

FooResponse sayHello(FooRequest request) {
if (request == null) {
throw new IllegalArgumentException("Request cannot be null");
}
return blockingStub.sayHello(request);
}

FooResponse anotherHello(FooRequest request) {
if (request == null) {
throw new IllegalArgumentException("Request cannot be null");
}
return blockingStub.anotherHello(request);
}

void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
}
}

+ 44
- 0
sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/FooServiceImpl.java Переглянути файл

@@ -0,0 +1,44 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.grpc;

import com.alibaba.csp.sentinel.adapter.grpc.gen.FooRequest;
import com.alibaba.csp.sentinel.adapter.grpc.gen.FooResponse;
import com.alibaba.csp.sentinel.adapter.grpc.gen.FooServiceGrpc;

import io.grpc.stub.StreamObserver;

/**
* Implementation of FooService defined in proto.
*/
class FooServiceImpl extends FooServiceGrpc.FooServiceImplBase {

@Override
public void sayHello(FooRequest request, StreamObserver<FooResponse> responseObserver) {
String message = String.format("Hello %s! Your ID is %d.", request.getName(), request.getId());
FooResponse response = FooResponse.newBuilder().setMessage(message).build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}

@Override
public void anotherHello(FooRequest request, StreamObserver<FooResponse> responseObserver) {
String message = String.format("Good day, %s (%d)", request.getName(), request.getId());
FooResponse response = FooResponse.newBuilder().setMessage(message).build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}

+ 113
- 0
sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcClientInterceptorTest.java Переглянути файл

@@ -0,0 +1,113 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.grpc;

import java.io.IOException;
import java.util.Collections;

import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.adapter.grpc.gen.FooRequest;
import com.alibaba.csp.sentinel.adapter.grpc.gen.FooResponse;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.StatusRuntimeException;
import org.junit.Test;

import static org.junit.Assert.*;

/**
* Test cases for {@link SentinelGrpcClientInterceptor}.
*
* @author Eric Zhao
*/
public class SentinelGrpcClientInterceptorTest {

private final String resourceName = "com.alibaba.sentinel.examples.FooService/sayHello";
private final int threshold = 2;

private Server server;

private void configureFlowRule() {
FlowRule rule = new FlowRule()
.setCount(threshold)
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setResource(resourceName)
.setLimitApp("default")
.as(FlowRule.class);
FlowRuleManager.loadRules(Collections.singletonList(rule));
}

//@Test
public void testGrpcClientInterceptor() throws Exception {
final int port = 19328;

configureFlowRule();
prepareServer(port);

FooServiceClient client = new FooServiceClient("localhost", port, new SentinelGrpcClientInterceptor());
final int total = 8;
for (int i = 0; i < total; i++) {
sendRequest(client);
}
ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(resourceName, EntryType.OUT);
assertNotNull(clusterNode);

assertEquals((total - threshold) / 2, clusterNode.blockedRequest());
assertEquals(total / 2, clusterNode.totalRequest());

long totalQps = clusterNode.totalQps();
long passQps = clusterNode.passQps();
long blockedQps = clusterNode.blockedQps();
assertEquals(total, totalQps);
assertEquals(total - threshold, blockedQps);
assertEquals(threshold, passQps);

stopServer();
}

private void sendRequest(FooServiceClient client) {
try {
FooResponse response = client.sayHello(FooRequest.newBuilder().setName("Sentinel").setId(666).build());
System.out.println(ClusterBuilderSlot.getClusterNode(resourceName, EntryType.OUT).avgRt());
System.out.println("Response: " + response);
} catch (StatusRuntimeException ex) {
System.out.println("Blocked, cause: " + ex.getMessage());
}
}

private void prepareServer(int port) throws IOException {
if (server != null) {
throw new IllegalStateException("Server already running!");
}
server = ServerBuilder.forPort(port)
.addService(new FooServiceImpl())
.build();
server.start();
}

private void stopServer() {
if (server != null) {
server.shutdown();
server = null;
}
}
}

+ 114
- 0
sentinel-adapter/sentinel-grpc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/grpc/SentinelGrpcServerInterceptorTest.java Переглянути файл

@@ -0,0 +1,114 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.grpc;

import java.io.IOException;
import java.util.Collections;

import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.adapter.grpc.gen.FooRequest;
import com.alibaba.csp.sentinel.adapter.grpc.gen.FooResponse;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.StatusRuntimeException;
import org.junit.Test;

import static org.junit.Assert.*;

/**
* Test cases for {@link SentinelGrpcServerInterceptor}.
*
* @author Eric Zhao
*/
public class SentinelGrpcServerInterceptorTest {

private final String resourceName = "com.alibaba.sentinel.examples.FooService/anotherHello";
private final int threshold = 4;

private Server server;
private FooServiceClient client;

private void configureFlowRule() {
FlowRule rule = new FlowRule()
.setCount(threshold)
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setResource(resourceName)
.setLimitApp("default")
.as(FlowRule.class);
FlowRuleManager.loadRules(Collections.singletonList(rule));
}

//@Test
public void testGrpcServerInterceptor() throws Exception {
final int port = 19329;
client = new FooServiceClient("localhost", port);

configureFlowRule();
prepareServer(port);

final int total = 8;
for (int i = 0; i < total; i++) {
sendRequest();
}
ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(resourceName, EntryType.IN);
assertNotNull(clusterNode);

assertEquals((total - threshold) / 2, clusterNode.blockedRequest());
assertEquals(total / 2, clusterNode.totalRequest());

long totalQps = clusterNode.totalQps();
long passQps = clusterNode.passQps();
long blockedQps = clusterNode.blockedQps();
assertEquals(total, totalQps);
assertEquals(total - threshold, blockedQps);
assertEquals(threshold, passQps);

stopServer();
}

private void sendRequest() {
try {
FooResponse response = client.anotherHello(FooRequest.newBuilder().setName("Sentinel").setId(666).build());
System.out.println("Response: " + response);
} catch (StatusRuntimeException ex) {
System.out.println("Blocked, cause: " + ex.getMessage());
}
}

private void prepareServer(int port) throws IOException {
if (server != null) {
throw new IllegalStateException("Server already running!");
}
server = ServerBuilder.forPort(port)
.addService(new FooServiceImpl())
.intercept(new SentinelGrpcServerInterceptor())
.build();
server.start();
}

private void stopServer() {
if (server != null) {
server.shutdown();
server = null;
}
}
}

+ 23
- 0
sentinel-adapter/sentinel-grpc-adapter/src/test/proto/example.proto Переглянути файл

@@ -0,0 +1,23 @@
syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.alibaba.csp.sentinel.adapter.grpc.gen";
option java_outer_classname = "FooProto";

package com.alibaba.sentinel.examples;

message FooRequest {
string name = 1;
int32 id = 2;
}

message FooResponse {
string message = 1;
}

// Example service definition.
service FooService {
rpc sayHello(FooRequest) returns (FooResponse) {}

rpc anotherHello(FooRequest) returns (FooResponse) {}
}

+ 18
- 0
sentinel-adapter/sentinel-spring-boot-starter/README.md Переглянути файл

@@ -0,0 +1,18 @@
# Sentinel Spring Boot Starter

Sentinel Spring Boot Starter provides out-of-box integration with Spring Boot applications
(e.g. web applications, Dubbo services).

## Web Servlet

Web servlet integration is enabled by default. You need to configure URL patterns in your config file (e.g. properties file):

```
spring.sentinel.servletFilter.urlPatterns=/*
```

By default the URL pattern is `/*`.

## Dubbo

Dubbo integration is enabled by default. You need to disable the filters manually if you don't want them.

+ 95
- 0
sentinel-adapter/sentinel-spring-boot-starter/pom.xml Переглянути файл

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sentinel-adapter</artifactId>
<groupId>com.alibaba.csp</groupId>
<version>0.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>

<artifactId>sentinel-spring-boot-starter</artifactId>

<properties>
<spring.boot.version>1.5.14.RELEASE</spring.boot.version>
</properties>

<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-dubbo-adapter</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring.boot.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<version>${spring.boot.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

+ 31
- 0
sentinel-adapter/sentinel-spring-boot-starter/src/main/java/com/alibaba/boot/sentinel/Constants.java Переглянути файл

@@ -0,0 +1,31 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.boot.sentinel;

/**
* @author Eric Zhao
*/
public final class Constants {

/**
* Property prefix in application.properties.
*/
public static final String PREFIX = "spring.sentinel";
public static final String SENTINEL_SERVLET_ENABLED = "spring.sentinel.servletFilter.enabled";
public static final String SENTINEL_ENABLED = "spring.sentinel.enabled";

private Constants() {}
}

+ 85
- 0
sentinel-adapter/sentinel-spring-boot-starter/src/main/java/com/alibaba/boot/sentinel/config/SentinelWebServletAutoConfiguration.java Переглянути файл

@@ -0,0 +1,85 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.boot.sentinel.config;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;

import com.alibaba.boot.sentinel.Constants;
import com.alibaba.boot.sentinel.property.SentinelProperties;
import com.alibaba.boot.sentinel.property.SentinelProperties.ServletFilterConfig;
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;

/**
* Auto configuration for Sentinel web servlet filter.
*
* @author Eric Zhao
*/
@Configuration
@ConditionalOnWebApplication
@ConditionalOnProperty(name = {Constants.SENTINEL_ENABLED, Constants.SENTINEL_SERVLET_ENABLED}, matchIfMissing = true)
@EnableConfigurationProperties(SentinelProperties.class)
public class SentinelWebServletAutoConfiguration {

private final Logger logger = LoggerFactory.getLogger(SentinelWebServletAutoConfiguration.class);

@Autowired
private SentinelProperties properties;

@Bean
@ConditionalOnWebApplication
public FilterRegistrationBean sentinelFilterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
if (!properties.isEnabled()) {
return registrationBean;
}

ServletFilterConfig filterConfig = properties.getServletFilter();
if (null == filterConfig) {
filterConfig = new ServletFilterConfig();
properties.setServletFilter(filterConfig);
}

if (CollectionUtils.isEmpty(filterConfig.getUrlPatterns())) {
List<String> defaultPatterns = new ArrayList<String>();
defaultPatterns.add("/*");
filterConfig.setUrlPatterns(defaultPatterns);
logger.info("[Sentinel Starter] Using default patterns for web servlet filter: {}", defaultPatterns);
}
registrationBean.addUrlPatterns(filterConfig.getUrlPatterns().toArray(new String[0]));

Filter filter = new CommonFilter();
registrationBean.setFilter(filter);
registrationBean.setOrder(filterConfig.getOrder());

logger.info("[Sentinel Starter] Web servlet filter registered with urlPatterns: {}",
filterConfig.getUrlPatterns());
return registrationBean;
}
}

+ 68
- 0
sentinel-adapter/sentinel-spring-boot-starter/src/main/java/com/alibaba/boot/sentinel/endpoint/SentinelActuatorEndpoint.java Переглянути файл

@@ -0,0 +1,68 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.boot.sentinel.endpoint;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.alibaba.boot.sentinel.property.SentinelProperties;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.mvc.AbstractMvcEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
* @author duanling
* @author Eric Zhao
*/
@ConfigurationProperties("endpoints.sentinel")
public class SentinelActuatorEndpoint extends AbstractMvcEndpoint {

@Autowired
private SentinelProperties sentinelProperties;

public SentinelActuatorEndpoint() {
super("/sentinel", false);
}

@RequestMapping
@ResponseBody
public Map<String, Object> invoke() {
Map<String, Object> result = new HashMap<String, Object>();

result.put("version", this.getClass().getPackage().getImplementationVersion());
result.put("properties", sentinelProperties);

List<FlowRule> flowRules = FlowRuleManager.getRules();
List<DegradeRule> degradeRules = DegradeRuleManager.getRules();
List<SystemRule> systemRules = SystemRuleManager.getRules();

result.put("flowRules", flowRules);
result.put("degradeRules", degradeRules);
result.put("systemRules", systemRules);

return result;
}
}

+ 39
- 0
sentinel-adapter/sentinel-spring-boot-starter/src/main/java/com/alibaba/boot/sentinel/endpoint/SentinelEndpointManagementContextConfiguration.java Переглянути файл

@@ -0,0 +1,39 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.boot.sentinel.endpoint;

import com.alibaba.boot.sentinel.property.SentinelProperties;

import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration;
import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

/**
* @author duanling
*/
@ManagementContextConfiguration
@EnableConfigurationProperties({SentinelProperties.class})
public class SentinelEndpointManagementContextConfiguration {

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint("sentinel")
public SentinelActuatorEndpoint sentinelEndPoint() {
return new SentinelActuatorEndpoint();
}
}

+ 93
- 0
sentinel-adapter/sentinel-spring-boot-starter/src/main/java/com/alibaba/boot/sentinel/property/SentinelProperties.java Переглянути файл

@@ -0,0 +1,93 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.boot.sentinel.property;

import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.Ordered;

/**
* @author Eric Zhao
*/
@ConfigurationProperties(prefix = "spring.sentinel")
public class SentinelProperties {

private boolean enabled = true;

private ServletFilterConfig servletFilter;

public boolean isEnabled() {
return enabled;
}

public SentinelProperties setEnabled(boolean enabled) {
this.enabled = enabled;
return this;
}

public ServletFilterConfig getServletFilter() {
return servletFilter;
}

public SentinelProperties setServletFilter(
ServletFilterConfig servletFilter) {
this.servletFilter = servletFilter;
return this;
}

public static class DubboFilterConfig {}

public static class ServletFilterConfig {

private boolean enabled = true;

/**
* Chain order for Sentinel servlet filter.
*/
private int order = Ordered.HIGHEST_PRECEDENCE;

/**
* URL pattern for Sentinel servlet filter.
*/
private List<String> urlPatterns;

public int getOrder() {
return this.order;
}

public void setOrder(int order) {
this.order = order;
}

public List<String> getUrlPatterns() {
return urlPatterns;
}

public void setUrlPatterns(List<String> urlPatterns) {
this.urlPatterns = urlPatterns;
}

public boolean isEnabled() {
return enabled;
}

public ServletFilterConfig setEnabled(boolean enabled) {
this.enabled = enabled;
return this;
}
}
}

+ 5
- 0
sentinel-adapter/sentinel-spring-boot-starter/src/main/resources/META-INF/spring.factories Переглянути файл

@@ -0,0 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.boot.sentinel.config.SentinelWebServletAutoConfiguration

org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration=\
com.alibaba.boot.sentinel.endpoint.SentinelEndpointManagementContextConfiguration

+ 1
- 0
sentinel-adapter/sentinel-spring-boot-starter/src/main/resources/META-INF/spring.provides Переглянути файл

@@ -0,0 +1 @@
provides: sentinel-core

+ 53
- 0
sentinel-adapter/sentinel-spring-boot-starter/src/test/java/com/alibaba/boot/sentinel/SimpleWebApplication.java Переглянути файл

@@ -0,0 +1,53 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.boot.sentinel;

import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Eric Zhao
*/
@SpringBootApplication
@RestController
@PropertySource("classpath:web-servlet.properties")
public class SimpleWebApplication {

@RequestMapping("/foo")
public String foo() {
return "Hello!";
}

@RequestMapping("/baz")
public String baz() {
ClusterNode node = ClusterBuilderSlot.getClusterNode("/foo");
if (node == null) {
return "/foo has not been called!";
} else {
return "/foo total request in metrics: " + node.totalRequest();
}
}

public static void main(String[] args) {
SpringApplication.run(SimpleWebApplication.class, args);
}
}

+ 3
- 0
sentinel-adapter/sentinel-spring-boot-starter/src/test/resources/web-servlet.properties Переглянути файл

@@ -0,0 +1,3 @@
spring.sentinel.enabled=true
spring.sentinel.servletFilter.enabled=true
spring.sentinel.servletFilter.urlPatterns=/foo

+ 22
- 0
sentinel-adapter/sentinel-web-servlet/README.md Переглянути файл

@@ -0,0 +1,22 @@
# Sentinel Web Servlet Filter

Sentinel provides Servlet filter integration. To use the filter,
you can simply configure your `web.xml` with:

```xml
<filter>
<filter-name>SentinelCommonFilter</filter-name>
<filter-class>com.alibaba.csp.sentinel.adapter.servlet.CommonFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>SentinelCommonFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
```

When a request is blocked, Sentinel servlet filter will give a default page indicating the request blocked.
If customized block page is set (via `WebServletConfig.setBlockPage(blockPage)` method),
the filter will redirect the request to provided URL. You can also implement your own
block handler (the `UrlBlockHandler` interface) and register to `WebCallbackManager`.


+ 25
- 0
sentinel-adapter/sentinel-web-servlet/pom.xml Переглянути файл

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-adapter</artifactId>
<version>0.1.0</version>
</parent>
<artifactId>sentinel-web-servlet</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>

</dependencies>
</project>

+ 88
- 0
sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java Переглянути файл

@@ -0,0 +1,88 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;

/***
* Servlet filter that integrates with Sentinel.
*
* @author youji.zj
*/
public class CommonFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest sRequest = (HttpServletRequest)request;
Entry entry = null;

try {
String target = FilterUtil.filterTarget(sRequest);
target = WebCallbackManager.getUrlCleaner().clean(target);

ContextUtil.enter(target);
entry = SphU.entry(target, EntryType.IN);

chain.doFilter(request, response);
} catch (BlockException e) {
HttpServletResponse sResponse = (HttpServletResponse)response;
WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse);
} catch (IOException e2) {
Tracer.trace(e2);
throw e2;
} catch (ServletException e3) {
Tracer.trace(e3);
throw e3;
} catch (RuntimeException e4) {
Tracer.trace(e4);
throw e4;
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}

@Override
public void destroy() {

}
}

+ 88
- 0
sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonTotalFilter.java Переглянути файл

@@ -0,0 +1,88 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;

/***
* Servlet filter for all requests.
*
* @author youji.zj
*/
public class CommonTotalFilter implements Filter {

public static final String TOTAL_URL_REQUEST = "total-url-request";

@Override
public void init(FilterConfig filterConfig) {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest sRequest = (HttpServletRequest)request;
String target = FilterUtil.filterTarget(sRequest);
target = WebCallbackManager.getUrlCleaner().clean(target);

Entry entry = null;
try {
ContextUtil.enter(target);
entry = SphU.entry(TOTAL_URL_REQUEST);
chain.doFilter(request, response);
} catch (BlockException e) {
HttpServletResponse sResponse = (HttpServletResponse)response;
WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse);
} catch (IOException e2) {
Tracer.trace(e2);
throw e2;
} catch (ServletException e3) {
Tracer.trace(e3);
throw e3;
} catch (RuntimeException e4) {
Tracer.trace(e4);
throw e4;
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}

@Override
public void destroy() {

}

}

+ 37
- 0
sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/DefaultUrlBlockHandler.java Переглянути файл

@@ -0,0 +1,37 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet.callback;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil;

/***
* The default {@link UrlBlockHandler}.
*
* @author youji.zj
*/
public class DefaultUrlBlockHandler implements UrlBlockHandler {

@Override
public void blocked(HttpServletRequest request, HttpServletResponse response) throws IOException {
// Directly redirect to the default flow control (blocked) page or customized block page.
FilterUtil.blockRequest(request, response);
}
}

+ 27
- 0
sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/DefaultUrlCleaner.java Переглянути файл

@@ -0,0 +1,27 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet.callback;

/***
* @author youji.zj
*/
public class DefaultUrlCleaner implements UrlCleaner {

@Override
public String clean(String originUrl) {
return originUrl;
}
}

+ 38
- 0
sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/UrlBlockHandler.java Переглянути файл

@@ -0,0 +1,38 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet.callback;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/***
* The URL block handler handles requests when blocked.
*
* @author youji.zj
*/
public interface UrlBlockHandler {

/**
* Handle the request when blocked.
*
* @param request Servlet request
* @param response Servlet response
* @throws IOException some error occurs
*/
void blocked(HttpServletRequest request, HttpServletResponse response) throws IOException;
}

+ 31
- 0
sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/UrlCleaner.java Переглянути файл

@@ -0,0 +1,31 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet.callback;

/***
* @author youji.zj
*/
public interface UrlCleaner {

/***
* <p>Process the url. Some path variables should be handled and unified.</p>
* <p>e.g. collect_item_relation--10200012121-.html will be converted to collect_item_relation.html</p>
*
* @param originUrl original url
* @return processed url
*/
String clean(String originUrl);
}

+ 50
- 0
sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/callback/WebCallbackManager.java Переглянути файл

@@ -0,0 +1,50 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet.callback;

/**
* Registry for URL cleaner and URL block handler.
*
* @author youji.zj
*/
public class WebCallbackManager {

/**
* URL cleaner
*/
private static volatile UrlCleaner urlCleaner = new DefaultUrlCleaner();

/**
* URL block handler
*/
private static volatile UrlBlockHandler urlBlockHandler = new DefaultUrlBlockHandler();

public static UrlCleaner getUrlCleaner() {
return urlCleaner;
}

public static void setUrlCleaner(UrlCleaner urlCleaner) {
WebCallbackManager.urlCleaner = urlCleaner;
}

public static UrlBlockHandler getUrlBlockHandler() {
return urlBlockHandler;
}

public static void setUrlBlockHandler(UrlBlockHandler urlBlockHandler) {
WebCallbackManager.urlBlockHandler = urlBlockHandler;
}
}

+ 42
- 0
sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/config/WebServletConfig.java Переглянути файл

@@ -0,0 +1,42 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet.config;

import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
import com.alibaba.csp.sentinel.adapter.servlet.CommonTotalFilter;
import com.alibaba.csp.sentinel.config.SentinelConfig;

/**
* @author leyou
*/
public class WebServletConfig {

public static final String BLOCK_PAGE = "csp.sentinel.web.servlet.block.page";

/**
* Get redirecting page when Sentinel blocking for {@link CommonFilter} or
* {@link CommonTotalFilter} occurs.
*
* @return the block page URL, maybe null if not configured.
*/
public static String getBlockPage() {
return SentinelConfig.getConfig(BLOCK_PAGE);
}

public static void setBlockPage(String blockPage) {
SentinelConfig.setConfig(BLOCK_PAGE, blockPage);
}
}

+ 185
- 0
sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/util/FilterUtil.java Переглянути файл

@@ -0,0 +1,185 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servlet.util;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig;
import com.alibaba.csp.sentinel.util.StringUtil;

/**
* Util class for web servlet filter.
*
* @author youji.zj
* @author Eric Zhao
*/
public final class FilterUtil {

public static String filterTarget(HttpServletRequest request) {
String pathInfo = getResourcePath(request);
if (!pathInfo.startsWith("/")) {
pathInfo = "/" + pathInfo;
}

if ("/".equals(pathInfo)) {
return pathInfo;
}

// Note: pathInfo should be converted to camelCase style.
int lastSlashIndex = pathInfo.lastIndexOf("/");

if (lastSlashIndex >= 0) {
pathInfo = pathInfo.substring(0, lastSlashIndex) + "/"
+ StringUtil.trim(pathInfo.substring(lastSlashIndex + 1));
} else {
pathInfo = "/" + StringUtil.trim(pathInfo);
}

return pathInfo;
}

public static void blockRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
StringBuffer url = request.getRequestURL();

if ("GET".equals(request.getMethod()) && StringUtil.isNotBlank(request.getQueryString())) {
url.append("?").append(request.getQueryString());
}

if (StringUtil.isEmpty(WebServletConfig.getBlockPage())) {
writeDefaultBlockedPage(response);
} else {
String redirectUrl = WebServletConfig.getBlockPage() + "?http_referer=" + url.toString();
// Redirect to the customized block page.
response.sendRedirect(redirectUrl);
}
}

private static void writeDefaultBlockedPage(HttpServletResponse response) throws IOException {
PrintWriter out = response.getWriter();
out.println("Blocked by Sentinel (flow limiting)");
out.flush();
out.close();
}

private static String getResourcePath(HttpServletRequest request) {
String pathInfo = normalizeAbsolutePath(request.getPathInfo(), false);
String servletPath = normalizeAbsolutePath(request.getServletPath(), pathInfo.length() != 0);

return servletPath + pathInfo;
}

private static String normalizeAbsolutePath(String path, boolean removeTrailingSlash) throws IllegalStateException {
return normalizePath(path, true, false, removeTrailingSlash);
}

private static String normalizePath(String path, boolean forceAbsolute, boolean forceRelative,
boolean removeTrailingSlash) throws IllegalStateException {
char[] pathChars = StringUtil.trimToEmpty(path).toCharArray();
int length = pathChars.length;

// Check path and slash.
boolean startsWithSlash = false;
boolean endsWithSlash = false;

if (length > 0) {
char firstChar = pathChars[0];
char lastChar = pathChars[length - 1];

startsWithSlash = firstChar == '/' || firstChar == '\\';
endsWithSlash = lastChar == '/' || lastChar == '\\';
}

StringBuilder buf = new StringBuilder(length);
boolean isAbsolutePath = forceAbsolute || !forceRelative && startsWithSlash;
int index = startsWithSlash ? 0 : -1;
int level = 0;

if (isAbsolutePath) {
buf.append("/");
}

while (index < length) {
index = indexOfSlash(pathChars, index + 1, false);

if (index == length) {
break;
}

int nextSlashIndex = indexOfSlash(pathChars, index, true);

String element = new String(pathChars, index, nextSlashIndex - index);
index = nextSlashIndex;

// Ignore "."
if (".".equals(element)) {
continue;
}

// Backtrack ".."
if ("..".equals(element)) {
if (level == 0) {
if (isAbsolutePath) {
throw new IllegalStateException(path);
} else {
buf.append("../");
}
} else {
buf.setLength(pathChars[--level]);
}

continue;
}

pathChars[level++] = (char)buf.length();
buf.append(element).append('/');
}

// remove the last "/"
if (buf.length() > 0) {
if (!endsWithSlash || removeTrailingSlash) {
buf.setLength(buf.length() - 1);
}
}

return buf.toString();
}

private static int indexOfSlash(char[] chars, int beginIndex, boolean slash) {
int i = beginIndex;

for (; i < chars.length; i++) {
char ch = chars[i];

if (slash) {
if (ch == '/' || ch == '\\') {
break; // if a slash
}
} else {
if (ch != '/' && ch != '\\') {
break; // if not a slash
}
}
}

return i;
}

private FilterUtil() {}
}

+ 27
- 0
sentinel-core/pom.xml Переглянути файл

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parent</artifactId>
<version>0.1.0</version>
</parent>
<artifactId>sentinel-core</artifactId>
<packaging>jar</packaging>
<description>The core of Sentinel</description>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

+ 52
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java Переглянути файл

@@ -0,0 +1,52 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel;

import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.node.EntranceNode;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
import com.alibaba.csp.sentinel.slots.system.SystemRule;

/**
* @author qinan.qn
* @author youji.zj
* @author jialiang.linjl
*/
public class Constants {

public final static int MAX_CONTEXT_NAME_SIZE = 2000;
public final static int MAX_SLOT_CHAIN_SIZE = 6000;
public final static String ROOT_ID = "machine-root";
public final static String CONTEXT_DEFAULT_NAME = "default_context_name";

public final static DefaultNode ROOT = new EntranceNode(new StringResourceWrapper(ROOT_ID, EntryType.IN),
Env.nodeBuilder.buildClusterNode());

/**
* statistics for {@link SystemRule} checking.
*/
public final static ClusterNode ENTRY_NODE = new ClusterNode();

/**
* 超过这个时间的请求不作为平均时间计算
*/
public final static int TIME_DROP_VALVE = 4900;

/*** 框架功能打开或者关闭的开关 ***/
public static volatile boolean ON = true;

}

+ 279
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java Переглянути файл

@@ -0,0 +1,279 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.context.NullContext;
import com.alibaba.csp.sentinel.node.Node;
import com.alibaba.csp.sentinel.slotchain.MethodResourceWrapper;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.Rule;

/**
* {@inheritDoc}
*
* @author jialiang.linjl
* @author leyou(lihao)
* @author Eric Zhao
* @see Sph
*/
public class CtSph implements Sph {

private static final Object[] OBJECTS0 = new Object[0];

/**
* Same resource({@link ResourceWrapper#equals(Object)}) will share the same
* {@link ProcessorSlotChain}, no matter in which {@link Context}.
*/
private static Map<ResourceWrapper, ProcessorSlotChain> chainMap
= new HashMap<ResourceWrapper, ProcessorSlotChain>();

private static final Object LOCK = new Object();

/**
* Do all {@link Rule}s checking about the resource.
*
* <p>Each distinct resource will use a {@link ProcessorSlot} to do rules checking. Same resource will use
* same {@link ProcessorSlot} globally. </p>
*
* <p>Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE},
* otherwise no rules checking will do. In this condition, all requests will pass directly, with no checking
* or exception.</p>
*
* @param resourceWrapper resource name
* @param count tokens needed
* @param args arguments of user method call
* @return {@link Entry} represents this call
* @throws BlockException if any rule's threshold is exceeded
*/
public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException {
Context context = ContextUtil.getContext();
if (context instanceof NullContext) {
// Init the entry only. No rule checking will occur.
return new CtEntry(resourceWrapper, null, context);
}

if (context == null) {
context = MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", resourceWrapper.getType());
}

// Global switch is close, no rule checking will do.
if (!Constants.ON) {
return new CtEntry(resourceWrapper, null, context);
}

ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

/*
* Means processor size exceeds {@link Constants.MAX_ENTRY_SIZE}, no
* rule checking will do.
*/
if (chain == null) {
return new CtEntry(resourceWrapper, null, context);
}

Entry e = new CtEntry(resourceWrapper, chain, context);
try {
chain.entry(context, resourceWrapper, null, count, args);
} catch (BlockException e1) {
e.exit(count, args);
throw e1;
} catch (Throwable e1) {
RecordLog.info("sentinel unexpected exception", e1);
}
return e;
}

/**
* Get {@link ProcessorSlotChain} of the resource. new {@link ProcessorSlotChain} will
* be created if the resource doesn't relate one.
*
* <p>Same resource({@link ResourceWrapper#equals(Object)}) will share the same
* {@link ProcessorSlotChain} globally, no matter in witch {@link Context}.<p/>
*
* <p>
* Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE},
* otherwise null will return.
* </p>
*
* @param resourceWrapper target resource
* @return {@link ProcessorSlotChain} of the resource
*/
private ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
if (chain == null) {
synchronized (LOCK) {
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// Entry size limit.
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}

chain = Env.slotsChainbuilder.build();
HashMap<ResourceWrapper, ProcessorSlotChain> newMap
= new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
return chain;
}

private static class CtEntry extends Entry {

protected Entry parent = null;
protected Entry child = null;
private ProcessorSlot<Object> chain;
private Context context;

CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
super(resourceWrapper);
this.chain = chain;
this.context = context;
parent = context.getCurEntry();
if (parent != null) {
((CtEntry)parent).child = this;
}
context.setCurEntry(this);
}

@Override
public void exit(int count, Object... args) throws ErrorEntryFreeException {
trueExit(count, args);
}

@Override
protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException {
if (context != null) {
if (context.getCurEntry() != this) {
// Clean previous call stack.
CtEntry e = (CtEntry)context.getCurEntry();
while (e != null) {
e.exit(count, args);
e = (CtEntry)e.parent;
}
throw new ErrorEntryFreeException(
"The order of entry free is can't be paired with the order of entry");
} else {
if (chain != null) {
chain.exit(context, resourceWrapper, count, args);
}
// Modify the call stack.
context.setCurEntry(parent);
if (parent != null) {
((CtEntry)parent).child = null;
}
if (parent == null) {
// Auto-created entry indicates immediate exit.
ContextUtil.exit();
}
// Clean the reference of context in current entry to avoid duplicate exit.
context = null;
}
}
return parent;

}

@Override
public Node getLastNode() {
return parent == null ? null : parent.getCurNode();
}
}

/**
* This class is used for skip context name checking.
*/
private final static class MyContextUtil extends ContextUtil {
static Context myEnter(String name, String origin, EntryType type) {
return trueEnter(name, origin);
}
}

@Override
public Entry entry(String name) throws BlockException {
StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT);
return entry(resource, 1, OBJECTS0);
}

@Override
public Entry entry(Method method) throws BlockException {
MethodResourceWrapper resource = new MethodResourceWrapper(method, EntryType.OUT);
return entry(resource, 1, OBJECTS0);
}

@Override
public Entry entry(Method method, EntryType type) throws BlockException {
MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
return entry(resource, 1, OBJECTS0);
}

@Override
public Entry entry(String name, EntryType type) throws BlockException {
StringResourceWrapper resource = new StringResourceWrapper(name, type);
return entry(resource, 1, OBJECTS0);
}

@Override
public Entry entry(Method method, EntryType type, int count) throws BlockException {
MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
return entry(resource, count, OBJECTS0);
}

@Override
public Entry entry(String name, EntryType type, int count) throws BlockException {
StringResourceWrapper resource = new StringResourceWrapper(name, type);
return entry(resource, count, OBJECTS0);
}

@Override
public Entry entry(Method method, int count) throws BlockException {
MethodResourceWrapper resource = new MethodResourceWrapper(method, EntryType.OUT);
return entry(resource, count, OBJECTS0);
}

@Override
public Entry entry(String name, int count) throws BlockException {
StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT);
return entry(resource, count, OBJECTS0);
}

@Override
public Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException {
MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
return entry(resource, count, args);
}

@Override
public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
StringResourceWrapper resource = new StringResourceWrapper(name, type);
return entry(resource, count, args);
}
}

+ 141
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/Entry.java Переглянути файл

@@ -0,0 +1,141 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel;

import com.alibaba.csp.sentinel.util.TimeUtil;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.node.Node;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.context.Context;

/**
* Each {@link SphU}#entry() will return an {@link Entry}. This class holds information of current invocation:<br/>
*
* <ul>
* <li>createTime, the create time of this entry, using for rt statistics.</li>
* <li>current {@link Node}, that is statistics of the resource in current context.</li>
* <li>origin {@link Node}, that is statistics for the specific origin. Usually the
* origin could be the Service Consumer's app name, see
* {@link ContextUtil#enter(String name, String origin)} </li>
* <li>{@link ResourceWrapper}, that is resource name.</li>
* <br/>
* </ul>
*
* <p>
* A invocation tree will be created if we invoke SphU#entry() multi times in the same {@link Context},
* so parent or child entry may be held by this to form the tree. Since {@link Context} always holds
* the current entry in the invocation tree, every {@link Entry#exit()} call should modify
* {@link Context#setCurEntry(Entry)} as parent entry of this.
* </p>
*
* @author qinan.qn
* @author jialiang.linjl
* @author leyou(lihao)
* @see SphU
* @see Context
* @see ContextUtil
*/
public abstract class Entry {

private static final Object[] OBJECTS0 = new Object[0];

private long createTime;
private Node curNode;
/**
* {@link Node} of the specific origin, Usually the origin is the Service Consumer.
*/
private Node originNode;
private Throwable error;
protected ResourceWrapper resourceWrapper;

public Entry(ResourceWrapper resourceWrapper) {
this.resourceWrapper = resourceWrapper;
this.createTime = TimeUtil.currentTimeMillis();
}

public ResourceWrapper getResourceWrapper() {
return resourceWrapper;
}

public void exit() throws ErrorEntryFreeException {
exit(1, OBJECTS0);
}

public void exit(int count) throws ErrorEntryFreeException {
exit(count, OBJECTS0);
}

/**
* Exit this entry. This method should invoke if and only if once at the end of the resource protection.
*
* @param count tokens to release.
* @param args
* @throws ErrorEntryFreeException, if {@link Context#getCurEntry()} is not this entry.
*/
public abstract void exit(int count, Object... args) throws ErrorEntryFreeException;

/**
* Exit this entry.
*
* @param count tokens to release.
* @param args
* @return next available entry after exit, that is the parent entry.
* @throws ErrorEntryFreeException, if {@link Context#getCurEntry()} is not this entry.
*/
protected abstract Entry trueExit(int count, Object... args) throws ErrorEntryFreeException;

/**
* Get related {@link Node} of the parent {@link Entry}.
*
* @return
*/
public abstract Node getLastNode();

public long getCreateTime() {
return createTime;
}

public Node getCurNode() {
return curNode;
}

public void setCurNode(Node node) {
this.curNode = node;
}

public Throwable getError() {
return error;
}

public void setError(Throwable error) {
this.error = error;
}

/**
* Get origin {@link Node} of the this {@link Entry}.
*
* @return origin {@link Node} of the this {@link Entry}, may be null if no origin specified by
* {@link ContextUtil#enter(String name, String origin)}.
*/
public Node getOriginNode() {
return originNode;
}

public void setOriginNode(Node originNode) {
this.originNode = originNode;
}

}

+ 47
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/EntryType.java Переглянути файл

@@ -0,0 +1,47 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel;

/**
* An enum marks resource invocation direction.
*
* @author jialiang.linjl
*/
public enum EntryType {
/**
* Inbound traffic
*/
IN("IN"),
/**
* Outbound traffic
*/
OUT("OUT");

private final String name;

EntryType(String s) {
name = s;
}

public boolean equalsName(String otherName) {
return name.equals(otherName);
}

@Override
public String toString() {
return name;
}
}

+ 38
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/Env.java Переглянути файл

@@ -0,0 +1,38 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel;

import com.alibaba.csp.sentinel.init.InitExecutor;
import com.alibaba.csp.sentinel.node.DefaultNodeBuilder;
import com.alibaba.csp.sentinel.node.NodeBuilder;
import com.alibaba.csp.sentinel.slots.DefaultSlotsChainBuilder;
import com.alibaba.csp.sentinel.slots.SlotsChainBuilder;

/**
* @author jialiang.linjl
*/
public class Env {

public static final SlotsChainBuilder slotsChainbuilder = new DefaultSlotsChainBuilder();
public static final NodeBuilder nodeBuilder = new DefaultNodeBuilder();
public static final Sph sph = new CtSph();

static {
// If init fails, the process will exit.
InitExecutor.doInit();
}

}

+ 28
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/ErrorEntryFreeException.java Переглянути файл

@@ -0,0 +1,28 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel;

/**
* Represents order mismatch of resource entry and resource exit (pair mismatch).
*
* @author qinan.qn
*/
public class ErrorEntryFreeException extends RuntimeException {

public ErrorEntryFreeException(String s) {
super(s);
}
}

+ 145
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/Sph.java Переглянути файл

@@ -0,0 +1,145 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel;

import java.lang.reflect.Method;

import com.alibaba.csp.sentinel.slots.block.BlockException;

/**
* Interface to get {@link Entry} for resource protection. If any block criteria is met,
* a {@link BlockException} or its subclasses will be thrown. Successfully getting a entry
* indicates permitting the invocation pass.
*
* @author qinan.qn
* @author jialiang.linjl
* @author leyou
*/
public interface Sph {

/**
* Create a protected resource.
*
* @param name the unique name of the protected resource
* @return entry get.
* @throws BlockException if the block criteria is met
*/
Entry entry(String name) throws BlockException;

/**
* Create a protected method.
*
* @param method the protected method
* @return entry get.
* @throws BlockException if the block criteria is met
*/
Entry entry(Method method) throws BlockException;

/**
* Create a protected method.
*
* @param method the protected method
* @param count the count that the resource requires
* @return entry get.
* @throws BlockException if the block criteria is met
*/
Entry entry(Method method, int count) throws BlockException;

/**
* Create a protected resource.
*
* @param name the unique string for the resource
* @param count the count that the resource requires
* @return entry get.
* @throws BlockException if the block criteria is met
*/
Entry entry(String name, int count) throws BlockException;

/**
* Create a protected method.
*
* @param method the protected method
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable
* @return entry get.
* @throws BlockException if the block criteria is met
*/
Entry entry(Method method, EntryType type) throws BlockException;

/**
* Create a protected resource.
*
* @param name the unique name for the protected resource
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable
* @return entry get.
* @throws BlockException if the block criteria is met
*/
Entry entry(String name, EntryType type) throws BlockException;

/**
* Create a protected method.
*
* @param method the protected method
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable
* @param count the count that the resource requires
* @return entry get.
* @throws BlockException if the block criteria is met
*/
Entry entry(Method method, EntryType type, int count) throws BlockException;

/**
* Create a protected resource.
*
* @param name the unique name for the protected resource
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable
* @param count the count that the resource requires
* @return entry get.
* @throws BlockException if the block criteria is met
*/
Entry entry(String name, EntryType type, int count) throws BlockException;

/**
* Create a protected resource.
*
* @param method the protected method
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable
* @param count the count that the resource requires
* @param args the parameters of the method. It can also be counted by setting
* hot parameter rule
* @return entry get.
* @throws BlockException if the block criteria is met
*/
Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException;

/**
* Create a protected resource.
*
* @param name the unique name for the protected resource
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable
* @param count the count that the resource requires
* @param args the parameters of the method. It can also be counted by setting
* hot parameter rule
* @return entry get.
* @throws BlockException if the block criteria is met
*/
Entry entry(String name, EntryType type, int count, Object... args) throws BlockException;

}

+ 225
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphO.java Переглянути файл

@@ -0,0 +1,225 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel;

import java.lang.reflect.Method;
import java.util.List;

import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.Rule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;

/**
* Conceptually, physical or logical resource that need protection should be
* surrounded by an entry. The requests to this resource will be blocked if any
* criteria is met, eg. when any {@link Rule}'s threshold is exceeded. Once blocked,
* {@link SphU}#enter() will return false.
*
* <p>
* To configure the criteria, we can use <code>XXXRuleManager.loadRules()</code> to add rules. eg.
* {@link FlowRuleManager#loadRules(List)}, {@link DegradeRuleManager#loadRules(List)},
* {@link SystemRuleManager#loadRules(List)}.
* </p>
*
* <p>
* Following code is an example. {@code "abc"} represent a unique name for the
* protected resource:
* </p>
*
* <pre>
* public void foo() {
* if (SphO.entry("abc")) {
* try {
* // business logic
* } finally {
* SphO.exit(); // must exit()
* }
* } else {
* // failed to enter the protected resource.
* }
* }
* </pre>
*
* Make sure {@code SphO.entry()} and {@link SphO#exit()} be paired in the same thread,
* otherwise {@link ErrorEntryFreeException} will be thrown.
*
* @author jialiang.linjl
* @author leyou
* @see SphU
*/
public class SphO {

private static final Object[] OBJECTS0 = new Object[0];

/**
* Checking all {@link Rule}s about the resource.
*
* @param name the unique name of the protected resource
* @return true if no rule's threshold is exceeded, otherwise return false.
*/
public static boolean entry(String name) {
return entry(name, EntryType.OUT, 1, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the protected method.
*
* @param method the protected method
* @return true if no rule's threshold is exceeded, otherwise return false.
*/
public static boolean entry(Method method) {
return entry(method, EntryType.OUT, 1, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the protected method.
*
* @param method the protected method
* @param count tokens required
* @return true if no rule's threshold is exceeded, otherwise return false.
*/
public static boolean entry(Method method, int count) {
return entry(method, EntryType.OUT, count, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the resource.
*
* @param name the unique string for the resource
* @param count tokens required
* @return true if no rule's threshold is exceeded, otherwise return false.
*/
public static boolean entry(String name, int count) {
return entry(name, EntryType.OUT, count, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the protected method.
*
* @param method the protected method
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @return true if no rule's threshold is exceeded, otherwise return false.
*/
public static boolean entry(Method method, EntryType type) {
return entry(method, type, 1, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the resource.
*
* @param name the unique name for the protected resource
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @return true if no rule's threshold is exceeded, otherwise return false.
*/
public static boolean entry(String name, EntryType type) {
return entry(name, type, 1, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the protected method.
*
* @param method the protected method
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @param count tokens required
* @return true if no rule's threshold is exceeded, otherwise return false.
*/
public static boolean entry(Method method, EntryType type, int count) {
return entry(method, type, count, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the resource.
*
* @param name the unique name for the protected resource
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @param count tokens required
* @return true if no rule's threshold is exceeded, otherwise return false.
*/
public static boolean entry(String name, EntryType type, int count) {
return entry(name, type, count, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the resource.
*
* @param name the unique name for the protected resource
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @param count tokens required
* @param args extra parameters.
* @return true if no rule's threshold is exceeded, otherwise return false.
*/
public static boolean entry(String name, EntryType type, int count, Object... args) {
try {
Env.sph.entry(name, type, count, args);
} catch (BlockException e) {
return false;
} catch (Throwable e) {
RecordLog.info("[Sentinel] Fatal error", e);
return true;
}
return true;
}

/**
* Checking all {@link Rule}s about the protected method.
*
* @param method the protected method
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @param count tokens required
* @param args the parameters of the method.
* @return true if no rule's threshold is exceeded, otherwise return false.
*/
public static boolean entry(Method method, EntryType type, int count, Object... args) {
try {
Env.sph.entry(method, type, count, args);
} catch (BlockException e) {
return false;
} catch (Throwable e) {
RecordLog.info("[Sentinel] Fatal error", e);
return true;
}
return true;
}

public static void exit(int count, Object... args) {
ContextUtil.getContext().getCurEntry().exit(count, args);
}

public static void exit(int count) {
ContextUtil.getContext().getCurEntry().exit(count, OBJECTS0);
}

public static void exit() {
exit(1, OBJECTS0);
}
}

+ 203
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphU.java Переглянути файл

@@ -0,0 +1,203 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel;

import java.lang.reflect.Method;
import java.util.List;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.Rule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;

/**
* Conceptually, physical or logical resource that need protection should be
* surrounded by an entry. The requests to this resource will be blocked if any
* criteria is met, eg. when any {@link Rule}'s threshold is exceeded. Once blocked,
* a {@link BlockException} will be thrown.
*
* <p>
* To configure the criteria, we can use <code>XXXRuleManager.loadRules()</code> to add rules, eg.
* {@link FlowRuleManager#loadRules(List)}, {@link DegradeRuleManager#loadRules(List)},
* {@link SystemRuleManager#loadRules(List)}.
* </p>
*
* <p>
* Following code is an example, {@code "abc"} represent a unique name for the
* protected resource:
* </p>
*
* <pre>
* public void foo() {
* Entry entry = null;
* try {
* entry = SphU.entry("abc");
* // resource that need protection
* } catch (BlockException blockException) {
* // when goes there, it is blocked
* // add blocked handle logic here
* } catch (Throwable bizException) {
* // business exception
* Tracer.trace(bizException);
* } finally {
* // ensure finally be executed
* if (entry != null){
* entry.exit();
* }
* }
* }
* </pre>
*
* <p>
* Make sure {@code SphU.entry()} and {@link Entry#exit()} be paired in the same thread,
* otherwise {@link ErrorEntryFreeException} will be thrown.
* </p>
*
* @author jialiang.linjl
* @see SphO
*/
public class SphU {

private static final Object[] OBJECTS0 = new Object[0];

/**
* Checking all {@link Rule}s about the resource.
*
* @param name the unique name of the protected resource
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
*/
public static Entry entry(String name) throws BlockException {
return Env.sph.entry(name, EntryType.OUT, 1, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the protected method.
*
* @param method the protected method
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
*/
public static Entry entry(Method method) throws BlockException {
return Env.sph.entry(method, EntryType.OUT, 1, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the protected method.
*
* @param method the protected method
* @param count tokens required
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
*/
public static Entry entry(Method method, int count) throws BlockException {
return Env.sph.entry(method, EntryType.OUT, count, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the resource.
*
* @param name the unique string for the resource
* @param count tokens required
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
*/
public static Entry entry(String name, int count) throws BlockException {
return Env.sph.entry(name, EntryType.OUT, count, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the protected method.
*
* @param method the protected method
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
*/
public static Entry entry(Method method, EntryType type) throws BlockException {
return Env.sph.entry(method, type, 1, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the resource.
*
* @param name the unique name for the protected resource
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
*/
public static Entry entry(String name, EntryType type) throws BlockException {
return Env.sph.entry(name, type, 1, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the protected method.
*
* @param method the protected method
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @param count tokens required
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
*/
public static Entry entry(Method method, EntryType type, int count) throws BlockException {
return Env.sph.entry(method, type, count, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the resource.
*
* @param name the unique name for the protected resource
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @param count tokens required
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
*/
public static Entry entry(String name, EntryType type, int count) throws BlockException {
return Env.sph.entry(name, type, count, OBJECTS0);
}

/**
* Checking all {@link Rule}s about the protected method.
*
* @param method the protected method
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @param count tokens required
* @param args the parameters of the method.
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
*/
public static Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException {
return Env.sph.entry(method, type, count, args);
}

/**
* Checking all {@link Rule}s about the resource.
*
* @param name the unique name for the protected resource
* @param type the resource is an inbound or an outbound method. This is used
* to mark whether it can be blocked when the system is unstable,
* only inbound traffic could be blocked by {@link SystemRule}
* @param count tokens required
* @param args extra parameters.
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded.
*/
public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
return Env.sph.entry(name, type, count, args);
}
}

+ 58
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/Tracer.java Переглянути файл

@@ -0,0 +1,58 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel;

import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.slots.block.BlockException;

/**
* This class is used to record other exception except block exception.
*
* @author jialiang.linjl
*/
public final class Tracer {

public static void trace(Throwable e) {
trace(e, 1);
}

public static void trace(Throwable e, int count) {
if (e instanceof BlockException) {
return;
}

Context context = ContextUtil.getContext();
if (context == null) {
return;
}

DefaultNode curNode = (DefaultNode)context.getCurNode();
if (curNode == null) {
return;
}

// clusterNode can be null when Constants.ON is false.
ClusterNode clusterNode = curNode.getClusterNode();
if (clusterNode == null) {
return;
}
clusterNode.trace(e, count);
}

}

+ 50
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/concurrent/NamedThreadFactory.java Переглянути файл

@@ -0,0 +1,50 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.concurrent;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
* Wrapped thread factory for better use.
*/
public class NamedThreadFactory implements ThreadFactory {

private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);

private final String namePrefix;
private final boolean daemon;

public NamedThreadFactory(String namePrefix, boolean daemon) {
this.daemon = daemon;
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
this.namePrefix = namePrefix;
}

public NamedThreadFactory(String namePrefix) {
this(namePrefix, false);
}

@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + "-thread-" + threadNumber.getAndIncrement(), 0);
t.setDaemon(daemon);
return t;
}
}

+ 149
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfig.java Переглянути файл

@@ -0,0 +1,149 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.config;

import java.io.File;
import java.io.FileInputStream;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import com.alibaba.csp.sentinel.log.LogBase;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.AppNameUtil;
import com.alibaba.csp.sentinel.util.StringUtil;

/**
* The universal config of Courier. The config is retrieved from
* {@code ${user.home}/logs/csp/${appName}.properties} by default.
*
* @author leyou
*/
public class SentinelConfig {

private static final Map<String, String> props = new ConcurrentHashMap<String, String>();

public static final String CHARSET = "csp.sentinel.charset";
public static final String SINGLE_METRIC_FILE_SIZE = "csp.sentinel.metric.file.single.size";
public static final String TOTAL_METRIC_FILE_COUNT = "csp.sentinel.metric.file.total.count";
public static final String COLD_FACTOR = "csp.sentinel.flow.cold.factor";

static final long DEFAULT_SINGLE_METRIC_FILE_SIZE = 1024 * 1024 * 50;
static final int DEFAULT_TOTAL_METRIC_FILE_COUNT = 6;

static {
initialize();
loadProps();
}

private static void initialize() {
// Init default properties.
SentinelConfig.setConfig(CHARSET, "UTF-8");
SentinelConfig.setConfig(SINGLE_METRIC_FILE_SIZE, String.valueOf(DEFAULT_SINGLE_METRIC_FILE_SIZE));
SentinelConfig.setConfig(TOTAL_METRIC_FILE_COUNT, String.valueOf(DEFAULT_TOTAL_METRIC_FILE_COUNT));
SentinelConfig.setConfig(COLD_FACTOR, String.valueOf(3));
}

private static void loadProps() {
// Resolve app name.
AppNameUtil.resolveAppName();
try {
String appName = AppNameUtil.getAppName();
if (appName == null) {
appName = "";
}
// We first retrieve the properties from the property file.
String fileName = LogBase.getLogBaseDir() + appName + ".properties";
File file = new File(fileName);
if (file.exists()) {
RecordLog.info("read SentinelConfig from " + fileName);
FileInputStream fis = new FileInputStream(fileName);
Properties fileProps = new Properties();
fileProps.load(fis);
fis.close();

for (Object key : fileProps.keySet()) {
SentinelConfig.setConfig((String)key, (String)fileProps.get(key));
try {
String systemValue = System.getProperty((String)key);
if (!StringUtil.isEmpty(systemValue)) {
SentinelConfig.setConfig((String)key, systemValue);
}
} catch (Exception e) {
RecordLog.info(e.getMessage(), e);
}
RecordLog.info(key + " value: " + SentinelConfig.getConfig((String)key));
}
}
} catch (Throwable ioe) {
RecordLog.info(ioe.getMessage(), ioe);
}

// JVM parameter override file config.
for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
SentinelConfig.setConfig(entry.getKey().toString(), entry.getValue().toString());
}
}

/**
* Get config value of the specific key.
*
* @param key config key
* @return the config value.
*/
public static String getConfig(String key) {
return props.get(key);
}

public static void setConfig(String key, String value) {
props.put(key, value);
}

public static void setConfigIfAbsent(String key, String value) {
String v = props.get(key);
if (v == null) {
props.put(key, value);
}
}

public static String getAppName() {
return AppNameUtil.getAppName();
}

public static String charset() {
return props.get(CHARSET);
}

public static long singleMetricFileSize() {
try {
return Long.parseLong(props.get(SINGLE_METRIC_FILE_SIZE));
} catch (Throwable throwable) {
RecordLog.info("SentinelConfig get singleMetricFileSize fail, use default value: "
+ DEFAULT_SINGLE_METRIC_FILE_SIZE, throwable);
return DEFAULT_SINGLE_METRIC_FILE_SIZE;
}
}

public static int totalMetricFileCount() {
try {
return Integer.parseInt(props.get(TOTAL_METRIC_FILE_COUNT));
} catch (Throwable throwable) {
RecordLog.info("SentinelConfig get totalMetricFileCount fail, use default value: "
+ DEFAULT_TOTAL_METRIC_FILE_COUNT, throwable);
return DEFAULT_TOTAL_METRIC_FILE_COUNT;
}
}
}

+ 164
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/Context.java Переглянути файл

@@ -0,0 +1,164 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.context;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphO;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.node.EntranceNode;
import com.alibaba.csp.sentinel.node.Node;
import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot;

/**
* This class holds metadata of current invocation:<br/>
*
* <ul>
* <li>the {@link EntranceNode}: the root of the current invocation
* tree.</li>
* <li>the current {@link Entry}: the current invocation point.</li>
* <li>the current {@link Node}: the statistics related to the
* {@link Entry}.</li>
* <li>the origin:The origin is useful when we want to control different
* invoker/consumer separately. Usually the origin could be the Service Consumer's app name. </li>
* </ul>
* <p>
* Each {@link SphU}#entry() or {@link SphO}#entry() should be in a {@link Context},
* if we don't invoke {@link ContextUtil}#enter() explicitly, DEFAULT context will be used.
* </p>
* <p>
* A invocation tree will be created if we invoke {@link SphU}#entry() multi times in
* the same context.
* </p>
* <p>
* Same resource in different context will count separately, see {@link NodeSelectorSlot}.
* </p>
*
* @author jialiang.linjl
* @author leyou(lihao)
* @author Eric Zhao
* @see ContextUtil
* @see NodeSelectorSlot
*/
public class Context {

/**
* Context name.
*/
private String name;

/**
* The entrance node of current invocation tree.
*/
private DefaultNode entranceNode;

/**
* Current processing entry.
*/
private Entry curEntry;

/**
* the origin of this context, usually the origin is the Service Consumer's app name.
*/
private String origin = "";

public Context(DefaultNode entranceNode, String name) {
super();
this.name = name;
this.entranceNode = entranceNode;
}

public String getName() {
return name;
}

public Node getCurNode() {
return curEntry.getCurNode();
}

public void setCurNode(Node node) {
this.curEntry.setCurNode(node);
}

public Entry getCurEntry() {
return curEntry;
}

public void setCurEntry(Entry curEntry) {
this.curEntry = curEntry;
}

public String getOrigin() {
return origin;
}

public void setOrigin(String origin) {
this.origin = origin;
}

public double getOriginTotalQps() {
return getOriginNode() == null ? 0 : getOriginNode().totalQps();
}

public double getOriginBlockedQps() {
return getOriginNode() == null ? 0 : getOriginNode().blockedQps();
}

public double getOriginPassedReqQps() {
return getOriginNode() == null ? 0 : getOriginNode().successQps();
}

public double getOriginPassedQps() {
return getOriginNode() == null ? 0 : getOriginNode().passQps();
}

public long getOriginTotalRequest() {
return getOriginNode() == null ? 0 : getOriginNode().totalRequest();
}

public long getOriginBlockedRequest() {
return getOriginNode() == null ? 0 : getOriginNode().blockedRequest();
}

public double getOriginAvgRt() {
return getOriginNode() == null ? 0 : getOriginNode().avgRt();
}

public int getOriginCurThreadNum() {
return getOriginNode() == null ? 0 : getOriginNode().curThreadNum();
}

public DefaultNode getEntranceNode() {
return entranceNode;
}

/**
* Get the parent {@link Node} of the current.
*
* @return the parent node of the current.
*/
public Node getLastNode() {
if (curEntry != null && curEntry.getLastNode() != null) {
return curEntry.getLastNode();
} else {
return entranceNode;
}
}

public Node getOriginNode() {
return curEntry == null ? null : curEntry.getOriginNode();
}
}

+ 26
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/ContextNameDefineException.java Переглянути файл

@@ -0,0 +1,26 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.context;

/**
* @author qinan.qn
*/
public class ContextNameDefineException extends RuntimeException {

public ContextNameDefineException(String message) {
super(message);
}
}

+ 178
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/ContextUtil.java Переглянути файл

@@ -0,0 +1,178 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.context;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

import com.alibaba.csp.sentinel.Constants;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphO;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.node.EntranceNode;
import com.alibaba.csp.sentinel.node.Node;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot;

/**
* Utility class to get or create {@link Context} in current thread.
*
* <p>
* Each {@link SphU}#entry() or {@link SphO}#entry() should be in a {@link Context}.
* If we don't invoke {@link ContextUtil}#enter() explicitly, DEFAULT context will be used.
* </p>
*
* @author jialiang.linjl
* @author leyou(lihao)
* @author Eric Zhao
*/
public class ContextUtil {

/**
* Store the context in ThreadLocal for easy access.
*/
private static ThreadLocal<Context> contextHolder = new ThreadLocal<Context>();

/**
* Holds all {@link EntranceNode}
*/
private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<String, DefaultNode>();

private static final ReentrantLock LOCK = new ReentrantLock();
private static final Context NULL_CONTEXT = new NullContext();

/**
* <p>
* Enter the invocation context. The context is ThreadLocal, meaning that
* each thread has it's own {@link Context}. New context will be created if
* current thread doesn't have one.
* </p>
* <p>
* A context will be related to a {@link EntranceNode}, which represents the entrance
* of the invocation tree. New {@link EntranceNode} will be created if
* current context does't have one. Note that same context name will share
* same {@link EntranceNode} globally.
* </p>
* <p>
* Note that each distinct {@code origin} of {@code name} will lead to creating a new
* {@link Node}, meaning that total {@link Node} created will be of:<br/>
* {@code distinct context name count * distinct origin count} <br/>
* So when origin is too many, memory efficiency should be carefully considered.
* </p>
* <p>
* Same resource in different context will count separately, see {@link NodeSelectorSlot}.
* </p>
*
* @param name the context name.
* @param origin the origin of this invocation, usually the origin could be the Service
* Consumer's app name. The origin is useful when we want to control different
* invoker/consumer separately.
* @return The invocation context of the current thread.
*/
static public Context enter(String name, String origin) {
if (Constants.CONTEXT_DEFAULT_NAME.equals(name)) {
throw new ContextNameDefineException(
"The " + Constants.CONTEXT_DEFAULT_NAME + " can't be permit to defined!");
}
return trueEnter(name, origin);
}

protected static Context trueEnter(String name, String origin) {
Context context = contextHolder.get();
if (context == null) {
Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
DefaultNode node = localCacheNameMap.get(name);
if (node == null) {
if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
return NULL_CONTEXT;
} else {
try {
LOCK.lock();
node = contextNameNodeMap.get(name);
if (node == null) {
if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
return NULL_CONTEXT;
} else {
node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
// Add entrance node.
Constants.ROOT.addChild(node);

Map<String, DefaultNode> newMap = new HashMap<String, DefaultNode>(
contextNameNodeMap.size() + 1);
newMap.putAll(contextNameNodeMap);
newMap.put(name, node);
contextNameNodeMap = newMap;
}
}
} finally {
LOCK.unlock();
}
}
}
context = new Context(node, name);
context.setOrigin(origin);
contextHolder.set(context);
}

return context;
}

/**
* <p>
* Enter the invocation context. The context is ThreadLocal, meaning that
* each thread has it's own {@link Context}. New context will be created if
* current thread doesn't have one.
* </p>
* <p>
* A context will related to A {@link EntranceNode}, which is the entrance
* of the invocation tree. New {@link EntranceNode} will be created if
* current context does't have one. Note that same resource name will share
* same {@link EntranceNode} globally.
* </p>
* <p>
* Same resource in different context will count separately, see {@link NodeSelectorSlot}.
* </p>
*
* @param name the context name.
* @return The invocation context of the current thread.
*/
public static Context enter(String name) {
return enter(name, "");
}

/**
* Exit context of current thread, that is removing {@link Context} in the
* ThreadLocal.
*/
public static void exit() {
Context context = contextHolder.get();
if (context != null && context.getCurEntry() == null) {
contextHolder.set(null);
}
}

/**
* Get {@link Context} of current thread.
*
* @return context of current thread. Null value will be return if current
* thread does't have context.
*/
public static Context getContext() {
return contextHolder.get();
}
}

+ 33
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/NullContext.java Переглянути файл

@@ -0,0 +1,33 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.context;

import com.alibaba.csp.sentinel.Constants;

/**
* If total {@link Context} exceed {@link Constants#MAX_CONTEXT_NAME_SIZE}, a
* {@link NullContext} will get when invoke {@link ContextUtil}.enter(), means
* no rules checking will do.
*
* @author qinan.qn
*/
public class NullContext extends Context {

public NullContext() {
super(null, null);
}

}

+ 83
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/BaseLoggerBuilder.java Переглянути файл

@@ -0,0 +1,83 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

class BaseLoggerBuilder<T extends BaseLoggerBuilder<T>> {

protected final String loggerName;

protected String filePath = null;

protected long maxFileSize = 1024;

protected char entryDelimiter = '|';

protected int maxBackupIndex = 3;

BaseLoggerBuilder(String loggerName) {
this.loggerName = loggerName;
}

public T logFilePath(String logFilePath) {
return configLogFilePath(logFilePath, EagleEye.EAGLEEYE_LOG_DIR);
}

public T appFilePath(String appFilePath) {
return configLogFilePath(appFilePath, EagleEye.APP_LOG_DIR);
}

public T baseLogFilePath(String baseLogFilePath) {
return configLogFilePath(baseLogFilePath, EagleEye.BASE_LOG_DIR);
}

@SuppressWarnings("unchecked")
private T configLogFilePath(String filePathToConfig, String basePath) {
EagleEyeCoreUtils.checkNotNullEmpty(filePathToConfig, "filePath");
if (filePathToConfig.charAt(0) != '/') {
filePathToConfig = basePath + filePathToConfig;
}
this.filePath = filePathToConfig;
return (T)this;
}

@SuppressWarnings("unchecked")
public T maxFileSizeMB(long maxFileSizeMB) {
if (maxFileSize < 10) {
throw new IllegalArgumentException("Invalid maxFileSizeMB");
}
this.maxFileSize = maxFileSizeMB * 1024 * 1024;
return (T)this;
}

@SuppressWarnings("unchecked")
public T maxBackupIndex(int maxBackupIndex) {
if (maxBackupIndex < 1) {
throw new IllegalArgumentException("");
}
this.maxBackupIndex = maxBackupIndex;
return (T)this;
}

@SuppressWarnings("unchecked")
public T entryDelimiter(char entryDelimiter) {
this.entryDelimiter = entryDelimiter;
return (T)this;
}

String getLoggerName() {
return loggerName;
}
}

+ 235
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEye.java Переглянути файл

@@ -0,0 +1,235 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;

public final class EagleEye {

public static final String CLASS_LOCATION = getEagleEyeLocation();

static final String USER_HOME = locateUserHome();

static final String BASE_LOG_DIR = locateBaseLogPath();

static final String EAGLEEYE_LOG_DIR = locateEagleEyeLogPath();

static final String APP_LOG_DIR = locateAppLogPath();

static final Charset DEFAULT_CHARSET = getDefaultOutputCharset();

static final String EAGLEEYE_SELF_LOG_FILE = EagleEye.EAGLEEYE_LOG_DIR + "eagleeye-self.log";

// 200MB
static final long MAX_SELF_LOG_FILE_SIZE = 200 * 1024 * 1024;

static EagleEyeAppender selfAppender = createSelfLogger();

static private TokenBucket exceptionBucket = new TokenBucket(10, TimeUnit.SECONDS.toMillis(10));

static String getEagleEyeLocation() {
try {
URL resource = EagleEye.class.getProtectionDomain().getCodeSource().getLocation();
if (resource != null) {
return resource.toString();
}
} catch (Throwable t) {
// ignore
}
return "unknown location";
}

static Charset getDefaultOutputCharset() {
Charset cs;
String charsetName = EagleEyeCoreUtils.getSystemProperty("EAGLEEYE.CHARSET");
if (EagleEyeCoreUtils.isNotBlank(charsetName)) {
charsetName = charsetName.trim();
try {
cs = Charset.forName(charsetName);
if (cs != null) {
return cs;
}
} catch (Exception e) {
// quietly
}
}
try {
cs = Charset.forName("GB18030");
} catch (Exception e) {
try {
cs = Charset.forName("GBK");
} catch (Exception e2) {
cs = Charset.forName("UTF-8");
}
}
return cs;
}

private static String locateUserHome() {
String userHome = EagleEyeCoreUtils.getSystemProperty("user.home");
if (EagleEyeCoreUtils.isNotBlank(userHome)) {
if (!userHome.endsWith(File.separator)) {
userHome += File.separator;
}
} else {
userHome = "/tmp/";
}
return userHome;
}

private static String locateBaseLogPath() {
String tmpPath = EagleEyeCoreUtils.getSystemProperty("JM.LOG.PATH");
if (EagleEyeCoreUtils.isNotBlank(tmpPath)) {
if (!tmpPath.endsWith(File.separator)) {
tmpPath += File.separator;
}
} else {
tmpPath = USER_HOME + "logs" + File.separator;
}
return tmpPath;
}

private static String locateEagleEyeLogPath() {
String tmpPath = EagleEyeCoreUtils.getSystemProperty("EAGLEEYE.LOG.PATH");
if (EagleEyeCoreUtils.isNotBlank(tmpPath)) {
if (!tmpPath.endsWith(File.separator)) {
tmpPath += File.separator;
}
} else {
tmpPath = BASE_LOG_DIR + "eagleeye" + File.separator;
}
return tmpPath;
}

private static String locateAppLogPath() {
String appName = EagleEyeCoreUtils.getSystemProperty("project.name");
if (EagleEyeCoreUtils.isNotBlank(appName)) {
return USER_HOME + appName + File.separator + "logs" + File.separator;
} else {
return EAGLEEYE_LOG_DIR;
}
}

static private final EagleEyeAppender createSelfLogger() {
EagleEyeRollingFileAppender selfAppender = new EagleEyeRollingFileAppender(EAGLEEYE_SELF_LOG_FILE,
EagleEyeCoreUtils.getSystemPropertyForLong("EAGLEEYE.LOG.SELF.FILESIZE", MAX_SELF_LOG_FILE_SIZE),
false);
return new SyncAppender(selfAppender);
}

static {
initEagleEye();
}

private static void initEagleEye() {
try {
selfLog("[INFO] EagleEye started (" + CLASS_LOCATION + ")" + ", classloader="
+ EagleEye.class.getClassLoader());
} catch (Throwable e) {
selfLog("[INFO] EagleEye started (" + CLASS_LOCATION + ")");
}

try {
EagleEyeLogDaemon.start();
} catch (Throwable e) {
selfLog("[ERROR] fail to start EagleEyeLogDaemon", e);
}
try {
StatLogController.start();
} catch (Throwable e) {
selfLog("[ERROR] fail to start StatLogController", e);
}

}

public static void shutdown() {
selfLog("[WARN] EagleEye is shutting down (" + CLASS_LOCATION + ")");

EagleEye.flush();

try {
StatLogController.stop();
EagleEye.selfLog("[INFO] StatLogController stopped");
} catch (Throwable e) {
selfLog("[ERROR] fail to stop StatLogController", e);
}

try {
EagleEyeLogDaemon.stop();
EagleEye.selfLog("[INFO] EagleEyeLogDaemon stopped");
} catch (Throwable e) {
selfLog("[ERROR] fail to stop EagleEyeLogDaemon", e);
}

EagleEye.selfLog("[WARN] EagleEye shutdown successfully (" + CLASS_LOCATION + ")");
try {
selfAppender.close();
} catch (Throwable e) {
// ignore
}
}

private EagleEye() {
}

static public StatLogger statLogger(String loggerName) {
return statLoggerBuilder(loggerName).buildSingleton();
}

static public StatLoggerBuilder statLoggerBuilder(String loggerName) {
return new StatLoggerBuilder(loggerName);
}

static void setEagelEyeSelfAppender(EagleEyeAppender appender) {
selfAppender = appender;
}

public static void selfLog(String log) {
try {
String timestamp = EagleEyeCoreUtils.formatTime(System.currentTimeMillis());
String line = "[" + timestamp + "] " + log + EagleEyeCoreUtils.NEWLINE;
selfAppender.append(line);
} catch (Throwable t) {
}
}

public static void selfLog(String log, Throwable e) {
long now = System.currentTimeMillis();
if (exceptionBucket.accept(now)) {
try {
String timestamp = EagleEyeCoreUtils.formatTime(now);
StringWriter sw = new StringWriter(4096);
PrintWriter pw = new PrintWriter(sw, false);
pw.append('[').append(timestamp).append("] ").append(log).append(EagleEyeCoreUtils.NEWLINE);
e.printStackTrace(pw);
pw.println();
pw.flush();
selfAppender.append(sw.toString());
} catch (Throwable t) {
}
}
}

static public void flush() {
EagleEyeLogDaemon.flushAndWait();
}

}

+ 45
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeAppender.java Переглянути файл

@@ -0,0 +1,45 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

public abstract class EagleEyeAppender {

public abstract void append(String log);

public void flush() {
// do nothing
}

public void rollOver() {
// do nothing
}

public void reload() {
// do nothing
}

public void close() {
// do nothing
}

public void cleanup() {
// do nothing
}

public String getOutputLocation() {
return null;
}
}

+ 237
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtils.java Переглянути файл

@@ -0,0 +1,237 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

final class EagleEyeCoreUtils {

public static final String EMPTY_STRING = "";
public static final String NEWLINE = "\r\n";

public static final String[] EMPTY_STRING_ARRAY = new String[0];

public static boolean isBlank(String str) {
int strLen;
if (str == null || (strLen = str.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if ((!Character.isWhitespace(str.charAt(i)))) {
return false;
}
}
return true;
}

public static String checkNotNullEmpty(String value, String name) throws IllegalArgumentException {
if (isBlank(value)) {
throw new IllegalArgumentException(name + " is null or empty");
}
return value;
}

public static <T> T checkNotNull(T value, String name) throws IllegalArgumentException {
if (value == null) {
throw new IllegalArgumentException(name + " is null");
}
return value;
}

public static <T> T defaultIfNull(T value, T defaultValue) {
return (value == null) ? defaultValue : value;
}

public static boolean isNotBlank(String str) {
return !isBlank(str);
}

public static boolean isNotEmpty(String str) {
return str != null && str.length() > 0;
}

public static String trim(String str) {
return str == null ? null : str.trim();
}

public static String[] split(String str, char separatorChar) {
return splitWorker(str, separatorChar, false);
}

private static String[] splitWorker(String str, char separatorChar, boolean preserveAllTokens) {
if (str == null) {
return null;
}
int len = str.length();
if (len == 0) {
return EMPTY_STRING_ARRAY;
}
List<String> list = new ArrayList<String>();
int i = 0, start = 0;
boolean match = false;
boolean lastMatch = false;
while (i < len) {
if (str.charAt(i) == separatorChar) {
if (match || preserveAllTokens) {
list.add(str.substring(start, i));
match = false;
lastMatch = true;
}
start = ++i;
continue;
}
lastMatch = false;
match = true;
i++;
}
if (match || (preserveAllTokens && lastMatch)) {
list.add(str.substring(start, i));
}
return list.toArray(new String[list.size()]);
}

public static StringBuilder appendWithBlankCheck(String str, String defaultValue, StringBuilder appender) {
if (isNotBlank(str)) {
appender.append(str);
} else {
appender.append(defaultValue);
}
return appender;
}

public static StringBuilder appendWithNullCheck(Object obj, String defaultValue, StringBuilder appender) {
if (obj != null) {
appender.append(obj.toString());
} else {
appender.append(defaultValue);
}
return appender;
}

public static StringBuilder appendLog(String str, StringBuilder appender, char delimiter) {
if (str != null) {
int len = str.length();
appender.ensureCapacity(appender.length() + len);
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
if (c == '\n' || c == '\r' || c == delimiter) {
c = ' ';
}
appender.append(c);
}
}
return appender;
}

private static final ThreadLocal<FastDateFormat> dateFmt = new ThreadLocal<FastDateFormat>() {
@Override
protected FastDateFormat initialValue() {
return new FastDateFormat();
}
};

public static String formatTime(long timestamp) {
return dateFmt.get().format(timestamp);
}

public static String getSystemProperty(String key) {
try {
return System.getProperty(key);
} catch (Throwable t) {
return null;
}
}

public static long getSystemPropertyForLong(String key, long defaultValue) {
try {
return Long.parseLong(System.getProperty(key));
} catch (Throwable t) {
return defaultValue;
}
}

public static boolean isHexNumeric(char ch) {
return (ch >= 'a' && ch <= 'f') || (ch >= '0' && ch <= '9');
}

public static boolean isNumeric(char ch) {
return ch >= '0' && ch <= '9';
}

public static void shutdownThreadPool(ExecutorService pool, long awaitTimeMillis) {
try {
pool.shutdown();

boolean done = false;
if (awaitTimeMillis > 0) {
try {
done = pool.awaitTermination(awaitTimeMillis, TimeUnit.MILLISECONDS);
} catch (Exception e) {
}
}

if (!done) {
pool.shutdownNow();
}
} catch (Exception e) {
// quietly
}
}

// Unsafe mechanics
@SuppressWarnings("restriction")
private static final sun.misc.Unsafe UNSAFE = doGetUnsafe();

@SuppressWarnings("restriction")
public static sun.misc.Unsafe getUnsafe() {
return UNSAFE;
}

/**
* Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package.
* Replace with a simple call to Unsafe.getUnsafe when integrating into a
* jdk.
*
* @return a sun.misc.Unsafe
*/
@SuppressWarnings("restriction")
private static sun.misc.Unsafe doGetUnsafe() {
try {
return sun.misc.Unsafe.getUnsafe();
} catch (Throwable tryReflectionInstead) {
}
try {
return java.security.AccessController
.doPrivileged(new java.security.PrivilegedExceptionAction<sun.misc.Unsafe>() {
@Override
public sun.misc.Unsafe run() throws Exception {
Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class;
for (java.lang.reflect.Field f : k.getDeclaredFields()) {
f.setAccessible(true);
Object x = f.get(null);
if (k.isInstance(x)) { return k.cast(x); }
}
throw new NoSuchFieldError("the Unsafe");
}
});
} catch (Throwable t) {
return null;
}
}
}

+ 140
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeLogDaemon.java Переглянути файл

@@ -0,0 +1,140 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

class EagleEyeLogDaemon implements Runnable {

private static final long LOG_CHECK_INTERVAL = TimeUnit.SECONDS.toMillis(20);

private static AtomicBoolean running = new AtomicBoolean(false);

private static Thread worker = null;

private static final CopyOnWriteArrayList<EagleEyeAppender> watchedAppenders
= new CopyOnWriteArrayList<EagleEyeAppender>();

static EagleEyeAppender watch(EagleEyeAppender appender) {
watchedAppenders.addIfAbsent(appender);
return appender;
}

static boolean unwatch(EagleEyeAppender appender) {
return watchedAppenders.remove(appender);
}

@Override
public void run() {
while (running.get()) {

cleanupFiles();

try {
Thread.sleep(LOG_CHECK_INTERVAL);
} catch (InterruptedException e) {

}

flushAndReload();
}
}

private void cleanupFiles() {
for (EagleEyeAppender watchedAppender : watchedAppenders) {
try {
watchedAppender.cleanup();
} catch (Exception e) {
EagleEye.selfLog("[ERROR] fail to cleanup: " + watchedAppender, e);
}
}
try {
EagleEye.selfAppender.cleanup();
} catch (Exception e) {
// quietly
}
}

private void flushAndReload() {
for (EagleEyeAppender watchedAppender : watchedAppenders) {
try {
watchedAppender.reload();
} catch (Exception e) {
EagleEye.selfLog("[ERROR] fail to reload: " + watchedAppender, e);
}
}
try {
EagleEye.selfAppender.reload();
} catch (Exception e) {
// quietly
}
}

static void start() {
if (running.compareAndSet(false, true)) {
Thread worker = new Thread(new EagleEyeLogDaemon());
worker.setDaemon(true);
worker.setName("EagleEye-LogDaemon-Thread");
worker.start();
EagleEyeLogDaemon.worker = worker;
}
}

static void stop() {
if (running.compareAndSet(true, false)) {

closeAppenders();

final Thread worker = EagleEyeLogDaemon.worker;
if (worker != null) {
try {
worker.interrupt();
} catch (Exception e) {
// ignore
}
try {
worker.join(1000);
} catch (Exception e) {
// ignore
}
}
}
}

private static void closeAppenders() {
for (EagleEyeAppender watchedAppender : watchedAppenders) {
try {
watchedAppender.close();
} catch (Exception e) {
EagleEye.selfLog("[ERROR] fail to close: " + watchedAppender, e);
}
}
}

static void flushAndWait() {
for (EagleEyeAppender watchedAppender : watchedAppenders) {
try {
watchedAppender.flush();
} catch (Exception e) {
EagleEye.selfLog("[ERROR] fail to flush: " + watchedAppender, e);
}
}
}

private EagleEyeLogDaemon() {}
}

+ 332
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeRollingFileAppender.java Переглянути файл

@@ -0,0 +1,332 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

class EagleEyeRollingFileAppender extends EagleEyeAppender {

private static final long LOG_FLUSH_INTERVAL = TimeUnit.SECONDS.toMillis(1);

private static final int DEFAULT_BUFFER_SIZE = 4 * 1024; // 4KB

private final int maxBackupIndex = 3;

private final long maxFileSize;

private final int bufferSize = DEFAULT_BUFFER_SIZE;

private final String filePath;

private final AtomicBoolean isRolling = new AtomicBoolean(false);

private BufferedOutputStream bos = null;

private long nextFlushTime = 0L;

private long lastRollOverTime = 0L;

private long outputByteSize = 0L;

private final boolean selfLogEnabled;

private boolean multiProcessDetected = false;

private static final String DELETE_FILE_SUFFIX = ".deleted";

public EagleEyeRollingFileAppender(String filePath, long maxFileSize) {
this(filePath, maxFileSize, true);
}

public EagleEyeRollingFileAppender(String filePath, long maxFileSize, boolean selfLogEnabled) {
this.filePath = filePath;
this.maxFileSize = maxFileSize;
this.selfLogEnabled = selfLogEnabled;
setFile();
}

private void setFile() {
try {
File logFile = new File(filePath);
if (!logFile.exists()) {
File parentFile = logFile.getParentFile();
if (!parentFile.exists() && !parentFile.mkdirs()) {
doSelfLog("[ERROR] Fail to mkdirs: " + parentFile.getAbsolutePath());
return;
}
try {
if (!logFile.createNewFile()) {
doSelfLog("[ERROR] Fail to create file, it exists: " + logFile.getAbsolutePath());
}
} catch (IOException e) {
doSelfLog(
"[ERROR] Fail to create file: " + logFile.getAbsolutePath() + ", error=" + e.getMessage());
}
}
if (!logFile.isFile() || !logFile.canWrite()) {
doSelfLog("[ERROR] Invalid file, exists=" + logFile.exists() + ", isFile=" + logFile.isFile()
+ ", canWrite=" + logFile.canWrite() + ", path=" + logFile.getAbsolutePath());
return;
}
FileOutputStream ostream = new FileOutputStream(logFile, true);
// true
// O_APPEND
this.bos = new BufferedOutputStream(ostream, bufferSize);
this.lastRollOverTime = System.currentTimeMillis();
this.outputByteSize = logFile.length();
} catch (Throwable e) {
doSelfLog("[ERROR] Fail to create file to write: " + filePath + ", error=" + e.getMessage());
}
}

@Override
public void append(String log) {
BufferedOutputStream bos = this.bos;
if (bos != null) {
try {
waitUntilRollFinish();

byte[] bytes = log.getBytes(EagleEye.DEFAULT_CHARSET);
int len = bytes.length;
if (len > DEFAULT_BUFFER_SIZE && this.multiProcessDetected) {
len = DEFAULT_BUFFER_SIZE;
bytes[len - 1] = '\n';
}
bos.write(bytes, 0, len);
outputByteSize += len;

if (outputByteSize >= maxFileSize) {
rollOver();
} else {
if (System.currentTimeMillis() >= nextFlushTime) {
flush();
}
}
} catch (Exception e) {
doSelfLog("[ERROR] fail to write log to file " + filePath + ", error=" + e.getMessage());
close();
setFile();
}
}
}

@Override
public void flush() {
final BufferedOutputStream bos = this.bos;
if (bos != null) {
try {
bos.flush();
nextFlushTime = System.currentTimeMillis() + LOG_FLUSH_INTERVAL;
} catch (Exception e) {
doSelfLog("[WARN] Fail to flush OutputStream: " + filePath + ", " + e.getMessage());
}
}
}

@Override
public void rollOver() {
final String lockFilePath = filePath + ".lock";
final File lockFile = new File(lockFilePath);

RandomAccessFile raf = null;
FileLock fileLock = null;

if (!isRolling.compareAndSet(false, true)) {
return;
}

try {
raf = new RandomAccessFile(lockFile, "rw");
fileLock = raf.getChannel().tryLock();

if (fileLock != null) {
File target;
File file;
final int maxBackupIndex = this.maxBackupIndex;

reload();
if (outputByteSize >= maxFileSize) {
file = new File(filePath + '.' + maxBackupIndex);
if (file.exists()) {
target = new File(filePath + '.' + maxBackupIndex + DELETE_FILE_SUFFIX);
if (!file.renameTo(target) && !file.delete()) {
doSelfLog("[ERROR] Fail to delete or rename file: " + file.getAbsolutePath() + " to "
+ target.getAbsolutePath());
}
}

for (int i = maxBackupIndex - 1; i >= 1; i--) {
file = new File(filePath + '.' + i);
if (file.exists()) {
target = new File(filePath + '.' + (i + 1));
if (!file.renameTo(target) && !file.delete()) {
doSelfLog("[ERROR] Fail to delete or rename file: " + file.getAbsolutePath() + " to "
+ target.getAbsolutePath());
}
}
}

target = new File(filePath + "." + 1);

close();

file = new File(filePath);
if (file.renameTo(target)) {
doSelfLog("[INFO] File rolled to " + target.getAbsolutePath() + ", "
+ TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - lastRollOverTime)
+ " minutes since last roll");
} else {
doSelfLog("[WARN] Fail to rename file: " + file.getAbsolutePath() + " to "
+ target.getAbsolutePath());
}

setFile();
}
}
} catch (IOException e) {
doSelfLog("[ERROR] Fail rollover file: " + filePath + ", error=" + e.getMessage());
} finally {
isRolling.set(false);

if (fileLock != null) {
try {
fileLock.release();
} catch (IOException e) {
doSelfLog("[ERROR] Fail to release file lock: " + lockFilePath + ", error=" + e.getMessage());
}
}

if (raf != null) {
try {
raf.close();
} catch (IOException e) {
doSelfLog("[WARN] Fail to close file lock: " + lockFilePath + ", error=" + e.getMessage());
}
}

if (fileLock != null) {
if (!lockFile.delete() && lockFile.exists()) {
doSelfLog("[WARN] Fail to delete file lock: " + lockFilePath);
}
}
}
}

@Override
public void close() {
BufferedOutputStream bos = this.bos;
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
doSelfLog("[WARN] Fail to close OutputStream: " + e.getMessage());
}
this.bos = null;
}
}

@Override
public void reload() {
flush();
File logFile = new File(filePath);
long fileSize = logFile.length();
boolean fileNotExists = fileSize <= 0 && !logFile.exists();

if (this.bos == null || fileSize < outputByteSize || fileNotExists) {
doSelfLog("[INFO] Log file rolled over by outside: " + filePath + ", force reload");
close();
setFile();
} else if (fileSize > outputByteSize) {
this.outputByteSize = fileSize;
if (!this.multiProcessDetected) {
this.multiProcessDetected = true;
if (selfLogEnabled) {
doSelfLog("[WARN] Multi-process file write detected: " + filePath);
}
}
} else {

}
}

@Override
public void cleanup() {
try {
File logFile = new File(filePath);
File parentDir = logFile.getParentFile();
if (parentDir != null && parentDir.isDirectory()) {
final String baseFileName = logFile.getName();
File[] filesToDelete = parentDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
if (name != null && name.startsWith(baseFileName) && name.endsWith(DELETE_FILE_SUFFIX)) {
return true;
}
return false;
}
});
if (filesToDelete != null && filesToDelete.length > 0) {
for (File f : filesToDelete) {
boolean success = f.delete() || !f.exists();
if (success) {
doSelfLog("[INFO] Deleted log file: " + f.getAbsolutePath());
} else if (f.exists()) {
doSelfLog("[ERROR] Fail to delete log file: " + f.getAbsolutePath());
}
}
}
}
} catch (Exception e) {
doSelfLog("[ERROR] Fail to cleanup log file, error=" + e.getMessage());
}
}

void waitUntilRollFinish() {
while (isRolling.get()) {
try {
Thread.sleep(1L);
} catch (Exception e) {
// quietly
}
}
}

private void doSelfLog(String log) {
if (selfLogEnabled) {
EagleEye.selfLog(log);
} else {
System.out.println("[EagleEye]" + log);
}
}

@Override
public String getOutputLocation() {
return filePath;
}

@Override
public String toString() {
return "EagleEyeRollingFileAppender [filePath=" + filePath + "]";
}
}

+ 81
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/FastDateFormat.java Переглянути файл

@@ -0,0 +1,81 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

class FastDateFormat {

private final SimpleDateFormat fmt = createSimpleDateFormat();

private char[] buffer = new char[23];

private long lastSecond = -1;
private long lastMillis = -1;

public String format(long timestamp) {
formatToBuffer(timestamp);
return new String(buffer, 0, 23);
}

public String format(Date date) {
return format(date.getTime());
}

public void formatAndAppendTo(long timestamp, StringBuilder appender) {
formatToBuffer(timestamp);
appender.append(buffer, 0, 23);
}

private void formatToBuffer(long timestamp) {
if (timestamp == lastMillis) {
return;
}
long diff = timestamp - lastSecond;
if (diff >= 0 && diff < 1000) {
int ms = (int)(timestamp % 1000);
buffer[22] = (char)(ms % 10 + '0');
ms /= 10;
buffer[21] = (char)(ms % 10 + '0');
buffer[20] = (char)(ms / 10 + '0');
lastMillis = timestamp;
} else {
String result = fmt.format(new Date(timestamp));
result.getChars(0, result.length(), buffer, 0);
lastSecond = timestamp / 1000 * 1000;
lastMillis = timestamp;
}
}

String formatWithoutMs(long timestamp) {
long diff = timestamp - lastSecond;
if (diff < 0 || diff >= 1000) {
String result = fmt.format(new Date(timestamp));
result.getChars(0, result.length(), buffer, 0);
lastSecond = timestamp / 1000 * 1000;
lastMillis = timestamp;
}
return new String(buffer, 0, 19);
}

private SimpleDateFormat createSimpleDateFormat() {
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
fmt.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
return fmt;
}
}

+ 179
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatEntry.java Переглянути файл

@@ -0,0 +1,179 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

import java.util.Arrays;
import java.util.List;

public final class StatEntry {

private final StatLogger statLogger;

private final String[] keys;
private transient int hash;

public StatEntry(StatLogger statLogger, String key) {
this.statLogger = statLogger;
this.keys = new String[] {key};
}

public StatEntry(StatLogger statLogger, String key1, String key2) {
this.statLogger = statLogger;
this.keys = new String[] {key1, key2};
}

public StatEntry(StatLogger statLogger, String key1, String key2, String key3) {
this.statLogger = statLogger;
this.keys = new String[] {key1, key2, key3};
}

public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4) {
this.statLogger = statLogger;
this.keys = new String[] {key1, key2, key3, key4};
}

public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5) {
this.statLogger = statLogger;
this.keys = new String[] {key1, key2, key3, key4, key5};
}

public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5,
String key6) {
this.statLogger = statLogger;
this.keys = new String[] {key1, key2, key3, key4, key5, key6};
}

public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5,
String key6, String key7) {
this.statLogger = statLogger;
this.keys = new String[] {key1, key2, key3, key4, key5, key6, key7};
}

public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5,
String key6, String key7, String key8) {
this.statLogger = statLogger;
this.keys = new String[] {key1, key2, key3, key4, key5, key6, key7, key8};
}

public StatEntry(StatLogger statLogger, String key1, String... moreKeys) {
String[] keys = new String[1 + moreKeys.length];
keys[0] = key1;
for (int i = 0; i < moreKeys.length; ++i) {
keys[i + 1] = moreKeys[i];
}
this.statLogger = statLogger;
this.keys = keys;
}

public StatEntry(StatLogger statLogger, List<String> keys) {
if (keys == null || keys.isEmpty()) {
throw new IllegalArgumentException("keys empty or null: " + keys);
}
this.statLogger = statLogger;
this.keys = keys.toArray(new String[keys.size()]);
}

public StatEntry(StatLogger statLogger, String[] keys) {
if (keys == null || keys.length == 0) {
throw new IllegalArgumentException("keys empty or null");
}
this.statLogger = statLogger;
this.keys = Arrays.copyOf(keys, keys.length);
}

public String[] getKeys() {
return keys;
}

void appendTo(StringBuilder appender, char delimiter) {
final int len = keys.length;
if (len > 0) {
appender.append(keys[0]);
for (int i = 1; i < len; ++i) {
appender.append(delimiter).append(keys[i]);
}
}
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder(64);
sb.append("StatKeys [");
appendTo(sb, ',');
sb.append("]");
return sb.toString();
}

@Override
public int hashCode() {
if (hash == 0) {
int result = 1;
result = 31 * result + Arrays.hashCode(keys);
hash = result;
}
return hash;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
StatEntry other = (StatEntry)obj;
if (hash != 0 && other.hash != 0 && hash != other.hash) {
return false;
}
if (!Arrays.equals(keys, other.keys)) {
return false;
}

return true;
}

StatEntryFunc getFunc(final StatEntryFuncFactory factory) {
return this.statLogger.getRollingData().getStatEntryFunc(this, factory);
}

public void count() {
count(1);
}

public void count(long count) {
getFunc(StatEntryFuncFactory.COUNT_SUM).count(count);
}

public void countAndSum(long valueToSum) {
countAndSum(1, valueToSum);
}

public void countAndSum(long count, long valueToSum) {
getFunc(StatEntryFuncFactory.COUNT_SUM).countAndSum(count, valueToSum);
}

public void minMax(long candidate) {
minMax(candidate, null);
}

public void minMax(long candidate, String ref) {
getFunc(StatEntryFuncFactory.MIN_MAX).minMax(candidate, ref);
}
}

+ 206
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatEntryFunc.java Переглянути файл

@@ -0,0 +1,206 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

import java.util.concurrent.atomic.AtomicReference;

import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder;

interface StatEntryFunc {

void appendTo(StringBuilder appender, char delimiter);

int getStatType();

Object[] getValues();

void count(long count);

void countAndSum(long count, long value);

void arrayAdd(long... values);

void arraySet(long... values);

void minMax(long candidate, String ref);

void batchAdd(long... values);

void strArray(String... values);
}

enum StatEntryFuncFactory {
COUNT_SUM {
@Override
StatEntryFunc create() {
return new StatEntryFuncCountAndSum();
}
},
MIN_MAX {
@Override
StatEntryFunc create() {
return new StatEntryFuncMinMax();
}
};

abstract StatEntryFunc create();
}

class StatEntryFuncCountAndSum implements StatEntryFunc {

private LongAdder count = new LongAdder();
private LongAdder value = new LongAdder();

@Override
public void appendTo(StringBuilder appender, char delimiter) {
appender.append(count.sum()).append(delimiter).append(value.sum());
}

@Override
public Object[] getValues() {
return new Object[] {count.sum(), value.sum()};
}

@Override
public int getStatType() {
return 1;
}

@Override
public void count(long count) {
this.count.add(count);
}

@Override
public void countAndSum(long count, long value) {
this.count.add(count);
this.value.add(value);
}

@Override
public void arrayAdd(long... values) {
throw new IllegalStateException("arrayAdd() is unavailable if countAndSum() has been called");
}

@Override
public void arraySet(long... values) {
throw new IllegalStateException("arraySet() is unavailable if countAndSum() has been called");
}

@Override
public void minMax(long candidate, String ref) {
throw new IllegalStateException("minMax() is unavailable if countAndSum() has been called");
}

@Override
public void batchAdd(long... values) {
throw new IllegalStateException("batchAdd() is unavailable if countAndSum() has been called");
}

@Override
public void strArray(String... values) {
throw new IllegalStateException("strArray() is unavailable if countAndSum() has been called");
}
}

class StatEntryFuncMinMax implements StatEntryFunc {

private AtomicReference<ValueRef> max = new AtomicReference<ValueRef>(new ValueRef(Long.MIN_VALUE, null));
private AtomicReference<ValueRef> min = new AtomicReference<ValueRef>(new ValueRef(Long.MAX_VALUE, null));

@Override
public void appendTo(StringBuilder appender, char delimiter) {
ValueRef lmax = max.get();
ValueRef lmin = min.get();

appender.append(lmax.value).append(delimiter);
if (lmax.ref != null) {
appender.append(lmax.ref);
}
appender.append(delimiter);

appender.append(lmin.value).append(delimiter);
if (lmin.ref != null) {
appender.append(lmin.ref);
}
}

@Override
public Object[] getValues() {
ValueRef lmax = max.get();
ValueRef lmin = min.get();
return new Object[] {lmax.value, lmax.ref, lmin.value, lmin.ref};
}

@Override
public int getStatType() {
return 4;
}

@Override
public void count(long count) {
throw new IllegalStateException("count() is unavailable if minMax() has been called");
}

@Override
public void countAndSum(long count, long value) {
throw new IllegalStateException("countAndSum() is unavailable if minMax() has been called");
}

@Override
public void arrayAdd(long... values) {
throw new IllegalStateException("arrayAdd() is unavailable if minMax() has been called");
}

@Override
public void arraySet(long... values) {
throw new IllegalStateException("arraySet() is unavailable if minMax() has been called");
}

@Override
public void batchAdd(long... values) {
throw new IllegalStateException("batchAdd() is unavailable if minMax() has been called");
}

@Override
public void minMax(long candidate, String ref) {
ValueRef lmax = max.get();
if (lmax.value <= candidate) {
final ValueRef cmax = new ValueRef(candidate, ref);
while (!max.compareAndSet(lmax, cmax) && (lmax = max.get()).value <= candidate) { ; }
}
ValueRef lmin = min.get();
if (lmin.value >= candidate) {
final ValueRef cmin = new ValueRef(candidate, ref);
while (!min.compareAndSet(lmin, cmin) && (lmin = min.get()).value >= candidate) { ; }
}
}

@Override
public void strArray(String... values) {
throw new IllegalStateException("strArray() is unavailable if minMax() has been called");
}

private static final class ValueRef {
final long value;
final String ref;

ValueRef(long value, String ref) {
this.value = value;
this.ref = ref;
}
}
}

+ 190
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatLogController.java Переглянути файл

@@ -0,0 +1,190 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;

class StatLogController {

private static final Map<String, StatLogger> statLoggers = new ConcurrentHashMap<String, StatLogger>();

private static final int STAT_ENTRY_COOL_DOWN_MILLIS = 200;

private static final ScheduledThreadPoolExecutor rollerThreadPool =
new ScheduledThreadPoolExecutor(1, new NamedThreadFactory(
"EagleEye-StatLogController-roller", true));

private static final ScheduledThreadPoolExecutor writerThreadPool =
new ScheduledThreadPoolExecutor(1, new NamedThreadFactory(
"EagleEye-StatLogController-writer", true));

private static AtomicBoolean running = new AtomicBoolean(false);

static StatLogger createLoggerIfNotExists(StatLoggerBuilder builder) {
String loggerName = builder.getLoggerName();
StatLogger statLogger = statLoggers.get(loggerName);
if (statLogger == null) {
synchronized (StatLogController.class) {
if ((statLogger = statLoggers.get(loggerName)) == null) {
statLogger = builder.create();
statLoggers.put(loggerName, statLogger);

writerThreadPool.setMaximumPoolSize(Math.max(1, statLoggers.size()));

scheduleNextRollingTask(statLogger);
EagleEye.selfLog("[INFO] created statLogger[" + statLogger.getLoggerName() +
"]: " + statLogger.getAppender());
}
}
}
return statLogger;
}

static Map<String, StatLogger> getAllStatLoggers() {
return Collections.unmodifiableMap(statLoggers);
}

private static void scheduleNextRollingTask(StatLogger statLogger) {
if (!running.get()) {
EagleEye.selfLog("[INFO] stopped rolling statLogger[" + statLogger.getLoggerName() + "]");
return;
}

StatLogRollingTask rollingTask = new StatLogRollingTask(statLogger);

long rollingTimeMillis = statLogger.getRollingData().getRollingTimeMillis();
long delayMillis = rollingTimeMillis - System.currentTimeMillis();
if (delayMillis > 5) {
rollerThreadPool.schedule(rollingTask, delayMillis, TimeUnit.MILLISECONDS);
} else if (-delayMillis > statLogger.getIntervalMillis()) {
EagleEye.selfLog("[WARN] unusual delay of statLogger[" + statLogger.getLoggerName() +
"], delay=" + (-delayMillis) + "ms, submit now");
rollerThreadPool.submit(rollingTask);
} else {
rollerThreadPool.submit(rollingTask);
}
}

static void scheduleWriteTask(StatRollingData statRollingData) {
if (statRollingData != null) {
try {
StatLogWriteTask task = new StatLogWriteTask(statRollingData);
writerThreadPool.schedule(task, STAT_ENTRY_COOL_DOWN_MILLIS, TimeUnit.MILLISECONDS);
} catch (Throwable t) {
EagleEye.selfLog("[ERROR] fail to roll statLogger[" +
statRollingData.getStatLogger().getLoggerName() + "]", t);
}
}
}

private static class StatLogRollingTask implements Runnable {

final StatLogger statLogger;

StatLogRollingTask(StatLogger statLogger) {
this.statLogger = statLogger;
}

@Override
public void run() {
scheduleWriteTask(statLogger.rolling());
scheduleNextRollingTask(statLogger);
}
}

private static class StatLogWriteTask implements Runnable {

final StatRollingData statRollingData;

StatLogWriteTask(StatRollingData statRollingData) {
this.statRollingData = statRollingData;
}

@Override
public void run() {
final StatRollingData data = statRollingData;
final StatLogger logger = data.getStatLogger();
try {
final FastDateFormat fmt = new FastDateFormat();
final StringBuilder buffer = new StringBuilder(256);
final String timeStr = fmt.formatWithoutMs(data.getTimeSlot());

final EagleEyeAppender appender = logger.getAppender();
final Set<Entry<StatEntry, StatEntryFunc>> entrySet = data.getStatEntrySet();
final char entryDelimiter = logger.getEntryDelimiter();
final char keyDelimiter = logger.getKeyDelimiter();
final char valueDelimiter = logger.getValueDelimiter();

for (Entry<StatEntry, StatEntryFunc> entry : entrySet) {
buffer.delete(0, buffer.length());
StatEntryFunc func = entry.getValue();
// time|statType|keys|values
buffer.append(timeStr).append(entryDelimiter);
buffer.append(func.getStatType()).append(entryDelimiter);
entry.getKey().appendTo(buffer, keyDelimiter);
buffer.append(entryDelimiter);
func.appendTo(buffer, valueDelimiter);
buffer.append(EagleEyeCoreUtils.NEWLINE);
appender.append(buffer.toString());
}

appender.flush();
} catch (Throwable t) {
EagleEye.selfLog("[WARN] fail to write statLogger[" +
logger.getLoggerName() + "]", t);
}
}
}

static void start() {
if (running.compareAndSet(false, true)) {
rollerThreadPool.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
writerThreadPool.setExecuteExistingDelayedTasksAfterShutdownPolicy(true);
}
}

static void stop() {
if (running.compareAndSet(true, false)) {
EagleEyeCoreUtils.shutdownThreadPool(rollerThreadPool, 0);
EagleEye.selfLog("[INFO] StatLoggerController: roller ThreadPool shutdown successfully");

for (StatLogger statLogger : statLoggers.values()) {
new StatLogRollingTask(statLogger).run();
}

try {
Thread.sleep(STAT_ENTRY_COOL_DOWN_MILLIS);
} catch (InterruptedException e) {
// quietly
}

EagleEyeCoreUtils.shutdownThreadPool(writerThreadPool, 2000);
EagleEye.selfLog("[INFO] StatLoggerController: writer ThreadPool shutdown successfully");
}
}

private StatLogController() {
}
}

+ 145
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatLogger.java Переглянути файл

@@ -0,0 +1,145 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

/**
* @author jifeng
*/
public final class StatLogger {

private final String loggerName;

private final EagleEyeAppender appender;

private final AtomicReference<StatRollingData> ref;

private final long intervalMillis;

private final int maxEntryCount;

private final char entryDelimiter;
private final char keyDelimiter;
private final char valueDelimiter;

StatLogger(String loggerName, EagleEyeAppender appender, long intervalMillis, int maxEntryCount,
char entryDelimiter, char keyDelimiter, char valueDelimiter) {
this.loggerName = loggerName;
this.appender = appender;
this.intervalMillis = intervalMillis;
this.maxEntryCount = maxEntryCount;
this.entryDelimiter = entryDelimiter;
this.keyDelimiter = keyDelimiter;
this.valueDelimiter = valueDelimiter;
this.ref = new AtomicReference<StatRollingData>();
rolling();
}

public String getLoggerName() {
return loggerName;
}

EagleEyeAppender getAppender() {
return appender;
}

StatRollingData getRollingData() {
return ref.get();
}

long getIntervalMillis() {
return intervalMillis;
}

int getMaxEntryCount() {
return maxEntryCount;
}

char getEntryDelimiter() {
return entryDelimiter;
}

char getKeyDelimiter() {
return keyDelimiter;
}

char getValueDelimiter() {
return valueDelimiter;
}

StatRollingData rolling() {
do {
long now = System.currentTimeMillis();
long timeSlot = now - now % intervalMillis;

StatRollingData prevData = ref.get();
long rollingTimeMillis = timeSlot + intervalMillis;
int initialCapacity = prevData != null ? prevData.getStatCount() : 16;
StatRollingData nextData = new StatRollingData(
this, initialCapacity, timeSlot, rollingTimeMillis);
if (ref.compareAndSet(prevData, nextData)) {
return prevData;
}
} while (true);
}

public StatEntry stat(String key) {
return new StatEntry(this, key);
}

public StatEntry stat(String key1, String key2) {
return new StatEntry(this, key1, key2);
}

public StatEntry stat(String key1, String key2, String key3) {
return new StatEntry(this, key1, key2, key3);
}

public StatEntry stat(String key1, String key2, String key3, String key4) {
return new StatEntry(this, key1, key2, key3, key4);
}

public StatEntry stat(String key1, String key2, String key3, String key4, String key5) {
return new StatEntry(this, key1, key2, key3, key4, key5);
}

public StatEntry stat(String key1, String key2, String key3, String key4, String key5, String key6) {
return new StatEntry(this, key1, key2, key3, key4, key5, key6);
}

public StatEntry stat(String key1, String key2, String key3, String key4, String key5, String key6, String key7) {
return new StatEntry(this, key1, key2, key3, key4, key5, key6, key7);
}

public StatEntry stat(String key1, String key2, String key3, String key4, String key5, String key6, String key7,
String key8) {
return new StatEntry(this, key1, key2, key3, key4, key5, key6, key7, key8);
}

public StatEntry stat(String key1, String... moreKeys) {
return new StatEntry(this, key1, moreKeys);
}

public StatEntry stat(List<String> keys) {
return new StatEntry(this, keys);
}

public StatEntry stat(String[] keys) {
return new StatEntry(this, keys);
}
}

+ 113
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatLoggerBuilder.java Переглянути файл

@@ -0,0 +1,113 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

import java.util.concurrent.TimeUnit;

/**
* @author jifeng
*/
public final class StatLoggerBuilder extends BaseLoggerBuilder<StatLoggerBuilder> {

private int intervalSeconds = 60;

private int maxEntryCount = 20000;

private char keyDelimiter = ',';

private char valueDelimiter = ',';

private EagleEyeAppender appender = null;

StatLoggerBuilder(String loggerName) {
super(loggerName);
}

public StatLoggerBuilder intervalSeconds(int intervalSeconds) {
validateInterval(intervalSeconds);
this.intervalSeconds = intervalSeconds;
return this;
}

public StatLoggerBuilder maxEntryCount(int maxEntryCount) {
if (maxEntryCount < 1) {
throw new IllegalArgumentException("Max entry count should be at least 1: " + maxEntryCount);
}
this.maxEntryCount = maxEntryCount;
return this;
}

public StatLoggerBuilder keyDelimiter(char keyDelimiter) {
this.keyDelimiter = keyDelimiter;
return this;
}

public StatLoggerBuilder valueDelimiter(char valueDelimiter) {
this.valueDelimiter = valueDelimiter;
return this;
}

StatLoggerBuilder appender(EagleEyeAppender appender) {
this.appender = appender;
return this;
}

StatLogger create() {
long intervalMillis = TimeUnit.SECONDS.toMillis(this.intervalSeconds);

String filePath;
if (this.filePath == null) {
filePath = EagleEye.EAGLEEYE_LOG_DIR + "stat-" + loggerName + ".log";
} else if (this.filePath.endsWith("/") || this.filePath.endsWith("\\")) {
filePath = this.filePath + "stat-" + loggerName + ".log";
} else {
filePath = this.filePath;
}

EagleEyeAppender appender = this.appender;
if (appender == null) {
EagleEyeRollingFileAppender rfAppender = new EagleEyeRollingFileAppender(filePath, maxFileSize);
appender = new SyncAppender(rfAppender);
}

EagleEyeLogDaemon.watch(appender);
return new StatLogger(loggerName, appender, intervalMillis, maxEntryCount,
entryDelimiter, keyDelimiter, valueDelimiter);
}

public StatLogger buildSingleton() {
return StatLogController.createLoggerIfNotExists(this);
}

static void validateInterval(final long intervalSeconds) throws IllegalArgumentException {
if (intervalSeconds < 1) {
throw new IllegalArgumentException("Interval cannot be less than 1" + intervalSeconds);
} else if (intervalSeconds < 60) {
if (60 % intervalSeconds != 0) {
throw new IllegalArgumentException("Invalid second interval (cannot divide by 60): " + intervalSeconds);
}
} else if (intervalSeconds <= 5 * 60) {
if (intervalSeconds % 60 != 0) {
throw new IllegalArgumentException("Invalid second interval (cannot divide by 60): " + intervalSeconds);
}
if (60 % intervalSeconds != 0) {
throw new IllegalArgumentException("Invalid second interval (cannot divide by 60): " + intervalSeconds);
}
} else if (intervalSeconds > 5 * 60) {
throw new IllegalArgumentException("Interval should be less than 5 min: " + intervalSeconds);
}
}
}

+ 108
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/StatRollingData.java Переглянути файл

@@ -0,0 +1,108 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

/**
* @author jifeng
*/
final class StatRollingData {

private final StatLogger statLogger;

private final long timeSlot;

private final long rollingTimeMillis;

private final ReentrantLock writeLock;

private final Map<StatEntry, StatEntryFunc> statMap;

StatRollingData(StatLogger statLogger, int initialCapacity, long timeSlot, long rollingTimeMillis) {
this(statLogger, timeSlot, rollingTimeMillis,
new ConcurrentHashMap<StatEntry, StatEntryFunc>(
Math.min(initialCapacity, statLogger.getMaxEntryCount())));
}

private StatRollingData(StatLogger statLogger, long timeSlot, long rollingTimeMillis,
Map<StatEntry, StatEntryFunc> statMap) {
this.statLogger = statLogger;
this.timeSlot = timeSlot;
this.rollingTimeMillis = rollingTimeMillis;
this.writeLock = new ReentrantLock();
this.statMap = statMap;
}

StatEntryFunc getStatEntryFunc(
final StatEntry statEntry, final StatEntryFuncFactory factory) {
StatEntryFunc func = statMap.get(statEntry);
if (func == null) {
StatRollingData clone = null;
writeLock.lock();
try {
int entryCount = statMap.size();
if (entryCount < statLogger.getMaxEntryCount()) {
func = statMap.get(statEntry);
if (func == null) {
func = factory.create();
statMap.put(statEntry, func);
}
} else {
Map<StatEntry, StatEntryFunc> cloneStatMap =
new HashMap<StatEntry, StatEntryFunc>(statMap);
statMap.clear();

func = factory.create();
statMap.put(statEntry, func);
clone = new StatRollingData(statLogger, timeSlot, rollingTimeMillis, cloneStatMap);
}
} finally {
writeLock.unlock();
}

if (clone != null) {
StatLogController.scheduleWriteTask(clone);
}
}
return func;
}

StatLogger getStatLogger() {
return statLogger;
}

long getRollingTimeMillis() {
return rollingTimeMillis;
}

long getTimeSlot() {
return timeSlot;
}

int getStatCount() {
return statMap.size();
}

Set<Entry<StatEntry, StatEntryFunc>> getStatEntrySet() {
return statMap.entrySet();
}
}

+ 79
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/SyncAppender.java Переглянути файл

@@ -0,0 +1,79 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

/**
* @author jifeng
*/
final class SyncAppender extends EagleEyeAppender {

private final EagleEyeAppender delegate;
private final Object lock = new Object();

public SyncAppender(EagleEyeAppender delegate) {
this.delegate = delegate;
}

@Override
public void append(String log) {
synchronized (lock) {
delegate.append(log);
}
}

@Override
public void flush() {
synchronized (lock) {
delegate.flush();
}
}

@Override
public void rollOver() {
synchronized (lock) {
delegate.rollOver();
}
}

@Override
public void reload() {
synchronized (lock) {
delegate.reload();
}
}

@Override
public void close() {
synchronized (lock) {
delegate.close();
}
}

@Override
public void cleanup() {
delegate.cleanup();
}

@Override
public String getOutputLocation() {
return delegate.getOutputLocation();
}

@Override
public String toString() {
return "SyncAppender [appender=" + delegate + "]";
}
}

+ 58
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/TokenBucket.java Переглянути файл

@@ -0,0 +1,58 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.eagleeye;

import java.util.concurrent.atomic.AtomicLong;

class TokenBucket {

private final long maxTokens;

private final long intervalMillis;

private volatile long nextUpdate;

private AtomicLong tokens;

public TokenBucket(long maxTokens, long intervalMillis) {
if (maxTokens <= 0) {
throw new IllegalArgumentException("maxTokens should > 0, but given: " + maxTokens);
}
if (intervalMillis < 1000) {
throw new IllegalArgumentException("intervalMillis should be at least 1000, but given: " + intervalMillis);
}
this.maxTokens = maxTokens;
this.intervalMillis = intervalMillis;
this.nextUpdate = System.currentTimeMillis() / 1000 * 1000 + intervalMillis;
this.tokens = new AtomicLong(maxTokens);
}

public boolean accept(long now) {
long currTokens;
if (now > nextUpdate) {
currTokens = tokens.get();
if (tokens.compareAndSet(currTokens, maxTokens)) {
nextUpdate = System.currentTimeMillis() / 1000 * 1000 + intervalMillis;
}
}

do {
currTokens = tokens.get();
} while (currTokens > 0 && !tokens.compareAndSet(currTokens, currTokens - 1));

return currTokens > 0;
}
}

+ 101
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitExecutor.java Переглянути файл

@@ -0,0 +1,101 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.init;

import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicBoolean;

import com.alibaba.csp.sentinel.log.RecordLog;

/**
* Load registered init functions and execute in order.
*
* @author Eric Zhao
*/
public final class InitExecutor {

private static AtomicBoolean initialized = new AtomicBoolean(false);

/**
* If one {@link InitFunc} throws an exception, the init process
* will immediately be interrupted and the application will exit.
*
* The initialization will be executed only once.
*/
public static void doInit() {
if (!initialized.compareAndSet(false, true)) {
return;
}
try {
ServiceLoader<InitFunc> loader = ServiceLoader.load(InitFunc.class);
List<OrderWrapper> initList = new ArrayList<OrderWrapper>();
for (InitFunc initFunc : loader) {
RecordLog.info("[Sentinel InitExecutor] Found init func: " + initFunc.getClass().getCanonicalName());
insertSorted(initList, initFunc);
}
for (OrderWrapper w : initList) {
w.func.init();
RecordLog.info(String.format("[Sentinel InitExecutor] Initialized: %s with order %d",
w.func.getClass().getCanonicalName(), w.order));
}
} catch (Exception ex) {
RecordLog.info("[Sentinel InitExecutor] Init failed", ex);
ex.printStackTrace();
System.exit(-1);
}
}

private static void insertSorted(List<OrderWrapper> list, InitFunc func) {
int order = resolveOrder(func);
int idx = 0;
for (; idx < list.size(); idx++) {
if (list.get(idx).getOrder() > order) {
break;
}
}
list.add(idx, new OrderWrapper(order, func));
}

private static int resolveOrder(InitFunc func) {
if (!func.getClass().isAnnotationPresent(InitOrder.class)) {
return InitOrder.LOWEST_PRECEDENCE;
} else {
return func.getClass().getAnnotation(InitOrder.class).value();
}
}

private InitExecutor() {}

private static class OrderWrapper {
private final int order;
private final InitFunc func;

OrderWrapper(int order, InitFunc func) {
this.order = order;
this.func = func;
}

int getOrder() {
return order;
}

InitFunc getFunc() {
return func;
}
}
}

+ 24
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitFunc.java Переглянути файл

@@ -0,0 +1,24 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.init;

/**
* @author Eric Zhao
*/
public interface InitFunc {

void init() throws Exception;
}

+ 41
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitOrder.java Переглянути файл

@@ -0,0 +1,41 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.init;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @author Eric Zhao
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface InitOrder {

int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

/**
* The order value. Lowest precedence by default.
*
* @return the order value
*/
int value() default LOWEST_PRECEDENCE;
}

+ 57
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CommandCenterLog.java Переглянути файл

@@ -0,0 +1,57 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.log;

import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Logger for command center.
*/
public class CommandCenterLog extends LogBase {

private static final Logger heliumRecordLog = Logger.getLogger("cspMetricLog");
private static final String FILE_NAME = "metricStat.log";
private static Handler logHandler = null;

static {
logHandler = makeLogger(FILE_NAME, heliumRecordLog);
}

/**
* Change log dir, the dir will be created if not exits
*/
public static void resetLogBaseDir(String baseDir) {
setLogBaseDir(baseDir);
logHandler = makeLogger(FILE_NAME, heliumRecordLog);
}

public static void info(String msg) {
LoggerUtils.disableOtherHandlers(heliumRecordLog, logHandler);
heliumRecordLog.log(Level.INFO, msg);
}

public static void info(String msg, Throwable e) {
LoggerUtils.disableOtherHandlers(heliumRecordLog, logHandler);
heliumRecordLog.log(Level.INFO, msg, e);
}

public static void warn(String msg, Throwable e) {
LoggerUtils.disableOtherHandlers(heliumRecordLog, logHandler);
heliumRecordLog.log(Level.WARNING, msg, e);
}
}

+ 54
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java Переглянути файл

@@ -0,0 +1,54 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.log;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;

/**
* @author xuyue
*/
class CspFormatter extends Formatter {

private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

@Override
public String format(LogRecord record) {
StringBuilder builder = new StringBuilder(1000);
builder.append(df.format(new Date(record.getMillis()))).append(" ");
builder.append(formatMessage(record));

String throwable = "";
if (record.getThrown() != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println();
record.getThrown().printStackTrace(pw);
pw.close();
throwable = sw.toString();
}
builder.append(throwable);
if ("".equals(throwable)) {
builder.append("\n");
}
return builder.toString();
}
}

+ 124
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/DateFileLogHandler.java Переглянути файл

@@ -0,0 +1,124 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.log;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.LogRecord;

class DateFileLogHandler extends Handler {

private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

private volatile FileHandler handler;

private final String pattern;
private final int limit;
private final int count;
private final boolean append;

private volatile boolean initialized = false;

private volatile long startDate = System.currentTimeMillis();
private volatile long endDate;

private final Object monitor = new Object();

DateFileLogHandler(String pattern, int limit, int count, boolean append) throws SecurityException {
this.pattern = pattern;
this.limit = limit;
this.count = count;
this.append = append;
rotateDate();
this.initialized = true;
}

@Override
public void close() throws SecurityException {
handler.close();
}

@Override
public void flush() {
handler.flush();
}

@Override
public void publish(LogRecord record) {
synchronized (monitor) {
if (endDate < record.getMillis() || !logFileExits()) { rotateDate(); }
}

if (System.currentTimeMillis() - startDate > 25 * 60 * 60 * 1000) {
String msg = record.getMessage();
record.setMessage("missed file rolling at: " + new Date(endDate) + "\n" + msg);
}
handler.publish(record);
}

@Override
public void setFormatter(Formatter newFormatter) {
super.setFormatter(newFormatter);
if (handler != null) { handler.setFormatter(newFormatter); }
}

private boolean logFileExits() {
try {
File logFile = new File(pattern);
return logFile.exists();
} catch (Throwable e) {

}
return false;
}

private void rotateDate() {
this.startDate = System.currentTimeMillis();
if (handler != null) { handler.close(); }
String newPattern = pattern.replace("%d", format.format(new Date()));
// Get current date.
Calendar next = Calendar.getInstance();
// Begin of next date.
next.set(Calendar.HOUR_OF_DAY, 0);
next.set(Calendar.MINUTE, 0);
next.set(Calendar.SECOND, 0);
next.set(Calendar.MILLISECOND, 0);
next.add(Calendar.DATE, 1);
this.endDate = next.getTimeInMillis();

try {
this.handler = new FileHandler(newPattern, limit, count, append);
if (initialized) {
handler.setEncoding(this.getEncoding());
handler.setErrorManager(this.getErrorManager());
handler.setFilter(this.getFilter());
handler.setFormatter(this.getFormatter());
handler.setLevel(this.getLevel());
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

}

+ 83
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java Переглянути файл

@@ -0,0 +1,83 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.log;

import java.io.File;
import java.io.IOException;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.alibaba.csp.sentinel.util.PidUtil;

/**
* @author leyou
*/
public class LogBase {
public static final String LOG_CHARSET = "utf-8";
private static final String DIR_NAME = "logs" + File.separator + "csp";
private static final String USER_HOME = "user.home";
private static String logBaseDir;

static {
String userHome = System.getProperty(USER_HOME);
setLogBaseDir(userHome);
}

/**
* Get log file base directory path, the returned path is guaranteed end with {@link File#separator}
*
* @return log file base directory path.
*/
public static String getLogBaseDir() {
return logBaseDir;
}

/**
* Change log dir, the dir will be created if not exits
*
* @param baseDir
*/
protected static void setLogBaseDir(String baseDir) {
if (!baseDir.endsWith(File.separator)) {
baseDir += File.separator;
}
String path = baseDir + DIR_NAME + File.separator;
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
logBaseDir = path;
}

protected static Handler makeLogger(String logName, Logger heliumRecordLog) {
CspFormatter formatter = new CspFormatter();
String fileName = LogBase.getLogBaseDir() + logName + ".pid" + PidUtil.getPid();
Handler handler = null;
try {
handler = new DateFileLogHandler(fileName + ".%d", 1024 * 1024 * 200, 1, true);
handler.setFormatter(formatter);
handler.setEncoding(LOG_CHARSET);
} catch (IOException e) {
e.printStackTrace();
}
if (handler != null) {
LoggerUtils.disableOtherHandlers(heliumRecordLog, handler);
}
heliumRecordLog.setLevel(Level.ALL);
return handler;
}
}

+ 55
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LoggerUtils.java Переглянути файл

@@ -0,0 +1,55 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.log;

import java.util.logging.Handler;
import java.util.logging.Logger;

/**
* Util class for logger.
*/
class LoggerUtils {

/**
* Remove all current handlers from the logger and attach it with the given log handler.
*
* @param logger logger
* @param handler the log handler
*/
static void disableOtherHandlers(Logger logger, Handler handler) {
if (logger == null) {
return;
}

synchronized (logger) {
Handler[] handlers = logger.getHandlers();
if (handlers == null) {
return;
}
if (handlers.length == 1 && handlers[0].equals(handler)) {
return;
}

logger.setUseParentHandlers(false);
// Remove all current handlers.
for (Handler h : handlers) {
logger.removeHandler(h);
}
// Attach the given handler.
logger.addHandler(handler);
}
}
}

+ 53
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/RecordLog.java Переглянути файл

@@ -0,0 +1,53 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.log;

import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

/***
* The basic logger for vital events.
*
* @author youji.zj
*/
public class RecordLog extends LogBase {
private static final Logger heliumRecordLog = Logger.getLogger("cspRecordLog");
private static final String FILE_NAME = "record.log";
private static Handler logHandler = null;

static {
logHandler = makeLogger(FILE_NAME, heliumRecordLog);
}

/**
* Change log dir, the dir will be created if not exits
*/
public static void resetLogBaseDir(String baseDir) {
setLogBaseDir(baseDir);
logHandler = makeLogger(FILE_NAME, heliumRecordLog);
}

public static void info(String detail) {
LoggerUtils.disableOtherHandlers(heliumRecordLog, logHandler);
heliumRecordLog.log(Level.INFO, detail);
}

public static void info(String detail, Throwable e) {
LoggerUtils.disableOtherHandlers(heliumRecordLog, logHandler);
heliumRecordLog.log(Level.INFO, detail, e);
}
}

+ 100
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/ClusterNode.java Переглянути файл

@@ -0,0 +1,100 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.node;

import java.util.HashMap;
import java.util.concurrent.locks.ReentrantLock;

import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;

/**
* <p>
* This class stores summary runtime statistics of the resource, including rt, thread count, qps
* and so on. Same resource shares the same {@link ClusterNode} globally, no matter in witch
* {@link com.alibaba.csp.sentinel.context.Context}.
* </p>
* <p>
* To distinguish invocation from different origin (declared in
* {@link ContextUtil#enter(String name, String origin)}),
* one {@link ClusterNode} holds an {@link #originCountMap}, this map holds {@link StatisticNode}
* of different origin. Use {@link #getOriginNode(String)} to get {@link Node} of the specific
* origin.<br/>
* Note that 'origin' usually is Service Consumer's app name.
* </p>
*
* @author qinan.qn
* @author jialiang.linjl
*/
public class ClusterNode extends StatisticNode {

/**
* <p>
* the longer the application runs, the more stable this mapping will
* become. so we don't concurrent map but a lock. as this lock only happens
* at the very beginning while concurrent map will hold the lock all the
* time
* </p>
*/
private HashMap<String, StatisticNode> originCountMap = new HashMap<String, StatisticNode>();
private ReentrantLock lock = new ReentrantLock();

/**
* Get {@link Node} of the specific origin. Usually the origin is the Service Consumer's app name.
*
* @param origin The caller's name. It is declared in the
* {@link ContextUtil#enter(String name, String origin)}.
* @return the {@link Node} of the specific origin.
*/
public Node getOriginNode(String origin) {
StatisticNode statisticNode = originCountMap.get(origin);
if (statisticNode == null) {
try {
lock.lock();
statisticNode = originCountMap.get(origin);
if (statisticNode == null) {
statisticNode = new StatisticNode();
HashMap<String, StatisticNode> newMap = new HashMap<String, StatisticNode>(
originCountMap.size() + 1);
newMap.putAll(originCountMap);
newMap.put(origin, statisticNode);
originCountMap = newMap;
}
} finally {
lock.unlock();
}
}
return statisticNode;
}

public synchronized HashMap<String, StatisticNode> getOriginCountMap() {
return originCountMap;
}

/**
* Add exception count only when {@code throwable} is not {@link BlockException#isBlockException(Throwable)}
*
* @param throwable
* @param count count to add.
*/
public void trace(Throwable throwable, int count) {
if (!BlockException.isBlockException(throwable)) {
for (int i = 0; i < count; i++) {
this.increaseExceptionQps();
}
}
}
}

+ 0
- 0
sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/DefaultNode.java Переглянути файл


Деякі файли не було показано, через те що забагато файлів було змінено

Завантаження…
Відмінити
Зберегти