写点什么

【HZERO 微服务平台 4】源码分析之 admin 服务刷新路由、权限、swagger 的过程

作者:qiaoxingxing
  • 2021 年 12 月 13 日
  • 本文字数:3659 字

    阅读完需:约 12 分钟

[toc]

概述

hzero 的微服务体系里, 服务除了要注册到注册中心(nacos 等), 还注册到 admin 服务(平台治理服务);

admin 服务接收到业务服务的注册信息, 负责完成路由刷新、权限刷新、swagger 信息刷新, 任意一项刷新失败都会导致服务出现异常现象;

由于刷新是异步执行, 且涉及的服务较多, 出现问题通常不好排查; 详细了解注册流程有助于快速排查定位;

注册流程图:

注册成功的表现:

  • 服务没有不停地报错

  • swagger 中可以看到服务并且可以调用接口;

注册失败时的排查方法

检查注册流程每个环节, 与之对应的排查过程:


代码调用过程

业务服务:

业务服务依赖了 jar 包hzero-boot-admin.jar, 其中注册了自动配置:AdminRegistrationAutoConfiguration, 容器启动时自动执行:


AdminAutoRegistration#start  //SmartLifecycle.startAdminAutoRegistration#registerretryAlwaysUntilSuccess //死循环, 从一启动就开始注册, 直到成功AdminTransport#transport 注册到adminAdminTransport#requestAdmin//请求的地址: post 8061端口: /actuator/service-init-registry/register//requestBody: org.hzero.boot.admin.registration.Registration 实例response = (Response)this.httpTransporter.transport(new StaticEndpointHttpRequest(instance, staticEndpoint, requestBody, Response.class));
复制代码


其中的注册接口:


StaticEndpoint#ADMIN_SERVICE_REGISTERADMIN_SERVICE_REGISTER("/actuator/service-init-registry/register", HttpMethod.POST, true, "HZERO治理服务注册服务的api接口"),
复制代码


doc 接口:


/v2/iosp/api-docs...swagger.controller.CustomController.getDocumentation
/v2/api-docsspringfox.documentation.swagger2.web.Swagger2Controller.getDocumentation
复制代码

admin 服务:

业务服务调用 admin 的注册接口actuator/service-init-registry/register:


ServiceInitRegistryEndpoint#registerRestServiceInitRegistry#registerassertHealth(service); //只判断服务的状态, 如果up就把服务添加到启动成功的列表里serviceInitRegistryRepository.add(service);
复制代码


其中的ServiceInitRegistryRepository接口:维护需要初始化(未刷新路由、权限、swagger)的服务信息; 具体实现:RedisServiceInitRegistryRepository, 存储在 redis db1 hadm.services-to-init;


其中的RestServiceInitRegistry类:


public class RestServiceInitRegistry implements ServiceInitRegistry, SmartLifecycle { ...RestServiceInitRegistry#start  //SmartLifecycle.start, 应用启动成功后自动执行; RestServiceInitRegistry#doStart this.initExecutor = new ThreadPoolExecutor( ... //服务刷新执行器, 执行刷新任务; this.checkExecutor = new ThreadPoolExecutor( ...  //检查执行器, while循环检查, 负责清理已下线的过期服务、如果检查到未刷新服务唤醒服务刷新线程(initExecutor)
复制代码


initExecutor的流程;


RestServiceInitRegistry#doStartthis.initExecutor.execute(() -> {init(getUnInitializedServices())  //刷新未初始化的服务ServiceInitRegistry#initRestServiceInitRegistry#doInitRestServiceInitRegistry#executeInit //初始化链, 调用刷新链buildInitChain().doChain(context);//依次执行InitFilter的实例RouteInitFilterPermissionInitFilterSwaggerInitFilter
复制代码


任务最终执行是责任链/过滤链模式, 相关代码: ...admin.infra.chain包、InitFilter、InitChain、DefaultInitChain、InitChainFactoryBean

权限刷新

自动刷新权限

服务注册后自动刷新权限流程:


admin 服务:


PermissionInitFilter#doFilterPermissionRefreshService#innerRefreshv1/tool/permission/inner/freshACTUATOR_PERMISSION("/v2/actuator/permission", HttpMethod.GET, false, "获取服务权限信息"); //注意端口不是管理端口, 是服务端口; 
复制代码


iam 服务:


ToolPermissionController#innerRefreshIDocumentServiceImpl#refreshPermissionAsyncIDocumentServiceImpl#refreshPermissionParseServicePermissionImpl#parserList<Permission> permissions = this.permissionHandler.handle(serviceName, instance);AbstractPermissionHandler#handleActuatorPermissionHandler#doHandlefetchPermissionDataByIp
复制代码

手动刷新权限

开发管理 - 系统工具 - 刷新权限, 调用/iam/v1/tool/permission/fresh的过程:


iam 服务:


ToolPermissionController#refreshIDocumentServiceImpl#refreshPermissionParseServicePermissionImpl#parserAbstractPermissionHandler#handleParseServicePermissionImpl#processPermissions
复制代码


ParseService#parser的 javadoc:


解析权限步骤:


