负载均衡:Ribbon – 远程调用:RestTemplate & OpenFeign

  • Post author:
  • Post category:其他




概念

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。

是进程内负载均衡,即使服务内部的负载均衡

Eureka Server允许不同项目的应用名称,即spring.application.name相同。当应用名称相同时会认定这些项目一个集群。所以部署集群时都是设置相应的应用程序相同的应用名称.

注意: ​ Application Client会从Eureka Server中根据spring.application.name加载Application Service的列表。根据设定的负载均衡算法,从列表中取出一个合适的URL,到此Ribbon的事情结束了。(Ribbon的功能只是负责挑选众多Service中的一个url,挑完就没它事了)

主流的负载均衡解决方案有:集中式负载均衡和进程内负载均衡。

1.集中式负载均衡: 在客户端和服务端之间使用

独立

的负载均衡设施,也叫服务器端负载均衡.

可以是硬件,如F5, 也可以是软件,如nginx(注意这里的客户端指的是用户端,服务端是服务器)

2.进程内负载均衡: 将负载均衡逻辑

集成到客户端组件中

(注意不是用户端,是相对于Eureka Server中调用其他服务的服务型客户端)

客户端组件从服务注册中心获知有哪些地址可用,再从这些地址中选择出一个合适的服务端发起请求。

Ribbon就是一个进程内的负载均衡实现。也叫做:客户端负载均衡。

(注意: 这里的客户端指的是一个服务调用另一个服务(不涉及用户端,是内部服务间的调用),调用方是客户端,被调用方是服务端)



测试Ribbon负载均衡



搭建Eureka Server

pom.xml

<parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.12.RELEASE</version>
</parent>
    <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-server</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

yml

server:
  port: 8888
eureka:
  instance:
    prefer-ip-address: true
  client:
    # 因为当前项目为服务,不需要向服务注册自己,默认为true
    register-with-eureka: false
    # 因为当前为非集群版eureka,所以不需要同步其他节点数据
    fetch-registry: false
# 当server.port配置不是8761时需要配置内容
    service-url:
      defaultZone: http://127.0.0.1:${server.port}/eureka/



搭建Eureka Client


1.搭建Application Server集群


pom.xml

<parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.12.RELEASE</version>
</parent>
    <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>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR12</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

集群配置: 创建两个application Server 实例,及两个yml文件:


注意: application Server集群的spring.application.name要一致!!!


1.yml

# 此处应该定义名称,否则注册到Server后的名字为UNKNOWN
server:
  port: 8080
spring:
  application:
    name: application-server
# 注册中心地址,默认值是http://localhost:8761/eureka/,端口号不是8761,则要和Eureka Server一致
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8888/eureka/

application-server2.yml

server:
  port: 8081

spring:
  application:
    name: application-server
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8888/eureka/

通过两个配置文件创建application-server实例,及搭建的集群.


2.创建application Client


pom.xml


注意spring-cloud-starter-netflix-eureka-client里面就包含Ribbon,所以不需要额外导jar包

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
</parent>
    <dependencies>
        <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>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR12</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

yml配置

server:
  port: 9090
spring:
  application:
    name: application-client
    # 和Eureka Server一致
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8888/eureka/

client创建controller

import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
@RestController
public class ApplicationClient{
    /**
     * Ribbon提供的负载均衡器
     */
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    
    @RequestMapping("/fs")
    public String client() {
        ServiceInstance si = loadBalancerClient.choose("application-server");//这里是Application Server集群的统一名称
        // 获取Application Service IP。
        String port = String.valueOf(si.getPort());
        return port;
    }
}

可以看到浏览器轮询打印8080,8081



Ribbon负载均衡算法及配置

常用:

轮询策略(默认)

权重轮询策略(常用,中小型项目使用) :根据每个application service的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。

将轮询改为权重轮询策略

@Configuration
public class RibbonConfig {
    @Bean
    public WeightedResponseTimeRule getWeightedResponseTimeRule(){
        return new WeightedResponseTimeRule();
    }
}



服务之间的远程调用

两种方式:

RestTemplate: spring web 提供: 和eureka无关,就是服务之间发送到对应控制单元类似ajax的请求.

OpenFeign: spring cloud 提供 (常用)

发送的请求相当于ajax



RestTemplate

不需要额外导包,包含在spring-boot-starter-web中

和eureka无关,就是服务之间发送到对应控制单元类似ajax的请求.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


1.一个服务向另一个服务发送get请求

