您的位置:首页 > 编程语言 > Java开发

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

2018-12-19 21:35 399 查看

转载请注明出处: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', 'lucy@163.com', '女');
    INSERT INTO `t_user` VALUES (3, 21, '小熙', '456', 'xiaoxi@163.com', '男');
    INSERT INTO `t_user` VALUES (4, 22, '小白', '123', 'xiaobai@163.com', '男');
    
    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类代码和 4000 服务提供端一样,以后可以提出一个po类项目,之后可以项目间依赖调用而不用重写,这里只是简单demo案例

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

二.搭建服务端集群

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

步骤如图:

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

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

三. 测试负载均衡

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

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

    在这里小熙使用的是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熔断技术

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: