0%

springcloud服务容错

说明

  • 上篇文章 主要介绍了spring cloud的业务演示,本次主要介绍服务治理

服务治理介绍

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

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

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

什么是服务治理?

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

服务注册: 在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服务的详细信息。并在注册中心形成一张服务的清单,服务注册中心需要以心跳的方式去监测清单中的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。

服务发现: 服务调用方向服务注册中心咨询服务,并获取所有服务的服务清单,实现对具体服务实例的访问。

image-20210618154855845

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

  1. 服务发现
    1. 服务注册: 保存服务提供者和服务调用者的信息
    2. 服务订阅: 服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者的信息
  2. 服务配置
    1. 配置订阅:服务提供者和服务调用者订阅微服务相关的配置
    2. 配置下发:主动将配置推送给服务提供者和服务调用者
  3. 服务健康检测
    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的组件之一

nacos

  • 打开官网下载2.3.1.zip的版本
1
2
3
4
## 进入解压目录,进入bin目录
cd nacos/bin
##命令启动
startup.cmd -m standalone
  • 结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
D:\exe\nacos\bin>startup.cmd -m standalone
"nacos is starting with standalone"

,--.
,--.'|
,--,: : | Nacos 2.3.1
,`--.'`| ' : ,---. Running in stand alone mode, All function modules
| : : | | ' ,'\ .--.--. Port: 8848
: | \ | : ,--.--. ,---. / / | / / ' Pid: 14064
| : ' '; | / \ / \. ; ,. :| : /`./ Console: http://192.168.163.198:8848/nacos/index.html
' ' ;. ;.--. .-. | / / '' | |: :| : ;_
| | | \ | \__\/: . .. ' / ' | .; : \ \ `. https://nacos.io
' : | ; .' ," .--.; |' ; :__| : | `----. \
| | '`--' / / ,. |' | '.'|\ \ / / /`--' /
' : | ; : .' \ : : `----' '--'. /
; |.' | , .-./\ \ / `--'---'
'---' `--`---' `----'

2024-03-27 19:39:12,401 INFO Tomcat initialized with port(s): 8848 (http)

2024-03-27 19:39:12,850 INFO Root WebApplicationContext: initialization completed in 3709 ms

image-20240327194023531

将商品微服务注册到Nacos

  • shop-goods模块中依赖文件加入
1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  • 主类上添加@EnableDiscoveryClient
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package xyz.shi.shop.goods;

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

@EnableDiscoveryClient
@SpringBootApplication
public class GoodsApp {

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

  • application.yml配置中添加nacos的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
application:
name: shop-goods
datasource:
password: root
username: 123456
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_
  • 启动商品模块的微服务后,查看到数据

image-20240327194825898

将用户微服务注册到Nacos

  • 步骤同上

将订单微服务注册到Nacos

  • 步骤同上

在APP应用服务中添加Nacos

  • 步骤同上

dao修改

使用nacos调用微服务

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
package xyz.shi.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 RestTemplateConfig {

@Bean
@LoadBalanced
public RestTemplate restTemplate(ClientHttpRequestFactory requestFactory){
// return new RestTemplate(requestFactory);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}

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

​ @LoadBalanced 表示加上负载均衡

service修改

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
67
package xyz.shi.shop.app.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import xyz.shi.shop.app.popj.BuyParams;
import xyz.shi.shop.app.service.BuyService;
import xyz.shi.shop.common.Result;
import xyz.shi.shop.common.goods.GoodsBO;
import xyz.shi.shop.common.user.UserBO;
import xyz.shi.shop.order.pojo.Order;

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, "余额不足");
}
Order orderParams = new Order();
orderParams.setUserId(userBO.getId());
orderParams.setGoodsId(goodsBO.getId());
orderParams.setOrderPrice(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);
}
}

老的代码:

String userResult = restTemplate.getForObject(“http://localhost:8001/user/findUser/" + buyParams.getUserId(), String.class);

新的代码

String goodsResult = restTemplate.getForObject(“http://“ + shopGoodsName + “/goods/findGoods/“ ..);

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

测试

  • 所有的微服务模块全部启动后,查看nacos

image-20240327201224830

  • 测试结果再次如下
1
2
3
4
5
6
7
8
9
10
import requests

header = {"content-type": "application/json",}
data = {"userId": 1, "goodsId": 1111}
t = requests.post("http://localhost:8004/buy/submit", json=data, headers=header)
s = t.text
print(s)


{"success":true,"code":10001,"msg":"用户不存在","data":null}

负载均衡

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

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

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

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

image-20210619162742384

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

基于Feign实现服务调用

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

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

  • shop-app模块,加入Feign的依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 在主类上添加Feign的注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package xyz.shi.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;

@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients

public class App {

public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
  • 创建各个微服务的feign接口,实现feign的微服务调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package xyz.shi.shop.app.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import xyz.shi.shop.common.Result;
import xyz.shi.shop.common.goods.GoodsBO;

@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
package xyz.shi.shop.app.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import xyz.shi.shop.common.Result;
import xyz.shi.shop.order.pojo.Order;

@FeignClient("shop-order")
public interface OrderFeign {

//调用路径同http访问路径
@PostMapping("/order/createOrder")
public Result<String> createOrder(@RequestBody Order orderParams);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package xyz.shi.shop.app.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import xyz.shi.shop.common.Result;
import xyz.shi.shop.common.user.UserBO;

@FeignClient("shop-user")
public interface UserFeign {

//调用路径同http访问路径
@GetMapping("/user/findUser/{id}")
public Result<UserBO> findUser(@PathVariable("id") Long id);
}
  • 更改BuyService的实现,使用feign来完成调用
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
package xyz.shi.shop.app.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import xyz.shi.shop.app.feign.GoodsFeign;
import xyz.shi.shop.app.feign.OrderFeign;
import xyz.shi.shop.app.feign.UserFeign;
import xyz.shi.shop.app.popj.BuyParams;
import xyz.shi.shop.app.service.BuyService;
import xyz.shi.shop.common.Result;
import xyz.shi.shop.common.goods.GoodsBO;
import xyz.shi.shop.common.user.UserBO;
import xyz.shi.shop.order.pojo.Order;

import java.math.BigDecimal;

@Service
public class BuyServiceImpl implements BuyService {
@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();
System.out.println(userBO);
Result<GoodsBO> goodsBOResult = goodsFeign.findGoods(buyParams.getGoodsId());
System.out.println(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,"余额不足");
}
Order orderParams = new Order();
orderParams.setUserId(userBO.getId());
orderParams.setGoodsId(goodsBO.getId());
orderParams.setOrderPrice(goodsBO.getGoodsPrice());
Result<String> orderResultString = orderFeign.createOrder(orderParams);
System.out.println(orderResultString);

if (orderResultString == null || !orderResultString.isSuccess()){
return Result.fail(10005,"下单失败");
}
String orderId = orderResultString.getData();

return Result.success(orderId);
}
}

老代码

String goodsResult = restTemplate.getForObject(“http://“ + shopGoodsName + “/goods/findGoods/“ ..);

新代码

Result userBOResult = userFeign.findUser(buyParams.getUserId());

  • 重启APP服务,测试,整合上feign之后,我们发现,整个微服务的开发变的开始舒服起来。