SpringCloud(五)之Ribbon(负载均衡和源码追踪)

转载请注明出处:https://blog.csdn.net/weixin_41133233/article/details/85070323
本文出自 程熙cjp 的博客

继上一篇讲述完ribbon的概念和搭建服务和消费之后,本篇小熙将会讲述负载均衡以及源码追踪。

一. 准备环境

  1. SQL表数据

    注意小熙的mysql数据库是5.5的,版本不一致的可以提取表结构即可。

    /*
     Navicat Premium Data Transfer
    
     Source Server         : chengnuo
     Source Server Type    : MySQL
     Source Server Version : 50559
     Source Host           : localhost:3306
     Source Schema         : vue01
    
     Target Server Type    : MySQL
     Target Server Version : 50559
     File Encoding         : 65001
    
     Date: 19/12/2018 15:45:19
    */
    
    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for t_user
    -- ----------------------------
    DROP TABLE IF EXISTS `t_user`;
    CREATE TABLE `t_user`  (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `age` int(11) NULL DEFAULT NULL,
      `username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `password` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `sex` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
    
    -- ----------------------------
    -- Records of t_user
    -- ----------------------------
    INSERT INTO `t_user` VALUES (1, 20, '小歌', '789', 'xiaoge@163com', '女');
    INSERT INTO `t_user` VALUES (2, 21, 'lucy', '123', '[email protected]', '女');
    INSERT INTO `t_user` VALUES (3, 21, '小熙', '456', '[email protected]', '男');
    INSERT INTO `t_user` VALUES (4, 22, '小白', '123', '[email protected]', '男');
    
    SET FOREIGN_KEY_CHECKS = 1;
    
  2. 服务提供端代码结构:
    服务提供端代码结构
    (1). pojo类代码

    @Entity
    @Table(name = "t_user")
    @Data
    public class TUser {
    
        @Id
        @Column(name = "id")
        private Integer id;
    
        @Column(name = "age")
        private Integer age;
    
        @Column(name = "username")
        private String username;
    
        @Column(name = "password")
        private String password;
    
        @Column(name = "email")
        private String email;
    
        @Column(name = "sex")
        private String sex;
    }
    

    (2). dao层代码

    package com.chengxi.dao;
    
    import tk.mybatis.mapper.common.Mapper;
    import com.chengxi.pojo.TUser;
    @org.apache.ibatis.annotations.Mapper
    public interface TUserMapper extends Mapper<TUser> {}
    

    (3). service层代码

    接口类代码

    package com.chengxi.service;
    
    import com.chengxi.pojo.TUser;
    
    /**
     * @author chengxi
     * @date 2018/12/3 09:29
     */
    public interface UserService {
    
        /**
         * 通过id查找用户信息
         * @param id
         * @return
         */
       public TUser selectUserById(Integer id);
    
    
    }
    

    实现类代码

    package com.chengxi.service.Impl;
    
    import com.chengxi.dao.TUserMapper;
    import com.chengxi.pojo.TUser;
    import com.chengxi.service.UserService;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import javax.annotation.Resource;
    
    /**
     * @author chengxi
     * @date 2018/12/3 09:31
     */
    
    @Service
    @Transactional
    public class UserServiceImpl implements UserService {
    
        @Resource
        private TUserMapper tUserMapper;
    
        @Override
        public TUser selectUserById(Integer id) {
            return tUserMapper.selectByPrimaryKey(id);
        }
    }
    

    (4). controller层代码

    package com.chengxi.controller;
    
    import com.chengxi.pojo.TUser;
    import com.chengxi.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.Random;
    
    /**
     * @author chengxi
     * @date 2018/12/3 09:28
     */
    @RestController
    @RequestMapping(value = "/userService")
    public class UserController {
    
         @Autowired
         private UserService userServiceImpl;
    
         @GetMapping(value = "/{id}")
        public TUser selectUserById(@PathVariable Integer id) throws InterruptedException {
        
          return   userServiceImpl.selectUserById(id);
        }
    }
    
  3. 消费者端代码结构

    目前只关注controller层和pojo层就好,其他的后面会介绍
    消费端代码

    (1)controller层代码

    /**
     * @author chengxi
     * @date 2018/12/3 15:09
     */
    
    @RestController
    @RequestMapping(value = "/userConsumer")
    public class UserController {
    
        @Autowired
        private DiscoveryClient discoveryClient;
    
        @Resource
        private RestTemplate restTemplate;
    
        @GetMapping(value = "/{id}")
        public TUser selectUserById(@PathVariable Integer id){
            // 从注册服务中根据提供类的名称,获取对应的集群集合
            List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
            // 由于只测试了一个提供者,所以直接取第一个(从零开始),多个的时候涉及到负载均衡以及轮询等算法
            ServiceInstance serviceInstance = instances.get(0);
    
            // 根据获取的提供者信息,根据其中host获取对应的ip地址
            String ip = serviceInstance.getHost();
            // 根据获取的提供者信息,获取对应的port信息
            int port = serviceInstance.getPort();
    
            // 拼接url地址
            String url = "http://"+ip+":"+port+"/userService/"+id;
    
            ResponseEntity<TUser> forEntity = restTemplate.getForEntity(url, TUser.class);
            return forEntity.getBody();
        }
        }
    

    (2)pojo层代码

    这里的用户po类代码和服务提供端一样,以后可以提出一个po类项目,之后可以项目间依赖调用而不用重写,这里只是简单demo案例

    至此项目准备环境都完成了。

二.搭建服务端集群

 由于要展示ribbon的负载均衡的方式去访问服务端,所以要搭建服务端集群
 这里介绍一个简单的方法:

步骤如图:

搭建服务端集群

按照如上步骤,可以创建多个服务端集群节点,小熙由于电脑性能问题,就只创建两个节点了。

将项目全部启动,结果如图:

项目启动的注册列表

三. 测试负载均衡

  1. 第一种不使用@LoadBalanced注解找服务名称,最麻烦但最接近源码的调用方法。

    (注意将springboot启动配置类中restTemplate方法上的@LoadBalanced注解去掉,否则由于该注解拦截httpclient发出的请求,而找寻找服务名称而报找不到实例的错误,下面会在源码中详细解说)

    使用postman复杂测试

    在这里小熙使用的是postman测试的,也可以使用浏览器测试。这里主要是理解流程。

  2. 使用@LoadBalanced注解找服务名称,简单但是源码被封装了

    首先在该类中添加调用的方法:

     /**
         * 使用Ribbon的负载均衡抒写的查询
         * @param ids
         * @return
         */
        @GetMapping(value = "/selectUsers")
        public List<TUser> selectUsersById(@RequestParam ArrayList<Integer> ids){
            // 创建一个用户集合类,用于接收用户集合
            List<TUser> tUsers = new ArrayList<>();
    
            // 直接拼写服务端地址
            String serviceUrl = "http://user-service/userService/";
    
            // 使用java8新特性lambda表达式
            ids.forEach(id -> {
                // 执行多次查询,这里使用提供者的服务器名称进行路径访问,
                // 因为在restTemplate的配制方法上加了@LoadBalanced注解,所以在每次发送url请求时都会进行拦截,由Ribbon在服务中进行服务名对应的ip,port查找、替换然后再查询
                tUsers.add(restTemplate.getForEntity(serviceUrl + id, TUser.class).getBody());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
    
            return tUsers;
        }
    

    调用过程如图:

    测试简单的调用过程

四. 负载均衡源码解析

  1. 首先找到LoadBalancerInterceptor类,因为这个是Ribbon拦截请求的实现类
    负载均衡请求拦截类
    解释拦截

  2. 点击execute进入RibbonLoadBalancerClient类查看具体实现过程

    具体实现

  3. 点击ILoadBalancer进入BaseLoadBalancer类中(如果想深入理解可以深入看看)
    轮询方式

  4. 还是主要实现类RibbonLoadBalancerClient中的两个重要方法
    两个重要的方法

至此,大体的流程的简要概括清晰了吧。

大体流程

可能还会有同学问为什么我在restTemplate方法上添加@LoadBalanced注解就会被拦截呢?

别急下面给你解答:

全局搜索ctr+shift+f @LoadBalanced有哪些类用到了LoadBalanced有哪些类用到了, 发现LoadBalancerAutoConfiguration类,即LoadBalancer自动配置类。

第一张源码图
第二章源码图

在该类中,首先维护了一个被@LoadBalanced修饰的RestTemplate对象的List,在初始化的过程中,通过调用customizer.customize(restTemplate)方法来给RestTemplate增加拦截器LoadBalancerInterceptor。

而LoadBalancerInterceptor,用于实时拦截,在LoadBalancerInterceptor这里实现来负载均衡。LoadBalancerInterceptor的拦截方法又回到了上面刚讲解完的,进入源码的第一个方法intercept,如图:
拦截方法

至此,小熙将自己对于Ribbon源码的理解简单讲述了一下。如果理解有什么出入,还望大神们指点下哈。

嗯,本篇小熙简述完了负载均衡的实现和源码追踪,下一篇小熙将讲解hystrix熔断技术

猜你喜欢

转载自blog.csdn.net/weixin_41133233/article/details/85108698