单体架构(ALL IN ONE)
所有功能都在一个项目
项目打包 jar
开发部署简单
无法应对高并发
域名 IP 节点 应用 数据库
购买域名绑定IP访问简单
服务器:节点 一个服务器是一个节点
集群架构
物理形态
解决大并发问题
副本 集群 网关(部署到某个服务器,请求流量的入口 )路由 负载均衡扩缩容...
分布式架构
一个大型应用被拆分成很多小应用,分布部署在各个机器,拆分成小应用,拆分数据库;
是一种工作方式;
问题一:模块化升级:
订单功能需要经常升级
问题二:多语言团队
微服务(自治) 独立部署、数据隔离、语言无关
单点故障(鸡蛋不放在一个篮子里)—> 远程调用(Remote Procedure Call ,RPC:HTTP+JSON),调用之前需要找到对方在哪
(如何找到其他服务器的应用功能:)服务发现、服务注册 —> 由一个组件 注册中心 提供,服务·IP对应的清单列表
注册中心还有一个功能:配置中心 —> 统一管理所有配置,需要改配置,只需要在配置中心修改,改后数据主动推送给指定的微服务(推送配置变更)
服务雪崩——> 服务熔断(快速失败机制,及时释放到资源) 解决
网关 —》 问注册中心 —》 请求路由
分布式事务
各种方法组件方法
微服务:Spring Boot ——> 注册中心/配置中心:Spring Cloud Alibaba Nacos ——>
网关:Spring Cloud Gateway ——>远程调用:Spring Cloud OpenFeign ——>
服务熔断:Spring Cloud Alibaba Sentinel ——>分布式事务:Spring Cloud Alibaba Seata
微服务单体架构区别:
-
单体架构所有的模块全都耦合在一块,代码量大,维护困难。
微服务每个模块就相当于一个单独的项目,代码量明显减少,遇到问题也相对来说比较好解决。
-
单体架构所有的模块都共用一个数据库,存储方式比较单一。
微服务每个模块都可以使用不同的存储方式(比如有的用redis,有的用mysql等),数据库也是单个模块对应自己的数据库。(单体架构也可以实现,但是比较麻烦)
-
单体架构所有的模块开发所使用的技术一样。
微服务每个模块都可以使用不同的开发技术,开发模式更灵活。
创建分布式项目
父项目
项目创建:
next后不做选择 直接创建
项目结构:
删掉多余的,只留这些内容
依赖:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<packaging>pom</packaging>
<groupId>com.cg</groupId>
<artifactId>cloud-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud-demo</name>
<description>cloud-demo</description>
<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>
<!-- pom清单文件 导入-->
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
父项目—services
项目创建:
右击项目->New->Module
添加依赖
<packaging>pom</packaging>
删掉src包
子项目—service-order
创建项目:
右击创建的services->New->Module
同样的方法 创建一个子项目 service-order
查看结构:
点击三个点,点:Group Modules
在父项目services导入公共依赖
pom.xml:
<dependencies>
<!--Nacos 的 注册中心的服务发现功能-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--导入 远程调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
Nacos — 注册中心、配置中心
服务注册:
启动Nacos注册中心—>微服务 就会连上注册中心, 注册进去(比如service-order启动以后就会连上Nacos),形成一个清单列表
服务发现:
订单服务想要连接商品服务,就会访问注册中心商品服务哪些机子里有,然后随机连接一个
Nacos下载使用
安装包:https://github.com/alibaba/nacos/releases?page=3
解压到非中文目录下
启动:
进入bin目录,
执行命令:
windows命令(单机模式命令)
startup.cmd -m standalone
问题:
D:\Program Files (x86)\nacos\bin>startup.cmd -m standalone "nacos is starting with standalone" 此时不应有 \nacos"\logs\java_heapdump.hprof -XX:-UseLargePages"。
原因一:Program Files (x86)路径问题
D:\Program Files\nacos\bin>startup.cmd -m standalone "nacos is starting with standalone" 14:22:08.471 [main] ERROR org.springframework.boot.SpringApplication - Application run failed java.lang.RuntimeException: java.io.IOException: Unable to create directory D:\Program Files\nacos\logs
原因一:权限问题,换个地方
springboot与springcloud版本对应
-
Spring Cloud 基于 Spring Boot
-
Spring Cloud Alibaba 是 Spring Cloud 的子项目
功能:
配置管理——配置中心
服务管理——注册中心
流程 | 内容 | 核心 |
---|---|---|
1 | 启动微服务 | Spring Boot微服务web项目启动 |
2 | 引入服务发现依赖 | spring-cloud-starter-alibaba-nacos-discovery |
3 | 配置Nocos地址 | spring.cloud.nacos.server-addr=127.0.0.1:8848 |
4 | 查看注册中心效果 | 访问http://localhost:8848/nacos |
5 | 集群模式启动测试 | 单机情况下通过改变端口模拟微服务集群 |
一、启动spring-order微服务注册到Nacos
1、导入web依赖
(1)下载Edit Starters插件
在pom.xml文件中,<dependencies></dependencies>中,按Alt+Insert。
(2)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
其他依赖在services父项目的公共依赖里,可以共用,比如:spring-cloud-starter-alibaba-nacos-discovery—>注册中心的服务发现功能
2、写一个项目启动类在Java包下:
com/cg/order/OrderMainApplication.java
package com.cg.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//代表它是一个springboot应用
@SpringBootApplication
public class OrderMainApplication {
public static void main(String[] args) {
SpringApplication.run(OrderMainApplication.class,args);
}
}
3、编写配置文件application.properties
src/main/resources/application.properties
**spring应用的名字 --项目名
spring.application.name=service-order
**这个应用启动占用的端口号
server.port=8000
**注册到注册中心,要告诉这个应用Nacos注册中心在哪里,不写默认:127.0.0.1:8848
spring.cloud.nacos.server-addr=127.0.0.1:8848
4、启动Nocas后,启动service-order项目
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. 2025-04-17T15:22:23.399+08:00 ERROR 25900 --- [service-order] [ main] o.s.b.d.LoggingFailureAnalysisReporter : ********* APPLICATION FAILED TO START ********* Description: Your project setup is incompatible with our requirements due to following reasons: - Spring Boot [3.4.4] is not compatible with this Spring Cloud release train
这个错误是因为 Spring Boot 和 Spring Cloud 版本不兼容。你的项目使用的是 Spring Boot 3.4.4,但当前的 Spring Cloud 发行版(Release Train)不支持该版本
其它微服务同理
注意刷新
5、使用日志—Services
点击加号——》选择Run Configuration Type——》选择Maven——》重新启动IDEA,重进项目——》右下角提示里,选择: Manage multiple Spring Boot run configurations in the Services tool window Use Services Don't Show Again——》最后得到
启动后得到:
二、注册中心的服务发现
想通过代码获得注册中心中,注册的所有的微服务,以及所在的机器IP地址和端口列表。
流程 | 内容 | 核心 |
---|---|---|
1 | 开启服务发现功能 | @EnableDiscoveryClient |
2(组件1) | 测试服务发现Api | DiscoveryClient |
2(组件2) | 测试服务发现Api | NacosServiceDiscovery |
1、导入测试依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!-- 规定测试范围-->
<scope>test</scope>
</dependency>
2、启动类加上一个注解—@EnableDiscoveryClient
package com.cg.produce;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient//开启发现服务注册的功能
@SpringBootApplication
public class ProduceMainApplication {
public static void main(String[] args) { SpringApplication.run(ProduceMainApplication.class,args);
}
}
3、创建测试类
测试类的包名要和主程序的一致
com/cg/produce/DiscoveryTest.java
查询所有微服务的名字:
package com.cg.produce;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.discovery.DiscoveryClient;
@SpringBootTest
public class DiscoveryTest {
//服务发现的Api————自动注入
@Autowired
DiscoveryClient discoveryClient;
@Test
void discoveryClientTest(){
//.getServices()——查询所有微服务的名字。
for (String service : discoveryClient.getServices()){
System.out.println("service = " + service);
}
}
}
每个微服务都在哪些机器,它们对应的IP和端口号:
@SpringBootTest
public class DiscoveryTest {
//服务发现的Api————自动注入
@Autowired
DiscoveryClient discoveryClient;
@Test
void discoveryClientTest(){
//.getServices()——查询所有微服务的名字。
for (String service : discoveryClient.getServices()){
System.out.println("service = " + service);
//每个微服务都在哪些机器,它们对应的IP和端口号
List<ServiceInstance> instances = discoveryClient.getInstances(service);
for (ServiceInstance instance : instances) {
System.out.println("ip:"+instance.getHost()+";"+"port = "+instance.getPort());
}
}
}
}
__________________________________________________
结果:
service = service-produce
ip:192.168.140.1;port = 9001
ip:192.168.140.1;port = 9000
service = service-order
ip:192.168.140.1;port = 8002
ip:192.168.140.1;port = 8000
ip:192.168.140.1;port = 8001
@Autowired
NacosServiceDiscovery nacosServiceDiscovery;
@Test
void nacosServiceDiscoveryTest() throws NacosException {
for (String service : nacosServiceDiscovery.getServices()) {
System.out.println("service = " + service);
//每个微服务都在哪些机器,它们对应的IP和端口号
List<ServiceInstance> instances = nacosServiceDiscovery.getInstances(service);
for (ServiceInstance instance : instances) {
System.out.println("ip:"+instance.getHost()+";"+"port = "+instance.getPort());
}
}
}
三、集群管理——Nocoa集群模式
在本机通过启动不同端口的方式来模拟集群
右键订单选择—Copy Configuration....
修改名字,启动要占用不同端口,所以要设置加一个程序参数,选择program arguments
在多出的一行里填上
--server.port=8001
点击Apply - OK
通过此操作多复制几分
然后选择所有启动类,全部启动起来
设置中心——页面相关设置
远程调用-基本流程
模拟一个下单场景
GenerateAllSetMethod插件—Alt+Enter—对象的分号前
一键调用一个对象的所有的set方法,get方法等 在方法上生成两个对象的转换
微服务之间的调用
模型层里囊括了所有微服务的数据层,不需要再在微服务里创建数据层
订单实体:
package com.cg.order.bean;
import com.cg.product.bean.Product;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class Order {
private Long id;
private BigDecimal totalAmount;
private Long userId;
private String nickName;
private String address;
private List<Product> productList;
}
商品实体:
package com.cg.product.bean;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class Product {
private Long id;
private BigDecimal price;
private String productName;
private int num;
}
依赖:
<dependencies>
<!--自动生成 get set 等方法 依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
将模型层引入到公共微服务pom.xml的依赖里面—services父微服务里面添加
<dependency>
<groupId>com.cg</groupId>
<artifactId>module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
service-order 微服务
controller:
package com.cg.order.controller;
import com.cg.order.bean.Order;
import com.cg.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/create")
private Order createOrder(@RequestParam("userId") Long userId,
@RequestParam("productId") Long productId){
Order order = orderService.createOrder(userId, productId);
return order;
}
}
service:
package com.cg.order.service;
import com.cg.order.bean.Order;
public interface OrderService {
Order createOrder(Long userId, Long productId);
}
impl —— 远程调用 商品
package com.cg.order.service.impl;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import com.cg.order.bean.Order;
import com.cg.order.service.OrderService;
import com.cg.product.bean.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@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);
//TOOO 总金额需要计算
order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
order.setUserId(userId);
order.setNickName("ssss");
order.setAddress("北京");
//远程查询商品列表
order.setProductList(Arrays.asList(product));
return order;
}
//远程调用 (要实现负载均衡)
private Product getProductFromRemote(Long productId){
//1、获取到商品服务所在的所有机器IP+port
List<ServiceInstance> instances = discoveryClient.getInstances("service-produce");
//随便获取一个实例
ServiceInstance instance = instances.get(0);
//http://localhost:9001/product/4
//远程url地址
String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/product/" + productId;
log.info("远程请求:{}", url);
//2、给远程发送请求即可
//spring 提供了一个 组件 发送请求
//这个组件线程安全的, 全局只有一个都可以
//RestTemplate restTemplate = new RestTemplate();
Product product = restTemplate.getForObject(url, Product.class);//第二个数据是json,转化成对象
return product;
}
}
config —— 配置类
package com.cg.order.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class OrderConfig {
@Bean //放到容器中
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
service-produce 微服务
controller:
package com.cg.produce.controller;
import com.cg.produce.service.ProductService;
import com.cg.product.bean.Product;
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.RestController;
@RestController//接受请求并响应数据
public class ProductController {
@Autowired
private ProductService productService;
//查询商品
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable("id") Long productId){
Product product = productService.getProductById(productId);
return product;
}
}
service
package com.cg.produce.service;
import com.cg.product.bean.Product;
public interface ProductService {
Product getProductById(Long productId);
}
impl:
package com.cg.produce.service.impl;
import java.math.BigDecimal;
import com.cg.produce.service.ProductService;
import com.cg.product.bean.Product;
import org.springframework.stereotype.Service;
@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(2);
return product;
}
}
config —— 配置类
package com.cg.produce.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ProductServiceConfig {
@Bean //放到容器中
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
负载均衡 —— 远程调用
流程 | 内容 | 核心 |
---|---|---|
1 | 引入负载均衡依赖 | spring-cloud-starter-loadbalancer |
2 | 测试负载均衡API | LoadBalancerClient |
3 | 测试远程调用 | RestTemplate |
4 | 测试负载均衡调用 | @LoadBalanced |
订单调别人—引用负载均衡依赖 (进行单元测试)
service-order 微服务 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
test 测试类
测试类 包名 和 主程序 要相同
package com.cg.order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
@SpringBootTest
public class LoadBalancerTest {
@Autowired
LoadBalancerClient loadBalancerClient;
@Test
void test(){
//会选择某个微服务的地址 会返回单个实例
//按照轮询规则的负载均衡
ServiceInstance choose = loadBalancerClient.choose("service-produce");
System.out.println("choose = " + choose.getHost() + ":" + choose.getPort());
choose = loadBalancerClient.choose("service-produce");
System.out.println("choose = " + choose.getHost() + ":" + choose.getPort());
choose = loadBalancerClient.choose("service-produce");
System.out.println("choose = " + choose.getHost() + ":" + choose.getPort());
choose = loadBalancerClient.choose("service-produce");
System.out.println("choose = " + choose.getHost() + ":" + choose.getPort());
}
}
————————————————————————————————————————————————————————————————
结果:
choose = 192.168.140.1:9000
choose = 192.168.140.1:9001
choose = 192.168.140.1:9000
choose = 192.168.140.1:9001
修改 impl —— 远程调用 商品
package com.cg.order.service.impl;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import com.cg.order.bean.Order;
import com.cg.order.service.OrderService;
import com.cg.product.bean.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Slf4j //日志记录
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
@Autowired
LoadBalancerClient loadBalancerClient;
@Override
public Order createOrder(Long userId, Long productId) {
//调用 —— 远程方法
Product product = getProductFromRemoteLoadBalancerClient(productId);
Order order = new Order();
order.setId(1l);
//TOOO 总金额需要计算
order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
order.setUserId(userId);
order.setNickName("ssss");
order.setAddress("北京");
//远程查询商品列表
order.setProductList(Arrays.asList(product));
return order;
}
private Product getProductFromRemote(Long productId){
//1、获取到商品服务所在的所有机器IP+port
List<ServiceInstance> instances = discoveryClient.getInstances("service-produce");
//随便获取一个实例
ServiceInstance instance = instances.get(0);
//http://localhost:9001/product/4
//远程url地址
String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/product/" + productId;
log.info("远程请求:{}", url);
//2、给远程发送请求即可
//spring 提供了一个 组件 发送请求
//这个组件线程安全的, 全局只有一个都可以
//RestTemplate restTemplate = new RestTemplate();
Product product = restTemplate.getForObject(url, Product.class);//第二个数据是json,转化成对象
return product;
}
//完成负载均衡 发送请求
private Product getProductFromRemoteLoadBalancerClient(Long productId){
//会选择某个微服务的地址 会返回单个实例 ————按照轮询规则的负载均衡
ServiceInstance choose = loadBalancerClient.choose("service-produce");
//远程url地址
String url = "http://" + choose.getHost() + ":" + choose.getPort() + "/product/" + productId;
log.info("远程请求:{}", url);
//2、给远程发送请求即可
Product product = restTemplate.getForObject(url, Product.class);//第二个数据是json,转化成对象
return product;
}
}
订单调别人—负载均衡 (更简单 注解版@LoadBalanced)
修改 service-order config配置
package com.cg.order.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class OrderConfig {
//远程调用就自带 负载均衡
@LoadBalanced //注解式 负载均衡
@Bean //放到容器中
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
修改 impl —— 远程调用 商品
package com.cg.order.service.impl;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import com.cg.order.bean.Order;
import com.cg.order.service.OrderService;
import com.cg.product.bean.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Slf4j //日志记录
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
@Autowired
LoadBalancerClient loadBalancerClient;
@Override
public Order createOrder(Long userId, Long productId) {
//调用 —— 远程方法
Product product = getProductFromRemoteLoadBalanced(productId);
Order order = new Order();
order.setId(1l);
//TOOO 总金额需要计算
order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
order.setUserId(userId);
order.setNickName("ssss");
order.setAddress("北京");
//远程查询商品列表
order.setProductList(Arrays.asList(product));
return order;
}
private Product getProductFromRemote(Long productId){
//1、获取到商品服务所在的所有机器IP+port
List<ServiceInstance> instances = discoveryClient.getInstances("service-produce");
//随便获取一个实例
ServiceInstance instance = instances.get(0);
//http://localhost:9001/product/4
//远程url地址
String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/product/" + productId;
log.info("远程请求:{}", url);
//2、给远程发送请求即可
//spring 提供了一个 组件 发送请求
//这个组件线程安全的, 全局只有一个都可以
//RestTemplate restTemplate = new RestTemplate();
Product product = restTemplate.getForObject(url, Product.class);//第二个数据是json,转化成对象
return product;
}
//进阶2 完成负载均衡 发送请求
private Product getProductFromRemoteLoadBalancerClient(Long productId){
//会选择某个微服务的地址 会返回单个实例 ————按照轮询规则的负载均衡
ServiceInstance choose = loadBalancerClient.choose("service-produce");
//远程url地址
String url = "http://" + choose.getHost() + ":" + choose.getPort() + "/product/" + productId;
log.info("远程请求:{}", url);
//2、给远程发送请求即可
Product product = restTemplate.getForObject(url, Product.class);//第二个数据是json,转化成对象
return product;
}
//进阶3 基于注解的负载均衡
private Product getProductFromRemoteLoadBalanced(Long productId){
//远程url地址
String url = "http://service-produce/product/" + productId;
log.info("远程请求:{}", url);
//2、给远程发送请求即可 service-produce 这个 会被动态替换
Product product = restTemplate.getForObject(url, Product.class);
return product;
}
}
注册中心宕机——远程调用还会成功吗(面试题)
发两次请求:
1、请求注册中心获取微服务地址列表
2、给对方服务的某个地址发送请求
实例缓存 —— 和注册中心 实时同步:
请求注册中心获取微服务地址列表 后 进行缓存
注册中心宕机,微服务到底调用通不通过(两种情况):
1、调用过:远程调用不在依赖注册中心,可以通过
2、没调用过:(第一次发起远程请求),不能通过
配置中心
配置中心 —> 统一管理所有配置,需要改配置,只需要在配置中心修改,改后数据主动推送给指定的微服务(推送配置变更)
基本使用
启动Nacos——》引入依赖——》 application.properties配置 ——》创建 data-id (数据集)
可能都要用到—在父项目添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
在server-order子项目 编写配置
spring.cloud.nacos.server-addr=127.0.0.1:8848
spring.config.import=nacos:server-order.properties
在Nacos编写 配置
数据集:server-order.properties
内容:
1、 order.timeout=10min
2、 order.auto-confirm=7d
弄好后,其他人就可以使用
在server-order controller里面进行测试
想要实现配置刷新(修改配置中心值,刷新获取结果)添加@RefreshScope
package com.cg.order.controller;
import com.cg.order.bean.Order;
import com.cg.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RefreshScope //配置刷新
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@Value("${order.timeout}")
String orderTimeout;
@Value("${order.auto-confirm}")
String orderAutoConfirm;
@GetMapping("/config")
public String config(){
return "order.timeout="+orderTimeout+", order.auto-confirm="+orderAutoConfirm;
}
@GetMapping("/create")
private Order createOrder(@RequestParam("userId") Long userId,
@RequestParam("productId") Long productId){
Order order = orderService.createOrder(userId, productId);
return order;
}
}
启动启动类:
其他微服务 没有 导入任何配置 报错
引用了Nacos配置中心,就必须使用Nacos前缀指定
两种方法解决:
1、Nacos某个引用的配置文件 设置成可选的
spring.config.import=optional:nacos: instead
2、***把Nacos导入检查功能禁用掉
#导入
#禁用导入检查
spring.cloud.nacos.config.import-check.enabled=false
配置中心—动态刷新
非常多的属性要取值,每个都要@Value,非常麻烦
步骤:
1、@Value("${xxx}")获取配置+@RefreshScope实现自动刷新
2、@ConfigurationProperties无感自动刷新
3、NacosConfigManager监听配置变化
写一个类—装配置属性
package com.cg.order.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
//配置批量绑定在nacos下, 可以无需@RefreshScope就能实现自动刷新
@ConfigurationProperties(prefix = "order")
@Data
public class OrderProperties {
String timeout;
String autoConfirm;
}
在server-order controller里面进行测试
package com.cg.order.controller;
import com.alibaba.nacos.shaded.com.google.j2objc.annotations.ReflectionSupport;
import com.cg.order.bean.Order;
import com.cg.order.properties.OrderProperties;
import com.cg.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
OrderProperties orderProperties;
@GetMapping("/config")
public String config(){
return "order.timeout="+orderProperties.getTimeout()+", order.auto-confirm="+orderProperties.getAutoConfirm();
}
@GetMapping("/create")
private Order createOrder(@RequestParam("userId") Long userId,
@RequestParam("productId") Long productId){
Order order = orderService.createOrder(userId, productId);
return order;
}
}
编码方式监听 配置变化 —NacosConfigManager
在启动类 写
package com.cg.order;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@EnableDiscoveryClient//开启发现服务注册的功能
//代表它是一个springboot应用
@SpringBootApplication
public class OrderMainApplication {
public static void main(String[] args) {
SpringApplication.run(OrderMainApplication.class,args);
}
//1.项目启动就监听配置文件变化
//2.发生变化后拿到变化值
//3.发送邮件
// ApplicationRunner applicationRunner(){
// return new ApplicationRunner() {
// @Override
// public void run(ApplicationArguments args) throws Exception {
//
// }
// };
// }
//箭头函数简写
@Bean
ApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager){
return args -> {
ConfigService configService = nacosConfigManager.getConfigService();
configService.addListener("server-order.properties",
"DEFAULT_GROUP", new Listener() {
@Override
public Executor getExecutor() {
//线程池
return Executors.newFixedThreadPool(4);
}
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("变化的配置信息"+configInfo);
System.out.println("邮件通知....");
}
});
System.out.println("===============");
};
}
}
Nacos中的数据集和application.properties有相同配置项,哪个生效?(面试)
以配置中心的为准
先导入优先——》到外部优先
配置中心—数据隔离
-
需求描述
-
项目有多套环境:dev ,test, prod
-
每个微服务,同一种配置,在每套环境的值都不一样
-
如:database.properties
-
如:common.properties
-
-
项目可以通过切换环境,加载本环境的配置
-
-
难点
-
区分多套环境
-
区分多种微服务
-
区分多种配置
-
按需加载配置
-
Namespace 名称空间 — 区分多套环境
Nacos 里有 多个 名称空间
一个Namespace 有 多个 分组(Group),Group ——区分多种微服务
一个分组有多个 数据集(Data-id),Data-id ——区分多种配置
通过spring boot 激活 指定环境 ,激活 这套配置
在Nacos里进行装配
点击创建命名空间,创建出 dev ,test, prod 这几套环境
在配置列表 , 给一个环境 装配好配置 后 ,可以通过克隆 复制给其他 环境, 然后修改参数
实现按需加载
application.properties 废弃 配置多的话 ,会杂乱
使用 application.yml
server:
port: 8000
spring:
application:
name: service-order
cloud:
nacos:
server-addr: 127.0.0.1:8848
config:
namespace: test
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
添加配置属性
package com.cg.order.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
//配置批量绑定在nacos下, 可以无需@RefreshScope就能实现自动刷新
@ConfigurationProperties(prefix = "order")
@Data
public class OrderProperties {
String timeout;
String autoConfirm;
String dbUrl;
}
控制器方法增加返回值
@GetMapping("/config")
public String config(){
return "order.timeout="+orderProperties.getTimeout()+
", order.auto-confirm="+orderProperties.getAutoConfirm()+
", order.db-url="+orderProperties.getDbUrl();
}
运行
配置不一样,动态的 处理
yml多文档模式
server:
port: 8000
spring:
profiles:
active: test
application:
name: service-order
# Nacos 的地址
cloud:
nacos:
server-addr: 127.0.0.1:8848
config:
import-check:
enabled: false
namespace: ${spring.profiles.active:dev}
# :dev设置默认值
---
spring:
#指定导入文件
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
activate:
on-profile: dev
---
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
- nacos:haha.properties?group=order
activate:
on-profile: test
---
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
- nacos:common.properties?group=product
activate:
on-profile: prod