速通springcloud--01nacos

创建微服务工程

cloud-demo
|__services
    |__service-product
    |__service-order

1.创建父项目

创建一个名为cloud-demo的springboot父项目,用于对springboot,springcloud,springcloud alibaba版本锁定

<packaging>pom</packaging>
<properties>
    <java.version>17</java.version>
    <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来进行依赖管理 -->
<!-- 即让子模块需要显式声明依赖,不用指定version或scope -->
<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>

2.创建微服务管理子项目

创建一个名为services的普通maven项目该子项目继承自cloud-demo项目,其管理了所有微服务的公共依赖

<packaging>pom</packaging>

<dependencies>
    <!--注册中心-->
    <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>

3.创建微服务子项目

创建名为service-product,service-order的普通maven子项目。这两个项目都是services项目的子项目,是微服务项目

Nacos

官网:https://nacos.io/docs/latest/overview/

它是一个能更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。可以实现服务发现和服务健康检测,动态配置服务,动态DNS服务,服务以及元数据管理

对于windows平台的nacos在官网下载并解压后可以在nacos/bin目录通过startup.cmd -m standalone以单机状态启动nacos

服务注册

  1. 启动nacos
    1. nacos/bin目录通过startup.cmd -m standalone以单机状态启动nacos
  2. 启动微服务
    1. 由于所有微服务模块都继承自cloud-demo,那么其本身也是boot项目
    2. 直接在微服务模块中导入spring-boot-starter-web依赖即可实现web开发
    3. 创建一个boot的启动类
    4. 编写一个配置文件,在其中配置应用名(会在服务中心显示为服务名),端口号,nacos地址
  3. 引入服务发现依赖
    1. spring-cloud-starter-alibaba-nacos-discovery
    2. 该依赖是所有微服务模块的公共依赖,故在service-product模块中导入
  4. 配置nacos地址
    1. spring.cloud.nacos.server-addr=127.0.0.1:8848
  5. 查看注册中心
    1. 访问http://localhost:8848/nacos
    2. 此时会在服务列表中看到我们启动的服务
  6. 集群模式启动测试
    1. 单机情况下通过拷贝实例模拟微服务集群
    2. 拷贝实例时需要在修改选项中勾选程序参数,在程序参数中可以配置在properties文件中能配置的所有参数,只需要在前面添加--例如:--server.port=8001
    3. 对于拷贝添加的服务会在nacos控制台的实例数进行显示
// service-order

// pom.xml
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

// com.atli.order.OrderMainApplication.java
@SpringBootApplication
public class OrderMainApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderMainApplication.class,args);
    }
}
// resources/application.properties
spring.application.name=service-order
server.port=8000
spring.cloud.nacos.server-addr=127.0.0.1:8848



// service-product

// pom.xml
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

// com.atli.product
@SpringBootApplication
public class ProductMainApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductMainApplication.class, args);
    }
}
// resources/application.properties
spring.application.name=service-product
server.port=9000

spring.cloud.nacos.server-addr=127.0.0.1:8848

服务发现

  1. 添加测试依赖
    1. 在要进行测试的服务模块中添加spring-boot-starter-test依赖
  2. 开启服务发现功能
    1. 在微服务启动文件添加@EnableDiscoveryClient
  3. 测试服务发现API
    1. DiscoveryClient
    2. NacosServiceDiscovery
// service-product

// pom.xml
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>

// discoveryTest.java
@SpringBootTest
public class DiscoveryTest {

    @Autowired
    DiscoveryClient discoveryClient;

    @Autowired
    NacosServiceDiscovery nacosServiceDiscovery;

    @Test
    void discoveryClientTest(){
        discoveryClient.getServices().forEach(service -> {
            // 打印微服务名称
            System.out.println(service);
            discoveryClient.getInstances(service)
                    // 打印服务ip及其端口号
                    .forEach(instance -> System.out.println(instance.getHost() + ":" + instance.getPort()));
        });
    }

    @Test
    void nacosServiceDiscoveryTest() throws NacosException {
        nacosServiceDiscovery.getServices().forEach(service -> {
            System.out.println(service);
            discoveryClient.getInstances(service)
                    .forEach(instance -> System.out.println(instance.getHost() + ":" + instance.getPort()));
        });
    }
}

