SpringCloud(1)---入门篇

SpringCloud理解篇

 

一、微服务概述

1、什么是微服务

      目前的微服务并没有一个统一的标准,一般是以业务来划分将传统的一站式应用,拆分成一个个的服务,彻底去耦合,一个微服务就是单功能业务,只做一件事。

      与微服务相对的叫巨石 。

2、微服务与微服务架构

  • 微服务是一种架构模式或者一种架构风格,提倡将单一应用程序划分成一组小的服务==独立部署==,服务之间相互配合、相互协调,每个服务运行于自己的==进程==中。
  • 服务与服务间采用轻量级通讯,如HTTP的RESTful API等
  • 避免统一的、集中式的服务管理机制 

3、微服务的优缺点

优点

  1. 每个服务足够内聚,足够小,比较容易聚焦
  2. 开发简单且效率高,一个服务只做一件事情
  3. 开发团队小,一般2-5人足以(当然按实际为准)
  4. 微服务是松耦合的,无论开发还是部署都可以独立完成
  5. 微服务能用不同的语言开发
  6. 易于和第三方集成,微服务允许容易且灵活的自动集成部署(持续集成工具有Jenkins,Hudson,bamboo等)
  7. 微服务易于被开发人员理解,修改和维护,这样可以使小团队更加关注自己的工作成果,而无需一定要通过合作才能体现价值
  8. 微服务允许你融合最新的技术
  9. ==微服务只是业务逻辑的代码,不会和HTML,CSS或其他界面组件融合==。
  10. ==每个微服务都可以有自己的存储能力,数据库可自有也可以统一,十分灵活==。

缺点

  1. 开发人员要处理分布式系统的复杂性
  2. 多服务运维难度,随着服务的增加,运维的压力也会增大
  3. 依赖系统部署
  4. 服务间通讯的成本
  5. 数据的一致性
  6. 系统集成测试
  7. 性能监控的难度 

4、微服务的技术栈

微服务条目落地技术
服务开发 SpringBoot,Spring,SpringMVC
服务配置与管理 Netflix公司的Archaius、阿里的Diamond等
服务注册与发现 Eureka、Consul、Zookeeper等
服务调用 Rest、RPC、gRPC
服务熔断器 Hystrix、Envoy等
负载均衡 Ribbon、Nginx等
服务接口调用(客户端调用服务的简化工具) Feign等
消息队列 Kafka、RabbitMQ、ActiveMQ等
服务配置中心管理 SpringCloudConfig、Chef等
服务路由(API网关) Zuul等
服务监控 Zabbix、Nagios、Metrics、Specatator等
全链路追踪 Zipkin、Brave、Dapper等
服务部署 Docker、OpenStack、Kubernetes等
数据流操作开发包 SpringCloud Stream(封装与Redis,Rabbit,Kafka等发送接收消息)
事件消息总线 SpringCloud Bus

 

 

二、SpringCloud入门概述

       Spring的三大模块:SpringBoot(构建),Spring Cloud(协调),Spring Cloud Data Flow(连接) 

1、SpringCloud是什么

  • 分布式系统的简化版(官方介绍)
  • SpringCloud基于SpringBoot提供了一整套微服务的解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于Netflix的开源组件做高度抽象封装之外,还有一些选型中立的开源组件
  • SpringCloud利用SpringBoot的开发便利性巧妙地简化了分布式系统的基础设施开发,SpringCloud为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线,全局所、决策精选、分布式会话等等,他们都可以用SpringBoot的开发风格做到一键启动和部署。
  • ==一句话概括:SpringCloud是分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的几何体,俗称微服务全家桶== 

2、SpringCloud和SpringBoot的关系

    SpringBoot:专注于快速方便的开发单个个体微服务(关注微观)

  SpringCloud:关注全局的微服务协调治理框架,将SpringBoot开发的一个个单体微服务组合并管理起来(关注宏观)

  注意:SpringBoot可以离开SpringCloud独立使用,但是SpringCloud不可以离开SpringBoot,属于依赖关系

3、Dubbo和SpringCloud比较

 DubboSpring
服务注册中心 Zookeeper Spring Cloud Netfilx Eureka
服务调用方式 RPC REST API
服务监控 Dubbo-monitor Spring Boot Admin
断路器 不完善 Spring Cloud Netflix Hystrix
服务网关 Spring Cloud Netflix Zuul
分布式配置 Spring Cloud Config
服务跟踪 Spring Cloud Sleuth
消息总线 Spring Cloud Bus
数据流 Spring Cloud Stream
批量任务 Spring Cloud Task

 

 

 

 

 

 

 

 

 

 

 

 

 

