微服务(ribbon 负载均衡器)

ribbon

概念:

Ribbon是一种客户端负载平衡器,可让您对HTTP和TCP客户端的行为进行大量控制(借助spring封装类RestTemplate,所有的入参,请求URL及出参数都是自己配置)。Feign已使用Ribbon,因此,如果使用@FeignClient,则本节也适用。

Ribbon中的中心概念是指定客户的概念。每个负载均衡器都是组件的一部分,这些组件可以一起工作以按需联系远程服务器,并且该组件具有您作为应用程序开发人员提供的名称(指定远程调用的服务名称,例如,使用@FeignClient批注)。根据需要,Spring Cloud通过使RibbonClientConfiguration为每个命名的客户端创建一个新的集合作为ApplicationContext。其中包含ILoadBalancer,RestClient和ServerListFilter。

工作流程:

实现过程:

 模拟场景:根据商品编号查询商品中,要使用订单对象,在展示商品的同时,展示订单

  根据order_server_1 创建order_server_2,order_server_3 

  创建goods_server_ribbon微服务  

1)引入jar

考虑到微服务项目之间调用都可以使用ribbon方式,直接把jar放到micro_services项目pom.xml

 <!--ribbon包-->
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>2.2.10.RELEASE</version>
</dependency>

2)创建数据库(如果有数据库也是可以直接用,这边只是做示范)

-- 创建商品 模拟

CREATE TABLE `tb_product` (

  `id` bigint(20) NOT NULL AUTO_INCREMENT,

  `shop_id` bigint(20) NOT NULL COMMENT '店铺ID',

  `brand_id` bigint(20) DEFAULT NULL COMMENT '品牌ID',

  `category_id` bigint(20) DEFAULT NULL COMMENT '产品类别ID',

  `name` varchar(64) NOT NULL,

  `pic` varchar(255) DEFAULT NULL,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

INSERT INTO `tb_product` VALUES ('1', '1001', '2001', '3001', '上衣', 'path1');

INSERT INTO `tb_product` VALUES ('2', '1002', '2002', '3003', '男裤', 'path2');

-- 给商品表添加字段

alter  table tb_product add order_id int;

-- 查看表结构

desc tb_product;

3)添加配置
#当前服务端口号
server:
  port: 8091

#  servlet:
#    #配置上下文对象  访问的项目名称
#    context-path: /ordera
#阿里druid连接池配置
spring:
  datasource:
    druid:
      url: jdbc:mysql://localhost:3306/00_1226db?useSSL=false&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      initial-size: 5
      max-active: 20
      min-idle: 10
      max-wait: 10

  #应用名称,向注册中心注册时非常有用
  application:
    #当前服务要注册的名字
    name: productRibbonService
  cloud:
    nacos:
      discovery:
        #nacos服务器的地址
        server-addr: localhost:8848

mybatis-plus:
  # mapper.xml配置
  mapper-locations: classpath:mapper/*.xml
  configuration:
    #控制台日志输出配置
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  #别名包配置
  type-aliases-package: com.common.entity

#swagger配置
swagger:
  base-package: com.product.ribbon.controller
  title: "电商项目-商品模块-商品swagger"
  description: "描述"
  version: "3.0"
  contact:
    name: "AAA"
    email: "test@163.com"
    url: "https://www.baidu.com"
  terms-of-service-url: "服务条款:https://www.baidu.com"

4)RestTemplate 用法 

RestTemplate简介:

       spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接, 我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。   spring模板模式的体现)

例如:

  //使用RestTemplate发起http请求,获取订单对象
        Result res =
                restTemplate.getForObject(
                        "http://orderService/order/getById?orderId="+orderId,
                        Result.class
                );

RestTemplate常用方法:

public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables){}

public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)

public <T> T getForObject(URI url, Class<T> responseType)     

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables){}

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables){}

public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType){}

public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables)

            throws RestClientException {}

public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables)

            throws RestClientException {}

public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException {}

https://www.cnblogs.com/javazhiyin/p/9851775.html

5)两种方式配置ribbon
方式1:

配置类RibbonConfiguration:

package com.aaa.gs.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RoundRobinRule;
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;
import java.util.Random;

@Configuration
public class RibbonConfig {
    /**
     * 实例化RestTemplate 并添加负载均衡配置
     * @return
     */
    @Bean
    @LoadBalanced  //远程调用服务时,如果远程服务是多个,使用负载均衡
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    /**
     * 实例化IRule的子类 配置负载均衡算法
     * @return
     */
    @Bean
    public IRule iRule(){
        //随机
        //return new RandomRule();
        //轮询
        return new RoundRobinRule();
    }
}        
方式2:

 启动类上添加注解:

  @RibbonClient(name = "ribbonc",configuration = RibbonConfig.class)
        启动类上添加方法:
        @Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}

