基于Spring Boot 2.0的IoT应用集成和使用servicecomb实践
目录
- 集成步骤
- 集成效果
- 运行时变化
- 开发方式变化
- 周边工具变化
- 开启metrics监控
- 常见问题
- 三方件冲突
- SpringMVC数据类型支持
- 依赖于Spring MVC特定机制的业务逻辑
本文通过一个IoT的应用展现在Spring Boot 2.0中集成和使用servicecomb。IoT应用原来使用Spring Boot 2.0开发,然后通过少量的步骤集成servicecomb。本文说明集成servicecomb以后给应用带来了哪些新特性,中间存在的一些变化和改造过程中碰到的问题。本文档根据servicecomb 2.0.1版本刷新,详细的版本介绍请参考SDK配套关系说明。
集成步骤
在Spring Boot中集成servicecomb的基本原理,可以参考在spring boot中使用java chassis的描述。将一个现有 Spring Boot的应用改造为servciecomb,一般可以采用下面的步骤。需要注意下面的步骤是一般的改造步骤,完成这些步骤后,可以识别出常见问题,逐个解决这些问题。
增加依赖关系
在dependencyManagement里面导入servicecomb的依赖管理,dependencyManage能够简化依赖管理的工作量,减少三方件冲突,详细可以参考使用maven管理复杂依赖关系的技巧。
<dependencyManagement> <dependencies> <dependency> <groupId>org.apache.servicecomb</groupId> <artifactId>java-chassis-dependencies</artifactId> <version>2.0.1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
然后引入servicecomb对于spring boot 2提供的支持组件包。
<dependencies> <dependency> <groupId>org.apache.servicecomb</groupId> <artifactId>java-chassis-spring-boot-starter-servlet</artifactId> </dependency> </dependencies>
2. 启用ServiceComb能力
通过@EnableServiceComb标签在Spring Boot 2中启用和加载servicecomb。在Spring Boot 2中,开发者会将服务通过DispatcherServlet发布为REST接口,在Spring Boot 2默认实现中,REST接口通过DispatcherServlet发布,集成servicecomb后,服务通过servicecomb提供的RestServlet进行发布。集成后,两个Servlet可以并存,可以将他们的上下文设置为不同的前缀,以避免冲突。具体参考步骤3。
@SpringBootApplication @EnableServiceComb public class UserMain { public static void main(String[] args) throws Exception { try { SpringApplication.run(UserMain.class, args); } catch (Exception e) { e.printStackTrace(); } } }
3. 发布服务接口
和Spring Boot 2一样,servicecom也会扫描@RestContoller标签的类,并将这些类发布为REST接口。同时将接口信息注册到服务中心。可以在application.yml中指定服务中心地址,并提供简单的微服务信息。其中service_description.name可以使用原来项目的名字,servicecomb.rest.address采用tomcat的监听端口server.port进行发布。其中server.servlet.path=/mvc和servicecomb.rest.servlet.urlPattern=/cse/*分别是Spring Boot的DispatcherServlet和servicecom的RestServlet前缀。
APPLICATION_ID: MY_APPLICATION service_description: name: ${spring.application.name} version: 0.0.1 servicecomb: service: registry: address: http://localhost:30100 instance: watch: false rest: address: 0.0.0.0:9091 servlet: urlPattern: /cse/* server: servlet: path: /mvc
集成效果
在上面的例子中,很简单的将原来的Spring Boot 2.0应用的运行时转换成了了servicecom。但是转换本身不是目的,下面通过一些变化来说明转换后给业务系统带来的收益以及可能的功能丢失和工作量,以方便用户评估是否值得做这样的集成。
运行时变化
运行时变化是收益和损失的直接来源。在上面的改造中,客户端代码和服务端代码均没有变化(或者极少变化),客户端仍然使用RestTemplate调用服务端代码,服务端仍然提供的是REST接口,可以使用post man等工具访问。运行时变化不修改微服务对外的接口行为,这个是改造的基础。
改造前,Spring Boot 2的运行时简图如下。在客户端,可以使用Spring RestTemplate提供的各种MessageConverters处理请求消息编码和响应消息解码,在服务端,可以使用Spring MVC提供的各种功能对用户参数进行校验。
改造后,运行时简图如下。servicecomb客户端和服务端均提供了统一一致的处理链,并默认实现了负载均衡、熔断容错、灰度发布等功能。
运行时功能的一个核心特征是快速完成了应用微服务化改造。微服务的一个基本特征是多实例,通过网络接口进行通信。因此解决服务发现问题以及通信不可靠性问题是进行微服务化的一个基本保障。通过集成servicecomb,帮用户快速构建这些能力,减少了需要学习和开发其他Spring Boot、Spring Cloud组件的成本。
servicecomb本身也提供了丰富的开发和扩展能力,可以从设计选型参考了解servicecomb的更多信息。
开发方式变化
使用Spring Boot 2,开发者可以使用RestTemplate或者使用Feign来访问服务端接口。servicecomb也支持两种方式RestTemplate和RPC。相比较于Feign,servicecomb的RPC更加简洁,无需在客户端定义和使用REST标签。
@RpcReference(microserviceName = "hello", schemaId = "hello") private Hello hello; System.out.println(hello.sayHi("Java Chassis")); |
这种方式适用于采用servicecomb框架开发的微服务之间的访问。如果访问第三方,servicecomb已提供非常简单的方式,详细参考调用第三方服务
通过集成servicecomb,开发者可以方便的在代码中随时使用REST和RPC,非常灵活,大大节省书写代码的时间。
周边工具变化
上面的讨论都限制在微服务本身。为了保证业务功能能够持续高效运行运维,还需要给微服务提供一个功能强大的运行环境,实现微服务运行状况的监控、微服务功能的实时调整(流量控制、灰度发布、熔断容错等)。这里可以借助于商业解决方案快速实现或者开源解决方案自行搭建。
l 商业解决方案
华为[微服务引擎]( https://console.huaweicloud.com/cse/)提供了一站式微服务管理功能。将改造的应用部署到微服务引擎,即可实现微服务目录查看、接口管理、动态治理等多种功能。
微服务引擎的核心组件包括服务中心、配置中心和治理中心。除了将业务部署到云上,开发者可以在本地使用这些服务,只需要有可用的网络连接,注册华为云微服务引擎并获取AK/SK身份信息。
l 开源解决方案
[服务中心]( https://github.com/apache/incubator-servicecomb-service-center)。服务中心提供了注册发现服务,还提供了前端服务,用于查看服务目录和进行接口测试。
[配置中心]( https://github.com/ctripcorp/apollo)。这个是携程开发的配置中心。CSE支持通过Netflix Archaius扩展,使用各种配置服务。
[调用链zipkin]( https://github.com/openzipkin/zipkin)。这个标准的zipkin调用链服务。CSE支持通过Hanlder扩展实现了与zipkin调用链对接。
[调用链skywalking]( https://github.com/apache/incubator-skywalking/tree/master/apm-sniffer/apm-sdk-plugin/servicecomb-plugin)。
可以从开发指南了解如何集成和使用这些开源组件。
servicecomb本身作为Spring Boot 2.0的一个Servlet运行,因此Spring Boot、Spring Cloud提供的大多数组件,用户也可以选择使用。Spring Cloud场景会选择eureka等作为服务发现服务,CSE需要使用服务中心作为服务发现。在服务中心选择上,目前还未有统一的标准,配套的SDK只能采用推荐的服务发现工具。
开启metrics监控
servicecomb提供了强大的metrics功能,能够帮助开发者分析性能问题。改造后的引用只需要引入metrics相关jar包,并在application.yml增加开关启用metrics,即可使用。metrics数据可以通过两种方式获取。
l 通过接口查询
地址: http://localhost:9091/metrics 输出参考:(截取了执行时间和平均调用次数,统计周期为60s) "servicecomb.invocation(operation=huawei-cloud-ioccity-app-basemgmt.EquipmentInfoController.getEquipmentList,role=PRODUCER,stage=execution,statistic=totalTime,status=200,transport=rest)":0.011417577750000001, "servicecomb.invocation(operation=huawei-cloud-ioccity-app-basemgmt.EquipmentInfoController.getEquipmentList,role=PRODUCER,stage=total,statistic=count,status=200,transport=rest)":0.016666666666666666 |
l 输出到日志文件
配置项: cse.metrics.publisher.defaultLog.enabled=true 输出参考:(截取了性能部分) producer: tps latency(ms) max-latency(ms) queue(ms) max-queue(ms) execute(ms) max-execute(ms) operation rest.200: 0 0.000 727.756 0.000 23.542 0.000 704.214 huawei-cloud-ioccity-app-basemgmt.EquipmentInfoController.getEquipmentList |
常见问题
在使用新的框架对业务进行改造的时候,都会碰到一些问题。原理介绍了改造过程中可能碰到问题,以及一些通用的模式,可以阅读这个文章以评估改造的工作量。下面列举了在改造IoT系统中碰到的几个常见问题及其解决方案。
三方件冲突
在引入新的框架或者组件的时候,三方件冲突是非常常见的问题。解决三方件冲突的前提是版本之间能够兼容。冲突的原因可能很多,解决这类问题没有固定的思路,但掌握了maven依赖关系管理技巧以及理解冲突产生的原因后,通常分析和解决这类问题会变得更加简单。建议阅读maven管理技巧。
SpringMVC数据类型支持
servicecomb支持SpringMVC的标签定义REST接口,一般情况下,将SpringMVC的应用改造为servicecomb都会非常简单。但是servicecomb和SpringMVC的运行时不同,设计目标也有差异,所以还会存在一些差异的地方。这块的主要差异点是使用CSE定义REST接口,支持的annotation和数据类型相对于SpringMVC变少了,详细说明参考。
比如,下面的一些REST接口定义在servicecomb中是不能使用或者有限制使用的。
import org.springframework.data.domain.Page; @GetMapping(params = { "pageNumber", "pageSize" }) public ResponseEntity<Page<T>> listByPagination(@RequestParam int pageNumber, @RequestParam int pageSize)
该接口在定义的时候,Page是一个interface。servicecomb不支持在接口定义上使用泛型,包括interface、abstract class等。servicecomb做出这个限制的原因是因为servicecomb需要遵循open API规范,接口定义必须能够存在对应的swagger描述。如果使用interface和abstract class,那么则无法通过swagger来描述这个接口。
l 解决方案
业务代码应该采用合理的分层设计,这样就可以保证代码能够非常灵活的在不同框架下进行迁移。如果使用Spring开发,分层设计可以遵循Spring的一些建议。业务逻辑在@Service中实现,框架接口发布在@Controller实现。当在不同框架发布服务的时候(比如Servlet、Spring MVC、servicecomb等),只需要简单的调整@Controller代码,不需要修改@Service代码。以上面的接口举个例子。
@Service public class MyService { public Page<T> findAll(Pageable pageable) }
发布为Spring MVC的Endpoint:
@RestController public class SpringMVCEndpoint { @Autowired private MyService service; @PostMapping(value = "/search") public ResponseEntity<Page<T>> findAll(@RequestParam int pageNumber, @RequestParam int pageSize) { return new ResponseEntity<Page<T>>(service.findAll(pageable), HttpStatus.OK); } }
发布为Servlet:
public class ServletEndpoint extends HttpServlet { @Autowired private MyService service; void doPost(HttpServletRequest req, HttpServletResponse resp) { // extract request service.findAll(request, HttpStatus.OK); // send response } }
发布为servicecomb的接口:
Class PageModel<T> { private int totalPages; private int totalElements; private int number; private int size; private int numberOfElements; private List<T> content; private boolean first; private boolean last; } Class Record { private long id; } @RestSchema(schemaId= "CSEEndpoint") @RequestMapping(value = "/cseEndpoint") public class CSEEndpoint { @Autowired private MyService service; @PostMapping(value = "/search") public ResponseEntity<PageModel<Record>> findAll(@RequestParam int pageNumber, @RequestParam int pageSize) { Page<Record> pages = service.findAll(example); // convert pages to PageModel PageModel<Page> result = ; return new ResponseEntity<List<T>>(result, HttpStatus.OK); } }
依赖于Spring MVC特定机制的业务逻辑
在我们的改造步骤二中,去掉了DispatcherServletAutoConfiguration,即Spring MVC REST 提供的相关功能被移除了。Spring MVC提供的有些接口需要依赖于这个功能,移除后,会导致这部分功能无法使用。比如:
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 接收到请求,记录请求内容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 记录下请求内容 log.info("URL : " + request.getRequestURL().toString()); }
上面这段代码通过RequestContextHolder获取Servlet请求,并从请求中获取一些头信息来记录日志。RequestContextHolder依赖于Spring MVC REST来设置请求上下文,在整改后,获取到的attributes为空,从而导致抛出NPE异常。
l 解决方案
这种依赖于平台提供的特定功能,在进行改造的时候,都需要结合使用的平台,看是否有替代方案。这段代码的本意是实现审计日志或者调用链等逻辑,servicecomb提供Handler来获取这些信息,并已经实现了调用链等功能,所以可以直接使用,或者通过自定义Handler、HttpFilter等机制实现类似的功能。
- 点赞
- 收藏
- 关注作者
评论(0)