Java微服务应用开发(简版)实战之SpringCloud

本文涉及的产品
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
简介: 简单粗暴的SpringCloud实操

微服务核心模块

cloud.png

这是微服务的基本架构图,不同终端可以通过网关调用我们的核心服务,每个服务可以独立水平扩展,它们各自管辖自己的数据库。下面是SpringCloud相关常见技术栈(模块),我们将通过一个简化后的真实案例来串联起它们:

Eureka/Nacos:服务注册中心,后者由阿里巴巴开源

Ribbon:负载均衡组件

Hystrix:熔断器组件

Feign:请求客户端组件

SpringCloud GateWay:网关组件,提供路由、过滤等功能

1. 准备工作

下面我们通过一个案例来整体介绍这些组件。案例背景:B2C商城里,用户在购物时会生成订单,除了支付业务本身的订单状态处理之外,系统还会围绕这些订单分别给商家、用户端做些处理。最典型的比如,商家端要做订单统计、用户端要做订单查询、积分计算等等。为了将不同端的订单处理分层解耦,通常会划分多个服务,最简单的方案是分为商家服务和用户服务,商家服务管理商家订单、用户服务管理用户订单。

当用户下单后,前端通过调用平台聚合层来分别调用商家、用户服务。

所以,我们可以新建三个服务项目,PlatformDemo、MerchantDemo、UserDemo。按照微服务的理论,每个服务管控自己的数据库,所以可以新建两个单独的库,分别是merchantdb、userdb,然后分别新建各自的订单表merchantorder、userorder。(其实就是垂直分库)

编译相关命令

clean compile package -Dmaven.test.skip=true

PlatformDemo怎么调用MerchantDemo和UserDemo呢?两种方式:

  1. platform直接通过http调用merchant和user,优点是:简单,缺点是:假如merchant和user是多实例的,那么platform需要手动维护每个实例的地址;
  2. 将merchant、user注册到一个服务注册中心,然后platform仅通过单一的【服务名称】来路由到不同的merchnt、user服务实例。优点是:服务实例水平扩展很方便,不需要在platform维护实例地址。缺点是:要安装单独的服务注册中心。

在实际场景中,肯定会选2,原因就在于,微服务的意义就是让服务实例更方便的水平扩展,假如每次还得在调用层手动维护实例地址,会非常麻烦。另外,注册中心只需要安装一次,也不存在其他太复杂的操作。

2. Nacos基本介绍

微服务比较常见的注册中心有Eureka、ZK、Consul、Nacos等。Nacos由阿里巴巴开源,它提供了服务注册、配置管理等功能。其简单易用的风格,越来越受到大家的关注,我们的生产级项目都已采用,目前运行良好。
Nacos注册中心.png

实际上Nacos思路非常简单,它提供中心服务器(可集群扩展,消除单点)及控制台,服务提供者(比如Merchant服务)首先主动注册到中心服务,中心服务轮询其存活状态。服务消费者(比如Platform)根据固定的服务名从中心服务器调用目标服务。这种架构的优点是:服务提供者的水平扩展可以对服务消费者完全透明,后者不需要手动维护前者服务列表。

下面我们以Nacos为例,来对注册中心做个演示。

Nacos服务安装

安装过程可以看这里:https://nacos.io/zh-cn/docs/quick-start.html

我这里是按照源码方式安装,相关nacos命令在 distribution/target/nacos-server-$version/nacos/bin目录下。

启动命令:

sh startup.sh -m standalone

关停命令:

sh shutdown.sh

控制台页面: http://localhost:8848/nacos/ 默认密码:nacos/nacos

使用Nacos进行服务注册

首先引入nacos依赖:

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      <version>>0.2.1.RELEASE</version>
</dependency>

这里我们采用生产验证过的0.2.1版本

代码及配置方面的变动:

在主类上加上@EnableDiscoveryClient注解
在配置文件中新增如下内容:

server.port=0
spring.application.name=merchant-service
spring.cloud.nacos.discovery.server-addr=localhost:8848