编写MyRule:

       @Configuration
public class RibbonConfig {
     /**
     * 实例化IRule的子类 配置负载均衡算法
     * @return
     */
    @Bean
    public IRule iRule(){
        //随机
        //return new RandomRule();
        //轮询
        return new RoundRobinRule();
    }
 
}      

业务层:

package com.aaa.gs.service.impl;
import com.aaa.core.bo.Order;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.api.R;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.aaa.gs.dao.ProductDao;
import com.aaa.gs.entity.Product;
import com.aaa.gs.service.ProductService;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import springfox.documentation.spring.web.json.Json;
import javax.annotation.Resource;
import java.io.Serializable;
/**
 * (Product)表服务实现类
 *
 * @author makejava
 * @since 2022-02-19 09:38:00
 */
@Service("productService")
public class ProductServiceImpl extends ServiceImpl<ProductDao, Product> implements ProductService {

    @Resource
    private RestTemplate restTemplate;

    @Override
    public Product getById(Serializable id) {
        //根据商品id查询商品
        Product product = this.baseMapper.selectById(id);
        //int orderId = product.getOrderId;
        //假如商品中有订单id
        // url  http://服务名称一定是想调用服务注册到注册中心的名称
        R resultR = restTemplate.getForObject("http://orderService/order/selectOne/8", R.class);
       //toJSONString  json对象转为字符串
        Order orderBo = JSON.parseObject(JSON.toJSONString(resultR.getData()), Order.class);
        product.setOrder(orderBo);
        //product.setR(resultR);
        return product;
    }
}

7)测试:

先启动注册中心(eureka/nacos),再启动3个提供者order_server,最后启动消费者goods_server

http://localhost:14131/swagger-ui/index.html#/product-controller/selectOneUsingGET

http://localhost:14131/product/selectOne/2

http://localhost:14820/product/queryById?id=1

七种负载均衡算法:

   1RoundRobinRule----默认算法,轮询 

     2RandomRule----随机   

     3、AvailabilityFilteringRule----会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问

     4WeightedResponseTimeRule----根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,会切换到WeightedResponseTimeRule

       在任意一个服务上,让它速度变慢,修改负载均衡策略,再测试效果:

    /*try {

        //让响应速度变慢

        Thread.sleep(500);

    } catch (InterruptedException e) {

        e.printStackTrace();

    }*/

    /**

 * 配置负载均衡算法

 * @return

 */

@Bean  //<bean id=iRule class=com.netflix.loadbalancer.IRule>

public IRule   iRule(){

     //使用随机算法

     //return new RandomRule();

     //使用轮询算法

     //return new RoundRobinRule();

     //按照响应时间,响应时间越短的,权重会高 更多的请求会到该服务器

     return new WeightedResponseTimeRule();

}

5、RetryRule----先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务

     6,BestAvailableRule----会先过滤掉由于多次访问故障而处于断路器跳闸(打开)状态的服务,然后选择一个并发量最小的服务

     

     7、ZoneAvoidanceRule----默认规则,复合判断server所在区域的性能和server的可用性选择服务器

随机算法的源码解析   

package com.aaa.gs.config;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

