Ribbon负载均衡和RestTemplate使用

在 Spring Cloud中,提供的服务调用是 Ribbon和 OpenFeign。Ribbon是 Netflix公司开发的组件,OpenFeign也是基于 Ribbon实现的工具。

Spring Cloud Netflix Ribbon是一个基于HTTP和TCP的客户端负载均衡的组件工具,它对 Netflix Ribbon的二次封装。通过Spring Cloud的二次封装,可以让开发者轻松地将面向服务的 REST模版请求自动转换成客户端负载均衡的服务调用。

Spring Cloud Netflix Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关等组件那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。

微服务之间的调用往往被称为客户端负载均衡

即是当浏览器向后台发出请求的时候,客户端会向 Eureka Server 读取注册到服务器的可用服务实例清单列表,并基于负载均衡算法,选择其中微服务的一个具体实例来发送请求。

一、Netflix Ribbon简介

1、Ribbon简介

Ribbon(/rɪbən/) 是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机等)去连接这些机器。也可以使用Ribbon实现自定义的负载均衡算法。

Ribbon实现的关键点是为 Ribbon定制的RestTemplate ,Ribbon利用了 RestTemplate的拦截器机制,在拦截器中实现Ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。

2、负载均衡简介

负载均衡(Load Balance,简称LB) 分为硬件负载均衡(比如 F5)和软件负载均衡(比如 Nginx)。这里讨论软件负载均衡。
负载均衡,是一种计算机技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。

可以理解为:是将负载(工作任务,访问请求)进行平衡、分摊到多个操作单元(服务器,组件)上进行执行。是解决高性能,单点故障(高可用),扩展性(水平伸缩)的终极解决方案。

(1)负载均衡技术具有一些优势:

  • 高可用和高性能: 负载均衡技术将业务较均衡的分担到多台设备或链路上,通过心跳机制检测服务节点,保证了高可用,提高了整个系统的性能;
  • 可扩展性: 负载均衡技术可以方便的增加集群中设备或链路的数量,在不降低业务质量的前提下满足不断增长的业务需求;
  • 高可靠性: 单个甚至多个设备或链路法神故障也不会导致业务中断,提高了整个系统的可靠性;
  • 可管理性: 大量的管理共组都集中在使用负载均衡技术的设备上,设备集群或链路集群只需要维护通过的配置即可;

(2)负载均衡需要解决的两个最基本的问题

  • 从哪里获取服务实例
    服务获取是指微服务实例作为 Eureka的客户端,从 Eureka服务治理中心获取其他服务实例清单的功能。服务获取会将服务实例清单列表缓存到本地,并且按一定的时间间隔刷新。
  • 如何选择服务实例调用
    Ribbon会根据请求的 URL知道调用的是哪个微服务,然后再服务获取的清单列表中,通过一种负载均衡算法(默认采用轮询策略)选择其中一个具体实例进行服务调用。。

二、Ribbon(RestTemplate)的使用

使用Ribbon,主要就是 RestTemplate的使用。

1、搭建项目

传送门:Eureke服务治理中心

上篇文章已经搭建好项目,也是用了一下 RestTemplate,下面也说明一下Ribbon整合RestTemplate。然后详解RestTemplate的具体使用。
在这里插入图片描述

2、Ribbon整合RestTemplate

在启动类或者创建一个配置类都可以,然后只需要在 RestTemplate类添加@LoadBalanced注解 即可,就可以实现负载均衡。

@SpringBootApplication
@EnableEurekaClient
public class UserApplication {

	// 负载均衡
	@LoadBalanced
	@Bean
	public RestTemplate initRestTemplate() {
		return new RestTemplate();
	}

	public static void main(String[] args) {
		SpringApplication.run(UserApplication.class, args);
	}

}

@LoadBalanced 注解原理

Spring Cloud 给我们做了大量的自动化配置。通过在spring-cloud-commons-2.2.6.RELEASE.jar中查看 LoadBalancerAutoConfiguration 的源码,可以看到这里维护了一个 @LoadBalanced 的 RestTemplate 列表。
在这里插入图片描述

3、RestTemplate的具体使用

