Spring Cloud Alibaba之集成负载均衡Ribbon与声明式HTTP客户端Feign
使用Spring Cloud Alibaba
添加SpringBoot、SpringCloud、SpringCloudAlibaba版本依赖整合,在dependencyManagement添加核心依赖,在dependencies添加需使用的依赖
spring-cloud-alibaba版本说明: https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<!--整合spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
负载均衡Ribbon
spring-cloud-starter-alibaba-nacos-discovery依赖已经包含了Ribbon,无需再引入Ribbon依赖
开启负载均衡
在RestTemplate 的生成方法上添加@LoadBalanced
注解
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
RestTemplate template = new RestTemplate();
return template;
}
使用RestTemplate,同时断点测试
@GetMapping("test/{id}")
public UserDTO selectUserById(@PathVariable Integer id) {
UserDTO userDTO=this.restTemplate.getForObject("http://user-center/users/selectUserById/{id}", UserDTO.class,id);
return userDTO;
}
Ribbon的核心组成
接口 | 作用 | 默认值 |
---|---|---|
IClientConfig | 读取配置 | DefaultClientConfigImpl |
IRule | 负载均衡规则,选择实例 | ZoneAvoidanceRule |
IPing | 筛选掉ping不通的实例 | DummyPing |
ServerList<T extends Server> |
交给Ribbon的实例列表 | Ribbon: ConfigurationBasedServerList
Spring Cloud Alibaba: NacosServerList |
ServerListFilter<T extends Server> | 过滤掉不符合条件的实例 | ZonePreferenceServerListFilter |
ILoadBalancer | Ribbon的入口 | ZoneAwareLoadBalancer |
ServerListUpdater | 更新交给Ribbon的List的策略 | PollingServerListUpdater |
内置负载均衡策略
Ribbon内置了多种负载均衡策略,内部负载均衡的顶级接口为IRule
规则名称 | 策略描述 | 实现说明 |
---|---|---|
AvailabilityFilteringRule | 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(activeconnections 超过配置的阈值) | 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态 |
BestAvailableRule | 选择一个最小的并发请求的server | 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server |
NacosRule | 优先寻找与自己同集群的服务,本集群内会随机查询 | |
RandomRule | 随机选择一个server | 在index上随机,选择index对应位置的server |
ResponseTimeWeightedRule | 已废弃,作用同WeightedResponseTimeRule | |
RetryRule | 对选定的负载均衡策略机上重试机制。 | 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server |
RoundRobinRule | 轮询方式轮询选择server | 轮询index,选择index对应位置的server |
WeightedResponseTimeRule | 根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。 | 一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成statas时,使用roubine策略选择server。 |
ZoneAvoidanceRule | 复合判断server所在区域的性能和server的可用性选择server | 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。 |
自定义Ribbon策略
@Slf4j
public class NacosWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Autowired
private NacosServiceManager nacosServiceManager;
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// 读取配置文件,并初始化NacosWeightedRule,参考其他实现,通常不做任何处理
}
@Override
public Server choose(Object key) {
try {
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
// 请求的微服务名称
String name = loadBalancer.getName();
// 拿到服务发现相关API
NamingService namingService = this.nacosServiceManager.getNamingService(this.nacosDiscoveryProperties.getNacosProperties());
// 基于权重的负载均衡算法选择一个实例
Instance instance = namingService.selectOneHealthyInstance(name);
log.info("选择实例:port = {}, instance = {}", instance.getPort(), instance);
return new NacosServer(instance);
} catch (NacosException e) {
return null;
}
}
}
自定义Ribbon配置
代码配置方式
配置负载均衡策略
// 注意父子上下文,不能被启动类扫描
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
@Bean
public IPing ping(){
return new PingUrl();
}
}
针对某服务生效
@Configuration
@RibbonClient(name="user-center", configuration=RibbonConfiguration.class)
public class UserCenterRibbonConfiguration {
}
全局生效
@Configuration
@RibbonClients(defaultConfiguration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration {
}
属性配置方式
服务实例.ribbon.属性:某类策略实现
public static final IClientConfigKey<String> NFLoadBalancerRuleClassName = new CommonClientConfigKey<String>("NFLoadBalancerRuleClassName"){};
public static final IClientConfigKey<String> NFLoadBalancerPingClassName = new CommonClientConfigKey<String>("NFLoadBalancerPingClassName"){};
public static final IClientConfigKey<Integer> NFLoadBalancerPingInterval = new CommonClientConfigKey<Integer>("NFLoadBalancerPingInterval"){};
public static final IClientConfigKey<Integer> NFLoadBalancerMaxTotalPingTime = new CommonClientConfigKey<Integer>("NFLoadBalancerMaxTotalPingTime"){};
public static final IClientConfigKey<String> NFLoadBalancerStatsClassName = new CommonClientConfigKey<String>("NFLoadBalancerStatsClassName"){};
针对服务调整Ribbon的负载均衡策略
user-center:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
NFLoadBalancerPingClassName: com.netflix.loadbalancer.PingUrl
全局配置
# ribbon配置
ribbon:
eager-load:
# 开启饥饿加载; 默认false:第一次通过ribbon调用服务速度较慢
enabled: true
# 只能那些服务开启
clients: user-center
声明式HTTP客户端Feign
Feign的概述
Feign是Spring Cloud提供的一个声明式的伪Http客户端, 它使得调用远程服务就像调用本地服务
一样简单, 只需要创建一个接口并添加一个注解即可。
Nacos很好的兼容了Feign, Feign默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负
载均衡的效果。
Feign的使用
添加Feign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动类添加@EnableFeignClients开启Feign功能
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class PayApplication {
public static void main(String[] args) {
SpringApplication.run(PayApplication .class, args);
}
}
创建Feign接口
@FeignClient(name = "user-center") // 声明调用的提供者的name
public interface UserCenterFeignClient {
// 指定调用提供者的哪个方法
// @FeignClient+@GetMapping 就构成了一个完整的请求路径 http://user-center/users/selectUserById/{id}
@GetMapping("/users/selectUserById/{id}")
UserDTO selectUserById(@PathVariable Integer id);
}
调用Feign接口
@RestController
@Slf4j
public class TestController {
private final UserCenterFeignClient userCenterFeignClient;
@GetMapping("test/{id}")
public UserDTO selectUserById(@PathVariable Integer id) {
// 通过fegin调用微服务
UserDTO userDTO = this.userCenterFeignClient.selectUserById(userId);
}
}
访问http://192.168.179.1:8082/test/1
{
"userName": "小白",
"age": 20,
"sex": "男",
}
Feign的组成
接口 | 作用 | 默认值 |
---|---|---|
Feign.Builder | Feign的入口 | Feign.Builder |
Client | Feign底层用什么去请求 | 和Ribbon配合时 :LoadBalancerFeignClient
不和Ribbon配合时feign.Client.Default |
Contract | 契约,注解支持 | SpringMvcContract |
Encoder | 编码器,用于将对象转换成HTTP请求消息体 | SpringEncoder |
Decoder | 解码器,将响应消息体转换成对象 | ResponseEntityDecoder |
Logger | 日志管理器 | SIf4jLogger |
RequestInterceptor | 用于为每个请求添加通用逻辑 | 无 |
Feign的日志级别配置
级别 | 打印内容 |
---|---|
NONE (默认值) | 不记录任何日志 |
BASIC | 仅记录请求方法、URL、响应状态代码以及执行时间 |
HEADERS | 记录BASIC级别的基础上,记录请求和响应的header |
FULL | 记录请求和响应的header、hbody和元数 |
代码配置方式
细腻度配置
/**
* feign的配置类
*/
//@Configuration 如果添加该注解,则必须移动到@ComponentScan能扫描的包以外,不能被启动类扫描,否则将出现父子容器嵌套异常
public class GlobalFeignConfiguration {
@Bean
public Logger.Level level(){
return Logger.Level.FULL;
}
}
定义多个FeignConfiguration配置,针对不同服务使用不同的FeignConfiguration对象
@FeignClient(name = "user-center", configuration = GlobalFeignConfiguration.class)
public interface UserCenterFeignClient {
/**
* http://user-center/users/selectUserById/{id}
*
* @param id
* @return
*/
@GetMapping("/selectUserById/{id}")
UserDTO selectUserById(@PathVariable Integer id);
}
配置文件添加配置
logging:
level:
cn.ybzy.paycenter.feignclient.GlobalFeignConfiguration: debug
全局配置
@SpringBootApplication
@EnableFeignClients(defaultConfiguration = GlobalFeignConfiguration.class)
public class PayApplication {
public static void main(String[] args) {
SpringApplication.run(PayApplication .class, args);
}
}
属性配置方式
细腻度配置
feign:
client:
config:
# 调用服务实例名称
user-center:
loggerLevel: full
全局配置
feign:
client:
config:
# 全局配置
default:
loggerLevel: full
Feign的其他可选配置
代码方式配置项
配置项 | 作用 |
---|---|
Logger.Level | 指定日志级别 |
Retryer | 指定重试策略 |
ErrorDecoder | 指定错误解码器 |
Request.Options | 超时时间 |
Collection | 拦截器 |
SetterFactory | 用于设置Hystrix的配置属性,Feign整合Hystrix才会用 |
属性方式配置项
feign:
client:
config:
# 全局配置
default:
# 连接超时时间
connectTimeout: 2000
# 连接超时时间
connectTimeout: 5000
# 读取超时时间
readTimeout: 5000
# 日志级别
loggerLevel: full
# 错误解码器
errorDecoder: x.x.errorDecoder
# 重试策略
retryer: x.x.SimpleRetryer
requestInterceptors:
- x.x.FooRequestInterceptor # 拦截器
# 是否对404错误码解码
decode404: false
encoder: x.x.SimpleEncoder
# 编码器
decoder: x.x.SimpleDecoder
# 契约
contract: x.x.SimpleContract
Feign的优化
使用连接池与设置合理的日志级别可对Feign进行一定的优化
Feign相比RestTemplate而言,只有其50%左右的性能,默认情况下,Feign是使用URLConnection去请求,是没有连接池的,实际上Feign是支持使用连接池的。
httpclient
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
具体连接数需进行相应压测结果配置优化
feign:
httpclient:
# 让feign使用apache httpclient做请求;而不是默认的urlconnection
enabled: true
# feign的最大连接数
max-connections: 100
# feign单个路径的最大连接数
max-connections-per-route: 20
okhttp
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
feign:
okhttp:
enabled: true
Feign实现Token的传递
使用@RequestHeader
@GetMapping("test/{id}")
// 接受Token
public UserDTO selectUserById(@PathVariable Integer id, @RequestHeader("Token") String token) {
UserDTO userDTO = userCenterFeignClient.selectUserById(id, token);
return userDTO;
}
@FeignClient(name = "user-center",
// fallback = UserCenterFeignClientFallback.class,
fallbackFactory = UserCenterFeignClientFallbackFactory.class
)
public interface UserCenterFeignClient {
@GetMapping("/users/{id}")
// 传输、发送Token
UserDTO selectUserById(@PathVariable Integer id, @RequestHeader("Token") String token);
}
Interceptor拦截器
public class TokenRequestIntecepor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 获取token
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("Token");
// token传递
if (StringUtils.isNotBlank(token)) {
template.header("Token", token);
}
}
}
feign:
client:
config:
# 全局配置
default:
loggerLevel: full
requestInterceptors:
- cn.ybzy.demo.feignclient.interceptor.TokenRequestIntecepor
RestTemplate实现Token的传递
exchange()
@GetMapping("test/{id}")
public ResponseEntity<UserDTO> selectUserById(@PathVariable Integer id, HttpServletRequest request) {
System.out.println(id);
String token = request.getHeader("Token");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Token", token);
ResponseEntity<UserDTO> responseEntity = this.restTemplate.exchange("http://user-center/selectUserById/{id}",
HttpMethod.GET,
new HttpEntity<>(httpHeaders),
UserDTO.class,
id
);
return responseEntity;
}
ClientHttpRequestInterceptor
public class RestTemplateTokenInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest httpRequest = attributes.getRequest();
String token = httpRequest.getHeader("Token");
HttpHeaders headers = request.getHeaders();
headers.add("Token", token);
return execution.execute(request, body);
}
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
RestTemplate template = new RestTemplate();
template.setInterceptors(
Collections.singletonList(
new RestTemplateTokenInterceptor()
)
);
return template;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137006.html