精华内容
下载资源
问答
  • SpringCloud详细教程(上)

    万次阅读 多人点赞 2019-07-04 16:30:11
    SpringCloud详细教程,SpringCloud详细教程。SpringCloud是一系列框架的有序集合。如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用SpringBoot的开发风格做到一键启动和部署。本文主要...

    【仅需9.9订阅专栏合集,作者所有付费文章都能看】

    推荐【Kafka教程https://bigbird.blog.csdn.net/article/details/108770504
    推荐【Flink教程https://blog.csdn.net/hellozpc/article/details/109413465
    推荐【JVM面试与调优教程https://bigbird.blog.csdn.net/article/details/113888604
    推荐【SpringBoot教程https://blog.csdn.net/hellozpc/article/details/107095951
    推荐【SpringCloud教程https://blog.csdn.net/hellozpc/article/details/83692496
    推荐【Mybatis教程https://blog.csdn.net/hellozpc/article/details/80878563
    推荐【SnowFlake教程https://blog.csdn.net/hellozpc/article/details/108248227
    推荐【并发限流教程https://blog.csdn.net/hellozpc/article/details/107582771
    推荐【Redis教程https://bigbird.blog.csdn.net/article/details/81267030

    SpringCloud详细教程代码下载地址:https://download.csdn.net/download/zpcandzhj/10762209

    SpringCloud详细教程(上)

    springcloud 官网 https://spring.io/projects/spring-cloud/

    文章目录

    1.SpringCloud详细教程目录

    • 开发环境说明
    • 细说微服务架构
    • SpringCloud 概论
    • SpringCloud 快速入门
    • Eureka 服务注册中心
    • 使用Spring Cloud Ribbon 实现负载均衡
    • 使用Spring Cloud Hystrix 实现容错

    2. 开发环境说明

    • JDK:  1.8
    • IDE:    IntelliJ IDEA
    • Maven:3.3.9
    • OS:        Windows 10 10.0
    • Springboot版本:2.0+

    3.细说微服务架构

    目前微服务是非常火的架构或者说概念,也是在构建大型互联网项目时采用的架构方式。

    3.1.单体架构

    单体架构,是指将开发好的项目打成war包,然后发布到tomcat等容器中的应用。

    假设你正准备开发一款与Uber和滴滴竞争的出租车调度软件,经过初步会议和需求分析,你可能会手动或者使用基于SpringBoot、Play或者Maven的生成器开始这个新项目,它的六边形架构是模块化的 ,架构图如下:
    在这里插入图片描述

    • 应用核心是业务逻辑,由定义服务、领域对象和事件的模块完成。围绕着核心的是与外界打交道的适配器。适配器包括数据库访问组件、生产和处理消息的消息组件,以及提供API或者UI访问支持的web模块等。

    • 尽管也是模块化逻辑,但是最终它还是会打包并部署为单体式应用。具体的格式依赖于应用语言和框架。例如,许多Java应用会被打包为WAR格式,部署在Tomcat或者Jetty上,而另外一些Java应用会被打包成自包含的JAR格式,类似的,Rails和Node.js会被打包成层级目录。

    • 这种应用开发风格很常见,因为IDE和其它工具都擅长开发一个简单应用,这类应用也很易于调试,只需要简单运行此应用,用Selenium链接UI就可以完成端到端测试。单体式应用也易于部署,只需要把打包应用拷贝到服务器端,通过在负载均衡器后端运行多个拷贝就可以轻松实现应用扩展。在早期这类应用运行的很好。

    3.2.单体架构存在的问题

    在这里插入图片描述
    在这里插入图片描述
    如何解决以上问题呢? – 使用微服务架构。使得应用由重变轻。

    3.3.什么是微服务?

    在这里插入图片描述

    作者:Martin Fowler
    在这里插入图片描述
    在这里插入图片描述

    3.4.微服务架构的特征

    在这里插入图片描述

    3.5.微服务架构示例

    在这里插入图片描述

    每一个应用功能区都使用微服务完成。

    4.SpringCloud概论

    4.1.简介

    SpringCloud项目的官方网址:
    http://projects.spring.io/spring-cloud/
    在这里插入图片描述
    在这里插入图片描述

    4.2.SpringCloud子项目

    在这里插入图片描述

    4.3.Springcloud版本说明

    在这里插入图片描述

    官方版本:
    在这里插入图片描述

    在这里插入图片描述

    可见,目前Finchley.SR2版本是最新的稳定版,所以我们学习的过程中,就是使用的这个版本。
    在这里插入图片描述

    4.4.SpringCloud框架特点

    在这里插入图片描述

    5.使用 SpringBoot 实现微服务

    在正式学习SpringCloud 之前我们先使用SpringBoot实现一个微服务。

    业务非常简单:
    1、商品微服务:通过商品id查询商品的服务;
    2、订单微服务:创建订单时通时,通过调用商品的微服务进行查询商品数据;

    图示:
    在这里插入图片描述

    说明:
    1、对于商品微服务而言,商品微服务是服务的提供者,订单微服务是服务的消费者;
    2、对于订单微服务而言,订单微服务是服务的提供者,人是服务的消费者。

    5.1.实现商品微服务

    5.1.1.创建maven工程

    在这里插入图片描述
    在这里插入图片描述

    5.1.2.导入依赖

    重点是导入SpringBoot的依赖:

    <?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.zpc.microservice</groupId>
        <artifactId>microservice-item</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <finalName>${project.artifactId}</finalName>
            <plugins>
                <!-- 资源文件拷贝插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <!-- java编译插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        </project>
    
    5.1.3.创建实体Item
    package com.zpc.item.entity;
    
    public class Item {
    
        private Long id;
    
        private String title;
    
        private String pic;
    
        private String desc;
    
        private Long price;
    
        public Item(){}
    
        public Item(long id, String title, String pic, String desc, Long price) {
            this.id=id;
            this.title=title;
            this.pic=pic;
            this.desc=desc;
            this.price=price;
        }
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getPic() {
            return pic;
        }
    
        public void setPic(String pic) {
            this.pic = pic;
        }
    
        public String getDesc() {
            return desc;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    
        public Long getPrice() {
            return price;
        }
    
        public void setPrice(Long price) {
            this.price = price;
        }
    
        @Override
        public String toString() {
            return "Item [id=" + id + ", title=" + title + ", pic=" + pic + ", desc=" + desc + ", price=" + price + "]";
        }
    
    }
    
    5.1.4.编写ItemService

    编写ItemService用于实现具体的商品查询逻辑,为了演示方便,我们并不真正的连接数据库,而是做模拟实现。

    package com.zpc.item.service;
    import com.zpc.item.entity.Item;
    import org.springframework.stereotype.Service;
    import java.util.HashMap;
    import java.util.Map;
    
    @Service
    public class ItemService {
    
        private static final Map<Long, Item> ITEM_MAP = new HashMap<Long, Item>();
    
        static {// 准备一些静态数据,模拟数据库
            ITEM_MAP.put(1L, new Item(1L, "商品1", "http://图片1", "商品描述1", 1000L));
            ITEM_MAP.put(2L, new Item(2L, "商品2", "http://图片2", "商品描述2", 2000L));
            ITEM_MAP.put(3L, new Item(3L, "商品3", "http://图片3", "商品描述3", 3000L));
            ITEM_MAP.put(4L, new Item(4L, "商品4", "http://图片4", "商品描述4", 4000L));
            ITEM_MAP.put(5L, new Item(5L, "商品5", "http://图片5", "商品描述5", 5000L));
            ITEM_MAP.put(6L, new Item(6L, "商品6", "http://图片6", "商品描述6", 6000L));
            ITEM_MAP.put(7L, new Item(7L, "商品7", "http://图片7", "商品描述7", 7000L));
            ITEM_MAP.put(8L, new Item(8L, "商品8", "http://图片8", "商品描述8", 8000L));
            ITEM_MAP.put(8L, new Item(9L, "商品9", "http://图片9", "商品描述9", 9000L));
            ITEM_MAP.put(8L, new Item(10L, "商品10", "http://图片10", "商品描述10", 10000L));
        }
    
        /**
         * 模拟实现商品查询
         *
         * @param id
         * @return
         */
        public Item queryItemById(Long id) {
            return ITEM_MAP.get(id);
        }
    
    }
    
    5.1.5.编写ItemController
    package com.zpc.item.controller;
    
    import com.zpc.item.entity.Item;
    import com.zpc.item.service.ItemService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class ItemController {
    
        @Autowired
        private ItemService itemService;
    
        /**
         * 对外提供接口服务,查询商品信息
         *
         * @param id
         * @return
         */
        @GetMapping(value = "item/{id}")
        public Item queryItemById(@PathVariable("id") Long id) {
            return this.itemService.queryItemById(id);
        }
    
    }
    

    @RestController注解的说明:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Controller
    @ResponseBody
    public @interface RestController {
    
       /**
        * The value may indicate a suggestion for a logical component name,
        * to be turned into a Spring bean in case of an autodetected component.
        * @return the suggested component name, if any (or empty String otherwise)
        * @since 4.0.1
        */
       @AliasFor(annotation = Controller.class)
       String value() default "";
    
    }
    

    从源码可以看出,这是一个组合注解,组合了@Controller@Response注解。相当于我们同时写了这2个注解。

    @GetMapping注解的说明:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @RequestMapping(method = RequestMethod.GET)
    public @interface GetMapping {...}
    

    @GetMapping注解是@RequestMapping(method = RequestMethod.GET)简写方式。其功能都是一样的。

    同理还有其它注解:
    在这里插入图片描述

    5.1.6.编写程序入口
    package com.zpc.item.runner;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    
    /**
     * @author Evan
     */
    
    @SpringBootApplication//申明这是一个Spring Boot项目
    @ComponentScan(basePackages = {"com.zpc.item.controller","com.zpc.item.service"})//手动指定bean组件扫描范围
    public class ItemApp {
        public static void main(String[] args) {
            SpringApplication.run(ItemApp.class, args);
        }
    }
    
    5.1.7.创建application.yml配置文件

    SpringBoot以及SpringCloud项目支持yml和properties格式的配置文件。

    yml格式是YAML(Yet Another Markup Language)编写的格式,YAML和properties格式的文件是可以相互转化的。如:

    server:
      port: 8081 #服务端口
    

    等价于properties文件的配置:
    server.port=8081

    配置文件的示例:

    server:
      port: 8081 #服务端口
    
    5.1.8.启动程序测试

    在这里插入图片描述

    可以看到已经通过微服务查询到数据。

    5.2.实现订单微服务

    5.2.1.创建工程

    在这里插入图片描述
    在这里插入图片描述

    5.2.2.导入依赖
    <?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.zpc.microservice</groupId>
        <artifactId>microservice-item</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <finalName>${project.artifactId}</finalName>
            <plugins>
                <!-- 资源文件拷贝插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <!-- java编译插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        </project>
    
    5.2.3.创建订单Order实体
    package com.zpc.order.entity;
    
    import java.util.Date;
    import java.util.List;
    
    public class Order {
    
        private String orderId;
    
        private Long userId;
    
        private Date createDate;
    
        private Date updateDate;
    
        private List<OrderDetail> orderDetails;
    
        public Order() {
    
        }
    
        public Order(String orderId, Long userId, Date createDate, Date updateDate) {
            this.orderId = orderId;
            this.userId = userId;
            this.createDate = createDate;
            this.updateDate = updateDate;
        }
    
        public String getOrderId() {
            return orderId;
        }
    
        public void setOrderId(String orderId) {
            this.orderId = orderId;
        }
    
        public Long getUserId() {
            return userId;
        }
    
        public void setUserId(Long userId) {
            this.userId = userId;
        }
    
        public Date getCreateDate() {
            return createDate;
        }
    
        public void setCreateDate(Date createDate) {
            this.createDate = createDate;
        }
    
        public Date getUpdateDate() {
            return updateDate;
        }
    
        public void setUpdateDate(Date updateDate) {
            this.updateDate = updateDate;
        }
    
    
        public List<OrderDetail> getOrderDetails() {
            return orderDetails;
        }
    
        public void setOrderDetails(List<OrderDetail> orderDetails) {
            this.orderDetails = orderDetails;
        }
    
        @Override
        public String toString() {
            return "Order [orderId=" + orderId + ", userId=" + userId
                    + ", createDate=" + createDate + ", updateDate=" + updateDate
                    + "]";
        }}
    
    5.2.4.创建订单详情OrderDetail实体

    订单与订单详情是一对多的关系。

    package com.zpc.order.entity;
    
    public class OrderDetail {
    
        private String orderId;
    
        private Item item = new Item();
    
        public OrderDetail() {
    
        }
    
        public OrderDetail(String orderId, Item item) {
            this.orderId = orderId;
            this.item = item;
        }
    
        public String getOrderId() {
            return orderId;
        }
    
        public void setOrderId(String orderId) {
            this.orderId = orderId;
        }
    
        public Item getItem() {
            return item;
        }
    
        public void setItem(Item item) {
            this.item = item;
        }
    
        @Override
        public String toString() {
            return "OrderDetail [orderId=" + orderId + ", item=" + item + "]";
        }
    
    }
    
    5.2.5.将商品微服务项目中的Item类拷贝到当前工程

    在这里插入图片描述

    5.2.6.编写OrderService

    该Service实现的根据订单Id查询订单的服务,为了方便测试,我们将构造数据实现,不采用查询数据库的方式。

    package com.zpc.order.service;
    
    import com.zpc.order.entity.Item;
    import com.zpc.order.entity.Order;
    import com.zpc.order.entity.OrderDetail;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.*;
    
    @Service
    public class OrderService {
    
        private static final Map<String, Order> ORDER_DATA = new HashMap<String, Order>();
    
        static {
            // 模拟数据库,构造测试数据
            Order order = new Order();
            order.setOrderId("201810300001");
            order.setCreateDate(new Date());
            order.setUpdateDate(order.getCreateDate());
            order.setUserId(1L);
            List<OrderDetail> orderDetails = new ArrayList<OrderDetail>();
    
            Item item = new Item();// 此处并没有商品的数据,只是保存了商品ID,需要调用商品微服务获取
            item.setId(1L);
            orderDetails.add(new OrderDetail(order.getOrderId(), item));
    
            item = new Item(); // 构造第二个商品数据
            item.setId(2L);
            orderDetails.add(new OrderDetail(order.getOrderId(), item));
    
            order.setOrderDetails(orderDetails);
    
            ORDER_DATA.put(order.getOrderId(), order);
        }
    
        @Autowired
        private ItemService itemService;
    
        /**
         * 根据订单id查询订单数据
         *
         * @param orderId
         * @return
         */
        public Order queryOrderById(String orderId) {
            Order order = ORDER_DATA.get(orderId);
            if (null == order) {
                return null;
            }
            List<OrderDetail> orderDetails = order.getOrderDetails();
            for (OrderDetail orderDetail : orderDetails) {
                // 通过商品微服务查询商品详细数据
                Item item = this.itemService.queryItemById(orderDetail.getItem()
                        .getId());
                if (null == item) {
                    continue;
                }
                orderDetail.setItem(item);
            }
    
            return order;
        }
    
    }
    
    5.2.7.实现ItemService
    package com.zpc.order.service;
    
    
    import com.zpc.order.entity.Item;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    @Service
    public class ItemService {
    
        // Spring框架对RESTful方式的http请求做了封装,来简化操作
        @Autowired
        private RestTemplate restTemplate;
    
        public Item queryItemById(Long id) {
            return this.restTemplate.getForObject("http://127.0.0.1:8081/item/"
                    + id, Item.class);
        }
    
    }
    
    5.2.8.编写OrderController
    package com.zpc.order.controller;
    
    import com.zpc.order.entity.Order;
    import com.zpc.order.service.OrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class OrderController {
        @Autowired
        private OrderService orderService;
    
        @GetMapping(value = "order/{orderId}")
        public Order queryOrderById(@PathVariable("orderId") String orderId) {
            return this.orderService.queryOrderById(orderId);
        }
    }
    
    5.2.9.编写程序入口
    package com.zpc.order.runner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * @author Evan
     */
    @SpringBootApplication//申明这是一个Spring Boot项目
    @ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service"})//手动指定bean扫描范围
    public class OrderApp {
        public static void main(String[] args) {
            SpringApplication.run(OrderApp.class, args);
        }
    
        /**
         * 向Spring容器中定义RestTemplate对象
         * @return
         */
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
    5.2.10.编写application.yml配置文件
    server:
      port: 8082 #服务端口
    
    5.2.11.启动测试

    在这里插入图片描述

    测试结果可见,查询订单时,同时也将商品数据查询到。

    5.3.添加okHttp的支持

    okhttp是一个封装URL,比HttpClient更友好易用的工具。目前似乎okhttp更流行一些。

    官网:http://square.github.io/okhttp/

    在这里插入图片描述

    RestTemplate底层默认使用的jdk的标准实现,如果我们想让RestTemplate的底层使用okhttp,非常简单:
    1、添加okhttp依赖

    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>3.9.0</version>
    </dependency>
    

    2、设置requestFactory
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }
    

    测试:
    在这里插入图片描述

    结果(没有变化):
    在这里插入图片描述
    测试结果是一样的。

    5.4.解决订单系统中的url硬编码问题

    • 通过以上的测试我们发现,在订单系统中要调用商品微服务中的查询接口来获取数据,在订单微服务中将url硬编码到代码中,这样显然不好,因为,运行环境一旦发生变化这个url地址将不可用。

    • 如何解决呢?

    解决方案:将url地址写入到application.yml配置文件中。

    实现:
    修改application.yml文件:

    server:
      port: 8082 #服务端口
    myspcloud:
      item:
        url: http://127.0.0.1:8081/item/
    

    修改ItemService中的实现:

    @Service
    public class ItemService {
    
        // Spring框架对RESTful方式的http请求做了封装,来简化操作
        @Autowired
        private RestTemplate restTemplate;
    
        @Value("${myspcloud.item.url}")
        private String itemUrl;
    
        public Item queryItemById(Long id) {
            return this.restTemplate.getForObject(itemUrl
                    + id, Item.class);
        }
    
    }
    

    测试(debug可以看到效果):
    在这里插入图片描述

    5.5.继续优化解决硬编码的问题

    在SpringBoot中使用@ConfigurationProperties注解可以非常简单的将配置文件中的值映射成对象。
    第一步,创建ItemProperties类:

    package com.zpc.order.properties;
    
    public class ItemProperties {
        private String url;
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
    }
    

    第二步,创建OrderProperties类:

    package com.zpc.order.properties;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    /**
     * 以myspcloud开头的配置被匹配到
     * @author Evan
     */
    @Component
    @ConfigurationProperties(prefix="myspcloud")
    public class OrderProperties {
    
        private ItemProperties item = new ItemProperties();
    
        public ItemProperties getItem() {
            return item;
        }
    
        public void setItem(ItemProperties item) {
            this.item = item;
        }
    }
    

    第三步,在Itemservice中注入该对象:

    @Service
    public class ItemService {
    
        // Spring框架对RESTful方式的http请求做了封装,来简化操作
        @Autowired
        private RestTemplate restTemplate;
    
        //@Value("${myspcloud.item.url}")
        //private String itemUrl;
    
        @Autowired
        OrderProperties orderProperties;
    
        public Item queryItemById(Long id) {
            return this.restTemplate.getForObject(orderProperties.getItem().getUrl()
                    + id, Item.class);
        }
    

    第四步,启动类中增加bean扫描路径

    @ComponentScan(basePackages = {"com.zpc.order.controller","com.zpc.order.service","com.zpc.order.properties"})//
    手动指定bean扫描范围
    

    第五步,debug运行
    在这里插入图片描述
    可以看出,这种解决方案比第一种好很多,更加的方便的。

    思考:

    • 这样是否还存在问题?如果商品的微服务有多个怎么办?
    • 我们在订单微服务中使用了Item实体,直接采取从商品微服务拷贝代码的方式是否太生硬?

    6.SpringCloud快速入门

    6.1.分析硬编码的问题

    通过前面5.4、5.5的实现,我们视乎已经解决了url硬编码的问题,但是我们想想:
    1、如果商品微服务的ip地址发生了变更,订单微服务中的配置文件也需要跟着修改
    2、如果商品微服务有多个,那么在订单微服务中又该如何写地址?

    那应该怎么解决呢? – 通过服务注册、发现的机制来完成。

    6.2.微服务注册与发现

    原理示意图:
    在这里插入图片描述

    由上图可以看出:
    1、服务提供者将服务注册到注册中心
    2、服务消费者通过注册中心查找服务
    3、查找到服务后进行调用(这里就是无需硬编码url的解决方案)
    4、服务的消费者与服务注册中心保持心跳连接,一旦服务提供者的地址发生变更时,注册中心会通知服务消费者

    6.3.注册中心:Eureka

    SpringCloud提供了多种注册中心的支持,如:Eureka、consul、ZooKeeper等。Eureka已经闭源了。本教程第二篇也会介绍使用其它两种方式作为注册中心。
    在这里插入图片描述

    6.3.1.原理

    在这里插入图片描述

    Eureka包含两个组件:Eureka ServerEureka Client

    • Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。

    • Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就别一个内置的、使用轮询(round-robin)负载算法的负载均衡器。

    • 在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。

    • Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。

    6.3.2.编写Eureka Server

    第一步:创建Maven工程:

    在这里插入图片描述
    在这里插入图片描述
    第二步,导入依赖:
    这里需要导入SpringCloud的管理依赖。

    <?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.zpc.springcloud.eureka</groupId>
        <artifactId>demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>demo</name>
        <description>Demo project for Spring Boot</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
        <dependencyManagement>
            <dependencies>
                <!-- 导入SpringCloud的依赖管理 -->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Finchley.SR1</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        <dependencies>
        	<!--springboot 整合eureka服务端-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    第三步,编写程序启动类:

    package com.zpc.springcloud.eureka;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    /**
     * Eureka注册中心
     */
    @SpringBootApplication
    @EnableEurekaServer //申明这是一个Eureka服务
    public class AppEureka {
    
       public static void main(String[] args) {
          SpringApplication.run(AppEureka.class, args);
       }
    }
    

    第四步,编写application.yml配置文件:

    ###服务端口号
    server:
      port: 8100
    
    ###服务名称
    spring:
      application:
        name: app-eureka-center
    
    eureka:
      instance:
        #注册中心地址
        hostname: 127.0.0.1
    ###客户端调用地址
      client:
        serviceUrl:
          defaultZone: http://${eureka.instance.hostname}:8100/eureka/
    ###是否将自己注册到Eureka服务中,因为该应用本身就是注册中心,不需要再注册自己(集群的时候为true)
        register-with-eureka: false
    ###是否从Eureka中获取注册信息,因为自己为注册中心,不会在该应用中的检索服务信息
        fetch-registry: false
    

    第五步,启动程序做测试:
    在这里插入图片描述

    6.4.将商品微服务注册到Eureka

    接下来,我们需要将商品的微服务注册到Eureka服务中。

    第一步:修改pom文件,引入SpringCloud的管理依赖以及eureka服务依赖。

    <?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.zpc.microservice</groupId>
        <artifactId>microservice-item</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Finchley.SR1</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--springboot 整合eureka客户端-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <finalName>${project.artifactId}</finalName>
            <plugins>
                <!-- 资源文件拷贝插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <!-- java编译插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    第二步,修改application.yml配置文件:

    ###服务端口号(本身是一个web项目)
    server:
      port: 8081
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-item
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://127.0.0.1:8100/eureka
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
    

    第三步,修改启动类,增加@EnableEurekaClient 注解:

    package com.zpc.item.runner;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.context.annotation.ComponentScan;
    
    /**
     * @author Evan
     */
    //申明这是一个Spring Boot项目
    @SpringBootApplication
    @EnableEurekaClient
    @ComponentScan(basePackages = {"com.zpc.item.controller","com.zpc.item.service"})
    public class ItemApp {
        public static void main(String[] args) {
            SpringApplication.run(ItemApp.class, args);
        }
    
    }
    

    第四步,启动测试:

    在这里插入图片描述

    至此,我们已经将自己的微服务注册到Eureka server中了。

    6.5.订单系统从Eureka中发现商品服务

    之前我们在订单系统中是将商品微服务的地址进行了硬编码,现在,由于已经将商品服务注册到Eureka中,所以,只需要从Eureka中发现服务即可。

    第一步,在订单系统中添加依赖:

    <?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.zpc.microservice</groupId>
        <artifactId>microservice-order</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Finchley.SR1</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--springboot 整合eureka客户端-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.squareup.okhttp3</groupId>
                <artifactId>okhttp</artifactId>
                <version>3.9.0</version>
            </dependency>
    
        </dependencies>
    
        <build>
            <finalName>${project.artifactId}</finalName>
            <plugins>
                <!-- 资源文件拷贝插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <!-- java编译插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    第二步,修改application.yml配置文件:

    server:
      port: 8082 #服务端口
    myspcloud:
      item:
        url: http://127.0.0.1:8081/item/
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如订单服务)
    spring:
        application:
            name: app-order
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://127.0.0.1:8100/eureka
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
    

    第三步,修改ItemService的实现逻辑:

    package com.zpc.order.service;
    import com.zpc.order.entity.Item;
    import com.zpc.order.properties.OrderProperties;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    @Service
    public class ItemService {
    
        // Spring框架对RESTful方式的http请求做了封装,来简化操作
        @Autowired
        private RestTemplate restTemplate;
    
        @Autowired
        OrderProperties orderProperties;
    
        public Item queryItemById(Long id) {
    
            // 该方法走eureka注册中心调用(去注册中心根据app-item查找服务,这种方式必须先开启负载均衡@LoadBalanced)
            String itemUrl = "http://app-item/item/{id}";
            Item result = restTemplate.getForObject(itemUrl, Item.class, id);
            System.out.println("订单系统调用商品服务,result:" + result);
            return result;
        }
    }
    

    第四步,在启动类中添加@EnableEurekaClient注解 ,获取RestTemplate的方法上加 @LoadBalanced注解

    package com.zpc.order.runner;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * @author Evan
     */
    
    @SpringBootApplication//申明这是一个Spring Boot项目
    @EnableEurekaClient
    @ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties"})//手动指定bean扫描范围
    public class OrderApp {
        public static void main(String[] args) {
            SpringApplication.run(OrderApp.class, args);
        }
    
        /**
         * 向Spring容器中定义RestTemplate对象
         * @return
         */
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
        }
    
    }
    

    第五步,启动测试(此时有3个应用:Eureka注册中心、Item服务、order服务)
    在这里插入图片描述

    在注册中心http://localhost:8100看到有2个客户端:
    在这里插入图片描述

    7.深入理解Eureka

    7.1.为Eureka添加用户认证

    在前面的示例中,我们可以看到我们需要登录即可访问到Eureka服务,这样其实是不安全的。

    接下来,我们为Eureka添加用户认证。

    第一步,为Eureka服务端(eureka-server)添加安全认证依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    

    第二步,增加application.yml配置文件:

    ###服务端口号
    server:
      port: 8100
    
    ###服务名称
    spring:
      application:
        name: app-eureka-center
      security:
        basic:
          enable: true #开启基于HTTP basic的认证
        user: #配置用户的账号信息
          name: zpc
          password: 123456
    
    eureka:
      instance:
        #注册中心地址
        hostname: 127.0.0.1
    
    ###客户端调用地址
      client:
        serviceUrl:
          defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:8100/eureka/
    ###是否将自己注册到Eureka服务中,因为该应用本身就是注册中心,不需要再注册自己(集群的时候为true)
        register-with-eureka: false
    ###是否从Eureka中获取注册信息,因为自己为注册中心,不会在该应用中的检索服务信息
        fetch-registry: true
    

    第三步,在eurka服务端添加一个安全认证类:

    package com.zpc.springcloud.eureka;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.http.SessionCreationPolicy;
    
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        /**
         * 高版本springcloud的丢弃了配置:
         *
         * security:
         *   basic:
         *    enabled: true
         *
         * 所以应该使用以下方式开启
         *
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // Configure HttpSecurity as needed (e.g. enable http basic).
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
            http.csrf().disable();
            //注意:为了可以使用 http://${user}:${password}@${host}:${port}/eureka/ 这种方式登录,所以必须是httpBasic,
            // 如果是form方式,不能使用url格式登录
            http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
        }
    }
    

    第四步,重新启动Eureka服务进行测试:
    在这里插入图片描述

    输入正确的用户名密码即可登录。

    这时,服务提供者注册到Eureka时会报错:

    2018-10-31 23:01:13.419  WARN 143840 --- [nfoReplicator-0] c.n.d.s.t.d.RetryableEurekaHttpClient    : Request execution failure with status code 401; retrying on another server if available
    2018-10-31 23:01:13.420  WARN 143840 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_APP-ORDER/Evan-Zhou:app-order:8082 - registration failed Cannot execute request on any known server
    
    com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
    

    所以,需要在服务注册时也需要设置用户名和密码。

    7.1.1.服务注册时(client端)设置账户信息

    服务注册到有认证需求的注册中心时,需要设置如下地址:

    http://USER:PASSWORD@127.0.0.1:端口号/eureka/

    配置如下(至此,Eureka注册中心、Item服务、order服务3个配置文件全部改成了带账号密码的请求地址):

    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka
    

    7.2.Eureka的自我保护模式

    在这里插入图片描述

    如图,当前Eureka进入了自我保护模式。(先开启Eureka server端和client端,然后再断开client端,此时刷新Eureka界面,就会看到红色字样)
    在这里插入图片描述

    在短时间内丢失了服务实例的心跳,不会剔除该服务,这是eurekaserver的自我保护机制的宗旨。主要是为了防止由于短暂的网络故障误删除可用的服务。

    所以,一般进入自我保护模式,无需处理。如果,需要禁用自我保护模式,只需要在配置文件中添加配置即可:
    (测试环境、开发环境可以关闭自我保护机制,保证服务不可用时及时剔除)
    配置方式:在server端 配置文件中添加server: 配置

    eureka:
      instance:
        #注册中心地址
        hostname: 127.0.0.1
    
    ###客户端调用地址
      client:
        serviceUrl:
          defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:8100/eureka/
        register-with-eureka: false
        fetch-registry: true
    
      server:
          enable-self-preservation: false #禁用自我保护模式
    

    重新启动服务查看效果:
    在这里插入图片描述
    提示,如果禁用自我保护模式,在网络通信故障下可能会出现问题,因为服务可能只是短暂的不可用。
    上述界面出现后,断开client端,此时刷新Eureka界面,就会看到红色字样,同时会把服务实例从注册中心剔除:No instances available
    在这里插入图片描述

    7.3.Eureka的高可用(Eureka集群)

    前面的测试,我们会发现,Eureka服务是一个单点服务,在生产环境就会出现单点故障,为了确保Eureka服务的高可用,我需要搭建Eureka服务的集群。

    搭建Eureka集群非常简单,只要启动多个Eureka Server服务并且让这些Server端之间彼此进行注册即可实现。

    第一步,修改eureka server端的application.yml文件:

    • 端口为8100的机器注册到端口为9100的注册中心
    ###服务端口号
    server:
      port: 8100
    
    ###服务名称
    spring:
      application:
        name: app-eureka-center
      security:
        basic:
          enable: true #开启基于HTTP basic的认证
        user: #配置用户的账号信息
          name: zpc
          password: 123456
    
    eureka:
      instance:
        #注册中心地址
        hostname: 127.0.0.1
    
    ###客户端调用地址
      client:
        serviceUrl:
          defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:9100/eureka/
    ###是否将自己注册到Eureka服务中,集群的时候为true
        register-with-eureka: true
        fetch-registry: true
    
      server:
          enable-self-preservation: false
    

    第二步,修改配置文件,再建一个Eureka server工程,启动两个工程(两个工程的name属性一样)测试:

    • 端口为9100的机器注册到端口为8100的注册中心
    ###服务端口号
    server:
     port: 9100
    
    ###服务名称
    spring:
     application:
       name: app-eureka-center
     security:
       basic:
         enable: true #开启基于HTTP basic的认证
       user: #配置用户的账号信息
         name: zpc
         password: 123456
    
    eureka:
     instance:
       #注册中心地址
       hostname: 127.0.0.1
    ###客户端调用地址
     client:
       serviceUrl:
         defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:8100/eureka/
    ###是否将自己注册到Eureka服务中,集群的时候为true
       register-with-eureka: true
       fetch-registry: true
    ###测试环境、开发环境可以关闭自我保护机制,保证服务不可用时及时剔除
     server:
       enable-self-preservation: false
       eviction-interval-timer-in-ms: 2000
    

    测试结果:
    在这里插入图片描述
    在这里插入图片描述

    可以看到,2个Eureka服务进行了彼此注册。

    7.4.将client端服务注册到Eureka高可用集群

    服务注册到Eureka集群时,可以指定多个,也可以指定一个Eureka服务(因为Eureka服务集群间彼此互联)。

    修改商品微服务项目microservice-item的application.yml配置文件,defaultZone 写上所有注册中心的地址:

    ###服务端口号(本身是一个web项目)
    server:
      port: 8081
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-item
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
    
    

    重启启动,测试:可以通过停止其中一个Eureka server服务进行测试,结果会发现集群是高可用。
    即:启动2个Eureka服务端(注册中心),1个商品服务(item),1个订单服务(order)。停掉其中一个Eureka服务端后,访问另外一个Eureka服务端页面可以看到item服务立马切换到此注册中心,订单服务照样可以访问商品服务,浏览器输入http://localhost:8082/order/201810300001还能获取到商品数据(因为商品服务同时注册到了2个注册中心)。

    为了防止是缓存的效果,OrderService 再加一条数据201810300002 ,用http://localhost:8082/order/201810300002测试高可用。

    package com.zpc.order.service;
    
    import com.zpc.order.entity.Item;
    import com.zpc.order.entity.Order;
    import com.zpc.order.entity.OrderDetail;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.*;
    
    @Service
    public class OrderService {
    
        private static final Map<String, Order> ORDER_DATA = new HashMap<String, Order>();
    
        static {
            // 模拟数据库,构造测试数据
            Order order1 = new Order();
            order1.setOrderId("201810300001");
            order1.setCreateDate(new Date());
            order1.setUpdateDate(order1.getCreateDate());
            order1.setUserId(1L);
            List<OrderDetail> orderDetails1 = new ArrayList<OrderDetail>();
    
            Item item = new Item();// 此处并没有商品的数据,只是保存了商品ID,需要调用商品微服务获取
            item.setId(1L);
            orderDetails1.add(new OrderDetail(order1.getOrderId(), item));
    
            item = new Item(); // 构造第二个商品数据
            item.setId(6L);
            orderDetails1.add(new OrderDetail(order1.getOrderId(), item));
            order1.setOrderDetails(orderDetails1);
            ORDER_DATA.put(order1.getOrderId(), order1);
    
            Order order2 = new Order();
            order2.setOrderId("201810300002");
            order2.setCreateDate(new Date());
            order2.setUpdateDate(order2.getCreateDate());
            order2.setUserId(3L);
            List<OrderDetail> orderDetails2 = new ArrayList<OrderDetail>();
    
            Item item2 = new Item();// 此处并没有商品的数据,只是保存了商品ID,需要调用商品微服务获取
            item2.setId(3L);
            orderDetails2.add(new OrderDetail(order2.getOrderId(), item2));
    
            item2 = new Item(); // 构造第二个商品数据
            item2.setId(5L);
            orderDetails2.add(new OrderDetail(order2.getOrderId(), item2));
            order2.setOrderDetails(orderDetails2);
            ORDER_DATA.put(order2.getOrderId(), order2);
        }
    
        @Autowired
        private ItemService itemService;
    
        /**
         * 根据订单id查询订单数据
         *
         * @param orderId
         * @return
         */
        public Order queryOrderById(String orderId) {
            Order order = ORDER_DATA.get(orderId);
            if (null == order) {
                return null;
            }
            List<OrderDetail> orderDetails = order.getOrderDetails();
            for (OrderDetail orderDetail : orderDetails) {
                // 通过商品微服务查询商品详细数据
                Item item = this.itemService.queryItemById(orderDetail.getItem()
                        .getId());
                if (null == item) {
                    continue;
                }
                orderDetail.setItem(item);
            }
    
            return order;
        }
    
    }
    

    7.5.指定服务的IP地址

    在服务的提供者配置文件中可以指定ip地址,如下:

    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
    

    在这里插入图片描述

    7.6.指定实例id

    通过instance-id 参数指定服务注册到Eureka中的服务实例id:

    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    

    在这里插入图片描述

    8.负载均衡:Ribbon

    首先,我们思考一个问题,如果为同一个的提供者在Eureka中注册了多个服务,那么客户端该如何选择服务呢?

    这时,就需要在客户端实现服务的负载均衡。

    在SpringCloud中推荐使用Ribbon来实现负载均衡。

    8.1.Ribbon简介

    在这里插入图片描述

    8.2.架构

    在这里插入图片描述

    8.3.开始使用SpringCloud Ribbon

    8.3.1.为microservice order增加ribbon依赖
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
    

    其实,该依赖是可以省略的,因为spring-cloud-starter-netflix-eureka-client中已经包含了spring-cloud-starter-netflix-ribbon:
    在这里插入图片描述

    8.3.2.为RestTemplate设置@LoadBalanced注解
    package com.zpc.order.runner;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * @author Evan
     */
    @SpringBootApplication//申明这是一个Spring Boot项目
    @EnableEurekaClient
    @ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties"})//手动指定bean扫描范围
    public class OrderApp {
        public static void main(String[] args) {
            SpringApplication.run(OrderApp.class, args);
        }
    
        /**
         * 向Spring容器中定义RestTemplate对象
         * @return
         */
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
        }
    
    }
    

    这样,RestTemplate就具备了负载均衡的功能。

    8.3.3.改造ItemService的实现

    之前的实现(其实本教程前面没有用这种方式):

    public Item queryItemById(Long id) {
        String serviceId = "app-item";
        List<ServiceInstance> instances = this.discoveryClient.getInstances(serviceId);
        if(instances.isEmpty()){
            return null;
        }
        // 为了演示,在这里只获取第一个实例
        ServiceInstance serviceInstance = instances.get(0);
        String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
        return this.restTemplate.getForObject("http://" + url + "/item/" + id, Item.class);
    }
    

    改造后的实现(其实本教程一直采用的是这种方式):

    public Item queryItemById(Long id) {
        // 该方法走eureka注册中心调用(这种方式必须先开启负载均衡@LoadBalanced)
        String itemUrl = "http://app-item/item/{id}";
        Item result = restTemplate.getForObject(itemUrl, Item.class, id);
        System.out.println("订单系统调用商品服务,result:" + result);
        return result;
    }
    

    可以发现,实现更加简化了。
    这种方式关键在于获取RestTemplat对象时要加上@LoadBalanced注解 ,否则restTemplate.getForObject方法会报java.net.UnknownHostException: app-item,而前面一种方式是手动指定了获取的服务实例,不需要加此注解。

    8.3.4.重启订单服务进行测试

    测试结果:
    在这里插入图片描述

    结果显示,可以正常获取到商品数据。

    内部原理(可以启动多个item实例看效果,比如端口分别设为8081,8091,在下面2个类里打断点):
    org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor
    在这里插入图片描述
    org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient
    在这里插入图片描述

    在执行请求前会经过org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor这个拦截器,并且在org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient中,通过serverId查找服务地址,通过一定的负载均衡策略去做真正的请求。

    8.3.5.测试负载均衡

    测试方法:
    第一步,启动2个microservice-item服务(多个也可以)
    在这里插入图片描述
    第二步,导入测试依赖,编写单元测试用例:

    <!-- 引入SpringBoot的单元测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    

    第三步,编写测试用例:

    package com.zpc.test;
    
    import com.zpc.order.runner.OrderApp;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
    import org.springframework.context.annotation.Import;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes = ItemServiceTest.class)
    @Import(OrderApp.class)
    public class ItemServiceTest {
    
        @Autowired
        private LoadBalancerClient loadBalancerClient;//自动注入
    
        @Test
        public void test() {
            String serviceId = "app-item";
            for (int i = 0; i < 100; i++) {
                ServiceInstance serviceInstance = this.loadBalancerClient.choose(serviceId);
                System.out.println("第" + (i + 1) + "次:" + serviceInstance.getHost() + ": " + serviceInstance.getPort());
            }
        }
    
    }
    

    测试结果:
    第1次:127.0.0.1: 8091
    第2次:127.0.0.1: 8081
    第3次:127.0.0.1: 8091
    第4次:127.0.0.1: 8081
    第5次:127.0.0.1: 8091
    第6次:127.0.0.1: 8081
    第7次:127.0.0.1: 8091
    第8次:127.0.0.1: 8081
    第9次:127.0.0.1: 8091
    第10次:127.0.0.1: 8081
    第11次:127.0.0.1: 8091
    第12次:127.0.0.1: 8081
    第13次:127.0.0.1: 8091
    第14次:127.0.0.1: 8081
    第15次:127.0.0.1: 8091
    第16次:127.0.0.1: 8081
    第17次:127.0.0.1: 8091
    第18次:127.0.0.1: 8081
    第19次:127.0.0.1: 8091
    第20次:127.0.0.1: 8081
    第21次:127.0.0.1: 8091
    第22次:127.0.0.1: 8081
    第23次:127.0.0.1: 8091
    第24次:127.0.0.1: 8081
    第25次:127.0.0.1: 8091
    第26次:127.0.0.1: 8081
    第27次:127.0.0.1: 8091
    第28次:127.0.0.1: 8081
    第29次:127.0.0.1: 8091
    第30次:127.0.0.1: 8081
    第31次:127.0.0.1: 8091
    第32次:127.0.0.1: 8081
    第33次:127.0.0.1: 8091
    第34次:127.0.0.1: 8081
    第35次:127.0.0.1: 8091
    第36次:127.0.0.1: 8081
    第37次:127.0.0.1: 8091
    第38次:127.0.0.1: 8081
    第39次:127.0.0.1: 8091
    第40次:127.0.0.1: 8081

    或者在应用程序里测试,比如在item微服务里打印端口信息,这样多启动几个item服务,多次请求可以看到效果:

    package com.zpc.item.controller;
    
    import com.zpc.item.entity.Item;
    import com.zpc.item.service.ItemService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author Evan
     */
    @RestController
    public class ItemController {
    
        @Value("${server.port}")
        private String port;
    
        @Autowired
        private ItemService itemService;
    
        /**
         * 对外提供接口服务,查询商品信息
         *
         * @param id
         * @return
         */
        @GetMapping(value = "item/{id}")
        public Item queryItemById(@PathVariable("id") Long id) {
            System.out.println("service port:"+port);
            return this.itemService.queryItemById(id);
        }
    
    }
    

    8.4.设置负载均衡策略

    只需要在配置文件中添加配置
    serviceId.ribbon.NFLoadBalancerRuleClassName=自定义的负载均衡策略类

    例如在order微服务的yml配置文件中添加:

    app-item:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    

    其中app-item是要访问的服务id

    测试:
    第1次:127.0.0.1: 8081
    第2次:127.0.0.1: 9081
    第3次:127.0.0.1: 9081
    第4次:127.0.0.1: 8081
    第5次:127.0.0.1: 9081
    第6次:127.0.0.1: 8081
    第7次:127.0.0.1: 9081
    第8次:127.0.0.1: 9081
    第9次:127.0.0.1: 8081
    第10次:127.0.0.1: 8081
    第11次:127.0.0.1: 8081
    第12次:127.0.0.1: 8081
    第13次:127.0.0.1: 8081
    第14次:127.0.0.1: 9081
    第15次:127.0.0.1: 9081
    第16次:127.0.0.1: 9081
    第17次:127.0.0.1: 8081
    第18次:127.0.0.1: 9081
    第19次:127.0.0.1: 8081
    第20次:127.0.0.1: 8081

    8.5.其它策略

    接口:com.netflix.loadbalancer.IRule,其实现类:
    在这里插入图片描述
    策略描述:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    9.容错保护:Hystrix

    9.1.分析

    在这里插入图片描述
    在这里插入图片描述

    9.2.雪崩效应

    • 在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。

    • 如果下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。

    在这里插入图片描述

    9.3.SpringCloud Hystrix简介

    主页:https://github.com/Netflix/Hystrix/

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    9.4.原理说明

    正常情况:
    在这里插入图片描述
    在这里插入图片描述
    当对特定服务的呼叫达到一定阈值时(Hystrix中的默认值为5秒内的20次故障),电路打开,不进行通讯。并且是一个隔离的线程中进行的。

    9.5.快速入门

    在 microservice-order系统中增加Hystrix容错(本教程中order微服务工程主要扮演消费者的角色)。

    9.5.1.导入依赖
    <!--整合hystrix-->
    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
    9.5.2.修改ItemService的queryItemById方法

    在要做容错处理的方法上加@HystrixCommand注解,并指定一个容错方法,即fallbackMethod 。

    /**
     * 进行容错处理
     * fallbackMethod的方法参数个数类型要和原方法一致
     *
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
    public Item queryItemById3(Long id) {
        String itemUrl = "http://app-item/item/{id}";
        Item result = restTemplate.getForObject(itemUrl, Item.class, id);
        System.out.println("===========HystrixCommand queryItemById-线程池名称:" + Thread.currentThread().getName() + "订单系统调用商品服务,result:" + result);
        return result;
    }
    
    /**
     * 请求失败执行的方法
     * fallbackMethod的方法参数个数类型要和原方法一致
     *
     * @param id
     * @return
     */
    public Item queryItemByIdFallbackMethod(Long id) {
        return new Item(id, "查询商品信息出错!", null, null, null);
    }
    
    9.5.3.在启动类OrderApplication添加@EnableHystrix注解
    /**
     * @author Evan
     */
    @SpringBootApplication
    @EnableEurekaClient
    @EnableHystrix
    @ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties"})//手动指定bean扫描范围
    public class OrderApp {
        public static void main(String[] args) {
            SpringApplication.run(OrderApp.class, args);
        }
    
        /**
         * 向Spring容器中定义RestTemplate对象
         * @return
         */
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
        }
    
    }
    
    9.5.4.在Controller增加一个入口
    package com.zpc.order.controller;
    
    import com.zpc.order.entity.Order;
    import com.zpc.order.service.OrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class OrderController {
        @Autowired
        private OrderService orderService;
    
        @GetMapping(value = "order/{orderId}")
        public Order queryOrderById(@PathVariable("orderId") String orderId) {
            return this.orderService.queryOrderById(orderId);
        }
    
        @GetMapping(value = "order2/{orderId}")
        public Order queryOrderById2(@PathVariable("orderId") String orderId) {
            return this.orderService.queryOrderByIdx(orderId);
        }
    }
    

    OrderService中增加:

    public Order queryOrderByIdx(String orderId) {
        Order order = ORDER_DATA.get(orderId);
        if (null == order) {
            return null;
        }
        List<OrderDetail> orderDetails = order.getOrderDetails();
        for (OrderDetail orderDetail : orderDetails) {
            // 通过商品微服务查询商品详细数据
            Item item = this.itemService.queryItemById3(orderDetail.getItem()
                    .getId());
            if (null == item) {
                continue;
            }
            orderDetail.setItem(item);
        }
    
        return order;
    }
    
    9.5.5.重新启动进行测试

    http://localhost:8082/order/201810300001
    http://localhost:8082/order2/201810300001
    在这里插入图片描述

    测试一切正常。可以发现加了@HystrixCommand注解的方法和普通方法不是共用的线程池,是隔离的:
    在这里插入图片描述

    接下来,我们把商品服务停止进行测试:

    在这里插入图片描述

    • 可以看到,使用了hystrix容错机制时订单服务(app-order)正常,尽管查询商品服务(app-item)已停止服务,查询到的是错误信息。

    • 由此可见,商品服务的宕机并没有影响订单服务的正常工作,起到的容错效果。

    如果调用没有做Hytrix容错的方法,则直接返回异常信息:
    在这里插入图片描述

    9.6.定义统一的fallback接口

            如果在每个方法里都定义对应的fallback方法,显得太臃肿,实际项目中可以单独使用一个类定义各种fallback,在使用feign客户端时使用统一fallback接口。
    详情请见本教程的姊妹篇 Spring cloud详细教程2 :介绍了其它SpringCloud组件。

    欢迎关注公众号「程猿薇茑」
    微信扫一扫
    展开全文
  • SpringCloud详细教程(下)

    万次阅读 多人点赞 2018-11-16 19:11:00
    SpringCloud详细教程,springcloud完全教程。Springcloud微服务教程(上)的姊妹篇:介绍了其它SpringCloud组件。

    【仅需9.9订阅专栏合集,作者所有付费文章都能看】

    推荐【SpringCloud教程https://blog.csdn.net/hellozpc/article/details/83692496
    推荐【Kafka教程https://bigbird.blog.csdn.net/article/details/108770504
    推荐【rabbitmq教程https://bigbird.blog.csdn.net/article/details/81436980
    推荐【Flink教程https://blog.csdn.net/hellozpc/article/details/109413465
    推荐【SpringBoot教程https://blog.csdn.net/hellozpc/article/details/107095951
    推荐【Mybatis教程https://blog.csdn.net/hellozpc/article/details/80878563
    推荐【SnowFlakehttps://blog.csdn.net/hellozpc/article/details/108248227
    推荐【并发限流https://blog.csdn.net/hellozpc/article/details/107582771

    SpringCloud教程代码下载地址  https://download.csdn.net/download/zpcandzhj/10780804

    SpringCloud 详细教程

    文章目录

    1.教程大纲

    1、Feign声明式客户端(REST调用)
    2、服务网关 SpringCloud Zuul
    3、分布式配置中心SpringCloud Config
    4、消息总线 SpringCloud Bus
    5、整合consul、zookeeper作为注册中心
    6、整合swagger 统一管理API

    2.Feign客户端-声明式REST调用

    2.1.分析

    之前《SpringCloud教程(一)》中我们通过RestTemplate调用REST服务,代码是这样的:

    @HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
    public Item queryItemById(Long id) {
        String itemUrl = "http://app-item/item/{id}";
        Item result = restTemplate.getForObject(itemUrl, Item.class, id);
        return result;
    }
    

    虽然使用了Ribbon和Hystrix可以实现负载均衡和容错处理,但是这个编码在实现大量业务时会显得太过于冗余(如,多参数的URL拼接)。
    思考:有没有更加优雅的实现呢?

    2.2.Feign简介

    项目主页:https://github.com/OpenFeign/feign
    在这里插入图片描述
    在这里插入图片描述

    2.3.快速入门

    在订单微服务microservice order中增加对Feign的支持。

    2.3.1.导入依赖
    <!--springboot 整合fegnin客户端-->
    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
    2.3.2.创建一个ItemFeignClient接口
    package com.zpc.order.feign;
    import com.zpc.order.entity.Item;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    /**
     * 申明这是一个Feign客户端,并且指明服务id
     * @author Evan
     */
    @FeignClient(value = "app-item")
    public interface ItemFeignClient {
        /**
         * 这里定义了类似于SpringMVC用法的方法,就可以进行RESTful方式的调用了
         *
         * @param id
         * @return
         */
        @RequestMapping(value = "/item/{id}", method = RequestMethod.GET)
        Item queryItemById(@PathVariable("id") Long id);
    }
    
    2.3.3.改造ItemService
    @Autowired
    private ItemFeignClient itemFeignClient;
    
    @HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
    public Item queryItemById3(Long id) {
        String itemUrl = "http://app-item/item/{id}";
        Item result = itemFeignClient.queryItemById(id);
        System.out.println("===========HystrixCommand queryItemById-线程池名称:" + Thread.currentThread().getName() + "订单系统调用商品服务,result:" + result);
        return result;
    }
    
    2.3.4.在启动类中添加 @EnableFeignClients 注解
    package com.zpc.order.runner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.netflix.hystrix.EnableHystrix;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication//申明这是一个Spring Boot项目
    @EnableEurekaClient
    @EnableHystrix
    @EnableFeignClients(basePackages ="com.zpc.order.feign")
    @ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties"})//手动指定bean扫描范围
    public class OrderApp {
        public static void main(String[] args) {
            SpringApplication.run(OrderApp.class, args);
        }
    
        /**
         * 向Spring容器中定义RestTemplate对象
         * @return
         */
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
        }
    }
    
    2.3.5.重启测试

    在这里插入图片描述

    测试结果,一切正常。

    2.4.到底发生了什么?

    @Autowired
    private ItemFeignClient itemFeignClient;
    
    @HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
    public Item queryItemById3(Long id) {
        String itemUrl = "http://app-item/item/{id}";
        //Item result = restTemplate.getForObject(itemUrl, Item.class, id);
        Item result = itemFeignClient.queryItemById(id);
        return result;
    }
    

    写这样的代码,就可以访问RESTful服务啦?

    流程分析:
    1、由于我们在入口程序使用了@EnableFeignClients注解,Spring启动后会扫描标注了@FeignClient注解的接口,然后生成代理类。
    2、我们在@FeignClient接口中指定了value,其实就是指定了在Eureka中的服务名称。
    3、在FeignClient中的定义方法以及使用了SpringMVC的注解,Feign就会根据注解中的内容生成对应的URL,然后基于Ribbon的负载均衡去调用REST服务。
    为什么使用的是SpringMVC的注解?

    i.其实,Feign是有自己的注解的@RequestLine,是因为SpringCloud对Feign做了增强,兼容了SpringMVC的注解,使我们的学习成本更低
    在这里插入图片描述
    ii.专业的解释是这样的:
    org.springframework.cloud.netflix.feign.FeignClientsConfiguration
    在这里插入图片描述
    设置的默认契约是SpringMVC契约。

    2.5.Feign的多参数构造

    服务方参数列表使用注解@RequestParam@PathVariable修饰
    在这里插入图片描述

    2.6.设置统一的hystrix fallback接口

    参考:http://www.ityouknow.com/springcloud/2017/05/16/springcloud-hystrix.html
    一般在实际开发中fallback 方法不会直接写在接口方法所在类里,那样太杂乱,例如之前订单工程中的写法:

    @HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
    public Item queryItemById3(Long id) {
        Item result = itemFeignClient.queryItemById(id);
        System.out.println("===========HystrixCommand queryItemById-线程池名称:" + Thread.currentThread().getName() + "订单系统调用商品服务,result:" + result);
        return result;
    }
    
    public Item queryItemByIdFallbackMethod(Long id) {
        return new Item(id, "查询商品信息出错!", null, null, null);
    }
    

    Order工程改进(将fallback方法放到类中):
    1.不在方法上使用@HystrixCommand注解

    //@HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
    public Item queryItemById3(Long id) {
        Item result = itemFeignClient.queryItemById(id);
        System.out.println("===========HystrixCommand queryItemById-线程池名称:" + Thread.currentThread().getName() + "订单系统调用商品服务,result:" + result);
        return result;
    }
    

    2.创建回调类

    package com.zpc.order.fallback;
    import com.zpc.order.entity.Item;
    import com.zpc.order.feign.ItemFeignClient;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.PathVariable;
    /**
     * 此类中的方法专门用于服务降级,该类一般要实现调用远程服务的接口(这样保证方法名一致)
     */
    @Component
    public class ItemServiceFallback  implements ItemFeignClient {
        /**
         * 服务降级的方法要和原方法一致(名称、参数列表)
         * @param id
         * @return
         */
        @Override
        public Item queryItemById(@PathVariable("id") Long id) {
            return new Item(null, "服务降级方法queryItemById", null, "服务降级方法queryItemById", null);
        }
    }
    

    3.在Feign客户端中添加fallback属性

    package com.zpc.order.feign;
    import com.zpc.order.entity.Item;
    import com.zpc.order.fallback.ItemServiceFallback;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    /**
     * 申明这是一个Feign客户端,并且指明服务id
     * 实际开发中ItemFeignClient一般直接继承(extends)服务提供方的接口以避免代码重复(例如Item工程会以jar包的形式提供ItemService接口)
     * @author Evan
     */
    @FeignClient(value = "app-item",fallback = ItemServiceFallback.class)
    public interface ItemFeignClient{
        /**
         * 这里定义了类似于SpringMVC用法的方法,就可以进行RESTful方式的调用了
         *
         * @param id
         * @return
         */
        @RequestMapping(value = "/item/{id}", method = RequestMethod.GET)
        Item queryItemById(@PathVariable("id") Long id);
    }
    

    4.配置文件中开启hystrix

    #开启hystrix断路器
    feign:
      hystrix:
        enabled: true
    

    5.修改启动类,增加包扫描

    @ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties","com.zpc.order.fallback"})//手动指定bean扫描范围
    

    6.重新启动应用测试
    服务都正常的情况:
    在这里插入图片描述
    在这里插入图片描述
    停止Item服务,访问Order服务没有配置hystrix熔断的普通方法:
    在这里插入图片描述

    停止Item服务,访问Order服务中配置了hystrix熔断的方法:
    在这里插入图片描述
    配置fallback类OK!

    3.服务网关 SpringCloud Zuul

    3.1.分析

    通过前面的学习,使用SpringCloud实现微服务的架构基本成型,大致是这样的:

    在这里插入图片描述

    • 我们使用SpringCloud Netflix中的Eureka作为服务注册中心完成服务注册与发现;而服务间通过FeignRibbon实现服务的消费以及负载均衡;通过SpringCloud Config实现了应用多环境的外部化配置以及版本管理。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。

    • 思考:在该架构中,我们的服务集群包含:内部服务Service A和Service B,他们都会注册与订阅服务至Eureka Server,而Open Service是一个对外的服务,通过负载均衡公开至服务调用方。我们把焦点聚集在对外服务这块,这样的实现是否合理,或者是否有更好的实现方式呢?

    先来说说这样架构需要做的一些事儿以及存在的不足:

    • 首先,破坏了服务无状态特点。
      为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。
      从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外可续对接口访问的控制处理。
    • 其次,无法直接复用既有接口。
      当我们需要对一个既有的集群内的接口,实现外部访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。

    面对类似上面的问题,我们要如何解决呢? 答案是:服务网关

    • 为了解决上面这些问题,我们需要将权限控制、日志收集这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的均衡负载器 服务网关。

    • 服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、负载均衡功能之外,它还具备了权限控制等功能。SpringCloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

    3.2.Zuul的简介

    官网:https://github.com/Netflix/zuul
    在这里插入图片描述

    在这里插入图片描述

    3.3.使用Zuul之后的架构

    在这里插入图片描述
    从架构图中可以看出,客户端请求微服务时,先经过Zuul之后再请求,这样就可以将一些类似于校验的业务逻辑放到zuul中完成。
    而微服务自身只需要关注自己的业务逻辑即可。当然在Zuul上层也可以搭建Nginx、F5等负载均衡设施。

    3.4.快速入门

    3.4.1.创建工程microsrvice-api-gateway

    在这里插入图片描述
    在这里插入图片描述

    3.4.2.导入依赖
    <?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.zpc.microservice</groupId>
        <artifactId>microservice-api-gateway</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
    
        <dependencyManagement>
            <dependencies>
                <!-- 导入SpringCloud的依赖管理 -->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Finchley.SR1</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        <dependencies>
    <!--整合zuul网关-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            </dependency>
         </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    
    3.4.3.编写启动类ApiGatewayApplication
    package com.zpc.gateway;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    
    @EnableZuulProxy
    @SpringBootApplication
    public class ApiGatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(ApiGatewayApplication.class, args);
        }
    }
    
    3.4.4.编写application.yml文件
    server:
      port: 8087 #服务端口
    spring:
      application:
        name: app-zuul-gateway #指定服务名
    
    3.4.5.编写路由规则

    我们编写路由规则:

    server:
      port: 8087 #服务端口
    spring:
      application:
        name: app-zuul-gateway #指定服务名
    zuul:
      routes: #定义服务转发规则
        item-service: #item-service这个名字任意取的
          path: /item-service/** #配置请求URL的请求规则
          url: http://127.0.0.1:8081 #真正的微服务地址,path匹配的请求都转发到这里
    
    3.4.6.启动测试

    同时启动商品微服务工程、网关工程。
    在这里插入图片描述

    可以看到,已经通过zuul访问到了商品微服务。

    3.5.面向服务的路由

    在快速入门中我们已经通过了URL映射,访问到了商品微服务。这样做会存在一个问题,就是,如果商品微服务的地址发生了改变怎么办?
    很自然的能够想到,不应该配置具体的url而是走Eureka注册中心获取地址。

    3.5.1.添加Eureka服务的依赖
    <!--整合eureka客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
    3.5.2.修改application.yml配置文件
    server:
      port: 8087 #服务端口
    spring:
      application:
        name: app-zuul-gateway #指定服务名
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    zuul:
      routes: #定义服务转发规则
        item-service: #item-service这个名字是任意写的
          path: /item-service/** #匹配item-service的请求app-item服务
          #url: http://127.0.0.1:8081 #真正的微服务地址
          serviceid: app-item
        order-service: #名字尽量和业务系统相关
          path: /order-service/** #匹配order-service的请求app-order服务
          serviceid: app-order
    
    3.5.3.启动测试

    启动Eureka注册中心、Item服务、order服务、网管服务4个工程。
    查看Eureka注册中:
    在这里插入图片描述
    发现已经有gateway在注册中心了。
    接下来测试,功能是否正常:
    在这里插入图片描述
    在这里插入图片描述
    发现一切正常。

    3.6.zuul配置详解

    3.6.1.指定服务id

    在这里插入图片描述

    3.6.2.忽略指定服务

    在这里插入图片描述

    3.6.3.忽略所有服务,只是有路由指定

    在这里插入图片描述

    3.6.4.同时配置path和url

    在这里插入图片描述

    3.6.5.面向服务配置,不破坏Hystrix、Ribbon特性
    zuul:
      routes: #定义服务转发规则
        item-service: #item-service这个名字是任意写的
          path: /item-service/** #匹配item-service的url路径请求app-item服务
          #url: http://127.0.0.1:8081 #真正的微服务地址
          serviceid: app-item
        order-service: #名字尽量和业务系统相关
          path: /order-service/** #匹配order-service的url路径请求app-order服务
          serviceid: app-order #指定Eureka注册中心的服务id
    
    3.6.6.使用正则表达式指定路由规则

    在这里插入图片描述

    3.6.7.路由前缀

    在这里插入图片描述

    3.6.8.忽略某些路径

    在这里插入图片描述

    3.7.过滤器

    过滤器是Zuul的重要组件。

    3.7.1.过滤器ZuulFilter

    ZuulFilter是一个抽象类,其实现类需要实现4个方法:

    • shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
    • run:过滤器的具体业务逻辑。
    • filterType:返回字符串代表过滤器的类型
      a)pre:请求在被路由之前执行
      b)routing:在路由请求时调用
      c)post:在routing和errror过滤器之后调用
      d)error:处理请求时发生错误调用
    • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
    3.7.2.执行流程

    在这里插入图片描述
    在这里插入图片描述

    3.8.过滤器实战

    需求:通过编写过滤器实现用户是否登录的检查。
    实现:通过判断请求中是否有token,如果有认为就是已经登录的,如果没有就认为是非法请求,响应401.

    3.8.1.编写UserLoginZuulFilter
    package com.zpc.gateway.filter;
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.stereotype.Component;
    import javax.servlet.http.HttpServletRequest;
    /**
     *  网关过滤器
     *  加入到Spring容器
     * @author Evan
     */
    @Component
    public class UserLoginZuulFilter extends ZuulFilter {
    
        @Override
        public boolean shouldFilter() {
            return true; // 该过滤器需要执行
        }
    
        @Override
        public Object run() { //编写业务逻辑
            RequestContext requestContext = RequestContext.getCurrentContext();
            HttpServletRequest request = requestContext.getRequest();
            String token = request.getParameter("token");
            if(StringUtils.isEmpty(token)){
                requestContext.setSendZuulResponse(false); // 过滤该请求,不对其进行路由
                requestContext.setResponseStatusCode(401); // 设置响应状态码
                requestContext.setResponseBody(" token is empty!!"); // 设置响应状态码
                return null;
            }
            return null;
        }
    
        @Override
        public String filterType() {
            return "pre"; // 设置过滤器类型为:pre
        }
    
        @Override
        public int filterOrder() {
            return 0;// 设置执行顺序为0
        }
    }
    

    入口程序:

    package com.zpc.gateway.runner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    import org.springframework.context.annotation.ComponentScan;
    
    @EnableZuulProxy
    @SpringBootApplication
    @ComponentScan(basePackages = "com.zpc.gateway.filter")
    public class ApiGatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(ApiGatewayApplication.class, args);
        }
    }
    
    3.8.2.启动测试

    在这里插入图片描述
    带token正常访问:
    在这里插入图片描述
    可以看到过滤器已经生效。

    3.9.默认开启ribbon

    Zuul网关默认开启了 ribbon 负载均衡,可以修改端口,启动多个Item服务进行测试,不断刷新浏览器请求:
    http://localhost:8087/item-service/item/2?token=1
    发现多个Item服务被轮询的访问:
    service port:8080
    service port:8081

    3.10.动态网关

    将yml配置文件中的路由配置信息存在配置中心中可以实现网关的动态配置。请见4.8节。

    3.11.网关集群

    Zuul可以配合nginx搭建网关集群。只要在nginx的配置文件nginx.conf里配置多个zuul地址:

    http {
      	upstream myhttpServer{
    #配置多个zuul地址
    	server localhost:8087;
    	server localhost:8086;
    	}
    	server {
    		listen       80;
           server_name  www.zpc.com;
    		location / {
                proxy_pass   http://myhttpServer;
                index  index.html index.htm;
            }
    	}
    }
    

    架构图:
    在这里插入图片描述
    浏览器访问:http://www.zpc.com/item-service/item/2?token=1

    欢迎关注公众号「程猿薇茑」

    4.分布式配置中心 SpringCloud Config

    4.1.之前的配置文件用法存在什么问题?

    • 我们开发项目时,需要有很多的配置项需要写在配置文件中,如:数据库的连接信息等。
    • 这样看似没有问题,但是我们想一想,如果我们的项目已经启动运行,那么数据库服务器的ip地址发生了改变,我们该怎么办?
    • 如果真是这样,我们的应用需要重新修改配置文件,然后重新启动,如果应用数量庞大,那么这个维护成本就太大了!
    • 有没有好的办法解决呢?当然是有的,SpringCloud Config提供了这样的功能,可以让我们统一管理配置文件,以及实时同步更新,并不需要重新启动应用程序。

    4.2.SpringCloud Config 简介

    在这里插入图片描述

    Config Server是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环境下的配置,默认使用Git存储配置文件内容,也可以使用SVN存储,或者是本地文件存储。
    Config Client是Config Server的客户端,用于操作存储在Config Server中的配置内容。微服务在启动时会请求Config Server获取配置文件的内容,请求到后再启动容器。
    使用SpringCloud Config的架构:
    在这里插入图片描述

    4.3.快速入门 – 搭建Config Server

    4.3.1.准备3个配置文件,推送到Git服务器

    准备3个文件(也可以使用yml文件):

    microservice-dev.properties
    microservice-prod.properties
    microservice-test.properties
    

    该文件的命名规则是:{application}-{profile}.properties
    其内容是(另外2个文件内容稍有不同即可):

    jdbc.driverClassName=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    jdbc.username=root
    jdbc.password=123456
    

    推送文件到git服务器,这里使用的是码云,当然也可以使用github或者使用svn。使用码云创建一个项目(私有项目需要账号密码)
    在这里插入图片描述

    4.3.2.创建工程microservice-config-server

    pom依赖:

    <?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.zpc.microservice</groupId>
        <artifactId>microservice-config-server</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
    
        <dependencyManagement>
            <dependencies>
                <!-- 导入SpringCloud的依赖管理 -->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Finchley.SR1</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        <dependencies>
            <!--整合配置中心-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config-server</artifactId>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    4.3.3.编写入口ConfigApplication

    package com.zpc.configcenter;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.config.server.EnableConfigServer;
    /**
     * 开启配置服务
     */
    @EnableConfigServer
    @SpringBootApplication
    public class ConfigApplication {
        public static void main(String[] args) {
            SpringApplication.run(ConfigApplication.class, args);
        }
    }
    

    4.3.4.编写application.yml

    server:
      port: 7788 #服务端口
    spring:
      application:
        name:  microservice-config-server #指定服务名
      cloud:
        config:
          server:
            git: #配置git仓库地址
              uri: https://gitee.com/hellpzpc/myspconfig.git
              search-paths:
              - myspringcloudconfig		#配置文件目录地址
              username: 3747xxxx@qq.com	#码云账号(公有项目不需要设置)
              password: m86xxx 	#码云密码(公有项目不需要设置)
          label: master	#分支名称
    

    4.3.5.启动测试

    在这里插入图片描述
    测试已经看到了配置文件的内容。

    请求配置文件的规则如下:

    /{application}/{profile}/[label]
    /{application}-{profile}.yml
    /{label}/{application}-{profile}.yml
    /{application}-{profile}.properties
    /{label}/{application}-{profile}.properties
    

    其中{label}是指分支,默认是master。

    4.4.快速入门 – 搭建Config Client

    我们在microservice-item项目中添加Config Client的支持。来读取JDBC的配置文件的内容。

    4.4.1.导入依赖
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-client</artifactId>
    </dependency>
    
    4.4.2.新增配置文件bootstrap.yml
    spring:
      cloud:
        config:
          uri: http://127.0.0.1:7788/  #配置中心的地址
          profile: dev #对应配置服务中的{profile}
          label: master #对应的分支
    
    4.4.3.编写JdbcConfigBean

    编写对象通过@Value注解读取Config Server中的值。

    package com.zpc.item.config;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    @Component
    public class JdbcConfigBean {
        @Value("${jdbc.url}")
        private String url;
        @Value("${jdbc.username}")
        private String username;
        @Value("${jdbc.password}")
        private String password;
        @Value("${jdbc.driverClassName}")
        private String driverClassName;
        public String getUrl() {
            return url;
        }
        public void setUrl(String url) {
            this.url = url;
        }
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        public String getDriverClassName() {
            return driverClassName;
        }
        public void setDriverClassName(String driverClassName) {
            this.driverClassName = driverClassName;
        }
        @Override
        public String toString() {
            return "JdbcConfigBean [url=" + url + ", username=" + username
                    + ", password=" + password + ", driverClassName="
                    + driverClassName + "]";
        }
    }
    
    4.4.4.编写测试方法进行测试

    在ItemController中增加testconfig方法:

    package com.zpc.item.controller;
    import com.zpc.item.config.JdbcConfigBean;
    import com.zpc.item.entity.Item;
    import com.zpc.item.service.ItemService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    /**
     * @author Evan
     */
    @RestController
    public class ItemController {
        @Value("${server.port}")
        private String port;
        @Autowired
        private ItemService itemService;
        @Autowired
        private JdbcConfigBean jdbcConfigBean;
        /**
         * 对外提供接口服务,查询商品信息
         *
         * @param id
         * @return
         */
        @GetMapping(value = "item/{id}")
        public Item queryItemById(@PathVariable("id") Long id) {
            System.out.println("service port:"+port);
            return this.itemService.queryItemById(id);
        }
    
        @GetMapping(value = "testconfig")
        public String testconfig(){
            return this.jdbcConfigBean.toString();
        }
    }
    
    4.4.5.启动测试

    在这里插入图片描述
    测试结果显示,已经从Config Server中获取到配置文件的内容。

    4.5.手动更新运行中的配置文件

    如果git服务器中的配置文件更新了怎么办?正在运行的应用中的配置内容如何更新?

    4.5.1.测试

    现在我们更改git服务器中的配置文件内容,端口改成8888:
    在这里插入图片描述
    然后刷新Config Server地址观察:
    在这里插入图片描述
    可以看到这里查询到的是新的数据,端口已经更新为8888。
    但是在Config Client中测试:
    在这里插入图片描述
    看到依然是旧的数据。
    如何才能在重启应用的情况下,获取到最新的配置文件内容呢? – 为Config Client添加refresh支持。

    4.5.2.加入依赖

    使用actuator 监控中心完成刷新功能:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
    4.5.3.为JdbcConfigBean添加@RefreshScope注解

    需要为动态更新配置内容的bean添加@RefreshScope注解。

    @Component
    @RefreshScope
    public class JdbcConfigBean {...//代码省略}
    
    4.5.4.修改application.yml文件
    ###服务端口号(本身是一个web项目)
    server:
      port: 8080
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-item
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    #开启所有端点
    management:
      endpoints:
        web:
         exposure:
           include: "*"
    
    4.5.5.重启做测试

    加了actuator依赖后,启动项目控制台输出一行:

    2018-11-04 21:17:19.749  INFO 187328 --- [main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/refresh],methods=[POST],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
    

    为了测试,需要手动发起post请求actuator接口:
    POST请求监控中心 http://localhost:8080/actuator/refresh
    使用接口测试工具(例如postman)发送post请求actuator/refresh来更新配置内容:
    在这里插入图片描述
    响应:
    在这里插入图片描述
    刷新可以看到有配置文件内容更新了。
    在这里插入图片描述
    可以看到应该更新了最新的配置文件内容,我们也就实现了在未重启项目的情况下实现了动态修改配置文件内容。

    但是,项目已经发布上线了,不可能人为的守在服务器前,发现有更新了就手动请求监控中心的refresh接口。
    是否自动完成呢?

    **欢迎关注技术号【程猿薇茑】**

    4.6.借助与git的webhook(web钩子)实现自动更新

    码云、github等git服务器提供了web hook功能,意思是,在仓库中的资源发生更新时会通知给谁,这里的谁是一个url地址。
    在这里插入图片描述
    可以在命令行查看服务器ip地址,把外部(例如码云)能访问到的你的服务器ip设置为推送地址。
    接下来进行测试,更新配置文件的内容。
    测试的结果会发现,配置文件内容会动态更到Bean中。
    (要使自己的服务能够被外部访问到需要做端口映射)
    总结下流程:
    在这里插入图片描述

    4.7.Config Client配置优化-注册到Eureka中

    4.7.1.分析

    在 microservice-item中作为Config Client,在配置文件中了写死了Config Server的地址:

    spring:
      cloud:
        config:
          name: microservice #对应配置中心的应用名称,不写默认是本应用名,即spring.application.name
          uri: http://127.0.0.1:7788/
          profile: dev #对应配置服务中的{profile}
          label: master #对应的分支
    

    这样的硬编码是不好的,如果配置中心的ip地址发生了改变,那么久需要重新修改并且重启应用了。
    想想,有什么好的办法解决呢? 如果将Config Server作为一个微服务,并且将其注册的Eureka中,是不是就可以不用硬编码了?

    4.7.2.在 microservice-config-server中添加Eureka的依赖
    <!--整合eureka客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
    4.7.3.修改application.yml文件
    server:
      port: 7788 #服务端口
    spring:
      application:
        name:  microservice-config-server #指定服务名
      cloud:
        config:
          server:
            git: #配置git仓库地址
              uri: https://gitee.com/hellpzpc/myspconfig.git
              search-paths:
              - myspringcloudconfig       #配置文件目录地址
              username: 3xxxx@qq.com   #码云账号(公有项目不需要设置)
              password: mx8xxx2  #码云密码(公有项目不需要设置)
          label: master    #分支
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    
    4.7.4.ConfigApplication添加@EnableEurekaClient注解
    /**
     * 开启配置服务
     */
    @EnableConfigServer
    @SpringBootApplication
    @EnableEurekaClient
    public class ConfigApplication {
        public static void main(String[] args) {
            SpringApplication.run(ConfigApplication.class, args);
        }
    }
    
    4.7.5.在microservice-item中修改bootstrap.yml配置
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
          instance-id: ${spring.application.name}###${server.port} #指定实例id  
    spring:
      cloud:
        config:
          name: microservice #对应配置中心的应用名称,默认是本应用名,即spring.application.name,该名称要和git中的配置一致
          #uri: http://127.0.0.1:7788/
          profile: dev #对应配置服务中的{profile}
          label: master #对应的分支
          discovery:
            enabled: true #启用发现服务功能
            service-id: microservice-config-server #指定配置中心工程的名称
    

    疑问:在application.yml中以及配置Eureka的信息,为什么在bootstrap.yml还需要配置?
    因为在Spring Boot中bootstrap.yml在application.yml之前加载,所以即使在application.yml中以及配置Eureka的信息,是使用不了的,所以需要在bootstrap.yml中配置Eureka的信息。

    4.7.6.测试

    端口改成9999:
    在这里插入图片描述
    发post请求手动刷新:
    在这里插入图片描述
    查看最新配置:
    在这里插入图片描述
    测试结果,一切正常。这就完美解决了硬编码的问题。

    4.8.动态网关

    传统方式将zuul路由规则配置在配置文件中,如果修改了路由规则,无疑需要重启服务器。可以将配置文件存在配置中心,利用SpringCloud Config实现动态路由规则添加。
    1.在git服务器上创建配置文件service-zuul-dev.yml,注释掉网关工程yml文件中的zuul配置,把配置拷到service-zuul-dev.yml中。
    在这里插入图片描述
    注释掉网关工程microservice-api-gateway中的配置,并开启actuator:
    在这里插入图片描述
    2.在网关工程的pom中引入actuator依赖:

    <!--开启监控中心-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    

    3.在网关工程新建bootstrap.yml,启用springcloud config 功能:

    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    spring:
      cloud:
        config:
          name: service-zuul #对应配置中心的应用名称,默认是本应用名,即spring.application.name,该名称要和git中的配置一致
          #uri: http://127.0.0.1:7788/
          profile: dev #对应配置服务中的{profile}
          label: master #对应的分支
          discovery:
            enabled: true #启用发现服务功能
            service-id: microservice-config-server #指定配置中心工程的名称
    

    4.在网关工程启动类增加@RefreshScope注解,支持手动刷新

    package com.zpc.gateway.runner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
    import org.springframework.context.annotation.ComponentScan;
    
    @EnableZuulProxy
    @SpringBootApplication
    @ComponentScan(basePackages = "com.zpc.gateway.filter")
    public class ApiGatewayApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ApiGatewayApplication.class, args);
        }
    
        @RefreshScope
        @ConfigurationProperties("zuul")
        public ZuulProperties zuulProperties(){
            ZuulProperties properties = new ZuulProperties();
            System.out.println("properties:"+properties);
            return properties ;
        }
    }
    

    5.启动配置中心(microservice-config-server)工程,查看能否访问git上的配置文件:
    在这里插入图片描述

    6.启动网关工程(microservice-api-gateway)、订单工程(microservice-order)、商品工程(microservice-item)测试。
    Eureka:
    在这里插入图片描述
    通过Zuul网关访问订单的服务OK:
    在这里插入图片描述
    通过Zuul网关访问商品的服务OK:
    在这里插入图片描述

    7.修改git中的配置文件,手动发post请求刷新,看修改是否生效
    修改了serviceid为不存在的服务app-itemXX:
    在这里插入图片描述
    刷新:
    在这里插入图片描述

    再次通过zuul访问item的服务:
    在这里插入图片描述
    8.把配置文件改回来,再发post请求http://localhost:8087/actuator/refresh,再次测试:
    在这里插入图片描述
    OK了,说明通过配置中心动态设置zuul配置生效:
    在这里插入图片描述

    5.消息总线 SpringCloud Bus

    5.1.分析

    • 虽然通过Git服务器的web hook可以实现自动更新,但是,如果Config Client有很多的话,那么需要在web hook中维护很多地址,如果是手动刷新的话还得一个一个地发送POST请求,这显然是不现实的做法。

    • 有没有更好的方案呢? 我们非常容易想到消息队列中的Topic,没错! 通过消息实现通知。

    5.2.SpringCloud Bus消息总线的简介

    在这里插入图片描述
    目前SpringCloud Bus消息总线只是实现了对RabbitMQ以及Kafka的支持。

    5.3.RabbitMQ的安装

    参考《RabbitMQ教程》 https://blog.csdn.net/hellozpc/article/details/81436980

    5.4.使用SpringCloud Bus的架构

    在这里插入图片描述

    5.5.实现

    5.5.1.在 microservice-item添加依赖
    <!--整合SpringCloud Bus-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    
    5.5.2.在bootstrap.yml添加rabbitmq的配置
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    
    spring:
      cloud:
        config:
          name: microservice #对应配置中心的应用名称,默认是本应用名,即spring.application.name,该名称要和git中的配置一致
          #uri: http://127.0.0.1:7788/
          profile: dev #对应配置服务中的{profile}
          label: master #对应的分支
          discovery:
            enabled: true #启用发现服务功能
            service-id: microservice-config-server #指定配置中心工程的名称(在Eureka中的服务名)
      rabbitmq: #RabbitMQ相关的配置
        host: 127.0.0.1
        port: 5672
        username: guest
        password: guest
    
    5.5.3.修改web hook的地址

    在启动Item工程时会看到这样的日志:

    2018-11-12 21:43:47.722  INFO 221032 --- [main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/bus-refresh],methods=[POST]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
    

    说明刷新的地址/bus-refresh是由SpringCloud Bus来处理,之前的/refresh依然是由以前的逻辑处理。
    所以要修改Git服务器中的web hook的地址:(修改或者添加都可以)
    在这里插入图片描述

    5.5.4.启动测试

    首先需要保证Rabbit的服务开启:
    在这里插入图片描述
    启动rabbit管理工具:
    在这里插入图片描述
    查看是否启动成功:
    在这里插入图片描述
    启动Item工程后查看RabbitMQ中的交换机:
    在这里插入图片描述
    再看队列:
    在这里插入图片描述
    在这里插入图片描述

    接着,将 microservice-item的端口改成8082,再启动一个 microservice-item实例,进行测试。

    在这里插入图片描述
    发现,有2个队列,分别都绑定到springCloudBus的交换机。

    接下里,修改配置文件的内容进行测试。
    在这里插入图片描述
    在这里插入图片描述
    可以看到8081和8082这2个实例查询到的信息都是一样的。
    接下来,修改配置文件内容将9999改成8888:

    在这里插入图片描述
    在这里插入图片描述
    结果显示,都是获取到最新的数据。
    在测试时,会发现,由于Git服务器的web钩子推送到8181,所以8081的更新快一些,而8082更新就相对慢一些。

    5.5.5.流程总结

    更新文件到git服务器,Git服务器通过web钩子通知到8081的/bus/refresh,8081的实例将消息发送到springCloudBus的交换机,由于8081的队列页绑定到交换机,所以8082也获取到了更新的通知,然后去Config Server获取最新的数据。

    5.6.架构优化

    在前面实现的架构中,发现8081这个实例不仅仅是提供了商品查询的服务,还负责发送更新的消息到RabbitMQ。
    这其实是违反了微服务架构中的职责单一的原则。
    其实这个架构是可以改进的,就是将原有的Config Server不仅仅提供配置查询的服务,而且还要负责更新消息的发送。
    在这里插入图片描述

    5.6.1.在microservice-config-server中导入依赖
    <!--整合SpringCloud Bus-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    
    5.6.2.修改application.yml配置文件
    server:
      port: 7788 #服务端口
    spring:
      application:
        name:  microservice-config-server #指定服务名
      cloud:
        config:
          server:
            git: #配置git仓库地址
              uri: https://gitee.com/hellpzpc/myspconfig.git
              search-paths:
              - myspringcloudconfig       #配置文件目录地址
              username: 3747xxxx@qq.com   #码云账号(公有项目不需要设置)
              password: myxxxx  #码云密码(公有项目不需要设置)
          label: master    #分支
      rabbitmq: #RabbitMQ相关的配置
        host: 127.0.0.1
        port: 5672
        username: guest
        password: guest
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    
    5.6.3.修改Git中的web钩子

    在这里插入图片描述

    5.6.4.重启测试

    测试结果,和之前一样,可以同步更新到8081和8082。

    6.整合consul&zookeeper作为注册中心

    由于Eureka 2.0已结宣称闭源,因此可以考试使用其它注册中心组件,例如zk、consul。

    6.1.整合zookeeper作为注册中心

    6.1.1.修改商品(Item)微服务项目

    第一步 添加依赖(注意zk和Eureka不能共存,请先注释掉Eureka的pom依赖)

    <!--springboot 整合zookeeper客户端(注意保证服务端和客户端zk版本一致)-->
    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    </dependency>
    

    第二步 修改yml配置文件,添加zk作为注册中心(注意zk和Eureka不能共存,请先注释掉Eureka的配置)

    ###服务端口号(本身是一个web项目)
    server:
      port: 8080
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-item
    ###服务注册到zookeeper注册中心的地址
        cloud:
            zookeeper:
              connect-string: 127.0.0.1:2181
    

    第三步 修改入口程序
    @EnableEurekaClient
    改为:
    @EnableDiscoveryClient

    6.1.2.修改订单(order)微服务项目

    第一步 添加依赖(注意zk和Eureka不能共存,请先注释掉Eureka的pom依赖)

    <!--springboot 整合zookeeper客户端(注意保证服务端和客户端zk版本一致)-->
    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    </dependency>
    

    第二步 修改yml配置文件,添加zk作为注册中心(注意zk和Eureka不能共存,请先注释掉Eureka的配置)

    ###服务端口号(本身是一个web项目)
    server:
      port: 8080
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-order
    ###服务注册到zookeeper注册中心的地址
        cloud:
            zookeeper:
              connect-string: 127.0.0.1:2181
    

    第三步 修改入口程序
    @EnableEurekaClient
    改为:
    @EnableDiscoveryClient

    6.1.3.启动zookeeper、商品工程、订单工程

    运行zookeeper目录\bin\zkServer.cmd
    注意项目中引入的zookeeper jar包版本要和你本地运行的zk服务器一致!!否则拒绝连接!笔者使用的是zookeeper-3.5.3-beta
    在这里插入图片描述
    通过ZooInspector查看注册的服务(两个临时节点):
    在这里插入图片描述

    6.1.4. 测试功能

    在这里插入图片描述

    切换到zk注册中心一切OK!

    6.2.整合consul作为注册中心

    6.2.1.Consul环境搭建

    1)下载
    https://www.consul.io/downloads.html
    2)启动
    进入到consul目录,命令行运行:
    consul agent -dev -ui -node=test
    -dev开发服务器模式启动,-node设置节点名称为test,-ui可以用界面访问(默认就能访问)
    3)访问
    http://localhost:8500
    在这里插入图片描述

    6.2.2.修改商品(Item)微服务项目

    第一步 引入依赖 (务必注释掉其他注册中心的pom依赖)

    <!--springboot 整合consul客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    

    第二步 修改yml配置文件(务必注释掉其他注册中心的配置项)

    ###服务端口号(本身是一个web项目)
    server:
      port: 8081
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-item
    ###服务注册到consul注册中心的地址
    ###spring.cloud.consul.host/port配置的是Consul注册中心(Consul服务器)的地址和端口(Server节点和Client节点都可以),**SpringCloud** Consul调用 Consul HTTP REST 接口,进行服务注册
    ###spring.cloud.consul.discovery.hostname配置 Spring Boot 服务的主机地址,也可以不进行配置,默认本机地址。
    ###healthCheckPath: /health 指定健康检查的url地址(保证该接口地址返回http 2xx 返回码,当然也可以忽略健康检查register-health-check: false)
        cloud:
           consul:
              host: 127.0.0.1
              port: 8500
              discovery:
                healthCheckPath: /health
                healthCheckInterval: 5s
    

    第三步 ItemController增加健康检查接口(即一个正常返回http 200的接口,如果consul健康检查失败,则服务不可调用)

    /**
     * 健康检查
     * @return
     */
    @RequestMapping("/health")
    public String healthCheck() {
        return "OK";
    }
    

    第四步 入口程序中@EnableEurekaClient换成@EnableDiscoveryClient

    6.2.3.修改订单(order)微服务项目

    第一步 引入依赖 (务必注释掉其他注册中心的pom依赖)

    <!--springboot 整合consul客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    

    第二步 修改yml配置文件(务必注释掉其他注册中心的配置项)

    ###服务端口号(本身是一个web项目)
    server:
      port: 8082
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-order
    ###服务注册到consul注册中心的地址
    ###spring.cloud.consul.host/port配置的是Consul注册中心(Consul服务器)的地址和端口(Server节点和Client节点都可以),**SpringCloud** Consul调用 Consul HTTP REST 接口,进行服务注册
    ###spring.cloud.consul.discovery.hostname配置 Spring Boot 服务的主机地址,也可以不进行配置,默认本机地址。
    ###healthCheckPath: /health 指定健康检查的url地址(保证该接口地址返回http 2xx 返回码,当然也可以忽略健康检查register-health-check: false)
        cloud:
           consul:
              host: 127.0.0.1
              port: 8500
              discovery:
                healthCheckPath: /health
                healthCheckInterval: 5s
    

    第三步 OrderController增加健康检查接口(即一个正常返回http 200的接口,如果consul健康检查失败,则服务不可调用)

    /**
     * 健康检查
     * @return
     */
    @RequestMapping("/health")
    public String healthCheck() {
        return "OK";
    }
    

    第四步 入口程序中@EnableEurekaClient换成@EnableDiscoveryClient

    6.2.4.启动consul、商品工程、订单工程

    在这里插入图片描述
    保证健康检查全部OK,否则服务调不通!
    在这里插入图片描述

    6.2.5.测试功能

    在这里插入图片描述
    切换到consul注册中心一切OK!

    7.整合swagger API管理

    Swagger是一款能够让你更好地书写API文档的框架。它可以生成一个具有互动性的API控制台,方便前后端开发人员面向接口交流、接口测试。

    7.1.快速入门

    在商品系统(microservice-item)中做演示
    1.引入依赖

    <!--整合swagger -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.8.0</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.8.0</version>
    </dependency>
    

    2.配置Swagger

    package com.zpc.item.config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    @Configuration
    @EnableSwagger2//开启swagger2
    public class SwaggerConfig {
        @Bean
        public Docket createRestApi() {
            //配置swagger 生成API的扫描范围
            return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.zpc.item")).paths(PathSelectors.any()).build();
        }
        /**
         * 创建api文档信息
         * @return
         */
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder().title("鸟鹏的专栏").description("SpringCloud教程").termsOfServiceUrl("https://blog.csdn.net/hellozpc").version("1.0").build();
        }
    }
    

    3.编写Controller入口

    package com.zpc.item.controller;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.math.BigDecimal;
    @Api("SwaggerDemo控制器")
    @RestController
    public class SwaggerController {
    
        @ApiOperation("Swagger演示")
        @GetMapping("/swaggerIndex")
        public String swaggerIndex(String msg) {
            return "This is swaggerIndex!" + msg;
        }
    
        @ApiOperation("获取商品详情")
        @ApiImplicitParam(name = "itemName", value = "商品名称", required = true, dataType = "String")
        @PostMapping("/getItemInfo")
        public String getItemInfo(String itemName) {
            return "商品名:" + itemName + " 商品价格:" + new BigDecimal(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP);
        }
    }
    

    4.启动工程,访问http://localhost:8081/swagger-ui.html
    在这里插入图片描述
    在这个页面可以执行交互式的测试:
    在这里插入图片描述
    在这里插入图片描述

    7.2.Zuul整合Swagger统一管理微服务Api

    在一个入口统一管理所有微服务(订单服务、商品服务等)项目的API。实现方式:zuul
    1.在订单服务(microservice-order)中也引入swagger
    这次直接使用springboot提供的starter,不使用7.1那种方式。

    <!--引入swagger-->
    <dependency>
        <groupId>com.spring4all</groupId>
        <artifactId>swagger-spring-boot-starter</artifactId>
        <version>1.7.0.RELEASE</version>
    </dependency>
    

    创建Controller入口

    package com.zpc.order.controller;
    import com.spring4all.swagger.EnableSwagger2Doc;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.math.BigDecimal;
    @EnableSwagger2Doc
    @Api("订单服务接口")
    @RestController
    public class OrderApiController {
        @ApiOperation("提交订单")
        @ApiImplicitParam(name = "orderNo", value = "订单号", required = true, dataType = "String")
        @PostMapping("/putOrderInfo")
        public String putOrderInfo(String orderNo) {
            return "订单:" + orderNo + " 已提交,商品总价:" + new BigDecimal(Math.random() * 1000).setScale(2, BigDecimal.ROUND_HALF_UP);
        }
    
        @ApiOperation("获取订单详情")
        @ApiImplicitParam(name = "orderNo", value = "订单号", required = true, dataType = "String")
        @GetMapping("/getOrderInfo")
        public String getOrderInfo(String orderNo) {
            return "订单:" + orderNo + " 商品总价格:" + new BigDecimal(Math.random() * 1000).setScale(2, BigDecimal.ROUND_HALF_UP);
        }
    }
    

    yml中配置扫描范围

    #配置Swagger接口扫描范围
    swagger:
      base-package: com.zpc.order
    

    2.在网关服务中也引入Swagger

    <!--引入swagger-->
    <dependency>
        <groupId>com.spring4all</groupId>
        <artifactId>swagger-spring-boot-starter</artifactId>
        <version>1.7.0.RELEASE</version>
    </dependency>
    

    3.修改网关服务的启动类
    增加一个内部类DocumentationConfig

    package com.zpc.gateway.runner;
    import com.spring4all.swagger.EnableSwagger2Doc;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Primary;
    import org.springframework.stereotype.Component;
    import springfox.documentation.swagger.web.SwaggerResource;
    import springfox.documentation.swagger.web.SwaggerResourcesProvider;
    import java.util.ArrayList;
    import java.util.List;
    
    @EnableZuulProxy
    @EnableSwagger2Doc
    @SpringBootApplication
    @ComponentScan(basePackages = "com.zpc.gateway.filter")
    public class ApiGatewayApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ApiGatewayApplication.class, args);
        }
    
        @RefreshScope
        @ConfigurationProperties("zuul")
        public ZuulProperties zuulProperties(){
            ZuulProperties properties = new ZuulProperties();
            System.out.println("properties:"+properties);
            return properties ;
        }
    
        /**
         * 配置Swagger
         */
        @Component
        @Primary
        class DocumentationConfig implements SwaggerResourcesProvider{
            @Override
            public List<SwaggerResource> get() {
                List resource=new ArrayList<>();
                //name可以随便写,location前缀要与zuul配置的path一致。zuul开了token验证,要加上token,否则不用加?token=1
                resource.add(swaggerResource("myapp-item","/item-service/v2/api-docs?token=1","2.0"));
                resource.add(swaggerResource("myapp-order","/order-service/v2/api-docs?token=1","2.0"));
                return resource;
            }
    
    	 //name可以随便写,location前缀要与zuul配置的path一致
            private SwaggerResource swaggerResource(String name,String location,String version){
                SwaggerResource swaggerResource=new SwaggerResource();
                swaggerResource.setName(name);
                swaggerResource.setLocation(location);
                swaggerResource.setSwaggerVersion(version);
                return swaggerResource;
            }
        }
    }
    

    4.测试
    启动Eureka注册中心、item服务、order服务、网关服务开始测试
    直接通过网关的入口地址http://localhost:8087/swagger-ui.html?token=1统一访问所有微服务的api,界面右上角可以选择。
    在这里插入图片描述
    在这里插入图片描述

    **欢迎关注公众号**
    **微信扫一扫**
    展开全文
  • SpringCloud 详细教程

    2020-11-11 10:23:01
    SpringCloud 详细教程代码下载地址:https://download.csdn.net/download/zpcandzhj/10762209 1.SpringCloud教程目录 开发环境说明 细说微服务架构 SpringCloud 概论 SpringCloud 快速入门 Eureka 服务注册中心 使用...

    SpringCloud 详细教程代码下载地址:https://download.csdn.net/download/zpcandzhj/10762209

    1.SpringCloud教程目录

    开发环境说明

    • 细说微服务架构
    • SpringCloud 概论
    • SpringCloud 快速入门
    • Eureka 服务注册中心
    • 使用Spring Cloud Ribbon 实现负载均衡
    • 使用Spring Cloud Hystrix 实现容错
    • Feign声明式客户端(REST调用)
    • 服务网关 SpringCloud Zuul
    • 分布式配置中心SpringCloud Config
    • 消息总线 SpringCloud Bus
    • 整合consul、zookeeper作为注册中心
    • 整合swagger 统一管理API

    2.开发环境说明

    • JDK: 1.8
    • IDE: IntelliJ IDEA
    • Maven:3.3.9
    • OS: Windows 10 10.0
    • Springboot版本:2.0+

    3.细说微服务架构

    • 目前微服务是非常火的架构或者说概念,也是在构建大型互联网项目时采用的架构方式。

    3.1.单体架构

     	-  单体架构,是指将开发好的项目打成war包,然后发布到tomcat等容器中的应用。
    
     	- 假设你正准备开发一款与Uber和滴滴竞争的出租车调度软件,经过初步会议和需求分析,你可能会手动或者使用基于SpringBoot、Play或者Maven的生成器开始这个新项目,它的六边形架构是模块化的 ,架构图如下:
     
    	- 应用核心是业务逻辑,由定义服务、领域对象和事件的模块完成。围绕着核心的是与外界打交道的适配器。适配器包括数据库访问组件、生产和处理消息的消息组件,以及提供API或者UI访问支持的web模块等。
    
        - 尽管也是模块化逻辑,但是最终它还是会打包并部署为单体式应用。具体的格式依赖于应用语言和框架。例如,许多Java应用会被打包为WAR格式,部署在Tomcat或者Jetty上,而另外一些Java应用会被打包成自包含的JAR格式,类似的,Rails和Node.js会被打包成层级目录。
    
        - 这种应用开发风格很常见,因为IDE和其它工具都擅长开发一个简单应用,这类应用也很易于调试,只需要简单运行此应用,用Selenium链接UI就可以完成端到端测试。单体式应用也易于部署,只需要把打包应用拷贝到服务器端,通过在负载均衡器后端运行多个拷贝就可以轻松实现应用扩展。在早期这类应用运行的很好。
    

    在这里插入图片描述

    3.2.单体架构存在的问题

    请添加图片描述
    在这里插入图片描述

    如何解决以上问题呢? – 使用微服务架构。使得应用由重变轻。
    

    3.3.什么是微服务?

    请添加图片描述

    作者:Martin Fowler

    在这里插入图片描述
    在这里插入图片描述

    3.4.微服务架构的特征

    在这里插入图片描述

    3.5.微服务架构示例

    在这里插入图片描述

    每一个应用功能区都使用微服务完成。
    

    4.SpringCloud概论

    4.1.简介

    SpringCloud项目的官方网址:http://projects.spring.io/spring-cloud/

    在这里插入图片描述
    在这里插入图片描述

    4.2.SpringCloud子项目

    在这里插入图片描述

    4.3.Springcloud版本说明

    在这里插入图片描述

    官方版本:
    请添加图片描述
    在这里插入图片描述

    可见,目前Finchley.SR2版本是最新的稳定版,所以我们学习的过程中,就是使用的这个版本。

    在这里插入图片描述

    4.4.SpringCloud框架特点

    在这里插入图片描述

    5.使用 SpringBoot 实现微服务

    在正式学习SpringCloud 之前我们先使用SpringBoot实现一个微服务。

    业务非常简单:
    1、商品微服务:通过商品id查询商品的服务;
    2、订单微服务:创建订单时通时,通过调用商品的微服务进行查询商品数据;

    图示:在这里插入图片描述

    说明:
    1、对于商品微服务而言,商品微服务是服务的提供者,订单微服务是服务的消费者;
    2、对于订单微服务而言,订单微服务是服务的提供者,人是服务的消费者。

    5.1.实现商品微服务

    5.1.1.创建maven工程

    在这里插入图片描述
    在这里插入图片描述

    5.1.2.导入依赖

    重点是导入SpringBoot的依赖:

    <?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.zpc.microservice</groupId>
        <artifactId>microservice-item</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <finalName>${project.artifactId}</finalName>
            <plugins>
                <!-- 资源文件拷贝插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <!-- java编译插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        </project>
    

    5.1.3.创建实体Item

    package com.zpc.item.entity;
    
    public class Item {
    
        private Long id;
    
        private String title;
    
        private String pic;
    
        private String desc;
    
        private Long price;
    
        public Item(){}
    
        public Item(long id, String title, String pic, String desc, Long price) {
            this.id=id;
            this.title=title;
            this.pic=pic;
            this.desc=desc;
            this.price=price;
        }
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getPic() {
            return pic;
        }
    
        public void setPic(String pic) {
            this.pic = pic;
        }
    
        public String getDesc() {
            return desc;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    
        public Long getPrice() {
            return price;
        }
    
        public void setPrice(Long price) {
            this.price = price;
        }
    
        @Override
        public String toString() {
            return "Item [id=" + id + ", title=" + title + ", pic=" + pic + ", desc=" + desc + ", price=" + price + "]";
        }
    
    }
    

    5.1.4.编写ItemService

    编写ItemService用于实现具体的商品查询逻辑,为了演示方便,我们并不真正的连接数据库,而是做模拟实现。

    package com.zpc.item.service;
    import com.zpc.item.entity.Item;
    import org.springframework.stereotype.Service;
    import java.util.HashMap;
    import java.util.Map;
    
    @Service
    public class ItemService {
    
        private static final Map<Long, Item> ITEM_MAP = new HashMap<Long, Item>();
    
        static {// 准备一些静态数据,模拟数据库
            ITEM_MAP.put(1L, new Item(1L, "商品1", "http://图片1", "商品描述1", 1000L));
            ITEM_MAP.put(2L, new Item(2L, "商品2", "http://图片2", "商品描述2", 2000L));
            ITEM_MAP.put(3L, new Item(3L, "商品3", "http://图片3", "商品描述3", 3000L));
            ITEM_MAP.put(4L, new Item(4L, "商品4", "http://图片4", "商品描述4", 4000L));
            ITEM_MAP.put(5L, new Item(5L, "商品5", "http://图片5", "商品描述5", 5000L));
            ITEM_MAP.put(6L, new Item(6L, "商品6", "http://图片6", "商品描述6", 6000L));
            ITEM_MAP.put(7L, new Item(7L, "商品7", "http://图片7", "商品描述7", 7000L));
            ITEM_MAP.put(8L, new Item(8L, "商品8", "http://图片8", "商品描述8", 8000L));
            ITEM_MAP.put(8L, new Item(9L, "商品9", "http://图片9", "商品描述9", 9000L));
            ITEM_MAP.put(8L, new Item(10L, "商品10", "http://图片10", "商品描述10", 10000L));
        }
    
        /**
         * 模拟实现商品查询
         *
         * @param id
         * @return
         */
        public Item queryItemById(Long id) {
            return ITEM_MAP.get(id);
        }
    
    }
    

    5.1.5.编写ItemController

    package com.zpc.item.controller;
    
    import com.zpc.item.entity.Item;
    import com.zpc.item.service.ItemService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class ItemController {
    
        @Autowired
        private ItemService itemService;
    
        /**
         * 对外提供接口服务,查询商品信息
         *
         * @param id
         * @return
         */
        @GetMapping(value = "item/{id}")
        public Item queryItemById(@PathVariable("id") Long id) {
            return this.itemService.queryItemById(id);
        }
    
    }
    

    @RestController注解的说明:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Controller
    @ResponseBody
    public @interface RestController {
    
       /**
        * The value may indicate a suggestion for a logical component name,
        * to be turned into a Spring bean in case of an autodetected component.
        * @return the suggested component name, if any (or empty String otherwise)
        * @since 4.0.1
        */
       @AliasFor(annotation = Controller.class)
       String value() default "";
    
    }
    

    从源码可以看出,这是一个组合注解,组合了@Controller@Response注解。相当于我们同时写了这2个注解。

    @GetMapping注解的说明:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @RequestMapping(method = RequestMethod.GET)
    public @interface GetMapping {...}
    

    @GetMapping注解是@RequestMapping(method = RequestMethod.GET)简写方式。其功能都是一样的。
    同理还有其它注解:

    在这里插入图片描述

    5.1.6.编写程序入口

    package com.zpc.item.runner;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    
    /**
     * @author Evan
     */
     
    @SpringBootApplication//申明这是一个Spring Boot项目
    @ComponentScan(basePackages = {"com.zpc.item.controller","com.zpc.item.service"})//手动指定bean组件扫描范围
    public class ItemApp {
        public static void main(String[] args) {
            SpringApplication.run(ItemApp.class, args);
        }
    }
    

    5.1.7.创建application.yml配置文件

    SpringBoot以及SpringCloud项目支持yml和properties格式的配置文件。

    yml格式是YAML(Yet Another Markup Language)编写的格式,YAML和properties格式的文件是可以相互转化的。如:

    server:
      port: 8081 #服务端口
    

    等价于properties文件的配置:
    server.port=8081

    配置文件的示例:

    server:
      port: 8081 #服务端口
    

    5.1.8.启动程序测试

    在这里插入图片描述

    可以看到已经通过微服务查询到数据。

    5.2.实现订单微服务

    5.2.1.创建工程

    在这里插入图片描述
    在这里插入图片描述

    5.2.2.导入依赖

    <?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.zpc.microservice</groupId>
        <artifactId>microservice-item</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <finalName>${project.artifactId}</finalName>
            <plugins>
                <!-- 资源文件拷贝插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <!-- java编译插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        </project>
    

    5.2.3.创建订单Order实体

    package com.zpc.order.entity;
    
    import java.util.Date;
    import java.util.List;
    
    public class Order {
    
        private String orderId;
    
        private Long userId;
    
        private Date createDate;
    
        private Date updateDate;
    
        private List<OrderDetail> orderDetails;
    
        public Order() {
    
        }
    
        public Order(String orderId, Long userId, Date createDate, Date updateDate) {
            this.orderId = orderId;
            this.userId = userId;
            this.createDate = createDate;
            this.updateDate = updateDate;
        }
    
        public String getOrderId() {
            return orderId;
        }
    
        public void setOrderId(String orderId) {
            this.orderId = orderId;
        }
    
        public Long getUserId() {
            return userId;
        }
    
        public void setUserId(Long userId) {
            this.userId = userId;
        }
    
        public Date getCreateDate() {
            return createDate;
        }
    
        public void setCreateDate(Date createDate) {
            this.createDate = createDate;
        }
    
        public Date getUpdateDate() {
            return updateDate;
        }
    
        public void setUpdateDate(Date updateDate) {
            this.updateDate = updateDate;
        }
    
    
        public List<OrderDetail> getOrderDetails() {
            return orderDetails;
        }
    
        public void setOrderDetails(List<OrderDetail> orderDetails) {
            this.orderDetails = orderDetails;
        }
    
        @Override
        public String toString() {
            return "Order [orderId=" + orderId + ", userId=" + userId
                    + ", createDate=" + createDate + ", updateDate=" + updateDate
                    + "]";
        }}
    

    5.2.4.创建订单详情OrderDetail实体

    订单与订单详情是一对多的关系。

    package com.zpc.order.entity;
    
    public class OrderDetail {
    
        private String orderId;
    
        private Item item = new Item();
    
        public OrderDetail() {
    
        }
    
        public OrderDetail(String orderId, Item item) {
            this.orderId = orderId;
            this.item = item;
        }
    
        public String getOrderId() {
            return orderId;
        }
    
        public void setOrderId(String orderId) {
            this.orderId = orderId;
        }
    
        public Item getItem() {
            return item;
        }
    
        public void setItem(Item item) {
            this.item = item;
        }
    
        @Override
        public String toString() {
            return "OrderDetail [orderId=" + orderId + ", item=" + item + "]";
        }
    
    }
    

    5.2.5.将商品微服务项目中的Item类拷贝到当前工程

    在这里插入图片描述

    5.2.6.编写OrderService

    该Service实现的根据订单Id查询订单的服务,为了方便测试,我们将构造数据实现,不采用查询数据库的方式。

    package com.zpc.order.service;
    
    import com.zpc.order.entity.Item;
    import com.zpc.order.entity.Order;
    import com.zpc.order.entity.OrderDetail;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.*;
    
    @Service
    public class OrderService {
    
        private static final Map<String, Order> ORDER_DATA = new HashMap<String, Order>();
    
        static {
            // 模拟数据库,构造测试数据
            Order order = new Order();
            order.setOrderId("201810300001");
            order.setCreateDate(new Date());
            order.setUpdateDate(order.getCreateDate());
            order.setUserId(1L);
            List<OrderDetail> orderDetails = new ArrayList<OrderDetail>();
    
            Item item = new Item();// 此处并没有商品的数据,只是保存了商品ID,需要调用商品微服务获取
            item.setId(1L);
            orderDetails.add(new OrderDetail(order.getOrderId(), item));
    
            item = new Item(); // 构造第二个商品数据
            item.setId(2L);
            orderDetails.add(new OrderDetail(order.getOrderId(), item));
    
            order.setOrderDetails(orderDetails);
    
            ORDER_DATA.put(order.getOrderId(), order);
        }
    
        @Autowired
        private ItemService itemService;
    
        /**
         * 根据订单id查询订单数据
         *
         * @param orderId
         * @return
         */
        public Order queryOrderById(String orderId) {
            Order order = ORDER_DATA.get(orderId);
            if (null == order) {
                return null;
            }
            List<OrderDetail> orderDetails = order.getOrderDetails();
            for (OrderDetail orderDetail : orderDetails) {
                // 通过商品微服务查询商品详细数据
                Item item = this.itemService.queryItemById(orderDetail.getItem()
                        .getId());
                if (null == item) {
                    continue;
                }
                orderDetail.setItem(item);
            }
    
            return order;
        }
    
    }
    

    5.2.7.实现ItemService

    package com.zpc.order.service;
    
    
    import com.zpc.order.entity.Item;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    @Service
    public class ItemService {
    
        // Spring框架对RESTful方式的http请求做了封装,来简化操作
        @Autowired
        private RestTemplate restTemplate;
    
        public Item queryItemById(Long id) {
            return this.restTemplate.getForObject("http://127.0.0.1:8081/item/"
                    + id, Item.class);
        }
    
    }
    

    5.2.8.编写OrderController

    package com.zpc.order.controller;
    
    import com.zpc.order.entity.Order;
    import com.zpc.order.service.OrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class OrderController {
        @Autowired
        private OrderService orderService;
    
        @GetMapping(value = "order/{orderId}")
        public Order queryOrderById(@PathVariable("orderId") String orderId) {
            return this.orderService.queryOrderById(orderId);
        }
    }
    

    5.2.9.编写程序入口

    package com.zpc.order.runner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * @author Evan
     */
    @SpringBootApplication//申明这是一个Spring Boot项目
    @ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service"})//手动指定bean扫描范围
    public class OrderApp {
        public static void main(String[] args) {
            SpringApplication.run(OrderApp.class, args);
        }
    
        /**
         * 向Spring容器中定义RestTemplate对象
         * @return
         */
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    

    5.2.10.编写application.yml配置文件

    server:
      port: 8082 #服务端口
    

    5.2.11.启动测试

    在这里插入图片描述

    测试结果可见,查询订单时,同时也将商品数据查询到。

    5.3.添加okHttp的支持

    okhttp是一个封装URL,比HttpClient更友好易用的工具。目前似乎okhttp更流行一些。

    官网:http://square.github.io/okhttp/
    在这里插入图片描述
    RestTemplate底层默认使用的jdk的标准实现,如果我们想让RestTemplate的底层使用okhttp,非常简单:
    1、添加okhttp依赖

    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>3.9.0</version>
    </dependency>
    

    2、设置requestFactory
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }
    

    测试:

    在这里插入图片描述
    结果(没有变化):

    在这里插入图片描述

    测试结果是一样的。

    5.4.解决订单系统中的url硬编码问题

    通过以上的测试我们发现,在订单系统中要调用商品微服务中的查询接口来获取数据,在订单微服务中将url硬编码到代码中,这样显然不好,因为,运行环境一旦发生变化这个url地址将不可用。

    如何解决呢?

    解决方案:将url地址写入到application.yml配置文件中。

    实现:
    修改application.yml文件:

    server:
      port: 8082 #服务端口
    myspcloud:
      item:
        url: http://127.0.0.1:8081/item/
    

    修改ItemService中的实现:

    @Service
    public class ItemService {
    
        // Spring框架对RESTful方式的http请求做了封装,来简化操作
        @Autowired
        private RestTemplate restTemplate;
    
        @Value("${myspcloud.item.url}")
        private String itemUrl;
    
        public Item queryItemById(Long id) {
            return this.restTemplate.getForObject(itemUrl
                    + id, Item.class);
        }
    
    }
    

    测试(debug可以看到效果):

    在这里插入图片描述

    5.5.继续优化解决硬编码的问题

    在SpringBoot中使用@ConfigurationProperties注解可以非常简单的将配置文件中的值映射成对象。
    第一步,创建ItemProperties类:

    package com.zpc.order.properties;
    
    public class ItemProperties {
        private String url;
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
    }
    
    

    第二步,创建OrderProperties类:

    package com.zpc.order.properties;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    /**
     * 以myspcloud开头的配置被匹配到
     * @author Evan
     */
    @Component
    @ConfigurationProperties(prefix="myspcloud")
    public class OrderProperties {
    
        private ItemProperties item = new ItemProperties();
    
        public ItemProperties getItem() {
            return item;
        }
    
        public void setItem(ItemProperties item) {
            this.item = item;
        }
    }
    
    

    第三步,在Itemservice中注入该对象:

    @Service
    public class ItemService {
    
        // Spring框架对RESTful方式的http请求做了封装,来简化操作
        @Autowired
        private RestTemplate restTemplate;
    
        //@Value("${myspcloud.item.url}")
        //private String itemUrl;
    
        @Autowired
        OrderProperties orderProperties;
    
        public Item queryItemById(Long id) {
            return this.restTemplate.getForObject(orderProperties.getItem().getUrl()
                    + id, Item.class);
        }
    
    

    第四步,启动类中增加bean扫描路径

    @ComponentScan(basePackages = {"com.zpc.order.controller","com.zpc.order.service","com.zpc.order.properties"})//
    手动指定bean扫描范围
    
    

    第五步,debug运行

    在这里插入图片描述

    可以看出,这种解决方案比第一种好很多,更加的方便的。

    思考:

    这样是否还存在问题?如果商品的微服务有多个怎么办?
    我们在订单微服务中使用了Item实体,直接采取从商品微服务拷贝代码的方式是否太生硬?

    6.SpringCloud快速入门

    6.1.分析硬编码的问题

    通过前面5.4、5.5的实现,我们视乎已经解决了url硬编码的问题,但是我们想想:
    1、如果商品微服务的ip地址发生了变更,订单微服务中的配置文件也需要跟着修改
    2、如果商品微服务有多个,那么在订单微服务中又该如何写地址?

    那应该怎么解决呢? – 通过服务注册、发现的机制来完成。

    6.2.微服务注册与发现

    原理示意图:
    在这里插入图片描述

    由上图可以看出:
    1、服务提供者将服务注册到注册中心
    2、服务消费者通过注册中心查找服务
    3、查找到服务后进行调用(这里就是无需硬编码url的解决方案)
    4、服务的消费者与服务注册中心保持心跳连接,一旦服务提供者的地址发生变更时,注册中心会通知服务消费者

    6.3.注册中心:Eureka

    SpringCloud提供了多种注册中心的支持,如:Eureka、consul、ZooKeeper等。Eureka已经闭源了。本教程第二篇也会介绍使用其它两种方式作为注册中心。

    在这里插入图片描述

    6.3.1.原理

    请添加图片描述
    请添加图片描述

    Eureka包含两个组件:Eureka ServerEureka Client

    Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。

    Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就别一个内置的、使用轮询(round-robin)负载算法的负载均衡器。

    在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。

    Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。

    6.3.2.编写Eureka Server

    第一步:创建Maven工程:
    在这里插入图片描述
    在这里插入图片描述

    第二步,导入依赖:
    这里需要导入SpringCloud的管理依赖。

    <?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.zpc.springcloud.eureka</groupId>
        <artifactId>demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>demo</name>
        <description>Demo project for Spring Boot</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
        <dependencyManagement>
            <dependencies>
                <!-- 导入SpringCloud的依赖管理 -->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Finchley.SR1</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        <dependencies>
        	<!--springboot 整合eureka服务端-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    
    

    第三步,编写程序启动类:

    package com.zpc.springcloud.eureka;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    /**
     * Eureka注册中心
     */
    @SpringBootApplication
    @EnableEurekaServer //申明这是一个Eureka服务
    public class AppEureka {
    
       public static void main(String[] args) {
          SpringApplication.run(AppEureka.class, args);
       }
    }
    
    

    第四步,编写application.yml配置文件:

    ###服务端口号
    server:
      port: 8100
    
    ###服务名称
    spring:
      application:
        name: app-eureka-center
    
    eureka:
      instance:
        #注册中心地址
        hostname: 127.0.0.1
    ###客户端调用地址
      client:
        serviceUrl:
          defaultZone: http://${eureka.instance.hostname}:8100/eureka/
    ###是否将自己注册到Eureka服务中,因为该应用本身就是注册中心,不需要再注册自己(集群的时候为true)
        register-with-eureka: false
    ###是否从Eureka中获取注册信息,因为自己为注册中心,不会在该应用中的检索服务信息
        fetch-registry: false
    
    

    第五步,启动程序做测试:
    在这里插入图片描述

    6.4.将商品微服务注册到Eureka

    接下来,我们需要将商品的微服务注册到Eureka服务中。

    第一步:修改pom文件,引入SpringCloud的管理依赖以及eureka服务依赖。

    <?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.zpc.microservice</groupId>
        <artifactId>microservice-item</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Finchley.SR1</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--springboot 整合eureka客户端-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <finalName>${project.artifactId}</finalName>
            <plugins>
                <!-- 资源文件拷贝插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <!-- java编译插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    

    第二步,修改application.yml配置文件:

    ###服务端口号(本身是一个web项目)
    server:
      port: 8081
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-item
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://127.0.0.1:8100/eureka
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
    
    

    第三步,修改启动类,增加@EnableEurekaClient 注解:

    package com.zpc.item.runner;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.context.annotation.ComponentScan;
    
    /**
     * @author Evan
     */
    //申明这是一个Spring Boot项目
    @SpringBootApplication
    @EnableEurekaClient
    @ComponentScan(basePackages = {"com.zpc.item.controller","com.zpc.item.service"})
    public class ItemApp {
        public static void main(String[] args) {
            SpringApplication.run(ItemApp.class, args);
        }
    
    }
    
    

    第四步,启动测试:

    在这里插入图片描述
    至此,我们已经将自己的微服务注册到Eureka server中了。

    6.5.订单系统从Eureka中发现商品服务

    之前我们在订单系统中是将商品微服务的地址进行了硬编码,现在,由于已经将商品服务注册到Eureka中,所以,只需要从Eureka中发现服务即可。

    第一步,在订单系统中添加依赖:

    <?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.zpc.microservice</groupId>
        <artifactId>microservice-order</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Finchley.SR1</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--springboot 整合eureka客户端-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.squareup.okhttp3</groupId>
                <artifactId>okhttp</artifactId>
                <version>3.9.0</version>
            </dependency>
    
        </dependencies>
    
        <build>
            <finalName>${project.artifactId}</finalName>
            <plugins>
                <!-- 资源文件拷贝插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <!-- java编译插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    

    第二步,修改application.yml配置文件:

    server:
      port: 8082 #服务端口
    myspcloud:
      item:
        url: http://127.0.0.1:8081/item/
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如订单服务)
    spring:
        application:
            name: app-order
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://127.0.0.1:8100/eureka
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
    
    

    第三步,修改ItemService的实现逻辑:

    package com.zpc.order.service;
    import com.zpc.order.entity.Item;
    import com.zpc.order.properties.OrderProperties;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    @Service
    public class ItemService {
    
        // Spring框架对RESTful方式的http请求做了封装,来简化操作
        @Autowired
        private RestTemplate restTemplate;
    
        @Autowired
        OrderProperties orderProperties;
    
        public Item queryItemById(Long id) {
    
            // 该方法走eureka注册中心调用(去注册中心根据app-item查找服务,这种方式必须先开启负载均衡@LoadBalanced)
            String itemUrl = "http://app-item/item/{id}";
            Item result = restTemplate.getForObject(itemUrl, Item.class, id);
            System.out.println("订单系统调用商品服务,result:" + result);
            return result;
        }
    }
    
    

    第四步,在启动类中添加@EnableEurekaClient注解 ,获取RestTemplate的方法上加 @LoadBalanced注解

    package com.zpc.order.runner;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * @author Evan
     */
    
    @SpringBootApplication//申明这是一个Spring Boot项目
    @EnableEurekaClient
    @ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties"})//手动指定bean扫描范围
    public class OrderApp {
        public static void main(String[] args) {
            SpringApplication.run(OrderApp.class, args);
        }
    
        /**
         * 向Spring容器中定义RestTemplate对象
         * @return
         */
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
        }
    
    }
    
    

    第五步,启动测试(此时有3个应用:Eureka注册中心、Item服务、order服务)

    在这里插入图片描述
    在注册中心http://localhost:8100看到有2个客户端:

    在这里插入图片描述

    7.深入理解Eureka

    7.1.为Eureka添加用户认证

    在前面的示例中,我们可以看到我们需要登录即可访问到Eureka服务,这样其实是不安全的。

    接下来,我们为Eureka添加用户认证。

    第一步,为Eureka服务端(eureka-server)添加安全认证依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
    

    第二步,增加application.yml配置文件:

    ###服务端口号
    server:
      port: 8100
    
    ###服务名称
    spring:
      application:
        name: app-eureka-center
      security:
        basic:
          enable: true #开启基于HTTP basic的认证
        user: #配置用户的账号信息
          name: zpc
          password: 123456
    
    eureka:
      instance:
        #注册中心地址
        hostname: 127.0.0.1
    
    ###客户端调用地址
      client:
        serviceUrl:
          defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:8100/eureka/
    ###是否将自己注册到Eureka服务中,因为该应用本身就是注册中心,不需要再注册自己(集群的时候为true)
        register-with-eureka: false
    ###是否从Eureka中获取注册信息,因为自己为注册中心,不会在该应用中的检索服务信息
        fetch-registry: true
    
    

    第三步,在eurka服务端添加一个安全认证类:

    package com.zpc.springcloud.eureka;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.http.SessionCreationPolicy;
    
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        /**
         * 高版本springcloud的丢弃了配置:
         *
         * security:
         *   basic:
         *    enabled: true
         *
         * 所以应该使用以下方式开启
         *
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // Configure HttpSecurity as needed (e.g. enable http basic).
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
            http.csrf().disable();
            //注意:为了可以使用 http://${user}:${password}@${host}:${port}/eureka/ 这种方式登录,所以必须是httpBasic,
            // 如果是form方式,不能使用url格式登录
            http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
        }
    }
    
    

    第四步,重新启动Eureka服务进行测试:

    在这里插入图片描述
    输入正确的用户名密码即可登录。

    这时,服务提供者注册到Eureka时会报错:

    2018-10-31 23:01:13.419  WARN 143840 --- [nfoReplicator-0] c.n.d.s.t.d.RetryableEurekaHttpClient    : Request execution failure with status code 401; retrying on another server if available
    2018-10-31 23:01:13.420  WARN 143840 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_APP-ORDER/Evan-Zhou:app-order:8082 - registration failed Cannot execute request on any known server
    
    com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
    
    

    所以,需要在服务注册时也需要设置用户名和密码。

    7.1.1.服务注册时(client端)设置账户信息

    服务注册到有认证需求的注册中心时,需要设置如下地址:

    http://USER:PASSWORD@127.0.0.1:端口号/eureka/
    配置如下(至此,Eureka注册中心、Item服务、order服务3个配置文件全部改成了带账号密码的请求地址):

    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka
    
    

    7.2.Eureka的自我保护模式

    在这里插入图片描述
    如图,当前Eureka进入了自我保护模式。(先开启Eureka server端和client端,然后再断开client端,此时刷新Eureka界面,就会看到红色字样)
    在这里插入图片描述
    在短时间内丢失了服务实例的心跳,不会剔除该服务,这是eurekaserver的自我保护机制的宗旨。主要是为了防止由于短暂的网络故障误删除可用的服务。

    所以,一般进入自我保护模式,无需处理。如果,需要禁用自我保护模式,只需要在配置文件中添加配置即可:
    (测试环境、开发环境可以关闭自我保护机制,保证服务不可用时及时剔除)
    配置方式:在server端 配置文件中添加server: 配置

    eureka:
      instance:
        #注册中心地址
        hostname: 127.0.0.1
    
    ###客户端调用地址
      client:
        serviceUrl:
          defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:8100/eureka/
        register-with-eureka: false
        fetch-registry: true
    
      server:
          enable-self-preservation: false #禁用自我保护模式
    
    

    重新启动服务查看效果:
    请添加图片描述
    提示,如果禁用自我保护模式,在网络通信故障下可能会出现问题,因为服务可能只是短暂的不可用。
    上述界面出现后,断开client端,此时刷新Eureka界面,就会看到红色字样,同时会把服务实例从注册中心剔除:No instances available

    在这里插入图片描述

    7.3.Eureka的高可用(Eureka集群)

    前面的测试,我们会发现,Eureka服务是一个单点服务,在生产环境就会出现单点故障,为了确保Eureka服务的高可用,我需要搭建Eureka服务的集群。

    搭建Eureka集群非常简单,只要启动多个Eureka Server服务并且让这些Server端之间彼此进行注册即可实现。

    第一步,修改eureka server端的application.yml文件:

    端口为8100的机器注册到端口为9100的注册中心

    ###服务端口号
    server:
      port: 8100
    
    ###服务名称
    spring:
      application:
        name: app-eureka-center
      security:
        basic:
          enable: true #开启基于HTTP basic的认证
        user: #配置用户的账号信息
          name: zpc
          password: 123456
    
    eureka:
      instance:
        #注册中心地址
        hostname: 127.0.0.1
    
    ###客户端调用地址
      client:
        serviceUrl:
          defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:9100/eureka/
    ###是否将自己注册到Eureka服务中,集群的时候为true
        register-with-eureka: true
        fetch-registry: true
    
      server:
          enable-self-preservation: false
    
    

    第二步,修改配置文件,再建一个Eureka server工程,启动两个工程(两个工程的name属性一样)测试:

    端口为9100的机器注册到端口为8100的注册中心

    ###服务端口号
    server:
     port: 9100
    
    ###服务名称
    spring:
     application:
       name: app-eureka-center
     security:
       basic:
         enable: true #开启基于HTTP basic的认证
       user: #配置用户的账号信息
         name: zpc
         password: 123456
    
    eureka:
     instance:
       #注册中心地址
       hostname: 127.0.0.1
    ###客户端调用地址
     client:
       serviceUrl:
         defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:8100/eureka/
    ###是否将自己注册到Eureka服务中,集群的时候为true
       register-with-eureka: true
       fetch-registry: true
    ###测试环境、开发环境可以关闭自我保护机制,保证服务不可用时及时剔除
     server:
       enable-self-preservation: false
       eviction-interval-timer-in-ms: 2000
    
    

    测试结果:

    在这里插入图片描述
    在这里插入图片描述
    可以看到,2个Eureka服务进行了彼此注册。

    7.4.将client端服务注册到Eureka高可用集群

    服务注册到Eureka集群时,可以指定多个,也可以指定一个Eureka服务(因为Eureka服务集群间彼此互联)。

    修改商品微服务项目microservice-item的application.yml配置文件,defaultZone 写上所有注册中心的地址:

    ###服务端口号(本身是一个web项目)
    server:
      port: 8081
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-item
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
    
    
    

    重启启动,测试:可以通过停止其中一个Eureka server服务进行测试,结果会发现集群是高可用。
    即:启动2个Eureka服务端(注册中心),1个商品服务(item),1个订单服务(order)。停掉其中一个Eureka服务端后,访问另外一个Eureka服务端页面可以看到item服务立马切换到此注册中心,订单服务照样可以访问商品服务,浏览器输入http://localhost:8082/order/201810300001还能获取到商品数据(因为商品服务同时注册到了2个注册中心)。

    为了防止是缓存的效果,OrderService 再加一条数据201810300002 ,用http://localhost:8082/order/201810300002测试高可用。

    package com.zpc.order.service;
    
    import com.zpc.order.entity.Item;
    import com.zpc.order.entity.Order;
    import com.zpc.order.entity.OrderDetail;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.*;
    
    @Service
    public class OrderService {
    
        private static final Map<String, Order> ORDER_DATA = new HashMap<String, Order>();
    
        static {
            // 模拟数据库,构造测试数据
            Order order1 = new Order();
            order1.setOrderId("201810300001");
            order1.setCreateDate(new Date());
            order1.setUpdateDate(order1.getCreateDate());
            order1.setUserId(1L);
            List<OrderDetail> orderDetails1 = new ArrayList<OrderDetail>();
    
            Item item = new Item();// 此处并没有商品的数据,只是保存了商品ID,需要调用商品微服务获取
            item.setId(1L);
            orderDetails1.add(new OrderDetail(order1.getOrderId(), item));
    
            item = new Item(); // 构造第二个商品数据
            item.setId(6L);
            orderDetails1.add(new OrderDetail(order1.getOrderId(), item));
            order1.setOrderDetails(orderDetails1);
            ORDER_DATA.put(order1.getOrderId(), order1);
    
            Order order2 = new Order();
            order2.setOrderId("201810300002");
            order2.setCreateDate(new Date());
            order2.setUpdateDate(order2.getCreateDate());
            order2.setUserId(3L);
            List<OrderDetail> orderDetails2 = new ArrayList<OrderDetail>();
    
            Item item2 = new Item();// 此处并没有商品的数据,只是保存了商品ID,需要调用商品微服务获取
            item2.setId(3L);
            orderDetails2.add(new OrderDetail(order2.getOrderId(), item2));
    
            item2 = new Item(); // 构造第二个商品数据
            item2.setId(5L);
            orderDetails2.add(new OrderDetail(order2.getOrderId(), item2));
            order2.setOrderDetails(orderDetails2);
            ORDER_DATA.put(order2.getOrderId(), order2);
        }
    
        @Autowired
        private ItemService itemService;
    
        /**
         * 根据订单id查询订单数据
         *
         * @param orderId
         * @return
         */
        public Order queryOrderById(String orderId) {
            Order order = ORDER_DATA.get(orderId);
            if (null == order) {
                return null;
            }
            List<OrderDetail> orderDetails = order.getOrderDetails();
            for (OrderDetail orderDetail : orderDetails) {
                // 通过商品微服务查询商品详细数据
                Item item = this.itemService.queryItemById(orderDetail.getItem()
                        .getId());
                if (null == item) {
                    continue;
                }
                orderDetail.setItem(item);
            }
    
            return order;
        }
    
    }
    
    

    7.5.指定服务的IP地址

    在服务的提供者配置文件中可以指定ip地址,如下:

    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
    
    

    在这里插入图片描述

    7.6.指定实例id

    通过instance-id 参数指定服务注册到Eureka中的服务实例id:

    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    
    

    在这里插入图片描述

    8.负载均衡:Ribbon

    首先,我们思考一个问题,如果为同一个的提供者在Eureka中注册了多个服务,那么客户端该如何选择服务呢?

    这时,就需要在客户端实现服务的负载均衡。

    在SpringCloud中推荐使用Ribbon来实现负载均衡。

    8.1.Ribbon简介

    在这里插入图片描述

    8.2.架构

    在这里插入图片描述

    8.3.开始使用SpringCloud Ribbon

    8.3.1.为microservice order增加ribbon依赖

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

    其实,该依赖是可以省略的,因为spring-cloud-starter-netflix-eureka-client中已经包含了spring-cloud-starter-netflix-ribbon:
    在这里插入图片描述

    8.3.2.为RestTemplate设置@LoadBalanced注解

    package com.zpc.order.runner;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * @author Evan
     */
    @SpringBootApplication//申明这是一个Spring Boot项目
    @EnableEurekaClient
    @ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties"})//手动指定bean扫描范围
    public class OrderApp {
        public static void main(String[] args) {
            SpringApplication.run(OrderApp.class, args);
        }
    
        /**
         * 向Spring容器中定义RestTemplate对象
         * @return
         */
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
        }
    
    }
    
    

    这样,RestTemplate就具备了负载均衡的功能。

    8.3.3.改造ItemService的实现

    之前的实现(其实本教程前面没有用这种方式):

    public Item queryItemById(Long id) {
        String serviceId = "app-item";
        List<ServiceInstance> instances = this.discoveryClient.getInstances(serviceId);
        if(instances.isEmpty()){
            return null;
        }
        // 为了演示,在这里只获取第一个实例
        ServiceInstance serviceInstance = instances.get(0);
        String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
        return this.restTemplate.getForObject("http://" + url + "/item/" + id, Item.class);
    }
    
    

    改造后的实现(其实本教程一直采用的是这种方式):

    public Item queryItemById(Long id) {
        // 该方法走eureka注册中心调用(这种方式必须先开启负载均衡@LoadBalanced)
        String itemUrl = "http://app-item/item/{id}";
        Item result = restTemplate.getForObject(itemUrl, Item.class, id);
        System.out.println("订单系统调用商品服务,result:" + result);
        return result;
    }
    
    

    可以发现,实现更加简化了。
    这种方式关键在于获取RestTemplat对象时要加上@LoadBalanced注解 ,否则restTemplate.getForObject方法会报java.net.UnknownHostException: app-item,而前面一种方式是手动指定了获取的服务实例,不需要加此注解。

    8.3.4.重启订单服务进行测试

    测试结果:
    在这里插入图片描述
    结果显示,可以正常获取到商品数据。

    内部原理(可以启动多个item实例看效果,比如端口分别设为8081,8091,在下面2个类里打断点):
    org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor
    请添加图片描述

    org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient

    在这里插入图片描述
    在执行请求前会经过org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor这个拦截器,并且在org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient中,通过serverId查找服务地址,通过一定的负载均衡策略去做真正的请求。

    8.3.5.测试负载均衡

    测试方法:
    第一步,启动2个microservice-item服务(多个也可以)
    在这里插入图片描述
    第二步,导入测试依赖,编写单元测试用例:

    <!-- 引入SpringBoot的单元测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    
    

    第三步,编写测试用例:

    package com.zpc.test;
    
    import com.zpc.order.runner.OrderApp;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
    import org.springframework.context.annotation.Import;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes = ItemServiceTest.class)
    @Import(OrderApp.class)
    public class ItemServiceTest {
    
        @Autowired
        private LoadBalancerClient loadBalancerClient;//自动注入
    
        @Test
        public void test() {
            String serviceId = "app-item";
            for (int i = 0; i < 100; i++) {
                ServiceInstance serviceInstance = this.loadBalancerClient.choose(serviceId);
                System.out.println("第" + (i + 1) + "次:" + serviceInstance.getHost() + ": " + serviceInstance.getPort());
            }
        }
    
    }
    
    

    测试结果:
    第1次:127.0.0.1: 8091
    第2次:127.0.0.1: 8081
    第3次:127.0.0.1: 8091
    第4次:127.0.0.1: 8081
    第5次:127.0.0.1: 8091
    第6次:127.0.0.1: 8081
    第7次:127.0.0.1: 8091
    第8次:127.0.0.1: 8081
    第9次:127.0.0.1: 8091
    第10次:127.0.0.1: 8081
    第11次:127.0.0.1: 8091
    第12次:127.0.0.1: 8081
    第13次:127.0.0.1: 8091
    第14次:127.0.0.1: 8081
    第15次:127.0.0.1: 8091
    第16次:127.0.0.1: 8081
    第17次:127.0.0.1: 8091
    第18次:127.0.0.1: 8081
    第19次:127.0.0.1: 8091
    第20次:127.0.0.1: 8081
    第21次:127.0.0.1: 8091
    第22次:127.0.0.1: 8081
    第23次:127.0.0.1: 8091
    第24次:127.0.0.1: 8081
    第25次:127.0.0.1: 8091
    第26次:127.0.0.1: 8081
    第27次:127.0.0.1: 8091
    第28次:127.0.0.1: 8081
    第29次:127.0.0.1: 8091
    第30次:127.0.0.1: 8081
    第31次:127.0.0.1: 8091
    第32次:127.0.0.1: 8081
    第33次:127.0.0.1: 8091
    第34次:127.0.0.1: 8081
    第35次:127.0.0.1: 8091
    第36次:127.0.0.1: 8081
    第37次:127.0.0.1: 8091
    第38次:127.0.0.1: 8081
    第39次:127.0.0.1: 8091
    第40次:127.0.0.1: 8081

    或者在应用程序里测试,比如在item微服务里打印端口信息,这样多启动几个item服务,多次请求可以看到效果:

    package com.zpc.item.controller;
    
    import com.zpc.item.entity.Item;
    import com.zpc.item.service.ItemService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author Evan
     */
    @RestController
    public class ItemController {
    
        @Value("${server.port}")
        private String port;
    
        @Autowired
        private ItemService itemService;
    
        /**
         * 对外提供接口服务,查询商品信息
         *
         * @param id
         * @return
         */
        @GetMapping(value = "item/{id}")
        public Item queryItemById(@PathVariable("id") Long id) {
            System.out.println("service port:"+port);
            return this.itemService.queryItemById(id);
        }
    
    }
    
    

    8.4.设置负载均衡策略

    只需要在配置文件中添加配置
    serviceId.ribbon.NFLoadBalancerRuleClassName=自定义的负载均衡策略类

    例如在order微服务的yml配置文件中添加:

    app-item:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    
    

    其中app-item是要访问的服务id

    测试:
    第1次:127.0.0.1: 8081
    第2次:127.0.0.1: 9081
    第3次:127.0.0.1: 9081
    第4次:127.0.0.1: 8081
    第5次:127.0.0.1: 9081
    第6次:127.0.0.1: 8081
    第7次:127.0.0.1: 9081
    第8次:127.0.0.1: 9081
    第9次:127.0.0.1: 8081
    第10次:127.0.0.1: 8081
    第11次:127.0.0.1: 8081
    第12次:127.0.0.1: 8081
    第13次:127.0.0.1: 8081
    第14次:127.0.0.1: 9081
    第15次:127.0.0.1: 9081
    第16次:127.0.0.1: 9081
    第17次:127.0.0.1: 8081
    第18次:127.0.0.1: 9081
    第19次:127.0.0.1: 8081
    第20次:127.0.0.1: 8081

    8.5.其它策略

    接口:com.netflix.loadbalancer.IRule,其实现类:
    在这里插入图片描述
    策略描述:
    在这里插入图片描述
    请添加图片描述
    在这里插入图片描述
    在这里插入图片描述

    9.容错保护:Hystrix

    9.1.分析

    在这里插入图片描述
    在这里插入图片描述

    9.2.雪崩效应

    在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。

    如果下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。
    在这里插入图片描述

    9.3.SpringCloud Hystrix简介

    主页:https://github.com/Netflix/Hystrix/
    请添加图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    9.4.原理说明

    正常情况:
    在这里插入图片描述
    在这里插入图片描述
    当对特定服务的呼叫达到一定阈值时(Hystrix中的默认值为5秒内的20次故障),电路打开,不进行通讯。并且是一个隔离的线程中进行的。

    9.5.快速入门

    在 microservice-order系统中增加Hystrix容错(本教程中order微服务工程主要扮演消费者的角色)。

    9.5.1.导入依赖

    <!--整合hystrix-->
    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
    

    9.5.2.修改ItemService的queryItemById方法

    在要做容错处理的方法上加@HystrixCommand注解,并指定一个容错方法,即fallbackMethod 。

    /**
     * 进行容错处理
     * fallbackMethod的方法参数个数类型要和原方法一致
     *
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
    public Item queryItemById3(Long id) {
        String itemUrl = "http://app-item/item/{id}";
        Item result = restTemplate.getForObject(itemUrl, Item.class, id);
        System.out.println("===========HystrixCommand queryItemById-线程池名称:" + Thread.currentThread().getName() + "订单系统调用商品服务,result:" + result);
        return result;
    }
    
    /**
     * 请求失败执行的方法
     * fallbackMethod的方法参数个数类型要和原方法一致
     *
     * @param id
     * @return
     */
    public Item queryItemByIdFallbackMethod(Long id) {
        return new Item(id, "查询商品信息出错!", null, null, null);
    }
    
    

    9.5.3.在启动类OrderApplication添加@EnableHystrix注解

    /**
     * @author Evan
     */
    @SpringBootApplication
    @EnableEurekaClient
    @EnableHystrix
    @ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties"})//手动指定bean扫描范围
    public class OrderApp {
        public static void main(String[] args) {
            SpringApplication.run(OrderApp.class, args);
        }
    
        /**
         * 向Spring容器中定义RestTemplate对象
         * @return
         */
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
        }
    
    }
    
    

    9.5.4.在Controller增加一个入口

    package com.zpc.order.controller;
    
    import com.zpc.order.entity.Order;
    import com.zpc.order.service.OrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class OrderController {
        @Autowired
        private OrderService orderService;
    
        @GetMapping(value = "order/{orderId}")
        public Order queryOrderById(@PathVariable("orderId") String orderId) {
            return this.orderService.queryOrderById(orderId);
        }
    
        @GetMapping(value = "order2/{orderId}")
        public Order queryOrderById2(@PathVariable("orderId") String orderId) {
            return this.orderService.queryOrderByIdx(orderId);
        }
    }
    
    

    OrderService中增加:

    public Order queryOrderByIdx(String orderId) {
        Order order = ORDER_DATA.get(orderId);
        if (null == order) {
            return null;
        }
        List<OrderDetail> orderDetails = order.getOrderDetails();
        for (OrderDetail orderDetail : orderDetails) {
            // 通过商品微服务查询商品详细数据
            Item item = this.itemService.queryItemById3(orderDetail.getItem()
                    .getId());
            if (null == item) {
                continue;
            }
            orderDetail.setItem(item);
        }
    
        return order;
    }
    
    

    9.5.5.重新启动进行测试

    http://localhost:8082/order/201810300001
    http://localhost:8082/order2/201810300001
    在这里插入图片描述
    测试一切正常。可以发现加了@HystrixCommand注解的方法和普通方法不是共用的线程池,是隔离的:
    在这里插入图片描述
    接下来,我们把商品服务停止进行测试:

    在这里插入图片描述
    可以看到,使用了hystrix容错机制时订单服务(app-order)正常,尽管查询商品服务(app-item)已停止服务,查询到的是错误信息。

    由此可见,商品服务的宕机并没有影响订单服务的正常工作,起到的容错效果。

    如果调用没有做Hytrix容错的方法,则直接返回异常信息:

    在这里插入图片描述

    10.Feign客户端-声明式REST调用

    10.1.分析

    之前教程中我们通过RestTemplate调用REST服务,代码是这样的:

    @HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
    public Item queryItemById(Long id) {
        String itemUrl = "http://app-item/item/{id}";
        Item result = restTemplate.getForObject(itemUrl, Item.class, id);
        return result;
    }
    
    

    虽然使用了Ribbon和Hystrix可以实现负载均衡和容错处理,但是这个编码在实现大量业务时会显得太过于冗余(如,多参数的URL拼接)。
    思考:有没有更加优雅的实现呢?

    10.2.Feign简介

    项目主页:https://github.com/OpenFeign/feign
    在这里插入图片描述

    10.3.快速入门

    在订单微服务microservice order中增加对Feign的支持。

    10.3.1.导入依赖

    <!--springboot 整合fegnin客户端-->
    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
    

    10.3.2.创建一个ItemFeignClient接口

    package com.zpc.order.feign;
    import com.zpc.order.entity.Item;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    /**
     * 申明这是一个Feign客户端,并且指明服务id
     * @author Evan
     */
    @FeignClient(value = "app-item")
    public interface ItemFeignClient {
        /**
         * 这里定义了类似于SpringMVC用法的方法,就可以进行RESTful方式的调用了
         *
         * @param id
         * @return
         */
        @RequestMapping(value = "/item/{id}", method = RequestMethod.GET)
        Item queryItemById(@PathVariable("id") Long id);
    }
    
    

    10.3.3.改造ItemService

    @Autowired
    private ItemFeignClient itemFeignClient;
    
    @HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
    public Item queryItemById3(Long id) {
        String itemUrl = "http://app-item/item/{id}";
        Item result = itemFeignClient.queryItemById(id);
        System.out.println("===========HystrixCommand queryItemById-线程池名称:" + Thread.currentThread().getName() + "订单系统调用商品服务,result:" + result);
        return result;
    }
    
    

    10.3.4.在启动类中添加 @EnableFeignClients 注解

    package com.zpc.order.runner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.netflix.hystrix.EnableHystrix;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication//申明这是一个Spring Boot项目
    @EnableEurekaClient
    @EnableHystrix
    @EnableFeignClients(basePackages ="com.zpc.order.feign")
    @ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties"})//手动指定bean扫描范围
    public class OrderApp {
        public static void main(String[] args) {
            SpringApplication.run(OrderApp.class, args);
        }
    
        /**
         * 向Spring容器中定义RestTemplate对象
         * @return
         */
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
        }
    }
    
    

    10.3.5.重启测试

    在这里插入图片描述
    测试结果,一切正常。

    10.4.到底发生了什么?

    @Autowired
    private ItemFeignClient itemFeignClient;
    
    @HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
    public Item queryItemById3(Long id) {
        String itemUrl = "http://app-item/item/{id}";
        //Item result = restTemplate.getForObject(itemUrl, Item.class, id);
        Item result = itemFeignClient.queryItemById(id);
        return result;
    }
    
    

    写这样的代码,就可以访问RESTful服务啦?

    流程分析:
    1、由于我们在入口程序使用了@EnableFeignClients注解,Spring启动后会扫描标注了@FeignClient注解的接口,然后生成代理类。
    2、我们在@FeignClient接口中指定了value,其实就是指定了在Eureka中的服务名称。
    3、在FeignClient中的定义方法以及使用了SpringMVC的注解,Feign就会根据注解中的内容生成对应的URL,然后基于Ribbon的负载均衡去调用REST服务。
    为什么使用的是SpringMVC的注解?

    i.其实,Feign是有自己的注解的@RequestLine,是因为SpringCloudFeign做了增强,兼容了SpringMVC的注解,使我们的学习成本更低
    在这里插入图片描述
    ii.专业的解释是这样的:
    org.springframework.cloud.netflix.feign.FeignClientsConfiguration
    在这里插入图片描述
    设置的默认契约是SpringMVC契约。

    10.5.Feign的多参数构造

    服务方参数列表使用注解@RequestParam@PathVariable修饰
    在这里插入图片描述

    10.6.设置统一的hystrix fallback接口

    参考:http://www.ityouknow.com/springcloud/2017/05/16/springcloud-hystrix.html
    一般在实际开发中fallback 方法不会直接写在接口方法所在类里,那样太杂乱,例如之前订单工程中的写法:

    @HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
    public Item queryItemById3(Long id) {
        Item result = itemFeignClient.queryItemById(id);
        System.out.println("===========HystrixCommand queryItemById-线程池名称:" + Thread.currentThread().getName() + "订单系统调用商品服务,result:" + result);
        return result;
    }
    
    public Item queryItemByIdFallbackMethod(Long id) {
        return new Item(id, "查询商品信息出错!", null, null, null);
    }
    
    

    Order工程改进(将fallback方法放到类中):
    1.不在方法上使用@HystrixCommand注解

    //@HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod")
    public Item queryItemById3(Long id) {
        Item result = itemFeignClient.queryItemById(id);
        System.out.println("===========HystrixCommand queryItemById-线程池名称:" + Thread.currentThread().getName() + "订单系统调用商品服务,result:" + result);
        return result;
    }
    
    

    2.创建回调类

    package com.zpc.order.fallback;
    import com.zpc.order.entity.Item;
    import com.zpc.order.feign.ItemFeignClient;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.PathVariable;
    /**
     * 此类中的方法专门用于服务降级,该类一般要实现调用远程服务的接口(这样保证方法名一致)
     */
    @Component
    public class ItemServiceFallback  implements ItemFeignClient {
        /**
         * 服务降级的方法要和原方法一致(名称、参数列表)
         * @param id
         * @return
         */
        @Override
        public Item queryItemById(@PathVariable("id") Long id) {
            return new Item(null, "服务降级方法queryItemById", null, "服务降级方法queryItemById", null);
        }
    }
    
    

    3.在Feign客户端中添加fallback属性

    package com.zpc.order.feign;
    import com.zpc.order.entity.Item;
    import com.zpc.order.fallback.ItemServiceFallback;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    /**
     * 申明这是一个Feign客户端,并且指明服务id
     * 实际开发中ItemFeignClient一般直接继承(extends)服务提供方的接口以避免代码重复(例如Item工程会以jar包的形式提供ItemService接口)
     * @author Evan
     */
    @FeignClient(value = "app-item",fallback = ItemServiceFallback.class)
    public interface ItemFeignClient{
        /**
         * 这里定义了类似于SpringMVC用法的方法,就可以进行RESTful方式的调用了
         *
         * @param id
         * @return
         */
        @RequestMapping(value = "/item/{id}", method = RequestMethod.GET)
        Item queryItemById(@PathVariable("id") Long id);
    }
    
    

    4.配置文件中开启hystrix

    #开启hystrix断路器
    feign:
      hystrix:
        enabled: true
    
    

    5.修改启动类,增加包扫描

    @ComponentScan(basePackages = {"com.zpc.order.controller", "com.zpc.order.service","com.zpc.order.properties","com.zpc.order.fallback"})//手动指定bean扫描范围
    
    

    6.重新启动应用测试
    服务都正常的情况:
    在这里插入图片描述
    在这里插入图片描述
    停止Item服务,访问Order服务没有配置hystrix熔断的普通方法:
    在这里插入图片描述
    停止Item服务,访问Order服务中配置了hystrix熔断的方法:
    在这里插入图片描述
    配置fallback类OK!

    11.服务网关 SpringCloud Zuul

    11.1.分析

    通过前面的学习,使用SpringCloud实现微服务的架构基本成型,大致是这样的:
    在这里插入图片描述

    • 我们使用SpringCloud Netflix中的Eureka作为服务注册中心完成服务注册与发现;而服务间通过Feign和Ribbon实现服务的消费以及负载均衡;通过SpringCloud Config实现了应用多环境的外部化配置以及版本管理。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。

    • 思考:在该架构中,我们的服务集群包含:内部服务Service A和Service B,他们都会注册与订阅服务至Eureka Server,而Open Service是一个对外的服务,通过负载均衡公开至服务调用方。我们把焦点聚集在对外服务这块,这样的实现是否合理,或者是否有更好的实现方式呢?

    先来说说这样架构需要做的一些事儿以及存在的不足:

    • 首先,破坏了服务无状态特点。
      为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。
      从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外可续对接口访问的控制处理。

    • 其次,无法直接复用既有接口。
      当我们需要对一个既有的集群内的接口,实现外部访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。

    • 面对类似上面的问题,我们要如何解决呢? 答案是:服务网关!
      为了解决上面这些问题,我们需要将权限控制、日志收集这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的均衡负载器 服务网关。

    • 服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、负载均衡功能之外,它还具备了权限控制等功能。SpringCloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

    11.2.Zuul的简介

    官网:https://github.com/Netflix/zuul
    在这里插入图片描述
    在这里插入图片描述

    11.3.使用Zuul之后的架构

    在这里插入图片描述
    从架构图中可以看出,客户端请求微服务时,先经过Zuul之后再请求,这样就可以将一些类似于校验的业务逻辑放到zuul中完成。
    而微服务自身只需要关注自己的业务逻辑即可。当然在Zuul上层也可以搭建Nginx、F5等负载均衡设施。

    11.4.快速入门

    11.4.1.创建工程microsrvice-api-gateway

    在这里插入图片描述
    在这里插入图片描述

    11.4.2.导入依赖

    <?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.zpc.microservice</groupId>
        <artifactId>microservice-api-gateway</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
    
        <dependencyManagement>
            <dependencies>
                <!-- 导入SpringCloud的依赖管理 -->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Finchley.SR1</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        <dependencies>
    <!--整合zuul网关-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            </dependency>
         </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    
    

    11.4.3.编写启动类ApiGatewayApplication

    package com.zpc.gateway;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    
    @EnableZuulProxy
    @SpringBootApplication
    public class ApiGatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(ApiGatewayApplication.class, args);
        }
    }
    
    

    11.4.4.编写application.yml文件

    server:
      port: 8087 #服务端口
    spring:
      application:
        name: app-zuul-gateway #指定服务名
    
    

    11.4.5.编写路由规则

    我们编写路由规则:

    server:
      port: 8087 #服务端口
    spring:
      application:
        name: app-zuul-gateway #指定服务名
    zuul:
      routes: #定义服务转发规则
        item-service: #item-service这个名字任意取的
          path: /item-service/** #配置请求URL的请求规则
          url: http://127.0.0.1:8081 #真正的微服务地址,path匹配的请求都转发到这里
    
    

    11.4.6.启动测试

    同时启动商品微服务工程、网关工程。
    在这里插入图片描述
    可以看到,已经通过zuul访问到了商品微服务。

    11.5.面向服务的路由

    在快速入门中我们已经通过了URL映射,访问到了商品微服务。这样做会存在一个问题,就是,如果商品微服务的地址发生了改变怎么办?
    很自然的能够想到,不应该配置具体的url而是走Eureka注册中心获取地址。

    11.5.1.添加Eureka服务的依赖

    <!--整合eureka客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
    

    11.5.2.修改application.yml配置文件

    server:
      port: 8087 #服务端口
    spring:
      application:
        name: app-zuul-gateway #指定服务名
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    zuul:
      routes: #定义服务转发规则
        item-service: #item-service这个名字是任意写的
          path: /item-service/** #匹配item-service的请求app-item服务
          #url: http://127.0.0.1:8081 #真正的微服务地址
          serviceid: app-item
        order-service: #名字尽量和业务系统相关
          path: /order-service/** #匹配order-service的请求app-order服务
          serviceid: app-order
    
    

    11.5.3.启动测试

    启动Eureka注册中心、Item服务、order服务、网管服务4个工程。
    查看Eureka注册中:
    在这里插入图片描述
    发现已经有gateway在注册中心了。
    接下来测试,功能是否正常:
    在这里插入图片描述
    在这里插入图片描述
    发现一切正常。

    11.6.zuul配置详解

    11.6.1.指定服务id

    在这里插入图片描述

    11.6.2.忽略指定服务

    在这里插入图片描述

    11.6.3.忽略所有服务,只是有路由指定

    在这里插入图片描述

    11.6.4.同时配置path和url

    在这里插入图片描述

    11.6.5.面向服务配置,不破坏Hystrix、Ribbon特性

    zuul:
      routes: #定义服务转发规则
        item-service: #item-service这个名字是任意写的
          path: /item-service/** #匹配item-service的url路径请求app-item服务
          #url: http://127.0.0.1:8081 #真正的微服务地址
          serviceid: app-item
        order-service: #名字尽量和业务系统相关
          path: /order-service/** #匹配order-service的url路径请求app-order服务
          serviceid: app-order #指定Eureka注册中心的服务id
    
    

    11.6.6.使用正则表达式指定路由规则

    在这里插入图片描述

    11.6.7.路由前缀

    在这里插入图片描述

    11.6.8.忽略某些路径

    在这里插入图片描述

    11.7.过滤器

    过滤器是Zuul的重要组件。

    11.7.1.过滤器ZuulFilter

    ZuulFilter是一个抽象类,其实现类需要实现4个方法:

    shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。

    • run:过滤器的具体业务逻辑。
    • filterType:返回字符串代表过滤器的类型
      a)pre:请求在被路由之前执行
      b)routing:在路由请求时调用
      c)post:在routing和errror过滤器之后调用
      d)error:处理请求时发生错误调用
    • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

    11.7.2.执行流程

    在这里插入图片描述
    在这里插入图片描述

    11.8.过滤器实战

    需求:通过编写过滤器实现用户是否登录的检查。
    实现:通过判断请求中是否有token,如果有认为就是已经登录的,如果没有就认为是非法请求,响应401.

    11.8.1.编写UserLoginZuulFilter

    package com.zpc.gateway.filter;
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.stereotype.Component;
    import javax.servlet.http.HttpServletRequest;
    /**
     *  网关过滤器
     *  加入到Spring容器
     * @author Evan
     */
    @Component
    public class UserLoginZuulFilter extends ZuulFilter {
    
        @Override
        public boolean shouldFilter() {
            return true; // 该过滤器需要执行
        }
    
        @Override
        public Object run() { //编写业务逻辑
            RequestContext requestContext = RequestContext.getCurrentContext();
            HttpServletRequest request = requestContext.getRequest();
            String token = request.getParameter("token");
            if(StringUtils.isEmpty(token)){
                requestContext.setSendZuulResponse(false); // 过滤该请求,不对其进行路由
                requestContext.setResponseStatusCode(401); // 设置响应状态码
                requestContext.setResponseBody(" token is empty!!"); // 设置响应状态码
                return null;
            }
            return null;
        }
    
        @Override
        public String filterType() {
            return "pre"; // 设置过滤器类型为:pre
        }
    
        @Override
        public int filterOrder() {
            return 0;// 设置执行顺序为0
        }
    }
    
    

    入口程序:

    package com.zpc.gateway.runner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    import org.springframework.context.annotation.ComponentScan;
    
    @EnableZuulProxy
    @SpringBootApplication
    @ComponentScan(basePackages = "com.zpc.gateway.filter")
    public class ApiGatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(ApiGatewayApplication.class, args);
        }
    }
    
    

    11.8.2.启动测试

    在这里插入图片描述
    带token正常访问:
    在这里插入图片描述
    可以看到过滤器已经生效。

    11.9.默认开启ribbon

    Zuul网关默认开启了 ribbon 负载均衡,可以修改端口,启动多个Item服务进行测试,不断刷新浏览器请求:
    http://localhost:8087/item-service/item/2?token=1
    发现多个Item服务被轮询的访问:
    service port:8080
    service port:8081

    11.10.动态网关

    将yml配置文件中的路由配置信息存在配置中心中可以实现网关的动态配置。请见4.8节。

    11.11.网关集群

    Zuul可以配合nginx搭建网关集群。只要在nginx的配置文件nginx.conf里配置多个zuul地址:

    http {
      	upstream myhttpServer{
    #配置多个zuul地址
    	server localhost:8087;
    	server localhost:8086;
    	}
    	server {
    		listen       80;
           server_name  www.zpc.com;
    		location / {
                proxy_pass   http://myhttpServer;
                index  index.html index.htm;
            }
    	}
    }
    
    

    架构图:
    在这里插入图片描述
    浏览器访问:http://www.zpc.com/item-service/item/2?token=1

    12.分布式配置中心 SpringCloud Config

    12.1.之前的配置文件用法存在什么问题?

    我们开发项目时,需要有很多的配置项需要写在配置文件中,如:数据库的连接信息等。
    这样看似没有问题,但是我们想一想,如果我们的项目已经启动运行,那么数据库服务器的ip地址发生了改变,我们该怎么办?
    如果真是这样,我们的应用需要重新修改配置文件,然后重新启动,如果应用数量庞大,那么这个维护成本就太大了!
    有没有好的办法解决呢?当然是有的,SpringCloud Config提供了这样的功能,可以让我们统一管理配置文件,以及实时同步更新,并不需要重新启动应用程序。

    12.2.SpringCloud Config 简介

    在这里插入图片描述
    Config Server是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环境下的配置,默认使用Git存储配置文件内容,也可以使用SVN存储,或者是本地文件存储。
    Config Client是Config Server的客户端,用于操作存储在Config Server中的配置内容。微服务在启动时会请求Config Server获取配置文件的内容,请求到后再启动容器。
    使用SpringCloud Config的架构:
    在这里插入图片描述

    12.3.快速入门 – 搭建Config Server

    12.3.1.准备3个配置文件,推送到Git服务器

    准备3个文件(也可以使用yml文件):

    microservice-dev.properties
    microservice-prod.properties
    microservice-test.properties
    

    该文件的命名规则是:{application}-{profile}.properties
    其内容是(另外2个文件内容稍有不同即可):

    jdbc.driverClassName=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    jdbc.username=root
    jdbc.password=123456
    
    

    推送文件到git服务器,这里使用的是码云,当然也可以使用github或者使用svn。使用码云创建一个项目(私有项目需要账号密码)

    在这里插入图片描述

    12.3.2.创建工程microservice-config-server

    pom依赖:

    <?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.zpc.microservice</groupId>
        <artifactId>microservice-config-server</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.6.RELEASE</version>
        </parent>
    
        <dependencyManagement>
            <dependencies>
                <!-- 导入SpringCloud的依赖管理 -->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Finchley.SR1</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        <dependencies>
            <!--整合配置中心-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config-server</artifactId>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    
    

    12.3.3.编写入口ConfigApplication

    package com.zpc.configcenter;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.config.server.EnableConfigServer;
    /**
     * 开启配置服务
     */
    @EnableConfigServer
    @SpringBootApplication
    public class ConfigApplication {
        public static void main(String[] args) {
            SpringApplication.run(ConfigApplication.class, args);
        }
    }
    
    

    12.3.4.编写application.yml

    server:
      port: 7788 #服务端口
    spring:
      application:
        name:  microservice-config-server #指定服务名
      cloud:
        config:
          server:
            git: #配置git仓库地址
              uri: https://gitee.com/hellpzpc/myspconfig.git
              search-paths:
              - myspringcloudconfig		#配置文件目录地址
              username: 3747xxxx@qq.com	#码云账号(公有项目不需要设置)
              password: m86xxx 	#码云密码(公有项目不需要设置)
          label: master	#分支名称
    
    

    12.3.5.启动测试

    在这里插入图片描述
    测试已经看到了配置文件的内容。

    请求配置文件的规则如下:

    /{application}/{profile}/[label]
    /{application}-{profile}.yml
    /{label}/{application}-{profile}.yml
    /{application}-{profile}.properties
    /{label}/{application}-{profile}.properties
    
    

    其中{label}是指分支,默认是master。

    12.4.快速入门 – 搭建Config Client

    我们在microservice-item项目中添加Config Client的支持。来读取JDBC的配置文件的内容。

    12.4.1.导入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-client</artifactId>
    </dependency>
    
    

    12.4.2.新增配置文件bootstrap.yml

    spring:
      cloud:
        config:
          uri: http://127.0.0.1:7788/  #配置中心的地址
          profile: dev #对应配置服务中的{profile}
          label: master #对应的分支
    
    

    12.4.3.编写JdbcConfigBean

    编写对象通过@Value注解读取Config Server中的值。

    package com.zpc.item.config;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    @Component
    public class JdbcConfigBean {
        @Value("${jdbc.url}")
        private String url;
        @Value("${jdbc.username}")
        private String username;
        @Value("${jdbc.password}")
        private String password;
        @Value("${jdbc.driverClassName}")
        private String driverClassName;
        public String getUrl() {
            return url;
        }
        public void setUrl(String url) {
            this.url = url;
        }
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        public String getDriverClassName() {
            return driverClassName;
        }
        public void setDriverClassName(String driverClassName) {
            this.driverClassName = driverClassName;
        }
        @Override
        public String toString() {
            return "JdbcConfigBean [url=" + url + ", username=" + username
                    + ", password=" + password + ", driverClassName="
                    + driverClassName + "]";
        }
    }
    
    

    12.4.4.编写测试方法进行测试

    在ItemController中增加testconfig方法:

    package com.zpc.item.controller;
    import com.zpc.item.config.JdbcConfigBean;
    import com.zpc.item.entity.Item;
    import com.zpc.item.service.ItemService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    /**
     * @author Evan
     */
    @RestController
    public class ItemController {
        @Value("${server.port}")
        private String port;
        @Autowired
        private ItemService itemService;
        @Autowired
        private JdbcConfigBean jdbcConfigBean;
        /**
         * 对外提供接口服务,查询商品信息
         *
         * @param id
         * @return
         */
        @GetMapping(value = "item/{id}")
        public Item queryItemById(@PathVariable("id") Long id) {
            System.out.println("service port:"+port);
            return this.itemService.queryItemById(id);
        }
    
        @GetMapping(value = "testconfig")
        public String testconfig(){
            return this.jdbcConfigBean.toString();
        }
    }
    
    

    12.4.5.启动测试

    在这里插入图片描述
    测试结果显示,已经从Config Server中获取到配置文件的内容。

    12.5.手动更新运行中的配置文件

    如果git服务器中的配置文件更新了怎么办?正在运行的应用中的配置内容如何更新?

    12.5.1.测试

    现在我们更改git服务器中的配置文件内容,端口改成8888:
    在这里插入图片描述
    然后刷新Config Server地址观察:
    在这里插入图片描述
    可以看到这里查询到的是新的数据,端口已经更新为8888。
    但是在Config Client中测试:
    在这里插入图片描述
    看到依然是旧的数据。
    如何才能在重启应用的情况下,获取到最新的配置文件内容呢? – 为Config Client添加refresh支持。

    12.5.2.加入依赖

    使用actuator 监控中心完成刷新功能:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
    

    12.5.3.为JdbcConfigBean添加@RefreshScope注解

    需要为动态更新配置内容的bean添加@RefreshScope注解。

    @Component
    @RefreshScope
    public class JdbcConfigBean {...//代码省略}
    
    

    12.5.4.修改application.yml文件

    ###服务端口号(本身是一个web项目)
    server:
      port: 8080
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-item
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    #开启所有端点
    management:
      endpoints:
        web:
         exposure:
           include: "*"
    
    

    12.5.5.重启做测试

    加了actuator依赖后,启动项目控制台输出一行:

    2018-11-04 21:17:19.749  INFO 187328 --- [main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/refresh],methods=[POST],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
    
    

    为了测试,需要手动发起post请求actuator接口:
    POST请求监控中心 http://localhost:8080/actuator/refresh
    使用接口测试工具(例如postman)发送post请求actuator/refresh来更新配置内容:
    在这里插入图片描述
    响应:
    在这里插入图片描述
    刷新可以看到有配置文件内容更新了。
    在这里插入图片描述
    可以看到应该更新了最新的配置文件内容,我们也就实现了在未重启项目的情况下实现了动态修改配置文件内容。

    但是,项目已经发布上线了,不可能人为的守在服务器前,发现有更新了就手动请求监控中心的refresh接口。
    是否自动完成呢?

    12.6.借助与git的webhook(web钩子)实现自动更新

    码云、github等git服务器提供了web hook功能,意思是,在仓库中的资源发生更新时会通知给谁,这里的谁是一个url地址。
    在这里插入图片描述
    可以在命令行查看服务器ip地址,把外部(例如码云)能访问到的你的服务器ip设置为推送地址。
    接下来进行测试,更新配置文件的内容。
    测试的结果会发现,配置文件内容会动态更到Bean中。
    (要使自己的服务能够被外部访问到需要做端口映射)
    总结下流程:
    在这里插入图片描述

    12.7.Config Client配置优化-注册到Eureka中

    12.7.1.分析

    在 microservice-item中作为Config Client,在配置文件中了写死了Config Server的地址:

    spring:
      cloud:
        config:
          name: microservice #对应配置中心的应用名称,不写默认是本应用名,即spring.application.name
          uri: http://127.0.0.1:7788/
          profile: dev #对应配置服务中的{profile}
          label: master #对应的分支
    
    

    这样的硬编码是不好的,如果配置中心的ip地址发生了改变,那么久需要重新修改并且重启应用了。
    想想,有什么好的办法解决呢? 如果将Config Server作为一个微服务,并且将其注册的Eureka中,是不是就可以不用硬编码了?

    12.7.2.在 microservice-config-server中添加Eureka的依赖

    <!--整合eureka客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
    

    12.7.3.修改application.yml文件

    server:
      port: 7788 #服务端口
    spring:
      application:
        name:  microservice-config-server #指定服务名
      cloud:
        config:
          server:
            git: #配置git仓库地址
              uri: https://gitee.com/hellpzpc/myspconfig.git
              search-paths:
              - myspringcloudconfig       #配置文件目录地址
              username: 3xxxx@qq.com   #码云账号(公有项目不需要设置)
              password: mx8xxx2  #码云密码(公有项目不需要设置)
          label: master    #分支
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    
    

    12.7.4.ConfigApplication添加@EnableEurekaClient注解

    /**
     * 开启配置服务
     */
    @EnableConfigServer
    @SpringBootApplication
    @EnableEurekaClient
    public class ConfigApplication {
        public static void main(String[] args) {
            SpringApplication.run(ConfigApplication.class, args);
        }
    }
    
    

    12.7.5.在microservice-item中修改bootstrap.yml配置

    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
          instance-id: ${spring.application.name}###${server.port} #指定实例id  
    spring:
      cloud:
        config:
          name: microservice #对应配置中心的应用名称,默认是本应用名,即spring.application.name,该名称要和git中的配置一致
          #uri: http://127.0.0.1:7788/
          profile: dev #对应配置服务中的{profile}
          label: master #对应的分支
          discovery:
            enabled: true #启用发现服务功能
            service-id: microservice-config-server #指定配置中心工程的名称
    
    

    疑问:在application.yml中以及配置Eureka的信息,为什么在bootstrap.yml还需要配置?
    因为在Spring Boot中bootstrap.yml在application.yml之前加载,所以即使在application.yml中以及配置Eureka的信息,是使用不了的,所以需要在bootstrap.yml中配置Eureka的信息。

    12.7.6.测试

    端口改成9999:
    在这里插入图片描述
    发post请求手动刷新:
    在这里插入图片描述
    查看最新配置:
    在这里插入图片描述
    测试结果,一切正常。这就完美解决了硬编码的问题。

    12.8.动态网关

    传统方式将zuul路由规则配置在配置文件中,如果修改了路由规则,无疑需要重启服务器。可以将配置文件存在配置中心,利用SpringCloud Config实现动态路由规则添加。
    1.在git服务器上创建配置文件service-zuul-dev.yml,注释掉网关工程yml文件中的zuul配置,把配置拷到service-zuul-dev.yml中。
    在这里插入图片描述
    注释掉网关工程microservice-api-gateway中的配置,并开启actuator:
    在这里插入图片描述
    2.在网关工程的pom中引入actuator依赖:

    <!--开启监控中心-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
    

    3.在网关工程新建bootstrap.yml,启用springcloud config 功能:

    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    spring:
      cloud:
        config:
          name: service-zuul #对应配置中心的应用名称,默认是本应用名,即spring.application.name,该名称要和git中的配置一致
          #uri: http://127.0.0.1:7788/
          profile: dev #对应配置服务中的{profile}
          label: master #对应的分支
          discovery:
            enabled: true #启用发现服务功能
            service-id: microservice-config-server #指定配置中心工程的名称
    
    

    4.在网关工程启动类增加@RefreshScope注解,支持手动刷新

    package com.zpc.gateway.runner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
    import org.springframework.context.annotation.ComponentScan;
    
    @EnableZuulProxy
    @SpringBootApplication
    @ComponentScan(basePackages = "com.zpc.gateway.filter")
    public class ApiGatewayApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ApiGatewayApplication.class, args);
        }
    
        @RefreshScope
        @ConfigurationProperties("zuul")
        public ZuulProperties zuulProperties(){
            ZuulProperties properties = new ZuulProperties();
            System.out.println("properties:"+properties);
            return properties ;
        }
    }
    
    

    5.启动配置中心(microservice-config-server)工程,查看能否访问git上的配置文件:
    在这里插入图片描述
    6.启动网关工程(microservice-api-gateway)、订单工程(microservice-order)、商品工程(microservice-item)测试。
    Eureka:
    在这里插入图片描述
    通过Zuul网关访问订单的服务OK:
    在这里插入图片描述
    通过Zuul网关访问商品的服务OK:
    在这里插入图片描述
    7.修改git中的配置文件,手动发post请求刷新,看修改是否生效
    修改了serviceid为不存在的服务app-itemXX:
    在这里插入图片描述
    刷新:
    在这里插入图片描述
    再次通过zuul访问item的服务:
    在这里插入图片描述
    8.把配置文件改回来,再发post请求http://localhost:8087/actuator/refresh,再次测试:
    在这里插入图片描述
    OK了,说明通过配置中心动态设置zuul配置生效:
    在这里插入图片描述

    13.消息总线 SpringCloud Bus

    13.1.分析

    虽然通过Git服务器的web hook可以实现自动更新,但是,如果Config Client有很多的话,那么需要在web hook中维护很多地址,如果是手动刷新的话还得一个一个地发送POST请求,这显然是不现实的做法。

    有没有更好的方案呢? 我们非常容易想到消息队列中的Topic,没错! 通过消息实现通知。

    13.2.SpringCloud Bus消息总线的简介

    在这里插入图片描述
    目前SpringCloud Bus消息总线只是实现了对RabbitMQ以及Kafka的支持。

    13.3.RabbitMQ的安装

    过于简单自行百度

    13.4.使用SpringCloud Bus的架构

    在这里插入图片描述

    13.5.实现

    13.5.1.在 microservice-item添加依赖

    <!--整合SpringCloud Bus-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    
    

    13.5.2.在bootstrap.yml添加rabbitmq的配置

    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          ip-address: 127.0.0.1
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    
    spring:
      cloud:
        config:
          name: microservice #对应配置中心的应用名称,默认是本应用名,即spring.application.name,该名称要和git中的配置一致
          #uri: http://127.0.0.1:7788/
          profile: dev #对应配置服务中的{profile}
          label: master #对应的分支
          discovery:
            enabled: true #启用发现服务功能
            service-id: microservice-config-server #指定配置中心工程的名称(在Eureka中的服务名)
      rabbitmq: #RabbitMQ相关的配置
        host: 127.0.0.1
        port: 5672
        username: guest
        password: guest
    
    

    13.5.3.修改web hook的地址

    在启动Item工程时会看到这样的日志:

    2018-11-12 21:43:47.722  INFO 221032 --- [main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/bus-refresh],methods=[POST]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
    
    

    说明刷新的地址/bus-refresh是由SpringCloud Bus来处理,之前的/refresh依然是由以前的逻辑处理。
    所以要修改Git服务器中的web hook的地址:(修改或者添加都可以)
    在这里插入图片描述

    13.5.4.启动测试

    首先需要保证Rabbit的服务开启:
    在这里插入图片描述
    启动rabbit管理工具:
    在这里插入图片描述
    查看是否启动成功:
    在这里插入图片描述
    启动Item工程后查看RabbitMQ中的交换机:
    在这里插入图片描述
    再看队列:
    在这里插入图片描述
    在这里插入图片描述
    接着,将 microservice-item的端口改成8082,再启动一个 microservice-item实例,进行测试。

    在这里插入图片描述
    发现,有2个队列,分别都绑定到springCloudBus的交换机。

    接下里,修改配置文件的内容进行测试。
    在这里插入图片描述
    在这里插入图片描述
    可以看到80818082这2个实例查询到的信息都是一样的。
    接下来,修改配置文件内容将9999改成8888:
    在这里插入图片描述
    在这里插入图片描述
    结果显示,都是获取到最新的数据。
    在测试时,会发现,由于Git服务器的web钩子推送到8181,所以8081的更新快一些,而8082更新就相对慢一些。

    13.5.5.流程总结

    更新文件到git服务器,Git服务器通过web钩子通知到8081的/bus/refresh,8081的实例将消息发送到springCloudBus的交换机,由于8081的队列页绑定到交换机,所以8082也获取到了更新的通知,然后去Config Server获取最新的数据。

    13.6.架构优化

    在前面实现的架构中,发现8081这个实例不仅仅是提供了商品查询的服务,还负责发送更新的消息到RabbitMQ。
    这其实是违反了微服务架构中的职责单一的原则。
    其实这个架构是可以改进的,就是将原有的Config Server不仅仅提供配置查询的服务,而且还要负责更新消息的发送。
    在这里插入图片描述

    13.6.1.在microservice-config-server中导入依赖

    <!--整合SpringCloud Bus-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    
    

    13.6.2.修改application.yml配置文件

    server:
      port: 7788 #服务端口
    spring:
      application:
        name:  microservice-config-server #指定服务名
      cloud:
        config:
          server:
            git: #配置git仓库地址
              uri: https://gitee.com/hellpzpc/myspconfig.git
              search-paths:
              - myspringcloudconfig       #配置文件目录地址
              username: 3747xxxx@qq.com   #码云账号(公有项目不需要设置)
              password: myxxxx  #码云密码(公有项目不需要设置)
          label: master    #分支
      rabbitmq: #RabbitMQ相关的配置
        host: 127.0.0.1
        port: 5672
        username: guest
        password: guest
    ###服务注册到eureka注册中心的地址
    eureka:
      client:
        service-url:
               defaultZone: http://zpc:123456@127.0.0.1:8100/eureka/,http://zpc:123456@127.0.0.1:9100/eureka/
    ###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
        register-with-eureka: true
    ###是否需要从eureka上检索服务
        fetch-registry: true
      instance:
          prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
          instance-id: ${spring.application.name}###${server.port} #指定实例id
    
    

    13.6.3.修改Git中的web钩子

    在这里插入图片描述

    13.6.4.重启测试

    测试结果,和之前一样,可以同步更新到8081和8082。

    14.整合consul&zookeeper作为注册中心

    由于Eureka 2.0已结宣称闭源,因此可以考试使用其它注册中心组件,例如zk、consul。

    14.1.整合zookeeper作为注册中心

    14.1.1.修改商品(Item)微服务项目

    第一步 添加依赖(注意zk和Eureka不能共存,请先注释掉Eureka的pom依赖)

    <!--springboot 整合zookeeper客户端(注意保证服务端和客户端zk版本一致)-->
    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    </dependency>
    
    

    第二步 修改yml配置文件,添加zk作为注册中心(注意zk和Eureka不能共存,请先注释掉Eureka的配置)

    ###服务端口号(本身是一个web项目)
    server:
      port: 8080
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-item
    ###服务注册到zookeeper注册中心的地址
        cloud:
            zookeeper:
              connect-string: 127.0.0.1:2181
    
    

    第三步 修改入口程序
    @EnableEurekaClient
    改为:
    @EnableDiscoveryClient

    14.1.2.修改订单(order)微服务项目

    第一步 添加依赖(注意zk和Eureka不能共存,请先注释掉Eureka的pom依赖)

    <!--springboot 整合zookeeper客户端(注意保证服务端和客户端zk版本一致)-->
    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    </dependency>
    
    

    第二步 修改yml配置文件,添加zk作为注册中心(注意zk和Eureka不能共存,请先注释掉Eureka的配置)

    ###服务端口号(本身是一个web项目)
    server:
      port: 8080
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-order
    ###服务注册到zookeeper注册中心的地址
        cloud:
            zookeeper:
              connect-string: 127.0.0.1:2181
    
    

    第三步 修改入口程序
    @EnableEurekaClient
    改为:
    @EnableDiscoveryClient

    14.1.3.启动zookeeper、商品工程、订单工程

    运行zookeeper目录\bin\zkServer.cmd
    注意项目中引入的zookeeper jar包版本要和你本地运行的zk服务器一致!!否则拒绝连接!笔者使用的是zookeeper-3.5.3-beta
    在这里插入图片描述
    通过ZooInspector查看注册的服务(两个临时节点):
    在这里插入图片描述

    14.1.4. 测试功能

    在这里插入图片描述
    切换到zk注册中心一切OK!

    14.2.整合consul作为注册中心

    14.2.1.Consul环境搭建

    1)下载
    https://www.consul.io/downloads.html
    2)启动
    进入到consul目录,命令行运行:
    consul agent -dev -ui -node=test
    -dev开发服务器模式启动,-node设置节点名称为test,-ui可以用界面访问(默认就能访问)
    3)访问
    http://localhost:8500
    在这里插入图片描述

    14.2.2.修改商品(Item)微服务项目

    第一步 引入依赖 (务必注释掉其他注册中心的pom依赖)

    <!--springboot 整合consul客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    
    

    第二步 修改yml配置文件(务必注释掉其他注册中心的配置项)

    ###服务端口号(本身是一个web项目)
    server:
      port: 8081
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-item
    ###服务注册到consul注册中心的地址
    ###spring.cloud.consul.host/port配置的是Consul注册中心(Consul服务器)的地址和端口(Server节点和Client节点都可以),**SpringCloud** Consul调用 Consul HTTP REST 接口,进行服务注册
    ###spring.cloud.consul.discovery.hostname配置 Spring Boot 服务的主机地址,也可以不进行配置,默认本机地址。
    ###healthCheckPath: /health 指定健康检查的url地址(保证该接口地址返回http 2xx 返回码,当然也可以忽略健康检查register-health-check: false)
        cloud:
           consul:
              host: 127.0.0.1
              port: 8500
              discovery:
                healthCheckPath: /health
                healthCheckInterval: 5s
    
    

    第三步 ItemController增加健康检查接口(即一个正常返回http 200的接口,如果consul健康检查失败,则服务不可调用)

    /**
     * 健康检查
     * @return
     */
    @RequestMapping("/health")
    public String healthCheck() {
        return "OK";
    }
    
    

    第四步 入口程序中@EnableEurekaClient换成@EnableDiscoveryClient

    14.2.3.修改订单(order)微服务项目

    第一步 引入依赖 (务必注释掉其他注册中心的pom依赖)

    <!--springboot 整合consul客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    
    

    第二步 修改yml配置文件(务必注释掉其他注册中心的配置项)

    ###服务端口号(本身是一个web项目)
    server:
      port: 8082
    ###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如商品服务)
    spring:
        application:
            name: app-order
    ###服务注册到consul注册中心的地址
    ###spring.cloud.consul.host/port配置的是Consul注册中心(Consul服务器)的地址和端口(Server节点和Client节点都可以),**SpringCloud** Consul调用 Consul HTTP REST 接口,进行服务注册
    ###spring.cloud.consul.discovery.hostname配置 Spring Boot 服务的主机地址,也可以不进行配置,默认本机地址。
    ###healthCheckPath: /health 指定健康检查的url地址(保证该接口地址返回http 2xx 返回码,当然也可以忽略健康检查register-health-check: false)
        cloud:
           consul:
              host: 127.0.0.1
              port: 8500
              discovery:
                healthCheckPath: /health
                healthCheckInterval: 5s
    
    

    第三步 OrderController增加健康检查接口(即一个正常返回http 200的接口,如果consul健康检查失败,则服务不可调用)

    /**
     * 健康检查
     * @return
     */
    @RequestMapping("/health")
    public String healthCheck() {
        return "OK";
    }
    
    

    第四步 入口程序中@EnableEurekaClient换成@EnableDiscoveryClient

    14.2.4.启动consul、商品工程、订单工程

    在这里插入图片描述
    保证健康检查全部OK,否则服务调不通!
    在这里插入图片描述

    14.2.5.测试功能

    在这里插入图片描述
    切换到consul注册中心一切OK!

    15.整合swagger API管理

    Swagger是一款能够让你更好地书写API文档的框架。它可以生成一个具有互动性的API控制台,方便前后端开发人员面向接口交流、接口测试。

    15.1.快速入门

    在商品系统(microservice-item)中做演示
    1.引入依赖

    <!--整合swagger -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.8.0</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.8.0</version>
    </dependency>
    
    

    2.配置Swagger

    package com.zpc.item.config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    @Configuration
    @EnableSwagger2//开启swagger2
    public class SwaggerConfig {
        @Bean
        public Docket createRestApi() {
            //配置swagger 生成API的扫描范围
            return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.zpc.item")).paths(PathSelectors.any()).build();
        }
        /**
         * 创建api文档信息
         * @return
         */
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder().title("鸟鹏的专栏").description("SpringCloud教程").termsOfServiceUrl("https://blog.csdn.net/hellozpc").version("1.0").build();
        }
    }
    
    

    3.编写Controller入口

    package com.zpc.item.controller;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.math.BigDecimal;
    @Api("SwaggerDemo控制器")
    @RestController
    public class SwaggerController {
    
        @ApiOperation("Swagger演示")
        @GetMapping("/swaggerIndex")
        public String swaggerIndex(String msg) {
            return "This is swaggerIndex!" + msg;
        }
    
        @ApiOperation("获取商品详情")
        @ApiImplicitParam(name = "itemName", value = "商品名称", required = true, dataType = "String")
        @PostMapping("/getItemInfo")
        public String getItemInfo(String itemName) {
            return "商品名:" + itemName + " 商品价格:" + new BigDecimal(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP);
        }
    }
    
    

    4.启动工程,访问http://localhost:8081/swagger-ui.html
    在这里插入图片描述
    在这个页面可以执行交互式的测试:
    在这里插入图片描述
    在这里插入图片描述

    15.2.Zuul整合Swagger统一管理微服务Api

    在一个入口统一管理所有微服务(订单服务、商品服务等)项目的API。实现方式:zuul
    1.在订单服务(microservice-order)中也引入swagger
    这次直接使用springboot提供的starter,不使用7.1那种方式。

    <!--引入swagger-->
    <dependency>
        <groupId>com.spring4all</groupId>
        <artifactId>swagger-spring-boot-starter</artifactId>
        <version>1.7.0.RELEASE</version>
    </dependency>
    
    

    创建Controller入口

    package com.zpc.order.controller;
    import com.spring4all.swagger.EnableSwagger2Doc;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.math.BigDecimal;
    @EnableSwagger2Doc
    @Api("订单服务接口")
    @RestController
    public class OrderApiController {
        @ApiOperation("提交订单")
        @ApiImplicitParam(name = "orderNo", value = "订单号", required = true, dataType = "String")
        @PostMapping("/putOrderInfo")
        public String putOrderInfo(String orderNo) {
            return "订单:" + orderNo + " 已提交,商品总价:" + new BigDecimal(Math.random() * 1000).setScale(2, BigDecimal.ROUND_HALF_UP);
        }
    
        @ApiOperation("获取订单详情")
        @ApiImplicitParam(name = "orderNo", value = "订单号", required = true, dataType = "String")
        @GetMapping("/getOrderInfo")
        public String getOrderInfo(String orderNo) {
            return "订单:" + orderNo + " 商品总价格:" + new BigDecimal(Math.random() * 1000).setScale(2, BigDecimal.ROUND_HALF_UP);
        }
    }
    
    

    yml中配置扫描范围

    #配置Swagger接口扫描范围
    swagger:
      base-package: com.zpc.order
    
    

    2.在网关服务中也引入Swagger

    <!--引入swagger-->
    <dependency>
        <groupId>com.spring4all</groupId>
        <artifactId>swagger-spring-boot-starter</artifactId>
        <version>1.7.0.RELEASE</version>
    </dependency>
    
    

    3.修改网关服务的启动类
    增加一个内部类DocumentationConfig

    package com.zpc.gateway.runner;
    import com.spring4all.swagger.EnableSwagger2Doc;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Primary;
    import org.springframework.stereotype.Component;
    import springfox.documentation.swagger.web.SwaggerResource;
    import springfox.documentation.swagger.web.SwaggerResourcesProvider;
    import java.util.ArrayList;
    import java.util.List;
    
    @EnableZuulProxy
    @EnableSwagger2Doc
    @SpringBootApplication
    @ComponentScan(basePackages = "com.zpc.gateway.filter")
    public class ApiGatewayApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ApiGatewayApplication.class, args);
        }
    
        @RefreshScope
        @ConfigurationProperties("zuul")
        public ZuulProperties zuulProperties(){
            ZuulProperties properties = new ZuulProperties();
            System.out.println("properties:"+properties);
            return properties ;
        }
    
        /**
         * 配置Swagger
         */
        @Component
        @Primary
        class DocumentationConfig implements SwaggerResourcesProvider{
            @Override
            public List<SwaggerResource> get() {
                List resource=new ArrayList<>();
                //name可以随便写,location前缀要与zuul配置的path一致。zuul开了token验证,要加上token,否则不用加?token=1
                resource.add(swaggerResource("myapp-item","/item-service/v2/api-docs?token=1","2.0"));
                resource.add(swaggerResource("myapp-order","/order-service/v2/api-docs?token=1","2.0"));
                return resource;
            }
    
    	 //name可以随便写,location前缀要与zuul配置的path一致
            private SwaggerResource swaggerResource(String name,String location,String version){
                SwaggerResource swaggerResource=new SwaggerResource();
                swaggerResource.setName(name);
                swaggerResource.setLocation(location);
                swaggerResource.setSwaggerVersion(version);
                return swaggerResource;
            }
        }
    }
    
    

    4.测试
    启动Eureka注册中心、item服务、order服务、网关服务开始测试
    直接通过网关的入口地址http://localhost:8087/swagger-ui.html?token=1统一访问所有微服务的api,界面右上角可以选择。
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • spring cloud详细教程

    2020-04-08 15:49:09
    https://github.com/mercyblitz/segmentfault-lessons https://www.bilibili.com/video/BV1ut411K7qj?p=20

    https://github.com/mercyblitz/segmentfault-lessons
    https://www.bilibili.com/video/BV1ut411K7qj?p=20

    展开全文
  • springCloud详细教程

    2019-03-14 13:46:06
    http://blog.csdn.net/forezp/article/details/70148833
  • SpringCloud详细教程6-Zookeeper 一、Zookeeper概述 Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。 Zookeeper设计模式:是一个基于观察者模式设计的分布式服务管理框架,它主要负责存储和...
  • SpringCloud详细教程3-Eureka服务注册中心 一、是么是服务注册中心 ​ SpringCloud封装了Netflix公司开发的Eureka模块来实现服务治理。 在传统的RPC远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理...
  • SpringCloud详细教程5-Eureka更多介绍 Eureka的actuator微服务信息完善: 一、主机名称的修改: 修改我们支付模块8001、8002的yml文件: eureka: instance: instance-id: payment8001 //8002中值为payment8002...
  • SpringCloud详细教程5-nacos的安装配置 今天我们先把nacos安装配置完成,接下来就不需要在来说这个了。 环境:win10、nacos1.4、MySQL 5.56+ 一、下载nacosnacos下载地址 提取码:0812 二、压缩我们下载好的nacos-...
  • SpringCloud详细教程4-Eureka集群的构建 学习之前小问题: 微服务RPC远程调用最核心的是什么?懂得略过。 ​ 核心:高可用 ​ 原因:假如,你的注册中心只有一个,那么当它宕机了之后,整个服务环境都不可用,其带来...
  • SpringCloud 详细教程(1) 搭建多模块开发环境 进行引入。 1. 修改父模块cloud_parent 配置 在cloud_parent 的pom.xml 中添加 一下依赖: &lt;dependency&gt; &lt;groupId&gt;org.spring...
  • 详细SpringCloud环境的搭建 开始前默认已配置基础环境: 基础环境:jdk 1.8、 IDEA、 MySQL数据库或其他数据库、Maven 一、学习方向 主要学习springcloud分布式开发 学习其设计模式以及设计思路 学习更多的小技巧...
  • 详细学习SpringCloud教程2—消费者模块 每天学习一点点,进步一点点,过段时间回头看看,进步的可不止一点点。 用户模块:承接我们的支付模块,程序是写给用户的,不可能只有支付对吧,还要给用户可用的接口,不...
  • 初识SpringCloud SpringCloud, 基于SpringBoot提供了一套微服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于NetFlix的开源组件做高度抽象封装外,还有一些选型中立的...
  • 文章目录服务调用RibbongetForEntityRibbon的负载均衡更换负载均衡算法手写一个轮询算法服务调用OpenFeignOpenFeign的使用OpenFeign的超时控制...路由断言过滤器SpringCloud Config分布式配置中心Config的服务端配置C

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 885
精华内容 354
关键字:

springcloud详细教程

spring 订阅