最大区别

(1)Spring Cloud抛弃了RPC通讯,采用基于HTTP的REST方式。Spring Cloud牺牲了服务调用的性能,但是同时也避免了原生RPC带来的问题。REST比RPC更为灵活,不存在代码级别的强依赖,在强调快速演化

的微服务环境下,显然更合适。

(2)Dubbo像组装机,Spring Cloud像一体机

(3)社区的支持与力度:Dubbo曾经停运了5年,虽然重启了,但是对于技术发展的新需求,还是需要开发者自行去拓展,对于中小型公司,显然显得比较费时费力,也不一定有强大的实力去修改源码  

总结

     解决的问题域不一样:Dubbo的定位是一款RPC框架,Spring Cloud的目标是微服务架构下的一站式解决方案 

Eureka服务注册与发现

 GitHub地址https://github.com/yudiandemingzi/spring-cloud-study

一、Eureka概述

 1、Eureka特点

  (1) Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。

  (2) Eureka 主管服务注册与发现,在微服务中,以后了这两者,只需要使用服务的标识符(==就是那个在每个服务的yml文件中取得服务名称==),

          就可以访问到服务,不需要修改服务调用的配置文件。

  (3) Eureka遵循AP原则(高可用,分区容错性),因为使用了自我保护机制所以保证了高可用。

2、Eureka两大组件

    两大组件:Eureka Server(提供注册服务)、 Eureka Client(JAVA客户端,负责发送心跳)

   系统中的其他微服务使用Eureka客户端连接到Eureka服务端维持心跳连接(即注册)。SpringCloud的其他模块可以通过Eureka Server 来发现系统中的微服务并加以调用

3、Eureka三大角色

            Eureka Server:提供服务注册和发现

        Service Provider:服务提供方,将自身服务注册到Eureka,从而使服务消费方能够找到

     Service Consumer:服务消费方,从Eureka获取注册服务列表,从而能够消费服务。

 

 

二、Eureka Server服务注册中心

    1、pom.xml

<!--注册服务中心的jar要多个-server-->
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
  </dependency>

    2、application.yml

复制代码
server:
  port: 7001
eureka:
  instance:
    hostname: localhost
  client:
  #声明自己是个服务端
    registerWithEureka: false    #false表示不向注册中心注册自己
    fetchRegistry: false         #false表示自己就是注册中心,职责是维护实例,不参加检索
    serviceUrl:                  #设置eureka server的交互地址,即对外暴露的地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
复制代码

    3、启动类

复制代码
//注意:要在类前加@EnableEurekaServer标注
@SpringBootApplication
@EnableEurekaServer
public class Eureka7001_APP {
    public static void main(String[] args) {
        SpringApplication.run(Eureka7001_APP.class,args);
    }
}
复制代码

运行结果:输入:http://localhost:7001/

 

三、Service Provider服务提供方

假设这个商品微服务。

   1、pom.xml

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

   2、application.yml

复制代码
server:
  port: 8001
#指定注册中心地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:7001/eureka/
#服务的名称
spring:
  application:
    name: product-service
复制代码

     3、启动类

复制代码
@SpringBootApplication
public class ProductApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class, args);
    }
}
复制代码

    4、启动后查看服务注册中心

 发现在服务注册中心已经注册了一个服务

    5、换端口号再启动一个

   6、在看服务中心

 

这就是搭建了商品微服务集群。

 

四、Service Consumer服务消费方

      其实服务方和消费在配置时候没有任何区别,它们都属于Eureka Client组件。只是涉及服务间的调用,所以就把被调方称为提供方,调用方称为消费方。就好比订单微服务,

订单服务肯定需要去调商品微服务,所以这个订单微服务对于商品来讲可以理解服务提供方。一个微服务即可以是服务方也同时是提供方。

      1、pom.xml

    <!--这个对于每个不是注册中心的微服务都要添加-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

     2、application.yml

复制代码
server:
  port: 9001

#指定注册中心地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:7001/eureka/

#服务的名称
spring:
  application:
    name: order-service
复制代码

    3、启动类

复制代码
@SpringBootApplication
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}
复制代码

4、查看注册中心

