前面我们已经完成了注册中心和服务提供者两个基础组件。接着介绍使用Spring Cloud Ribbon在客户端负载均衡的调用服务。
ribbon 是一个客户端负载均衡器,可以简单的理解成类似于 nginx的负载均衡模块的功能。
主流的LB方案可分成两类:
一种是集中式LB, 即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;
另一种是进程内LB,将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于后者,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
Ribbon的架构图:如下:
1.首先我们先在原来的基础上新建一个Ribbon模块,如下图:
现在我们单独使用ribbon,在Ribbon模块下添加依赖,如下图所示:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> <version>1.4.0.RELEASE</version> </dependency>
修改application.yml文件,如下所示:
server: port: 8082 spring: application: name: Ribbon-Consumer #providers这个是自己命名的,ribbon,listOfServer这两个是规定的 providers: ribbon: listOfServers: localhost:8080,localhost:8081
在Ribbon模块下新建一个测试类如下代码 * Created by cong on 2018/5/8. */
@RestController public class ConsumerController {
//注入负载均衡客户端 @Autowired
private LoadBalancerClient loadBalancerClient; @RequestMapping("/consumer") public String helloConsumer() throws ExecutionException, InterruptedException {
//这里是根据配置文件的那个providers属性取的 ServiceInstance serviceInstance = loadBalancerClient.choose("providers");
//负载均衡算法默认是轮询,轮询取得服务 URI uri = URI.create(String.format("http://%s:%s", serviceInstance.getHost(), serviceInstance.getPort())); return uri.toString();
}
运行结果如下:
会轮询的获取到两个服务的URL 访问第一次,浏览器出现http://localhost:8080 访问第二次就会出现http://localhost:8081
在这里给普及一下有哪些负载均衡算法:
1:简单轮询负载均衡(RoundRobin)
以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。
2:随机负载均衡 (Random)
随机选择状态为UP的Server
3:加权响应时间负载均衡 (WeightedResponseTime)
根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。
4:区域感知轮询负载均衡(ZoneAvoidanceRule)
复合判断server所在区域的性能和server的可用性选择server
有兴趣的还可以看一下我在Ngnix的随笔文章中列出的负载均衡算法实现:http://www.cnblogs.com/huangjuncong/p/8319182.html
如果想配置其他轮询算法在yml配置文件中配置,如下配置一个随机算法所示:
server: port: 8082 spring: application: name: Ribbon-Consumer #providers这个是自己命名的,ribbon,listOfServer这两个是规定的 providers: ribbon: listOfServers: localhost:8080,localhost:8081 ##如果不想选用默认的轮询的负载均衡算法,在这里做如下配置 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
接着在启动类动一下手脚让我们配置的随机算法的负载均衡生效,只需要实现一个实现了IRule接口的Bean即可,如下:
package hjc; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class RibbonApplication { public static void main(String[] args) { SpringApplication.run(RibbonApplication.class, args); } @Bean public IRule ribbonRule(){ return new RandomRule(); } }
因此重新启动Ribbon启动类,得到的结果是随机的,如下所示:
浏览器随机出现http://localhost:8080 或者http://localhost:8081
那么问题来了,服务的地址是写死在配置文件中,如果某个服务挂了,那么还会把请求转发到挂掉的服务中,因此,解决的办法是,跟Eureka对接,结合一起用。就可以依靠Eureka动态的获取一个可用的服务列表,隔一段时间我就更新一次,
或者Eureka设置一个监听端口,某一个服务挂了,Eureka通知我,我会知道,变更服务列表,这样不久形成一个闭环了吗?这样就不存在高可用性问题了。跟Eureka配合一起用同时解决了的Ribbon的单点故障问题。
第一步,毫无疑问就是修改Ribbon模块的pom.xml文件,加入如下依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.3.5.RELEASE</version> </dependency>
第二步,修改yml配置文件如下:
server: port: 8082 spring: application: name: Ribbon-Consumer eureka: #客户端 client: #注册中心地址 service-url: defaultZone: http://localhost:8888/eureka/,http://localhost:8889/eureka/
这样就可以跟Eureka结合,这样Ribbon就可以通过Eureka动态的获取服务列表
接着在启动类加上服务发现注解,如下:
@EnableDiscoveryClient
启动类接着声明一个负载均衡的请求器@LoadBalanced,还有请求发起的工具RestTemplate
如下代码:
@SpringBootApplication @EnableDiscoveryClient public class RibbonApplication { public static void main(String[] args) { SpringApplication.run(RibbonApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
接着我们在上一节文章中的两个provider1,provider2模块添加一下测试代码:如下:
provider1:
package hjc.hello; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Created by cong on 2018/5/8. */ @RestController public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello1"; } }
provider2代码如下:
package hjc.hello; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Created by cong on 2018/5/8. */ @RestController public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello2"; } }
接着我们用RestTemplate进行面向服务调用,不再面向IP调用。
如下代码:
/** * Created by cong on 2018/5/8. */ @RestController public class ConsumerController { @Autowired private RestTemplate restTemplate; @RequestMapping("/consumer") public String helloConsumer() throws ExecutionException, InterruptedException { return restTemplate.getForEntity("http://HELLO-SERVICE/hello",String.class).getBody();
}
接着启动Ribbon模块,我们看一下Eureka仪表盘,如下:
可以看到多了RIBBON-CONSUMER服务
接着我们继续在已经运行的Ribbon模块上,在浏览器输入localhost:8082,运行结果如下:
hello1或者hello2,
可以看到hello1 ,hello2轮询方式出现,因为默认就是轮询方式
到这里我们还发现Ribbon还是单点故障的,这里我来解释一下:
因为这里我是单独建立一个SpringBoot的Ribbon模块,实际使用并不是这样用的,Ribbon是客户端的负载均衡,是跟客户端绑定在一起的,我们实际运用的时候往往会在服务里面引入一个客户端负载均衡去连接到Eureka客户中心,
这样我们还存在Ribbon单点故障吗?不存在了,因为我们服务提供者就是高可用的,这样还是个单点吗?这里读者的思考必须转过弯来,这一篇随笔我只是为了演示Ribbon,实际使用并不是这样用的。