章节目标
基于前一章节: (一)SpringCloudAlibaba入门-注册中心
上面的章节已完成了一个服务提供方的模块的搭建,本章节将演示完成消费端服务的搭建。
功能:
- 在该模块中引入服务发现nacos,用于发现注册中心可用服务
- 引入openfeign用于完成远程服务的调用
- 分析loadbalancer负载均衡策略,以及如何修改策略
涉及框架
- nacos服务发现
- openfiegn,负责远程服务调用
- loadbanlancer,openfeign依赖其完成服务调用过程中的负载均衡,即选择具体调用服务实例
- 基础的spring web能力
准备:服务提供方添加一个远程接口
在demo-provider-service模块中新增一个ProviderController来提供对外服务
GET /provider/remoteService
@RestController
public class ProviderController {
@Value("${server.port}")
private int serverPort;
@RequestMapping("/invokeRemoteService")
public String invokeRemoteService(String say){
return providerServiceFeign.remoteService(say);
}
}
搭建(一):创建demo-consumer-service模块
- pom.xml,中引入
- spring-cloud-starter-alibaba-nacos-discovery
- spring-cloud-starter-openfeign
- spring-cloud-starter-loadbalancer
<dependencies>
<!-- nacos服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- openfeign 需要依赖loadbalancer进行负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- spring web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- application.yml
server:
port: 8081
spring:
application:
name: consumer-service
cloud:
nacos:
discovery:
enabled: true
username: nacos
password: nacos
server-addr: 127.0.0.1:8848
- 启动类ConsumerApplication
package demo.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 默认扫描当前启动类所在包下所有@FeignClient修饰的接口
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
搭建(二):新建Feign接口
package demo.consumer.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "provider-service")
public interface ProviderServiceFeign {
/**
* 调用远程接口<br>
* 注意:
* url:必须是完整的目标服务访问路径,如果在接口上面也定义了{@link RequestMapping}则与Spring web规则一致,否则必须是全路径。
* 关于注解{@link RequestParam},在feign定义必须明确声明name属性
* @param say
* @return
*/
@RequestMapping(value = "/provider/remoteService")
String remoteService(@RequestParam(name="say") String say);
}
关键点说明:
- @FeignClient注解的name属性指定了服务名称:provider-service
- 接口地址url必须是完整路径,这里完整路径规则与Spring Web中@RequestMapping规则一致,只是这里因为在接口类上无@RequestMapping(value="/provider"),所以在方法上需要指定完整的全路径。
- @RequestParam:在服务端作为参数声明时,可以不明确声明name值,即参数名与形参一致。但是在feign定义中,即便是隐式一致,还是需要明确声明name值。否则将会报错。
搭建(三):建立一个消费端服务进行远程调用
新建ConsumerController.java,用于触发对远程接口的调用发起
package demo.consumer;
import demo.consumer.feign.ProviderServiceFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private ProviderServiceFeign providerServiceFeign;
@RequestMapping("/invokeRemoteService")
public String invokeRemoteService(String say){
return providerServiceFeign.remoteService(say);
}
}
搭建(四):结果验证
- 服务端运行状态验证
首先启动我们的demo-provider-service。
可以浏览器访问:http://localhost:8080/provider/remoteService?say=hello
展现如下效果,即服务端正常。
- 消费端调用服务验证
启动demo-consumer-service。
浏览器访问:http://localhost:8081/consumer/invokeRemoteService?say=i am consumer
至此,远程调用搭建成功!以下内容为扩展知识,可选了解!但是建议了解,知其然并知其所以然。
拓展延伸
在上述过程中存在如下几个问题,值得剖析:
- 当我们一个服务有多个注册实例在线时,那么在feign调用时负载均衡策略是什么样的?
- 负载均衡策略如何修改?
拓展1:默认的负载均衡策略是什么?
先上结论:RoundRobinLoadBalancer,轮训策略
以下为分析过程。
此处追溯涉及Spring Boot的自动装配原理,不在本章讨论范围内,暂时忽略。直达主题,根据如下代码,可以知晓feign进行调用的时候,需要首先选取出一个目标调用目标地址。
- 负载均衡入口
org.springframework.cloud.loadbalancer.blocking.client.
BlockingLoadBalancerClient#execute(java.lang.String,
org.springframework.cloud.client.loadbalancer.LoadBalancerRequest<T>)
继续向下
我们查看这个变量loadBalancer具体实现种类可知
目前一共有三种负载均衡策略
- com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer
- org.springframework.cloud.loadbalancer.core.RandomLoadBalancer
- org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer
同时根据包名可知:RandomLoadBalancer、RoundRobinLoadBalancer为spring-cloud-loadBalancer提供,而NacosLoadBalancer则是因为我们使用的是nacos服务注册与发现由nacos所提供。
那么到底目前默认的是哪种负载均衡策略呢?
代码关键点
org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration
public class LoadBalancerClientConfiguration {
private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;
@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
... ...
}
所以,我们最终得到结论默认负载均衡策略为RoundRobinLoadBalancer,也就是轮训策略。
拓展2:如何修改负载均衡策略?
这里我们参照SpringCloud官方文档说明
https://docs.spring.io/spring-cloud-commons/docs/4.0.4/reference/html/#eager-loading-of-loadbalancer-contexts
核心重点
- 一个自定义的LoadBalancerConfiguration类
- @LoadBalancerClient或@LoadBalancerClients中指定配置类
官方提示:
其中自定义的LoadBalancerConfiguration类,不要被@Configuration修饰并且不要可以被包扫描到。解释说明:配置类不要被默认规则将配置内容注入到IOC中,可能会造成混乱。
原文:
接下来,案例演示自定义负载均衡策略:
需求:
- 针对项目demo-consumer-service
- 将对服务provider-service进行随机策略
- 其他服务保持轮训策略
配置步骤
- 需要新增两个LoadBalancerConfiguration类
这里分别定义为
- DefaultLoadBalancerConfiguration:用于默认配置
- ProviderServiceLoadBalancerConfiguration:用于对服务provider-service的个性配置
在配置类中添加服务的负载均衡策略
public class DefaultLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
System.out.println(this.getClass().getName()+":"+name);
return new RoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
public class ProviderServiceLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
System.out.println(this.getClass().getName()+":"+name);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
- 通过@LoadBalancerClient或@LoadBalancerClients使用指定配置类
在ComsumerApplication启动类(当然也可以是其他@Configuration类)进行配置。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 默认扫描当前启动类所在包下所有@FeignClient修饰的接口
@LoadBalancerClients(value = {
@LoadBalancerClient(name = "provider-service", configuration = {
ProviderServiceLoadBalancerConfiguration.class
})
},
defaultConfiguration = DefaultLoadBalancerConfiguration.class
)
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
注意:
每个测试类在注入负载均衡策略时增加了@ConditionalOnMissingBean,千万不要丢掉,否则配置可能与预期不符甚至报错。
本文暂时没有评论,来添加一个吧(●'◡'●)