时光闹钟app开发者,请关注我,后续分享更精彩! 坚持原创,共同进步!前言 阿里开源Sentinel框架功能强大,实现了对服务的流控、熔断降级,并支持通过管理页面进行配置和监控。Client集成支持Java、Go等语言。但管理后台Dashboard,默认只适合开发和演示,要生产环境使用需要做相应源代码级改造。本文将介绍具体改造步骤,帮助大家更快实现在生产环境的部署应用。当然,如果预算充足,也可以直接使用阿里云开箱即用的SentinelDashboard服务。整体架构 SentinelDashboard规则默认存储在内存中,一旦服务重启,规则将丢失。要实现生产环境的大规模应用,需要把规则做持久化存储,Sentinel支持把nacoszookeeperapolloredis等中间件作为存储介质。本文以Nacos作为规则存储端进行介绍。 整体架构如上图,SentinelDashboard通过界面操作,添加资源的规则,保存时将信息推送到nacos中。nacos收到数据变更,并实时推送到应用中的SentinelSDK客户端,SentinelSDK应用规则实现对服务资源的流控熔断降级管控。代码改造 Sentinel使用nacos作为存储端,需要修改源代码。 1。源代码下载gitclonegitgithub。com:hcq0514Sentinel。git IDE工具打开sentineldashboard项目模块 注意:将项目切换到使用的sentineltag稳定版本,这里为1。8。6版本 2。pom。xml添加依赖 sentineldashboard模块pom。xml文件中,把sentineldatasourcenacos依赖的注释掉。!forNacosrulepublishersampledependencygroupIdcom。alibaba。cspgroupIdsentineldatasourcenacosartifactId!scopetestscopedependency 3。前端页面修改 resourcesappscriptsdirectivessidebarsidebar。html文件,将dashboard。flowV1改成dashboard。flowliuisrefactiveactivengif!entry。isGateway!iclassglyphiconglyphiconfilteri流控规则li 4。Java代码修改 com。alibaba。csp。sentinel。dashboard。rule包中创建一个nacos包,用来存放Nacos相关代码类。 nacos包下创建NacosPropertiesConfiguration类,用于nacosserver配置属性封装packagecom。alibaba。csp。sentinel。dashboard。rule。nacos;importorg。springframework。boot。context。properties。ConfigurationProperties;ConfigurationProperties(prefixsentinel。nacos)publicclassNacosPropertiesConfiguration{privateStringnamespace;privateStringserverAddr;privateStringusername;privateStringpassword;省略属性setget方法。。。。。。} nacos包下创建NacosConfigUtil工具类publicfinalclassNacosConfigUtil{publicstaticfinalStringGROUPIDSENTINELGROUP;publicstaticfinalStringFLOWDATAIDPOSTFIXflowrules;publicstaticfinalStringDEGRADEDATAIDPOSTFIXdegraderules;publicstaticfinalStringPARAMFLOWDATAIDPOSTFIXparamrules;publicstaticfinalStringCLUSTERMAPDATAIDPOSTFIXclustermap;ccforclusterclientpublicstaticfinalStringCLIENTCONFIGDATAIDPOSTFIXccconfig;csforclusterserverpublicstaticfinalStringSERVERTRANSPORTCONFIGDATAIDPOSTFIXcstransportconfig;publicstaticfinalStringSERVERFLOWCONFIGDATAIDPOSTFIXcsflowconfig;publicstaticfinalStringSERVERNAMESPACESETDATAIDPOSTFIXcsnamespaceset;privateNacosConfigUtil(){}} nacos包创建NacosConfigurationbean配置类EnableConfigurationProperties(NacosPropertiesConfiguration。class)ConfigurationpublicclassNacosConfiguration{BeanQualifier(degradeRuleEntityEncoder)publicConverterListDegradeRuleEntity,StringdegradeRuleEntityEncoder(){returnJSON::toJSONString;}BeanQualifier(degradeRuleEntityDecoder)publicConverterString,ListDegradeRuleEntitydegradeRuleEntityDecoder(){returnsJSON。parseArray(s,DegradeRuleEntity。class);}BeanQualifier(flowRuleEntityEncoder)publicConverterListFlowRuleEntity,StringflowRuleEntityEncoder(){returnJSON::toJSONString;}BeanQualifier(flowRuleEntityDecoder)publicConverterString,ListFlowRuleEntityflowRuleEntityDecoder(){returnsJSON。parseArray(s,FlowRuleEntity。class);}BeanpublicConfigServicenacosConfigService(NacosPropertiesConfigurationnacosPropertiesConfiguration)throwsNacosException{PropertiespropertiesnewProperties();properties。put(PropertyKeyConst。SERVERADDR,nacosPropertiesConfiguration。getServerAddr());if(StringUtils。isNotBlank(nacosPropertiesConfiguration。getNamespace())){properties。put(PropertyKeyConst。NAMESPACE,nacosPropertiesConfiguration。getNamespace());}if(StringUtils。isNotBlank(nacosPropertiesConfiguration。getUsername())){properties。put(PropertyKeyConst。USERNAME,nacosPropertiesConfiguration。getUsername());}if(StringUtils。isNotBlank(nacosPropertiesConfiguration。getPassword())){properties。put(PropertyKeyConst。PASSWORD,nacosPropertiesConfiguration。getPassword());}returnConfigFactory。createConfigService(properties);}} nacos包下创建流控的provider和publisher实现类Component(flowRuleNacosProvider)publicclassFlowRuleNacosProviderimplementsDynamicRuleProviderListFlowRuleEntity{AutowiredprivateConfigServiceconfigService;AutowiredQualifier(flowRuleEntityDecoder)privateConverterString,ListFlowRuleEntityconverter;OverridepublicListFlowRuleEntitygetRules(StringappName)throwsException{StringrulesconfigService。getConfig(appNameNacosConfigUtil。FLOWDATAIDPOSTFIX,NacosConfigUtil。GROUPID,3000);if(StringUtil。isEmpty(rules)){returnnewArrayList();}returnconverter。convert(rules);}}Component(flowRuleNacosPublisher)publicclassFlowRuleNacosPublisherimplementsDynamicRulePublisherListFlowRuleEntity{AutowiredprivateConfigServiceconfigService;AutowiredQualifier(flowRuleEntityEncoder)privateConverterListFlowRuleEntity,Stringconverter;Overridepublicvoidpublish(Stringapp,ListFlowRuleEntityrules)throwsException{AssertUtil。notEmpty(app,appnamecannotbeempty);if(rulesnull){return;}configService。publishConfig(appNacosConfigUtil。FLOWDATAIDPOSTFIX,NacosConfigUtil。GROUPID,converter。convert(rules));}} nacos包下创建熔断降级的provider和publisher实现类Component(degradeRuleNacosProvider)publicclassDegradeRuleNacosProviderimplementsDynamicRuleProviderListDegradeRuleEntity{AutowiredprivateConfigServiceconfigService;AutowiredQualifier(degradeRuleEntityDecoder)privateConverterString,ListDegradeRuleEntityconverter;OverridepublicListDegradeRuleEntitygetRules(StringappName)throwsException{StringrulesconfigService。getConfig(appNameNacosConfigUtil。DEGRADEDATAIDPOSTFIX,NacosConfigUtil。GROUPID,3000);if(StringUtil。isEmpty(rules)){returnnewArrayList();}returnconverter。convert(rules);}}Component(degradeRuleNacosPublisher)publicclassDegradeRuleNacosPublisherimplementsDynamicRulePublisherListDegradeRuleEntity{AutowiredprivateConfigServiceconfigService;AutowiredQualifier(degradeRuleEntityEncoder)privateConverterListDegradeRuleEntity,Stringconverter;Overridepublicvoidpublish(Stringapp,ListDegradeRuleEntityrules)throwsException{AssertUtil。notEmpty(app,appnamecannotbeempty);if(rulesnull){return;}configService。publishConfig(appNacosConfigUtil。DEGRADEDATAIDPOSTFIX,NacosConfigUtil。GROUPID,converter。convert(rules));}} 修改com。alibaba。csp。sentinel。dashboard。controller。v2。FlowControllerV2类。将注入的bean标识由下图右边替换左边的值。将原有的服务引用基于内存存储,替换为nacos存储的服务引用。上面截图红框替换值flowRuleDefaultProvider替换为flowRuleNacosProviderflowRuleDefaultPublisher替换为flowRuleNacosPublisher 删除com。alibaba。csp。sentinel。dashboard。controller。DegradeController类,com。alibaba。csp。sentinel。dashboard。controller。v2包下新增DegradeControllerV2类RestControllerRequestMapping(degrade)publicclassDegradeControllerV2{privatefinalLoggerloggerLoggerFactory。getLogger(DegradeControllerV2。class);AutowiredprivateRuleRepositoryDegradeRuleEntity,Longrepository;AutowiredQualifier(degradeRuleNacosProvider)privateDynamicRuleProviderListDegradeRuleEntityruleProvider;AutowiredQualifier(degradeRuleNacosPublisher)privateDynamicRulePublisherListDegradeRuleEntityrulePublisher;GetMapping(rules。json)AuthAction(PrivilegeType。READRULE)publicResultListDegradeRuleEntityapiQueryMachineRules(Stringapp,Stringip,Integerport){if(StringUtil。isEmpty(app)){returnResult。ofFail(1,appcantbenullorempty);}if(StringUtil。isEmpty(ip)){returnResult。ofFail(1,ipcantbenullorempty);}if(portnull){returnResult。ofFail(1,portcantbenull);}try{ListDegradeRuleEntityrulessentinelApiClient。fetchDegradeRuleOfMachine(app,ip,port);ListDegradeRuleEntityrulesruleProvider。getRules(app);if(rules!null!rules。isEmpty()){for(DegradeRuleEntityentity:rules){entity。setApp(app);}}rulesrepository。saveAll(rules);returnResult。ofSuccess(rules);}catch(Throwablethrowable){logger。error(queryAppserror:,throwable);returnResult。ofThrowable(1,throwable);}}PostMapping(rule)AuthAction(PrivilegeType。WRITERULE)publicResultDegradeRuleEntityapiAddRule(RequestBodyDegradeRuleEntityentity){ResultDegradeRuleEntitycheckResultcheckEntityInternal(entity);if(checkResult!null){returncheckResult;}DatedatenewDate();entity。setGmtCreate(date);entity。setGmtModified(date);try{entityrepository。save(entity);}catch(Throwablet){logger。error(Failedtoaddnewdegraderule,app{},ip{},entity。getApp(),entity。getIp(),t);returnResult。ofThrowable(1,t);}if(!publishRules(entity。getApp(),entity。getIp(),entity。getPort())){logger。warn(Publishdegraderulesfailed,app{},entity。getApp());}returnResult。ofSuccess(entity);}PutMapping(rule{id})AuthAction(PrivilegeType。WRITERULE)publicResultDegradeRuleEntityapiUpdateRule(PathVariable(id)Longid,RequestBodyDegradeRuleEntityentity){if(idnullid0){returnResult。ofFail(1,idcantbenullornegative);}DegradeRuleEntityoldEntityrepository。findById(id);if(oldEntitynull){returnResult。ofFail(1,Degraderuledoesnotexist,idid);}entity。setApp(oldEntity。getApp());entity。setIp(oldEntity。getIp());entity。setPort(oldEntity。getPort());entity。setId(oldEntity。getId());ResultDegradeRuleEntitycheckResultcheckEntityInternal(entity);if(checkResult!null){returncheckResult;}entity。setGmtCreate(oldEntity。getGmtCreate());entity。setGmtModified(newDate());try{entityrepository。save(entity);}catch(Throwablet){logger。error(Failedtosavedegraderule,id{},rule{},id,entity,t);returnResult。ofThrowable(1,t);}if(!publishRules(entity。getApp(),entity。getIp(),entity。getPort())){logger。warn(Publishdegraderulesfailed,app{},entity。getApp());}returnResult。ofSuccess(entity);}DeleteMapping(rule{id})AuthAction(PrivilegeType。DELETERULE)publicResultLongdelete(PathVariable(id)Longid){if(idnull){returnResult。ofFail(1,idcantbenull);}DegradeRuleEntityoldEntityrepository。findById(id);if(oldEntitynull){returnResult。ofSuccess(null);}try{repository。delete(id);}catch(Throwablethrowable){logger。error(Failedtodeletedegraderule,id{},id,throwable);returnResult。ofThrowable(1,throwable);}if(!publishRules(oldEntity。getApp(),oldEntity。getIp(),oldEntity。getPort())){logger。warn(Publishdegraderulesfailed,app{},oldEntity。getApp());}returnResult。ofSuccess(id);}privatebooleanpublishRules(Stringapp,Stringip,Integerport){ListDegradeRuleEntityrulesrepository。findAllByMachine(MachineInfo。of(app,ip,port));returnsentinelApiClient。setDegradeRuleOfMachine(app,ip,port,rules);ListDegradeRuleEntityrulesrepository。findAllByApp(app);try{rulePublisher。publish(app,rules);}catch(Exceptione){logger。error(Failedtopublishnacos,app{},app);logger。error(erroris:,e);}returntrue;}privateRResultRcheckEntityInternal(DegradeRuleEntityentity){if(StringUtil。isBlank(entity。getApp())){returnResult。ofFail(1,appcantbeblank);}if(StringUtil。isBlank(entity。getIp())){returnResult。ofFail(1,ipcantbenullorempty);}if(entity。getPort()nullentity。getPort()0){returnResult。ofFail(1,invalidport:entity。getPort());}if(StringUtil。isBlank(entity。getLimitApp())){returnResult。ofFail(1,limitAppcantbenullorempty);}if(StringUtil。isBlank(entity。getResource())){returnResult。ofFail(1,resourcecantbenullorempty);}Doublethresholdentity。getCount();if(thresholdnullthreshold0){returnResult。ofFail(1,invalidthreshold:threshold);}IntegerrecoveryTimeoutSecentity。getTimeWindow();if(recoveryTimeoutSecnullrecoveryTimeoutSec0){returnResult。ofFail(1,recoveryTimeoutshouldbepositive);}Integerstrategyentity。getGrade();if(strategynull){returnResult。ofFail(1,circuitbreakerstrategycannotbenull);}if(strategyCircuitBreakerStrategy。SLOWREQUESTRATIO。getType()strategyRuleConstant。DEGRADEGRADEEXCEPTIONCOUNT){returnResult。ofFail(1,Invalidcircuitbreakerstrategy:strategy);}if(entity。getMinRequestAmount()nullentity。getMinRequestAmount()0){returnResult。ofFail(1,InvalidminRequestAmount);}if(entity。getStatIntervalMs()nullentity。getStatIntervalMs()0){returnResult。ofFail(1,InvalidstatInterval);}if(strategyRuleConstant。DEGRADEGRADERT){DoubleslowRatioentity。getSlowRatioThreshold();if(slowRationull){returnResult。ofFail(1,SlowRatioThresholdisrequiredforslowrequestratiostrategy);}elseif(slowRatio0slowRatio1){returnResult。ofFail(1,SlowRatioThresholdshouldbeinrange:〔0。0,1。0〕);}}elseif(strategyRuleConstant。DEGRADEGRADEEXCEPTIONRATIO){if(threshold1){returnResult。ofFail(1,Ratiothresholdshouldbeinrange:〔0。0,1。0〕);}}returnnull;}} 5。maven打包 重新打包sentineldashboard模块mvncleanpackage 6。配置文件修改 application。properties添加nacos配置项nacos服务地址sentinel。nacos。serverAddr127。0。0。1:8848nacos命名空间sentinel。nacos。namespacecsentinel。nacos。usernamesentinel。nacos。password应用端集成 1。引入依赖dependencygroupIdcom。alibaba。cloudgroupIdspringcloudstarteralibabasentinelartifactIddependency 如果是maven多模块工程,父pom中添加alibaba的父pom依赖dependencyManagementdependencies!https:github。comalibabaspringcloudalibabawikiE78988E69CACE8AFB4E6988EdependencygroupIdcom。alibaba。cloudgroupIdspringcloudalibabadependenciesartifactIdversion2。2。6。RELEASEversiontypepomtypescopeimportscopedependencydependenciesdependencyManagement 2。注解支持 Sentinel提供了SentinelResource注解用于定义资源,实现资源的流控熔断降级处理。 注意:注解方式埋点不支持private方法。ServiceSlf4jpublicclassTestService{定义sayHello资源的方法,流控熔断降级处理逻辑paramnamereturnSentinelResource(valuesayHello,blockHandlersayHelloBlockHandler,fallbacksayHelloFallback)publicStringsayHello(Stringname){if(fallback。equalsIgnoreCase(name)){thrownewRuntimeException(fallback);}returnHello,name;}被流控后处理方法。1。SentinelResource注解blockHandler设置,值为函数方法名2。blockHandler函数访问范围需要是public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException。3。blockHandler函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定blockHandlerClass为对应的类的Class对象,注意对应的函数必需为static函数,否则无法解析。paramnameparamblockExceptionreturnpublicStringsayHelloBlockHandler(Stringname,BlockExceptionblockException){log。warn(已开启流控,blockException);return流控后的返回值;}被降级后处理方法注意!!!:如果blockHandler和fallback都配置,熔断降级规则生效,触发熔断,在熔断时长定义时间内,只有blockHandler生效1。SentinelResource注解fallback设置,值为函数方法名2。用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理3。返回值类型必须与原函数返回值类型一致;4。方法参数列表需要和原函数一致,或者可以额外多一个Throwable类型的参数用于接收对应的异常。5。allback函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定fallbackClass为对应的类的Class对象,注意对应的函数必需为static函数,否则无法解析。paramnameparamthrowablereturnpublicStringsayHelloFallback(Stringname,Throwablethrowable){log。warn(已降级,throwable);return降级后的返回值;}} 流控和降级处理逻辑定义详见代码注释。 SentinelResource还包含其他可能使用的属性:exceptionsToIgnore(since1。6。0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入fallback逻辑中,而是会原样抛出。exceptionsToTrace:用于指定处理的异常。默认为Throwable。class。可设置自定义需要处理的其他异常类。 3。添加配置 application。yml文件添加以下配置spring:application:name:demosentinelcloud:sentinel:阿里sentinel配置项transport:spring。cloud。sentinel。transport。port端口配置会在应用对应的机器上启动一个HttpServer,该Server会与Sentinel控制台做交互。比如Sentinel控制台添加了一个限流规则,会把规则数据push给这个HttpServer接收,HttpServer再将规则注册到Sentinel中。port:8719dashboard:localhost:8080datasource:ds1:数据源标识key,可以随意命名,保证唯一即可nacos:nacos数据源流控规则配置serveraddr:localhost:8848username:nacospassword:nacosdataid:{spring。application。name}flowrulesgroupid:SENTINELGROUPdatatype:jsonflow、degrade、paramflowruletype:flowds2:nacos:nacos数据源熔断降级规则配置serveraddr:localhost:8848username:nacospassword:nacosdataid:{spring。application。name}degraderulesgroupid:SENTINELGROUPdatatype:jsonflow、degrade、paramflowruletype:degrade验证 启动sentineldashboard。 应用端集成SentinelSDK,访问添加了降级逻辑处理的接口地址。 访问sentineldashboardweb页面:http:localhost:8080。在界面,添加相应流控和熔断规则。 Nacos管理界面查看:http:localhost:8848nacos。可以看到自动生成了两个配置界面,分别对应流控和熔断配置 点击规则详情,查看规则信息 规则json对象信息如下:〔{app:sentinel,clusterConfig:{acquireRefuseStrategy:0,clientOfflineTime:2000,fallbackToLocalWhenFail:true,resourceTimeout:2000,resourceTimeoutStrategy:0,sampleCount:10,strategy:0,thresholdType:0,windowIntervalMs:1000},clusterMode:false,controlBehavior:0,count:4000,gmtCreate:1677065845653,gmtModified:1677065845653,grade:1,id:1,ip:192。168。56。1,limitApp:default,port:8720,resource:sayHello2,strategy:0},{app:sentinel,clusterConfig:{acquireRefuseStrategy:0,clientOfflineTime:2000,fallbackToLocalWhenFail:true,resourceTimeout:2000,resourceTimeoutStrategy:0,sampleCount:10,strategy:0,thresholdType:0,windowIntervalMs:1000},clusterMode:false,controlBehavior:0,count:1000,gmtCreate:1677057866855,gmtModified:1677065997762,grade:1,id:3,ip:192。168。56。1,limitApp:default,port:8720,resource:sayHello,strategy:0},{app:sentinel,clusterConfig:{acquireRefuseStrategy:0,clientOfflineTime:2000,fallbackToLocalWhenFail:true,resourceTimeout:2000,resourceTimeoutStrategy:0,sampleCount:10,strategy:0,thresholdType:0,windowIntervalMs:1000},clusterMode:false,controlBehavior:0,count:2,gmtCreate:1677117433499,gmtModified:1677117433499,grade:1,id:6,ip:192。168。56。1,limitApp:default,port:8720,resource:ping,strategy:0}〕