文章目录
- SpringCloud
- 服务调用的负载均衡
- Ribbon负载均衡
- 面向接口的服务调用
- OpenFeign 客户端
- FeignClient日志输出
- 服务调用的超时设置
- 配置中心
- Nacos配置中心
- Nacos配置中心的使用
- Nacos配置的持久化
SpringCloud
服务调用的负载均衡
- 问题引出
// 服务发现
List<ServiceInstance> instances = discoveryClient.getInstances("服务名");
// 选择一个服务提供者
URI uri = instances.get(0).getUri();
// 向选择的服务提供者发起请求
ResponseEntity<String> response = template.getForEntity(uri.toString() + "/nacos/registry/hello?name={1}", String.class, name);
服务是可以有集群的,在发现了一个服务所有的实例之后,在一次服务调用过程中,我们还需要选择其中一个服务实例,发起调用请求,所以发起调用之前还存在着一个选择过程,这就涉及到了选择的策略问题,该按照何种策略选择出集群中的一个实例呢?
Ribbon负载均衡
- Ribbon是一个客户端负载均衡器,能够给HTTP客户端带来灵活的控制
- 实现的核心功能:就是一组选择策略,帮助我们在一个服务集群中,选择一个服务实例,并向该实例发起调用请求
- 在
Irule
这个接口类下面是所有的负载均衡选择策略
策略 | 实现类 | 描述 |
---|---|---|
随机策略 | RandomRule | 随机选择server |
轮训策略 | RoundRobinRule | 轮询选择 |
重试策略 | RetryRule | 对选定的负载均衡策略(轮训)之上重试机制,在一个配置时间段内当选择服务不成功,则一直尝试使用该策略选择一个可用的服务; |
最低并发策略 | BestAvailableRule | 逐个考察服务,如果服务断路器打开,则忽略,再选择其中并发连接最低的服务 |
可用过滤策略 | AvailabilityFilteringRule | 过滤掉因一直失败并被标记为circuit tripped的服务,过滤掉那些高并发链接的服务(active connections超过配置的阈值) |
响应时间加权重策略 | WeightedResponseTimeRule | 根据server的响应时间分配权重,响应时间越长,权重越低,被选择到的概率也就越低。响应时间越短,权重越高,被选中的概率越高,这个策略很贴切,综合了各种因素,比如:网络,磁盘,io等,都直接影响响应时间 |
区域权重策略 | ZoneAvoidanceRule | 综合判断服务所在区域的性能,和服务的,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server(默认策略) |
- RestTemplate整合Ribbon
- 首先,理论上需要在服务消费者(ribbon-consumer)工程中,添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
- 但是,因为nacos-discovery已经自己整合了ribbon依赖,所以实际上我们并不需要去添加该依赖
- 接着,需要增加Config配置类,并且需要修改RestTemplate的配置类,添加
@LoadBalance
注解
@Configuration public class RestTemplateConfig { @Bean // 开启ribbon的负载均衡 @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
- 然后在使用RestTemplate发起调用的时候,直接使用服务名进行调用即可
@RestController public class ConsumerController { // @Autowired // DiscoveryClient discoveryClient; @Autowired RestTemplate restTemplate; @GetMapping("/ribbon/consumer") public String ribbonDemo() { // 1. 创建restTemplate对象 // RestTemplate restTemplate = new RestTemplate(); // 从容器中去取RestTemplate对象 // 2. 发起请求 String url = "http://ribbon-provider/ribbon/provider?name={1}"; // 3. 接收响应,打印结果 String resp = restTemplate.getForObject(url, String.class, "zs"); return resp; } }
- 服务消费者的配置项
# 应用的端口号 server: port: 8005 # 服务集群的名字 spring.application.name spring: application: name: ribbon-consumer # 注册中心的地址 cloud: nacos: discovery: server-addr: localhost:8848
- 服务提供者同之前的
- 指定Ribbon负载均衡策略
- 方式一:通过配置文件来指定
# 这里的users是我们的服务名称
ribbon-provider:
ribbon:
# 这一行配置的就是实现具体负载均衡策略实现类的全类名
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
- 方式二:通过配置类来指定
- 定义配置类
@Configuration public class RibbonConfig { @Bean public IRule ribbonRule() { // 比如说采用随即策略,它就会对所有的服务集群生效 return new RandomRule(); } }
- 定义Ribbon客户端配置
- 我们自己定义的配置类(比如上面的RibbonConfig配置类),不能被
@ComponentScan
扫描
@Configuration @RibbonClient(name = "ribbon-provider", configuration = RibbonConfig.class) public class ProviderClientConfig { }
- 自定义Ribbon的负载均衡策略
public class MyRibbonRule extends AbstractLoadBalancerRule {
// 初始化配置的方法,可以忽略
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
// 选择策略
@Override
public Server choose(Object o) {
// 1. 获取服务实例列表
/**
* getReachableServers():获取可达的服务列表
* getAllServers():获取所有的服务列表
*/
List<Server> serverList = getLoadBalancer().getReachableServers();
// 2. 根据自定义策略选择其中的一个服务实例
Server server = serverList.get(0);
return server;
}
}
@Configuration
public class RibbonConfig {
@Bean
public IRule ribbonRule() {
// 采用自定义的负载均衡策略
return new MyRibbonRule();
}
}
面向接口的服务调用
- 问题引出
- 代码同上
- 我们会发现,因为我们是使用RestTemplate这个Http客户端发起的Http协议的服务调用请求,因此在发起请求的时候,我们得自己构建请求url,请求参数,获取响应体数据等等,导致我们的代码和Restful风格的Http请求紧密耦合。
- 那么有没有办法,让我们在服务调用的时候与Restful的请求“解耦”,直接以Java代码中接口调用的方式,来完成服务的调用呢?
OpenFeign 客户端
- 功能:OpenFeign就可以帮助我们实现,让服务调用代码与Restful风格的Http请求解耦的功能。
- openFeign其实是一个翻译的角色,帮助我们将方法翻译为http请求
- 主要流程:(以
http://feign-provider/feign/provider?name=xxx
为例)- 以openFeign调用feignDemo这个方法为例
- 获取类上面的FeignClient中的注解的值,这个值就是服务的名字
- 获取方法上的注解以及注解的值,注解的类型决定了http的请求类型,注解中的值决定了请求的路径
- 获取方法中参数的名字,其实就是方法参数中的注解的值
- 获取方法的参数值,其实就是url后面跟的参数值
- 在FeignClient中定义的方法和远程的目标方法名没有必要保持一致
- 以openFeign调用feignDemo这个方法为例
- OpenFeign的使用
- 在服务消费者中导入依赖
<!-- openFeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
- 服务消费者,需要以面向接口的方式调用其他服务,需要用到OpenFeign,所以需要定义Feign客户端
@FeignClient(name = "feign-provider") public interface ProviderClient { @GetMapping("/feign/provider") public String feignDemo(@RequestParam("name") String name); @GetMapping("/feign/provider/stu") public Student feignDemo2(); }
- 在启动类上加注解
@EnableFeignClients
,才能让我们定义的FeignClient生效
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class FeignConsumerApplication { public static void main(String[] args) { SpringApplication.run(FeignConsumerApplication.class, args); } }
- 使用接口类
@RestController public class FeignDemoController { @Autowired ProviderClient providerClient; @GetMapping("/feign/consumer") public String demo() { // 使用openFeign远程调用,发起Http请求,调用接口 // 1. 导入openFeign包 // 2. 在启动类上添加注解@EnableFeignClients,表示使用openFeign // 3. 声明一个接口,接口中的方法和目标服务的暴露方法一致 // 4. 调用 String resp = providerClient.feignDemo("ls"); // http://feign-provider/feign/provider?name={name} return resp; } @GetMapping("/feign/student") public String demo1() { Student student = providerClient.feignDemo2(); System.out.println("student = " + student); return "ok"; } }
FeignClient日志输出
- 当我们调用FeignClient发出请求的时候,如果我们希望能看到其发出的具体Http请求,我们可以通过配置来实现。
- 在配置文件中实现
# 这里的xxx表示我们自己的定义的FeignClient所在包的包名(比如: # com.coo1heisenberg.feign.consumer.client) logging: level: xxx: debug
- 在配置类中,配置FeignConfig,指定日志输出级别
@Configuration public class FeignConfig { @Bean public Logger.Level logLevel() { return Logger.Level.FULL; } }
服务调用的超时设置
- 通常,一次远程调用过程中,服务消费者不可能无限制的等待服务提供者返回的结果,正常情况下,服务提供者的一次调用执行过程也不会执行很长时间(除非出现网络故障,或者服务提供者宕机等问题),所以为防止,在非正常情况下服务消费者在调用过程中的长时间阻塞等待,对于一次服务调用过程,我们会设置其超时时间。一次服务调用,超时未返回即认为调用失败。在使用Feign的时候,我们可以配置其超时时间。
# 设置openFeign的超时时间
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
配置中心
类似于注册中心,配置中心的实现也有多种,而Nacos同时也实现了配置中心的角色。
- 在启动的时候拉取配置,当配置中心中的配置发生变化的时候,服务可以实时的感知到配置的变化
- 使用配置中心可以以中心化、外部化和动态化(动态化即可以实时刷新配置)的方式管理所有环境的应用配置和服务配置。
- 动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。
Nacos配置中心
- 注册中心的配置信息基本认识
- 配置中心中的配置,主要是以键值对的形式存在的,即每条配置都以key-value的形式存储,key是配置的名称,value才是配置的值
- 所以,很明显,不同配置的key值应该有所区别,或者即使key值相同,我们也应该有办法区分他们,即给key值划分不同的维度
- Nacos的基本概念
- 配置项: 一个具体的可配置的参数与其值域,通常以 param-key=param-value 的形式存在。例如我们常配置系统的日志输出级别(logLevel=INFO|WARN|ERROR) 就是一个配置项。
- 配置集:一组相关或者不相关的配置项的集合称为配置集。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置,每一个配置集都对应一个唯一的DataId,DataId必须由我们自己定义。
- 配置分组: Nacos 中的一组配置集,是组织配置的维度之一,每一个分组都有一个唯一的组名,如果我们未定义,则默认使用DEFAULT-GROUP分组
- 命名空间: 用于进行用户粒度的配置隔离,每一个命名空间都有一个唯一的Id值,如果我们未定义,则默认使用public命名空间
-
配置项中的key值,以及配置分组的组名都由我们自己根据场景去定义
-
命名空间的Id值,在我们定义命名空间的时候,由Nacos帮我们生成
-
- 在一个服务启动的时候,默认读取的配置集id即data_id和该服务的配置有关,按照如下公式计算:
${prefix}-${spring.profiles.active}.${file-extension}
-
prefix
默认为spring.application.name
的值,也可以通过配置项spring.cloud.nacos.config.prefix
来配置。 -
spring.profiles.active
即为当前环境对应的 profile,注意:当spring.profiles.active
为空时,对应的连接符-
也将不存在,dataId 的拼接格式变成${prefix}.${file-extension}
-
file-exetension
为配置内容的数据格式,可以通过配置项spring.cloud.nacos.config.file-extension
来配置。目前只支持properties
和yaml
类型。 -
这里要主要,我们的SpringBoot项目在启动的时候,就会根据
${prefix}-${spring.profiles.active}.${file-extension}
生成配置集名称,并自动去读取该配置集名称对应的配置。
Nacos配置中心的使用
- nacos-config子工程中添加如下依赖
<dependencies>
<!-- springboot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos 配置中心的依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
</dependencies>
- 在nacos-config的目录下添加两个配置文件,分别是
bootstrap.yml
和application.yml
,在bootstrap配置文件中必须有应用名,以及nacos服务器地址等,而在application.yml配置文件中,配置自己项目中的其他配置
# bootstrap.yml文件
# nacos相关的配置都放到bootstrap.yml中
# nacos配置中心的配置
spring:
cloud:
nacos:
config:
# 配置中心的地址
server-addr: localhost:8848
# 配置中心中配置集的类型
file-extension: yaml
# 默认是 DEFAULT_GROUP
group: test_group
# 默认是public,配置命名空间的id
namespace: d6dabccd-c9fa-47d3-93a8-526885ab302a
# 共享的配置
shared-configs:
- data-id: common.yaml
group: test_group
refresh: true # 共享配置也支持动态刷新
# ${prefix}-${spring.profiles.active}.${file-extension}
# nacos-config
application:
name: nacos-config-test
# 多环境配置 (dev / test / prod)
profiles:
active: prod
# {spring.application.name}-{spring.profiles.active}.{file-extension}
# dataId:nacos-config-dev.yaml
# application.yml文件
server:
port: 8080
- 在nacos中添加配置集
- 测试代码
@RefreshScope // 实现配置的动态刷新
@RestController
public class testController {
@Value("${coo1heisenberg.name}")
String name;
@GetMapping("/test")
public String test() {
return name + " hello nacos";
}
}
Nacos配置的持久化
- 在Nacos服务器上写入的配置,会被持久化保存到Nacos自带的一个嵌入式数据库derby中,因此当我们重启Nacos之后,仍然可以看到之前的配置信息
- 使用嵌入式数据库实现数据的存储,不方便观察数据存储的基本情况,因此,Nacos还支持将配置信息写入Mysql中
- 在数据库中,创建名为nacos的数据库
- 在nacos数据库中,执行数据库初始化文件:
nacos-mysql.sql
(改文件在conf目录下已经提供) - 修改conf/application.properties文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。
spring.datasource.platform=mysql db.num=1 # 这里的url要改成你自己的mysql数据库地址,并在你的mysql中创建名为nacos的数据库 db.url.0=jdbc:mysql://11.162.196.16:3306/nacos? characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true # 这里要改成你自己登录mysql的用户名和密码 db.user.0=nacos_devtest db.password.0=youdontknow
- 在配置了mysql数据库之后,我们会发现,之前配置中心的配置信息全部消失了,那是因为我们之前使用的是nacos的内嵌数据库derby,现在切换到mysql之后数据存储在nacos这个数据库中,而该数据库现在是没有数据的。