什么是分布式系统? 架构分:单体和分布式。集群只是一种物理形态,分布式是工作方式 。
架构演进
定义
优点
缺点
单体架构
所有功能模块都在一个项目里
开发部署简单
无法应对高并发
集群架构
单体的多服务器版本
解决大并发
问题1:模块升级麻烦 问题2:多语言团队交互不通
分布式架构
一个大型应用被拆分成很多小应用分布部署在各个机器
解决了单体+集群的问题
版本关系
Spring Boot 版本
Spring Cloud 版本
Spring Cloud Alibaba 版本
主要特点与说明
3.5.x
2025.0.x
2023.0.3.3
当前较新的稳定组合 :需要JDK 17 或更高版本,采用Jakarta EE命名空间。
3.2.x
2023.0.x
2022.0.x
需要JDK 17+,采用Jakarta EE。
2.7.x
2021.0.x
2021.0.6.2
支持JDK 8的最终LTS组合 :生态成熟稳定,是企业级项目的稳妥选择。
环境搭建
使用 JDK17 根目录pom文件如下
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 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <packaging > pom</packaging > <modules > <module > services</module > </modules > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 3.3.4</version > <relativePath /> </parent > <groupId > com.study</groupId > <artifactId > cloud_demo</artifactId > <version > 1.0-SNAPSHOT</version > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <spring-cloud.version > 2023.0.3</spring-cloud.version > <spring-cloud-alibaba.version > 2023.0.3.2</spring-cloud-alibaba.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > ${spring-cloud.version}</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-alibaba-dependencies</artifactId > <version > ${spring-cloud-alibaba.version}</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement > </project >
建一个 service 的 module,在这个 module 下面建 service-order 和 service product 模块。完成基本环境构建。
注册与发现 注册与发现有很多组件,如Nacos、Eureka、Consul、Zookeeper、Dubbo等,我们以阿里巴巴的nacos为例。nacos 官网|快速开始 ,这里要下载nacos的服务端:nacos server 账号密码都是nacos 在nacos的bin目录下启动cmd窗口,输入启动命令:startup.cmd -m standalone,访问 http://localhost:8848/nacos 即可访问nacos服务。
引入依赖 在service的根pom.xml 引入 nacos 的依赖
1 2 3 4 <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency >
在 service-order, service-product都加依赖
1 2 3 4 5 6 7 8 9 10 11 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies >
服务注册 service-order 模块中的 application.properties 添加如下配置:
1 2 3 server.port =8000 spring.application.name =service-order spring.cloud.nacos.server-addr =127.0.0.1:8848
然后创建 Springboot 启动类。
service-product 也一样,端口号和名称变一下就好。
启动服务后去nacos上,查看 服务管理/服务列表 就可以发现服务注册上来了,其次可以启动多个服务副本(勾选 Allow multiple instances,勾选 Program arguments,填入–server.port=9001),会发现都会注册上 nacos
服务发现 product 的启动类加上 @EnableDiscoveryClient 去测试包(和启动类包路径一致)测试下面的服务发现 代码:
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 @SpringBootTest public class ProductApplicationTest { @Autowired DiscoveryClient discoveryClient; @Autowired NacosDiscoveryClient nacosDiscoveryClient; @Test public void discoveryClientTest () { List<String> services = discoveryClient.getServices(); for (String service : services) { System.out.println("service = " + service); List<ServiceInstance> instances = discoveryClient.getInstances(service); for (ServiceInstance instance : instances) { System.out.println("instance.getHost() = " + instance.getHost()); System.out.println("instance.getPort() = " + instance.getPort()); } } } @Test public void nacosDiscoveryClientTest () { List<String> services = nacosDiscoveryClient.getServices(); for (String service : services) { System.out.println("service = " + service); List<ServiceInstance> instances = nacosDiscoveryClient.getInstances(service); for (ServiceInstance instance : instances) { System.out.println("instance.getHost() = " + instance.getHost()); System.out.println("instance.getPort() = " + instance.getPort()); } } } }
远程调用
order 会查询 product 的信息去生成订单,这之间就涉及到了一个远程调用。 因此,新建一个 model 模块,和services模块平级,实体类的统一管理。 在 model 的pom文件中引入 lombok 的依赖
1 2 3 4 5 6 7 <dependencies > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <scope > annotationProcessor</scope > </dependency > </dependencies >
创建 bean 信息Order :com.study.bean.Order.java
1 2 3 4 5 6 7 8 9 @Data public class Order { private Long id; private BigDecimal totalAmount; private Long userId; private String nickName; private String address; private List<Product> productList; }
Product :com.study.bean.Product.java
1 2 3 4 5 6 7 @Data public class Product { private Long id; private BigDecimal price; private String productName; private int num; }
在services的pom文件中导入model,就可以用了
1 2 3 4 5 <dependency > <groupId > com.study</groupId > <artifactId > model</artifactId > <version > 1.0-SNAPSHOT</version > </dependency >
service-prodcut 在 service-product 中创建 controller
1 2 3 4 5 6 7 8 9 10 11 12 @RestController public class ProductController { @Autowired ProductService productService; @GetMapping(value = "/product/{id}") public Product getProductById (@PathVariable("id") Long productId) { System.out.println("正在远程调用service-product..." ); Product product = productService.getProductById(productId); return product; } }
创建 service 接口
1 2 3 public interface ProductService { Product getProductById (Long productId) ; }
创建实现 service 接口的实现类。
1 2 3 4 5 6 7 8 9 10 11 12 @Service public class ProductServiceImpl implements ProductService { @Override public Product getProductById (Long productId) { Product product = new Product (); product.setId(productId); product.setPrice(new BigDecimal ("99" )); product.setProductName("苹果-" + productId); product.setNum(11 ); return product; } }
重启测试:http://localhost:9000/product/2
1 2 3 4 5 6 { "id" : 2 , "price" : 99 , "productName" : "苹果-2" , "num" : 11 }
service-order 同样的,在 service-order 中创建 controller
1 2 3 4 5 6 7 8 9 10 11 @RestController public class OrderController { @Autowired OrderService orderService; @GetMapping(value = "/create") public Order createOrder (@RequestParam("userId") Long userId, @RequestParam("productId") Long productId) { Order order = orderService.createOrder(userId, productId); return order; } }
创建 service 接口
1 2 3 public interface OrderService { Order createOrder (Long userId, Long productId) ; }
创建实现 service 接口的实现类。 此处需要对service-product服务进行远程调用,稍后处理,先测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Service public class OrderServiceImpl implements OrderService { @Override public Order createOrder (Long userId, Long productId) { Order order = new Order (); order.setId(1L ); order.setTotalAmount(new BigDecimal ("0" )); order.setUserId(userId); order.setNickName("张三" ); order.setAddress("火星" ); order.setProductList(null ); return order; } }
重启测试:http://localhost:8000/create?userId=2&productId=23
1 2 3 4 5 6 7 8 { "id" : 1 , "totalAmount" : 0 , "userId" : 2 , "nickName" : "张三" , "address" : "火星" , "productList" : null }
完善远程调用 service-order 创建包 config,将RestTemplate加入到spring容器
1 2 3 4 5 6 7 @Configuration public class OrderConfig { @Bean public RestTemplate restTemplate () { return new RestTemplate (); } }
结合之前的服务发现,完善Order的远程调用代码:
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 @Slf4j @Service public class OrderServiceImpl implements OrderService { @Autowired DiscoveryClient discoveryClient; @Autowired RestTemplate restTemplate; @Override public Order createOrder (Long userId, Long productId) { Product product = getProductFromRemote(productId); Order order = new Order (); order.setId(1L ); BigDecimal price = product.getPrice(); int num = product.getNum(); order.setTotalAmount(price.multiply(new BigDecimal (num))); order.setUserId(userId); order.setNickName("张三" ); order.setAddress("火星" ); order.setProductList(Arrays.asList(product)); return order; } public Product getProductFromRemote (Long productId) { List<ServiceInstance> instances = discoveryClient.getInstances("service-product" ); ServiceInstance instance = instances.get(0 ); String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/product/" + productId; log.info("远程请求:{}" , url); Product product = restTemplate.getForObject(url, Product.class); return product; } }
负载均衡 使用loadBalancerClient 在services模块加入依赖
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency >
然后测试
前提:只能负载均衡注册到nacos的服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @SpringBootTest public class OrderApplicationTest { @Autowired LoadBalancerClient loadBalancerClient; @Test public void test () { ServiceInstance choose = loadBalancerClient.choose("service-product" ); System.out.println("choose.getHost()+choose.getPort() = " + choose.getHost() + ":" + choose.getPort()); choose = loadBalancerClient.choose("service-product" ); System.out.println("choose.getHost()+choose.getPort() = " + choose.getHost() + ":" + choose.getPort()); choose = loadBalancerClient.choose("service-product" ); System.out.println("choose.getHost()+choose.getPort() = " + choose.getHost() + ":" + choose.getPort()); choose = loadBalancerClient.choose("service-product" ); System.out.println("choose.getHost()+choose.getPort() = " + choose.getHost() + ":" + choose.getPort()); choose = loadBalancerClient.choose("service-product" ); System.out.println("choose.getHost()+choose.getPort() = " + choose.getHost() + ":" + choose.getPort()); } }
输出:
1 2 3 4 5 choose.getHost()+choose.getPort() = 192.168.1.4:9001 choose.getHost()+choose.getPort() = 192.168.1.4:9000 choose.getHost()+choose.getPort() = 192.168.1.4:9002 choose.getHost()+choose.getPort() = 192.168.1.4:9001 choose.getHost()+choose.getPort() = 192.168.1.4:9000
有看出,采用的默认是轮询
将远程调用商品的方法getProductFromRemote() 替换为下面这个 getProductFromRemoteWithLoadBalance()
1 2 3 4 5 6 7 8 9 10 11 12 13 @Autowired LoadBalancerClient loadBalancerClient; public Product getProductFromRemoteWithLoadBalance (Long productId) { ServiceInstance choose = loadBalancerClient.choose("service-product" ); String url = "http://" + choose.getHost() + ":" + choose.getPort() + "/product/" + productId; log.info("远程请求:{}" , url); Product product = restTemplate.getForObject(url, Product.class); return product; }
重启请求6次:http://localhost:8000/create?userId=2&productId=23
输出
1 2 3 4 5 6 2025-11-04T13:28:06.454+08:00 INFO 7716 --- [service-order] [nio-8000-exec-1] c.study.service.Impl.OrderServiceImpl : 远程请求:http://192.168.1.4:9000/product/23 2025-11-04T13:28:08.260+08:00 INFO 7716 --- [service-order] [nio-8000-exec-2] c.study.service.Impl.OrderServiceImpl : 远程请求:http://192.168.1.4:9002/product/23 2025-11-04T13:28:09.371+08:00 INFO 7716 --- [service-order] [nio-8000-exec-3] c.study.service.Impl.OrderServiceImpl : 远程请求:http://192.168.1.4:9001/product/23 2025-11-04T13:28:10.411+08:00 INFO 7716 --- [service-order] [nio-8000-exec-4] c.study.service.Impl.OrderServiceImpl : 远程请求:http://192.168.1.4:9000/product/23 2025-11-04T13:28:11.473+08:00 INFO 7716 --- [service-order] [nio-8000-exec-7] c.study.service.Impl.OrderServiceImpl : 远程请求:http://192.168.1.4:9002/product/23 2025-11-04T13:28:12.331+08:00 INFO 7716 --- [service-order] [nio-8000-exec-6] c.study.service.Impl.OrderServiceImpl : 远程请求:http://192.168.1.4:9001/product/23
使用@LoadBalanced注解 在之前的 OrderConfig 下的 restTemplate() 方法上面加上 @LoadBalanced
将远程调用商品的方法getProductFromRemoteWithLoadBalance() 替换为下面这个 getProductFromRemoteWithLoadBalanceAnnotation()
1 2 3 4 5 6 7 8 public Product getProductFromRemoteWithLoadBalanceAnnotation (Long productId) { String url = "http://service-product/product/" + productId; Product product = restTemplate.getForObject(url, Product.class); return product; }
思考:注册中心宕机,远程调用还能成功吗?
远程中心没有启动前宕机了,不能调用成功。
远程中心启动后宕机了,可以调用成功,因为存在缓存。
配置中心 也是使用的nacos,但是要在 services 引入配置的依赖
1 2 3 4 5 <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-config</artifactId > </dependency >
导入了配置中心的依赖但是没设置(用到)配置中心就会报错(service-product)
1 2 3 Add a spring.config.import=nacos: property to your configuration. If configuration is not required add spring.config.import=optional:nacos: instead. To disable this check, set spring.cloud.nacos.config.import-check.enabled=false.
解决办法,关闭自动检查
1 2 spring.cloud.nacos.config.import-check.enabled =false
创建配置 在nacos中的 配置管理/配置列表 中新建配置:service-order.properties,选择格式 PROPERTIES
1 2 order.timeout =10min order.auto-confirm =20d
点击发布。
在 service-order 中指定配置:
1 spring.config.import=nacos:service-order.properties
然后再 order的controller中,可以获取到配置:
1 2 3 4 5 6 7 8 9 @Value("${order.timeout}") String orderTimeout; @Value("${order.auto-confirm}") String orderAutoConfirm; @GetMapping("/config") public String getConfig () { return "OrderTimeout+OrderAutoConfirm = " + orderTimeout+" : " + orderAutoConfirm; }
重启程序,访问:http://localhost:8000/config
自动刷新 像刚刚的设置,会产生一个问题:nacos配置中修改后,不重启服务,发请求不能自动更新修改后的数据。 这里有三个自动刷新的办法去解决:
@RefreshScope 实时更新配置显示的办法:在@RestController上加@RefreshScope即可
参考