zoukankan      html  css  js  c++  java
  • 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)

  • 相关阅读:
    股票行情
    证券总结
    求职
    Android的Touch事件处理机制
    订货量、成交量、价订货量、成交量、价格与行情之间的关系
    Android下如何理解onMeasure,onLayout的过程
    并发和并行的区别
    网页布局
    计算机发展史--图灵
    计算机发展史
  • 原文地址:https://www.cnblogs.com/qdhxhz/p/9568481.html
Copyright © 2011-2022 走看看