当前位置: 代码迷 >> 综合 >> Spring Cloud Ribbon(客户端负载均衡)(1)
  详细解决方案

Spring Cloud Ribbon(客户端负载均衡)(1)

热度:58   发布时间:2023-11-20 01:36:47.0

    Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于NetFlix Ribbon实现。通过Spring Cloud的封装可以让我们轻松的将面向服务的REST模板请求自动转换成客户端负载均衡的服务调用。在实际使用中微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括Feign,它也是基于Ribbon实现的工具。

1.客户端负载均衡

    负载均衡是在系统架构中一个非常重要的内容,它是对系统的高可用,网络压力的缓解和处理扩容的重要手段之一。通常我们所描述的负载均衡指的是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,如F5。而软件负载均衡则是在服务器上安装一些具有均衡负载功能或模块的软件来完成请求分发工作,比如Nginx等。不论是硬件负载均衡还是软件负载均衡,只要是服务端负载均衡都能以下面这种类似的建构方式构建起来:

    硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个下挂可用的服务端清单,通过心跳检测的方式来剔除故障的服务端节点以保证清单中的都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备时,该设备按某种算法(线性轮询,按权重负载,按流量负载等)从维护的可用服务端清单中取出一台服务端地址然后进行转发。

    而客户端负载均衡和服务端负载均衡最大的不同点在于:所有的客户端节点都维护着自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,这个步骤会与服务注册中心配合完成。在Spring Cloud实现的服务治理框架中,默认会创建针对各个服务治理框架的Ribbon自动整合配置,比如Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,Consul中的org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration。在实际使用的时候可以通过查看这两个类的实现,以找到他们的配置详情来更好的使用。

    在微服务架构中使用客户端负载均衡十分容易,只需要两步:

1.服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。

2.服务消费者直接通过调用被@LoadBalanced注解修饰过得RestTemplate来实现面向服务的接口调用。

这样就可以将服务提供者的高可用和服务消费者的负载均衡调用一起实现了。

2.RestTemplate详解

    RestTemplate这个对象会使用Ribbon的自动化配置,同时通过配置@LoadBalanced还能开启客户端负载均衡。下面演示下RestTemplate针对不同请求类型和参数类型的服务调用实现。

1.GET请求

    在RestTemplate中,对GET请求可以通过如下两个方法进行调用实现。

第一种:getForEntity函数。该方法返回的是ResponseEntity,该对象是Spring对HTTP请求响应的封装,其中主要存储了HTTP的几个重要元素,如HTTP请求状态码的枚举对象HttpStatus(404,505),在他的父类HttpEntity中还存储着HTTp请求头信息对象HttpHeaders以及泛型类型的请求体对象。

RestTemplate restTemplate =new RestTemplate();
ResponseEntity<String> responseEntity= restTemplate.getForEntity("http://USER-SERVER/user?name={1}",String.class,"didi");
String body=responseEntity.getBody();

这个例子会访问USER-SERVER服务的user请求,同时最后一个参数“didi”会用于替代url中的占位符{1}。返回的ResponseEntity对象中的Body内容类型会根据第二个参数类型进行转换。

比如返回的是一个User类型则:

RestTemplate restTemplate =new RestTemplate();
ResponseEntity<User> responseEntity= restTemplate.getForEntity("http://USER-SERVER/user?name={1}",User.class,"didi");
User body=responseEntity.getBody();

除此之外他还有三种不同的重载实现:

方式一:

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException

该方法提供三个参数,url是请求的地址,responseType是请求响应体的body包装类型,uriVariables是url中的参数绑定。GET请求的参数绑定通常使用url中的拼接方式。需要注意的是,因为uriVariables是一个数组,所以他的顺序会对应url中占位符定义的数字顺序。

方式二:

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException

该方法提供的参数中只有uriVariables的参数类型与上面的方法不同。这里用到的是map类型,所以使用该方法进行参数绑定时需要在占位符中指定Map中的key值,如:

        RestTemplate restTemplate =new RestTemplate();Map<String,String> map = new HashMap<String,String>();map.put("name","didi");ResponseEntity<String> responseEntity= restTemplate.getForEntity("http://USER-SERVER/user?name={name}",String.class,map);String body=responseEntity.getBody();

方式三:

public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException 

该方法使用URI对象来替代之前的url和uriVariables参数来指定访问地址和参数绑定。URI时JDK java.net包下的一个类,表示一个统一资源标识符:

        RestTemplate restTemplate =new RestTemplate();UriComponents uriComponents=UriComponentsBuilder.fromUriString("http://USER-SERVER/user?name={name}").build().expand("didi").encode();URI uri=uriComponents.toUri();ResponseEntity<String> responseEntity= restTemplate.getForEntity(uri,String.class);String body=responseEntity.getBody();

第二种:getForObject函数。这个方法可以理解为对getForEntity的进一步封装,它通过HttpMessageConverterExtractor对HTTP的响应体body内容进行对象转换,实现请求直接返回包装好的对象内容。如:

        RestTemplate restTemplate = new RestTemplate();UriComponents uriComponents=UriComponentsBuilder.fromUriString("http://USER-SERVER/user?name={name}").build().expand("didi").encode();URI uri=uriComponents.toUri();String result=restTemplate.getForObject(uri,String.class);

