Spring Boot版本:2.2.9.RELEASE Spring Cloud版本:Hoxton.SR7 Spring Cloud Alibaba版本:2.2.1.RELEASE
所有功能文档概况 官方文档
本文分为以下几部分:
普通服务集成控制台整合Nacos 自己Github重新封装地址网关集成(spring cloud gateway)控制台启动、参数和指定Nacos地址+namespace等注:Sentinel兼容Hystrix的FallbackFactory
从 Hystrix 迁移到 Sentinel方案(官方)
https://github.com/alibaba/Sentinel/wiki/Guideline:-%E4%BB%8E-Hystrix-%E8%BF%81%E7%A7%BB%E5%88%B0-SentinelSentinel 与 Hystrix 的对比(官方)
https://github.com/alibaba/Sentinel/wiki/Sentinel-%E4%B8%8E-Hystrix-%E7%9A%84%E5%AF%B9%E6%AF%94 import feign.hystrix.FallbackFactory; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Slf4j @Component public class NacosProviderClientFallback implements FallbackFactory<NacosProviderClient> { @Override public NacosProviderClient create(Throwable throwable) { return new NacosProviderClient() { @Override public String abc(String name, int age) { log.error("用 nacos-provider abc 方法失败, 异常信息={}", throwable.getMessage()); //throw new RuntimeException("abc:" + throwable.getMessage()); //这里直接返回BusinessException(),因为 throw new RuntimeException("aaaaaaadssssssssssss"); // return throwable.getClass().getName() + " : " + throwable.getMessage(); } @Override public String getEcho(String string) { log.error("用 nacos-provider getEcho 方法失败", throwable); throw new RuntimeException("getEcho:" + throwable.getMessage()); } }; } }基础包大概截图: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4jyKDPvL-1603193351351)(https://upload-images.jianshu.io/upload_images/23703439-9c0ba26f2dac32e3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
引入以下两个包,一个是sentinel核心组件,一个是整合nacos实现持久化功能组件(无关顺序)
<!-- alibaba Sentinel组件 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!-- alibaba Sentinel整合nacos组件 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>添加自定义异常处理(不加会返回默认的错误信息Blocked by Sentinel: XXXX)
注: 网上方案实现 UrlBlockHandler 接口的版本已经比较老了,新版本已经没有UrlBlockHandler 接口
/** * 没有配资源,默认Block异常处理(只能是Block异常) */ @Slf4j @Component public class CustomBlockExceptionHandler implements BlockExceptionHandler { @Autowired private ObjectMapper objectMapper; @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception { httpServletResponse.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value()); if (e instanceof FlowException) { log.error("FlowException 普通服务限流,资源信息:" + JSON.toJSONString(e.getRule())); httpServletResponse.setStatus(org.springframework.http.HttpStatus.TOO_MANY_REQUESTS.value()); ResponseUtil.responseFailed(objectMapper, httpServletResponse, 1000, "API interface limited flow."); } else if (e instanceof DegradeException) { log.error("DegradeException 普通服务降级,资源信息:" + JSON.toJSONString(e.getRule())); ResponseUtil.responseFailed(objectMapper, httpServletResponse, 1001, "API interface has been degraded."); } else if (e instanceof ParamFlowException) { ParamFlowException ex = (ParamFlowException) e; log.error("ParamFlowException 参数热点限流,资源名={},参数={},资源信息={}", ex.getResourceName(), ex.getLimitParam(), JSON.toJSONString(ex.getRule())); ResponseUtil.responseFailed(objectMapper, httpServletResponse, 1002, "API interface limited flow by params."); } else if (e instanceof AuthorityException) { log.error("AuthorityException 授权规则,资源信息:" + JSON.toJSONString(e.getRule())); ResponseUtil.responseFailed(objectMapper, httpServletResponse, 1003, "API interface limited by authority."); } else if (e instanceof SystemBlockException) { SystemBlockException systemBlockException = (SystemBlockException) e; log.error("SystemBlockException,资源名:{},资源类型:{}", systemBlockException.getResourceName(), systemBlockException.getRuleLimitApp()); ResponseUtil.responseFailed(objectMapper, httpServletResponse, 1004, "API interface limited by system."); } } }非必须。配置默认加注解 @SentinelResource(value = “abcdd”,fallbackClass = DefaultBlockFallbackHandler.class, defaultFallback = “defaultFallback”) 返回的异常,可以做默认异常配置。如果默认不符合异常需求,可以在各接口自己定制自己的异常,可以是同类,也可以是不同类(不同类必须指定fallbackClass属性,参照上面注解例子)
以下走的默认异常 或 不同类异常,不在同一个类中(默认异常加fallbackClass )
@GetMapping(value = "/woqu") @SentinelResource(value = "abcdd", fallbackClass = DefaultBlockFallbackHandler.class, defaultFallback = "defaultFallback") public String woqu(String name){ if(name.equals("abc")){ throw new IllegalArgumentException("adffdgdfg"); } if(name.equals("bbb")){ try { Thread.sleep(900); } catch (InterruptedException e) { e.printStackTrace(); } } return "aaaaaaasdjkl" + name; }以下走的自定义异常(可以用同一个类中的,也可以用不同类中的,不同类加fallbackClass 属性)
@SentinelResource(value = "chello", blockHandler = "exceptionHandler2", defaultFallback = "fallback2") @GetMapping(value = "/hello") public String abcd(@RequestParam(value = "name") String name, int age){ log.info("name={}",name); log.info("age={}",age); String content = nacosProviderClient.abc(name, age); return "我是feign: " + content; } public String exceptionHandler2(String name, int age, BlockException ex) { // Do some log here. ex.printStackTrace(); return "Oops, error occurred at "+ name + ","+ age; } public String fallback2(Throwable e){ log.info("进入sentinelResource注解测试,进入fallback2,参数b={}", e.getMessage()); return "defaultFallback"; }最后CustomRequestOriginParser这个只要是给授权黑名单或ip(AuthorityRule)生效的,可以各微服务自定义,这里放到基础包只是演示
简单实现黑名单功能:
@Component public class CustomRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest request) { // <X> 从 Header 中,获得请求来源 String origin = request.getHeader("s-user"); // <Y> 如果为空,给一个默认的 if (StringUtils.isEmpty(origin)) { origin = "default"; } return origin; } }简单实现ip限流功能:
@Component public class CustomRequestOriginParser2 implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest request) { String ip = null; try { ip = Inet4Address.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } String u = request.getRemoteUser(); return ip; } }上面基础包搭建完毕,各微服务只需引入上述基础包:
<!-- sentinel自定义封装组件 --> <dependency> <groupId>com.myyshop.framework</groupId> <artifactId>sentinel-spring-boot-starter</artifactId> <version>1.0.0-RELEASE</version> </dependency>整合Nacos持久化需要引入基础包(上面有)。配置则引入如下配置:
注意:下面配置的信息,只有server-addr中的地址修改成自己nacos-config的地址即可,其余信息都不需要动,尤其是groupId、dataId的格式(应用名-flow-rules、应用名-degrade-rules、应用名-system-rules、应用名-authority-rules、应用名-param-flow-rules)、rule-type不能修改,这个是与sentinel控制台源码中推送数据到nacos约定好的。
spring: cloud: sentinel: transport: dashboard: 192.168.128.36:8081 #主要改这里,其他基本不用动 datasource: # 名称随意,流控数据规则配置 flow: nacos: server-addr: ${spring.cloud.nacos.config.server-addr} dataId: ${spring.application.name}-flow-rules groupId: SENTINEL_GROUP # 规则类型,取值见: # org.springframework.cloud.alibaba.sentinel.datasource.RuleType rule-type: flow namespace: ${myyshop.sentinel.nacos.namespace} # 名称随意,流控数据规则配置 degrade: nacos: server-addr: ${spring.cloud.nacos.config.server-addr} dataId: ${spring.application.name}-degrade-rules groupId: SENTINEL_GROUP rule-type: degrade namespace: ${myyshop.sentinel.nacos.namespace} # 名称随意,流控数据规则配置 system: nacos: server-addr: ${spring.cloud.nacos.config.server-addr} dataId: ${spring.application.name}-system-rules groupId: SENTINEL_GROUP rule-type: system namespace: ${myyshop.sentinel.nacos.namespace} # 名称随意,流控数据规则配置 authority: nacos: server-addr: ${spring.cloud.nacos.config.server-addr} dataId: ${spring.application.name}-authority-rules groupId: SENTINEL_GROUP rule-type: authority namespace: ${myyshop.sentinel.nacos.namespace} # 名称随意,流控数据规则配置 param: nacos: server-addr: ${spring.cloud.nacos.config.server-addr} dataId: ${spring.application.name}-param-flow-rules groupId: SENTINEL_GROUP rule-type: param-flow namespace: ${myyshop.sentinel.nacos.namespace} eager: true #控制台热加载,false为懒加载(第一次调用接口才会加载)使用Sentinel Dashboard动态推,拉数据同步到Nacos,使用Sentinel Dashboard动态推,拉数据同步到Nacos。
个人改Alibaba重新封装控制台源码:(分支 lbj_release-1.7)
https://github.com/lbjfish/sentinel-parent具体实现可以参考我整理的如下大佬们的文档:
https://www.liangzl.com/get-article-detail-139092.htmlhttps://blog.csdn.net/dsh153/article/details/105767733 (跟下面那个一样)https://blog.csdn.net/weixin_41213402/article/details/105510373?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCfhttp://blog.didispace.com/spring-cloud-alibaba-sentinel-2-4/https://blog.csdn.net/LSY__/article/details/105114573?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.add_param_isCf&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.add_param_isCf (有源码1.6.2版本的)https://blog.csdn.net/lilizhou2008/article/details/97075236?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.add_param_isCf (主要参照这个的)https://blog.csdn.net/zhulin2012/article/details/100987420?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.add_param_isCf&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.add_param_isCf网关限流文档(官方)
https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81Sentinel提供针对Spring Cloud Gateway的参数开关,该开关是针对JVM -D(java -jar -Dproject.name=***)的开关,如果要开启Sentinel控制台对网关特定页面的开关,则需要配置如下(最主要是-Dcsp.sentinel.app.type=1): -Dcsp.sentinel.dashboard.server=192.168.128.36:8081 -Dcsp.sentinel.app.type=1
如果application.yml中有如下配置,则不需要加入( -Dcsp.sentinel.dashboard.server=192.168.128.36:8081)
spring: cloud: sentinel: transport: dashboard: 192.168.128.36:8081添加上述jar包启动参数之后,需要加入如下pom包依赖(引包顺序要严格按照下面这样,测试换位置不生效,不知道为什么)
<!--sentinel gateway依赖包--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>引入上述pom包依赖后,还需加入如下配置(主要是Nacos持久化配置):
spring: cloud: sentinel: transport: dashboard: 192.168.128.36:8081 #主要改这里,其他基本不用动 datasource: gw-flow: nacos: server-addr: ${spring.cloud.nacos.config.server-addr} dataId: ${spring.application.name}-gw-flow-rules groupId: SENTINEL_GROUP rule-type: gw-flow namespace: ${myyshop.sentinel.nacos.namespace} gw-api-group: nacos: server-addr: ${spring.cloud.nacos.config.server-addr} dataId: ${spring.application.name}-gw-api-rules groupId: SENTINEL_GROUP rule-type: gw-api-group namespace: ${myyshop.sentinel.nacos.namespace} degrade: nacos: server-addr: ${spring.cloud.nacos.config.server-addr} dataId: ${spring.application.name}-degrade-rules groupId: SENTINEL_GROUP rule-type: degrade namespace: ${myyshop.sentinel.nacos.namespace} eager: true #控制台热加载,false为懒加载(第一次调用接口才会加载)网关需要加入配置(主要是异常相关格式化,还有代码限流、降级等)
@Configuration public class SentinelGatewayConfig { private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public SentinelGatewayConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; } // @Bean // @Order(Ordered.HIGHEST_PRECEDENCE) // public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { // // Register the block exception handler for Spring Cloud Gateway. // return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); // } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { return new JsonSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); } // @Bean // @Order(-1) // public GlobalFilter sentinelGatewayFilter() { // return new SentinelGatewayFilter(); // } /*****************************************************************************/ //以下是添加 API 分组和route 维度 // @PostConstruct // public void doInit() { // initCustomizedApis(); // initGatewayRules(); // } private void initCustomizedApis() { Set<ApiDefinition> definitions = new HashSet<>(); ApiDefinition api1 = new ApiDefinition("some_customized_api") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ add(new ApiPathPredicateItem().setPattern("/Hansen666666")); // add(new ApiPathPredicateItem().setPattern("/gprovider/**") // .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); definitions.add(api1); GatewayApiDefinitionManager.loadApiDefinitions(definitions); } /** * 配置限流规则 */ private void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("some_customized_api") .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) .setCount(2) .setIntervalSec(1) ); // rules.add(new GatewayFlowRule("csdn") // .setCount(1) // .setIntervalSec(1) // ); // // rules.add(new GatewayFlowRule("gateway-provider") // .setCount(3) // 限流阈值 // .setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒 // ); GatewayRuleManager.loadRules(rules); } }添加自定义异常处理(不加会返回默认的错误信息Blocked by Sentinel: XXXX),下面为模仿源码写的自定义异常处理。 1.继承SentinelGatewayBlockExceptionHandler类(主要为了重写handle方法) 2.参照DefaultBlockRequestHandler类源码handleRequest方法和acceptsHtml方法(这个是SpringBoot默认返回的404异常页面,也就是说在网页上会有404异常,在Postman会显示Json异常)
public class JsonSentinelGatewayBlockExceptionHandler extends SentinelGatewayBlockExceptionHandler { public JsonSentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) { super(viewResolvers, serverCodecConfigurer); } @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { if (exchange.getResponse().isCommitted()) { return Mono.error(ex); } else { return !BlockException.isBlockException(ex) ? Mono.error(ex) : this.handleBlockedRequest(exchange, ex).flatMap((response) -> { if(ex instanceof ParamFlowException){ return WebfluxResponseUtil.responseFailed(exchange, 10000, HttpStatus.TOO_MANY_REQUESTS.value(), "API interface limited flow by gateway."); } if(ex instanceof DegradeException){ return WebfluxResponseUtil.responseFailed(exchange, 10001, HttpStatus.SERVICE_UNAVAILABLE.value(), "API interface degraded by gateway."); } return WebfluxResponseUtil.responseFailed(exchange, 10000, HttpStatus.TOO_MANY_REQUESTS.value(), "API interface limited flow by gateway."); }); } } private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) { return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable); } }控制台文档(官方) https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
目前集成控制台有两种方式:
官方jar下载地址,下载最新版本官方源码编译获取jar自己改官方源码增加Nacos持久化各自问题参照官方FAQ https://github.com/alibaba/Sentinel/wiki/FAQ
Sentinel的JVM -D启动参数配置项,包含log文件指定路径(官方) https://github.com/alibaba/Sentinel/wiki/%E5%90%AF%E5%8A%A8%E9%85%8D%E7%BD%AE%E9%A1%B9
下载jar或重新编译源码获取jar后,然后添加启动参数启动控制台,idea VM options启动参数如下:
-Dserver.port=8081 -Dcsp.sentinel.api.port=8723 -Dcsp.sentinel.dashboard.server=localhost:8081 -Dproject.name=sentinel-lbj -Dserver.servlet.session.timeout=864000 -Dnacos.serverAddr=localhost:8849 -Dnacos.namespace=d4178075-92ee-429a-baad-6cd01d59b9b8上面配置解释:
-Dserver.port 为控制台启动端口号(不配置默认8080)-Dcsp.sentinel.api.port Sentinel 客户端监控微服务 API 的端口,默认8719-Dcsp.sentinel.dashboard.server 默认控制台提供的监控(就是sentinel-dashboard的地址),此配置必须配置,如不配置,则没有sentinel-dashboard默认控制台-Dproject.name 默认控制台名称,默认sentinel-dashboard-Dserver.servlet.session.timeout 控制台登录过期时间,可以指定分钟,默认30分钟,如 7200 表示 7200 秒,60m 表示 60 分钟-Dnacos.serverAddr 官方没有此配置,自己加的自定义配置,指定Nacos配置中心地址-Dnacos.namespace 官方没有此配置,自己加的自定义配置,指定Nacos持久化命名空间,例如本项目命名空间是sentinel(d4178075-92ee-429a-baad-6cd01d59b9b8)下载官方jar或拿到源码编译后得到jar,直接执行下面代码:
nohup java -Dserver.port=8081 -Dcsp.sentinel.dashboard.server=localhost:8081 -Dnacos.namespace=d4178075-92ee-429a-baad-6cd01d59b9b8 -Dnacos.serverAddr=localhost:8849 -Dsentinel.dashboard.auth.username=myyshop -Dsentinel.dashboard.auth.password=myyshop -jar sentinel-dashboard.jar &上述是Linux后台运行方式,加入 nohup **** & 就会后台运行,否则关闭服务就断了。Windows类似,不需要加 nohup **** &
以Nacos限流配置为例,FlowRuleNacosPublisher 发布配置到Nacos,发布的配置是JSON格式,但是JSON没有格式化,不美观,以下推送到Nacos实现美化JSON功能,这样容易改Nacos配置。
@Component("flowRuleNacosPublisher") public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> { @Autowired private ConfigService configService; @Autowired private Converter<List<FlowRuleEntity>, String> converter; @Override public void publish(String app, List<FlowRuleEntity> rules) throws Exception { AssertUtil.notEmpty(app, "app name cannot be empty"); if (rules == null) { return; } //lbj增加格式化、美化json内容 - start String content = converter.convert(rules); JSONArray jsonArray = JSONArray.parseArray(content); String prettyContent = JSON.toJSONString(jsonArray, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat); //lbj增加格式化、美化json内容 - end configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX, NacosConfigUtil.GROUP_ID, prettyContent); } }自己加入Nacos的 -Dnacos.serverAddr 和 -Dnacos.namespace 相关参数,这样可以直接在JVM -D启动参数动态加,而不用改源码,再编译,这样很麻烦:
@Bean public ConfigService nacosConfigService() throws Exception { String namespace = System.getProperty("nacos.namespace"); String serverAddr = Optional.ofNullable(System.getProperty("nacos.serverAddr")).orElse("localhost:8848"); Properties properties = new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr); if(StringUtil.isNotBlank(namespace)){ properties.setProperty(PropertyKeyConst.NAMESPACE, namespace); } return ConfigFactory.createConfigService(properties); }上述配置在 NacosConfig 类中,通过System.getProperty获取启动参数, 获取 namespace 和 serverAddr。
如果你的微服务部署在docker中,并且没有指定host模式(–net=host),默认docker会走bridge模式,那这样docker镜像内部会分配自定义虚拟ip,这样跟主机互ping没有问题,但在一个局域网其他网段ip要想ping通docker内部虚拟ip就不行了。
而现在国内大部分公司都是使用docker去部署服务,Sentinel默认拿到ip是docker内部的虚拟ip,如果Sentinel部署在另外一台机器上(生产都要分开部署),则拿不到当前服务的簇点链路,因为Sentinel访问不到当前服务的端口数据(假如当前服务ip+端口=172.17.0.3:8719)。
注:172.17.0.3是docker内部虚拟ip,192.168.128.20是主机ip,8719是Sentinel客户端监控微服务 API 的端口,默认8719
host模式:使用 --net=host 指定。 none模式:使用 --net=none 指定。 bridge模式:使用 --net=bridge 指定,默认设置。 container模式:使用 --net=container:NAME_or_ID 指定。因此有两种方案可以解决此情况:
将docker默认模式改成host模式(–net=host)将Sentinel要监控的API端口(默认8719)从docker中绑定到宿主机上,可以使用 -p 参数显式将一个或者一组端口从容器里绑定到宿主机上(ip:hostPort:containerPort)第一套方案不用说了,如果选定第二套方案,需要每个微服务都配置一个端口暴露给Sentinel,如果10个微服务,就暴露10个,不能重复,需要如下配置(这里以uaa为例):
spring: cloud: sentinel: transport: client-ip: 192.168.128.20 #如果是docker容器,需要指定宿主机ip,这样sentinel就不会拉docker内虚拟ip port: 8777 #指定Sentinel客户端监控微服务API的端口,上述ip+端口配置好后(以uaa为例),通过docker run启动uaa服务(这里uaa暴露给sentinel的端口是8777,其他服务端口尽量不能重复),配置如下:
docker run -p 7001:7001 -p 8777:8777 --name ${app_name} \ --link registry2:registry2 \ -v /usr/local/server-log/uaa-server:/logs \ -d ${app_name}:latest注:8777是Sentinel客户端监控 uaa的端口,不指定默认8719