  • 判断是否要跳过解析服务权限,默认跳过 register, gateway, oauth

  • 调用服务接口 /v2/choerodon/api-docs 获取服务 swagger json 文档

  • 从 json 中解析权限

  • 如果权限编码重复,加上 HttpMethod 后缀

  • 保存权限,编码存在则更新,不存在则新增

  • 如果要清除过期权限,则清除过期权限

  • 缓存权限到 Redis,默认存储到 db4>gateway:permissions


实测: 手动刷新权限不会更新HADM_SWAGGER表里的数据; 程序每次启动的时候会更新HADM_SWAGGER并且刷新权限;

权限刷新报错: parse_permission_data.failure

症状: 【系统工具】-【刷新权限】报错:


hiam.error.parse_permission_data.failure
复制代码


原因: iam 刷新权限时获取的结果是乱码, 导致解析失败;


乱码原因: jhipster 的 prod 模式开启了 http 响应压缩, 返回的数据被压缩, response header 里包含: content-encoding: gzip;restTemplate 没有兼容压缩的情况;关闭压缩: server.compression.enabled: falseUsing JHipster in production

路由刷新

路由配置

ChoerodonRouteData choerodonRouteData = new ChoerodonRouteData();choerodonRouteData.setName(environment.getProperty("hzero.service.current.name", "demo"));choerodonRouteData.setPath(environment.getProperty("hzero.service.current.path", "/demo/**"));choerodonRouteData.setServiceId(environment.getProperty("hzero.service.current.service-name", "demo"));
复制代码


  • choerodonRouteData.name: 路由名称, gatewayRoute 的标识,对应 gatewayRoute 的 id 字段

  • choerodonRouteData.path: 路由的路径;

  • choerodonRouteData.serviceId: 通过实测、查看代码, 业务服务的这个配置无效; 虽然无效, 但是也不能不设置, 否则程序启动报错: bean 实例化失败;


//admin刷新路由的时候, 会把路由配置里的serviceId设置为serviceName: ParseRouteServiceImpl#executeRefreshRoutedata.setServiceId(serviceName);
复制代码

如果开启了 context-path, 路由配置需注意

//如果开启了context-path, 这里的路径必须和context-path保持一致choerodonRouteData.setPath(environment.getProperty("hzero.service.current.path", "/demo-qxx/**"));//这里设置为falsechoerodonRouteData.setStripPrefix(false);
复制代码

业务服务路由的 name 或 path 重复

新服务注册的路由如果和已有服务重复, 新服务路由注册失败, 但服务的日志里没有任何提示, swagger 里看不到服务, 普通开发者很难排查问题;admin 服务里会 info 级日志:


ParseRouteServiceImpl#executeRefreshRouteLOGGER.info("route conflict, try to modify it on the interface, cause: {}", cause.toString());
复制代码


最终采用的解决办法: 服务启动时路由注册成功才能启动(hzero 原版检查健康状态为 up 就能启动)

手动刷新路由报错: ExceptionResponse: For input string: "80,80"

dev 环境正常, prod 环境访问服务时报错error.permission.routeNotFound, 路由管理能看到路由, 手动刷新路由报错:


refresh service route error, serviceName=hw-platform, ex=fetch failed, instance: hw-platform, ex=http transport failed, ExceptionResponse: For input string: "80,80"
复制代码


admin 报错代码:


StringHttpTransporter#transportExceptionResponse response = objectMapper.readValue(body, ExceptionResponse.class);if (response != null && response.getFailed()) {    throw new RestClientException("http transport failed, ExceptionResponse: " + response.getMessage());}
复制代码


业务服务报错:


java.lang.NumberFormatException: For input string: "80,80"    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:1.8.0_272]    at java.lang.Integer.parseInt(Integer.java:580) ~[na:1.8.0_272]    at java.lang.Integer.parseInt(Integer.java:615) ~[na:1.8.0_272]    at ....swagger.controller.CustomController.componentsFrom(CustomController.java:145) ~[starter-core-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
复制代码


代码位置:


...swagger.controller.CustomController#componentsFromString port = request.getHeader("X-Forwarded-Port");
if (hasText(port)) { builder.port(Integer.parseInt(port));}
复制代码


报错原因: X-Forwarded-Port=80,80;修复方法: 加 try..catch, 重置为-1;

用户头像

qiaoxingxing

关注

还未添加个人签名 2021.12.07 加入

还未添加个人简介

评论

发布
暂无评论
【HZERO微服务平台4】源码分析之admin服务刷新路由、权限、swagger的过程