SpringCloud【Nacos】

 Nacos

1,注册中心

服务注册:服务启动,就会在nacos注册中心进行注册

服务发现:服务调用就会在nacos去寻找服务

下载安装:官网下载nacos Nacos Server 下载 | Nacos 官网

启动:bin目录下cmd--->startup.cmd -m standalone       单机模式启动

打开页面:http://192.168.1.137:8848/nacos/index.html

1.1,Nacos服务注册

pom文件下,新增web依赖

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

新增启动文件OrderMainApplication.java

package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class OrderMainApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderMainApplication.class, args);
    }
}

 新增配置文件application.properties

#应用名字
spring.application.name=services-order
#占用端口
server.port=8000
#告诉当前服务,注册中心在哪
spring.cloud.nacos.server-addr=127.0.0.1:8848

启动多个服务,或者同一个服务右键复制出来换不通端口启动,启动之后,进入Nacos即可看见注册得服务

1.2,Nacos服务发现

启动类上加上启动服务发现注解ProducetMainApplication 

@EnableDiscoveryClient//开启服务发现功能
@SpringBootApplication
public class ProducetMainApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProducetMainApplication.class, args);
    }
}

添加测试依赖pom

 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope><!--测试范围有效-->
    </dependency>

新增测试类 DiscoveryClientTest 

package org.example.product;

import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import com.alibaba.nacos.api.exception.NacosException;
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.discovery.DiscoveryClient;

@SpringBootTest
public class DiscoveryClientTest {


    @Autowired
    DiscoveryClient discoveryClient;//注册中心都可用

    @Autowired
    NacosServiceDiscovery nacosServiceDiscovery;//Nacos才可用

    @Test
    void discoveryClientTest(){
        for (String service : discoveryClient.getServices()) {
            //获取注册得微服务有哪些
            System.out.println("service == "+service);
            //获取微服务端口和IP
            for (ServiceInstance instance : discoveryClient.getInstances(service)) {
                System.out.println("host:port == "+instance.getHost()+":"+instance.getPort());
            }
        }
    }

    @Test
    void nacosDiscoveryClientTest() throws NacosException {
        for (String service : nacosServiceDiscovery.getServices()) {
            //获取注册得微服务有哪些
            System.out.println("service == "+service);
            //获取微服务端口和IP
            for (ServiceInstance instance : discoveryClient.getInstances(service)) {
                System.out.println("host:port == "+instance.getHost()+":"+instance.getPort());
            }
        }
    }
}

 打印结果如下

service == services-product
host:port == 192.168.1.137:9000
service == services-order
host:port == 192.168.1.137:8000
host:port == 192.168.1.137:8001

1.3,Nacos调用实例

order服务添加创建订单接口

//controller
@RestController
public class OrderController {

    @Autowired
    OrderService orderService;

    @GetMapping("/create")
    public Order createOrder(@RequestParam("userId") Long userId, @RequestParam("productId") Long productId){
        Order order = orderService.createOrder(userId,productId);
        return order;
    }
}
//domain
@Data
public class Order {
    private Long id;
    private BigDecimal totalAmount;
    private Long userId;
    private String nickName;
    private String address;
    private List productList;
}

//service
@Service
public class OrderServiceImpl implements OrderService {
    @Override
    public Order createOrder(Long userId, Long productId) {
        Order order = new Order();
        order.setId(1l);
        //todo  远程调用获取商品数据
        order.setTotalAmount(BigDecimal.ONE);
        order.setUserId(userId);
        order.setNickName("爱学习");
        order.setAddress("北京");
        //todo  远程调用获取商品数据
        order.setProductList(null);
        return order;
    }
}

peoduct服务添加商品接口 

//controller
@RestController
public class ProductController {

    @Autowired
    ProductService productService;

    @GetMapping("/getProductById/{productId}")
    public Product getProductById(@PathVariable("productId") Long productId) {
        Product product = productService.getProductById(productId);
        return product;
    }
}
//domain
@Data
public class Product {
    public Long id;
    private BigDecimal price;
    public String producetName;
    private Integer num;
}
//service
@Service
public class ProduecServiceImpl implements ProductService {

    @Override
    public Product getProductById(Long productId) {
        Product product = new Product();
        product.setId(productId);
        product.setPrice(new BigDecimal(10));
        product.setProducetName("苹果"+productId);
        product.setNum(3);
        return product;
    }
}

 订单服务,需要调用商品,所以此时修改如下