不传参数
    @Test
    void contextLoads() {
        RestTemplate   restTemplate=new RestTemplate();
        //发送请求  get post  put delete ..请求方式都可发送
        //  xxForObject() 和xxxForEntity() 区别
        //xxForObject() 返回的结果只有相应值
        //xxxForEntity() 返回结果 中不仅包含了相应的值 还有响应头 和响应体等信息
        String s = restTemplate.getForObject("http://192.168.163.1:8081/edit", String.class);
        System.out.println(s);
 
        ResponseEntity<String> entity = restTemplate.getForEntity("http://192.168.163.1:8081/edit", String.class);
        System.out.println(entity.getStatusCode());
        System.out.println(entity.getBody());
        System.out.println(entity.getHeaders());
    }
传参数
    @Test
    void contextLoads2() {
        RestTemplate   restTemplate=new RestTemplate();
        //====>请求方式のGET  GET不可以发送请求体数据!!!!
        //restTemplate.getForObject("http://192.168.163.1:8081/edit2?name=zs&age=19",String.class);
        
        // 在后面书写参数 在 前面进行参数的赋值 {1}...
        //restTemplate.getForObject("http://192.168.163.1:8081/edit2?name={1}&age={2}",String.class,"zs",19);
 
        //发送REST风格数据
        //restTemplate.getForObject("http://192.168.163.1:8081/edit3/{1}/{2}",String.class,"zs",19);
        //restTemplate.getForObject("http://192.168.163.1:8081/edit4?name={1}&age={2}",String.class,"zs",19);
 
        //发送数据 封装到map集合中 在取值的时候需要使用map的key 进行接收
        Map<String,Object>  map =new HashMap<>();
        map.put("a","zs");
        map.put("b",80);
        restTemplate.getForObject("http://192.168.163.1:8081/edit4?name={a}&age={b}",String.class,map);
    }


2.一个服务向另一个服务发送post请求

    @Test
    void contextLoads3() {
        RestTemplate   restTemplate=new RestTemplate();
        //====>请求方式のPOST
        //restTemplate.postForEntity("http://192.168.163.1:8081/edit2?name=zs&age=123",null,String.class);
        //restTemplate.postForEntity("http://192.168.163.1:8081/edit2?name={1}&age={2}",null,String.class,"zs",123);
        //restTemplate.postForEntity("http://192.168.163.1:8081/edit3/{1}/{2}",null,String.class,"zs",123);
        //restTemplate.postForEntity("http://192.168.163.1:8081/edit4?name={1}&age={2}",null,String.class,"zs",123);
 
        //POST请求可以传递请求体数据  例如一个对象 可以直接传递  一次请求中最多有一个请求体
        //Student  stu=new Student(111,"sxt");
        //restTemplate.postForEntity("http://192.168.163.1:8081/edit5",stu,String.class);
 
        //可以传递map集合
        Map<String,Object>  map =new HashMap<>();
        map.put("a","zs");
        map.put("b",80);
        restTemplate.postForEntity("http://192.168.163.1:8081/edit3/{a}/{b}",null,String.class,map);
    }


3.响应结果中有集合或泛型解决方法exchange

    @Test
    void contextLoads4() {
        RestTemplate   restTemplate=new RestTemplate();
        //exchange 方法可以发送任何请求  并且如何响应结果中含有泛型  使用ParameterizedTypeReference 直接指定即可
        ParameterizedTypeReference<List<Student>> pt=new ParameterizedTypeReference<List<Student>>() {};
        ResponseEntity<List<Student>> responseEntity = restTemplate.exchange("http://192.168.163.1:8081/list", HttpMethod.GET, null, pt);
        List<Student> list = responseEntity.getBody();
        System.out.println(list);
        System.out.println(list.get(0).getName());
    }


发送数据设置请求头和响应体HttpEntity

 		//HttpEntity: 进行请求体数据封装 里面不仅包含了请求体数据 还包含了 请求头 都可以进行设置
        //传递的请求体数据
        Student stu=new Student(19,"zs");
        //请求头
        MultiValueMap<String,String> map =new LinkedMultiValueMap<>();
        map.add("abc","xxx");
        HttpEntity httpEntity=new HttpEntity(stu,map);
 
        restTemplate.exchange("http://192.168.163.1:8081/edit5",HttpMethod.POST,httpEntity,String.class);



RestTemplate和Ribbon实现Application Client调用Application Service集群


