负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段。理解Ribbon对于我们使用Spring Cloud来讲非常的重要。它是一个基于Http和TCP的客户端负载均衡工具。它不像服务注册中心、配置中心、API网关那样独立部署,但是它几乎存在于每个微服务的基础设施中。
基于Ribbon+RestTemplate的用法
1、引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
注意:Eureka默认集成了Ribbon,只需引入Eureka JAR即可。
2、在启动类中注入配置
package com.mimaxueyuan.consumer.robbin; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableDiscoveryClient public class RibbonConsumerApplication { @Bean @LoadBalanced // 需要使用负载均衡,必须与Bean一同使用 public RestTemplate balanceRestTemplate() { return new RestTemplate(); } @Primary //自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常 @Bean //需要多个RestTemplate, 有的RestTemplate使用负载均衡,有的不使用,不使用的不增加@LoadBalanced注解 public RestTemplate noBalanceRestTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(RibbonConsumerApplication.class, args); } }
3、编写 Controller——演示使用负载均衡和不使用负载均衡的用法及区别
package com.mimaxueyuan.consumer.robbin.controller; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import com.mimaxueyuan.consumer.entity.User; @RestController public class RibbonController { // 注入restTemplate, 这个类已经在RibbonConsumerApplication中初始化,不使用负载均衡 @Autowired private RestTemplate noBalanceRestTemplate; // 注入restTemplate, 这个类已经在RibbonConsumerApplication中初始化,并且使用负载均衡 @Autowired // 默认按照类型注入,如果需要按照名字注入需要使用@Qualifier注解 //@LoadBalanced //使用带有负载均衡的RestTemplate @Qualifier("balanceRestTemplate") private RestTemplate balanceRestTemplate; // 以下注入负载均衡客户端LoadBalancerClient是一个接口,下面只有一个RibbonLoadBalancerClient实现类 @Autowired private LoadBalancerClient loadBalancerClient; @Autowired private RibbonLoadBalancerClient ribbonLoadBalancerClient; /** * 不使用ribbon的旧调用方式 * * @author Kevin * @Title: old * @return * @return: String */ @GetMapping("/ribbon/old/get/{id}") public String old(@PathVariable("id") String id) { // 使用noBalanceRestTemplate是非负载均衡的,所以没问题 String result = noBalanceRestTemplate.getForObject("http://localhost:9907/get/"+id, String.class); System.out.println("[hardcode1]" + result); // 由于balanceRestTemplate已经使用了Ribbon做负载均衡,所以使用硬编码方式就不允许了,会提示:No instances available for localhost result = balanceRestTemplate.getForObject("http://localhost:9907/get/"+id, String.class); System.out.println("[hardcode2]" + result); return "result"; } /** * ribbon使用 * * @author Kevin * @Title: ribbon * @param id * @return * @return: String */ @GetMapping("/ribbon/get/{id}") public String ribbon(@PathVariable("id") String id) { // -----------------以下代码使用ribbon做客户端负载均衡 // 使用provider的instanceName替代ip和端口的硬编码 String result = balanceRestTemplate.getForObject("http://mima-cloud-producer/get/"+id, String.class); System.out.println("[ribbon]" + result); System.out.println("[loadBalancerClient]choose的结果,代表负载均衡之后要选择的服务实例"); ServiceInstance instance = loadBalancerClient.choose("mima-cloud-producer"); System.out.println("host:" + instance.getHost() + ",port:" + instance.getPort() + ",serviceId=" + instance.getServiceId() + ",uri=" + instance.getUri()); System.out.println("[ribbonLoadBalancerClient]choose的结果,代表负载均衡之后要选择的服务实例"); instance = ribbonLoadBalancerClient.choose("mima-cloud-producer"); System.out.println("host:" + instance.getHost() + ",port:" + instance.getPort() + ",serviceId=" + instance.getServiceId() + ",uri=" + instance.getUri()); System.out.println("[ribbonLoadBalancerClient]choose的结果,代表负载均衡之后要选择的服务实例"); instance = ribbonLoadBalancerClient.choose("mima-cloud-producer"); System.out.println("host:" + instance.getHost() + ",port:" + instance.getPort() + ",serviceId=" + instance.getServiceId() + ",uri=" + instance.getUri()); try { // 根据负载均衡后的服务,构建一个访问url // 第二个参数不能为null System.out.println("根据负载均衡后的服务,构建一个访问url"); URI reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("")); System.out.println("reconstructURI1-yes:" + reconstructURI); // 拼写在请求地址后边,需要注意是否需要添加/ reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("/ribbon/get")); System.out.println("reconstructURI2-yes:" + reconstructURI); reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("http")); System.out.println("reconstructURI3-no:" + reconstructURI); reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("https")); System.out.println("reconstructURI4-no:" + reconstructURI); reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("test")); System.out.println("reconstructURI5-no:" + reconstructURI); // 使用http:/xxx、https:/xxx可以用于切换http协议还是https协议 reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("http:/ribbin/get")); System.out.println("reconstructURI6-yes:" + reconstructURI); reconstructURI = ribbonLoadBalancerClient.reconstructURI(instance, new URI("https:/ribbin/get")); System.out.println("reconstructURI7-yes:" + reconstructURI); } catch (URISyntaxException e) { // TODO Auto-generated catch block e.printStackTrace(); } return "ribbon's demo,please to see console output"; } @GetMapping("/nobalance/get/{id}") public String nobalance(@PathVariable("id") String id) { // -----------------以下代码使用硬编码方式调用服务 // 如果restTemplate已经使用了Ribbon做负载均衡,也就是使用了@LoadBaleced注解,依然使用硬编码方式就不允许了,会提示:No instances available for localhost String result = noBalanceRestTemplate.getForObject("http://localhost:9907/get/"+id, String.class); System.out.println("[noBalanceRestTemplate-hardcode1]" + result); //正常访问 result = noBalanceRestTemplate.getForObject("http://localhost:9908/get/"+id, String.class); System.out.println("[noBalanceRestTemplate-hardcode2]" + result); //正常访问 try { //异常访问,Ribbon负载均衡只能通过服务名调用 result = balanceRestTemplate.getForObject("http://localhost:9907/get/"+id, String.class); System.out.println("[balanceRestTemplate-hardcode1]" + result); //异常访问,Ribbon负载均衡只能通过服务名调用 result = balanceRestTemplate.getForObject("http://localhost:9908/get/"+id, String.class); System.out.println("[balanceRestTemplate-hardcode2]" + result); } catch (Exception e) { System.out.println("使用balanceRestTemplate同时使用地址硬编码错误:" + e.getMessage()); } return "ribbon's demo,please to see console output"; } @SuppressWarnings("unchecked") @GetMapping("listAll") public List<User> listAll() { // restTemplate怎样返回一个List对象 List<User> list = balanceRestTemplate.getForObject("http://mima-cloud-producer/listAll", List.class); return list; } }
其中 mima-cloud-producer 为服务名,启动两个服务节点如下:
http://localhost:9907/
http://localhost:9908/