新建一个公共服务,等级同services,模块名model,将services中得bean都放进去,以便公共使用,原services中得bean就去掉

订单service新建一个远程调用方法 ProduceServiceImpl

    @Autowired
    DiscoveryClient discoveryClient;//服务发现

    @Autowired
    RestTemplate restTemplate;

  
/**
     * 远程调用商品服务
     * @return
     */
    public Product getProductByRemote(Long productId) {
        //获取服务
        List<ServiceInstance> instances = discoveryClient.getInstances("services-product");
        //拿到默认第一个服务
        ServiceInstance serviceInstance = instances.get(0);
        //拼接字符串
        String url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/getProductById/"+productId;

//        RestTemplate restTemplate = new RestTemplate();
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
    }
}

restTemplate 这个组件是线程安全得,所以全局可以只有一个,所以可以加上以下配置类 ,然后自动注入即可

org.example.order.config.OrderConfig
package org.example.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();
    }
}

 创建订单服务实现得远程调用数据,即可实现如下

    @Override
    public Order createOrder(Long userId, Long productId) {
        Product product = getProductByRemote(productId);

        Order order = new Order();
        order.setId(1l);
        //todo  远程调用
        order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
        order.setUserId(userId);
        order.setNickName("爱学习");
        order.setAddress("北京");
        //todo  远程调用
        order.setProductList(Arrays.asList(product));
        return order;
    }

启动服务,查看结果

 

1.4,Nacos负载均衡

 测试案例

order需要负载均衡调用其他,所以order先添加对应依赖

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

添加测试类org.example.order.LoadBalancerTest.java

package org.example.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 lbClient;

    @Test
    void Teat(){
        ServiceInstance choose = lbClient.choose("services-product");
        System.out.println("11=="+choose.getHost()+":"+choose.getPort());
        choose = lbClient.choose("services-product");
        System.out.println("22=="+choose.getHost()+":"+choose.getPort());
        choose = lbClient.choose("services-product");
        System.out.println("33=="+choose.getHost()+":"+choose.getPort());
        choose = lbClient.choose("services-product");
        System.out.println("44=="+choose.getHost()+":"+choose.getPort());
    }
}

/**输出,可见会轮询查找
 * 11==192.168.137.1:9002
 * 22==192.168.137.1:9001
 * 33==192.168.137.1:9000
 * 44==192.168.137.1:9002
 */

进阶版:改造订单远程调用商品方法OrderServiceImpl

    @Autowired//一定导入Spring-cloud场景
    LoadBalancerClient loadBalancer; 
/**
     * 进阶版:负载均衡的远程调用商品服务负载均衡的远程调用商品服务
     * @return
     */
    public Product getProductByRemoteWithLoadBalancer(Long productId) {
        //获取服务
        ServiceInstance choose = loadBalancer.choose("services-product");
        //拼接字符串
        String url = "http://"+choose.getHost()+":"+choose.getPort()+"/getProductById/"+productId;
        log.info("调用url:"+url);
//        RestTemplate restTemplate = new RestTemplate();
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
    }

进阶版2:基于注解的远程调用改造

OrderConfig

@Configuration
public class OrderConfig {


    @LoadBalanced//此处加入远程调用注解
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

OrderServiceImpl

  /**
     * 进阶版2:基于注解负载均衡的远程调用商品服务
     * @return
     */
    public Product getProductByRemoteWithLoadBalancerByAnnotatin(Long productId) {
        //services-product会被动态的替换为为对用ip端口
        String url = "http://services-product/getProductById/"+productId;
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
        /**
         * 此处测试方法,就只能是去商品服务下,输出,看是不是每个商品服务轮流打印
         */
    }
思考:(面试题)

如果注册中心宕机,远程调用还能成功吗?

注解流程 

1,RestTemplate会先从url地址上获取服务名

2,去找注册中心,要到对应服务地址列表

3,RestTemplate利用他的负载均衡算法去选择一个ip端口

4,发起请求访问

如果没有实时缓存,则每次调用都要走一遍注册中心,

加入了实时缓存,则如上图所示,调用过之后就会在缓存中,后续不需要注册中心也可,后续注册中心有改变则实时同步缓存即可(目前的Nacos设计即是如此)

所以如果注册中心宕机,远程调用还能成功吗?

1,如果调用过,远程调用不再一俩注册中心,可以成功

2,如果没调用过,即是第一次发送远程调用,则不能成功

测试:

开启所有服务,调用一次商品服务后,关闭Nacos,再去调用仍然可以成功

关掉所有服务,重新启动Nacos,然后重新启动所有服务,但不做任何调用,然后关闭Nacos,再尝试去调用,则会报错 No instances available for services-product

2,配置中心

2.1,基本使用

统一加入Nacos配置依赖services-pom.xml

