【订阅专栏合集,关注公众号,作者所有付费文章都能看(持续更新)】

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

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

SpringCloud详细教程(上)

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

文章目录

  • SpringCloud详细教程(上)
    • 1.SpringCloud详细教程目录
    • 2. 开发环境说明
    • 3.细说微服务架构
      • 3.1.单体架构
      • 3.2.单体架构存在的问题
      • 3.3.什么是微服务?
      • 3.4.微服务架构的特征
      • 3.5.微服务架构示例
    • 4.SpringCloud概论
      • 4.1.简介
      • 4.2.SpringCloud子项目
      • 4.3.Springcloud版本说明
      • 4.4.SpringCloud框架特点
    • 5.使用 SpringBoot 实现微服务
      • 5.1.实现商品微服务
        • 5.1.1.创建maven工程
        • 5.1.2.导入依赖
        • 5.1.3.创建实体Item
        • 5.1.4.编写ItemService
        • 5.1.5.编写ItemController
        • 5.1.6.编写程序入口
        • 5.1.7.创建application.yml配置文件
        • 5.1.8.启动程序测试
      • 5.2.实现订单微服务
        • 5.2.1.创建工程
        • 5.2.2.导入依赖
        • 5.2.3.创建订单Order实体
        • 5.2.4.创建订单详情OrderDetail实体
        • 5.2.5.将商品微服务项目中的Item类拷贝到当前工程
        • 5.2.6.编写OrderService
        • 5.2.7.实现ItemService
        • 5.2.8.编写OrderController
        • 5.2.9.编写程序入口
        • 5.2.10.编写application.yml配置文件
        • 5.2.11.启动测试
      • 5.3.添加okHttp的支持
      • 5.4.解决订单系统中的url硬编码问题
      • 5.5.继续优化解决硬编码的问题
    • 6.SpringCloud快速入门
      • 6.1.分析硬编码的问题
      • 6.2.微服务注册与发现
      • 6.3.注册中心:Eureka
        • 6.3.1.原理
        • 6.3.2.编写Eureka Server
      • 6.4.将商品微服务注册到Eureka
      • 6.5.订单系统从Eureka中发现商品服务
    • 7.深入理解Eureka
      • 7.1.为Eureka添加用户认证
        • 7.1.1.服务注册时(client端)设置账户信息
      • 7.2.Eureka的自我保护模式
      • 7.3.Eureka的高可用(Eureka集群)
      • 7.4.将client端服务注册到Eureka高可用集群
      • 7.5.指定服务的IP地址
      • 7.6.指定实例id
    • 8.负载均衡:Ribbon
      • 8.1.Ribbon简介
      • 8.2.架构
      • 8.3.开始使用SpringCloud Ribbon
        • 8.3.1.为microservice order增加ribbon依赖
        • 8.3.2.为RestTemplate设置@LoadBalanced注解
        • 8.3.3.改造ItemService的实现
        • 8.3.4.重启订单服务进行测试
        • 8.3.5.测试负载均衡
      • 8.4.设置负载均衡策略
      • 8.5.其它策略
    • 9.容错保护:Hystrix
      • 9.1.分析
      • 9.2.雪崩效应
      • 9.3.SpringCloud Hystrix简介
      • 9.4.原理说明
      • 9.5.快速入门
        • 9.5.1.导入依赖
        • 9.5.2.修改ItemService的queryItemById方法
        • 9.5.3.在启动类OrderApplication添加@EnableHystrix注解
        • 9.5.4.在Controller增加一个入口
        • 9.5.5.重新启动进行测试
      • 9.6.定义统一的fallback接口

1.SpringCloud详细教程目录

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

2. 开发环境说明

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

3.细说微服务架构

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

3.1.单体架构

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

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

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

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

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

3.2.单体架构存在的问题



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

3.3.什么是微服务?

作者:Martin Fowler

3.4.微服务架构的特征

3.5.微服务架构示例

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

4.SpringCloud概论

4.1.简介

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

4.2.SpringCloud子项目

4.3.Springcloud版本说明

官方版本:

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

4.4.SpringCloud框架特点

5.使用 SpringBoot 实现微服务

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

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

