微服务技术负载均衡Ribbon

举报
tea_year 发表于 2023/12/10 19:51:39 2023/12/10
【摘要】 nacos 注册中心: 负责服务的注册和发现 提供者注册服务消费者要消费服务 注册中心都是遵循设计模式的6大原则之一 迪米特法则(门店模式)的产物,可以降低服务之间的耦合度。 搭建nacos服务端 (单机和集群版) 客户端 配置中心: 配置文件集中管理,方便不同环境切换 服务端编写配置(dataid namespace group), 客户端使用bootstrap引入(namespace...

nacos

注册中心: 负责服务的注册和发现 提供者注册服务消费者要消费服务 注册中心都是遵循设计模式的6大原则之一 迪米特法则(门店模式)的产物,可以降低服务之间的耦合度。

搭建nacos服务端 (单机和集群版) 客户端

配置中心: 配置文件集中管理,方便不同环境切换

服务端编写配置(dataid namespace group), 客户端使用bootstrap引入(namespace隔离用户或者不同环境 group 相同的namespace下同名不同组配置)

2,本章重点

ribbon (负载均衡器)如何实现服务到服务的调用

openfeign 服务到服务的调用

3,具体内容

3.1 ribbon

https://www.springcloud.cc/spring-cloud-brixton.html#spring-cloud-ribbon

3.1.1 概念

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

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


3.1.2 工作流程



3.1.3 实现过程

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

根据order_server_a 创建order_server_b,order_server_c

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

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

   order_id  int comment '订单ID',

  PRIMARY KEY (`id`)

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


 

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

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



3),添加application.yml配置

#当前服务端口号

server:

port: 14131

# 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: goodsService

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.gs.entity

#swagger配置

swagger:

base-package: com.aaa.gs.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),RestTemplate 用法

RestTemplate简介:

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

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

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;

/**

* @ fileName:RibbonConfig

* @ description:

* @ author:zhz

* @ createTime:2022/2/19 9:53

* @ version:1.0.0

*/

@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();

}

}       

   

  ribbon配置方法2:

  启动类上添加注解:

  @RibbonClient(name = "ribbonc",configuration = RibbonConfig.class)

        启动类上添加方法:

        @Bean

@LoadBalanced

public RestTemplate restTemplate(){

return new RestTemplate();

}

/**

* 在启动类配置RestTemplate模板类

* @return

*/

@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();

    }

 

}      

6) 业务类的编写

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



3.1.4 七种负载均衡算法

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

     2、RandomRule----随机

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

     4、WeightedResponseTimeRule----根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。刚启动时如果统计信息不足,则使用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的可用性选择服务器

3.1.5 随机算法的源码解析

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;

/**

* @ fileName:MyCustomRule

* @ description:

* @ author:zhz

* @ createTime:2022/2/19 11:21

* @ version:1.0.0

*/

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

}

}



3.2 openfeign

https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/

3.2.1简介:

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

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

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

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

3.2.2 运行流程:


3.2.3 实现过程:

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

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)


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

1), 启动方式不同

ribbon 使用:@RibbonClient

feign 使用: @EnableFeignClients

2),负载均衡位置不同

ribbon在RibbonClient注解上配置

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

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

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

4,知识点总结

ribbon 客户端负载均衡器

openfeign 内置ribbon 使用接口的方式进行远程调用

5,本章面试题


5.1 微服务之间是如何独立通讯的

同步:dubbo 使用rpc(rpc参考帖子),springcloud使用restful传递json(ribbon,openfeign)

异步:使用消息队列框架(MQ:kafka,activeMQ,rabbitMq RocketMQ等等)

https://blog.csdn.net/lizhiqiang1217/article/details/89682328


5.2 ribbon 和nginx 区别:

1)Ribbon是从注册中心服务器端上获取服务注册信息列表,缓存到本地,然后在本地实现负载均衡策略一种客户端负载平衡器

nginx是客户端所有请求统一交给 nginx,由 nginx 进行实现负载均衡请求转发,一个服务器端负载均衡。

2) 负载均衡算法不一样。nginx 5种 ribbon 7种

5.3 远程接口调用技术:

HttpClient RestTemplate 等等 (底层 Url类封装)



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。