         <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

在订单服务的pom文件中,导入对应nacos配置文件

#导入配置中心文件
spring.config.import=nacos:servicer-order.properties

在nacos中新增对应的配置文件,名为servicer-order.properties

添加俩测试配置

在OrderController中去获取配置文件里面的值

    @Value("${order.timeout}")
    String timeOut;
    @Value("${order.auto-confirm}")
    String autoConfirm;

    @GetMapping("/getConfig")
    public String getConfig(){
        return timeOut+"=="+autoConfirm;
    }

调用接口即可看到对应配置文件的内容值

 此时如果去修改Nacos配置文件,发布之后,在去调用,发现返回值并未改变,此时就需要在OrderController类上再加一个自动刷线的注解@RefreshScope//自动刷新

然后重启,修改Nacos发布后,立即调用即可随时拿到实时配置

由于nacos-config依赖是加到services整个模块下的,此时去启动product服务,会发现报错如下

此时是由于一旦引入了配置中心,项目启动还没有引入任何配置就会报错,说没有指定spring.config.import

此时可以修改配置文件,关闭导入检查功能

修改配置文件product--pom.xml

#关闭Nacos导入检查功能
spring.cloud.nacos.config.import-check.enabled=false

2.2,动态刷新

原@Value("${xx}")获取配置+@RefreshScope实现自动刷新

可替换为@ConfigurationProperties无感自动刷新

新增配置类

package org.example.order.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component//容器组件
@ConfigurationProperties(prefix = "order")//指定前缀名order.xxx
@Data
public class OrderProperties {

    String timeout;//去掉前缀名的名字
    String autoConfirm;

}

修改OrderController

//@RefreshScope//自动刷新
@RestController
public class OrderController {

    @Autowired
    OrderService orderService;
//    @Value("${order.timeout}")
//    String timeOut;
//    @Value("${order.auto-confirm}")
//    String autoConfirm;
    @Autowired
    OrderProperties orderProperties;

    @GetMapping("/getConfig")
    public String getConfig(){
        return orderProperties.getTimeout()+"=="+orderProperties.getAutoConfirm();
    }
}

 2.3,配置监听

OrderApplication

需求监听servicer-order.properties里面的变化,有变化发送消息提示
    1,项目启动就监听配置文件
    2,发生变化后拿到变化值
    3,发送邮件
    ApplicationRunner 是一个一次性任务,项目启动起来就会执行一次性任务,给容器中放入这个对象

package org.example.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
@SpringBootApplication
public class OrderMainApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderMainApplication.class, args);
    }

    //需求监听servicer-order.properties里面的变化,有变化发送消息提示
    //1,项目启动就监听配置文件
    //2,发生变化后拿到变化值
    //3,发送邮件
    //ApplicationRunner 是一个一次性任务,项目启动起来就会执行一次性任务,给容器中放入这个对象
    //@Bean方法下的参数,会自动从容器中拿
    @Bean
    ApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager) {
       /*本来写法是这样的,但他是一个函数式接口,可修改如下
        return new ApplicationRunner() {
            @Override
            public void run(ApplicationArguments args) throws Exception {

            }
        }*/
        //简写,new和方法声明,方法名都省略掉,参数只有一个,括号也可省略掉
        return args -> {
            //任务方法体
            System.out.println("启动即打印");
            ConfigService configService = nacosConfigManager.getConfigService();
            configService.addListener("servicer-order.properties", "DEFAULT_GROUP", new Listener() {
                @Override
                public Executor getExecutor() {//线程池
                    return Executors.newFixedThreadPool(4);
                }

                @Override
                public void receiveConfigInfo(String s) {
                    System.out.println("变化即打印===变化通知");
                    System.out.println(s);
                }
            });

        };
    }

}

重新启动服务器,打印出如下

修改Nacos配置发布之后,查看控制台打印如下

 思考:(面试题)

