SpringCloud 微服务 Nacos --分布式架构--超详细

单体架构(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

微服务单体架构区别:

  1. 单体架构所有的模块全都耦合在一块,代码量大,维护困难。

    微服务每个模块就相当于一个单独的项目,代码量明显减少,遇到问题也相对来说比较好解决。

  2. 单体架构所有的模块都共用一个数据库,存储方式比较单一。

    微服务每个模块都可以使用不同的存储方式(比如有的用redis,有的用mysql等),数据库也是单个模块对应自己的数据库。(单体架构也可以实现,但是比较麻烦)

  3. 单体架构所有的模块开发所使用的技术一样。

    微服务每个模块都可以使用不同的开发技术,开发模式更灵活。

创建分布式项目

父项目

项目创建:

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)测试服务发现ApiDiscoveryClient
2(组件2)测试服务发现ApiNacosServiceDiscovery
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测试负载均衡APILoadBalancerClient
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值