SpringCloud(4)---Ribbon服务调用,源码分析
SpringCloud(4)---Ribbon
本篇模拟订单服务调用商品服务,同时商品服务采用集群部署。
注册中心服务端口号7001,订单服务端口号9001,商品集群端口号:8001、8002、8003。

各服务的配置文件这里我这边不在显示了,和上篇博客配置一样。博客地址:SpringCloud(3)---Eureka服务注册与发现
一、商品中心服务端
1、pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.jincou</groupId>
<artifactId>product</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging> <name>product</name>
<description>Demo project for Spring Boot</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--定义当前springcloud版本-->
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> <!--表明是Eureka Client客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency> <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency> </dependencies> <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>
pom.xml
2、Product商品实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable { private int id;
//商品名称
private String name;
//价格,分为单位
private int price;
//库存
private int store;
}
3、ProductService商品接口
public interface ProductService {
//查找所有商品
List<Product> listProduct();
//根据商品ID查找商品
Product findById(int id);
}
4、ProductServiceImpl商品实现类
@Service
public class ProductServiceImpl implements ProductService { private static final Map<Integer, Product> daoMap = new HashMap<>(); //模拟数据库商品数据
static {
Product p1 = new Product(1, "苹果X", 9999, 10);
Product p2 = new Product(2, "冰箱", 5342, 19);
Product p3 = new Product(3, "洗衣机", 523, 90);
Product p4 = new Product(4, "电话", 64345, 150); daoMap.put(p1.getId(), p1);
daoMap.put(p2.getId(), p2);
daoMap.put(p3.getId(), p3);
daoMap.put(p4.getId(), p4);
} @Override
public List<Product> listProduct() {
Collection<Product> collection = daoMap.values();
List<Product> list = new ArrayList<>(collection);
return list;
}
@Override
public Product findById(int id) {
return daoMap.get(id);
}
}
5、ProductController
@RestController
@RequestMapping("/api/v1/product")
public class ProductController { //集群情况下,用于订单服务查看到底调用的是哪个商品微服务节点
@Value("${server.port}")
private String port; @Autowired
private ProductService productService; //获取所有商品列表
@RequestMapping("list")
public Object list(){
return productService.listProduct();
} //根据id查找商品详情
@RequestMapping("find")
public Object findById(int id){
Product product = productService.findById(id);
Product result = new Product();
BeanUtils.copyProperties(product,result);
result.setName( result.getName() + " data from port="+port );
return result;
}
}
6、测下该服务接口是否成功

二、订单中心服务端
1、pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.jincou</groupId>
<artifactId>order</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging> <name>order</name>
<description>Demo project for Spring Boot</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency> <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies> <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>
pom.xml
2、ProductOrder商品订单实体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProductOrder implements Serializable {
//订单ID
private int id;
// 商品名称
private String productName;
//订单号
private String tradeNo;
// 价格,分
private int price;
//订单创建时间
private Date createTime;
//用户id
private int userId;
//用户名
private String userName;
}
3、ProductOrderService订单接口
/**
* 订单业务类
*/
public interface ProductOrderService {
//下单接口
ProductOrder save(int userId, int productId);
}
4、ProductOrderServiceImpl订单实现类
@Service
public class ProductOrderServiceImpl implements ProductOrderService { @Autowired
private RestTemplate restTemplate; @Override
public ProductOrder save(int userId, int productId) {
//product-service是微服务名称(这里指向的商品微服务名称),api/v1/product/find?id=? 就是商品微服务对外的接口
Map<String, Object> productMap = restTemplate.getForObject("http://product-service/api/v1/product/find?id=" + productId, Map.class); ProductOrder productOrder = new ProductOrder();
productOrder.setCreateTime(new Date());
productOrder.setUserId(userId);
productOrder.setTradeNo(UUID.randomUUID().toString());
//获取商品名称和商品价格
productOrder.setProductName(productMap.get("name").toString());
productOrder.setPrice(Integer.parseInt(productMap.get("price").toString())); //因为在商品微服务配置了集群,所以这里打印看下调用了是哪个集群节点,输出端口号。
System.out.println(productMap.get("name").toString());
return productOrder;
}
}
5、OrderController类
@RestController
@RequestMapping("api/v1/order")
public class OrderController { @Autowired
private ProductOrderService productOrderService; @RequestMapping("save")
public Object save(@RequestParam("user_id")int userId, @RequestParam("product_id") int productId){ return productOrderService.save(userId, productId);
}
}
6、SpringBoot启动类
@SpringBootApplication
public class OrderApplication { public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
} //当添加@LoadBalanced注解,就代表启动Ribbon,进行负载均衡
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
7、接口测试

