springcloud_springcloud面试 - CSDN
  • Spring Cloud微服务--入门到精通

    千人学习 2019-11-19 17:07:18
    1.本套视频从面试题,到SpringCloud各种核心组件,到最终的微服务架构总结等方面进行讲解。 2.帮助大家快速入门、上手并精通微服务框架SpringCloud。 3.课程中对比了 Dubbo 和 SpringCloud,并深入...
  • SpringCloud详细教程(上)

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

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

    SpringCloud详细教程

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

    SpringCloud教程

    文章目录

    1.教程目录

    • 开发环境说明
    • 细说微服务架构
    • 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快速入门

    万次阅读 多人点赞 2019-03-13 16:39:32
    一、 网站的架构演变 网络架构由最开始的三层mvc渐渐演变。传统的三层架构后来在互联网公司让几百人几千人同时开发一个项目已经变得不可行,并且会产生代码冲突的问题。基于SOA面向服务开发的架构,渐渐产生了...

    一、  网站的架构演变

        网络架构由最开始的三层mvc渐渐演变。传统的三层架构后来在互联网公司让几百人几千人同时开发一个项目已经变得不可行,并且会产生代码冲突的问题。基于SOA面向服务开发的架构,渐渐产生了微服务架构。微服务的架构的特点就是项目拆分成各个子项目,进行解耦操作,提供外部访问接口,属于敏捷开发,其实也可以视为面向接口开发。

        一旦有了多个子项目,比如把淘宝网的订单系统和会员系统分开来看,就回产生如何管理接口、负载均衡、高并发情况下怎么限流断路等问题。那么这就有SpringCloud出现了。

        那么springCloud的组件大概有哪些呢,我先简单介绍下:

    1. Eureka  服务注册中心
    2. 服务消费者 Rest 和 Fegin  --消费实现负载均衡ribbon
    3. 接口网关Zuul
    4. Hystrix  关于服务雪崩的解决方案--服务熔断、服务降级、隔离资源。

    二、  Eureka

        eureka是个什么东西呢?它是一个服务注册中心。就拿上面的例子来说,如果要查看会员的订单详情,那么就要在会员系统的tomcat里面调用订单系统的tomcat里的方法。那么直接通过接口访问吗?显然这是不安全的。因此我们需要一个统一管理远程RPC调用的注册中心

    如图所示,会员系统和订单都是独立能够运行的SpringBoot项目,把SpringBoot注册进eureka中,这样我们就可以通过eureka让会员系统远程调用订单系统。具体配置要怎么做呢?

        首先我们要创建eureka注册中心,这里建议用idea的工具创建SpringBoot项目。

    选择如图的包,如果没有则直接复制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>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.3.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>my</groupId>
        <artifactId>learning</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>learning</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
            <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
        <repositories>
            <repository>
                <id>spring-milestones</id>
                <name>Spring Milestones</name>
                <url>https://repo.spring.io/milestone</url>
            </repository>
        </repositories>
    
    </project>
    

     

    然后我们去yml文件中做如下配置

    #eureka的端口号
    server:
      port: 8888
    eureka:
      instance:
        hostname: localhost
      client:
        registerWithEureka: false
        fetchRegistry: false
        serviceUrl:
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

    然后在启动类里添加表示为eureka注册中心

    @EnableEurekaServer
    @SpringBootApplication
    public class LearningApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(LearningApplication.class, args);
        }
    
    }

    通过localhost:8888可以进到如下页面就表示eureka注册中心启动成功

    OK,那么我们要怎么把订单系统和会员系统注册进去呢?同样创建两个SpringBoot项目,创建方式和导包方式和注册中心一样。我们关注的只有yml文件是如何把这个springBoot项目注册进去的,那么我们来看看yml文件如何配置

    eureka:
      client:
        serviceUrl:
    #      eureka的注册中心地址
          defaultZone: http://localhost:8888/eureka/
    server:
    #  此项目端口号
      port: 8889
    spring:
      application:
    #    注册进eureka的名字
        name: order-server

     

    创建controller包并且启动

    @RestController
    public class ordercontroller {
        @RequestMapping("orderTest")
        public String orderTest(){
            return "this is order";
        }
    }
    
    //  启动类
    @EnableEurekaClient   
    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
    }

     再次打开注册中心网页,就发现已经注册进去

    重复以上步骤把会员系统也注册进去

    三、  PRC远程调用的方法

        远程调用的方法有两种,我们一一来细说。

        1.  第一种,通过rest的方式来调用,首先我们要导入rest的pom依赖,我们要使用用户调用订单,就在用户里添加调用依赖。先解释一下什么是ribbon-----ribbon是一个负载均衡客户端 类似nginx反向代理,可以很好的控制htt和tcp的一些行为。

    <dependency>

    <groupId>org.springframework.cloud</groupId>

    <artifactId>spring-cloud-starter-ribbon</artifactId>

    </dependency>

    //  在启动类里把ribbon类注入spring
    @EnableEurekaClient
    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
        @Bean
        @LoadBalanced        // 开启负载均衡
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    }
    
    public class memService{
        @Autowired
        private RestTemplate restTemplate;
        @RequestMapping("/memTest")
        public String memTest(){
            String str = restTemplate.getForObject("http://order-server/orderTest",String.class);
            return str;
        }
    }

    然后我们调用会员系统的接口访问,看他会不会走到订单系统里

    这就是Rest远程调用的结果。

        2.  Feigin

    Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。

    简而言之:

    ·Feign 采用的是基于接口的注解

    ·Feign 整合了ribbon

       第一步依然是导入依赖

    <dependency>

    <groupId>org.springframework.cloud</groupId>

    <artifactId>spring-cloud-starter-feign</artifactId>

    </dependency>

    @Service
    @FeignClient("order-server")
    public interface orderFeign {
        @RequestMapping("/orderTest")
        public String orderTest();
    }
    @RestController
    public class memController {
        @Autowired
        private OrderFeign orderFeign;
        @RequestMapping("/memTest")
        public String memTest(){
            String str = orderFeign.orderTest();
            return str;
        }
    }
    
    // 启动类
    @EnableEurekaClient
    @EnableFeignClients
    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
    }

    因此成功。这两个方式使用ribbon均衡负载,一个需要手动启动,fegin是自动启动。

    四、  路由网关(ZUUL)

        路由网关有什么作用呢?上面订单和会员系统已经注册进服务中心,两者之间是通过网址直接访问。但是如果在浏览器里由订单访问会员,会因为域名不同而导致跨域问题。跨域问题的解决方案可以使用http client设置、设置请求头、nginx转发解决,那么在SpringCloud里面当然提供了一套解决方案,那就是网关ZUUL。

    如图所示,当一个客户端如果直接访问时,会因为域名不同导致跨域问题。而我们所需要做的就是在前面设置一个网关变成my.com/vip  my.com/order,这样就不会产生跨域的问题。接下来我们来设置zuul。

        第一步首先也要注册进eureka,导入依赖。

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

     第二步配置yml文件..

    #注册进eureka
    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8888/eureka/
    #配置网关端口号
    server:
      port: 8080
    spring:
      application:
        name: zuul-server
    #配置网关转发详情
    zuul:
      routes:
        api-a:
          path: /member/**
          service-id: member-server
        api-b:
          path: /order/**
          service-id: order-server
    //  开启网关
    @EnableZuulProxy
    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
    }

     访问配置的网关地址8080,调用member-server里的方法,成功!还可以使用网关过滤信息。具体怎样过滤不做赘述。

    五、  断路器(Hystrix)

    为什么需要 Hystrix?

    在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用(RPC)。为了保证其高可用,单个服务又必须集群部署。由于网络原因或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务累计,导致服务瘫痪,甚至导致服务“雪崩”。为了解决这个问题,就出现断路器模型。

     

    什么是服务雪崩

    分布式系统中经常会出现某个基础服务不可用造成整个系统不可用的情况, 这种现象被称为服务雪崩效应. 为了应对服务雪崩, 一种常见的做法是手动服务降级. 而Hystrix的出现,给我们提供了另一种选择.

    通俗来说: 就是对一个方法的PRC调用并发数量太大

     

     

    服务雪崩应对策略

    针对造成服务雪崩的不同原因, 可以使用不同的应对策略:

    1. 流量控制

    2. 改进缓存模式

    3. 服务自动扩容

    服务调用者降级服务

     

    流量控制 的具体措施包括:

    ·网关限流

    ·用户交互限流

    ·关闭重试

     

    什么是服务降级

    所有的RPC技术里面服务降级是一个最为重要的话题,所谓的降级指的是当服务的提供方不可使用的时候,程序不会出现异常,而会出现本地的操作调用。

    通俗解释来说:就是上面例子里的会员系统访问订单系统,执行远程RPC调用方法,但是当达到一定并发量的时候,比如200个人同时访问 orderTest()方法时,tomcat的容量设置的只有150个,剩余的50个人就在外面等待一直等待。服务降级就是不让他们一直等待,调用本地的方法来fallback消息。而不再去PRC方法。

    Hystrix的作用

    1.断路器机制

    断路器很好理解, 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.

    2.Fallback

    Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.

    3.资源隔离

    在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池. 例如调用产品服务的Command放入A线程池, 调用账户服务的Command放入B线程池. 这样做的主要优点是运行环境被隔离开了. 这样就算调用服务的代码存在bug或者由于其他原因导致自己所在线程池被耗尽时, 不会对系统的其他服务造成影响. 但是带来的代价就是维护多个线程池会对系统带来额外的性能开销. 如果是对性能有严格要求而且确信自己调用服务的客户端代码不会出问题的话, 可以使用Hystrix的信号模式(Semaphores)来隔离资源.

    第一步首先是导入依赖

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

    Rest方式调用

    @HystrixCommand(fallbackMethod = "testError")
    @RequestMapping("/memTest")
        public String memTest(){
            String str = restTemplate.getForObject("http://order-server/orderTest",String.class);
            return str;
        }
        public String testError(){
            //远程调用失败,调用此方法
        }
    
    
    //启动方式
    @EnableEurekaClient
    @EnableHystrix
    @SpringBootApplication
    public class MemApp {
    
    	public static void main(String[] args) {
    		SpringApplication.run(OrderApp.class, args);
    	}

    Fegin方式调用

        yml文件新增配置

    feign:

       hystrix:

         enabled: true

        注册一个继承了Fegin接口的类到Spring容器中

    @Component
    public class MemberFeignService implements orderFeign {
    
    	public String errorMsg {
    		return "出错啦";
    	}
    }
    
    @Service
    @FeignClient("order-server",fallback=MemberFeignService.class)
    public interface orderFeign {
        @RequestMapping("/orderTest")
        public String orderTest();
    }

      所以整个流程就是并发访问量太大导致服务雪崩。然后出发PRC的熔断机制。最后会根据情况来进行隔离资源。

    展开全文
  • Spring Cloud 从入门到精通

    万次阅读 多人点赞 2019-07-16 13:08:54
    Spring Cloud 是一套完整的微服务解决方案,基于 Spring Boot 框架,准确的说,它不是一个框架,而是一个大的容器,它将市面上较好的微服务框架集成进来,从而简化了开发者的代码量。 本课程由浅入深带领大家一步步...

    Spring Cloud 是一套完整的微服务解决方案,基于 Spring Boot 框架,准确的说,它不是一个框架,而是一个大的容器,它将市面上较好的微服务框架集成进来,从而简化了开发者的代码量。

    本课程由浅入深带领大家一步步攻克 Spring Cloud 各大模块,接着通过一个实例带领大家了解大型分布式微服务架构的搭建过程,最后深入源码加深对它的了解。

    本课程共分为四个部分:

    第一部分(第1-3课),初识 Spring Boot,掌握 Spring Boot 基础知识,为后续入门 Spring Cloud 打好基础 。

    第二部分(第4-13课),Spring Cloud 入门篇,主要介绍 Spring Cloud 常用模块,包括服务发现、服务注册、配置中心、链路追踪、异常处理等。

    第三部分(第14-18课),Spring Cloud 进阶篇,介绍大型分布式系统中事务处理、线程安全等问题,并以一个实例项目手把手教大家搭建完整的微服务系统。

    第四部分(第19-20课),Spring Cloud 高级篇,解析 Spring Cloud 源码,并讲解如何部署基于 Spring Cloud 的大型分布式系统。

    点击购买,可阅读课程全文

    作者介绍

    李熠,从事 Java 后端开发 6 年,现任职某大型互联网公司,担任 Java 高级开发工程师,CSDN 博客专家,全栈工程师。

    课程内容

    导读:什么是 Spring Cloud 及应用现状

    Spring Cloud 是什么?

    在学习本课程之前,读者有必要先了解一下 Spring Cloud。

    Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性简化了分布式系统的开发,比如服务发现、服务网关、服务路由、链路追踪等。Spring Cloud 并不重复造轮子,而是将市面上开发得比较好的模块集成进去,进行封装,从而减少了各模块的开发成本。换句话说:Spring Cloud 提供了构建分布式系统所需的“全家桶”。

    Spring Cloud 现状

    目前,国内使用 Spring Cloud 技术的公司并不多见,不是因为 Spring Cloud 不好,主要原因有以下几点:

    1. Spring Cloud 中文文档较少,出现问题网上没有太多的解决方案。
    2. 国内创业型公司技术老大大多是阿里系员工,而阿里系多采用 Dubbo 来构建微服务架构。
    3. 大型公司基本都有自己的分布式解决方案,而中小型公司的架构很多用不上微服务,所以没有采用 Spring Cloud 的必要性。

    但是,微服务架构是一个趋势,而 Spring Cloud 是微服务解决方案的佼佼者,这也是作者写本系列课程的意义所在。

    Spring Cloud 优缺点

    其主要优点有:

    1. 集大成者,Spring Cloud 包含了微服务架构的方方面面。
    2. 约定优于配置,基于注解,没有配置文件。
    3. 轻量级组件,Spring Cloud 整合的组件大多比较轻量级,且都是各自领域的佼佼者。
    4. 开发简便,Spring Cloud 对各个组件进行了大量的封装,从而简化了开发。
    5. 开发灵活,Spring Cloud 的组件都是解耦的,开发人员可以灵活按需选择组件。

    接下来,我们看下它的缺点:

    1. 项目结构复杂,每一个组件或者每一个服务都需要创建一个项目。
    2. 部署门槛高,项目部署需要配合 Docker 等容器技术进行集群部署,而要想深入了解 Docker,学习成本高。

    Spring Cloud 的优势是显而易见的。因此对于想研究微服务架构的同学来说,学习 Spring Cloud 是一个不错的选择。

    Spring Cloud 和 Dubbo 对比

    Dubbo 只是实现了服务治理,而 Spring Cloud 实现了微服务架构的方方面面,服务治理只是其中的一个方面。下面通过一张图对其进行比较:

    这里写图片描述

    (图片引自:程序猿DD,作者:翟永超)

    可以看出,Spring Cloud 比较全面,而 Dubbo 由于只实现了服务治理,需要集成其他模块,需要单独引入,增加了学习成本和集成成本。

    Spring Cloud 学习

    Spring Cloud 基于 Spring Boot,因此在研究 Spring Cloud 之前,本课程会首先介绍 Spring Boot 的用法,方便后续 Spring Cloud 的学习。

    本课程不会讲解 Spring MVC 的用法,因此学习本课程需要读者对 Spring 及 Spring MVC 有过研究。

    本课程共分为四个部分:

    • 第一部分初识 Spring Boot,掌握 Spring Boot 基础知识,为后续入门 Spring Cloud 打好基础 。

    • 第二部分 Spring Cloud 入门篇,主要介绍 Spring Cloud 常用模块,包括服务发现、服务注册、配置中心、链路追踪、异常处理等。

    • 第三部分 Spring Cloud 进阶篇,介绍大型分布式系统中事务处理、线程安全等问题,并以一个实例项目手把手教大家搭建完整的微服务系统。

    • 第四部分 Spring Cloud 高级篇,解析 Spring Cloud 源码,并讲解如何部署基于 Spring Cloud 的大型分布式系统。

    本课程的所有示例代码均可在:https://github.com/lynnlovemin/SpringCloudLesson 下载。

    第01课:Spring Boot 入门

    什么是 Spring Boot

    Spring Boot 是由 Pivotal 团队提供的基于 Spring 的全新框架,其设计目的是为了简化 Spring 应用的搭建和开发过程。该框架遵循“约定大于配置”原则,采用特定的方式进行配置,从而使开发者无需定义大量的 XML 配置。通过这种方式,Spring Boot 致力于在蓬勃发展的快速应用开发领域成为领导者。

    Spring Boot 并不重复造轮子,而且在原有 Spring 的框架基础上封装了一层,并且它集成了一些类库,用于简化开发。换句话说,Spring Boot 就是一个大容器。

    下面几张图展示了官网上提供的 Spring Boot 所集成的所有类库:

    这里写图片描述

    这里写图片描述

    这里写图片描述

    Spring Boot 官方推荐使用 Maven 或 Gradle 来构建项目,本教程采用 Maven。

    第一个 Spring Boot 项目

    大多数教程都是以 Hello World 入门,本教程也不例外,接下来,我们就来搭建一个最简单的 Spring Boot 项目。

    1.创建一个 Maven 工程,请看下图:

    这里写图片描述

    2.在 pom.xml 加入 Spring Boot 依赖:

    <parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>2.1.3.RELEASE</version></parent><dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency></dependencies>

    3.创建应用程序启动类 DemoApplication,并编写以下代码:

    import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class DemoApplication {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }}

    4.创建一个 Controller 类 HelloController,用以测试我们的第一个基于 Spring Boot 的 Web 应用:

    import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class HelloController {    @RequestMapping("hello")    String hello() {        return "Hello World!";    }}

    5.运行 DemoApplication 类中的 main 方法,看到下图所示内容说明应用启动成功:

    enter image description here

    6.浏览器访问:http://localhost:8080/hello,则会看到下图所示界面:

    这里写图片描述

    我们可以注意到,没有写任何的配置文件,更没有显示使用任何容器,只需要启动 Main 方法即可开启 Web 服务,从而访问到 HelloController 类里定义的路由地址。

    这就是 Spring Boot 的强大之处,它默认集成了 Tomcat 容器,通过 Main 方法编写的 SpringApplication.run 方法即可启动内置 Tomcat。

    它是如何启动的,内部又是如何运行的呢?具体原理我将在《第03课:Spring Boot 启动原理》一节中具体分析。

    在上面的示例中,我们没有定义应用程序启动端口,可以看到控制台,它开启了 8080 端口,这是 Spring Boot 默认的启动端口。Spring Boot 提供了默认的配置,我们也可以改变这些配置,具体方法将在后面介绍。

    我们在启动类里加入 @SpringBootApplication 注解,则这个类就是整个应用程序的启动类。如果不加这个注解,启动程序将会报错,读者可以尝试一下。

    Spring Boot 还有一个特点是利用注解代码繁琐的 XML 配置,整个应用程序只有一个入口配置文件,那就是 application.yml 或 application.properties。接下来,我将介绍其配置文件的用法。

    properties 和 yaml

    在前面的示例代码中,我们并没有看到该配置文件,那是因为 Spring Boot 对每个配置项都有默认值。当然,我们也可以添加配置文件,用以覆盖其默认值,这里以 .properties 文件为例,首先在 resources 下新建一个名为 application.properties(注意:文件名必须是 application)的文件,键入内容为:

    server.port=8081server.servlet.context-path=/api

    并且启动 Main 方法,这时程序请求地址则变成了:http://localhost:8081/api/hello。

    Spring Boot 支持 properties 和 yaml 两种格式的文件,文件名分别对应 application.properties 和 application.yml,下面贴出 yaml 文件格式供大家参考:

    server:  port: 8080  servlet:    context-path: /api

    可以看出 properties 是以逗号隔开,而 yaml 则换行+ 两个空格 隔开,这里需要注意的是冒号后面必须空格,否则会报错。yaml 文件格式更清晰,更易读,这里作者建议大家都采用 yaml 文件来配置。

    以上示例只是小试牛刀,更多的配置将在后面的课程中讲解。

    本教程的所有配置均采用 yaml 文件。

    打包、运行

    Spring Boot 打包分为 war 和 jar 两个格式,下面将分别演示如何构建这两种格式的启动包。

    在 pom.xml 加入如下配置:

    <packaging>war</packaging><build>    <finalName>api</finalName>    <resources>        <resource>            <directory>src/main/resources</directory>            <filtering>true</filtering>        </resource>    </resources>    <plugins>        <plugin>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-maven-plugin</artifactId>        </plugin>        <plugin>            <artifactId>maven-resources-plugin</artifactId>            <version>2.5</version>            <configuration>                <encoding>UTF-8</encoding>            </configuration>        </plugin>        <plugin>            <groupId>org.apache.maven.plugins</groupId>            <artifactId>maven-surefire-plugin</artifactId>            <version>2.18.1</version>            <configuration>                <skipTests>true</skipTests>            </configuration>        </plugin>    </plugins></build>

    这个时候运行 mvn package 就会生成 war 包,然后放到 Tomcat 当中就能启动,但是我们单纯这样配置在 Tomcat 是不能成功运行的,会报错,需要通过编码指定 Tomcat 容器启动,修改 DemoApplication 类:

    import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.builder.SpringApplicationBuilder;import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;@SpringBootApplicationpublic class DemoApplication extends SpringBootServletInitializer {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }    @Override    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {        return application.sources(DemoApplication.class);    }}

    这时再打包放到 Tomcat,启动就不会报错了。

    在上述代码中,DemoApplication 类继承了 SpringBootServletInitializer,并重写 configure 方法,目的是告诉外部 Tomcat,启动时执行该方法,然后在该方法体内指定应用程序入口为 DemoApplication 类,如果通过外部 Tomcat 启动 Spring Boot 应用,则其配置文件设置的端口和 contextPath 是无效的。这时,应用程序的启动端口即是 Tomcat 的启动端口,contextPath 和 war 包的文件名相同。

    接下来我们继续看如果达成 jar 包,在 pom.xml 加入如下配置:

    <!-- 需要将包类型改为 jar 包 --><packaging>jar</packaging><build>    <finalName>api</finalName>    <resources>        <resource>            <directory>src/main/resources</directory>            <filtering>true</filtering>        </resource>    </resources>    <plugins>        <plugin>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-maven-plugin</artifactId>            <configuration>                <fork>true</fork>                <!-- 指定 Main 方法所在类 -->                <mainClass>com.lynn.DemoApplication</mainClass>            </configuration>            <executions>                <execution>                    <goals>                        <goal>repackage</goal>                    </goals>                </execution>            </executions>        </plugin>        <plugin>            <artifactId>maven-resources-plugin</artifactId>            <version>2.5</version>            <configuration>                <encoding>UTF-8</encoding>                <useDefaultDelimiters>true</useDefaultDelimiters>            </configuration>        </plugin>        <plugin>            <groupId>org.apache.maven.plugins</groupId>            <artifactId>maven-surefire-plugin</artifactId>            <version>2.18.1</version>            <configuration>                <skipTests>true</skipTests>            </configuration>        </plugin>        <plugin>            <groupId>org.apache.maven.plugins</groupId>            <artifactId>maven-compiler-plugin</artifactId>            <version>2.3.2</version>            <configuration>                <source>1.8</source>                <target>1.8</target>            </configuration>        </plugin>    </plugins></build>

    然后通过 mvn package 打包,最后通过 java 命令启动:

    java -jar api.jar

    如果是 Linux 服务器,上述命令是前台进程,点击 Ctrl+C 进程就会停止,可以考虑用 nohup 命令开启守护进程,这样应用程序才不会自动停止。

    这样,最简单的 Spring Boot 就完成了,但是对于一个大型项目,这是远远不够的,Spring Boot 的详细操作可以参照官网

    下面展示一个最基础的企业级 Spring Boot 项目的结构:

    这里写图片描述

    其中,Application.java 是程序的启动类,Startup.java 是程序启动完成前执行的类,WebConfig.java 是配置类,所有 Bean 注入、配置、拦截器注入等都放在这个类里面。

    以上实例只是最简单的 Spring Boot 项目入门实例,后面会深入研究 Spring Boot。

    第02课:Spring Boot 进阶

    上一篇带领大家初步了解了如何使用 Spring Boot 搭建框架,通过 Spring Boot 和传统的 SpringMVC 架构的对比,我们清晰地发现 Spring Boot 的好处,它使我们的代码更加简单,结构更加清晰。

    从这一篇开始,我将带领大家更加深入的认识 Spring Boot,将 Spring Boot 涉及到东西进行拆解,从而了解 Spring Boot 的方方面面。学完本文后,读者可以基于 Spring Boot 搭建更加复杂的系统框架。

    我们知道,Spring Boot 是一个大容器,它将很多第三方框架都进行了集成,我们在实际项目中用到哪个模块,再引入哪个模块。比如我们项目中的持久化框架用 MyBatis,则在 pom.xml 添加如下依赖:

    <dependency>            <groupId>org.mybatis.spring.boot</groupId>            <artifactId>mybatis-spring-boot-starter</artifactId>            <version>1.1.1</version>        </dependency>        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>5.1.40</version>        </dependency>

    yaml/properties 文件

    我们知道整个 Spring Boot 项目只有一个配置文件,那就是 application.yml,Spring Boot 在启动时,就会从 application.yml 中读取配置信息,并加载到内存中。上一篇我们只是粗略的列举了几个配置项,其实 Spring Boot 的配置项是很多的,本文我们将学习在实际项目中常用的配置项(注:为了方便说明,配置项均以 properties 文件的格式写出,后续的实际配置都会写成 yaml 格式)。

    配置项 说明 举例
    server.port 应用程序启动端口 server.port=8080,定义应用程序启动端口为 8080
    server.servlet.context-path 应用程序上下文 server.servlet.context-path=/api,则访问地址为:http://ip:port/api
    spring.servlet.multipart.maxFileSize 最大文件上传大小,-1为不限制 spring.servlet.multipart.maxFileSize=-1
    spring.jpa.database 数据库类型 spring.jpa.database=MYSQL,指定数据库为mysql
    spring.jpa.properties.hibernate.dialect hql方言 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
    spring.datasource.url 数据库连接字符串 spring.datasource.url=jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF-8&useSSL=true
    spring.datasource.username 数据库用户名 spring.datasource.username=root
    spring.datasource.password 数据库密码 spring.datasource.password=root
    spring.datasource.driverClassName 数据库驱动 spring.datasource.driverClassName=com.mysql.jdbc.Driver
    spring.jpa.showSql 控制台是否打印 SQL 语句 spring.jpa.showSql=true

    下面是我参与的某个项目的 application.yml 配置文件内容:

    server:  port: 8080  servlet:    context-path: /api  tomcat:    basedir: /data/tmp    max-threads: 1000    min-spare-threads: 50  connection-timeout: 5000spring:  profiles:    active: dev  servlet:    multipart:      maxFileSize: -1  datasource:    url: jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF-8&useSSL=true    username: root    password: root    driverClassName: com.mysql.jdbc.Driver  jpa:    database: MYSQL    showSql: true    hibernate:      namingStrategy: org.hibernate.cfg.ImprovedNamingStrategy    properties:      hibernate:        dialect: org.hibernate.dialect.MySQL5Dialectmybatis:  configuration:     #配置项:开启下划线到驼峰的自动转换. 作用:将数据库字段根据驼峰规则自动注入到对象属性     map-underscore-to-camel-case: true

    以上列举了常用的配置项,所有配置项信息都可以在官网中找到,本课程就不一一列举了。

    多环境配置

    在一个企业级系统中,我们可能会遇到这样一个问题:开发时使用开发环境,测试时使用测试环境,上线时使用生产环境。每个环境的配置都可能不一样,比如开发环境的数据库是本地地址,而测试环境的数据库是测试地址。那我们在打包的时候如何生成不同环境的包呢?

    这里的解决方案有很多:

    1. 每次编译之前手动把所有配置信息修改成当前运行的环境信息。这种方式导致每次都需要修改,相当麻烦,也容易出错。
    2. 利用 Maven,在 pom.xml 里配置多个环境,每次编译之前将 settings.xml 里面修改成当前要编译的环境 ID。这种方式会事先设置好所有环境,缺点就是每次也需要手动指定环境,如果环境指定错误,发布时是不知道的。
    3. 第三种方案就是本文重点介绍的,也是作者强烈推荐的方式。

    首先,创建 application.yml 文件,在里面添加如下内容:

    spring:  profiles:    active: dev

    含义是指定当前项目的默认环境为 dev,即项目启动时如果不指定任何环境,Spring Boot 会自动从 dev 环境文件中读取配置信息。我们可以将不同环境都共同的配置信息写到这个文件中。

    然后创建多环境配置文件,文件名的格式为:application-{profile}.yml,其中,{profile} 替换为环境名字,如 application-dev.yml,我们可以在其中添加当前环境的配置信息,如添加数据源:

    spring:  datasource:    url: jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF-8&useSSL=true    username: root    password: root    driverClassName: com.mysql.jdbc.Driver

    这样,我们就实现了多环境的配置,每次编译打包我们无需修改任何东西,编译为 jar 文件后,运行命令:

    java -jar api.jar --spring.profiles.active=dev

    其中 --spring.profiles.active 就是我们要指定的环境。

    常用注解

    我们知道,Spring Boot 主要采用注解的方式,在《第01课:Spring Boot 入门》一节的入门实例中,我们也用到了一些注解。

    本文,我将详细介绍在实际项目中常用的注解。

    @SpringBootApplication

    我们可以注意到 Spring Boot 支持 main 方法启动,在我们需要启动的主类中加入此注解,告诉 Spring Boot,这个类是程序的入口。如:

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

    如果不加这个注解,程序是无法启动的。

    我们查看下 SpringBootApplication 的源码,源码如下:

    @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = {        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication {    /**     * Exclude specific auto-configuration classes such that they will never be applied.     * @return the classes to exclude     */    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")    Class<?>[] exclude() default {};    /**     * Exclude specific auto-configuration class names such that they will never be     * applied.     * @return the class names to exclude     * @since 1.3.0     */    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")    String[] excludeName() default {};    /**     * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}     * for a type-safe alternative to String-based package names.     * @return base packages to scan     * @since 1.3.0     */    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")    String[] scanBasePackages() default {};    /**     * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to     * scan for annotated components. The package of each class specified will be scanned.     * <p>     * Consider creating a special no-op marker class or interface in each package that     * serves no purpose other than being referenced by this attribute.     * @return base packages to scan     * @since 1.3.0     */    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")    Class<?>[] scanBasePackageClasses() default {};}

    在这个注解类上有 3 个注解,如下:

    @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = {        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

    因此,我们可以用这三个注解代替 SpringBootApplication,如:

    @SpringBootConfiguration@EnableAutoConfiguration@ComponentScanpublic class Application {    public static void main(String[] args) {        SpringApplication.run(Application.class, args);    }}

    其中,SpringBootConfiguration 表示 Spring Boot 的配置注解,EnableAutoConfiguration 表示自动配置,ComponentScan 表示 Spring Boot 扫描 Bean 的规则,比如扫描哪些包。

    @Configuration

    加入了这个注解的类被认为是 Spring Boot 的配置类,我们知道可以在 application.yml 设置一些配置,也可以通过代码设置配置。

    如果我们要通过代码设置配置,就必须在这个类上标注 Configuration 注解。如下代码:

    @Configurationpublic class WebConfig extends WebMvcConfigurationSupport{    @Override    protected void addInterceptors(InterceptorRegistry registry) {        super.addInterceptors(registry);        registry.addInterceptor(new ApiInterceptor());    }}

    不过 Spring Boot 官方推荐 Spring Boot 项目用 SpringBootConfiguration 来代替 Configuration。

    @Bean

    这个注解是方法级别上的注解,主要添加在 @Configuration@SpringBootConfiguration 注解的类,有时也可以添加在 @Component 注解的类。它的作用是定义一个Bean。

    请看下面代码:

        @Bean    public ApiInterceptor interceptor(){        return new ApiInterceptor();    }

    那么,我们可以在 ApiInterceptor 里面注入其他 Bean,也可以在其他 Bean 注入这个类。

    @Value

    通常情况下,我们需要定义一些全局变量,都会想到的方法是定义一个 public static 变量,在需要时调用,是否有其他更好的方案呢?答案是肯定的。下面请看代码:

        @Value("${server.port}")    String port;    @RequestMapping("/hello")    public String home(String name) {        return "hi "+name+",i am from port:" +port;    }

    其中,server.port 就是我们在 application.yml 里面定义的属性,我们可以自定义任意属性名,通过 @Value 注解就可以将其取出来。

    它的好处不言而喻:

    1. 定义在配置文件里,变量发生变化,无需修改代码。
    2. 变量交给Spring来管理,性能更好。

    注: 本课程默认针对于对 SpringMVC 有所了解的读者,Spring Boot 本身基于 Spring 开发的,因此,本文不再讲解其他 Spring 的注解。

    注入任何类

    本节通过一个实际的例子来讲解如何注入一个普通类,并且说明这样做的好处。

    假设一个需求是这样的:项目要求使用阿里云的 OSS 进行文件上传。

    我们知道,一个项目一般会分为开发环境、测试环境和生产环境。OSS 文件上传一般有如下几个参数:appKey、appSecret、bucket、endpoint 等。不同环境的参数都可能不一样,这样便于区分。按照传统的做法,我们在代码里设置这些参数,这样做的话,每次发布不同的环境包都需要手动修改代码。

    这个时候,我们就可以考虑将这些参数定义到配置文件里面,通过前面提到的 @Value 注解取出来,再通过 @Bean 将其定义为一个 Bean,这时我们只需要在需要使用的地方注入该 Bean 即可。

    首先在 application.yml 加入如下内容:

    oss:  appKey: 1  appSecret: 1  bucket: lynn  endPoint: https://www.aliyun.com

    其次创建一个普通类:

    public class Aliyun {    private String appKey;    private String appSecret;    private String bucket;    private String endPoint;    public static class Builder{        private String appKey;        private String appSecret;        private String bucket;        private String endPoint;        public Builder setAppKey(String appKey){            this.appKey = appKey;            return this;        }        public Builder setAppSecret(String appSecret){            this.appSecret = appSecret;            return this;        }        public Builder setBucket(String bucket){            this.bucket = bucket;            return this;        }        public Builder setEndPoint(String endPoint){            this.endPoint = endPoint;            return this;        }        public Aliyun build(){            return new Aliyun(this);        }    }    public static Builder options(){        return new Aliyun.Builder();    }    private Aliyun(Builder builder){        this.appKey = builder.appKey;        this.appSecret = builder.appSecret;        this.bucket = builder.bucket;        this.endPoint = builder.endPoint;    }    public String getAppKey() {        return appKey;    }    public String getAppSecret() {        return appSecret;    }    public String getBucket() {        return bucket;    }    public String getEndPoint() {        return endPoint;    }}

    然后在 @SpringBootConfiguration 注解的类添加如下代码:

        @Value("${oss.appKey}")    private String appKey;    @Value("${oss.appSecret}")    private String appSecret;    @Value("${oss.bucket}")    private String bucket;    @Value("${oss.endPoint}")    private String endPoint;    @Bean    public Aliyun aliyun(){        return Aliyun.options()                .setAppKey(appKey)                .setAppSecret(appSecret)                .setBucket(bucket)                .setEndPoint(endPoint)                .build();    }

    最后在需要的地方注入这个 Bean 即可:

        @Autowired    private Aliyun aliyun;

    以上代码其实并不完美,如果增加一个属性,就需要在 Aliyun 类新增一个字段,还需要在 Configuration 类里注入它,扩展性不好,那么我们有没有更好的方式呢?

    答案是肯定的,我们可以利用 ConfigurationProperties 注解更轻松地将配置信息注入到实体中。

    1.创建实体类 AliyunAuto,并编写以下代码:

    import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;@Component@ConfigurationProperties(prefix = "oss")public class AliyunAuto {    private String appKey;    private String appSecret;    private String bucket;    private String endPoint;    public String getAppKey() {        return appKey;    }    public void setAppKey(String appKey) {        this.appKey = appKey;    }    public String getAppSecret() {        return appSecret;    }    public void setAppSecret(String appSecret) {        this.appSecret = appSecret;    }    public String getBucket() {        return bucket;    }    public void setBucket(String bucket) {        this.bucket = bucket;    }    public String getEndPoint() {        return endPoint;    }    public void setEndPoint(String endPoint) {        this.endPoint = endPoint;    }    @Override    public String toString() {        return "AliyunAuto{" +                "appKey='" + appKey + '\'' +                ", appSecret='" + appSecret + '\'' +                ", bucket='" + bucket + '\'' +                ", endPoint='" + endPoint + '\'' +                '}';    }}

    其中,ConfigurationProperties 指定配置文件的前缀属性,实体具体字段和配置文件字段名一致,如在上述代码中,字段为 appKey,则自动获取 oss.appKey 的值,将其映射到 appKey 字段中,这样就完成了自动的注入。如果我们增加一个属性,则只需要修改 Bean 和配置文件即可,不用显示注入。

    拦截器

    我们在提供 API 的时候,经常需要对 API 进行统一的拦截,比如进行接口的安全性校验。

    本节,我会讲解 Spring Boot 是如何进行拦截器设置的,请看接下来的代码。

    创建一个拦截器类:ApiInterceptor,并实现 HandlerInterceptor 接口:

    public class ApiInterceptor implements HandlerInterceptor {    //请求之前    @Override    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {        System.out.println("进入拦截器");        return true;    }    //请求时    @Override    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {    }    //请求完成    @Override    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {    }}

    @SpringBootConfiguration 注解的类继承 WebMvcConfigurationSupport 类,并重写 addInterceptors 方法,将 ApiInterceptor 拦截器类添加进去,代码如下:

    @SpringBootConfigurationpublic class WebConfig extends WebMvcConfigurationSupport{    @Override    protected void addInterceptors(InterceptorRegistry registry) {        super.addInterceptors(registry);        registry.addInterceptor(new ApiInterceptor());    }}

    异常处理

    我们在 Controller 里提供接口,通常需要捕捉异常,并进行友好提示,否则一旦出错,界面上就会显示报错信息,给用户一种不好的体验。最简单的做法就是每个方法都使用 try catch 进行捕捉,报错后,则在 catch 里面设置友好的报错提示。如果方法很多,每个都需要 try catch,代码会显得臃肿,写起来也比较麻烦。

    我们可不可以提供一个公共的入口进行统一的异常处理呢?当然可以。方法很多,这里我们通过 Spring 的 AOP 特性就可以很方便的实现异常的统一处理。实现方法很简单,只需要在 Controller 类添加以下代码即可。

        @ExceptionHandler    public String doError(Exception ex) throws Exception{        ex.printStackTrace();        return ex.getMessage();    }

    其中,在 doError 方法上加入 @ExceptionHandler 注解即可,这样,接口发生异常会自动调用该方法。

    这样,我们无需每个方法都添加 try catch,一旦报错,则会执行 handleThrowing 方法。

    优雅的输入合法性校验

    为了接口的健壮性,我们通常除了客户端进行输入合法性校验外,在 Controller 的方法里,我们也需要对参数进行合法性校验,传统的做法是每个方法的参数都做一遍判断,这种方式和上一节讲的异常处理一个道理,不太优雅,也不易维护。

    其实,SpringMVC 提供了验证接口,下面请看代码:

    @GetMapping("authorize")public void authorize(@Valid AuthorizeIn authorize, BindingResult ret){    if(result.hasFieldErrors()){            List<FieldError> errorList = result.getFieldErrors();            //通过断言抛出参数不合法的异常            errorList.stream().forEach(item -> Assert.isTrue(false,item.getDefaultMessage()));        }}public class AuthorizeIn extends BaseModel{    @NotBlank(message = "缺少response_type参数")    private String responseType;    @NotBlank(message = "缺少client_id参数")    private String ClientId;    private String state;    @NotBlank(message = "缺少redirect_uri参数")    private String redirectUri;    public String getResponseType() {        return responseType;    }    public void setResponseType(String responseType) {        this.responseType = responseType;    }    public String getClientId() {        return ClientId;    }    public void setClientId(String clientId) {        ClientId = clientId;    }    public String getState() {        return state;    }    public void setState(String state) {        this.state = state;    }    public String getRedirectUri() {        return redirectUri;    }    public void setRedirectUri(String redirectUri) {        this.redirectUri = redirectUri;    }}

    我们再把验证的代码单独封装成方法:

    protected void validate(BindingResult result){        if(result.hasFieldErrors()){            List<FieldError> errorList = result.getFieldErrors();            errorList.stream().forEach(item -> Assert.isTrue(false,item.getDefaultMessage()));        }    }

    这样每次参数校验只需要调用 validate 方法就行了,我们可以看到代码的可读性也大大提高了。

    接口版本控制

    一个系统上线后会不断迭代更新,需求也会不断变化,有可能接口的参数也会发生变化,如果在原有的参数上直接修改,可能会影响线上系统的正常运行,这时我们就需要设置不同的版本,这样即使参数发生变化,由于老版本没有变化,因此不会影响上线系统的运行。

    一般我们可以在地址上带上版本号,也可以在参数上带上版本号,还可以再 header 里带上版本号,这里我们在地址上带上版本号,大致的地址如:http://api.example.com/v1/test,其中,v1 即代表的是版本号。具体做法请看代码:

    @Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Mappingpublic @interface ApiVersion {    /**     * 标识版本号     * @return     */    int value();}public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {    // 路径中版本的前缀, 这里用 /v[1-9]/的形式    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");    private int apiVersion;    public ApiVersionCondition(int apiVersion){        this.apiVersion = apiVersion;    }    @Override    public ApiVersionCondition combine(ApiVersionCondition other) {        // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义        return new ApiVersionCondition(other.getApiVersion());    }    @Override    public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {        Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());        if(m.find()){            Integer version = Integer.valueOf(m.group(1));            if(version >= this.apiVersion)            {                return this;            }        }        return null;    }    @Override    public int compareTo(ApiVersionCondition other, HttpServletRequest request) {        // 优先匹配最新的版本号        return other.getApiVersion() - this.apiVersion;    }    public int getApiVersion() {        return apiVersion;    }}public class CustomRequestMappingHandlerMapping extends        RequestMappingHandlerMapping {    @Override    protected RequestCondition<ApiVersionCondition> getCustomTypeCondition(Class<?> handlerType) {        ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);        return createCondition(apiVersion);    }    @Override    protected RequestCondition<ApiVersionCondition> getCustomMethodCondition(Method method) {        ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);        return createCondition(apiVersion);    }    private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) {        return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());    }}@SpringBootConfigurationpublic class WebConfig extends WebMvcConfigurationSupport {    @Bean    public AuthInterceptor interceptor(){        return new AuthInterceptor();    }    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(new AuthInterceptor());    }    @Override    @Bean    public RequestMappingHandlerMapping requestMappingHandlerMapping() {        RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();        handlerMapping.setOrder(0);        handlerMapping.setInterceptors(getInterceptors());        return handlerMapping;    }}

    Controller 类的接口定义如下:

    @ApiVersion(1)@RequestMapping("{version}/dd")public class HelloController{}

    这样我们就实现了版本控制,如果增加了一个版本,则创建一个新的 Controller,方法名一致,ApiVersion 设置为2,则地址中 v1 会找到 ApiVersion 为1的方法,v2 会找到 ApiVersion 为2的方法。

    自定义 JSON 解析

    Spring Boot 中 RestController 返回的字符串默认使用 Jackson 引擎,它也提供了工厂类,我们可以自定义 JSON 引擎,本节实例我们将 JSON 引擎替换为 fastJSON,首先需要引入 fastJSON:

    <dependency>            <groupId>com.alibaba</groupId>            <artifactId>fastjson</artifactId>            <version>${fastjson.version}</version>        </dependency>

    其次,在 WebConfig 类重写 configureMessageConverters 方法:

    @Override    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {        super.configureMessageConverters(converters);        /*        1.需要先定义一个 convert 转换消息的对象;        2.添加 fastjson 的配置信息,比如是否要格式化返回的 JSON 数据        3.在 convert 中添加配置信息        4.将 convert 添加到 converters 中         */        //1.定义一个 convert 转换消息对象        FastJsonHttpMessageConverter fastConverter=new FastJsonHttpMessageConverter();        //2.添加 fastjson 的配置信息,比如是否要格式化返回 JSON 数据        FastJsonConfig fastJsonConfig=new FastJsonConfig();        fastJsonConfig.setSerializerFeatures(                SerializerFeature.PrettyFormat        );        fastConverter.setFastJsonConfig(fastJsonConfig);        converters.add(fastConverter);    }

    单元测试

    Spring Boot 的单元测试很简单,直接看代码:

    @SpringBootTest(classes = DemoApplication.class)@RunWith(SpringJUnit4ClassRunner.class)public class TestDB {    @Test    public void test(){    }}

    模板引擎

    在传统的 SpringMVC 架构中,我们一般将 JSP、HTML 页面放到 webapps 目录下面,但是 Spring Boot 没有 webapps,更没有 web.xml,如果我们要写界面的话,该如何做呢?

    Spring Boot 官方提供了几种模板引擎:FreeMarker、Velocity、Thymeleaf、Groovy、mustache、JSP。

    这里以 FreeMarker 为例讲解 Spring Boot 的使用。

    首先引入 FreeMarker 依赖:

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

    在 resources 下面建立两个目录:static 和 templates,如图所示:

    这里写图片描述

    其中 static 目录用于存放静态资源,譬如 CSS、JS、HTML 等,templates 目录存放模板引擎文件,我们可以在 templates 下面创建一个文件:index.ftl(freemarker 默认后缀为 .ftl),并添加内容:

    <!DOCTYPE html><html>    <head>    </head>    <body>        <h1>Hello World!</h1>    </body></html>

    然后创建 PageController 并添加内容:

    @Controllerpublic class PageController {    @RequestMapping("index.html")    public String index(){        return "index";    }}

    启动 Application.java,访问:http://localhost:8081/api/index.html,就可以看到如图所示:

    enter image description here

    第03课:Spring Boot 启动原理第04课:初识 Spring Cloud
    第05课:服务注册与发现
    第06课:服务网关
    第07课:服务消费者
    第08课:服务异常处理
    第09课:配置中心
    第10课:消息总线
    第11课:服务链路追踪
    第12课:分布式锁
    第13课:分布式事务
    第14课:Spring Cloud 实例详解——基础框架搭建(一)
    第15课:Spring Cloud 实例详解——基础框架搭建(二)
    第16课:Spring Cloud 实例详解——基础框架搭建(三)
    第17课:Spring Cloud 实例详解——业务代码实现
    第18课:Spring Cloud 实例详解——系统发布
    第19课:Spring Cloud 源码解析
    第20课:K8S+Docker 部署 Spring Cloud 集群

    点击购买,可阅读课程全文

    展开全文
  • 史上最简单的 SpringCloud 教程 | 终章

    万次阅读 多人点赞 2019-09-30 17:39:35
    本文出自方志朋的博客 错过了这一篇,你可能再也学...Spring Boot做为下一代 web 框架,Spring Cloud 作为最新最火的微服务的翘楚,你还有什么理由拒绝。赶快上船吧,老船长带你飞。终章不是最后一篇,它是一个...

    转载请标明出处:
    http://blog.csdn.net/forezp/article/details/70148833
    本文出自方志朋的博客


    扫码关注有惊喜

    (转载本站文章请注明作者和出处 方志朋的博客

    个人博客纯净版https://www.fangzhipeng.com/spring-cloud.html

    错过了这一篇,你可能再也学不会 Spring Cloud 了!Spring Boot做为下一代 web 框架,Spring Cloud 作为最新最火的微服务的翘楚,你还有什么理由拒绝。赶快上船吧,老船长带你飞。终章不是最后一篇,它是一个汇总,未来还会写很多篇。

    我为什么这些文章?一是巩固自己的知识,二是希望有更加开放和与人分享的心态,三是接受各位大神的批评指教,有任何问题可以联系我: miles02@163.com .

    码农下载:https://git.oschina.net/forezp/SpringCloudLearning

    github下载:https://github.com/forezp/SpringCloudLearning,记得star哦!

    欢迎大家访问我的个人博客:https://www.fangzhipeng.com/

    点击获取SpringCloud 、Spring Boot视频

    《史上最简单的 SpringCloud 教程》系列:

    Spring Cloud Alibaba教程

    Greenwich版本

    Finchley版本

    Spring Cloud Finchley; Spring Boot 2.0.3

    源码篇:

    进阶篇

    D版本

    番外篇:

    怎么支持我?

    • 这个系列会持续更新,敬请关注!

    • 关注我的公众号,精彩内容不能错过!


    扫码关注有惊喜

    (转载本站文章请注明作者和出处 方志朋的博客

    展开全文
  • mango 前端安装指南 本系统前端使用Vue.js和Element框架搭建. 1、 开发环境 前端开发环境基于NPM环境,使用VS Code开发。 IDE : VS Code 1.27 或 WebStorm NODE: Node 10.15.x ...页面组件:Elem...
  • 七、springboot整合flowable(工作流)

    万次阅读 热门讨论 2020-01-20 11:07:50
    springboot整合flowable(工作流) 简介 Flowable 适用于开发人员,系统管理员和业务用户的紧凑且高效的工作流程和业务流程管理(BPM)平台。 Flowable的发布包里包含了大部分源码,以JAR文件方式提供。...
  • 搭建SpringCloud项目,并实现自动化部署

    万次阅读 多人点赞 2019-11-26 16:57:22
    项目资源:https://github.com/sunroyi/SpringCloud 主要分为以下几步: (1)搭建SpringBootService,这里是各个微服务的业务逻辑。(这里搭建了2个Service,用来测试熔断) (2)搭建SpringBootEureka,用来...
  • SpringCloud简介及使用

    万次阅读 多人点赞 2019-05-10 21:12:08
    SpringCloud简介 Spring cloud是一个基于Spring Boot实现的服务治理工具包,在微服务架构中用于管理和协调服务的 微服务:就是把一个单体项目,拆分为多个微服务,每个微服务可以独立技术选型,独立开发,独立部署,...
  • 转载请标明出处: ... 本文出自方志朋的博客 ...鉴于《史上最简单的Spring Cloud教程》很受读者欢迎,再次我特意升级了一下版本,目前支持的版本为Spring Boot版本2.0.3.RELEASE,Spring Cloud版本为F...
  • Spring Cloud Bus 将分布式的节点和轻量的消息代理连接起来。这可以用于广播配置文件的更改或者其他的管理工作。一个关键的思想就是,消息总线可以为微服务做监控,也可以作为应用程序之间相互通讯。本文要讲述的是...
  • 简介在分布式系统中,spring cloud config 提供一个服务端和客户端去提供可扩展的配置服务。我们可用用配置服务中心区集中的管理所有的服务的各种环境配置文件。配置服务中心采用git的方式存储配置文件,因此我们很...
  • SpringCloud面试题(一)

    万次阅读 多人点赞 2020-03-04 13:17:08
    SpringCloud面试题(一) 大家好,我是酷酷的韩~下面提供一些整理的springcloud面试题 一.微服务的优点缺点?说下开发项目中遇到的坑? 优点: 1.每个服务直接足够内聚,代码容易理解 2.开发效率高,一个服务只做一件事...
  • Spring Cloud原理详解

    万次阅读 多人点赞 2019-09-05 17:02:08
    毫无疑问,Spring Cloud是目前微服务架构领域的翘楚,无数的书籍博客都在讲解这个技术。不过大多数讲解还停留在对Spring Cloud功能使用的层面,其底层的很多原理,很多人可能并不知晓。因此本文将通过大量的手绘图,...
  • spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等。它运行环境简单,可以在开发人员的电脑上跑。另外说明...
  • 对于SpringCloud,很多小伙伴问到了我的研究学习资料来源,除官方文档外,特例完整整理一下自己的平时参考学习其他资料,以及分享实战项目源码和代码资源,供大家参考学习 主要教程:SpringCloud教程 Spring ...
  • Spring Cloud面试题(2020最新版)

    万次阅读 多人点赞 2020-05-06 14:12:33
    文章目录为什么需要学习Spring Cloud什么是Spring Cloud设计目标与优缺点设计目标优缺点Spring Cloud发展前景整体架构主要项目Spring Cloud ConfigSpring Cloud NetflixSpring Cloud BusSpring Cloud ConsulSpring ...
  • 比较spring cloud和dubbo,各自的优缺点是什么

    万次阅读 多人点赞 2019-10-24 21:13:18
    dubbo由于是二进制的传输,占用带宽会更少 springCloud是http协议传输,带宽...springcloud的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级 dubbo的注册中心可以选择zk,redis等多种,sp...
  • Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),之前在第一章节也介绍这些,比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring ...
  • SpringCloud之服务网关Gateway

    万次阅读 多人点赞 2020-03-15 17:19:57
    SpringCloud 是微服务中的翘楚,最佳的落地方案。 Spring Cloud Gateway 是 Spring Cloud 新推出的网关框架,之前是Netflix Zuul。网关通常在项目中为了简化 前端的调用逻辑,同时也简化内部服务之间互相调用的...
1 2 3 4 5 ... 20
收藏数 166,420
精华内容 66,568
关键字:

springcloud