public class MyCustomRule  extends AbstractLoadBalancerRule {
    public MyCustomRule() {
    }
    /**
     *  从众多正常工作的服务器选择一台进行请求
     * @param lb
     * @param key
     * @return
     */
    //SuppressWarnings抑制警告
    @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            System.out.println("ILoadBalancer开始--------"+lb+"--------ILoadBalancer开始");
            Server server = null;
            //只要后台有参与负载均衡的服务器,必然要返回一个服务
            while(server == null) {
                /**
                 * 判断刚在获取到正常的服务器是否线程中断(出现故障)
                 */
                if (Thread.interrupted()) {
                    return null;
                }
               //获取正在运行的服务器
                List<Server> upList = lb.getReachableServers();
                System.out.println("正在运行的服务器:"+upList);
                List<Server> allList = lb.getAllServers();
                System.out.println("所有的服务器:"+allList);
                int serverCount = allList.size();
                System.out.println("服务器数量:"+serverCount);
                if (serverCount == 0) {
                    return null;
                }
                //随机算法
                int index = this.chooseRandomInt(serverCount);
                System.out.println("随机出的数字必须是:0-"+(serverCount-1)+"中的一个是:"+index);
               //按照随机的下标获取对象
                server = (Server)upList.get(index);
                System.out.println("当前被选中的服务器为:"+server);
                //再次判断
                if (server == null) {
                    //让出CPU时间,让其他线程再次获取server
                    Thread.yield();
                } else {
                    //再次判断没有活着
                    if (server.isAlive()) {
                        return server;
                    }
                    server = null;
                    Thread.yield();
                }
            }
            return server;
        }
    }

    public Server choose(Object key)
    {
        return this.choose(this.getLoadBalancer(), key);
    }
    /**
     * 随机算法方法
     * @param serverCount
     * @return
     */
    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

openfeign:

Spring Cloud OpenFeign

简介:

OpenFeign是一种声明式、模板化的HTTP客户端。是server to server 服务到服务的相互调用的一个组件,推荐使用,和ribbon比较,更符合程序员编写代码的习惯,集成了ribbon 可以进行负载均衡

                  1)声明式rest客户端 组件(写在消费者/客户端)

                    2) 支持springmvc注解 (@RequestMapping @GetMapping @PutMapping @PostMapping...@RequestParam  @RequestBoby   @ResponseBody  @PathVariable...)

                    3) 集成了ribbon ,支持负载均衡

运行流程:

实现过程:

社交服务中评论对象远程调用查询订单服务中的订单对象

1)引入jar(在micro_services引入,所有微服务都可能使用openfeign

<!--openfeign-->

 <dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-openfeign</artifactId>

  </dependency>

2)创建评论表,创建项目,反向工程生成代码

CREATE TABLE  tb_comment (

id  bigint(20) PRIMARY key NOT NULL AUTO_INCREMENT ,

shop_id  bigint(20) NOT NULL ,

order_id  bigint(20) NULL DEFAULT NULL COMMENT '订单ID' ,

product_id  bigint(20) NULL DEFAULT NULL COMMENT '订单为单一商品时,该字段有值' ,

member_nick_name  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,

product_name  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,

star  int(3) NULL DEFAULT NULL COMMENT '评价星数:0->5' 

);

INSERT INTO tb_comment (id, shop_id, order_id, product_id, member_nick_name, product_name, star) VALUES ('1', '1', '1', '1', '1', '1', '1');

INSERT INTO tb_comment (id, shop_id, order_id, product_id, member_nick_name, product_name, star) VALUES ('2', '2', '2', '2', '2', '2', '2');

INSERT INTO tb_comment (id, shop_id, order_id, product_id, member_nick_name, product_name, star) VALUES ('3', '1', '11', '11', '测试昵称', '测试商品', '11');

INSERT INTO tb_comment (id, shop_id, order_id, product_id, member_nick_name, product_name, star) VALUES ('24', '0', '0', '0', '1', '1', '0');

INSERT INTO tb_comment (id, shop_id, order_id, product_id, member_nick_name, product_name, star) VALUES ('25', '0', '0', '0', '1', '1', '0');

INSERT INTO tb_comment (id, shop_id, order_id, product_id, member_nick_name, product_name, star) VALUES ('28', '0', '0', '0', '1', '1', '0');

3)编写启动类,添加配置文件application.yml配置

启动类:

@SpringBootApplication

@MapperScan("com.aaa.ss.dao")

@EnableSwagger2

@EnableDiscoveryClient // 开启发现客户端功能  任何注册中心都可以

application.yml配置:

#当前服务端口号

server:

  port: 14132

#  servlet:

#    #配置上下文对象  访问的项目名称

#    context-path: /ordera

#阿里druid连接池配置

