【摘要】 本文介绍了基于Spring Boot 2.0的IoT应用集成和使用CSE的步骤,收益以及问题。使用CSE,可以快速的让一个原型系统,具备微服务治理和运维能力,加快了业务上线速度,降低服务运行风险。
本文通过一个IoT的应用展现在Spring Boot 2.0中集成和使用CSE。IoT应用原来使用Spring Boot 2.0开发,通过少量的步骤集成CSE。本文说明集成CSE以后给应用带来了哪些新特性,中间存在的一些变化和改造过程中可能碰到的问题。通过这个例子,我们快速的将一个Spring Boot 2.0的原型系统,改造为具备通用可靠性和运维能力的微服务,加快了业务系统的建设速度。本文档根据CSE JAVA SDK 2.3.52版本刷新。
集成步骤
基本集成过程分为三步。
-
增加依赖关系
在dependencyManagement里面导入CSE对于spring boot 2提供的组件包依赖管理以及CSE自身的依赖管理。需要注意将spring boot 2的依赖管理放到前面,因为CSE缺省的依赖管理是spring boot 1的。如果业务需要引入特定的spring boot或者spring cloud版本,也可以通过这个机制将spring boot或者spring cloud的dependencyManagement引入。[这里]( http://servicecomb.apache.org/cn/docs/maven_dependency_management/) 有一个介绍dependencyManagement的文章,使用dependencyManagement?解决三方件冲突,是非常有用的技巧,正确掌握它的原理,能够帮助开发者快速定位和解决常见的三方件冲突问题。[这里](https://huaweicse.github.io/cse-java-chassis-doc/using-cse-in-spring-boot/spring-boot-2.html)说明了CSE集成spring boot 1、spring boot 2的原理和组件。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.huawei.paas.cse</groupId>
<artifactId>cse-dependency-spring-boot2</artifactId>
<version>${paas.cse.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后引入CSE对于spring boot 2提供的支持组件包。
<dependencies>
<dependency>
<groupId>com.huawei.paas.cse</groupId>
<artifactId>cse-solution-service-engine</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.servicecomb</groupId>
<artifactId>spring-boot2-starter-servlet</artifactId>
</dependency>
</dependencies>
2. 修改运行参数
通过@EnableServiceComb标签在Spring Boot 2中启用和加载CSE。在Spring Boot 2中,开发者会将服务通过DispatcherServlet发布为REST接口,在Spring Boot 2默认实现中,REST接口通过DispatcherServlet发布,集成CSE后,服务通过CSE提供的RestServlet进行发布。集成后,两个Servlet可以并存,可以将他们的上下文设置为不同的前缀,以避免冲突。具体参考步骤3。
@EnableServiceComb
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3. 发布服务接口
和Spring Boot 2一样,CSE也会扫描@RestContoller标签的类,并将这些类发布为REST接口。同时将接口信息注册到服务中心。可以在application.yml中指定服务中心地址,并提供简单的微服务信息。其中service_description.name可以使用原来项目的名字,cse.rest.address采用tomcat的监听端口server.port进行发布。其中server.servlet.path=/mvc和servicecomb.rest.servlet.urlPattern=/cse/*分别是Spring Boot的DispatcherServlet和CSE的RestServlet前缀。
APPLICATION_ID: houseapp
service_description:
name: ${spring.application.name}
version: 0.0.1
cse:
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应用的运行时转换成了了CSE。但是转换本身不是目的,下面通过一些变化来说明转换后给业务系统带来的收益以及可能的功能丢失和工作量,以方便用户评估是否值得做这样的集成。
运行时变化
运行时变化是收益和损失的直接来源。在上面的改造中,客户端代码和服务端代码均没有变化(或者极少变化),客户端仍然使用RestTemplate调用服务端代码,服务端仍然提供的是REST接口,可以使用post man等工具访问。运行时变化不修改微服务对外的接口行为,这个是改造的基础。
改造前,Spring Boot 2的运行时简图如下。在客户端,可以使用Spring RestTemplate提供的各种MessageConverters处理请求消息编码和响应消息解码,在服务端,可以使用Spring MVC提供的各种功能对用户参数进行校验。
改造后,运行时简图如下。CSE客户端和服务端均提供了统一一致的处理链,并默认实现了[负载均衡]( https://docs.servicecomb.io/java-chassis/zh_CN/references-handlers/loadbalance.html)、[熔断容错]( https://docs.servicecomb.io/java-chassis/zh_CN/build-provider/configuration/downgrade-strategy.html)、[流量控制]( https://docs.servicecomb.io/java-chassis/zh_CN/build-provider/configuration/ratelimite-strategy.html)等功能。
运行时功能的一个核心特征是快速完成了应用微服务化改造。微服务的一个基本特征是多实例,通过网络接口进行通信。因此解决服务发现问题以及通信不可靠性问题是进行微服务化的一个基本保障。通过集成CSE,帮用户快速构建这些能力,减少了需要学习和开发其他Spring Boot、Spring Cloud组件的成本。
CSE本身也提供了丰富的开发和扩展能力,可以从[设计]( https://bbs.huaweicloud.com/blogs/1fc9427c088611e89fc57ca23e93a89f)和[指南]( https://docs.servicecomb.io/java-chassis/zh_CN/)了解CSE的更多信息。
开发方式变化
使用Spring Boot 2,开发者可以使用RestTemplate或者使用Feign来访问服务端接口。CSE也支持两种方式RestTemplate和RPC。相比较于Feign,CSE的RPC更加简洁,无需在客户端定义和使用REST标签。
@RpcReference(microserviceName = "hello", schemaId = "hello")
private Hello hello;
System.out.println(hello.sayHi("Java Chassis"));
这种方式适用于采用CSE框架开发的微服务之间的访问。如果访问第三方,CSE也提供了类似Feign的机制,并且提供了比Feign强大的负载均衡管理和治理能力(Consumer处理链)。相关例子可以参考ServiceComb代码[示例]( https://github.com/apache/incubator-servicecomb-java-chassis/blob/master/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/thirdparty/Test3rdPartyInvocation.java)。
通过集成CSE,开发者可以方便的在代码中随时使用REST和RPC,非常灵活,大大节省书写代码的时间。
周边工具变化
上面的讨论都限制在微服务本身。为了保证业务功能能够持续高效运行运维,还需要给微服务提供一个功能强大的运行环境,实现微服务运行状况的监控、微服务功能的实时调整(流量控制、灰度发布、熔断容错等)。这里可以借助于商业解决方案快速实现或者开源解决方案自行搭建。
l 商业解决方案
华为[微服务引擎]( https://console.huaweicloud.com/cse/)提供了一站式微服务管理功能。将改造的应用部署到微服务引擎,即可实现微服务目录查看、接口管理、动态治理等多种功能。
微服务引擎的核心组件包括服务中心、配置中心和治理中心。除了将业务部署到云上,开发者可以在本地使用这些服务,只需要有可用的网络连接,注册华为云微服务引擎并获取AK/SK身份信息。[买房系统]( https://github.com/huaweicse/HouseApp/tree/spring-boot-2.0-with-zipkin)提供了一个本地使用华为云服务的例子。开发者只需要在application.yml中配置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)。
可以从[开发指南]( https://docs.servicecomb.io/java-chassis/zh_CN/)了解如何集成和使用这些开源组件。
CSE本身作为Spring Boot 2.0的一个Servlet运行,因此Spring Boot、Spring Cloud提供的大多数组件,用户也可以选择使用。Spring Cloud场景会选择eureka等作为服务发现服务,CSE需要使用服务中心作为服务发现。在服务中心选择上,目前还未有统一的标准,配套的SDK只能采用推荐的服务发现工具。
负载均衡和隔离能力展示
这里通过一个例子展现改造后,负载均衡能力和隔离能力。这个[例子]( https://bbs.huaweicloud.com/blogs/72a312f09c8911e89fc57ca23e93a89f)展现了在多实例部署情况下,滚动升级零中断。
[购房系统]( https://github.com/huaweicse/HouseApp/tree/spring-boot-2.0-with-zipkin)实现了一个完整的spring cloud应用,可以使用这个系统进行体验。
使用zikpin调用链追踪能力展示
这里演示快速在系统中构建调用链跟踪能力。改造后的引用只需要引入调用链jar包和在application.yml中增加处理链配置和zipkin服务器地址,并安装zipkin服务器,就能够使用调用链了。[开发步骤]( https://docs.servicecomb.io/java-chassis/zh_CN/general-development/microservice-invocation-chain.html)。
[购房系统]( https://github.com/huaweicse/HouseApp/tree/spring-boot-2.0-with-zipkin)实现了调用链。
开启metrics监控
CSE提供了强大的[metrics功能]( https://docs.servicecomb.io/java-chassis/zh_CN/general-development/metrics.html),能够帮助开发者分析性能问题。改造后的引用只需要引入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
常见问题
在使用新的框架对业务进行改造的时候,都会碰到一些问题。[原理]( https://bbs.huaweicloud.com/blogs/ba7b62178cb811e89fc57ca23e93a89f)介绍了改造过程中可能碰到问题,以及一些通用的模式,可以阅读这个文章以评估改造的工作量。下面列举了在改造IoT系统中碰到的几个常见问题及其解决方案。
三方件冲突
在引入新的框架或者组件的时候,三方件冲突是非常常见的问题。解决三方件冲突的前提是版本之间能够兼容。冲突的原因可能很多,解决这类问题没有固定的思路,但掌握了maven依赖关系管理技巧以及理解冲突产生的原因后,通常分析和解决这类问题会变得更加简单。建议阅读[maven管理技巧]( http://servicecomb.apache.org/cn/docs/maven_dependency_management/)。
SpringMVC数据类型支持
CSE支持SpringMVC的标签定义REST接口,一般情况下,将SpringMVC的应用改造为CSE都会非常简单。但是CSE和SpringMVC的运行时不同,设计目标也有差异,所以还会存在一些差异的地方。这块的主要差异点是使用CSE定义REST接口,支持的annotation和数据类型相对于SpringMVC变少了,详细说明[参考]( https://docs.servicecomb.io/java-chassis/zh_CN/using-java-chassis-in-spring-boot/diff-spring-mvc.html)。
比如,下面的一些REST接口定义在CSE中是不能使用或者有限制使用的。
import org.springframework.data.domain.Page;
@GetMapping(params = { "pageNumber", "pageSize" })
public ResponseEntity<Page<T>> listByPagination(@RequestParam int pageNumber, @RequestParam int pageSize)
该接口在定义的时候,Page是一个interface。CSE不支持在接口定义上使用泛型,包括interface、abstract class等。CSE做出这个限制的原因是因为CSE需要遵循open API规范,接口定义必须能够存在对应的swagger描述。如果使用interface和abstract class,那么则无法通过swagger来描述这个接口。
import org.springframework.data.domain.Page;
@GetMapping(params = { "pageNumber", "pageSize" })
public ResponseEntity<Page<T>> listByPagination(@RequestParam int pageNumber, @RequestParam int pageSize)
该接口在定义的时候,Page是一个interface。CSE不支持在接口定义上使用泛型,包括interface、abstract class等。CSE做出这个限制的原因是因为CSE需要遵循open API规范,接口定义必须能够存在对应的swagger描述。如果使用interface和abstract class,那么则无法通过swagger来描述这个接口。
CSE的数据类型支持[说明]( https://docs.servicecomb.io/java-chassis/zh_CN/build-provider/interface-constraints.html)
l 解决方案
业务代码应该采用合理的分层设计,这样就可以保证代码能够非常灵活的在不同框架下进行迁移。如果使用Spring开发,分层设计可以遵循Spring的一些建议。业务逻辑在@Service中实现,框架接口发布在@Controller实现。当在不同框架发布服务的时候(比如Servlet、Spring MVC、CSE等),只需要简单的调整@Controller代码,不需要修改@Service代码。以上面的接口举个例子。
@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
}
}
发布为CSE的接口:
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 解决方案
这种依赖于平台提供的特定功能,在进行改造的时候,都需要结合使用的平台,看是否有替代方案。这段代码的本意是实现审计日志或者调用链等逻辑,CSE提供Handler来获取这些信息,并已经实现了调用链等功能,所以可以直接使用,或者通过自定义Handler、HttpFilter等机制实现类似的功能。
来源:华为云社区 作者:liubao68