这里将port设置为0,意味着每次启动都会使用随机端口号,这主要是因为同一类的微服务实例通常会有多个,使用同样的固定端口会造成端口占用的问题。

Nacos控制台初探

启动主类后,即可在控制台的【服务列表】中看到merchant-service 服务:

nacos01.jpg

这里我们启动了3个实例,点击详情后,我们可以看到实例的权重及运行情况:

nacos2.jpg

在这里,我们可以直接编辑实例的权重,也可以直接上下线实例,后面我们会对此进行演示。

借助Nacos进行微服务调用

如之前所说,我们需要在Platform中调用Merchant服务,完成订单入库的操作。由于Merchant已经注册在了Nacos,所以Platform必须借助Nacos来完成服务的调用。

Platform项目的配置和前面类似,这里不再赘述,我们直接看怎么轮询调用Merchant服务。为了更清楚的演示轮询过程,我们直接采用LoadBalancerClient+RestTemplate的方案手动调用服务。LoadBalancerClient用于通过服务名选取服务信息(ip地址、端口号),RestTemplate用于做Http请求。

下面首先配置RestTemplate:

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory){
        return new RestTemplate(factory);
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(3000);//单位为ms
        factory.setConnectTimeout(3000);//单位为ms
        return factory;
    }
}

然后新建测试类,核心测试代码如下:

ServiceInstance serviceInstance = loadBalancerClient.choose("merchant-service");
String url = String.format("http://%s:%s/merchant/saveOrder",serviceInstance.getHost(),serviceInstance.getPort());
System.out.println("request url:"+url);
Object value=restTemplate.postForObject(url,null,String.class);

代码解释:首先通过ServiceInstance根据权重获取服务信息,该信息包括ip+端口,然后拼接服务地址信息,最后通过RestTemplate进行Http请求。

注意:在test之前,先启动多个merchant服务实例。大家不妨测试一下,假如请求多次,是能看到均衡负载的效果的。

上面这种方式比较手工一点,实际上,我们可以直接让RestTemplate集成Ribbon,实现LoadBalance的效果,做法很简单:

  1. 在构建RestTemplate时加上@LoadBalanced注解:
@LoadBalanced
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
    return new RestTemplate(factory);
}
  1. 请求服务时,直接使用服务名而非IP+端口:
restTemplate.postForObject("http://merchant-service/merchant/saveOrder",null,String.class);

3. 微服务调用之Feign

Feign是SpringCloud中非常常用的一个HTTP客户端组件,它提供了接口式的微服务调用API。

首先确保项目中已经导入了Feign依赖:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
  <version>2.0.0.RELEASE</version>
</dependency>

然后创建目标服务的接口,比如我们这里需要调用Merchant服务,那么可以新建MerchantService接口专门来处理与之相关的服务调用:

@FeignClient(value="merchant-service")
public interface MerchantService {

    @PostMapping("/merchant/saveOrder")
    public String saveMerchantOrder();
}

这个接口非常容易理解:使用@FeignClient将接口定义为服务接口,使用SpringMVC的@PostMapping、@GetMapping注解将接口方法定义为服务映射方法。就这样,调用微服务的方式就和普通方法调用的方式没太大区别(至少感觉上是这样)。

有时候,我们需要在发起Feign请求时,可以做一些统一的处理,比如:header设置、请求监控等。此时我们可以配置Feign拦截器来实现。

Feign拦截器的实现方式非常简单,主要分为两步:

  1. 实现feign.RequestInterceptor接口,重写其apply方法,如下:
public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header("token","123");
    }
}
  1. 将其配置在@FeignClient(configuration)中:
@FeignClient(value="merchant-service",configuration = FeignRequestInterceptor.class)
public interface MerchantService {


    @PostMapping("/merchant/saveOrder")
    public String saveMerchantOrder();
}

此时我们可以先调整下Merchant服务的接口,使用@RequestHeader("token")来接收token参数。

Feign超时及重试机制