多调几次接口,看后台打印

发现订单服务去掉商品服务的时候,不是固定节点,而且集群的每个节点都有可能。所以通过Ribbon实现了负载均衡。
三、Ribbon源码分析
1、@LoadBalanced注解作用
在springcloud中,引入Ribbon来作为客户端时,负载均衡使用的是被@LoadBalanced修饰的RestTemplate对象。

RestTemplate 是Spring自己封装的http请求的客户端,也就是说它只能发送一个正常的Http请求,这跟我们要求的负载均衡是有出入的,还有就是这个请求的链接上的域名
是我们微服的一个服务名,而不是一个真正的域名,那它是怎么实现负载均衡功能的呢?
我们来看看RestTemplate的父类InterceptingHttpAccessor。

从源码我们可以知道InterceptingHttpAccessor中有一个拦截器列表List<ClientHttpRequestInterceptor>,如果这个列表为空,则走正常请求流程,如果不为空则走
拦截器,所以只要给RestTemplate添加拦截器,而这个拦截器中的逻辑就是Ribbon的负载均衡的逻辑。通过@LoadBalanced注解为RestTemplate配置添加拦截器。
具体的拦截器的生成在LoadBalancerAutoConfiguration这个配置类中,所有的RestTemplate的请求都会转到Ribbon的负载均衡器上
(当然这个时候如果你用RestTemplate发起一个正常的Http请求时走不通,因为它找不到对应的服务。)这样就实现了Ribbon的请求的触发。
2.拦截器都做了什么?
上面提到过,发起http后请求后,请求会到达到达拦截器中,在拦截其中实现负载均衡,先看看代码:

我们可以看到在intercept()方法中实现拦截的具体逻辑,首先会根据传进来的请求链接,获取微服的名字serviceName,然后调用LoadBalancerClient的
execute(String serviceId, LoadBalancerRequest<T> request)方法,这个方法直接返回了请求结果,所以正真的路由逻辑在LoadBalancerClient的实现类中,
而这个实现类就是RibbonLoadBalancerClient,看看execute()的源码:

