说明
- 上篇文章 主要介绍了spring cloud的业务演示,本次主要介绍服务治理
服务治理介绍
前面我们实现了微服务之间的调用,但是我们把服务提供者的网络地址(ip,端口)等硬编码到了代码中,这种做法存在许多问题:
- 一旦服务提供者地址变化,就需要手工修改代码
- 一旦服务提供者为多个,无法实现负载均衡功能
- 一旦服务变的越来越多,人工维护调用关系困难
那么应该则么解决呢?这时候就需要通过注册中心动态的实现服务治理。
什么是服务治理?
服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的自动化注册和发现。
服务注册: 在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服务的详细信息。并在注册中心形成一张服务的清单,服务注册中心需要以心跳的方式去监测清单中的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。
服务发现: 服务调用方向服务注册中心咨询服务,并获取所有服务的服务清单,实现对具体服务实例的访问。

除了微服务,还有一个组件是服务注册中心, 他是微服务架构中非常重要的一个组件,在微服务架构里主要起到了协调者的一个作用。注册中心一般包含如下几个功能:
- 服务发现
- 服务注册: 保存服务提供者和服务调用者的信息
- 服务订阅: 服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者的信息
- 服务配置
- 配置订阅:服务提供者和服务调用者订阅微服务相关的配置
- 配置下发:主动将配置推送给服务提供者和服务调用者
- 服务健康检测
- 检测服务提供者的健康情况,如果发现异常,执行服务剔除
常见的注册中心
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
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
|

将商品微服务注册到Nacos
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_
|

将用户微服务注册到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中注册的微服务名称即可访问微服务。
测试

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}
|
负载均衡

- 在微服务调用关系中,一般会选择客户端负载均衡,也就是由服务调用一方来决定由哪个服务来提供执行
基于Feign实现服务调用
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
|
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之后,我们发现,整个微服务的开发变的开始舒服起来。