Nacos

1. 服务治理介绍

前面我们实现了微服务之间的调用,但是我们把服务提供者的网络地址(ip,端口)等硬编码到了代码中,这种做法存在许多问题:

  1. 一旦服务提供者地址变化,就需要手工修改代码
  2. 一旦服务提供者为多个,无法实现负载均衡功能
  3. 一旦服务变的越来越多,人工维护调用关系困难

那么应该则么解决呢?这时候就需要通过注册中心动态的实现服务治理。

什么是服务治理?

服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的自动化注册和发现。

  1. 服务注册: 在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服务的详细信息。并在注册中心形成一张服务的清单,服务注册中心需要以心跳的方式去监测清单中的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。
  2. 服务发现: 服务调用方向服务注册中心咨询服务,并获取所有服务的服务清单,实现对具体服务实例的访问。

image-20210618154855845

除了微服务,还有一个组件是服务注册中心, 他是微服务架构中非常重要的一个组件,在微服务架构里主要起到了协调者的一个作用。注册中心一般包含如下几个功能:

  1. 服务发现
    1. 服务注册: 保存服务提供者和服务调用者的信息
    2. 服务订阅: 服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者的信息
  2. 服务配置
    1. 配置订阅:服务提供者和服务调用者订阅微服务相关的配置
    2. 配置下发:主动将配置推送给服务提供者和服务调用者
  3. 服务健康检测
    1. 检测服务提供者的健康情况,如果发现异常,执行服务剔除

1.1 常见的注册中心

Zookeeper

zookeeper是一个分布式服务框架,是Apache Hadoop的一个子项目,它主要用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务,状态同步服务,集群管理,分布式应用配置项的管理等

Eureka

Eureka是Spring Cloud Netflix中的重要组件,主要作用就是做服务注册和发现。

Consul

Consul是基于Go语言开发的开源工具,主要面向分布式,服务化的系统提供服务注册,服务发现和配置管理等功能。Consul的功能都很实用,其中包括:服务注册/发现,健康检查,Key/Value存储,多数据中心和分布式一致性保证等特性。Consul本身只是一个二进制的可执行文件,所以安装和部署都非常简单,只需要从官网下载后,执行对应的脚本即可。

https://www.consul.io/

Nacos

Nacos 致力于帮助您发现、配置和管理微服务。它是Spring Cloud Alibaba的组件之一。

2. Nacos简介

https://nacos.io/zh-cn/

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。

2.1 什么是 Nacos?

服务(Service)是 Nacos 世界的一等公民。Nacos 支持几乎所有主流类型的“服务”的发现、配置和管理:

Kubernetes Serviceopen in new window

gRPCopen in new window & Dubbo RPC Serviceopen in new window

Spring Cloud RESTful Serviceopen in new window

Nacos 的关键特性包括:

  • 服务发现和服务健康监测

    Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用 原生SDKopen in new windowOpenAPIopen in new window、或一个独立的Agent TODOopen in new window注册 Service 后,服务消费者可以使用DNS TODOopen in new windowHTTP&APIopen in new window查找和发现服务。

    Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。 对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测2种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助您根据健康状态管理服务的可用性及流量。

  • 动态配置服务

    动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。

    动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。

    配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。

    Nacos 提供了一个简洁易用的UI (控制台样例 Demoopen in new window) 帮助您管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助您更安全地在生产环境中管理配置变更和降低配置变更带来的风险。

  • 动态 DNS 服务

    动态 DNS 服务支持权重路由,让您更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务。动态DNS服务还能让您更容易地实现以 DNS 协议为基础的服务发现,以帮助您消除耦合到厂商私有服务发现 API 上的风险。

    Nacos 提供了一些简单的 DNS APIs TODOopen in new window 帮助您管理服务的关联域名和可用的 IP:PORT 列表.

  • 服务及其元数据管理

    Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。

3 Nacos实战入门

3.1 下载安装Nacos

Nacos 依赖 Javaopen in new window 环境来运行。如果您是从代码开始构建并运行Nacos,还需要为此配置 Mavenopen in new window环境,请确保是在以下版本环境中安装使用:

  1. 64 bit OS,支持 Linux/Unix/Mac/Windows,推荐选用 Linux/Unix/Mac。
  2. 64 bit JDK 1.8+;下载open in new window & 配置open in new window
  3. Maven 3.2.x+;下载open in new window & 配置open in new window

