服务注册发现Eureka之三:Spring Cloud Ribbon实现客户端负载均衡(客户端负载均衡Ribbon之三:使用Ribbon实现客户端的均衡负载)
在使用RestTemplate来消费spring boot的Restful服务示例中,我们提到,调用spring boot服务的时候,需要将服务的URL写死或者是写在配置文件中,但这两种方式,无论哪一种,一旦ip地址发生了变化,都需要改动程序,并重新部署服务,使用Ribbon的时候,可以有效的避免这个问题。
前言:
软负载均衡的实现方式有两种,分别是服务端的负载均衡和客户端的负载均衡
服务端负载均衡:当浏览器向后台发出请求的时候,会首先向反向代理服务器发送请求,反向代理服务器会根据客户端部署的ip:port映射表以及负载均衡策略,来决定向哪台服务器发送请求,一般会使用到nginx反向代理技术。
客户端负载均衡:当浏览器向后台发出请求的时候,客户端会向服务注册器(例如:Eureka Server),拉取注册到服务器的可用服务信息,然后根据负载均衡策略,直接命中哪台服务器发送请求。这整个过程都是在客户端完成的,并不需要反向代理服务器的参与。
一、启动Eureka Server 和 启动微服务,并注册到Eureka Server上
请参考该例: 《服务注册发现Eureka之一:Spring Cloud Eureka的服务注册与发现》
二、服务提供端
2.1、为了更好的追踪负载均衡的分发,我将《服务注册发现Eureka之一:Spring Cloud Eureka的服务注册与发现》的示例修改一下,增加计数器的展示:
package com.dxz.compute; import java.time.LocalDateTime; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class ComputeController { private final Logger logger = Logger.getLogger(getClass()); @Autowired private DiscoveryClient client; @RequestMapping(value = "/add", method = RequestMethod.GET) public Integer add(@RequestParam Integer a, @RequestParam Integer b, @RequestParam Integer sn) { ServiceInstance instance = client.getLocalServiceInstance(); Integer r = a + b; logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r+ ",sn="+sn +",time="+ LocalDateTime.now()); return r; } }
2.2、为了演示负载均衡的效果,再启动一个为服务,注意需要将端口号改成不一致
如在eclipse中再拷贝一个
spring.application.name=compute-service server.port=2224 eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
三、服务消费端
1、构建consumer-movie-ribbon项目,在pom.xml中引入ribbon依赖
在引入Eureka依赖的时候,默认里面含有ribbon依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.dxz</groupId> <artifactId>consume-movie-ribbon</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.5.RELEASE</version> <!--配合spring cloud版本 --> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <!--设置字符编码及java版本 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!--增加eureka-server的依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> <!--用于测试的,本例可省略 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <!--依赖管理,用于管理spring-cloud的依赖 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-parent</artifactId> <version>Brixton.SR3</version> <!--官网为Angel.SR4版本,但是我使用的时候总是报错 --> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <!--使用该插件打包 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2、添加@LoadBalanced注解,实现负载均衡
ribbon负载均衡策略默认为轮循方式
package com.dxz.compute.demo1; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import org.springframework.web.client.RestTemplate; import com.dxz.compute.loadbalance.ExcludeFromComponentScan; import com.dxz.compute.loadbalance.TestConfiguration; @SpringBootApplication @EnableEurekaClient @RibbonClient(name = "compute-service", configuration = TestConfiguration.class) @ComponentScan(excludeFilters = {@ComponentScan.Filter(type=FilterType.ANNOTATION,value=ExcludeFromComponentScan.class)}) public class SpringbootRestTemplateApplication { @Bean @LoadBalanced // 添加负载均衡支持,很简单,只需要在RestTemplate上添加@LoadBalanced注解,那么RestTemplate即具有负载均衡的功能 public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(SpringbootRestTemplateApplication.class, args); } }
在consumer-movie-ribbon中通过RestTemplate 调用上面的2个服务提供方的服务。注意下面的url,是实例名称(不需要ip:port),restTemplate将有Ribbon提供的负载均衡功能。
package com.dxz.compute.demo1; import java.util.concurrent.atomic.AtomicInteger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class RestTemplateController { @Autowired private RestTemplate restTemplate; private AtomicInteger sn = new AtomicInteger(0); @RequestMapping(value = "/test", method = RequestMethod.GET) public void test(@RequestParam Integer a, @RequestParam Integer b) {// 将原来的ip:port的形式,改成注册到Eureka Server上的应用名即可 System.out.println("=============================="); String result = restTemplate.getForObject("http://compute-service/add?a="+a +"&b="+b + "&sn="+sn.incrementAndGet(), String.class); System.out.println("返回结果:"+result); } }
3、自定义负载均衡策略
package com.dxz.compute.loadbalance; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import com.netflix.loadbalancer.RetryRule; import com.netflix.loadbalancer.RoundRobinRule; /** * @Configuration注解不能放在@SpringBootApplication所在的包下 如果放在此包下,默认全部负载均衡使用此策略 */ @Configuration @ExcludeFromComponentScan public class TestConfiguration { @Bean public IRule ribbonRule() { //return new RandomRule(); //设置负载均衡的规则为随机 return new RoundRobinRule(); //默认的轮询策略 } }
4、指定对某个客户端使用自定义负载均衡
@RibbonClient(name = "compute-service", configuration = TestConfiguration.class)指定调用“compute-service”服务的客户端,使用TestConfiguration.class里配置的负载均衡策略。其他客户端不受影响。
package com.dxz.compute.demo1; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import org.springframework.web.client.RestTemplate; import com.dxz.compute.loadbalance.ExcludeFromComponentScan; import com.dxz.compute.loadbalance.TestConfiguration; @SpringBootApplication @EnableEurekaClient @RibbonClient(name = "compute-service", configuration = TestConfiguration.class) @ComponentScan(excludeFilters = {@ComponentScan.Filter(type=FilterType.ANNOTATION,value=ExcludeFromComponentScan.class)}) public class SpringbootRestTemplateApplication { ... }
5、如果将上面的TestConfiguration@Configuration注解放在@SpringBootApplication所在的包下,所以的客户端都按照这个策略进行。
a、在@Configuration包下创建ExcludeFromComponentScan注解,注解见下面:
package com.dxz.compute.loadbalance; public @interface ExcludeFromComponentScan { }
b、在入口类中排除此注解不扫描
package com.dxz.compute.demo1; @SpringBootApplication @EnableEurekaClient @RibbonClient(name = "compute-service", configuration = TestConfiguration.class) @ComponentScan(excludeFilters = {@ComponentScan.Filter(type=FilterType.ANNOTATION,value=ExcludeFromComponentScan.class)}) public class SpringbootRestTemplateApplication { @Bean @LoadBalanced // 添加负载均衡支持,很简单,只需要在RestTemplate上添加@LoadBalanced注解,那么RestTemplate即具有负载均衡的功能 public RestTemplate restTemplate() { return new RestTemplate(); } ... }
c、在TestConfiguration中使用此注解
@Configuration @ExcludeFromComponentScan public class TestConfiguration { @Bean public IRule ribbonRule() { //return new RandomRule(); //设置负载均衡的规则为随机 return new RoundRobinRule(); //默认的轮询策略 } }
6、开启多个compute-service微服务,测试结果
通过消费端服务调用服务提供方,负载均衡的结果如下:
第一种用轮询策略:
@Bean public IRule ribbonRule() { //return new RandomRule(); //设置负载均衡的规则为随机 return new RoundRobinRule(); //默认的轮询策略 }
下面2台服务提供方日志:
第二种:随机策略
@Configuration @ExcludeFromComponentScan public class TestConfiguration { @Bean public IRule ribbonRule() { return new RandomRule(); //设置负载均衡的规则为随机 //return new RoundRobinRule(); //默认的轮询策略 } }
两台服务提供方日志如下:
服务消费端日志: