本章主要介绍 Ribbon 、RestTemplate 的一些相关的基本概念和用法,以及 RestTemplate 和 Ribbon 如何结合使用,对 Ribbon 有一个基本的印象,下一章我们再对其进行相关的源码分析。
负载均衡是一种基础的网络服务,核心原理是按照指定的负载均衡算法,将请求分配到后端服务集群上,从而为系统提供并行处理和高可用的能力。
负载均衡一般分为以下两种:
集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的负载均衡器,比如 F5,也有软件,比如 Nginx。
客户端负载均衡,客户端根据自己的请求情况做负载,Ribbon 就属于客户端自己做负载均衡的框架。
如上图所示,负债均衡器维护了需要负载的服务器实例相关信息,比如:服务1:192.168.1.100,服务2:192.168.1.100 两个实例。
客户端发送请求到负债均衡器,负载均衡器根据相关的负载均衡算法(随机、轮询、加权)选择其中一台服务器,将请求转发到服务器上。
如上图所示,和集中式负载均衡的不同是,客户端负载均衡器需要自己维护服务实例的信息,然后通过相关的负载均衡算法(随机、轮询、加权)从实例中选取一个实例,直接进行访问。
Ribbon 是 Netflix 公司开源的一个负载均衡的组件,它属于上述的第二种方式,将负载均衡逻辑封装在客户端中,并且运行在客户端的进程里。Ribbon 是一个经过了云端测试的 IPC 库,可以很好的控制 HTTP 和 TCP 客户端的负债均衡行为。
在 Spring Cloud 体系中,Ribbon 作为服务端消费者的负载均衡器,有三种使用方式:
RestTemplate 是 Spring Resources 中一个访问第三方 RESTful API 接口的网络请求框架。RestTemplate 的设计原则和 Spring 中其他 Template (例如 JdbcTemplate、JmsTemplate)类似,都是为了执行复杂任务提供了一个具有默认行为的模板方法。
示例
@RestController
public class RestTestController {
@Autowired
@Qualifier("restTemplate")
private RestTemplate restTemplate;
@GetMapping("/testRest")
public String testRest() {
String html = restTemplate.getForObject("http://www.baidu.com", String.class);
return html;
}
}
测试浏览器输入 http://127.0.0.1:8180/testRest
可以看到返回了该网页的 Html 字符串。
public class RibbonNativeClientDemo {
public static void main(String[] args) {
// 服务实例列表
List<Server> serverList = Arrays.asList(new Server("localhost", 8081),
new Server("localhost", 8083),
new Server("localhost", 8084),
new Server("localhost", 8085));
// 构建 LoadBalancer
BaseLoadBalancer baseLoadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);
// 设置负债均衡策略
baseLoadBalancer.setRule(new RandomRule());// 随机
// 测试
for (int i = 0; i < 10; i++) {
test(baseLoadBalancer);
}
}
private static void test(BaseLoadBalancer baseLoadBalancer) {
String result = LoadBalancerCommand.<String>builder().withLoadBalancer(baseLoadBalancer).build()
.submit(server -> {
try {
String addr = "http://" + server.getHost() + ":" + server.getPort();
System.out.println("调用地址:" + addr);
return Observable.just("success");
} catch (Exception e) {
return Observable.error(e);
}
}).toBlocking().first();
System.out.println("调用结果:" + result);
}
}
测试结果:
调用地址:http://localhost:8085
调用结果:success
15:41:34.944 [main] DEBUG com.netflix.loadbalancer.LoadBalancerContext - default using LB returned Server: localhost:8081 for request null
调用地址:http://localhost:8081
调用结果:success
15:41:34.945 [main] DEBUG com.netflix.loadbalancer.LoadBalancerContext - default using LB returned Server: localhost:8081 for request null
调用地址:http://localhost:8081
调用结果:success
15:41:34.945 [main] DEBUG com.netflix.loadbalancer.LoadBalancerContext - default using LB returned Server: localhost:8085 for request null
调用地址:http://localhost:8085
调用结果:success
15:41:34.945 [main] DEBUG com.netflix.loadbalancer.LoadBalancerContext - default using LB returned Server: localhost:8083 for request null
调用地址:http://localhost:8083
...
可以看到每次返回的服务器实例信息是随机的。
完整的代码请参考 github 上的项目,这里只展示核心的代码
我们先配置两个 RestTemplate,注入到 IoC 容器中
@LoadBalanced
注解的 RestTemplate,也就是集成 Ribbon@Configuration
public class RestTemplateConfig {
@Bean
@Qualifier("restTemplate")
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
@LoadBalanced
@Qualifier("loadBalancedRestTemplate")
public RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
}
对应的 Controller 我们同样写两个方法,使用 @Qualifier
分别依赖注入两个 RestTemplate
@RestController
public class RibbonController {
@Autowired
@Qualifier("restTemplate")
private RestTemplate restTemplate;
@Autowired
@Qualifier("loadBalancedRestTemplate")
private RestTemplate loadBalancedRestTemplate;
@GetMapping("/getUser")
public User getUser() {
User user = restTemplate.getForObject("http://localhost:8181/getUser", User.class);
return user;
}
@GetMapping("/ribbon/getUser")
public User getUserRibbon() {
User user = loadBalancedRestTemplate.getForObject("http://user-service/getUser", User.class);
return user;
}
}
启动&测试
这里我们需要起两个服务 user-service 的服务注册到 Eureka 注册中心上。
UserController 代码如下:
@RestController
public class UserController {
@Value("${server.port}")
private String port;
@GetMapping("getUser")
public User getUser() {
User user = new User();
user.setId(1L);
user.setName("小仙 port:" + port);
return user;
}
}
全部启动完成之后的效果如下:
注册中心
测试普通接口,浏览器输入 http://127.0.0.1:8180/getUser
,每次刷新返回的结果都一样,如下图所示:
测试负载均衡结果,浏览器输入 http://127.0.0.1:8180/ribbon/getUser
第一次刷新,结果如下图所示:
第二次刷新,结果如下图所示:
多次刷新,每次的返回结果的 port 端口号都不一样,表示请求每次调用的服务提供者都不一样,这说明已经完成负债均衡。
《深入理解 Spring Cloud 与微服务架构》 方志朋
《300分钟搞懂 Spring Cloud》尹吉欢