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
流程
此时就可以通过上方激活时谁就导入对应的配置文件了