3、Spring Cloud之软负载均衡 Ribbon

前言

上一节咱们已经学习了Eureka 注册中心,其实咱们也使用到了Ribbon ,只是当时咱们没有细讲,因此咱们如今一块儿来学习一下Ribbon。html

什么是Ribbon

以前接触到的负载均衡都是硬负载均衡,什么是硬负载均衡呢?硬负载均衡就是在以往的大型系统中,会有单独一套系统来负责负载均衡策略,咱们因此的请求都会先走到负载均衡的系统上,进行分配到不一样的服务器处理。
好比咱们熟悉的nginx 。其实就能够算做一个负载均衡的系统,客户端请求的接口会先经过nginx 的负载均衡策略分配到不一样的服务器上。
在这里插入图片描述
那Ribbon 不是这样的吗?那又是怎样的呢?
Ribbon 是和 Eureka 同样是Netflix 推出的开源产品,它能够和Eureka 完成无缝结合,Ribbon 主要实现客户端负载均衡。
那什么是客户端负载均衡呢?
就是在客户端请求的时候,就经过均衡策略将请求分发到不一样的服务器上,以下图
在这里插入图片描述
这个图是根据上节的Eureka 的架构图改编来的,主要的流程仍是没有变,服务消费者和服务提供者都会注册到服务中心,而后服务消费者会从服务中心获取可用实例列表 ,这里就会经过负载均衡策略选择其中一个实例进行访问。java

好了咱们来用代码来看一下。linux

demo

咱们为了简化,注册中心服务端,咱们仍是用上节的单节点。怎么配置我不说了。而后咱们新建一个module 用来作服务提供者,其实也能够用上一节的服务提供者,可是我怕揉在一块儿很差,因此就全都分开了,不过不少代码都是同样的。nginx

服务提供者

咱们新建一个模块后pom.xml 文件以下:git

<parent>
        <groupId>cn.quellanan</groupId>
        <artifactId>SpringCloud</artifactId>
        <version>1.0.0</version>
    </parent>

    <groupId>com.quellanan.springcloud</groupId>
    <artifactId>ribbon-provider-9004</artifactId>
    <version>1.0.0</version>
    <name>ribbon-provider-9004</name>
    <description>ribbon-provider-9004 服务提供者</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

主要就是引入了eureka-client 能够注册到注册中心去,而后启动类加上@EnableEurekaClient 注解
在这里插入图片描述
在配置文件中加上配置以下:程序员

server.port=9004
spring.application.name=ribbon-provider
eureka.client.service-url.defaultZone=http://localhost:8000/eureka/

咱们在建立一个测试类:HelloControllergithub

@RestController
@Slf4j
public class HelloController {

    @Value("${server.port}")
    private String port;

    @RequestMapping("/hello")
    public String hello(){
        log.info(port);
        return "hello "+port;
    }
}

这样咱们一个服务提供者就弄好了,为了看出负载均衡的效果,咱们还须要建立一个同样的服务提供者。或者不一样的端口启动。咱们将端口改成9005.其余的和上面同样。web

服务消费者

服务提供者有了,咱们再来建立一个服务消费者的模块。pom.xml 较服务提供者就多了一个ribbon 的依赖spring

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

而后启动类中加上@EnableEurekaClient 和 RestTemplatejson

@SpringBootApplication
@EnableEurekaClient
public class RibbonConsumerApplication {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(RibbonConsumerApplication.class, args);
    }

}

@LoadBalanced 注解就是来实现客户端负载均衡的。

配置文件中加入以下配置:

server.port=9003
#服务名,在注册时所用
spring.application.name=ribbon-consumer
eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/

最后咱们来写一个调用服务提供者的接口,建立一个IndexController类,内容以下

@RestController
public class IndexController {
    private static final String applicationName = "ribbon-provider";

    @Autowired
    private RestTemplate restTemplate;
    @RequestMapping("/index")
    public String getHello(){
        String url = "http://"+ applicationName +"/hello";
        return  restTemplate.getForObject(url,String.class);
    }
}

测试

咱们如今来启动服务中心,两个服务提供者,一个服务消费者。启动以后咱们输入:

http://localhost:8000/