图示:

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

5.1.实现商品微服务

5.1.1.创建maven工程


5.1.2.导入依赖

重点是导入SpringBoot的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.zpc.microservice</groupId><artifactId>microservice-item</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.6.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies><build><finalName>${project.artifactId}</finalName><plugins><!-- 资源文件拷贝插件 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-resources-plugin</artifactId><configuration><encoding>UTF-8</encoding></configuration></plugin><!-- java编译插件 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin></plugins></build></project>
5.1.3.创建实体Item
package com.zpc.item.entity;public class Item {private Long id;private String title;private String pic;private String desc;private Long price;public Item(){}public Item(long id, String title, String pic, String desc, Long price) {this.id=id;this.title=title;this.pic=pic;this.desc=desc;this.price=price;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getPic() {return pic;}public void setPic(String pic) {this.pic = pic;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}public Long getPrice() {return price;}public void setPrice(Long price) {this.price = price;}@Overridepublic 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 {@Autowiredprivate 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;}@Overridepublic 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;}@Overridepublic 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);}@Autowiredprivate 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请求做了封装,来简化操作@Autowiredprivate 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 {@Autowiredprivate 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*/@Beanpublic 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请求做了封装,来简化操作@Autowiredprivate 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请求做了封装,来简化操作@Autowiredprivate RestTemplate restTemplate;//@Value("${myspcloud.item.url}")//private String itemUrl;@AutowiredOrderProperties 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-centereureka: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请求做了封装,来简化操作@Autowiredprivate RestTemplate restTemplate;@AutowiredOrderProperties 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@LoadBalancedpublic 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-centersecurity:basic:enable: true #开启基于HTTP basic的认证user: #配置用户的账号信息name: zpcpassword: 123456eureka: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*/@Overrideprotected 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 servercom.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: falsefetch-registry: trueserver: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-centersecurity:basic:enable: true #开启基于HTTP basic的认证user: #配置用户的账号信息name: zpcpassword: 123456eureka:instance:#注册中心地址hostname: 127.0.0.1###客户端调用地址client:serviceUrl:defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:9100/eureka/
###是否将自己注册到Eureka服务中,集群的时候为trueregister-with-eureka: truefetch-registry: trueserver:enable-self-preservation: false

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

  • 端口为9100的机器注册到端口为8100的注册中心
###服务端口号
server:port: 9100###服务名称
spring:application:name: app-eureka-centersecurity:basic:enable: true #开启基于HTTP basic的认证user: #配置用户的账号信息name: zpcpassword: 123456eureka:instance:#注册中心地址hostname: 127.0.0.1
###客户端调用地址client:serviceUrl:defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:8100/eureka/
###是否将自己注册到Eureka服务中,集群的时候为trueregister-with-eureka: truefetch-registry: true
###测试环境、开发环境可以关闭自我保护机制,保证服务不可用时及时剔除server:enable-self-preservation: falseeviction-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);}@Autowiredprivate 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: trueinstance: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: trueinstance:prefer-ip-address: true #将自己的ip地址注册到Eureka服务中ip-address: 127.0.0.1instance-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@LoadBalancedpublic 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 {@Autowiredprivate LoadBalancerClient loadBalancerClient;//自动注入@Testpublic 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;@Autowiredprivate 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@LoadBalancedpublic 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 {@Autowiredprivate 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详细教程(上)相关推荐

  1. 利用Fuel-OpenStack9.0安装部署私有云平台OpenStack详细教程(上)

    本教程使用VirtualBox创建虚拟机环境,VirtualBox虚拟机软件的安装见我发布的文章< Windows 10 安装Virtualbox 6.1.22详细教程> 1.创建网络环境 ...

  2. 微服务Springcloud超详细教程+实战(二)

    微服务Springcloud超详细教程+实战(二) -------------------------------------- 远程调用方式 无论是微服务还是分布式服务(都是SOA,都是面向服务编程 ...

  3. centos llvm安装_在CentOS上编译安装llvm-3.8.1详细教程

    在CentOS上编译安装llvm-3.8.1详细教程 2020/1/11  18:12 1682次 注:CentOS版本是6.8,6.x版本的操作类似 安装需求: [list] [*]Cmake: 3 ...

  4. ubuntu15.04配置php,Linux_Ubuntu 15.04上安装Justniffer的详细教程,Justniffer 是一个可用于替代 Snor - phpStudy...

    Ubuntu 15.04上安装Justniffer的详细教程 Justniffer 是一个可用于替代 Snort 的网络协议分析器.它非常流行,可交互式地跟踪/探测一个网络连接.它能从实时环境中抓取流 ...

  5. Android连接SQLServer详细教程(数据库+服务器+客户端),并在微软Azure云上搭建云服务

    Android连接SQLServer详细教程(数据库+服务器+客户端),并在微软Azure云上搭建云服务 参考博客:http://blog.csdn.net/zhyl8157121/article/d ...

  6. iphone照片恢复至android,绝招!如何恢复苹果/安卓手机上误删的照片,详细教程奉上!...

    原标题:绝招!如何恢复苹果/安卓手机上误删的照片,详细教程奉上! 经常是怕占内存 就把手机里的照片删除了 然后就是某一天 突然反悔了想把照片找回来 或是真的不小心就把照片误删了 小伙伴们有过这种烦恼吗 ...

  7. 在安卓手机上安装Ubuntu详细教程(无需root)

    在安卓手机上安装Ubuntu详细教程(无需root)    Android系统是基于Linux的,但是要在安卓上安装Linux却没有那么容易.本文法针对安卓手机上安装Ubuntu系统提出了一种方法,安 ...

  8. SpringCloud 基础教程(八)-Hystrix熔断器(上)

      我的博客:兰陵笑笑生,欢迎浏览博客!  上一章 SpringCloud基础教程(七)-声明式服务调用Fegign当中,我们介绍了使用Fegin更加简化的实现服务间的调用.本章节我将继续探索Hyst ...

  9. 计算机怎么放映文档,当贝投影怎么局域网共享看电脑上的电影详细教程分享

    当贝投影怎么局域网共享看电脑上的电影详细教程分享 2019年07月08日 14:50作者:黄页编辑:黄页 分享 当贝投影怎么局域网共享看电脑上的电影?这话什么意思?指的是电脑通过局域网共享电影文件,然 ...

最新文章

  1. 软件隐喻的本质与模式
  2. 每天一个linux命令(18):locate 命令
  3. 杨辉三角形又称Pascal三角形,它的第i+1行是(a+b)i的展开式的系数
  4. C语言简单题-找最大的字符串
  5. vmware的3种网络模式
  6. Oracle查询和解锁表
  7. mysql主外键引用关系,关于mysql:数据主/外键关系
  8. 老板让我用少量样本 finetune 模型,我还有救吗?急急急,在线等!
  9. 微课|中学生可以这样学Python(例6.4):因数分解
  10. php生成option,php递归实现无限分类生成下拉列表的函数
  11. 跨境电子商务独立站如何找到热门的利基市场
  12. Linux grep
  13. vn的可变数据类型_可变与不可变数据类型详解
  14. 总在说思科华为认证 可你真的清楚它们的区别吗?
  15. mysql命令报错-bash: mysql: command not found
  16. chrome浏览器控制台性能监控
  17. 深度 GHOST XP SP3 装机版 2012 08
  18. VMware安装中标麒麟Linux_server_x86_ZX64(兆芯版)
  19. 观《蓝天铁翼-红旗军演》所想到的
  20. 03_STM32新建工程

热门文章

  1. 遇险哪里还有空报警?求救app告诉你,有的
  2. 鸿蒙IPC摄像机,【HiSpark IPC DIY Camera试用连载 】第二篇 视频的人脸检测
  3. dsp28335杂记1
  4. Cartopy理解变换和投影关键字
  5. ArcGIS打开影像图显示全黑色解决办法
  6. 阿里面试官:“你有高并发经验吗?”
  7. 2012MDCC中国·移动开发者大会 邀请函
  8. 区块链报告会心得体会3000_实习心得体会3000字篇【优秀篇】
  9. 信息系统项目管理师核心考点(八)软件集成技术
  10. 转载:  Lodop、C-Lodop打印控件报错