spring:

  datasource:

    druid:

      url: jdbc:mysql://localhost:3306/db_qy141?useUnicode=true&characterEncoding=utf8

      username: root

      password: root

      initial-size: 5

      max-active: 20

      min-idle: 10

      max-wait: 10

  application:

    #当前应用的名称  注册后,注册中心会显示该名称,其他服务调用时,也是使用该名称

    name: snsService

  cloud:

    nacos:

      discovery:

        server-addr: localhost:8848

mybatis-plus:

  # mapper.xml配置

  mapper-locations: classpath:mapper/*.xml

  configuration:

    #控制台日志输出配置

    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

  #别名包配置

  type-aliases-package: com.aaa.ss.entity

#swagger配置

swagger:

  base-package: com.aaa.ss.controller

  title: "电商项目-商品模块-商品swagger"

  description: "描述"

  version: "3.0"

  contact:

    name: "AAA"

    email: "test@163.com"

    url: "https://www.baidu.com"

  terms-of-service-url: "服务条款:https://www.baidu.com"

#eureka:

#  client:

#    #eureka客户端注册域地址

#    service-url:

#      defaultZone: http://localhost:14112/eureka/

   

4) 启动类新加:

  @EnableFeignClients

5)服务接口
package com.aaa.ss.service;
import com.baomidou.mybatisplus.extension.api.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
/**
 * @ fileName:RemoteOrderService
 * @ description:
 * @ author:zhz
 * @ createTime:2022/2/21 9:23
 * @ version:1.0.0
 */
//通过声明式注解调用远程服务,在name/value必须配置要调用远程服务在注册中心注册的名称
@FeignClient(value = "orderService")
public interface RemoteOrderService {

    /**
     * 远程接口调用时 1,要求返回值必须和远程方法的返回值一致
     *               2,要求参数必须和远程方法的参数一致 并且如果是普通属性必须加@RequestParam
     *                  如果是对象则必须使用json必须加@RequestBody
     *               3, 必须使用restfull风格调用 请求方式和资源定义方式必须一致 也就是说
     *                   远程方法的注解是 @GetMapping,本地调用远程方法,也必须是@GetMapping
     * @param id
     * @return
     */
    @GetMapping("/order/selectOne")
    public R selectOrderOne(@RequestParam("id") Integer id);
}

远程接口调用时 注意事项:

                  1,要求返回值必须和远程方法的返回值一致

                  2,要求参数必须和远程方法的参数一致 并且如果是普通属性必须加@RequestParam  如果对象必须使用json必须加@RequestBody

                  3, 必须使用restfull风格调用 请求方式和资源定义方式必须一致 也就是说  

                      远程方法的注解是 @GetMapping,本地调用远程方法,也必须是@GetMapping

6)  本地服务合并数据方法

package com.aaa.ss.service.impl;
import com.aaa.ss.service.RemoteOrderService;
import com.baomidou.mybatisplus.extension.api.R;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.aaa.ss.dao.CommentDao;
import com.aaa.ss.entity.Comment;
import com.aaa.ss.service.CommentService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.Serializable;
/**
 * (Comment)表服务实现类
 *
 * @author makejava
 * @since 2022-02-21 09:11:50
 */
@Service("commentService")
public class CommentServiceImpl extends ServiceImpl<CommentDao, Comment> implements CommentService {
    //依赖注入
    @Resource
    private RemoteOrderService remoteOrderService;
    /**
     * 在当前方法中,封装上远程请求到的订单数据
     * @param id
     * @return
     */
    @Override
    public Comment getById(Serializable id) {
        //在评论中封装上订单数据
        Comment comment = baseMapper.selectById(id);
        Long orderId = comment.getOrderId();
        //获取远程数据
        R result = remoteOrderService.selectOrderOne(Integer.valueOf(orderId.toString()));
        comment.setResult(result);
        return comment;
    }
}
7)测试:

先启动注册中心,再启动提供者(order_server_a,b,c),再启动消费者(sns_server_feign)

 openfeign和ribbon有什么区别(面试题):

         1), 启动方式不同

              ribbon 使用:@RibbonClient

                feign  使用: @EnableFeignClients

          2),负载均衡位置不同

              ribbon在RibbonClient注解上配置

              feign  在接口注解上@feignclient 配置的

          3),ribbon借助于RestTemplate使用的Http和Tcp协议,实现服务调用和负载均衡

                feign  使用程序猿习惯的调用方式,调用接口,支持springmvc的注解  底层使用代理 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值