Spring Cloud Alibaba之集成负载均衡Ribbon与声明式HTTP客户端Feign

生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

导读:本篇文章讲解 Spring Cloud Alibaba之集成负载均衡Ribbon与声明式HTTP客户端Feign,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

使用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
ILoadBalancerRibbon的入口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

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!