1,课程回顾 2,本章重点 ribbon (负载均衡器)如何实现服务到服务的调用 feign 服务到服务的调用 3,具体内容 3.1 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_1 创建order_server_2,order_server_3 创建goods_server_ribbon微服务 1),引入jar 考虑到微服务项目之间调用都可以使用ribbon方式,直接把jar放到micro_services项目pom.xml org.springframework.cloud spring-cloud-starter-netflix-ribbon 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),添加application.yml配置 #当前服务端口号 server: port: 14131
#阿里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:
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 {}
两种方式配置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; /**
ribbon配置方法2: 启动类上添加注解: @RibbonClient(name = “ribbonc”,configuration = RibbonConfig.class) 启动类上添加方法: @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } 编写MyRule: @Configuration public class RibbonConfig { /**
} 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; /**
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(); }/ /**
public Server choose(Object key) { return this.choose(this.getLoadBalancer(), key); } /**
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 运行流程: 图片: https://uploader.shimo.im/f/4yVKLyNBFEHW4aCT.png
3.2.3 实现过程: 社交服务中评论对象远程调用查询订单服务中的订单对象 1)引入jar(在micro_services引入,所有微服务都可能使用openfeign)
org.springframework.cloud spring-cloud-starter-openfeign 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
#阿里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:
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
图片: https://uploader.shimo.im/f/rPvJ7h0yJC1OOZFO.png
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协议,实现服务调用和负载均衡 feign 使用程序猿习惯的调用方式,调用接口,支持springmvc的注解 底层使用代理 4,知识点总结 ribbon 客户端负载均衡器 openfeign 内置ribbon 使用接口的方式进行远程调用 5,本章面试题
5.1 微服务之间是如何独立通讯的 同步:dubbo 使用rpc(rpc参考帖子),springcloud使用restful传递json(ribbon,openfeign) 异步:使用消息队列框架(MQ:kafka,activeMQ,rabbitMq等等)
5.2 ribbon 和nginx 区别: 1)Ribbon是从注册中心服务器端上获取服务注册信息列表,缓存到本地,然后在本地实现轮询负载均衡策略一种客户端负载平衡器。 nginx是客户端所有请求统一交给 nginx,由 nginx 进行实现负载均衡请求转发,一个服务器端负载均衡。 2) 负载均衡算法不一样。nginx 5种 ribbon 7种 5.3 远程接口调用技术: HttpClient RestTemplate 等等 (底层 Url类封装)