在这里插入图片描述
重点看下ribbon-provider 有两个端口,分别对应的咱们的两个服务提供者。他们的appliaction.name是相同的。
再来调下面接口看看

http://localhost:9003/index

在这里插入图片描述
能够看到每次调用访问了不一样的服务提供者,两个服务端提供者是轮寻调用的。从而实现客户端的负载均衡

RestTemplate

上面说的负载均衡,其实仍是RestTemplate 对象加上@LoadBalanced来实现的。而且前面只是简单的调用,没有涉及参数和请求方式,接下来咱们看看常见的请求方式和有参数的调用。

Get 请求

其实咱们以前写的就是get 请求的方式,咱们在来写一个有参数的请求

@RequestMapping("index2")
    public String getHello2(){
        String url = "http://"+ applicationName +"/hello2?name={1}";
        return  restTemplate.getForObject(url,String.class,"quellanan");
    }

能够看到url 中请求的参数有占位符代替,getForObject或者getForEntity的第三个参数就是咱们实际传的参数。这里说明一下getForObject 是直接获取返回的内容,而getForEntity返回的是一个http对象,包含相应状态码,想要回去内容须要getForEntity().getBody() 才行。

那若是多个参数的呢?
多个参数的经常使用的有两种方式,一个是和上面同样,直接在后面加参数就行了以下:

@RequestMapping("index3")
    public String getHello3(){
        //多个参数拼接
        String url = "http://"+ applicationName +"/hello3?name={1}&age={2}";
        return  restTemplate.getForObject(url,String.class,"quellanan","18");
    }

还有一种方式就是将参数封装到map 中,传过去。同样的也能够解析

@RequestMapping("index4")
    public String getHello4(){
        //多参数组装
        Map<String,String> parms=new HashMap<>();
        parms.put("name","quellanan");
        parms.put("age","18");
        String url = "http://"+ applicationName +"/hello3?name={name}&age={age}";
        return  restTemplate.getForObject(url,String.class,parms);
    }

咱们在提供者中写两个方法便于测试

@RequestMapping("/hello2")
    public String hello2(@RequestParam("name") String name){
        log.info(name);
        return "hello "+name+port;
    }
    @RequestMapping("/hello3")
    public String hello3(@RequestParam("name") String name,@RequestParam("age") String age){
        log.info(name+age);
        return "hello "+name+age+port;
    }

咱们启动来看下结果
在这里插入图片描述
能够看到参数是传递成功的啦。

Post 请求

post 请求和get 请求差很少,我这里就将参数封装到map中了
postForEntity

@RequestMapping("index6")
    public String getHello6(){
        //postForEntity
        JSONObject params=new JSONObject();
        params.put("name","quellanan");
        params.put("age","18");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity request = new HttpEntity(params.toJSONString(), headers);
        String url = "http://"+ applicationName +"/hello4";
        return  restTemplate.postForEntity(url,request,String.class).getBody();
    }

postForObject

@RequestMapping("index7")
    public String getHello7(){
        //postForObject
        JSONObject params=new JSONObject();
        params.put("name","quellanan");
        params.put("age","18");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity request = new HttpEntity(params.toJSONString(), headers);
        String url = "http://"+ applicationName +"/hello4";
        return  restTemplate.postForObject(url,params,String.class);
    }

主要是先将参数封装在JSONObject 中,而后设置HttpHeaders 和HttpEntity ,而后请求。
咱们采用的application/json 的格式,咱们在服务提供者中加一个方法。用来接收json格式的参数。

@RequestMapping("/hello4")
    public String hello4(@RequestBody Map<String, Object> parms){
        return "hello "+parms.get("name")+parms.get("age")+port;
    }

如今咱们启动看下效果。
在这里插入图片描述
证实都是能够正常传递的。

Feign 简化微服务调用

上面咱们能够看到,咱们进行微服务调用,不论是使用get 或者post 方法,带有参数过多就会致使代码变得很臃肿,因此咱们就可使用一样是netflix 推出的Feign 来简化微服务调用,Feign 结合了Ribbon 以及Hystrix.Ribbon的功能它都有,而且还进行了封装,更加方便咱们使用。因此咱们使用的时候,只用引入 Feign 的依赖就能够。

那咱们如今对上面的这些进行调整一下。

pom.xml