您可以从 最新稳定版本open in new window 下载 nacos-server-$version.zip 包。

我们下载:https://github.com/alibaba/nacos/releases/download/2.0.2/nacos-server-2.0.2.zip

## 解压nacos-server-2.0.2.zip
## 进入解压目录,进入bin目录
cd nacos/bin
##命令启动
startup.cmd -m standalone
1
2
3
4
5

3.1.1 访问nacos

打开浏览器,输入http://localhost:8848/nacos 即可访问服务,默认密码 nacos/nacos

image-20210618163317909

3.2 将商品微服务注册到Nacos

3.2.1 添加依赖

 <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  </dependency>
1
2
3
4

3.2.2 添加@EnableDiscoveryClient

主类上添加

package com.mszlu.shop.goods;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class GoodsApp {

    public static void main(String[] args) {
        SpringApplication.run(GoodsApp.class);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

3.2.3 配置中添加nacos的地址

application.yml

spring:
  application:
    name: shop-goods
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
server:
  port: 8002

mybatis-plus:
  global-config:
    db-config:
      table-prefix: shop_
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

3.2.4 启动服务

image-20210618164536424

3.3 将用户微服务注册到Nacos

步骤同上:

  <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
1
2
3
4
package com.mszlu.shop.user;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class UserApp {

    public static void main(String[] args) {
        SpringApplication.run(UserApp.class,args);
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
  application:
    name: shop-user
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
server:
  port: 8001

mybatis-plus:
  global-config:
    db-config:
      table-prefix: shop_
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

image-20210618165906345

3.4 将订单微服务注册到Nacos

 <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
1
2
3
4
package com.mszlu.shop.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class OrderApp {

    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
  application:
    name: shop-order
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
server:
  port: 8003

mybatis-plus:
  global-config:
    db-config:
      table-prefix: shop_
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

image-20210618174529403

3.5 在APP应用服务中添加Nacos

3.5.1 添加依赖

    <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
1
2
3
4

3.5.2 配置

package com.mszlu.shop.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
  application:
    name: shop-app
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
server:
  port: 8004
1
2
3
4
5
6
7
8
9

3.5.3 使用nacos调用微服务

package com.mszlu.shop.app.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestConfig {

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

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.mszlu.shop.app.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.mszlu.common.Result;
import com.mszlu.common.goods.GoodsBO;
import com.mszlu.common.order.params.OrderParams;
import com.mszlu.common.user.UserBO;
import com.mszlu.shop.app.model.params.BuyParams;
import com.mszlu.shop.app.service.BuyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.util.Map;

@Service
public class BuyServiceImpl implements BuyService {
    @Autowired
    private RestTemplate restTemplate;

    private String shopUserName = "shop-user";
    private String shopGoodsName = "shop-goods";
    private String shopOrderName = "shop-order";

    @Override
    public Result submitOrder(BuyParams buyParams) {
        String userResult = restTemplate.getForObject("http://"+shopUserName+"/user/findUser/" + buyParams.getUserId(), String.class);
        Result<UserBO> userBOResult = JSON.parseObject(userResult,new TypeReference<Result<UserBO>>(){});
        if (userBOResult == null || !userBOResult.isSuccess() || userBOResult.getData() == null){
            return Result.fail(10001,"用户不存在");
        }
        UserBO userBO = userBOResult.getData();

        String goodsResult = restTemplate.getForObject("http://"+shopGoodsName+"/goods/findGoods/" + buyParams.getGoodsId(), String.class);
        Result<GoodsBO> goodsBOResult = JSON.parseObject(goodsResult,new TypeReference<Result<GoodsBO>>(){});

        if (goodsBOResult == null || !goodsBOResult.isSuccess() || goodsBOResult.getData() == null){
            return Result.fail(10002,"商品不存在");
        }
        GoodsBO goodsBO = (GoodsBO) goodsBOResult.getData();
        Integer goodsStock = goodsBO.getGoodsStock();
        if (goodsStock < 0){
            return Result.fail(10003,"商品库存不足");
        }
        BigDecimal goodsPrice = goodsBO.getGoodsPrice();
        BigDecimal account = userBO.getAccount();
        if (account.compareTo(goodsPrice) < 0){
            return Result.fail(10004,"余额不足");
        }
        OrderParams orderParams = new OrderParams();
        orderParams.setUserId(userBO.getId());
        orderParams.setGoodsId(goodsBO.getId());
        orderParams.setGoodsPrice(goodsBO.getGoodsPrice());
        String orderResult = restTemplate.postForObject("http://"+shopOrderName+"/order/createOrder", orderParams, String.class);
        Result<String> orderResultString = JSON.parseObject(orderResult,new TypeReference<Result<String>>(){});
        if (orderResultString == null || !orderResultString.isSuccess()){
            return Result.fail(10005,"下单失败");
        }
        String orderId =  orderResultString.getData();

        return Result.success(orderId);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

通过以上的代码,我们发现各个微服务的地址,不需要硬编码在代码中了,通过在nacos中注册的微服务名称即可访问微服务。

在RestTemplate上 我们加了一个@LoadBalanced注解,熟悉英文的小伙伴们,一定猜出来了,这是做负载均衡的,也就是说 如果微服务部署集群的话,服务调用方通过nacos可以很轻松的访问服务提供者,而不需要考虑到底应该访问哪台机器,nacos帮我们做了这件事情。

3.6 负载均衡

3.6.1 什么是负载均衡?

通俗的讲,负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。

根据负载均衡发生位置的不同,一般分为服务端负载均衡客户端负载均衡

服务端负载均衡指的是发生在服务提供者一方,比如常见的nginx负载均衡。

而客户端负载均衡指的是发生在服务请求一方,也就是在发送请求之前已经选好了由哪个实例处理请求。

image-20210619162742384

在微服务调用关系中,一般会选择客户端负载均衡,也就是由服务调用一方来决定由哪个服务来提供执行。

3.6.2 基于Ribbon实现负载均衡

Ribbon是Spring Cloud的一个组件,它可以让我们使用一个注解就能轻松的搞定负载均衡。

上面例子中,我们使用的@LoadBalanced 就是使用了Ribbon的负载均衡。

我们再起一个商品的微服务,验证一下负载均衡:

  1. 将原有的商品微服务Controller 打印一个语句,便于验证
package com.mszlu.shop.goods.controller;

import com.mszlu.common.Result;
import com.mszlu.shop.goods.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("goods")
public class GoodsController {

    @Autowired
    private GoodsService goodsService;

    @GetMapping("findGoods/{id}")
    public Result findGoods(@PathVariable("id") Long id){
        System.out.println("调用了111111111的商品微服务");
        return goodsService.findGoodsById(id);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  1. 更改打印语句,并更改端口号,在启动一个商品微服务:

    package com.mszlu.shop.goods.controller;
    
    import com.mszlu.common.Result;
    import com.mszlu.shop.goods.service.GoodsService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("goods")
    public class GoodsController {
    
        @Autowired
        private GoodsService goodsService;
    
        @GetMapping("findGoods/{id}")
        public Result findGoods(@PathVariable("id") Long id){
            System.out.println("调用了22222222222的商品微服务");
            return goodsService.findGoodsById(id);
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    server:
      port: 8012
    
    1
    2
  2. 启动,并查看nacos

    image-20210619164156745

    image-20210619164240261

  3. 启动App并进行多次调用

    image-20210619164418359

    image-20210619164431684

我们发现,执行http://localhost:8004/buy/submit四次,两个商品微服务分别调用了两次,四次的访问被两个商品微服务进行了分摊。

3.6.3 Ribbon支持的负载均衡策略

Ribbon内置了多种负载均衡策略,内部负载均衡的顶级接口为com.netflix.loadbalancer.IRule,具体的负载策略如下所示:

策略名策略描述实现说明
BestAvailableRule选择一个最小的并发请求的server逐个考察Server,如果Server被Tripped了,则忽略,再选择其中ActiveRequestCount最小的Server
AvailabilityFilteringRule过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的后端server(activeconnections超过配置的阀值)使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就是检查status里记录的各个server的运行状态
WeightedResponseTimeRule根据相应的时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低一个后台线程定期的从status中读取响应时间,为每个server计算一个weight。weight的计算也比较简单,responsetime减去每个server自己的平均的响应时间,就是server的权重。当开始运行,没有形成status时,使用roundbin策略选择server
RetryRule对选定的负载均衡策略机添加重试机制在配置时间内选择的server不成功,则一直尝试使用subRule的方式选择一个可用的server
RoundRobinRule轮询方式选择server轮询index,选择index位置对应的server
RandomRule随机选择一个server在index上随机,选择index位置对应的server
ZoneAvoidanceRule复合判断server所在的区域的性能和server的可用性选择server使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone内的运行性能是否可用,剔除不可用的zone(区域内的所有server),AvailabilityPredicate用于过滤掉连接次数过多的server

我们可以通过配置来调整Ribbon的负载均衡策略:

加在app端

shop-goods:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
1
2
3

3.7 基于Feign实现服务调用

3.7.1 什么是Feign?

Feign是Spring Cloud提供的一个声明式的伪Http客户端,它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。

Nacos很好的集成了Feign,Feign默认集成了Ribbon,所以在Nacos下使用Feign默认就实现了负载均衡的效果。

3.7.2 Feign的使用

  1. 加入Feign的依赖

    <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    
    1
    2
    3
    4
  2. 在主类上添加Feign的注解

    package com.mszlu.shop.app;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    public class App {
    
        public static void main(String[] args) {
            SpringApplication.run(App.class,args);
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
  3. 创建各个微服务的feign接口,实现feign的微服务调用

    package com.mszlu.shop.app.feign;
    
    import com.mszlu.common.Result;
    import com.mszlu.common.user.UserBO;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @FeignClient("shop-user")
    public interface UserFeign {
    
        //调用路径同http访问路径
        @GetMapping("/user/findUser/{id}")
        public Result<UserBO> findUser(@PathVariable("id") Long id);
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.mszlu.shop.app.feign;
    
    import com.mszlu.common.Result;
    import com.mszlu.common.goods.GoodsBO;
    import com.mszlu.common.user.UserBO;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @FeignClient("shop-goods")
    public interface GoodsFeign {
    
        //调用路径同http访问路径
        @GetMapping("/goods/findGoods/{id}")
        public Result<GoodsBO> findGoods(@PathVariable("id") Long id);
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package com.mszlu.shop.app.feign;
    
    import com.mszlu.common.Result;
    import com.mszlu.common.goods.GoodsBO;
    import com.mszlu.common.order.params.OrderParams;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    
    @FeignClient("shop-order")
    public interface OrderFeign {
    
        //调用路径同http访问路径
        @PostMapping("/order/createOrder")
        public Result<String> createOrder(@RequestBody OrderParams orderParams);
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
  4. 更改BuyService的实现,使用feign来完成调用

    @Autowired
        private UserFeign userFeign;
        @Autowired
        private GoodsFeign goodsFeign;
        @Autowired
        private OrderFeign orderFeign;
    
        @Override
        public Result submitOrder(BuyParams buyParams) {
            Result<UserBO> userBOResult = userFeign.findUser(buyParams.getUserId());
            if (userBOResult == null || !userBOResult.isSuccess() || userBOResult.getData() == null){
                return Result.fail(10001,"用户不存在");
            }
            UserBO userBO = userBOResult.getData();
            log.info("userBo:{}",userBO);
    
            Result<GoodsBO> goodsBOResult = goodsFeign.findGoods(buyParams.getGoodsId());
            log.info("goodsBOResult:{}",goodsBOResult);
            if (goodsBOResult == null || !goodsBOResult.isSuccess() || goodsBOResult.getData() == null){
                return Result.fail(10002,"商品不存在");
            }
            GoodsBO goodsBO = goodsBOResult.getData();
            Integer goodsStock = goodsBO.getGoodsStock();
            if (goodsStock < 0){
                return Result.fail(10003,"商品库存不足");
            }
            BigDecimal goodsPrice = goodsBO.getGoodsPrice();
            BigDecimal account = userBO.getAccount();
            if (account.compareTo(goodsPrice) < 0){
                return Result.fail(10004,"余额不足");
            }
            OrderParams orderParams = new OrderParams();
            orderParams.setUserId(userBO.getId());
            orderParams.setGoodsId(goodsBO.getId());
            orderParams.setGoodsPrice(goodsBO.getGoodsPrice());
            Result<String> orderResultString = orderFeign.createOrder(orderParams);
            log.info("orderResultString:{}",orderResultString);
            if (orderResultString == null || !orderResultString.isSuccess()){
                return Result.fail(10005,"下单失败");
            }
            String orderId =  orderResultString.getData();
    
            return Result.success(orderId);
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
  5. 重启APP服务,测试

整合上feign之后,我们发现,整个微服务的开发变的开始舒服起来。