方法1:实际上就是利用Ribbon的负载均衡器,获取不同的 uri 进行拼接成url,然后用RestTemplate动态的发送请求

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @RequestMapping("/fs")
    public String show(){
        ServiceInstance choose = loadBalancerClient.choose("e-server");
        URI uri = choose.getUri();//http://localhost:8081 || http://localhost:8080

        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.getForObject(uri + "/save", String.class);

        return result;
    }

方法2.利用Eureka

创建RestTemplate对象的方法,增加注解


LoadBalanced – 把Spring Cloud封装的LoadBalancerClient于RestTemplate整合。


让RestTemplate自带负载均衡能力。仅在当前的Ribbon环境中生效。

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


@RestController
public class DemoController {
    @Autowired
    private RestTemplate restTemplate;
    
    @RequestMapping("/fs")
    public String show(){
        String result = restTemplate.getForObject("http://e-server(application server集群名称)/save", String.class(返回值类型));
        return result;
    }
}



OpenFeign

声明式调用是指,就像调用本地方法一样调用远程方法.

1.Application Service向Eureka Server 注册服务, Application Client从Eureka Server中发现服务。

2.在Application Client中调用OpenFeign接口中方法,Application Client中OpenFeign通过应用程序名访问Application Service。

3.OpenFeign访问远程服务时,基于Ribbon实现负载均衡。



hello world

一.创建Eureka Server,详细参考

Eureka


二.创建Application Server,详细参考

Eureka


三.创建Application Client


1…导入依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.12.RELEASE</version>
</parent>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR12</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<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>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>


2.yml配置文件

server:
  port: 9999
spring:
  application:
    name: eureka-client-open-feign

假设远程调用的Application Server的spring.profiles.name为e-server

远程调用其的控制单元为:

    @RequestMapping("/save")
    public String show(){
        return "hello world";
    }


3.接下来在Application Client中的service包下创建AppClientOpenfeignClient接口

@FeignClient("e-server")//这里是Eureka Server中的application Server的spring.profiles.name名称
public interface AppClientOpenfeignClient{
    @RequestMapping("/save")//方法名可以不一致外,其余要一样(直接把除方法体外的复制过来就行)
    public String show();
}


4.直接注入后调用

@RestController
public class DemoController {
    @Autowired
    private AppClientOpenfeignClient appClientOpenfeignClient;

    @RequestMapping("/fs")
    public String show(){
        String show = appClientOpenfeignClient.show();
        System.out.println(show);
        return show;
    }
}

5.启动类上加@EnableFeignClients(basePackages = {“feign所在包”})

/**
 * EnableFeignClients - 开启Openfeign技术。让spring cloud扫描Openfeign相关注解,
 * 生成动态代理实现对象。
 *  可选属性 basePackages = {"feign接口所在包1", "feign接口所在包2"}
 *  默认扫描当前类型所在包,及所有子孙包。
 */
@SpringBootApplication
@EnableFeignClients(basePackages = {"com.feign"})
public class OpenFeignAppClientApp {
    public static void main(String[] args) {
        SpringApplication.run(OpenFeignAppClientApp.class, args);
    }
}


5.直接访问http://localhost:9999/fs


浏览器输出hello world.



参数传递

调用方法类型,返回值,参数顺序及个数要一致

//定义注解 书写应用名称 底层采用就是动态代理
@FeignClient("APPSERVICE")
public interface StudentFeign {
    //4、建议:本地feign中方法名称建议和远程中一致
    //   建议:本地feign中方法形参名称建议和远程中方法形参保持一致
    @RequestMapping("/edit")
    public    String  edit();
 
 
    //参数列表默认传递参数都是请求体数据 对每一个参数前加@RequestBody
    //如果我们传递是普通表单数据 必须增加@RequestParam
    @RequestMapping("/edit2")
    String  edit2(@RequestParam("name") String name,
                  @RequestParam("age") int age);
 
    //REST风格参数 要求 参数列表必须增加@PathVariable 尽管 形参名称和路径中名称一致
    //也建议增加name属性
    @RequestMapping("/edit3/{name}/{age}")
    String  edit3(@PathVariable("name") String name,
                  @PathVariable("age") int age);
 
    @RequestMapping("/edit4")
    String  edit4(@RequestParam("name") String name,
                  @RequestParam("age") int age);
 
    //默认就是请求体数据 在远程端必须使用@RequestBoby
    @RequestMapping("/edit5")
    String  edit5(@RequestBody Student student);
 
    @RequestMapping("/one")
    Student  findOne();
 
    @RequestMapping("/list")
    List<Student>  findAll();

}

gzip…



版权声明:本文为m0_56182317原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。