远程调用实例

这是一个下单场景,用户对订单服务发起创建订单请求,订单服务会去商品服务查询订单中的商品,查询后会返回商品数据到订单服务生成订单,最后返回订单数据

  1. 创建实体对象
    1. 由于在该实例中订单服务和商品服务都要使用商品类,故将所有实体类抽象成model层,以方便各服务对实体类的访问
    2. 又由于model层要被所有微服务调用,故需要放在与services模块同级目录下,并将其作为依赖导入到services模块
  2. 创建服务的controller,service层
    1. 按照三层架构模式编写controller和service层,mapper层用假数据代替
    2. 在订单服务的service层使用RestTemplate远程调用商品服务
  3. 启动所有微服务进行测试
cloud-demo
|__model
|   |__com.atli.order.bean.Order(class)
|   |__com.atli.product.bean.Product(class)
|
|__services
    |__service-order
    |    |__com.atli.order
    |        |__config.OrderConfig(class)
    |        |__controller.OrderController(class)
    |        |__service
    |            |__OrderService(interface)
    |            |__Impl
    |                |__OrderServiceImpl(class)
    |
    |__service-product
          |__com.atli.product
              |__config.ProductConfig(class)
              |__controller.ProductController(class)
              |__service
                  |__ProductService(interface)
                  |__Impl
                      |__ProductServiceImpl(class)

model 模块

// com.atli.order.bean.Order
@Data
public class Order {
    private Long id;
    private BigDecimal totalAmount;
    private Long userId;
    private String nickName;
    private String address;
    private List<Object> productList;
}

// com.atli.product.bean.Product
@Data
public class Product {
    private Long id;
    private BigDecimal price;
    private String productName;
    private int num;
}

services 模块

<!-- pom.xml(service) -->
<!--使所有微服务依赖公共模型层model-->
<dependency>
   <groupId>com.atli</groupId>
   <artifactId>model</artifactId>
   <version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>annotationProcessor</scope>
</dependency>
<!-- 用于远程调用时负载均衡 -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

service-product 模块

// com.atli.product.controller.ProductController
@RestController
public class ProductController {

    @Autowired
    ProductService productService;

    @GetMapping("/product/{id}")
    public Product getProduct(@PathVariable("id") Long productId) {
        return productService.getProductById(productId);
    }
}

// com.atli.product.service.ProductServiceImpl
@Service
public class ProductServiceImpl implements ProductService {
    @Override
    public Product getProductById(Long id) {
        Product product = new Product();
        product.setId(id);
        product.setProductName("apple");
        product.setPrice(BigDecimal.valueOf(8848));
        product.setNum(2);
        return product;
    }
}

// com.atli.product.config.ProductConfig
@Configuration
public class ProductConfig {
    @Bean
    // 由于RestTemplate是线程安全的,全局只有一个,故在这里将其加入ioc方便使用
    //RestTemplate用于远程调用
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

Order模块

// com.atli.order.OrderController
@RestController
public class OrderController {
    @Autowired
    OrderService orderService;

    @GetMapping("/create/{userId}/{productId}")
    public Order createOrder(@PathVariable Long userId, @PathVariable Long productId) {
        return orderService.createOrder(userId, productId);
    }
}

// com.atli.order.OrderServiceImpl
@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 = getProductFromRemote(productId);
        Order order = new Order();
        order.setId(1L);
        order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
        order.setUserId(userId);
        order.setNickName("zhangsan");
        order.setAddress("home");
        order.setProductList(Arrays.asList(product));

        return order;
    }

    private Product getProductFromRemote(Long productId) {
        // 获取到商品服务所在及其的IP和端口
        List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
        ServiceInstance serviceInstance = instances.get(0);
        // 该方法使用了LoadBalancerClient,底层使用轮询实现负载均衡
        // ServiceInstance serviceInstance = loadBalancerClient.choose("service-product");
        // 拼接远程调用的url
        String url ="http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/product/" + productId;

        // 发送远程请求
        log.info("已发送请求:{}",url);
        // 如果使用了注解式的负载均衡上面获取url的代码可以直接去掉
        // 这里的service-product是服务名称
        // String url = "http://service-product/product/"+productId
        Product product = restTemplate.getForObject(url, Product.class);

        return product;

    }
}

