




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第Sentinel源碼解析入口類和SlotChain構建過程詳解目錄1.測試用例1.1流控測試2.注解版源碼分析2.1默認Context創建2.2查找并創建SlotChain2.2.1創建slotChainBuilder2.2.2slotChainBuilder.build()參考文章
1.測試用例
我們以sentinel-demo中的sentinel-annotation-spring-aop為例,分析sentinel的源碼。核心代碼如下:
DemoController:
@RestController
publicclassDemoController{
@Autowired
privateTestServiceservice;
@GetMapping("/foo")
publicStringapiFoo(@RequestParam(required=false)Longt)throwsException{
if(t==null){
t=System.currentTimeMillis();
service.test();
returnservice.hello(t);
@GetMapping("/baz/{name}")
publicStringapiBaz(@PathVariable("name")Stringname){
returnservice.helloAnother(name);
TestServiceImpl:
@Service
publicclassTestServiceImplimplementsTestService{
@Override
@SentinelResource(value="test",blockHandler="handleException",blockHandlerClass={ExceptionUtil.class})
publicvoidtest(){
System.out.println("Test");
@Override
@SentinelResource(value="hello",fallback="helloFallback")
publicStringhello(longs){
if(s0){
thrownewIllegalArgumentException("invalidarg");
returnString.format("Helloat%d",s);
@Override
@SentinelResource(value="helloAnother",defaultFallback="defaultFallback",
exceptionsToIgnore={IllegalStateException.class})
publicStringhelloAnother(Stringname){
if(name==null||"bad".equals(name)){
thrownewIllegalArgumentException("oops");
if("foo".equals(name)){
thrownewIllegalStateException("oops");
return"Hello,"+name;
publicStringhelloFallback(longs,Throwableex){
//Dosomeloghere.
ex.printStackTrace();
return"Oops,erroroccurredat"+s;
publicStringdefaultFallback(){
System.out.println("Gotodefaultfallback");
return"default_fallback";
啟動類DemoApplication:
@SpringBootApplication
publicclassDemoApplication{
publicstaticvoidmain(String[]args){
SpringApplication.run(DemoApplication.class,args);
在啟動這個工程上增加參數:
-Dcsp.sentinel.dashboard.server=localhost:8081-D=annotation-aspectj
如圖:
打開http://localhost:8081/#/dashboard地址,可以看到應用已經注冊到sentinel管理后臺:
1.1流控測試
訪問http://localhost:19966/foot=188這個鏈接,多訪問幾次,在實時監控頁面可以看到:
然后,我們先簡單配置一個流控規則,如下:
其中,資源名為:
然后我們在快速刷新http://localhost:19966/foot=188接口,會出現限流的情況,返回如下:
Oops,erroroccurredat188
實時監控為:
2.注解版源碼分析
使用注解@SentinelResource核心原理就是利用AOP切入到方法中,我們直接看SentinelResourceAspect類,這是一個切面類:
@Aspect//切面
publicclassSentinelResourceAspectextendsAbstractSentinelAspectSupport{
//指定切入點為@SentinelResource注解
@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
publicvoidsentinelResourceAnnotationPointcut(){
//環繞通知
@Around("sentinelResourceAnnotationPointcut()")
publicObjectinvokeResourceWithSentinel(ProceedingJoinPointpjp)throwsThrowable{
MethodoriginMethod=resolveMethod(pjp);
SentinelResourceannotation=originMethod.getAnnotation(SentinelResource.class);
if(annotation==null){
//Shouldnotgothroughhere.
thrownewIllegalStateException("WrongstateforSentinelResourceannotation");
StringresourceName=getResourceName(annotation.value(),originMethod);
EntryTypeentryType=annotation.entryType();
intresourceType=annotation.resourceType();
Entryentry=null;
try{
//要織入的,增強的功能
entry=SphU.entry(resourceName,resourceType,entryType,pjp.getArgs());
//調用目標方法
returnceed();
}catch(BlockExceptionex){
returnhandleBlockException(pjp,annotation,ex);
}catch(Throwableex){
ClassextendsThrowable[]exceptionsToIgnore=annotation.exceptionsToIgnore();
//Theignorelistwillbecheckedfirst.
if(exceptionsToIgnore.length0exceptionBelongsTo(ex,exceptionsToIgnore)){
throwex;
if(exceptionBelongsTo(ex,annotation.exceptionsToTrace())){
traceException(ex);
returnhandleFallback(pjp,annotation,ex);
//Nofallbackfunctioncanhandletheexception,sothrowitout.
throwex;
}finally{
if(entry!=null){
entry.exit(1,pjp.getArgs());
核心方法SphU.entry():
publicstaticEntryentry(Stringname,intresourceType,EntryTypetrafficType,Object[]args)
throwsBlockException{
//注意第4個參數值為1
returnEnv.sph.entryWithType(name,resourceType,trafficType,1,args);
@Override
publicEntryentryWithType(Stringname,intresourceType,EntryTypeentryType,intcount,Object[]args)
throwsBlockException{
//count參數:表示當前請求可以增加多少個計數
//注意第5個參數為false
returnentryWithType(name,resourceType,entryType,count,false,args);
@Override
publicEntryentryWithType(Stringname,intresourceType,EntryTypeentryType,intcount,booleanprioritized,
Object[]args)throwsBlockException{
//將信息封裝為一個資源對象
StringResourceWrapperresource=newStringResourceWrapper(name,entryType,resourceType);
//返回一個資源操作對象entry
//prioritized為true表示當前訪問必須等待"根據其優先級計算出的時間"后才通過
//prioritized為false則當前請求無需等待
returnentryWithPriority(resource,count,prioritized,args);
我們重點看一下CtSph#entryWithPriority:
/**
*@paramresourceWrapper
*@paramcount默認為1
*@paramprioritized默認為false
*@paramargs
*@return
*@throwsBlockException
privateEntryentryWithPriority(ResourceWrapperresourceWrapper,intcount,booleanprioritized,Object...args)
throwsBlockException{
//從ThreadLocal中獲取Context
//一個請求會占用一個線程,一個線程會綁定一個context
Contextcontext=ContextUtil.getContext();
//若context是NullContext類型,則表示當前系統中的context數量已經超過閾值
//即訪問的請求的數量已經超出了閾值,此時直接返回一個無需做規則檢測的資源操作對象
if(contextinstanceofNullContext){
//The{@linkNullContext}indicatesthattheamountofcontexthasexceededthethreshold,
//sohereinittheentryonly.Norulecheckingwillbedone.
returnnewCtEntry(resourceWrapper,null,context);
//當前線程中沒有綁定context,則創建一個context并將其放入到Threadlocal
if(context==null){
//todoUsingdefaultcontext.
context=InternalContextUernalEnter(Constants.CONTEXT_DEFAULT_NAME);
//Globalswitchisclose,norulecheckingwilldo.
//若全局開關是關閉的,直接返回一個無需做規則檢測的資源操作對象
if(!Constants.ON){
returnnewCtEntry(resourceWrapper,null,context);
//todo查找SlotChain
ProcessorSlotObjectchain=lookProcessChain(resourceWrapper);
*Meansamountofresources(slotchain)exceeds{@linkConstants.MAX_SLOT_CHAIN_SIZE},
*sonorulecheckingwillbedone.
//若沒有知道chain,則意味著chain數量超出了閾值
if(chain==null){
returnnewCtEntry(resourceWrapper,null,context);
//創建一個資源操作對象
Entrye=newCtEntry(resourceWrapper,chain,context);
try{
//todo對資源進行操作
chain.entry(context,resourceWrapper,null,count,prioritized,args);
}catch(BlockExceptione1){
e.exit(count,args);
throwe1;
}catch(Throwablee1){
//Thisshouldnothappen,unlessthereareerrorsexistinginSentinelinternal.
RecordL("Sentinelunexpectedexception",e1);
returne;
2.1默認Context創建
當前線程沒有綁定Context,則創建一個context并將其放入到Threadlocal。核心方法為InternalContextUernalEnter:
publicstaticContextenter(Stringname,Stringorigin){
if(Constants.CONTEXT_DEFAULT_NAME.equals(name)){
thrownewContextNameDefineException(
"The"+Constants.CONTEXT_DEFAULT_NAME+"can'tbepermittodefined!");
returntrueEnter(name,origin);
protectedstaticContexttrueEnter(Stringname,Stringorigin){
//嘗試從ThreadLocal中獲取context
Contextcontext=contextHolder.get();
//若Threadlocal中沒有,則嘗試從緩存map中獲取
if(context==null){
//緩存map的key為context名稱,value為EntranceNode
MapString,DefaultNodelocalCacheNameMap=contextNameNodeMap;
//DCL雙重檢測鎖,防止并發創建對象
DefaultNodenode=localCacheNameMap.get(name);
if(node==null){
//若緩存map的size大于context數量的最大閾值,則直接返回NULL_CONTEXT
if(localCacheNameMap.size()Constants.MAX_CONTEXT_NAME_SIZE){
setNullContext();
returnNULL_CONTEXT;
}else{
LOCK.lock();
try{
node=contextNameNodeMap.get(name);
if(node==null){
if(contextNameNodeMap.size()Constants.MAX_CONTEXT_NAME_SIZE){
setNullContext();
returnNULL_CONTEXT;
}else{
//創建一個EntranceNode
node=newEntranceNode(newStringResourceWrapper(name,EntryType.IN),null);
//Addentrancenode.
//將新建的node添加到Root
Constants.ROOT.addChild(node);
//將新建的node寫入到緩存map
//為了防止"迭代穩定性問題"-iteratestable對于共享集合的寫操作
MapString,DefaultNodenewMap=newHashMap(contextNameNodeMap.size()+1);
newMap.putAll(contextNameNodeMap);
newMap.put(name,node);
contextNameNodeMap=newMap;
}finally{
LOCK.unlock();
//將context的name與entranceNode封裝成context
context=newContext(node,name);
//初始化context的來源
context.setOrigin(origin);
//將context寫入到ThreadLocal
contextHolder.set(context);
returncontext;
注意:因為privatestaticvolatileMapString,DefaultNodecontextNameNodeMap=newHashMap();是HashMap結構,所以存在并發安全問題,采用代碼中方式進行添加操作。
2.2查找并創建SlotChain
構建調用鏈lookProcessChain(resourceWrapper):
ProcessorSlotObjectlookProcessChain(ResourceWrapperresourceWrapper){
//緩存map的key為資源value為其相關的SlotChain
ProcessorSlotChainchain=chainMap.get(resourceWrapper);
//DCL
//若緩存中沒有相關的SlotChain則創建一個并放入到緩存中
if(chain==null){
synchronized(LOCK){
chain=chainMap.get(resourceWrapper);
if(chain==null){
//Entrysizelimit.
//緩存map的size大于chain數量的最大閾值,則直接返回null,不在創建新的chain
if(chainMap.size()=Constants.MAX_SLOT_CHAIN_SIZE){
returnnull;
//todo創建新的chain
chain=SlotChainProvider.newSlotChain();
//防止迭代穩定性問題
MapResourceWrapper,ProcessorSlotChainnewMap=newHashMapResourceWrapper,ProcessorSlotChain(
chainMap.size()+1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper,chain);
chainMap=newMap;
returnchain;
我們直接看核心方法SlotChainProvider.newSlotChain();:
publicstaticProcessorSlotChainnewSlotChain(){
//若builder不為null,則直接使用builder構建一個chain
//否則先創建一個builder
if(slotChainBuilder!=null){
returnslotChainBuilder.build();
//ResolvetheslotchainbuilderSPI.
//通過SPI方式創建builder
slotChainBuilder=SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
//若通過SPI未能創建builder,則創建一個默認的DefaultSlotChainBuilder
if(slotChainBuilder==null){
//Shouldnotgothroughhere.
RecordLog.warn("[SlotChainProvider]Wrongstatewhenresolvingslotchainbuilder,usingdefault");
slotChainBuilder=newDefaultSlotChainBuilder();
}else{
RecordL("[SlotChainProvider]Globalslotchainbuilderresolved:{}",
slotChainBuilder.getClass().getCanonicalName());
//todo構建一個chain
returnslotChainBuilder.build();
privateSlotChainProvider(){}
2.2.1創建slotChainBuilder
//通過SPI方式創建builder
slotChainBuilder=SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
通過SPI方法創建slotChainBuilder,去項目中META-INF.service中獲取:
2.2.2slotChainBuilder.build()
@Spi(isDefault=true)
publicclassDefaultSlotChainBuilderimplementsSlotChainBuilder{
@Override
publicProcessorSlotChainbuild(){
ProcessorSlotChainchain=newDefaultProcessorSlotChain();
//通過SPI方式構建Slot
ListProcessorSlotsortedSlotList=SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
for(ProcessorSlotslot:sortedSlotList){
if(!(slotinstanceofAbstractLinkedProcessorSlot)){
RecordLog.warn("TheProcessorSlot("+slot.getClass().getCanonicalName()+")isnotaninstanceofAbstractLinkedProcessorSlot,can'tbeaddedintoProcessorSlotChain");
continue;
chain.addLast((AbstractLinkedProcessorSlot)slot);
returnchain;
通過SPI機制,去項目中META-INF.service中獲取,在sentinel-core項目中:
還有一個ParamFlowSlot,在sentinel-extension/sentinel-parameter-flow-control下:
我們點擊NodeSelectorSlot,類上面是有優先級order,數字越小,優先級越高。
@Spi(isSingleton=false,order=Constants.ORDER_NODE_SELECTOR_SLOT)
publicclassNodeSelectorSlotextendsAbstractLinkedProcessorSlotObject{
優先級常量為:
publicstaticfinalintORDER_NODE_SELECTOR_SLOT=-10000;
publicstaticfinalintORDER_CLUSTER_BUILDER_SLOT=-9000;
publicstaticfinalintORDER_LOG_SLOT=-8000;
publicstaticfinalintORDER_STATISTIC_SLOT=-7000;
publicstaticfinalintORDER_AUTHORITY_SLOT=-6000;
publicstaticfinalintORDER_SYSTEM_SLOT=-5000;
publicstaticfinalintORDER_FLOW_SLOT=-2000;
publicstaticfinalintORDER_DEGRADE_SLOT=-1000;
我們看代碼中的變量sortedSlotList,已經按照優先級排序好了:
我們看一下構建的ProcessorSlotChain,類似一個單鏈表結構,如下:
我們看一下相關的類結構:DefaultProcessorSlotChain:
//這是一個單向鏈表,默認包含一個接節點,且有兩個指針first和end同時指向這個節點
publicclassDefaultProcessorSlotChainextendsProcessorSlotChain{
AbstractLinkedProcessorSlotfirst=newAbstractLinkedProcessorSlotObject(){
@Override
publicvoidentry(Contextcontext,ResourceWrapperresourceWrapper,Objectt,intcount,booleanprioritized,Object...args)
throwsThrowable{
super.fireEntry(context,resourceWrapper,t,count,prioritized,args);
@Override
publicvoidexit(Contextcontext,ResourceWrapperresourceWrapper,intcount,Object...args){
super.fireExit(context,resourceWrapper,count,args);
AbstractLinkedProcessorSlotend=first;
@Override
publicvoidaddFirst(AbstractLinkedProcessorSlotprotocolProcessor){
protocolProcessor.setNext(first.getNext());
first.setNext(protocolProcessor);
if(end==first){
end=protocolProcessor;
@Overr
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 語音識別試題及答案
- 阿里定級面試題及答案
- 房地產銷售策略與實戰
- 2025年 道真自治縣“特崗計劃”教師招聘考試筆試試卷附答案
- 員工安全培訓手冊
- 2025年中國噴氣背包行業市場全景分析及前景機遇研判報告
- 2025年中國內衣褲洗衣機行業市場全景分析及前景機遇研判報告
- 急救培訓圓滿畢業
- 住院患者護理風險評估制度
- 腫瘤晚期患者教育
- 抖音精準圈層種草
- 高考英語書面表達全國卷評分標準
- 店面運營手冊(店面布置與陳列)
- 裝修申請書模板
- 四川水電站建設用地地質災害危險性評估報告
- 建筑電氣設計技術規程
- 公開招標招標文件范本
- (完整版)OEE記錄表格(設備綜合效率)
- 智慧燃氣安全監管平臺解決方案
- 鋼結構檢測專項方案(33頁)
- 人教版小學三年級下冊英語復習課件(164頁PPT)
評論
0/150
提交評論