当body是个对象时:

        RestTemplate restTemplate = new RestTemplate();UriComponents uriComponents=UriComponentsBuilder.fromUriString("http://USER-SERVER/user?name={name}").build().expand("didi").encode();URI uri=uriComponents.toUri();User result=restTemplate.getForObject(uri,User.class);

当不需要关注请求响应除body以为的内容时,该函数十分好用,可以少一个从Response中获取body的步骤,除此之外他也有三种重载方式:

public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException

2.POST请求

在RestTemplate中,对POST请求是可以通过如下三个方法进行调用实现。

第一种:postForEntity函数。该方法跟getForEntity类似,会调用后返回ResponseEntity<T>对象,其中T为请求响应body类型。如:

        RestTemplate restTemplate =new RestTemplate();User user=new User("didi",30);ResponseEntity<String> responseEntity= restTemplate.postForEntity("http://USER-SERVER/user",user,String.class);String body=responseEntity.getBody();

使用postForEntity提交POST请求到USER-SERVER服务的/user接口,提交的body内容为user对象,请求响应返回的body类型为String。

postForEntity函数也有三种不同的重载:

public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,Class<T> responseType, Object... uriVariables) throws RestClientExceptionpublic <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,Class<T> responseType, Map<String, ?> uriVariables) throws RestClientExceptionpublic <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType)throws RestClientException

这些函数的参数大部分与getForEntity一致。需要注意的是,如果request不是一个HttpEntity对象时,RestTemplate会将请求转换为一个HttpEntity对象来处理,Object就是一个request的类型,request内容会被视作完整的body来处理。而如果request是一个HttpEntity对象,那么会被当做一个完成的HTTP请求对象来处理,这个request中不仅包括了body内容还包括了header的内容。

第二种:postForObject函数。该方法跟getForObject的类型类似,它用于简化postForEntity的后续处理。

        RestTemplate restTemplate =new RestTemplate();User user=new User("didi",30);String body= restTemplate.postForObject("http://USER-SERVER/user",user,String.class);

它的三个重载方法:

public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,Object... uriVariables) throws RestClientExceptionpublic <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,Map<String, ?> uriVariables) throws RestClientExceptionpublic <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType)throws RestClientException

第三种:postForLocation函数。该方法实现了以POST请求提交资源并返回新资源的URL:

        RestTemplate restTemplate =new RestTemplate();User user=new User("didi",30);URI url= restTemplate.postForLocation("http://USER-SERVER/user",User.class);

它的三个重载方法:

public URI postForLocation(String url, @Nullable Object request, Object... uriVariables)throws RestClientExceptionpublic URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables)throws RestClientExceptionpublic URI postForLocation(URI url, @Nullable Object request) throws RestClientException 

由于postForLocation函数回返回新的URI资源,该URI就相当于指定了返回类型,所以此方法实现的POST请求不需要像之前的postForEntity那样指定responseType。

注:在postForObject时,不可使用HashMap。而应该是MultiValueMap

MultiValueMap<String, String> paramMap = new LinkedMultiValueMap<>();paramMap.add("loginName", per.getRfPersonnelTel());LOGGER.info(paramMap);String body= restTemplate.postForObject(url,paramMap,String.class);RescueForcePersonnel rescueForcePersonnel = personnelRepository.findByRfPersonnelTel(personnel.getRfPersonnelTel());rescueForcePersonnel.setRfTown(rescueForceTeam.getRfTown());HttpHeaders headers = new HttpHeaders();MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");headers.setContentType(type);headers.add("Accept", MediaType.APPLICATION_JSON.toString());HttpEntity<String> formEntity = new HttpEntity<String>(JSONObject.toJSONString(rescueForcePersonnel).toString(), headers);String body= restTemplate.postForObject(urlAdd,formEntity,String.class);

3.PUT请求

在RestTemplate中,对PUT请求可以通过put方法进行调用实现:

        RestTemplate restTemplate =new RestTemplate();User user=new User("didi",30);restTemplate.put("http://USER-SERVER/user/{1}",user,100001L);

它的三个重载方式:

public void put(String url, @Nullable Object request, Object... uriVariables)throws RestClientExceptionpublic void put(String url, @Nullable Object request, Map<String, ?> uriVariables)throws RestClientExceptionpublic void put(URI url, @Nullable Object request) throws RestClientException

put函数为void类型,所以没有返回值,也就没有其他函数定义的responseType参数,除此之外的其他传入参数定义与用法与postForObject一致。

4.DELETE请求

在RestTemplate中,对DELETE请求可以通过delete方法进行调用实现:

        RestTemplate restTemplate =new RestTemplate();restTemplate.delete("http://USER-SERVER/user/{1}",100001L);

delete也有三种不同的重载方法:

public void delete(String url, Object... uriVariables) throws RestClientExceptionpublic void delete(String url, Map<String, ?> uriVariables) throws RestClientExceptionpublic void delete(URI url) throws RestClientException

由于我们在进行REST请求时,通常将DELETE请求的唯一标示拼接在url中,所以DELETE也不需要request的body信息。url指定DELETE请求的位置,urlVariables绑定url中的参数即可。

 

  相关解决方案