// com.atli.order.config.OrderConfig
@Configuration
public class OrderConfig {
    //使用该注解会让该服务自动拥有负载均衡功能
    //并且在发送http请求时直接使用服务名称来代替微服务的ip和端口号
    //@LoadBalanced
    @Bean
    // 由于RestTemplate是线程安全的,全局只有一个,故在这里将其加入ioc方便使用
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

配置中心

nacos配置中心可以实现配置的集中管理,推送配置变更,不停机配置更新。

基本使用

  1. 启动nacos
  2. 引入依赖
    1. spring-cloud-starter-alibaba-nacos-config
    2. 由于可能所有微服务都需要使用配置中心,故在services模块引入该依赖
  3. 对application.properties文件进行配置
    1. spring.cloud.nacos.server-addr=127.0.0.1:8848,该配置是指定nacos地址
    2. spring.config.import=optional:nacos:service-order.properties,该配置可以让service-order微服务在启动后从nacos中读取配置文件。并且这里设置了optional表示该项为可选项,防止项目启动但nacos没有配置出现报错
  4. 创建data-id(数据集)
    1. 在nacos界面配置管理的配置列表可以新建配置
    2. Data ID是该配置的名称,此处按照步骤3的名称应为service-order.properties
    3. 在配置内容中可以进行任意配置(只要是properties能配的都可以)
  5. 使用数据集
    1. 在使用数据集时,需要在当前类中添加@RefreshScope注解,让监听的数据有类似vue中响应式数据的动态刷新功能
    2. 获取数据集依然使用springboot中获取配置的方法:@Value("${data}")

批量动态刷新

上面的例子中,使用了springboot中获取配置的方法:@Value("${data}"),当遇到有大量配置需要被读取,可以将这些配置抽取到一个单独的类中,使用@ConfigurationProperties(prefix = "前缀名")来进行批量绑定

// 这里假设在配置中心配置了order.timeout=30min;order.auto-confirm=7d
@Component
@ConfigurationProperties(prefix="order")
@Data
public class OrderProperties{
    String timeout;
    String autoConfirm;
}

配置监听

对于配置文件的修改我们可以使用nacos提供的NacosConfigManager搭配spring的ApplicationRunner进行使用

// com.atli.order.OrderMainApplication
@Bean
// 使用ApplicationRunner让该段代码在启动spring时运行
ApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager){
    return args -> {
        // 获取配置服务
        ConfigService configService = nacosConfigManager.getConfigService();
        // 数据集ID(配置文件名):service-order.properties
        // 数据集组名:DEFAULT_GROUP,该组名可以在nacos面板看到
        // 添加监听,监听函数需要自己new
        configService.addListener("service-order.properties","DEFAULT_GROUP",new Listener(){
            @Override
            // 线程池
            public Executor getExecutor(){
                return Executors.newFixedThreadPool(4);
            }
            @Override
            // 接收变化的配置信息
            public void receiveConfigInfo(String configInfo){
                System.out.println("ReceiveConfigInfo:"+configInfo)
            }
        });
    };
}

数据隔离

在实际开发中,会需要到开发环境,测试环境,还有生产环境。其中每种环境中又有不同的微服务,每种微服务又会有各种配置。

我们可以使用springBoot定义多环境来区分各种场景,使用名称空间来区分多套环境,使用组来区分微服务,使用数据集来区分配置。这些操作可能很轻易的再nacos面板中实现

对于按需加载配置我们可以使用指定命名空间和组名实现

server:
    port: 8000
spring:
    profiles: # 这个配置用于指定当前环境
        active: dev
    application:
        name: service-order
    cloud:
        nacos:
            server-addr: 127.0.0.1:8848
            config:
                namespace: dev
# 这里的`---`是yml的多文档模式
---
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:environment.properties?group=order
        activate:
            on-profile: test #指明生效环境
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值