微服务之间调用最大的一个问题就是超时问题(没有之一)。比如说,当Platform调用Merchant时,由于网络不通或者Merchant服务响应缓慢,那么Platform是不能一直等待下去的,这样资源会一致被占用,前端也得不到快速响应。此时一般会设置超时时间。

大家可以测试一下,当连接不上服务端时,会报connect timeout,当服务端响应时间过长,会报read timeout。默认情况下,Feign是不会重试的,即重试逻辑为Retryer.NEVER_RETRY。我们可以根据实际情况作如下配置:

@Configuration
public class FeignConfigure {
    @Bean
    Request.Options feignOptions() {
        return new Request.Options(
                /**connectTimeoutMillis**/
                1 * 1000,
                /** readTimeoutMillis **/
                1 * 5000);
    }

    @Bean
    public Retryer feignRetryer() {
       return new Retryer.Default();
    }
}

该配置类里面,我们设置了连接超时未1秒、读取超时未5秒,然后默认重试机制会重试5次。测试方式比较简单,比如我们可以把Merchant服务从Nacos上摘除下来,或者在接口中手动设置sleep,这里不再给出。

调用方在收到超时异常时,很可能服务方会继续执行(比如执行过长导致de 超时),所以重试的前提是:【一定要保证服务方的幂等性】,即重复多次不会影响业务逻辑。

4. 微服务间的数据传输

在实际开发中,有一个很现实的问题是数据传输格式的约定问题。在微服务架构中,实现一个完整的功能需要涉及到多个服务的调用,每个服务都有与自己领域相关的数据封装,微服务之间的调用需要遵循对方的数据格式要求。以前面的订单为例,Platform在调用Merchant时,应该传入商户订单对象,然后被返回Merchant服务的响应对象。听起来很简单对吧?但是Platform和Merchant是不同项目,后者约定好的对象类在前者是不存在的,前者工程师需要手动新建匹配的类才行。在服务接口非常繁多的情况下,这种手工处理会占用工程师很多时间。为了让他们过的爽一点,我们应该让这些类/对象共享才对。

所以,笔者建议针对每个服务都新建一个DTO项目,专门用于定义数据传输对象。比如我们可以新建MerchantDTO,专门定义该服务对应的输入、输出对象,每次更新升级时,可以将其打入公司的私有仓库中。为了自动化这一过程,可以使用CI/CD工具(比如jenkins)自动拉取git代码并install/deploy到私有仓库。在需要调用Merchant服务时,在pom中加入依赖就可以了。在项目规模较小时,可以暂时只做一个DTO项目,涵盖所有服务,以后再拆也是OK的。

在DTO中,我们会约定两种类型的数据:请求参数值、响应返回值。请求参数与业务域相关。比如保存商户订单,那么请求参数就是商户订单数据,比如:

@ApiModel("商户订单实体")
@Setter
@Getter
public class MerchantOrderRequest {

    @ApiModelProperty(name = "ordername",value = "订单名称")
    private String ordername;

    @ApiModelProperty(name="price",value = "价格")
    private double price;

}

通常来说,响应返回值都会有些公共的字段,比如code、message等,一般来说会设计响应对象的基类,这样便于后面做统一的code处理:

@ApiModel(value = "默认响应实体")
@Setter
@Getter
public class DefaultResponseData {


    @ApiModelProperty(name = "code",value = "返回码,默认1000是成功、5000是失败")
    private String code;

    @ApiModelProperty(name = "message",value = "返回信息")
    private String message;
    /**
     * 额外数据
     */
    @ApiModelProperty(name = "extra",value = "额外数据")
    private String extra;
}

这里用到了swagger注解,这样在接口文档中就会有明确说明,方便调试。@Setter、@Getter主要用于生产Setter/Getter代码,有助于解放大家的双手,具体安装及依赖过程可以看这篇文章:
如何使用Lombok简化你的代码?

我们改造一下之前的saveMerchantOrder方法,让其传入MerchantOrderRequest、返回DefaultResponseData。

