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)

原文地址:https://www.cnblogs.com/qdhxhz/p/9568481.html

时间: 2024-11-16 20:44:43

SpringCloud(4)---Ribbon服务调用,源码分析的相关文章

Eureka 系列(02)服务发现源码分析

Eureka 系列(02)服务发现源码分析 [TOC] 在上一篇文章 Eureka 系列(02)客户端源码分析 中对客户端服务发现与 Eureka 一致性协议: Eureka 是 AP 模型 消息广播: Eureka源码解析 https://blog.csdn.net/u012394095/article/category/9279158 https://blog.csdn.net/u011834741/article/details/54694045 Eureka 集群发现 https://w

Spring Cloud Eureka服务注册源码分析

Eureka是怎么work的 那eureka client如何将本地服务的注册信息发送到远端的注册服务器eureka server上.通过下面的源码分析,看出Eureka Client的定时任务调用Eureka Server的Reset接口,而Eureka接收到调用请求后会处理服务的注册以及Eureka Server中的数据同步的问题. 服务注册 源码分析,看出服务注册可以认为是Eureka client自己完成,不需要服务本身来关心. Eureka Client的定时任务调用Eureka Se

Android的软件包管理服务PackageManagerService源码分析

Android系统下的apk程序都是通过名为PackageManagerService的包管理服务来管理的.PacketManagerService是安卓系统的一个重要服务,由SystemServer启动,主要实现apk程序包的解析,安装,更新,移动,卸载等服务.不管是系统apk(/system/app),还是我们手工安装上去的,系统所有的apk都是由其管理的. 以android 4.0.4的源码为例,android4.0.4/frameworks/base/services/java/com/

SpringCloud(5)---Feign服务调用

SpringCloud(5)---Feign服务调用 上一篇写了通过Ribbon进行服务调用,这篇其它都一样,唯一不一样的就是通过Feign进行服务调用. 注册中心和商品微服务不变,和上篇博客一样,具体参考:SpringCloud(4)---Ribbon服务调用,源码分析 这边只重写订单微服务. 一.OrderService 订单微服务 1.pom.xml 这里相对于上一篇的订单微服务只要新添加一个jar包 <!--feign依赖--> <dependency> <group

Eureka 源码分析之 Eureka Client

文章首发于微信公众号<程序员果果>地址:https://mp.weixin.qq.com/s/47TUd96NMz67_PCDyvyInQ 简介 Eureka是一种基于REST(Representational State Transfer)的服务,主要用于AWS云,用于定位服务,以实现中间层服务器的负载平衡和故障转移.我们将此服务称为Eureka Server.Eureka还附带了一个基于Java的客户端组件Eureka Client,它使与服务的交互变得更加容易.客户端还有一个内置的负载均

8.源码分析---从设计模式中看SOFARPC中的EventBus?

我们在前面分析客户端引用的时候会看到如下这段代码: // 产生开始调用事件 if (EventBus.isEnable(ClientStartInvokeEvent.class)) { EventBus.post(new ClientStartInvokeEvent(request)); } 这里用EventBus调用了一下post方法之后就什么也没做了,就方法名来看是发送了一个post请求,也不知道发给谁,到底有什么用. 所以这一节我们来分析一下EventBus这个类的作用. 首先我们来看一下

SOFA 源码分析 —— 服务引用过程

前言 在前面的 SOFA 源码分析 -- 服务发布过程 文章中,我们分析了 SOFA 的服务发布过程,一个完整的 RPC 除了发布服务,当然还需要引用服务. So,今天就一起来看看 SOFA 是如何引用服务的.实际上,基础逻辑和我们之前用 Netty 写的 RPC 小 demo 类似.有兴趣可以看看这个 demo-- 自己用 Netty 实现一个简单的 RPC. 示例代码 ConsumerConfig<HelloService> consumerConfig = new ConsumerCon

dubbox源码分析(一)-服务的启动与初始化

程序猿成长之路少不了要学习和分析源码的.最近难得能静得下心来,就针对dubbox为目标开始进行源码分析. [服务提供方] 步骤 调用顺序 备注 容器启动 com.alibaba.dubbo.container.Main.main(args);dubbo.properties -> dubbo.container -> container.start()container -> spring, log4j, jetty... [dubbo-container-spring] SpringC

netty 5 alph1源码分析(服务端创建过程)

参照<Netty系列之Netty 服务端创建>,研究了netty的服务端创建过程.至于netty的优势,可以参照网络其他文章.<Netty系列之Netty 服务端创建>是 李林锋撰写的netty源码分析的一篇好文,绝对是技术干货.但抛开技术来说,也存在一些瑕疵. 缺点如下 代码衔接不连贯,上下不连贯. 代码片段是截图,对阅读代理不便(可能和阅读习惯有关) 本篇主要内容,参照<Netty系列之Netty 服务端创建>,梳理出自己喜欢的阅读风格. 1.整体逻辑图 整体将服务