在咱们的ribbon-consumer 的pom 文件中删除ribbon 的依赖,增长feign 的依赖。

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
          </dependency>

增长@EnableFeignClients

在启动类中增长@EnableFeignClients 注解
在这里插入图片描述
在启动类中增长了@EnableFeignClients 注解,就能够在项目中使用Fegin 啦,那咱们怎么使用呢?
其实还要使用@FeignClient 注解,这个注解是在具体的应用中注入的,用来指定服务提供者的服务名称。而且这个注解只能做用 interface 上。
前面咱们调用服务提供者的接口须要写url,参数,返回类型等等很是的繁琐,因此Fegin 就帮咱们进行了简化,让咱们调用服务提供者的接口,能够像自身调用同样,很是放方便。

HelloService

咱们来建立一个HelloService 的接口。内容以下

@FeignClient("ribbon-provider")
public interface HelloService {

    @RequestMapping("/hello")
    public String hello();

    @RequestMapping("/hello2")
    public String hello2(@RequestParam(value = "name") String name);

    @RequestMapping("/hello3")
    public String hello3(@RequestParam(value = "name") String name,@RequestParam(value = "age") String age);

    @RequestMapping("/hello4")
    public String hello4(@RequestBody Map<String, Object> parms);

}

能够看到,上面的接口内容主要是针对服务提供者暴露出的几个接口进行调用。
在这里插入图片描述
对比服务消费者中的接口,和服务提供者中的接口,能够发现实际上是服务提供者中的接口对消费者中的HelloService 的实现。这个待会再说。从HelloService 中咱们能够看到,咱们调用服务提供者的接口同样的采用@RequestMapping,@RequestParam,@RequestBody来操做,这样更加简洁和方便。

FeginController

咱们再建立一个FeginController 来测试一下,内容以下:

@RestController
public class FeginController {

    @Autowired
    public HelloService helloService;


    @RequestMapping("/fegin")
    public String getHello(){
        return  helloService.hello();
    }

    @RequestMapping("/fegin2")
    public String getHello2(){
        String name="quellanan";
        return  helloService.hello2(name);
    }


    @RequestMapping("/fegin3")
    public String getHello3(){
        String name="quellanan";
        String age="18";
        return  helloService.hello3(name,age);
    }

    @RequestMapping("/fegin4")
    public String getHello4(){
        Map<String, Object> parms=new HashMap<>();
        parms.put("name","quellanan");
        parms.put("age","18");
        return  helloService.hello4(parms);
    }
}

能够看到就是普通的controller层调用service 层。

测试

好了咱们如今来测试一下。
分别输入以下地址来看看效果:

http://localhost:9003/fegin
http://localhost:9003/fegin2
http://localhost:9003/fegin3
http://localhost:9003/fegin4

在这里插入图片描述

继承

好了,来讲最后一个问题,刚刚上面咱们说了服务消费者中的HelloService 和服务提供者的HelloController很像,感受像是HelloController 实现了HelloService 。不错,当咱们正式开发的时候,会发现接口调用很是多,而且也很复杂,若是按照上面的方式来的话,会存在不少的重复代码且很容易出错,因此咱们能够将服务调用单独提取成一个模块么,而后分别在服务提供者和服务消费者中引入其依赖,而后在消费中的HelloService 继承其对应的接口。而在服务提供者中实现其对应的接口。固然@FeignClient仍是在服务消费者之中的。

这里只是提供了一种思路,没有给出实现方式,感兴趣的能够看看《Spring cloud 微服务实战》,也能够和我讨论下嘿嘿。

番外

总算是写完了,算是对ribbon 和fegin 有了一些了解,最起码如今可使用他们,并将其使用到到项目中没有什么问题啦。

代码上传到github:
https://github.com/QuellanAn/springcloud

后续加油♡

很荣幸,今年参加了CSDN博客之星评选活动,帮忙投下票,能够投5票 ,谢谢您

CSDN博客之星评选活动

最后啦,
欢迎你们关注我的公众号 "程序员爱酸奶"

分享各类学习资料,包含java,linux,大数据等。资料包含视频文档以及源码,同时分享本人及投递的优质技术博文。

若是你们喜欢记得关注和分享哟❤

file

相关文章
相关标签/搜索