Based on aop & amp; proxy & amp; Sentinel & amp; Nacos configuration control packaging class implementation principle
Hi, I am Achang
. Today I will record the idea of looking at sentinel source code and combining business implementation Based on aop & amp; proxy & amp; Sentinel & amp; Nacos configuration control packaging class implementation principle
;The following does not record the implementation process of the plan step by step, but records the important links and examples of the process to facilitate your own understanding and review.
1. Knowledge points involved
- SpringBoot
- Nacos
- Sentinel
- AOP
- proxy interceptor
2. Text
0. Overall framework diagram of Sentinel
1. Control resource json information based on Nacos configuration
After integrating Nacos, configure the JSON
type file under the corresponding DataId#Group, such as
{<!-- --> "flowRules": [ {<!-- --> "enabled": false, "clusterMode": false, "controlBehavior": 0, "count": 200, "grade": 1, "limitApp": "default", "maxQueueingTimeMs": 500, "resource": "com.achang.UserService", "strategy": 0, "warmUpPeriodSec": 10 }, {<!-- --> "enabled": false, "clusterMode": false, "controlBehavior": 2, "count": 0.1, "grade": 1, "limitApp": "default", "maxQueueingTimeMs": 30000, "resource": "achang:1", "strategy": 0, "warmUpPeriodSec": 10 } ], "sentinelEnabled": true }
The above is divided into main switch
and the corresponding sentinelSlot
switch
2. How to load the above configuration?
Use hutool’s spi package;
Service loading tool class in SPI mechanism, the process is as follows
1. Create an interface and create an implementation class
2. Create a file with the same fully qualified class name as the interface under ClassPath/META-INF/services
3. Fill in the fully qualified class name of the implementation class in the file content.
Load the corresponding NacosSpiService class through Java’s Spi mechanism
public interface NacosSpiService {<!-- --> void loadRules(String content); String getDataId(); String getGroupId(); }
Declare the classes that need to be loaded under META-INF/services
com.achang.core.sentinel.NacosSpiSentinelImpl
Then declare the method Spi loading in the @Configuration class of Nacos and add a listener to listen for Nacos configuration changes
private void refreshNacosConfigBySpi() {<!-- --> try {<!-- --> ServiceLoaderUtil.loadList(NacosSpiService.class) .stream() .filter(nacosSpiService -> nacosSpiService != null & amp; & amp; StringUtils.isNotBlank(nacosSpiService.getDataId())).forEach(new Consumer<NacosSpiService>() {<!-- --> @SneakyThrows @Override public void accept(NacosSpiService nacosSpiService) {<!-- --> try {<!-- --> // nacosSpiService.getGroupId() does not use spi group temporarily String content = configService.getConfigAndSignListener(nacosSpiService.getDataId(), group, 5000, new AbstractListener() {<!-- --> @Override public void receiveConfigInfo(String content) {<!-- --> try {<!-- --> nacosSpiService.loadRules(content); log.info("nacos configuration initialization" + nacosSpiService.getDataId() + ":" + content); } catch (Exception e) {<!-- --> log.error(nacosSpiService.getDataId() + "Configuration parsing failed: {}", e.getMessage(), e); } } }); try {<!-- --> nacosSpiService.loadRules(content); log.info("nacos configuration initialization" + nacosSpiService.getDataId() + ":" + content); } catch (Exception e) {<!-- --> log.error(nacosSpiService.getDataId() + "Configuration parsing failed: {}", e.getMessage(), e); } } catch (Throwable throwable) {<!-- --> log.error("nacos register listener:{},{} failed:{}", group, nacosSpiService.getDataId(), throwable.getMessage(), throwable); } } }); } catch (Throwable throwable) {<!-- --> log.error("refreshNacosConfigBySpi failed:{}", throwable.getMessage(), throwable); }
The above will eventually load the configuration information from nacos through the loadRules
method to initialize it into the resource control Rule corresponding to sentinel:
- com.alibaba.csp.sentinel.slots.system.SystemRule
- com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule
- com.alibaba.csp.sentinel.slots.block.flow.FlowRule
- com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule
Load it as a HashMap through the loadRules
method of the above Manager corresponding to Rules
The following takes com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager
as an example;
-
FlowRuleManager#flowRules to store the mapping relationship of control resources
-
FlowRuleManager#FlowPropertyListener to update & load configuration
-
The FlowRuleUtil.buildFlowRuleMap method is used to convert it into a ConcurrentHashMap, and use hash to deduplicate and sort it. The sorting rule is FlowRuleComparator.
{<!-- --> "enabled": false, "clusterMode": false, "controlBehavior": 0, "count": 200, "grade": 1, "limitApp": "default", "maxQueueingTimeMs": 500, "resource": "com.achang.UserService", "strategy": 0, "warmUpPeriodSec": 10 }
The above resources will be converted into:
-
Key: com.achang.UserService
-
Value: [{“enabled”:false,”clusterMode”:false,”controlBehavior”:0,”count”:200,”grade”:1,”limitApp”:”default”,”maxQueueingTimeMs”:500,”resource “:”com.achang.UserService”,”strategy”:0,”warmUpPeriodSec”:10}]
-
-
3. How to use loaded resources
The json string configured through Nacos is converted into the corresponding RuleMap, and then the rule map is obtained through getFlowRuleMap(); this involves the Slot responsibility chain in Sentinel, which still uses com.alibaba.csp.sentinel.slots.block.flow. FlowSlot example.
- com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#ruleProvider to obtain the rules of the corresponding resource;
- com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#checkFlow will wrap the resources obtained above into resourceWrapper
- A proxy method will call the com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#entry method of each responsibility chain to execute the logic of the slot to limit/downgrade/circuit, etc.
In the getFlowRuleMap method, the corresponding Map will be assembled according to the resource configuration, and generateRater will set the corresponding controlBehavior field to correspond to the TrafficShapingController (smoothizer com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController/pre Heater com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController, etc.) For specific logic, please refer to the official document https://sentinelguard.io/zh-cn/docs/flow-control.html
The following is an example of the core code canPass of RateLimiterController.
@Override public boolean canPass(Node node, int acquireCount, boolean prioritized) {<!-- --> // Pass when acquire count is less or equal than 0. if (acquireCount <= 0) {<!-- --> return true; } // Reject when count is less or equal than 0. // Otherwise, the costTime will be max of long and waitTime will overflow in some cases. if (count <= 0) {<!-- --> return false; } long currentTime = TimeUtil.currentTimeMillis(); // Calculate the interval between every two requests. long costTime = Math.round(1.0 * (acquireCount) / count * 1000); // Expected pass time of this request. long expectedTime = costTime + latestPassedTime.get(); if (expectedTime <= currentTime) {<!-- --> // Contention may exist here, but it's okay. latestPassedTime.set(currentTime); return true; } else {<!-- --> // Calculate the time to wait. long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis(); if (waitTime > maxQueueingTimeMs) {<!-- --> return false; } else {<!-- --> long oldTime = latestPassedTime.addAndGet(costTime); try {<!-- --> waitTime = oldTime - TimeUtil.currentTimeMillis(); if (waitTime > maxQueueingTimeMs) {<!-- --> latestPassedTime.addAndGet(-costTime); return false; } // in race condition waitTime may <= 0 if (waitTime > 0) {<!-- --> Thread.sleep(waitTime); } return true; } catch (InterruptedException e) {<!-- --> } } } return false; }
At the entry of the corresponding slot, com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#entry will be executed.
@Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable {<!-- --> checkFlow(resourceWrapper, context, node, count, prioritized); fireEntry(context, resourceWrapper, node, count, prioritized, args); }
There will be com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker in com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#checkFlow to determine whether to pass or perform the downgrade based on the corresponding FlowRule rules. Logic etc;
public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {<!-- --> if (ruleProvider == null || resource == null) {<!-- --> return; } Collection<FlowRule> rules = ruleProvider.apply(resource.getName()); if (rules != null) {<!-- --> for (FlowRule rule : rules) {<!-- --> if (!canPassCheck(rule, context, node, count, prioritized)) {<!-- --> throw new FlowException(rule.getLimitApp(), rule); } } } }
Then execute the corresponding logic according to the corresponding TrafficShapingController;
4. How to perform the above downgrade/circuit break judgments in the correct place?
Sentinel is implemented based on AOP using the @SentinelResource annotation, but the configuration cannot be modified dynamically, which is inflexible
Then you can wrap a template class [Achang’s Ugly Code Optimization] to optimize the Controller execution process through strategy mode & template mode, and then use a proxy object to proxy this template class to perform custom downgrades before and after the target method is executed/ Fusing, etc.;
Use Interceptor interceptors and other methods to write the before and after logic of the object, and implement the InvocationHandler class to override the invoke method.
The entry method of com.alibaba.csp.sentinel.SphU is used to pass the resource name to downgrade/circuit and other logic.
@Slf4j public class TemplateInterceptor implements InvocationHandler{<!-- --> try (Entry entry = SphU.entry(actionTemplateRequestInfo.getResource())) {<!-- --> // Call target method return method.invoke(target, args); } }
Declare the proxy wrapper class in the corresponding configuration class, as follows:
@Bean Template template() {<!-- --> Template template = new Template(); Template interceptor = new TemplateInterceptor(template); //Create proxy object return (Template) Proxy.newProxyInstance( Template.class.getClassLoader(), new Class[]{<!-- -->Template.class}, interceptor ); }
In this way, you can use agent
combined with aop
and combine it with the flexible sentinel
framework through Nacos
dynamic configuration. Control resources.
Reference:
- sentinel official documentation
- blog