public DefaultResponseData saveMerchantOrder(
  @RequestBody MerchantOrderRequest merchantOrderRequest, 
  @RequestHeader("token") String token){
...
    
}

重新启动Merchant服务,打开swagger,可以看到请求和响应参数的描述:
m1.jpg

User服务可以完全按照同样的处理策略,这里不再赘述。

5. 使用Hystrix进行熔断保护(降级)

在分布式/微服务环境中往往会出现各种各样的问题,比如网络异常,超时等,而这些问题可能会导致系统的级联失败,即使不断重试,也可能无法解决的,还会耗费更多的资源。比如说我们Platform在调用Merchant时,后者的数据库突然挂了,然后系统卡顿或者不停报错,用户此时可能会不断刷新,做更多的请求。这最终会让应用程序由于资源耗尽而导致雪崩。遇到这种情况,更好的做法是在调用阶段进行熔断保护并做降级处理。

熔断保护类似于电路中的保险丝,当电流异常升高时会自动切断电流,以保护电路安全。在开发中,熔断器通常有三个状态,即Closed、Open、Half-Open,如下图:

hystrix.png

默认情况下,熔断器是关闭(Closed)的,一旦在某个时间窗口T(默认10秒)内发生异常或者超时的次数在N以上,那么熔断器就会开启(Open),然后在时间窗口S之后,熔断器会进入半开状态(Half-Open),此时假如新请求成功执行,那么会进入关闭状态(Closed),否则继续开启(Open)。为了让熔断后能快速降级,我们通常需要指定相应的fallback处理逻辑。

在SpringCloud中,我们主要使用Hystrix组件来完成熔断降级,下面看看怎么实现。

首先,我们得引入依赖:

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

在启动类上加@EnableHystrix注解,开启Hystrix。

在API层,我们只需要加@HystrixCommand注解即可。如前面所说,当接口熔断后,我们需要指定降级逻辑,即指定fallback方法:

   @GetMapping("/simpleHystrix")
    @HystrixCommand(fallbackMethod = "fallbackHandler"
    })
    public String simpleHystrix(@RequestParam("count") Integer count){
        System.out.println("执行.................");
        int i=10/count;
        return "success";
    }

    public String fallbackHandler(Integer count){
        System.out.println("count="+count);
        return "fail";
    }

这里我们定义了一个简单的接口,当count=0时,很明显会发生异常,在某段时间内出现异常的次数达到阈值,新请求就会进入fallbackHandler进行处理,不会继续调用simpleHystrix的逻辑。我们可以通过commandProperties/@HystrixProperty指定一些基本的参数,比如:

commandProperties = {
            @HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,value = "3"),
            @HystrixProperty(name =HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,value = "20000")

这里我们指定了10秒内出现3次异常,就会进入Open状态,然后再20秒之后,会进入Half-Open状态。

Feign整合Hystrix

在实际场景中,熔断器解决的大部分是微服务调用的问题,所以这里我们看看怎样让Feign整合Hystrix。

前面提到过的@FeignClient,其实直接支持配置Hystrix。它支持的方式有两种:fallback、fallbackFactory。前者比较简单,仅需要配置当前接口实现类作为降级函数,后者功能丰富一点,可以获取触发降级的原因。我们这里先用前者快速实现一下。

首先定义fallback类,该类实现服务接口及其所有方法,以MerchantService为例:

public class MerchantServiceFallBack implements MerchantService {
    
    @Override
    public DefaultResponseData saveMerchantOrder(MerchantOrderRequest merchantOrderRequest) {
        DefaultResponseData responseData=new DefaultResponseData();
        responseData.setCode("1001");
        responseData.setMessage("fallback");
        return responseData;
    }
}

然后在@FeignClient中加上:

fallback = MerchantServiceFallBack.class

最后,别忘记在配置文件中开启feign-hystrix:

feign.hystrix.enabled=true

当调用MerchantService接口服务时,一旦出现异常情况,会转入MerchantServiceFallBack的逻辑。

6. API网关之Spring Cloud Gateway