这里通过 postman访问 User服务来调用 Order服务。注意Order服务的端口(轮询策略),数据都是json。
在这里插入图片描述

(1)使用 GET请求

GET请求相关的方法:在这里插入图片描述
getForObject 和 getForEntity 的区别主要体现在返回值的差异上:

getForObject的返回值就是服务提供者返回的数据,无法获取到响应头。
getForEntity的返回值是一个 ResponseEntity 的对象,包含了响应数据以及响应头。

  • Order服务
    @GetMapping("/get1/{id}")
    public String get1(@PathVariable Long id) {
        return port + "--" + "order服务getForObject返回数据 >>>>>>> " + id;
    }

    @GetMapping("/get2")
    public String get2(Long id) {
        return port + "--" + "order服务getForEntity返回数据 >>>>>>> " + id;
    }
  • User服务
    @GetMapping("/get1/{id}")
    public String get1(@PathVariable Long id) {
        String url = "http://ORDER/order/get1/" + id;
        String res = restTemplate.getForObject(url, String.class);
        return res;
    }

    @GetMapping("/get2")
    public String get2(Long id) {
        String url = "http://ORDER/order/get2?id={1}";
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class, id);
        HttpStatus statusCode = responseEntity.getStatusCode();
        int statusCodeValue = responseEntity.getStatusCodeValue();
        HttpHeaders headers = responseEntity.getHeaders();
        // 获取返回的数据
        String res = responseEntity.getBody();
        return res;
    }
  • 测试
    在这里插入图片描述

(2)使用 POST请求
POST请求相关的方法:
在这里插入图片描述
postForEntity 和 postForObject 与 GET请求雷同,参数的传递可以是 key/value 的形式,也可以是 JSON 数据。

postForLocation方法的返回值是一个 URI对象,因为 POST请求一般用来添加数据,有时需要将刚刚添加成功的数据的 URL返回来,此时就可以使用这个方法。
注意: URI实际上是指响应头的 Location 字段,所以,服务提供者中 register 接口的响应头必须要有 Location字段(即请求的接口实际上是一个重定向的接口),不要返回json就行,否则 postForLocation 方法的返回值为null。

比如:用户注册成功之后,可能就自动跳转到登录页面了,此时就可以使用该方法。这里简单点就返回get1的URI,然后请求它。

  • Order服务
    这里 @RestController 改为 @Controller
@Controller
public class OrderController {

    @Value("${server.port}")
    private String port;
    @Autowired
    private RestTemplate restTemplate;


    @GetMapping("/get1/{id}")
    @ResponseBody
    public String get1(@PathVariable Long id) {
        return port + "--" + "order服务getForObject返回数据 >>>>>>> " + id;
    }

    @RequestMapping("/post2Register")
    public String post2Register(@RequestBody OrderDO orderDO) throws UnsupportedEncodingException {
        orderDO.setId(200L);
        return "redirect:/get1/" + URLEncoder.encode(orderDO.getId().toString(),"UTF-8");
    }
// ...
}
  • User服务
    @PostMapping("/post1")
    public OrderDO post1(@RequestBody OrderDO orderDO) {
        String url = "http://ORDER/order/post1";
        OrderDO res = restTemplate.postForObject(url, orderDO, OrderDO.class);
        return res;
    }

    @PostMapping("/post2")
    public String post2(@RequestBody OrderDO orderDO) throws MalformedURLException {
        String url = "http://ORDER/order/post2Register";
        Map map = new HashMap();
        map.put("orderName", orderDO.getOrderName());
        map.put("createData", orderDO.getCreateData());
        URI uri = restTemplate.postForLocation(url, map);
        
        String res = restTemplate.getForObject(uri, String.class);
        return res;
    }
  • 测试
  • 在这里插入图片描述

postForLocation访问之后,简单跟踪了一下,后面访问 uri跑了两次,localhost 无法访问报错 ,应用名访问ok,最终结果报错,有点没搞懂。
在这里插入图片描述

(3)使用 PUT请求

