负载均衡 Ribbon 和 Feign
1. Ribbon 内容介绍
1.1 Ribbon 简介
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一R起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。
1.2 客户端”负载均衡“和服务端“负载均衡”
我R们用一张图来描述一下这两者的区别
这篇文章里面不会去解释nginx,如果不知道是什么的话,可以先忽略, 先看看这张图,其中:
-
服务端的负载均衡是一个url先经过一个代理服务器(这里是nginx),然后通过这个代理服务器通过算法(轮询,随机,权重等等…)反向代理你的服务,来完成负载均衡
-
客户端的负载均衡则是一个请求在客户端的时候已经声明了要调用哪个服务,然后通过具体的负载均衡算法来完成负载均衡
1.3 Quick Start
首先,我们还是要引入依赖,但是,eureka已经把ribbon集成到他的依赖里面去了,所以这里不需要再引用ribbon的依赖,如图:
要使用ribbon,只需要一个注解:
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
在RestTemplate上面加入@LoadBalanced注解,这样子就已经有了负载均衡, 怎么来证明?
我们这里现在启动了eureka集群(3个eureka) 和Power集群(2个power) 和一个服务调用者(User)
但是我们的User仅仅只需要调用服务,不需要注册服务信息,所以需要改一下配置文件:
配置什么意思就不做过多解释了,上面讲eureka的时候有讲到过
server:
port: 5000
eureka:
client:
registerWithEureka: false
serviceUrl:
defaultZone: http://localhost:3000/eureka/,http://eureka3001.com:3001/eureka,http://eureka3002.com:3002/eureka
然后启动起来的页面是这样子的
我们能看见 微服务名:SERVER-POWER4 下面有2个微服务(power-4,power5),现在我们来通过微服务名调用这个服务
这是我们的user项目的调用代码 :
private static final String URL="http://SERVER-POWER4";
@Autowired //在配置类中配置的“RestTemplate”Bean
private RestTemplate restTemplate;
@RequestMapping("/power.do")
public Object power(){
//从SERVER-POWER4调用power.do服务
return restTemplate.getForObject(URL+"/power.do",Object.class);
}
我们来看看效果:
这里可能有点抽象,需要自己去写才能体会到,但是我们已经完成了负载均衡, 他默认的负载均衡是轮询策略,也就是一人一次,下一节我们来讲一下他还有哪些策略。
1.4 核心组件:IRule
IRule是什么? 它是Ribbon对于负载均衡策略实现的接口, 怎么理解这句话? 说白了就是你实现这个接口,就能自定义负载均衡策略, 自定义我们待会儿来讲, 我们先来看看他有哪些默认的实现
这里是ribbon负载均衡默认的实现, 由于是笔记的关系,这里不好测试,只能你们自己去测试一下了, 具体怎么使用呢?
看代码:
@Bean
public IRule iRule(){
return new RoundRobinRule();
}
在Spring 的配置类里面把对应的实现作为一个Bean返回出去就行了。
1.5 自定义负载均衡策略
只要实现了IRule就可以完成自定义负载均衡,至于具体怎么来,我们这里先不讲
2. Feign 内容介绍
2.1 Feign 简介
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
2.2 Feign 作用
Feign旨在使编写Java Http客户端变得更容易。 前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
2.3 Feign使用
在客户端(User)引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在启动类上面加上注解:@EnableFeignClients
然后编写一个service类加上@FeignClient()注解 参数就是你的微服务名字
@FeignClient("SERVER-POWER")
public interface PowerServiceClient {
@RequestMapping("/power.do")
public Object power();
}
下面是调用代码:
@RestController
public class TestFeign {
@Autowired
private HelloFeignService helloFeignService;
@RequestMapping("/feignpower.do")
public String feignpower(){
return helloFeignService.power();
}
}
Feign集成了Ribbon
利用Ribbon维护了服务列表信息,并且融合了Ribbon的负载均衡配置,也就是说之前自定义的负载均衡也有效,这里需要你们自己跑一遍理解一下。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用
2.4 Feign 参数绑定
现实系统中的各种业务接口要比上一节复杂得多,我们会再HTTP的各个位置传入各种不同类型的参数,并且再返回响应的时候也可能是一个复杂的对象结构。再本节中,我们将详细介绍Feign中的不同形式参数的绑定方法。
再开始介绍Spring Cloud Feign的参数绑定之前,我们先用以下服务提供者hello-service-provider。使用下面这些接口,其中包含带有Request参数的请求、带有Header信息的请求、带有RequestBody的请求以及请求响应体中是一个对象的请求。
/**
* @param name
* @return Person
* @Description: post接口
* @create date 2018年5月19日上午9:44:08
*/
@RequestMapping(value = "/demo/postPerson", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
public Person postPerson(@RequestParam("name") String name) {
Person person = new Person();
person.setName(name);
person.setAge("10");
person.setSex("man");
return person;
}
/**
* @param person
* @return Person
* @Description: post接口
* @create date 2018年6月27日下午5:50:56
*/
@RequestMapping(value = "/demo/postPerson", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
public Person postPerson(@RequestBody Person person) {
person.setAge("10");
person.setSex("man");
return person;
}
/**
* @param name
* @return String
* @Description: get接口
* @create date 2018年5月19日上午9:46:34
*/
@RequestMapping(value = "/demo/getHost", method = RequestMethod.GET)
public String getHost(@RequestParam("name") String name) {
return "hi, " + name + "! i from " + ipAddress + ":" + port;
}
/**
* @param name
* @param age
* @return String
* @Description: get接口,包含header信息
* @create date 2018年6月27日下午5:43:29
*/
@RequestMapping(value = "/demo/getHost", method = RequestMethod.GET)
public String getHost(@RequestParam("name") String name, @RequestHeader Integer age) {
return "hi, " + name + ", your age is " + age + "! i from " + ipAddress + ":" + port;
}
在完成了对hello-service-provider的改造之后,下面我们开始在快速入门示例的kyle-service-feign应用中实现这些新增的绑定。
- 首先,在kyle-service-feign中创建Person类。
- 然后,在HelloServiceFeign接口中增加对上述三个新增接口的绑定声明,修改后,完成的HelloServiceFeign如下所示:
@FeignClient(value = "hello-service-provider")
public interface HelloServiceFeign {
@RequestMapping(value = "/demo/getHost", method = RequestMethod.GET, produces = "application/json")
public String getHost(@RequestParam("name") String name);
@RequestMapping(value = "/demo/postPerson", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
public Person postPerson(@RequestParam("name") String name);
@RequestMapping(value = "/body/postPerson", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
public Person postPerson(@RequestBody Person person);
@RequestMapping(value = "/head/getHost", method = RequestMethod.GET, produces = "application/json")
public String getHost(@RequestParam("name") String name, @RequestHeader("age") Integer age);
}
这里一定要注意,再定义各参数绑定时,@RequestParam、@RequestHeader等可以指定参数名称的主角,它们的value千万不能少。在Spring MVC程序中,这些注解会根据参数名来作为默认值,但是在Feign中绑定参数必须通过value属性来指明具体的参数名,不然会抛出IllegalStateException异常,value属性不能为空。
最后,在RestClientController中新增两个接口,来对本节新增的声明接口调用,修改后的完整代码如下所示:
/**
* @return Person
* @Description: post接口
* @create date 2018年6月27日下午5:50:56
*/
@RequestMapping(value = "/feign/project/postPerson", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
public Person postPerson() {
Person person = new Person();
person.setName("kyle");
return client.postPerson(person);
}
/**
* @param name
* @param age
* @return String
* @Description: get接口,包含header信息
* @create date 2018年6月27日下午5:43:29
*/
@RequestMapping(value = "/feign/head/getHost", method = RequestMethod.GET)
public String getHost(@RequestParam("name") String name, @RequestParam("name") Integer age) {
return client.getHost(name, age);
}
测试验证
在完成上述改造之后,启动服务注册中心、两个hello-service-privider服务以及我们改造的kyle-service-feign。通过发送GET请求到http://localhost:8868/feign/head/getHost?name=kyle&age=18,通过发送POST请求到http://localhost:8868/feign/project/postPerson,请求触发HelloServiceFeign对新增接口的调用。最终,我们会获得如下图的结果,代表接口绑定和调试成功。
新接口测试