API网关主要解决的问题有:API鉴权、流量控制、请求过滤、聚合服务等。它并非微服务的必需品,具体怎么用得看实际场景。目前比较流行的网关有Zuul、Spring Cloud GateWay等。前者比较老牌了,网上资料也较多,而后者是新贵,算是SpringCloud的亲儿子,个人感觉也更好用,我们以它为例来讲解API网关的常见用法。

Spring Cloud Gateway基于Spring5、Reactor以及SpringBoot2构建,提供路由(断言、过滤器)、熔断集成、请求限流、URL重写等功能。

SpringBoot/SpringCloud系列的组件太多,经常会出现版本不对应,以下是经过测试无误的搭配:

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.4.RELEASE</version>
  </parent>
  
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>Finchley.RELEASE</version>
      <type>pom</type>
    </dependency>

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
      <version>2.0.4.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
  </dependencies>

最重要的一步是定义路由:

@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
            .route(p -> p
                    .path("/api/order/gateWay")
                    .uri("http://localhost:8889"))
                .build();
    }

代码解释:当访问本服务的/api/order/gateWay时,会将请求转发到http://localhost:8889/api/order/gateWay。然后我们也可以在转发请求前进行过滤处理,比如新增header参数、请求参数等,大家可以自行测试:

filters(f -> f.addRequestHeader("token", "123"))

在实际项目中,网关所调用的目标服务都注册在注册中心里面,所以一般来说,会让网关访问注册中心地址。假如用的是Nacos,可以将uri中的http换成lb:

lb://platform-service
相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
10天前
|
存储 缓存 Java
Java中的分布式缓存与Memcached集成实战
通过在Java项目中集成Memcached,可以显著提升系统的性能和响应速度。合理的缓存策略、分布式架构设计和异常处理机制是实现高效缓存的关键。希望本文提供的实战示例和优化建议能够帮助开发者更好地应用Memcached,实现高性能的分布式缓存解决方案。
32 9
|
13天前
|
Java 编译器 开发者
Java中的this关键字详解:深入理解与应用
本文深入解析了Java中`this`关键字的多种用法
59 9
|
13天前
|
Java 应用服务中间件 API
【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析
本文介绍了 Spring Boot 的核心概念和使用场景,并通过一个实战项目演示了如何构建一个简单的 RESTful API。
31 5
|
13天前
|
人工智能 自然语言处理 搜索推荐
【潜意识Java】了解并详细分析Java与AIGC的结合应用和使用方式
本文介绍了如何将Java与AIGC(人工智能生成内容)技术结合,实现智能文本生成。
37 5
|
13天前
|
SQL Java 数据库连接
【潜意识Java】深入理解MyBatis,从基础到高级的深度细节应用
本文详细介绍了MyBatis,一个轻量级的Java持久化框架。内容涵盖MyBatis的基本概念、配置与环境搭建、基础操作(如创建实体类、Mapper接口及映射文件)以及CRUD操作的实现。此外,还深入探讨了高级特性,包括动态SQL和缓存机制。通过代码示例,帮助开发者更好地掌握MyBatis的使用技巧,提升数据库操作效率。总结部分强调了MyBatis的优势及其在实际开发中的应用价值。
24 1
|
24天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
2月前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
71 2
|
2月前
|
Java
Java基础却常被忽略:全面讲解this的实战技巧!
本次分享来自于一道Java基础的面试试题,对this的各种妙用进行了深度讲解,并分析了一些关于this的常见面试陷阱,主要包括以下几方面内容: 1.什么是this 2.this的场景化使用案例 3.关于this的误区 4.总结与练习
|
2月前
|
Java 程序员
Java基础却常被忽略:全面讲解this的实战技巧!
小米,29岁程序员,分享Java中`this`关键字的用法。`this`代表当前对象引用,用于区分成员变量与局部变量、构造方法间调用、支持链式调用及作为参数传递。文章还探讨了`this`在静态方法和匿名内部类中的使用误区,并提供了练习题。
50 1
|
3月前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
192 6