Nacos中的数据集和application.properties有相同的配置项目,哪个生效?

即使微服务有次配置,一旦配置中心有此配置,都已配置中心为主,才能实现统一管理

测试:

在order服务的application.properties中加入Nacos的servicer-order.properties里面相同的配置修改值

#测试这里还是按配置中心为主
order.timeout=11min
order.auto-confirm=6d

启动服务,调用接口,显示是按配置中心为主的

配置优先级

 先导入优先,外部优先,

多配置导入时,先声明优先

比如

<!--servicer-order的优先级就要高于xxx-->
spring.config.import=nacos:servicer-order.properties,xxx.properties

2.4,数据隔离

需求:区分多套环境---多种微服务----多种配置

实现:命名空间---多个分组(微服务)--多个配置文件---多个配置项目

图示:

按需加载:

首先,配置较多了,替换properties文件为yml如下

##应用名字
#spring.application.name=services-order
##占用端口
#server.port=8000
##告诉当前服务,注册中心在哪
#spring.cloud.nacos.server-addr=127.0.0.1:8848

server:
  port: 8000
spring:
  application:
    name: services-order
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848

测试导入配置文件

加入database.properties

修改yml文件

server:
  #服务端口
  port: 8000
spring:
  application:
    #服务名
    name: services-order
  cloud:
    nacos:
      #nacos配置
      server-addr: 127.0.0.1:8848
      #导入对应名称空间的dev/test/prod   需要用谁,这里就可以写谁
      config:
        namespace: dev
  #导入配置文件
  config:
    import:
      #import是list形式可写成短横线   默认导入是public名称空间的的默认分组下的配置文件
      #加?group=order可指定分组
      - nacos:common.properties?group=order
      - nacos:database.properties?group=order

修改 OrderProperties,添加dbUrl

package org.example.order.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component//容器组件
@ConfigurationProperties(prefix = "order")//指定前缀名order.xxx
@Data
public class OrderProperties {

    String timeout;//去掉前缀名的名字
    String autoConfirm;

    //order分组下的都可加入
    String dbUrl;

}

修改接口,添加dbUrl获取

@RestController
public class OrderController {
    @Autowired
    OrderProperties orderProperties;

    @GetMapping("/getConfig")
    public String getConfig(){
        return orderProperties.getTimeout()+"=="+orderProperties.getAutoConfirm()+
                "==="+orderProperties.getDbUrl();
    }
}

 调用接口,测试获取

问题:如果不同环境导入的配置文件数量不一样,又该如何处理

解决: 使用多文档模式---来添加配置

server:
  #服务端口
  port: 8000
spring:
  ########多环境激活,比如此处激活dev  则使用dev下的配置
  profiles:
    active: dev
  application:
    #服务名
    name: services-order
  cloud:
    nacos:
      #nacos配置
      server-addr: 127.0.0.1:8848
      ########导入对应名称空间的dev/test/prod
      config:
        #先默认把导入检查去掉
        import-check:
          enabled: false
        #######此时此也需要写活,获取spring.profiles.active下的值,:public代表没指定的时候默认用public
        namespace: ${spring.profiles.active:public}


######导入配置文件    多文档
---
spring:
  config:
    import:
      #import是list形式可写成短横线   默认导入是public名称空间的的默认分组下的配置文件
      #加?group=order可指定分组
      - nacos:common.properties?group=order
      - nacos:database.properties?group=order
    activate:
      #dev环境下,上面俩配置文件生效
      on-profile: dev
---
spring:
  config:
    import:
      #import是list形式可写成短横线   默认导入是public名称空间的的默认分组下的配置文件
      #加?group=order可指定分组
      - nacos:common.properties?group=order
      - nacos:database.properties?group=order
      - nacos:haha.properties?group=order
    activate:
      #test环境下,上面俩配置文件生效
      on-profile: test
---
spring:
  config:
    import:
      #import是list形式可写成短横线   默认导入是public名称空间的的默认分组下的配置文件
      #加?group=order可指定分组
      - nacos:common.properties?group=order
      - nacos:database.properties?group=order
      - nacos:hehe.properties?group=order
    activate:
      #prod环境下,上面俩配置文件生效
      on-profile: prod

流程

此时就可以通过上方激活时谁就导入对应的配置文件了

3,Nacos总结 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xhmdaydayup

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值