Sentinel源碼解析入口類和SlotChain構建過程詳解_第1頁
Sentinel源碼解析入口類和SlotChain構建過程詳解_第2頁
Sentinel源碼解析入口類和SlotChain構建過程詳解_第3頁
Sentinel源碼解析入口類和SlotChain構建過程詳解_第4頁
Sentinel源碼解析入口類和SlotChain構建過程詳解_第5頁
已閱讀5頁,還剩14頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

第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. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論