为什么不用集群架构,而用分布式:
- 集群架构项目改了之后需要重新打包,重新部署,牵一发而动全身
- 有些服务可能不适合用java语言写,比如流媒体服务器,直播功能,甚至对视频做一些处理,,java语言可能不擅长这个,,一般用c++开发,,就必须用分布式
分布式架构需要消除单点故障
Nacos
注册中心
每一个服务上线或者下线的是否,都去告诉nacos,nacos作为注册中心,维护了,这些服务节点的信息
nacos官网: https://nacos.io/docs/v2.4/quickstart/quick-start/
下载对应版本nacos,以单机模式启动
shell
# -m : mode 什么模式
startup.cmd -m standalone
cloud版本:

springboot使用nacos:
导入依赖:
xml
<!-- 服务发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
@EnableDiscoveryClient开启服务发现的功能
spring容器会有一个DiscoveryClient的Bean,,
- getServices() : 获取所有的服务
- getInstances("服务名字") : 获取指定服务名字的实例,,可能是多个实例
- 返回的
ServiceInstance实例,可以从中获取到ip和port,,进行远程调用
- 返回的
上面是获取指定某一个实例去调用,,实际中不可能请求都打在一个实例上,,需要将请求负载均衡:
引入spring-cloud-starter-loadbalancer
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
引入这个包之后,spring容器中会有一个负载均衡的客户端LoadBalancerClient,可以通过LoadBalancerClient的choose(服务名字)通过负载均衡,选择一个实例返回:
java
@Autowired
private LoadBalancerClient loadBalancerClient;
// 负载均衡调用 product
public Product getProductFromRemoteWithLoadBalance(Long productId){
ServiceInstance choose = loadBalancerClient.choose("service-product");
String url = "http://"+choose.getHost()+":"+choose.getPort()+"/product/"+productId;
// 给远程发送请求
log.info("远程请求:{}",url);
Product product = restTemplate.getForObject(url, Product.class);
return product;
}
但是一般不会这样用,,,
也可以使用@LoadBalanced 打在RestTemplate的bean上面,这样,直接通过restTemplate就可以直接负载均衡发送请求:
java
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
/**
* 基于注解的 负载均衡 @LoadBalancer,,, url的host换成服务名字
* @return
*/
private Product getProductFromRemoteWithLoadBalancerAndAnnotation(Long productId){
String url = "http://service-product/product/"+productId;
// 底层去负载均衡的,,这里看不到他具体调用了哪个的日志
Product product = restTemplate.getForObject(url, Product.class);
return product;
}
但是一般也不会这样用,,都是直接集成openfeign,openfeign直接发送网络请求,自带负载均衡,还有一些重试,兜底,拦截器等功能
nacos作为配置中心
依赖:
xml
<!-- nacos作为配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
nacos中的配置中心,使用了spring.config.import ...这个是springboot2.4之后的特性,,自动导入配置文件,,配置文件生效的规则就是:
- 先导入的先生效,,后导入的如果有属性和先导入的重复了,,使用先导入的
一旦统一引入了nacos配置的依赖,,如果当前服务没有指定对应的配置,,项目启动就会报错,,可以暂时使用spring.cloud-nacos.config.import-check.enabled=false关闭配置中心
nacos配置文件分环境,服务,数据集(每个具体的配置文件)
- 名称空间 :区分多套环境 dev ,prod
- group : 区分是哪一种微服务,,比如是order 还是 product
- 数据集 : 配置文件,比如
common.properties,database.properties
- 数据集 : 配置文件,比如
- group : 区分是哪一种微服务,,比如是order 还是 product
配置文件中设置名称空间 spring.cloud.nacos.config.namespace=dev
在导入的使用,,使用 group=xxx 区分是哪一个组,,
在nacos中创建名称空间和配置文件:

