• SpringCloud Ribbon 负载均衡的服务调用


    Ribbon简介

    实现负载均衡方式1:通过服务器端实现负载均衡(nginx)

    (nginx服务端负载均衡.jpg)

    实现负载均衡方式2:通过客户端实现负载均衡。

    客户端Ribbon负载均衡.jpg

    Ribbon是什么

    Ribbon是Netflix发布的云中间层服务开源项目,其主要功能是提供客户端实现负载均衡算法。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,Ribbon是一个客户端负载均衡器,我们可以在配置文件中Load Balancer后面的所有机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器,我们也很容易使用Ribbon实现自定义的负载均衡算法。

    下图展示了Eureka使用Ribbon时的大致架构:

    Ribbon工作图.jpg

    创建一个user-service-8301模块

    一,pom.xml需要添加的依赖:

    <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>
    

    二,application.yml的配置:

    server:
      port: 8301
    spring:
      application:
        name: user-service
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://replica1:8001/eureka/
    

    三,在src目录下新建一些包,例如controller、domain、service

    domain包下分别新建User类和CommonResult类

    User类的代码:

    package com.tiger.springcloud.domain;
    
    public class User {
        private Long id;
        private String username;
        private String password;
    
        public User(){}
    
        public User(Long id, String username, String password){
            this.id = id;
            this.username = username;
            this.password = password;
        }
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    

    CommonResult的代码:

    package com.tiger.springcloud.domain;
    
    public class CommonResult<T> {
        private T data;
        private String message;
        private Integer code;
    
        public CommonResult(){}
    
        public CommonResult(T data, String message, Integer code){
            this.data = data;
            this.message = message;
            this.code = code;
        }
    
        public CommonResult(String message, Integer code) {
            this(null, message, code);
        }
    
        public CommonResult(T data) {
            this(data, "操作成功", 200);
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    }
    

    service包下新建一个UserService接口和一个UserService实现类

    UserService的代码:

    package com.tiger.springcloud.service;
    
    import com.tiger.springcloud.domain.User;
    
    import java.util.List;
    
    public interface UserService {
        void create(User user);
    
        User getUser(Long id);
    
        void update(User user);
    
        void delete(Long id);
    
        User getByUsername(String username);
    
        List<User> getUserByIds(List<Long> ids);
    }
    

    UserService实现类的代码:

    package com.tiger.springcloud.service.impl;
    
    import com.tiger.springcloud.domain.User;
    import com.tiger.springcloud.service.UserService;
    import org.springframework.stereotype.Service;
    import org.springframework.util.CollectionUtils;
    
    import javax.annotation.PostConstruct;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;
    
    @Service
    public class UserServiceImpl implements UserService {
        private List<User> userList;
    
        public void create(User user) {
            userList.add(user);
        }
    
        public User getUser(Long id) {
            List<User> findUserList = userList.stream().filter(userItem -> userItem.getId().equals(id)).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(findUserList)) {
                return findUserList.get(0);
            }
            return null;
        }
    
        public void update(User user) {
            userList.stream().filter(userItem -> userItem.getId().equals(user.getId())).forEach(userItem -> {
                userItem.setUsername(user.getUsername());
                userItem.setPassword(user.getPassword());
            });
        }
    
        public void delete(Long id) {
            User user = getUser(id);
            if (user != null) {
                userList.remove(user);
            }
        }
    
        public User getByUsername(String username) {
            List<User> findUserList = userList.stream().filter(userItem -> userItem.getUsername().equals(username)).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(findUserList)) {
                return findUserList.get(0);
            }
            return null;
        }
    
        public List<User> getUserByIds(List<Long> ids) {
            return userList.stream().filter(userItem -> ids.contains(userItem.getId())).collect(Collectors.toList());
        }
    
        @PostConstruct
        public void initData() {
            userList = new ArrayList<>();
            userList.add(new User(1L, "tiger1_service1", "123456"));
            userList.add(new User(2L, "tiger2_service1", "123456"));
            userList.add(new User(3L, "tiger3_service1", "123456"));
        }
    }
    

    controller包下UserController的代码:

    package com.tiger.springcloud.controller;
    
    
    import com.tiger.springcloud.domain.CommonResult;
    import com.tiger.springcloud.domain.User;
    import com.tiger.springcloud.service.UserService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    
    @RestController
    @RequestMapping("/user")
    public class UserController {
        private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
    
        @Autowired
        private UserService userService;
    
        @PostMapping("/create")
        public CommonResult create(@RequestBody User user) {
            userService.create(user);
            return new CommonResult("操作成功", 200);
        }
    
        @GetMapping("/{id}")
        public CommonResult<User> getUser(@PathVariable Long id) {
            User user = userService.getUser(id);
            LOGGER.info("根据id获取用户信息,用户名称为:{}",user.getUsername());
            return new CommonResult<>(user);
        }
    
        @GetMapping("/getUserByIds")
        public CommonResult<List<User>> getUserByIds(@RequestParam List<Long> ids) {
            List<User> userList= userService.getUserByIds(ids);
            LOGGER.info("根据ids获取用户信息,用户列表为:{}",userList);
            return new CommonResult<>(userList);
        }
    
        @GetMapping("/getByUsername")
        public CommonResult<User> getByUsername(@RequestParam String username) {
            User user = userService.getByUsername(username);
            return new CommonResult<>(user);
        }
    
        @PostMapping("/update")
        public CommonResult update(@RequestBody User user) {
            userService.update(user);
            return new CommonResult("操作成功", 200);
        }
    
        @PostMapping("/delete/{id}")
        public CommonResult delete(@PathVariable Long id) {
            userService.delete(id);
            return new CommonResult("操作成功", 200);
        }
    }
    

    启动类的代码:

    package com.tiger.springcloud;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @EnableDiscoveryClient
    @SpringBootApplication
    public class UserServiceApplication8301 {
        public static void main(String[] args) {
            SpringApplication.run(UserServiceApplication8301.class, args);
        }
    }
    

    再创建一个user-service-8302模块

    user-service-8302模块参考user-service-8301模块的依赖配置

    user-service-8302的application.yam的配置:

    server:
      port: 8302
    spring:
      application:
        name: user-service
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://replica1:8001/eureka/
    

    创建一个ribbon-service模块

    一,pom.xml需要添加的依赖:

    <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-netflix-ribbon</artifactId>
            </dependency>
        </dependencies>
    

    二,application.yml的配置:

    server:
      port: 8303
    spring:
      application:
        name: ribbon-service
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://replica1:8001/eureka/
    service-url:
      user-service: http://user-service
    

    注意:service-url.user-service指向的是user-service这个名字,也就是user-service-8301模块和user-service-8302模块的spring.application.name

    三,src目录下相关的包有config,controller, domain

    domain包下的User类和CommonResult类的代码跟user-service-8301模块和user-service-8302模块的User类和CommonResult类的代码一样。

    config包下的RibbonConfig类的代码:

    package com.springcloud.ribbon.config;
    
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.client.RestTemplate;
    
    @Configuration
    public class RibbonConfig {
    
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    }
    

    controller包下的UserRibbonController代码:

    package com.springcloud.ribbon.controller;
    
    import com.springcloud.ribbon.domain.CommonResult;
    import com.springcloud.ribbon.domain.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.client.RestTemplate;
    
    @RestController
    @RequestMapping("/user")
    public class UserRibbonContoller {
    
        @Autowired
        private RestTemplate restTemplate;
    
        @Value("${service-url.user-service}")
        private String userServiceUrl;
    
        @GetMapping("/{id}")
        public CommonResult getUser(@PathVariable Long id) {
            return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
        }
    
        @GetMapping("/getByUsername")
        public CommonResult getByUsername(@RequestParam String username) {
            return restTemplate.getForObject(userServiceUrl + "/user/getByUsername?username={1}", CommonResult.class, username);
        }
    
        @GetMapping("/getEntityByUsername")
        public CommonResult getEntityByUsername(@RequestParam String username) {
            ResponseEntity<CommonResult> entity = restTemplate.getForEntity(userServiceUrl + "/user/getByUsername?username={1}", CommonResult.class, username);
            if (entity.getStatusCode().is2xxSuccessful()) {
                return entity.getBody();
            } else {
                return new CommonResult("操作失败", 500);
            }
        }
    
        @PostMapping("/create")
        public CommonResult create(@RequestBody User user) {
            return restTemplate.postForObject(userServiceUrl + "/user/create", user, CommonResult.class);
        }
    
        @PostMapping("/update")
        public CommonResult update(@RequestBody User user) {
            return restTemplate.postForObject(userServiceUrl + "/user/update", user, CommonResult.class);
        }
    
        @PostMapping("/delete/{id}")
        public CommonResult delete(@PathVariable Long id) {
            return restTemplate.postForObject(userServiceUrl + "/user/delete/{1}", null, CommonResult.class, id);
        }
    }
    

    结果演示

    • 启动eureka-server-8001启动类
    • 启动user-service-8301启动类
    • 启动user-service-8302启动类
    • 启动ribbon-service-8303启动类

    访问http://replica1:8001,如下图所示:

    ribbonEureka.png

    访问http://replica1:8303/user/1

    userServiceRibbon1.png

    刷新或刷新几次http://replica1:8303/user/1的页面变化如下图所示:

    userServiceRibbon2.png

    Ribbon的常用配置

    全局配置

    ribbon:
      ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
      ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
      OkToRetryOnAllOperations: true #对超时请求启用重试机制
      MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
      MaxAutoRetries: 1 # 切换实例后重试最大次数
      NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法
    

    指定服务进行配置

    与全局配置的区别就是ribbon节点挂在服务名称下面,如下是对ribbon-service调用user-service时的单独配置。

    user-service:
      ribbon:
        ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
        ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
        OkToRetryOnAllOperations: true #对超时请求启用重试机制
        MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
        MaxAutoRetries: 1 # 切换实例后重试最大次数
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法
    

    Ribbon的负载均衡策略

    所谓的负载均衡策略,就是当A服务调用B服务时,此时B服务有多个实例,这时A服务以何种方式来选择调用的B实例,ribbon可以选择以下几种负载均衡策略。

    • com.netflix.loadbalancer.RandomRule:从提供服务的实例中以随机的方式;
    • com.netflix.loadbalancer.RoundRobinRule:以线性轮询的方式,就是维护一个计数器,从提供服务的实例中按顺序选取,第一次选第一个,第二次选第二个,以此类推,到最后一个以后再从头来过;
    • com.netflix.loadbalancer.RetryRule:在RoundRobinRule的基础上添加重试机制,即在指定的重试时间内,反复使用线性轮询策略来选择可用实例;
    • com.netflix.loadbalancer.WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择;
    • com.netflix.loadbalancer.BestAvailableRule:选择并发较小的实例;
    • com.netflix.loadbalancer.AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例;
    • com.netflix.loadbalancer.ZoneAwareLoadBalancer:采用双重过滤,同时过滤不是同一区域的实例和故障实例,选择并发较小的实例。

    RestTemplate的使用

    RestTemplate是一个HTTP客户端,使用它我们可以方便的调用HTTP接口,支持GET、POST、PUT、DELETE等方法。

    GET请求方法

    <T> T getForObject(String url, Class<T> responseType, Object... uriVariables);
    
    <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables);
    
    <T> T getForObject(URI url, Class<T> responseType);
    
    <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables);
    
    <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables);
    
    <T> ResponseEntity<T> getForEntity(URI var1, Class<T> responseType);
    

    getForObject方法

    返回对象为响应体中数据转化成的对象,举例如下:

    @GetMapping("/{id}")
    public CommonResult getUser(@PathVariable Long id) {
        return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
    }
    

    getForEntity方法

    返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等,举例如下:

    @GetMapping("/getEntityByUsername")
    public CommonResult getEntityByUsername(@RequestParam String username) {
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(userServiceUrl + "/user/getByUsername?username={1}", CommonResult.class, username);
        if (entity.getStatusCode().is2xxSuccessful()) {
            return entity.getBody();
        } else {
            return new CommonResult("操作失败", 500);
        }
    }
    

    POST请求方法

    <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
    
    <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
    
    <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType);
    
    <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
    
    <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
    
    <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType);
    

    postForObject示例

    @PostMapping("/create")
    public CommonResult create(@RequestBody User user) {
        return restTemplate.postForObject(userServiceUrl + "/user/create", user, CommonResult.class);
    }
    

    postForEntity示例

    @PostMapping("/create")
    public CommonResult create(@RequestBody User user) {
        return restTemplate.postForEntity(userServiceUrl + "/user/create", user, CommonResult.class).getBody();
    }
    

    PUT请求方法

    void put(String url, @Nullable Object request, Object... uriVariables);
    void put(String url, @Nullable Object request, Map<String, ?> uriVariables);
    void put(URI url, @Nullable Object request);
    

    PUT请求示例

    @PutMapping("/update")
    public CommonResult update(@RequestBody User user) {
        restTemplate.put(userServiceUrl + "/user/update", user);
        return new CommonResult("操作成功",200);
    }
    

    DELETE请求方法

    void delete(String url, Object... uriVariables);
    void delete(String url, Map<String, ?> uriVariables);
    void delete(URI url);
    

    DELETE请求示例

    @DeleteMapping("/delete/{id}")
    public CommonResult delete(@PathVariable Long id) {
       restTemplate.delete(userServiceUrl + "/user/delete/{1}", null, id);
       return new CommonResult("操作成功",200);
    }
    

    注意:以上内容学习于掘金博主MacroZheng

    参考:

    SpringCloud实战微服务之——Ribbon详解 https://blog.csdn.net/u013087513/article/details/79775306

    Spring Cloud Ribbon:负载均衡的服务调用 https://juejin.im/post/5d7f9006f265da03951a260c#heading-22

    作者:tiger_yam
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。
  • 相关阅读:
    80年代的兄弟,你会什么?
    设计模式单件模式
    大冒险 这注定是部史诗级的探索。。。
    关于重构JS前端框架的失败经验(顺便怀念那些死去的代码)
    JDBC连接数据库类(主要用于存储过程)
    ActiveRecord学习(六):总结
    ASP.NET中常用的文件上传下载方法
    [整理]ASP.NET2.0新特性概述
    关于NHibernate中one to many 的问题
    关注06德国世界杯:比赛日程表
  • 原文地址:https://www.cnblogs.com/tiger-yam/p/13298905.html
Copyright © 2020-2023  润新知