PUT请求相关的方法在这里插入图片描述
可以用 key/value 的形式传参,也可以用 JSON 的形式传参

  • Order服务
    @PutMapping("/put1")
    @ResponseBody
    public void put1(@RequestBody OrderDO orderDO) {
        orderDO.setId(300L);
        System.out.println(orderDO);
    }
  • User服务
    @PutMapping("/put1")
    public void put1(@RequestBody OrderDO orderDO) {
        String url = "http://ORDER/order/put1";
        restTemplate.put(url, orderDO);
    }
  • 测试
    在这里插入图片描述

(4)使用 DELETE请求

DELETE请求相关的方法
在这里插入图片描述
参数只能在地址栏传送,可以是直接放在路径中,也可以用 key/value 的形式传递.

  • Order服务
    @DeleteMapping("/delete1")
    @ResponseBody
    public void delete1(Long id) {
        System.out.println("删除成功==" + id);
    }
  • User服务
    @DeleteMapping("/delete1")
    public void delete1(Long id) {
        String url = "http://ORDER/order/delete1?id={1}";
        Map<String,Object> map = new HashMap<>();
        map.put("id", id);
        restTemplate.delete(url, map);
    }
  • 测试
    在这里插入图片描述

(5)使用通用 exchange方式

exchange相关的方法
在这里插入图片描述
参数和前面的差不多,注意:多了一个请求类型的参数,需要创建一个 HttpEntity 作为参数来传递。
exchange方式需要开发者对请求进行封装,当你在调用时并指定请求类型,即它既能做 GET 请求,也能做 POST 请求,也能做其它各种类型的请求。

  • Order服务,不变
  • User服务,通过 exchange方式请求 delete和 post方式
    @DeleteMapping("/delete1Exchange")
    public void delete1Exchange(Long id) {
        String url = "http://ORDER/order/delete1?id={id}"; // 位置从1开始{1},最好使用字段名{id}
        Map<String,Object> map = new HashMap<>();
        map.put("id", id);

        HttpHeaders headers = new HttpHeaders();
        headers.add("cookie","cookievalue");
        HttpEntity<MultiValueMap<String,String>> httpEntity =  new HttpEntity<>(null,headers);
        ResponseEntity<Void> responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, Void.class, map);
    }

    @PostMapping("/post1Exchange")
    public OrderDO post1Exchange(@RequestBody OrderDO orderDO) throws URISyntaxException {
        String url = "http://ORDER/order/post1";
        ResponseEntity<OrderDO> responseEntity = restTemplate.exchange(
                new RequestEntity<OrderDO>(orderDO, HttpMethod.POST, new URI(url)),  //请求的信息
                new ParameterizedTypeReference<OrderDO>() {
                }    // 响应数据的类型
        );
        OrderDO res = responseEntity.getBody();
        return res;
    }
  • 测试ok

三、常见负载均衡策略

Ribbon自带负载均衡策略:

策略名策略声明策略描述实现说明
BestAvailableRulepublic class BestAvailableRule extends ClientConfigEnabledRoundRobinRule选择一个最小的并发请求的server逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server
AvailabilityFilteringRulepublic class AvailabilityFilteringRule extends PredicateBasedRule过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值)使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态
WeightedResponseTimeRulepublic class WeightedResponseTimeRule extends RoundRobinRule根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。一 个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成statas时,使用roubine策略选择 server。
RetryRulepublic class RetryRule extends AbstractLoadBalancerRule对选定的负载均衡策略机上重试机制。在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server
RoundRobinRulepublic class RoundRobinRule extends AbstractLoadBalancerRuleroundRobin方式轮询选择server轮询index,选择index对应位置的server
RandomRulepublic class RandomRule extends AbstractLoadBalancerRule随机选择一个server在index上随机,选择index对应位置的server
ZoneAvoidanceRulepublic class ZoneAvoidanceRule extends PredicateBasedRule复合判断server所在区域的性能和server的可用性选择server使 用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个 zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的 Server。

Ribbon在大部分情况下都不需要自定义,也不建议修改它们。遇到再说吧,Ribbon的使用就到这里了。

参考文章:

—— Stay Hungry. Stay Foolish. 求知若饥,虚心若愚。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值