SpringCloud之 LoadBalancer和Feign负载均衡 Posted 2023-04-01 一只咸鱼。。
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud之 LoadBalancer和Feign负载均衡相关的知识,希望对你有一定的参考价值。
文章目录
提示:以下是本篇文章正文内容,SpringCloud 系列学习将会持续更新
LoadBalancer 负载均衡
前面我们讲解了如何对服务进行拆分、如何通过 Eureka 服务器进行服务注册与发现,那么现在我们来看看,它的负载均衡到底是如何实现的,实际上之前演示的负载均衡是依靠 LoadBalancer 实现的。
在2020年前的 SpringCloud 版本是采用 Ribbon 作为负载均衡实现,但是2020年的版本之后SpringCloud 把 Ribbon 移除了,进而用自己编写的 LoadBalancer 替代。
那么,负载均衡是如何进行的呢?
一、@LoadBalanced 负载均衡
我们之前注册 RestTemplate 时,就用 @LoadBalanced
注解进行了修饰:
@Configuration
public class BeanConfiguration
@Bean
@LoadBalanced
public RestTemplate getRestTemplate ( )
return new RestTemplate ( ) ;
🌽①观察负载均衡现象
a. 我们有2个 UserApplication 的实例,可以在控制层的某个服务调用代码中添加日志 打印:当前实例的IP:PORT
@RestController
@Slf4j
public class UserController
@Resource
private UserService userService;
@Resource
Environment environment;
@GetMapping ( "/user/uid" )
public User findUserById ( @PathVariable ( "uid" ) int uid) throws UnknownHostException
String hostIp = InetAddress . getLocalHost ( ) . getHostAddress ( ) ;
String port = environment. getProperty ( "server.port" ) ;
log. info ( hostIp + ":" + port + " 的findUserById()被访问了!" ) ;
return userService. getUserById ( uid) ;
b. 然后重新启动。我们多次访问http://localhost:8082/borrow/3
,其中 BorrowService 就会进行远程调用 UserService ,这时我们通过查看 UserApplication-1 和 UserApplication-2 的控制台日志,就可以发现它们是被轮循调用 的。 这样,服务自动发现以及简单的负载均衡就实现完成了,并且,如果某个微服务挂掉了,只要存在其他同样的微服务实例在运行,那么就不会导致整个微服务不可用,极大地保证了安全性。
🌽②@LoadBalanced 源码剖析
实际上,在添加 @LoadBalanced 注解之后,会启用LoadBalancerInterceptor
拦截器对我们发起的服务调用请求进行拦截(只针对我们发起的请求)。
它实现ClientHttpRequestInterceptor
接口:
@FunctionalInterface
public interface ClientHttpRequestInterceptor
ClientHttpResponse intercept ( HttpRequest request, byte [ ] body, ClientHttpRequestExecution execution) throws IOException ;
LoadBalancerInterceptor
类对 intercept()方法 的实现:
public ClientHttpResponse intercept ( final HttpRequest request, final byte [ ] body, final ClientHttpRequestExecution execution) throws IOException
URI originalUri = request. getURI ( ) ;
String serviceName = originalUri. getHost ( ) ;
Assert . state ( serviceName != null , "Request URI does not contain a valid hostname: " + originalUri) ;
return ( ClientHttpResponse ) this . loadBalancer. execute ( serviceName, this . requestFactory. createRequest ( request, body, execution) ) ;
我们可以打个断点看看实际是怎么在执行的,可以看到: 服务端会在发起请求时执行这些拦截器。它们会去找 Eureka 获取真正需要访问的主机名称 。
我们来看看BlockingLoadBalancerClient
类对 loadBalancer.execute()方法 的具体实现:
public < T > T execute ( String serviceId, LoadBalancerRequest < T > request) throws IOException
String hint = this . getHint ( serviceId) ;
LoadBalancerRequestAdapter < T , DefaultRequestContext > lbRequest = new LoadBalancerRequestAdapter ( request, new DefaultRequestContext ( request, hint) ) ;
Set < LoadBalancerLifecycle > supportedLifecycleProcessors = this . getSupportedLifecycleProcessors ( serviceId) ;
supportedLifecycleProcessors. forEach ( ( lifecycle) ->
lifecycle. onStart ( lbRequest) ;
) ;
ServiceInstance serviceInstance = this . choose ( serviceId, lbRequest) ;
if ( serviceInstance == null )
supportedLifecycleProcessors. forEach ( ( lifecycle) ->
lifecycle. onComplete ( new CompletionContext ( Status . DISCARD, lbRequest, new EmptyResponse ( ) ) ) ;
) ;
throw new IllegalStateException ( "No instances available for " + serviceId) ;
else
return this . execute ( serviceId, serviceInstance, lbRequest) ;
所以,实际上在进行负载均衡的时候,会向 Eureka 发起请求,选择一个可用的对应服务,然后会返回此服务的主机地址等信息:
回到目录…
二、自定义负载均衡
LoadBalancer 默认提供了两种负载均衡策略:
RandomLoadBalancer
- 随机分配策略RoundRobinLoadBalancer
- (默认)轮询分配策略
现在我们希望修改默认的负载均衡策略,可以进行指定,比如我们现在希望用户服务采用随机分配策略 。
①我们需要先创建随机分配策略的配置类 (不用加@Configuration):
public class LoadBalancerConfig
@Bean
public ReactorLoadBalancer < ServiceInstance > randomLoadBalancer ( Environment environment, LoadBalancerClientFactory loadBalancerClientFactory)
String name = environment. getProperty ( LoadBalancerClientFactory . PROPERTY_NAME) ;
return new RandomLoadBalancer ( loadBalancerClientFactory. getLazyProvider ( name, ServiceInstanceListSupplier . class ) , name) ;
②接着我们需要为对应的服务指定负载均衡策略 ,直接使用注解即可:
@Configuration
@LoadBalancerClient ( value = "userservice" , configuration = LoadBalancerConfig . class )
public class BeanConfiguration
@Bean
@LoadBalanced
public RestTemplate getRestTemplate ( )
return new RestTemplate ( ) ;
接着我们在BlockingLoadBalancerClient
中添加断点,观察是否采用我们指定的策略进行请求: 发现访问 userservice 服务的策略已经更改为我们指定的策略了。
回到目录…
三、OpenFeign 实现负载均衡
官方文档:https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/
Feign 是⼀个声明式的 HTTP 客户端组件,它旨在是编写 Http 客户端变得更加容易。 OpenFeign 同时集成了 Spring Cloud LoadBalancer
和 Spring Cloud CircuitBreaker
,提供负载均衡 和熔断降级 的功能。 Feign 默认的负载均衡策略是轮询调用 。
🍆①添加依赖
< dependency>
< groupId> org.springframework.cloud</ groupId>
< artifactId> spring-cloud-starter-openfeign</ artifactId>
</ dependency>
🍆②启动类添加 @EnableFeignClients
@SpringBootApplication
@EnableFeignClients
public class BorrowApplication
public static void main ( String [ ] args)
SpringApplication . run ( BorrowApplication . class , args) ;
那么现在我们需要调用其他微服务提供的接口,该怎么做呢?
🍆③创建客户端接口 UserClient
我们的客户端接口需要用 @FeignClient
注解来指定向哪个微服务发送 HTTP 请求。
@FeignClient ( "userservice" )
public interface UserClient
我们之前的远程调用:
RestTemplate template = new RestTemplate ( ) ;
User user = template. getForObject ( "http://userservice/user/" + uid, User . class ) ;
现在直接在客户端接口中写入控制层的方法:
@FeignClient ( "userservice" )
public interface UserClient
@RequestMapping ( "/user/uid" )
User getUserById ( @PathVariable ( "uid" ) int uid) ;
🍆④service业务中调用客户端接口
我们直接注入使用(有 Mybatis 那味了):
@Service
public class BorrowServiceImpl implements BorrowService
@Resource
private BorrowMapper borrowMapper;
@Resource
private UserClient userClient;
@Resource
private BookClient bookClient;
@Override
public UserBorrowView getBorrowViewByUid ( int uid)
List < Borrow > borrowList = borrowMapper. getBorrowsByUid ( uid) ;
User user = userClient. findUserById ( uid) ;
List < Book > bookList = borrowList
. stream ( )
. map ( b -> bookClient. findBookById ( b. getBid ( ) ) )
. collect ( Collectors . toList ( ) ) ;
return new UserBorrowView ( user, bookList) ;
继续访问进行测试: OK,正常。
当然,Feign 也有很多的其他配置选项,这里就不多做介绍了,详细请查阅官方文档。
回到目录…
总结 : 提示:这里对文章进行总结: 本文是对SpringCloud的学习, 了解了LoadBalancer 负载均衡策略的内部流程,学习了如何自定义负载均衡策略,并且学习了使用OpenFeign实现负载均衡。之后的学习内容将持续更新!!!
SpringCloud之Ribbon
一:Ribbon是什么?
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随即连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。
二:LB方案分类
目前主流的LB方案可分成两类:一种是集中式LB, 即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;另一种是进程内LB,将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于后者,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
三:Ribbon的主要组件与工作流程
Ribbon的核心组件(均为接口类型)有以下几个:
ServerList:用于获取地址列表。它既可以是静态的(提供一组固定的地址),也可以是动态的(从注册中心中定期查询地址列表)。
ServerListFilter:仅当使用动态ServerList时使用,用于在原始的服务列表中使用一定策略过虑掉一部分地址。
IRule:选择一个最终的服务地址作为LB结果。选择策略有轮询、根据响应时间加权、断路器(当Hystrix可用时)等。
Ribbon在工作时首选会通过ServerList来获取所有可用的服务列表,然后通过ServerListFilter过虑掉一部分地址,最后在剩下的地址中通过IRule选择出一台服务器作为最终结果。
四:Ribbon提供的主要负载均衡策略介绍
1:简单轮询负载均衡(RoundRobin):以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。
2:随机负载均衡 (Random): 随机选择状态为UP的Server
3:加权响应时间负载均衡 (WeightedResponseTime):根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。
4:区域感知轮询负载均衡(ZoneAvoidanceRule):复合判断server所在区域的性能和server的可用性选择server
Ribbon自带负载均衡策略比较
五:Ribbon单独使用
创建一个maven工程,名称是ribbon_client,pom的内容如下:
<dependencies>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-core</artifactId>
<version>2.2 .0 </version>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-httpclient</artifactId>
<version>2.2 .0 </version>
</dependency>
</dependencies>
sample-client.properties配置文件
# Max number of retries
sample -client.ribbon.MaxAutoRetries=1
# Max number of next servers to retry (excluding the first server)
sample -client.ribbon.MaxAutoRetriesNextServer=1
# Whether all operations can be retried for this client
sample -client.ribbon.OkToRetryOnAllOperations=true
# Interval to refresh the server list from the source
sample -client.ribbon.ServerListRefreshInterval=2000
# Connect timeout used by Apache HttpClient
sample -client.ribbon.ConnectTimeout=3000
# Read timeout used by Apache HttpClient
sample -client.ribbon.ReadTimeout=3000
# Initial list of servers, can be changed via Archaius dynamic property at runtime
sample -client.ribbon.listOfServers=www.sohu.com:80 ,www.163 .com:80 ,www.sina.com.cn:80
sample -client.ribbon.EnablePrimeConnections=true
RibbonMain代码
import java.net.URI;
import com.netflix.client.ClientFactory;
import com.netflix.client.http.HttpRequest;
import com.netflix.client.http.HttpResponse;
import com.netflix.config.ConfigurationManager;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
import com.netflix.niws.client.http.RestClient;
public class RibbonMain {
public static void main( String[] args ) throws Exception {
ConfigurationManager.loadPropertiesFromResources( " sample-client.properties " );
System. out .println(ConfigurationManager.getConfigInstance().getProperty(" sample-client.ribbon.listOfServers " ));
RestClient client = (RestClient)ClientFactory.getNamedClient(" sample-client " );
HttpRequest request = HttpRequest.newBuilder().uri(new URI(" / " )).build();
for (int i = 0 ; i < 4 ; i ++) {
HttpResponse response = client.executeWithLoadBalancer(request);
System. out .println(" Status for URI: " + response.getRequestedURI() + " is : " + response.getStatus());
}
ZoneAwareLoadBalancer lb = (ZoneAwareLoadBalancer) client.getLoadBalancer();
System. out .println(lb.getLoadBalancerStats());
ConfigurationManager.getConfigInstance().setProperty( " sample-client.ribbon.listOfServers " , " ccblog.cn:80,www.linkedin.com:80 " );
System. out .println(" changing servers ... " );
Thread.sleep( 3000 );
for (int i = 0 ; i < 3 ; i ++) {
HttpResponse response = client.executeWithLoadBalancer(request);
System. out .println(" Status for URI: " + response.getRequestedURI() + " is : " + response.getStatus());
}
System. out .println(lb.getLoadBalancerStats());
}
}
代码解析
使用 Archaius ConfigurationManager 加载属性;
使用 ClientFactory 创建客户端和负载均衡器;
使用 builder 构建 http 请求。注意我们只支持 URI 的 "/" 部分的路径,一旦服务器被负载均衡器选中,会由客户端计算出完整的 URI;
调用 API client.executeWithLoadBalancer(),不是 exeucte() API;
动态修正配置中的服务器池;
等待服务器列表刷新(配置文件中定义的刷新间隔是为 3 秒钟);
打印出负载均衡器记录的服务器统计信息。
六:Ribbon结合eureka使用
创建maven工程 eureka_ribbon_client 该工程启动和相关配置依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4 .3 .RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在应用主类中,通过@EnableDiscoveryClient注解来添加发现服务能力。创建RestTemplate实例,并通过@LoadBalanced注解开启均衡负载能力。
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonApplication. class , args);
}
}
创建ConsumerController来消费service01的getuser服务。通过直接RestTemplate来调用服务
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@RequestMapping(value = " /getuserinfo " , method = RequestMethod.GET)
public String add() {
return restTemplate.getForEntity(" http://biz-service-0/getuser " , String.class ).getBody();
}
}
Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
以上是关于SpringCloud之 LoadBalancer和Feign负载均衡的主要内容,如果未能解决你的问题,请参考以下文章
SpringCloud升级之路-2020.0.x - 6.使用 Spring Cloud LoadBalancer
SpringCloud升级之路-2020.0.x - 6.使用 Spring Cloud LoadBalancer
SpringCloud升级之路2020.0.x版-21.Spring Cloud LoadBalancer简介
SpringCloud 升级之路-2020.0.x-7.使用 Spring Cloud LoadBalancer
SpringCloud 升级之路-2020.0.x-7.使用 Spring Cloud LoadBalancer
SpringCloud升级之路2020.0.x版-23.订制Spring Cloud LoadBalancer