首先是获得均衡器ILoadBalancer这个类上面讲到过这是Netflix Ribbon中的均衡器,这是一个抽象类,具体的实现类是ZoneAwareLoadBalancer上面也讲到过,
每一个微服名对应一个均衡器,均衡器中维护者微服名下所有的服务清单。getLoadBalancer()方法通过serviceId获得对应的均衡器,getServer()方法通过对应的均衡器
在对应的路由的算法下计算得到需要路由到Server,Server中有该服务的具体域名等相关信息。得到了具体的Server后执行正常的Http请求,整个请求的负载均衡逻辑就完成了。
在微服中Ribbon和 Hystrix通常是一起使用的,其实直接使用Ribbon和Hystrix实现服务间的调用并不是很方便,通常在Spring Cloud中我们使用Feign完成服务间的调用,
而Feign是对Ribbon和Hystrix做了进一步的封装方便大家使用,对Ribbon的学习能帮你更好的完成Spring Cloud中服务间的调用。
我只是偶尔安静下来,对过去的种种思忖一番。那些曾经的旧时光里即便有过天真愚钝,也不值得谴责。毕竟,往后的日子,还很长。不断鼓励自己,
天一亮,又是崭新的起点,又是未知的征程(上校6)
SpringCloud(4)---Ribbon服务调用,源码分析的更多相关文章
- 小D课堂 - 新版本微服务springcloud+Docker教程_4-03 高级篇幅之Ribbon负载均衡源码分析实战
笔记 3.高级篇幅之Ribbon负载均衡源码分析实战 简介: 讲解ribbon服务间调用负载均衡源码分析 1.完善下单接口 2.分析@LoadBalanced ...
- springcloud 入门 5 (feign源码分析)
feign:(推荐使用) Feign是受到Retrofit,JAXRS-2.0和WebSocket的影响,它是一个jav的到http客户端绑定的开源项目. Feign的主要目标是将Java Http ...
- Spring Cloud Eureka服务注册源码分析
Eureka是怎么work的 那eureka client如何将本地服务的注册信息发送到远端的注册服务器eureka server上.通过下面的源码分析,看出Eureka Client的定时任务调用E ...
- SpringCloud服务调用源码解析汇总
相信我,你会收藏这篇文章的,本篇文章涉及Ribbon.Hystrix.Feign三个组件的源码解析 Ribbon架构剖析 这篇文章介绍了Ribbon的基础架构,也就是下图涉及到的6大组件: Ribbo ...
- Spring Cloud学习 之 Spring Cloud Ribbon(负载均衡器源码分析)
文章目录 AbstractLoadBalancer: BaseLoadBalancer: DynamicServerListLoadBalancer: ServerList: ServerListUp ...
- spring boot actuator工作原理之http服务暴露源码分析
spring boot actuator的官方文档地址:https://docs.spring.io/spring-boot/docs/current/reference/html/productio ...
- fegin 调用源码分析
http://techblog.ppdai.com/2018/05/28/20180528/ 这篇文章是相当详细
- Netty源码分析 (十二)----- 心跳服务之 IdleStateHandler 源码分析
什么是心跳机制? 心跳说的是在客户端和服务端在互相建立ESTABLISH状态的时候,如何通过发送一个最简单的包来保持连接的存活,还有监控另一边服务的可用性等. 心跳包的作用 保活Q:为什么说心跳机制能 ...
- SpringCloud(5)---Feign服务调用
SpringCloud(5)---Feign服务调用 上一篇写了通过Ribbon进行服务调用,这篇其它都一样,唯一不一样的就是通过Feign进行服务调用. 注册中心和商品微服务不变,和上篇博客一样,具 ...
随机推荐
- 如何在.Net Core调用NodeJs
概述 Microsoft.AspNetCore.NodeServices库 实例 新建aspnet core站点 添加nuget包 建立node环境,此处示例用于扫描wifi环境 建立nodejs的程 ...
- Python 常用Web框架的比较
转载来自:https://www.cnblogs.com/sunshine-1/p/7372934.html 从GitHub中整理出的15个最受欢迎的Python开源框架.这些框架包括事件I/O,OL ...
- 爬虫之正则和xpath
一.正解解析 常用正则表达式回顾: 单字符: . : 除换行以外所有字符 [] :[aoe] [a-w] 匹配集合中任意一个字符 \d :数字 [-] \D : 非数字 \w :数字.字母.下划线.中 ...
- 【C语言编程练习】新娘与新郎
1. 题目要求 新郎A,B,C与新娘 X,Y,Z.有人不知道她们谁和谁结婚了,询问了6位新人中的三位,A说他将和X结婚,X说她的未婚夫是C,C说她会和Z结婚,一听就知道是全是假话,请编程找出谁和谁结婚 ...
- 把ssl模块加入到已经编译好的apache中实现HTTPS
为了使Apache支持https访问,系统需要安有apache.openssl.mod_ssl.so 1.安装openssl: 基本上系统都已经安装了,在/usr/bin/openssl下,直接使用o ...
- [python]numpy.mean()用法
a=np.array([[[1,1],[2,2],[3,3]],[[4,4],[5,5],[6,6]],[[7,7],[8,8],[9,9]],[[10,10],[11,11],[12,12]]]) ...
- SpringBoot加Poi仿照EasyPoi实现Excel导出
POI提供API给Java程序对Microsoft Office格式档案读和写的功能,详细功能可以直接查阅API,因为使用EasyPoi过程中总是缺少依赖,没有搞明白到底是什么坑,索性自己写一个简单工 ...
- React+Ant Design设置左侧菜单导航路由的显示与隐藏(与权限无关)
最近在学习react时,用到了很流行的UI框架Ant Design,也了解了一下Ant Design Pro,发现它们都有导航组件,Ant Design框架的导航菜单在这里,Ant Design Pr ...
- Java EE 导图
- 关于vue的数据增删的一些细节
第一种情况:在vue中使用的数据必须先在data中定义数据,不然报错: 第二种情况:访问对象中不存在的值,是可以得到undefined,但是不会报错 第三种:vue只会监听data已经定义的值,后续添 ...