yml
spring:
application:
name: service-order
cloud:
nacos:
# 配置注册中心的位置,,, 项目启动就会去注册
server-addr: 127.0.0.1:8848
config:
# 读取的是 这个名称空间的 配置
namespace: dev
config:
# 自动导入这个配置文件 ===》 灵活,统一的导入外部配置源,,, 替代了旧版本中部分零散的配置方式
# 显示指定要导入当前应用中的额外配置源
import:
# 导入 order组的配置
- nacos:common.properties?group=order
- nacos:datasource.properties?group=order
server:
port: 8000
order:
timeout: 111
auto-confirm: 222
配置文件实时更新:
使用@ConfigurationProperties,nacos配置文件改了之后,会实时更新
@Value引入不会实时更新,,但是在类上加上@RefreshScope
NacosConfigManager: 会将nacos拉取的配置融入spring的Environment体系,
NacosConfigManager能获取到ConfigService: 这个是nacos官方提供的底层客户端接口,,负责与Nacos服务建立连接,拉取配置,监听配置变更,,,
监听配置文件变更发送通知:
java
/**
* 项目启动之后执行,,, 在CommandLineRunner后面
* @param nacosConfigManager : nacos配置中心交互的 核心管理类 ---》 封装了底层的配置连接,拉取,监听等核心逻辑,,让你无需手动处理Nacos客户端的创建和管理
* spring应用与 Nacos配置中心之间的 桥梁,统一管理Nacos配置客户端(configService),配置拉取,配置监听等操作
*
* ConfigService : nacos官方提供的底层客户端接口,,, 负责与Nacos服务建立连接,拉取配置,监听配置变更,,,
* NacosConfigManager 可以获取到ConfigService 实例
*
*
* NacosConfigManager 会将从Nacos拉取的配置融入 Spring的Environment体系,,比如@Value,@ConfigurationProperties能读取nacos配置
* @return
*/
@Bean
ApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager){
return new ApplicationRunner() {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("==============");
ConfigService configService = nacosConfigManager.getConfigService();
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("configInfo = " + configInfo);
System.out.println("邮件通知。。。");
}
});
}
};
}
yml中--- 文档分隔符,, 在同一个yml文件中,分隔成多个独立的,完整的yaml文档,,每一个---分隔的部分都是一个独立的yaml结构
解决了,单个文件存储多套配置的问题,,可以将,开发,测试,生产的配置文件都写在一个yml中
nacos在不同的 名称空间 引入不同个数的配置文件,,可以使用---文档分隔符
yml
spring:
application:
name: service-order
cloud:
nacos:
# 配置注册中心的位置,,, 项目启动就会去注册
server-addr: 127.0.0.1:8848
config:
# 读取的是 这个名称空间的 配置 ===> 动态取值 ===> 如果没有配置,,默认是public
namespace: ${spring.profiles.active:public}
profiles:
active: dev
include: feign # 除了dev,,还包含了feign环境
server:
port: 8000
order:
timeout: 111
auto-confirm: 222
# 开启openfeign日志
logging:
level:
com.cj.order.feign: debug
---
# 生产环境 和 开发环境,, 导入的配置文件个数也不一致 ,, --- : 文档分隔符,,每个被分割的部分都是一个独立的yaml,,默认不激活,,满足条件才会激活
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:datasource.properties?group=order
activate:
on-profile: dev
---
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:datasource.properties?group=order
- nacos:xxx.properties?group=order
activate:
on-profile: prod
OpenFeign使用
openfeign是spring cloud中 声明式的http客户端,,用注解和接口来简化微服务之间的远程调用,,
可以重试,兜底,拦截器
开启openfeign远程调用:@EnableFeignClients,用@FeignClient标注发请求的客户端
记录openfeign远程调用日志
在yml中,设置日志级别
yml
# 开启openfeign日志
logging:
level:
com.cj.order.feign: debug
注入日志的bean
java
// feign打印日志
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
设置超时自动中断
服务之间调用,长时间没有相应,,会导致请求积压,服务雪崩,,需要引入超时机制
超时分为 : 连接超时 和 读取超时
yml
spring:
cloud:
openfeign:
client:
config:
# 默认设置
default:
logger-level: full
connect-timeout: 2000
read-timeout: 3000
# 对这个service-product客户端的配置
service-product:
logger-level: full
connect-timeout: 3000
read-timeout: 5000
# 这样配置只针对这个服务有效
# request-interceptors:
# - com.cj.order.interceptor.XTokenRequestInterceptor
feign:
sentinel:
# 开启之后,远程调用失败,,就会自动走,,兜底的方法
enabled: true
重试
openfeign默认不重试,,当时可以配置重试
java
/**
* openfeign 的重试 器,,, 间隔100ms重试一次,最大1s,,重试次数为3,,,, 下一次就会是100*1.5 ,,, 直到1s
* @return
*/
@Bean
Retryer retryer(){
return new Retryer.Default(100, 1, 3);
}
拦截器
分为请求拦截器 和 响应拦截器 ,,, 可以在配置文件中,单独给某个服务配置拦截器,,,也可以将拦截器注入到spring容器,,所有的openfeign请求都会被拦截
java
/**
* 想要让拦截器生效,,第一种方法,,在配置文件中指定拦截器
*/
@Component // 容器中只要有这个RequestInterceptor,,,他就会自动调用上,,每一次请求都会应用这个拦截器
public class XTokenRequestInterceptor implements RequestInterceptor {
/**
*
* @param requestTemplate 这次请求的详细信息,,,如果需要对这次请求进行修改,,直接修改这个RequestTemplate
*/
@Override
public void apply(RequestTemplate requestTemplate) {
System.out.println("xTokenInterceptor,,,,");
// 给请求添加请求头,或者是请求体
requestTemplate.header("X-Token", UUID.randomUUID().toString());
// requestTemplate.header()
}
}
可以用来整个链路共享数据
兜底fallback
需要配合sentinel才能使用默认的兜底策略
引入sentinel:
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
java
/**
* 兜里回调
*/
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
@Override
public Product getProductById(Long id) {
System.out.println("兜底回调");
// 远程失败了,,才会走这里
Product product = new Product();
product.setProductName("未知商品");
return product;
}
}
在openfeign的调用里面,声明fallback:
java
// contextId是客户端的名字,,, 如果不写的话,,客户端名字默认是value值
//@FeignClient(value = "service-product",contextId = "product-feignclient")
@FeignClient(value = "service-product",fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
//
@GetMapping("/product/{id}")
Product getProductById(@PathVariable("id") Long id);
}
openFeign调用外部接口
openfeign不仅能发送服务之间的调用,也能直接对外部的api发送请求
java
// 也可以之前放请求
@FeignClient(value = "weather-client",url = "http://www.baidu.com")
public interface WeatherFeignClient {
@PostMapping("/xxx")
String getWeather(@RequestHeader("Authorization") String auth, @RequestParam("token") String token,@RequestParam("cityId") String cityId);