发现订单微服务也成功注册到注册中心

 

至于订单微服务如何调商品微服务呢,下一遍博客在写咯。

 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>

 

  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>

  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中服务间的调用。

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

上一篇写了通过Ribbon进行服务调用,这篇其它都一样,唯一不一样的就是通过Feign进行服务调用。

注册中心和商品微服务不变,和上篇博客一样,具体参考:SpringCloud(4)---Ribbon服务调用,源码分析

这边只重写订单微服务。

  项目代码GitHub地址https://github.com/yudiandemingzi/spring-cloud-study

一、OrderService 订单微服务

   1、pom.xml

这里相对于上一篇的订单微服务只要新添加一个jar包

    <!--feign依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency> 

    2、application.yml

复制代码
server:
  port: 9001

#指定注册中心地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:7001/eureka/

#服务的名称
spring:
  application:
    name: order-service

#自定义负载均衡策略(一般不用配用默认的)
product-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
复制代码

      3、SpringBoot启动类

复制代码
@SpringBootApplication
//添加@EnableFeignClients注解
@EnableFeignClients
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

}
复制代码

     4、ProductOrderServiceImpl订单接口实现类

订单实体类和订单接口这里就不写类,和上篇一样

复制代码
@Service
public class ProductOrderServiceImpl implements ProductOrderService {

    @Autowired
    private ProductClient productClient;

    @Override
    public ProductOrder save(int userId, int productId) {

        //获取json格式的字符串数据
        String response = productClient.findById(productId);
        //Json字符串转换成JsonNode对象
        JsonNode jsonNode = JsonUtils.str2JsonNode(response);

        //将数据封装到订单实体中
        ProductOrder productOrder = new ProductOrder();
        productOrder.setCreateTime(new Date());
        productOrder.setUserId(userId);
        productOrder.setTradeNo(UUID.randomUUID().toString());
        //获取商品名称和商品价格
        productOrder.setProductName(jsonNode.get("name").toString());
        productOrder.setPrice(Integer.parseInt(jsonNode.get("price").toString()));

        //因为在商品微服务配置了集群,所以这里打印看下调用了是哪个集群节点,输出端口号。
        System.out.println(jsonNode.get("name").toString());
        return productOrder;
    }
}
复制代码

     5、ProductClient类

可以把这里类理解成,就是你需要调用的微服务的controller层(这里指商品微服务),这样相对于Ribbon来讲代码的可读性就高多了。

复制代码
/**
 * 商品服务客户端
 * name = "product-service"是服务端名称
 */
@FeignClient(name = "product-service")
public interface ProductClient {

    //这样组合就相当于http://product-service/api/v1/product/find
    @GetMapping("/api/v1/product/find")
    String findById(@RequestParam(value = "id") int id);

}
复制代码

     6、JsonUtils工具类

复制代码
/**
 * json工具类
 */
public class JsonUtils {

    private static final ObjectMapper objectMappper = new ObjectMapper();
     //json字符串转JsonNode对象的方法
    public static JsonNode str2JsonNode(String str){
        try {
            return  objectMappper.readTree(str);
        } catch (IOException e) {
            return null;
        }
    }
}
复制代码

    7、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);
    }
}
复制代码

   8、查看运行结果

同时同样它可以达到负载均衡的效果。

 

二、概念讲解

在使用Feign的时候,要注意使用requestBody,应该使用@PostMapping

    1、执行流程

    总到来说,Feign的源码实现的过程如下:

   (1)首先通过@EnableFeignCleints注解开启FeignCleint

   (2)根据Feign的规则实现接口,并加@FeignCleint注解

   (3)程序启动后,会进行包扫描,扫描所有的@ FeignCleint的注解的类,并将这些信息注入到ioc容器中。

   (4)当接口的方法被调用,通过jdk的代理,来生成具体的RequesTemplate

   (5)RequesTemplate在生成Request

   (6)Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp

   (7)最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。

2、Feign和Ribbon比较优点

  (1) feign本身里面就包含有了ribbon,只是对于ribbon进行进一步封装

  (2) feign自身是一个声明式的伪http客户端,写起来更加思路清晰和方便

  (3) fegin是一个采用基于接口的注解的编程方式,更加简便

最后推荐一篇对源码解析不错的博客:深入理解Feign之源码解析

 

posted @ 2022-02-26 10:34  hanease  阅读(76)  评论(0编辑  收藏  举报