Spring-Cloud组件之ribbon、IRule 、Feign
ribbon是什么?
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。
客户端负载均衡? 服务端负载均衡?
服务端的负载均衡是一个url先经过一个代理服务器(这里是nginx),然后通过这个代理服务器通过算法(轮询,随机,权重等等…)反向代理你的服务,来完成负载均衡
客户端的负载均衡则是一个请求在客户端的时候已经声明了要调用哪个服务,然后通过具体的负载均衡算法来完成负载均衡
接下来我们看看如何使用吧
这个项目还是基于前面的eureka项目来做的。对于其他操作我就不详细讲解了,只在原来的基础上继续做更改了。可以先看我(Spring-Cloud组件之eureka)。然后eureka已经把ribbon集成到他的依赖里面去了,所以这里不需要再引用ribbon的依赖。
其实只需要在调用方在注入RestTemplate 的时候,加上@LoadBalanced注解就可以了。
@Configuration
@ComponentScan("com")
public class AppConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
为了看到效果,需要把power项目拷贝一份,改一下端口就可以了。启动项目。
其实现在就已经拥有负载均衡的能力了。但是凭啥这就能达到负载均衡了呢?我们继续研究研究。其实这里呢,我还真讲不清楚了,我仔细看了一下江南一点雨大佬写的博客,超级棒,我感觉能看他的就可以了,我就不来抄袭一遍了。
接下来我们来聊聊 Ribbon对于负载均衡策略-------IRule
SpringCloud-IRule自带7种负载均衡算法:
1.RoundRobinRule–轮询
2.RandomRule–随机
3.AvailabilityFilteringRule --会先过滤掉由于多次访问故障处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对于剩余的服务列表按照轮询的策略进行访问
4.WeightedResponseTimeRul–根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越大。刚启动时如果同统计信息不足,则使用轮询的策略,等统计信息足够会切换到自身规则
5.RetryRule– 先按照轮询的策略获取服务,如果获取服务失败则在指定的时间内会进行重试,获取可用的服务
6.BestAvailableRule --会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务
7.ZoneAvoidanceRule – 默认规则,复合判断Server所在区域的性能和Server的可用行选择服务器。
我们来看看该如何自己选择需要的算法来实现负载均衡呢?
在AppConfig类中 加一个IRule的注入,然后返回需要的算法对象就可以啦。
@Configuration
@ComponentScan("com")
public class AppConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
public IRule iRule(){
return new RandomRule();
}
}
我们来瞅一下RandomRule的源码
public class RandomRule extends AbstractLoadBalancerRule {
Random rand = new Random();
public RandomRule() {
}
@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while(server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
int index = this.rand.nextInt(serverCount);
server = (Server)upList.get(index);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
不难理解了,其实大概意思呢就是 取出所有对应的server集合,然后随机选择一个就可以了(实际上是更深层次的东西我还没玩懂)。这样启动后我们来调试一下就不难发现,这个真的是一个随机的负载均衡算法了。
看了这个,我有点按捺不住了,我想自己也跟着做一个属于自己的算法来实现负载均衡。不过这里呢我做了一个超级简单的,如果我学习到其他好玩的算法了一定会分享出来的。好了,话不多说,我们来弄一个属于自己的负载均衡算法。我们在这个随机算法的基础之上加一点东西吧,就是假如一个List的下标连续随机到两次后,第三次还是这个这个下标,那么我们在随机一次吧。代码如下
/**
* 自定义负载均衡策略
* 目标:在随机策略的情况下,如果出现一个下标已被随机到2次了,第三次如果还是这个下标的话,那就再随机一次
*/
public class UserRule extends AbstractLoadBalancerRule {
Random rand = new Random();
public UserRule() {
rand = new Random();
}
private int lastIndex = -1;
private int nowIndex = -1;
private int skipIndex = -1;
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while(server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
int index = this.rand.nextInt(serverCount);
System.out.println("当前下标为:"+index);
if(skipIndex>=0 && index == skipIndex){
System.out.println("跳过");
index = rand.nextInt(serverCount);
System.out.println("跳过后的下标为:"+index);
}
skipIndex = -1;
nowIndex = index;
if(nowIndex == lastIndex){
System.out.println("下一次需要跳过的下标是:"+nowIndex);
skipIndex = nowIndex;
}
lastIndex = nowIndex;
server = (Server)upList.get(index);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
我们在改一下AppConfig
@Configuration
@ComponentScan("com")
public class AppConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
public IRule iRule(){
return new UserRule();
}
}
现在我们启动eurekaServer,user,power。服务提供方(power)就先启动一个(随机这个东西靠运气的,所以还是先启动一个我们先看看代码是否生效,如果生效后我们再多启动几个power看看效果),我们来调试一下,先看看如何代码运行的效果。
控制台打印出来了,因为我们就一个power,也就是list集合里面就一个数,所以一直是0,但是不难发现我们的算法已经生效了。
既然玩到这里了,那么我们再来看看实际的项目场景吧,不难想象,我们真实的项目肯定不止一个服务提供方的,并且肯定不会全部采用一种算法(当然,也有公司自己封装的算法,然后所有都会用这一种了),既然我们是学习嘛,那就需要想到各种场景,接下来就是我们有两个服务提供方,user在调用两个服务方的时候,一个采用随机算法,另一个采用轮询算法。我们要怎么做呢?
先拷贝一下power项目取名 sorder,然后改一下配置参数,如下:
server:
port: 7000
eureka:
client:
service-url:
defaultZone: http://eureka3000.com:3000/eureka,http://eureka3001.com:3001/eureka,http://eureka3002.com:3002/eureka #Eureka服务端提供的注册地址
instance:
instance-id: server-order #此实例注册到Eureka服务端的唯一的实例ID
prefer-ip-address: true #是否显示IP
lease-renewal-interval-in-seconds: 10 #Eureka 客户端需要多长时间发送心跳给Eyreka服务器,表明他还活着 ,默认为30 秒 (与下面配置的单位都是秒)
lease-expiration-duration-in-seconds: 30 #Eureka 服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
spring:
application:
name: server-order #此实例注册到Eureka服务端的name
然后把controller改一下:
@RestController
public class OrderController {
@RequestMapping("/getOrder.do")
public Object getPower(){
Map<String,Object> map = new HashMap<>();
map.put("key","order");
return map;
为了看到效果,我们吧sorder项目拷贝一份,取名sorder1,然后吧端口改一下,返回参数改为order1。
接下来我们还是要改一下调用方user项目了,新建两个类。如下:
@Configuration
public class OrderRuleConfig {
@Bean
public IRule iRule(){
return new RandomRule();
}
}
@Configuration
public class PowerRuleConfig {
@Bean
public IRule iRule(){
return new UserRule();
}
}
接下来我们改一下启动类:
@SpringBootApplication
@EnableEurekaClient
@RibbonClients({
@RibbonClient(name = "SERVER-POWER",configuration = PowerRuleConfig.class),
@RibbonClient(name = "SERVER-ORDER",configuration = OrderRuleConfig.class),
})
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class);
}
}
我们在改一下UserController:
@RestController
public class UserController {
private final static String POWER_URL="http://SERVER-POWER";
private final static String ORDER_URL="http://SERVER-ORDER";
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/getUser")
public Object getUser(){
Map<String,Object> map = new HashMap<>();
map.put("key","user");
return map;
}
@RequestMapping("/getPower")
public Object getPower(){
return restTemplate.getForObject(POWER_URL+"/getPower.do",Object.class);
}
@RequestMapping("/getOrder")
public Object getOrder(){
return restTemplate.getForObject(ORDER_URL+"/getOrder.do",Object.class);
}
}
记得在PowerRuleConfig和OrderRuleConfig 中打上断点,然后debug启动。
我们访问http://localhost:6001/getOrder就会进OrderRuleConfig 中的断点,访问http://localhost:6001/getPower 就会进PowerRuleConfig的断点
好了,这个多个算法我们也实现了。是不是超级开森,毕竟又学会了一个好玩的东西。
但是你们有没有发现我们的UserController那样写是不是太难看了?所以我们继续来学习下一个东西。
Spring Cloud 声明式服务调用 -----Feign
自我介绍一下:Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
我能干啥:Feign旨在使编写Java Http客户端变得更容易。 前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
看着这些比较官方的介绍是不是很难受?作为一个程序猿,看到文字头疼,还是来看看代码吧,看了代码你就知道干啥的了。
先把user项目的pom.xml加一个依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
然后我们再新建一个OrderServiceClinet接口,代码如下:
@FeignClient("SERVER-ORDER")
public interface OrderServiceClinet {
@RequestMapping(value = "/getOrder.do")
public Object order();
}
FeignClient括号里面填写的就是在eureka对应的 Application
RequestMapping 不需要多说,请求地址。
我们再多写一个PowerServiceClinet来加深记忆吧
@FeignClient("SERVER-POWER")
public interface PowerServiceClinet {
@RequestMapping(value = "/getPower.do")
public Object power();
}
我们再来改一下UserController,
@RestController
public class UserController {
private final static String POWER_URL="http://SERVER-POWER";
private final static String ORDER_URL="http://SERVER-ORDER";
@Autowired
private RestTemplate restTemplate;
@Autowired
private OrderServiceClinet orderServiceClinet;
@Autowired
private PowerServiceClinet powerServiceClinet;
@RequestMapping("/getUser")
public Object getUser(){
Map<String,Object> map = new HashMap<>();
map.put("key","user");
return map;
}
@RequestMapping("/getPower")
public Object getPower(){
return restTemplate.getForObject(POWER_URL+"/getPower.do",Object.class);
}
@RequestMapping("/getOrder")
public Object getOrder(){
return restTemplate.getForObject(ORDER_URL+"/getOrder.do",Object.class);
}
@RequestMapping("/getFeignPower")
public Object getFeignPower(){
return powerServiceClinet.power();
}
@RequestMapping("/getFeignOrder")
public Object getFeignOrder(){
return orderServiceClinet.order();
}
}
改一下启动类:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@RibbonClients({
@RibbonClient(name = "SERVER-POWER",configuration = PowerRuleConfig.class),
@RibbonClient(name = "SERVER-ORDER",configuration = OrderRuleConfig.class),
})
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class);
}
}
启动后,我们访问getPower和访问getFeignPower返回是一样的,那就说明我们又成功了。这样写是不是简单很多了?看起来好看多了。
好了,不多说了,今天已经写的够多了。如果觉得写得不错,点个关注呗,我们一起学习微服务和分布式。如果大家还有有什么好的建议或者有想学的技术都可以留言,我可以去学习一下,然后学会了会写博客分享的。感谢大家啦~