一、Spring Cloud Ribbon简介
在微服务架构中,很多服务都会部署多个,其他服务去调用该服务的时候,如何保证负载均衡是个不得不去考虑的问题。负载均衡可以增加系统的可用性和扩展性,当我们使用RestTemplate来调用其他服务时,Ribbon可以很方便的实现负载均衡功能。
Spring Cloud Ribbon 是基于Netflix Ribbon实现的一套客户端负载均衡的工具。它是一个基于HTTP和TCP的客户端负载均衡器。它可以通过在客户端中配置ribbonServerList来设置服务端列表去轮询访问以达到均衡负载的作用。
当Ribbon与Eureka联合使用时,ribbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心中获取服务实例列表。同时它也会用NIWSDiscoveryPing来取代IPing,它将职责委托给Eureka来确定服务端是否已经启动。
而当Ribbon与Consul联合使用时,ribbonServerList会被ConsulServerList来扩展成从Consul获取服务实例列表。同时由ConsulPing来作为IPing接口的实现。
我们在使用Spring Cloud Ribbon的时候,不论是与Eureka还是Consul结合,都会在引入Spring Cloud Eureka或Spring Cloud Consul依赖的时候通过自动化配置来加载上述所说的配置内容,所以我们可以快速在Spring Cloud中实现服务间调用的负载均衡。
二、准备工作
我们下面做个Eureka+Ribbon+RestTemplate的案例,端口规划如下:
- 1台Eureka服务器:8001
- 3台Ribbon客户端:8101 8102 8103
- 1台Ribbon服务器:8201
先创建Eureka服务器和客户端,参考上一节:📃 Eureka 服务注册与发现
在Eureka客户端(同时也扮演了Ribbon客户端的角色)中创建控制器,以便之后测试:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/test")
public class TestController {
private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
@GetMapping("/test")
@ResponseBody
public Object test() {
Map<String, Object> map = new HashMap<>();
map.put("code", "1");
map.put("message", "ok");
map.put("data", "");
LOGGER.info("服务已被调用");
return map;
}
}
启动Eureka服务器和客户端:
在Eureka服务端管理界面看到,3个客户端已注册成功:
访问客户端试试:
都能访问成功,准备工作已经完毕。
三、Ribbon服务
创建一个ribbon-server模块,添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
配置文件:
server:
port: 8201
spring:
application:
name: ribbon-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8001/eureka/
添加一个Ribbon的配置类,只需给RestTemplate添加一个@LoadBalanced
注解,表明restTemplate使用LoadBalancerClient(负载均衡客户端)执行请求:
@Configuration
public class RibbonConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
在控制器中使用:
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/test")
@ResponseBody
public Object test() {
return restTemplate.getForObject("http://eureka-client/test/{1}", Object.class, "test");
}
}
其中 http://eureka-client
指向Ribbon客户端的内网地址(即Ribbon客户端的服务名称,我们前面创建的3个Eureka客户端就充当了Ribbon客户端的角色)
启动Ribbon服务器,访问:http://localhost:8201/test/test
多访问几次,负载均衡服务器将在多个Ribbon客户端之间切换:
四、Ribbon常用配置
ribbon:
ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
OkToRetryOnAllOperations: true #对超时请求启用重试机制
MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
MaxAutoRetries: 1 # 切换实例后重试最大次数
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法
五、RestTemplate
上面调用远程服务,使用到了RestTemplate。、
RestTemplate是一个HTTP客户端,使用它我们可以方便的调用HTTP接口,支持GET、POST、PUT、DELETE等方法。
常用的调用方法:
- GET:
getForObject
或getForEntity
- POST:
postForObject
或postForEntity
- PUT:
put
- DELETE:
delete
GET
getForObject:返回对象为响应体中数据转化成的对象。
定义:
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables);
<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables);
<T> T getForObject(URI url, Class<T> responseType);
举例:
@GetMapping("/{id}")
public CommonResult getUser(@PathVariable Long id) {
return restTemplate.getForObject("http://eureka-client/user/{1}", CommonResult.class, id);
}
getForEntity:返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。
定义:
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables);
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables);
<T> ResponseEntity<T> getForEntity(URI var1, Class<T> responseType);
举例:
@GetMapping("/getEntityByUsername")
public CommonResult getEntityByUsername(@RequestParam String username) {
ResponseEntity<CommonResult> entity = restTemplate.getForEntity("http://eureka-client/user/getByUsername?username={1}", CommonResult.class, username);
if (entity.getStatusCode().is2xxSuccessful()) {
return entity.getBody();
} else {
return new CommonResult("操作失败", 500);
}
}
POST
postForObject
定义:
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
<T> T postForObject(URI url, @Nullable Object request, Class<T> responseType);
举例:
@PostMapping("/create")
public CommonResult create(@RequestBody User user) {
return restTemplate.postForObject("http://eureka-client/user/create", user, CommonResult.class);
}
postForEntity
定义:
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
<T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType);
举例:
@PostMapping("/create")
public CommonResult create(@RequestBody User user) {
return restTemplate.postForEntity("http://eureka-client/user/create", user, CommonResult.class).getBody();
}
PUT
定义:
void put(String url, @Nullable Object request, Object... uriVariables);
void put(String url, @Nullable Object request, Map<String, ?> uriVariables);
void put(URI url, @Nullable Object request);
示例:
@PutMapping("/update")
public CommonResult update(@RequestBody User user) {
restTemplate.put("http://eureka-client/user/update", user);
return new CommonResult("操作成功",200);
}
DELETE
定义:
void delete(String url, Object... uriVariables);
void delete(String url, Map<String, ?> uriVariables);
void delete(URI url);
示例:
@DeleteMapping("/delete/{id}")
public CommonResult delete(@PathVariable Long id) {
restTemplate.delete("http://eureka-client/user/delete/{1}", null, id);
return new CommonResult("操作成功",200);
}