Spring Cloud ~ 从入门到入坑。
文章目录
JavaSE
数据库
前端
Servlet
Http
MyBatis
Spring
SpringMVC
SpringBoot
Dubbo、Zookeeper、分布式基础
Maven、Git
Ajax、Json
三层架构 - MVC。
框架。Spring IoC AOP。
SpringBoot 新一代的 JavaEE 开发标准。自动装配。
Spring:the source for modern java
all in one -> 模块化。代码没变化。
Dubbo ~ RPC
Spring Cloud ~ RESTful
微服务架构 4 个核心问题。
- 服务很多,客户端该怎么访问。
- 这么多服务,服务之间该怎样通信。
- 这么多服务,如何治理。
- 服务器挂了怎么办。
解决方案。
Spring Cloud 生态。
- Spring Cloud NetFlix。一站式解决方案。(2018.12. 停更)。
api 网关 ~ zuul 组件。
Feign ~ HttpClient ~ Http 通信方式,同步,阻塞。
服务注册与发现 ~ Eureka。
熔断机制 ~ Hystrix。- Apache Dubbo Zookeeper。半自动。需要整合别人的。
api ~ 没有,找第三方组件,或自己实现。
Dubbo
Zookeeper
熔断机制 ~ 没有,借助 Hystrix。
Dubbo 方案并不完善。- Spring Cloud Alibaba。全新一站式解决方案。更简单。
-
新概念 ~ 服务网格 ~ Server Mesh ~ istio。
-
万变不离其宗。
- API。
- HTTP, RPC。
- 注册与发现。
- 熔断机制。
常见面试题。
- 什么是微服务。
- 微服务之间是如何通讯的。
- SpringCloud 和 Dubbo 有哪些区别。
- SpringBoot 和 SpringCloud,请谈谈对他们的理解。
- 什么是服务熔断。什么是服务降级。
- 微服务的优缺点。说一下你在开发中遇到的坑。
- 你所知道的微服务技术栈有那些,请列举一二。
- Eureka 和 Zookeeper 都可以提供服务注册与发现功能,请说说两个的区别。
Microservices ~ 微服务。
https://www.martinfowler.com/articles/microservices.html
就目前而言,对于微服务,业界并没有一个统一的、标准的定义。
但通常而言,微服务架构是一种架构模式,或者说是一种架构风格,ta 提倡将单一的应用程序划分成一组小的服务,每个服务运行在其独立的进程内,服务之间相互协调,互相配置,为用户提供最终价值。服务之间采用清凉的通信机制互相沟通,每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境中。另外,应尽量避免同一的、集中式的服务管理机制。对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储。
==》
将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事。
从技术角度看就是一种小而独立的处理过程,类似进程概念,能够自行单独启动或销毁,拥有自己独立的数据库。
微服务与微服务架构。
-
微服务。
强调的是服务的大小。ta 关注的是某一个点,是具体解决某一个问题/提供落地对应服务的一个服务应用。狭义的看,可以看做是 IDEA 中的一个个微服务工程,或者一个 Module。 -
微服务架构。
一种新的架构形式,Martin Fowler 2014 提出。
微服务架构是一种架构模式,ta 提倡将单一的应用程序划分成一组小的服务,每个服务运行在其独立的进程内,服务之间相互协调,互相配置,为用户提供最终价值。服务之间采用清凉的通信机制互相沟通,每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境中。另外,应尽量避免同一的、集中式的服务管理机制。对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。
微服务优缺点。
- 优点。
- 单一职责原则。
- 每个服务足够内聚,足够小,代码容易理解,这样能聚焦一个指定的业务功能或业务需求。
- 开发简单,开发效率提高,一个服务可能就是专一的只干一件事。
- 微服务能够被小团队单独开发,这个小团队是 2 ~ 5 人的开发人员组成。
- 微服务是松耦合的,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的。
- 微服务能使用不同的语言开发。
- 易于和第三方集成,微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如 jenkins,Hudson,Bamboo。
- 微服务易于被一个开发人员理解,修改和维护。这样小团队能够更关注自己的工作成果。无需通过合作才能体现价值。
- 微服务允许你利用、融合最新技术。
- 微服务只是业务逻辑的代码,不会和 HTML、CSS 或其他界面混合。
- 每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一数据库。
- 缺点。
- 开发人员要处理分布式系统的复杂性。
- 多服务运维难度,随着服务的增加,运维的压力也在增大。
- 系统部署依赖。
- 服务间通讯成本。
- 数据一致性。
- 系统集成测试。
- 性能监控。
Spring Cloud 和 Spring Boot 的关系。
- Spring Boot 专注于快速方便的开发单个个体微服务。
- Spring Cloud 关注全局的微服务协调整理治理框架,ta 将 Spring Boot 开发的一个个单体微服务整合并管理起来,为各个微服务之间提供:配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等集成服务。
- Spring Boot 可以离开 Spring Cloud 独立使用,开发项目,但是 Spring Cloud 离不开 Spring Boot,属于依赖关系。
- Spring Boot 专注于快速 、方便的开发单个个体服务,Spring Cloud 关注全局的服务治理框架。
Dubbo 和 Spring Cloud 技术选型。
- 最大区别:Spring Cloud 抛弃了 Dubbo 的 RPC 通信,采用的是基于 HTTP 的 REST 方式。
严格来说,这两种方式各有优劣。虽然从一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生 RPC 带来的问题。而且 REST 相比 RPC 更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快涑演化的微服务环境下,显得更加合适。
- 品牌机与组装机的区别。
很明显,Spring Cloud 的功能比 Dubbo 更加强大,涵盖面更广,而且作为 Spring 的拳头项目,它也能够与 Spring Framework、Spring Boot、Spring Data、Spring Batch 等其他 Spring 项目完美融合,这些对于微服务而言是至关重要的。使用 Dubbo 构建的微服务架构就像组装电脑,各环节我们的选择自由度很高,但是最终结果很有可能因为一条内存质量不行就点不亮了,总是让人不怎么放心。但是如果你是一名高手,那这些都不是问题。而 Spring Cloud 就像品牌机,在 Spring Source 的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性。但是如果要在使用非原装组件外的东西,就需要对其基础有足够的了解。
- 社区支持与更新力度。
最为重要的是,Dubbo 停止了 5 年左右的更新,虽然 2017.7. 重启了。对于技术发展的新需求,需要由开发者自行拓展升级(比如当当网弄出了 DubboX),这对于很多想要采用微服务架构的中小软件组织,显然是不太合适的,中小公司没有这么强大的技术能力去修改 Dubbo 源码 - 周边的一整套解决方案,并不是每一个公司都有阿里的大牛 - 真实的线上生产环境测试过。
设计模式 - 微服务拆分思想。
- 总结。
曾风靡国内的开源 RPC 服务框架 Dubbo 在重启维护后,令许多用户为之雀跃,但同时也迎来了一些质疑的声音。互联网技术发展迅速,Dubbo 是否还能跟上时代?Dubbo 与 Spring Cloud 相比又有何优势和差异?是否会有相关举措保证 Dubbo 的后续更新频率?
人物:Dubbo 重启维护开发的刘军,主要负责人之一。
刘军,阿里巴巴中间件高级研发工程师,主导了 Dubbo 重启维护以后的几个发版计划,专注于高性能 RPC 框架和微服务相关领域。曾负责网易考拉 RPC 框架的研发及指导在内部使用,参与了服务治理平台、分布式跟踪系统、分布式一致性框架等从无到有的设计与开发过程。
解决的问题领域不一样:Dubbo 定位的是一款 RPC 框架,Spring Cloud 的目标是微服务架构下的一站式解决方案。
能干嘛。
- Features
Spring Cloud focuses on providing good out of box experience for typical use cases and extensibility mechanism to cover others.
- Distributed/versioned configuration
分布式 / 版本控制配置。- Service registration and discovery
服务注册与发现。- Routing
路由。- Service-to-service calls
服务到服务的调用。- Load balancing
负载均衡。- Circuit Breakers
断路器。- Global locks
全局锁。- Leadership election and cluster state
选举与集群状态。- Distributed messaging
分布式消息。
Spring Cloud takes a very declarative approach, and often you get a lot of features with just a classpath change and/or an annotation. Example application that is a discovery client:
@SpringBootApplication
@EnableDiscoveryClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
文档。
https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.2.2.RELEASE/reference/html/
Spring Cloud 中文社区:https://www.springcloud.cc/
Spring Cloud 中国社区:http://www.springcloud.cn/
IDEA。
CREATE SCHEMA `db01` DEFAULT CHARACTER SET utf8 ;
create table dept
(
deptno bigint auto_increment,
dname varchar(60) null,
db_source varchar(60) null,
constraint dept_pk
primary key (deptno)
)
comment '部门表。';
insert into dept (dname, db_source)
values ('开发部', database());
insert into dept (dname, db_source)
values ('人事部', database());
insert into dept (dname, db_source)
values ('财务部', database());
insert into dept (dname, db_source)
values ('市场部', database());
insert into dept (dname, db_source)
values ('运维部', database());
Provider。
- pom.xml。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloudGeek</artifactId>
<groupId>com.geek</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-provider-dept-8001</artifactId>
<dependencies>
<!-- 我们需要拿到实体类,所有要配置 api module。-->
<dependency>
<groupId>com.geek</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- jetty。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!--热部署。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
- mybatis-config.xml。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 开启二级缓存。-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
- IDeptDao。
package com.geek.springcloud.dao;
import com.geek.springcloud.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface IDeptDao {
boolean addDept(Dept dept);
Dept queryById(Long id);
List<Dept> queryAll();
}
- DeptMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.geek.springcloud.dao.IDeptDao">
<insert id="addDept" parameterType="Dept">
insert into dept (dname, db_source)
VALUES (#{dname}, database())
</insert>
<select id="queryById" resultType="Dept" parameterType="Long">
select * from dept where deptno = #{deptno};
</select>
<select id="queryAll" resultType="Dept">
select * from dept;
</select>
</mapper>
- service。
package com.geek.springcloud.service.impl;
import com.geek.springcloud.dao.IDeptDao;
import com.geek.springcloud.pojo.Dept;
import com.geek.springcloud.service.IDeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeptServiceImpl implements IDeptService {
@Autowired
private IDeptDao deptDao;
@Override
public boolean addDept(Dept dept) {
return deptDao.addDept(dept);
}
@Override
public Dept queryById(Long id) {
return deptDao.queryById(id);
}
@Override
public List<Dept> queryAll() {
return deptDao.queryAll();
}
}
- DeptController。
package com.geek.springcloud.controller;
import com.geek.springcloud.pojo.Dept;
import com.geek.springcloud.service.IDeptService;
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.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 提供 RESTful 服务。
*/
@RestController
public class DeptController {
@Autowired
private IDeptService deptService;
@PostMapping("/dept/add")
public boolean addDept(Dept dept) {
return deptService.addDept(dept);
}
@GetMapping("/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return deptService.queryById(id);
}
@GetMapping("/dept/list")
public List<Dept> queryAll() {
return deptService.queryAll();
}
}
Consumer。
server:
port: 80
<?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">
<parent>
<artifactId>SpringCloudGeek</artifactId>
<groupId>com.geek</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-dept-80</artifactId>
<!-- 实体类 - web。-->
<dependencies>
<dependency>
<groupId>com.geek</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
- config。
package com.geek.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration// Spring applicationContext.xml。
public class ConfigBean {
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
- DeptConsumerController。
package com.geek.springcloud.controller;
import com.geek.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class DeptConsumerController {
// 消费者不应该有 service 层。
// RESTFul。
// RestTemplate。直接调用方法。注册到 Spring 中。
private static final String REST_URL_PREFIX = "http://localhost:8001";
@Autowired
private RestTemplate restTemplate;
// 提供多种便捷访问远程 http 服务的方法。简单的 RESTful 服务模板。
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX - "/dept/add", dept, Boolean.class);
}
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
// @Nullable
// public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
// @Nullable
// public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
// @Nullable
// public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
return restTemplate.getForObject(REST_URL_PREFIX - "/dept/get/" - id, Dept.class);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list() {
return restTemplate.getForObject(REST_URL_PREFIX - "/dept/list", List.class);
}
}
Eureka。
Netflix 在设计 Eureka 时,遵循的就是 AP 原则。
Eureka 是 Netflix 开发的服务发现框架,本身是一个基于 REST 的服务,主要用于定位运行在 AWS 域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud 将它集成在其子项目 spring-cloud-netflix 中,以实现 SpringCloud 的服务发现功能。
Eureka 是 Netflix 的一个子模块,也是核心模块之一。Eureka 是一个基于 REST 的服务,用于定位服务,以实现云端中间服务发现和故障转移。服务注册与发现对于微服务来说是非常重要的,有了服务注册与发现,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了,功能类似于 Dubbo 的注册中心,比如 Zookeeper。
Eureka 包含两个组件 ~ Eureka Server 和 Eureka Client。
- Eureka Server。
提供服务注册服务。各个节点启动后,会在 Eureka Server 中进行注册,这样 Eureka Server 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可在界面中直观的看到。
- Eureka Client。
是一个 Java 客户端,用于简化 Eureka Server 的交互,客户端同时也具备一个内置的,使用轮询负载算法的负载均衡器。在应用启动后,将会向 Eureka Server 改善心跳(默认周期为 30 s)。如果 Eureka Server 在多个心跳周期内没有接受到某个节点的心跳,Eureka Server 将会从服务注册表中把这个服务节点移除掉(默认周期为 90 s)。
三大角色。
- Eureka Server。提供服务的注册与发现。
- Service Provider。将自身服务注册到 Eureka 中,从而使消费方能够找到。
- Service Consumer。服务消费方从 Eureka 中获取注册服务列表,从而找到消费服务。
Eureka Server。
server:
port: 7001
# Eureka。
eureka:
instance:
hostname: localhost # Eureka 服务端的实例名字。
client:
register-with-eureka: false # 是否向 Eureka 注册中心注册。(服务端不用注册自己)。
fetch-registry: false # 不去服务端拿注册信息。表示自己为注册中心。
service-url: # 监控页面。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
# this.serviceUrl.put("defaultZone", "http://localhost:8761/eureka/");
package com.geek;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer// 服务端启动类。授受别人注册进来。
@SpringBootApplication
public class Eureka_7001Application {
public static void main(String[] args) {
SpringApplication.run(Eureka_7001Application.class, args);
}
}
Eureka Provider。
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
package com.geek.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient// 在服务启动后自动注册到 Eureka 中。
@SpringBootApplication
public class DeptProvider_8001Application {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001Application.class, args);
}
}
server:
port: 8001
mybatis:
type-aliases-package: com.geek.springcloud.pojo
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
spring:
application:
name: springcloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 数据源。
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://192.168.142.143:3307/db01?characterEncoding=utf-8
username: root
password: root
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
instance-id: springcloud-privider-dept8001 # 修改 Eureka 默认描述信息。
info。
pom 依赖中加入。
<!-- 完善监控信息。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
并在 application.yml 中加入。
info:
app.name: geek-springcloud
company.name: lyfGeek
点击 status 中的链接,就会显示(开发者自定义的信息)。
{"app":{"name":"geek-springcloud"},"company":{"name":"lyfGeek"}}
Eureka 自我保护机制 ~ 好死不如赖活着。
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
某时刻某一个微服务不可用了,Eureka 不会立刻清理,依旧会对该服务器的信息进行保存。
- 默认情况下,如果 Eureka Server 在一定时间内没有接收到某个微服务实例的心跳,Eureka Server 将会注销该实例(默认 90 s) 。但是当网络分区故障发生时,微服务与 Eureka 之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个服务。Eureka 通过自我保护机制来解决这个问题——当 Eureka Server 节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server 就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该 Eureka Server 节点会动退出自我保护模式。
- 在自我保护模式中,Eureka server 会保护服务注册表中的信息,不再注销任何服务实例。当 ta 收到的心跳数重新恢复到阈值以上时,该 Eureka Server 节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话:好死不如赖活着。
- 综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让 Eureka 集群更加的健壮和稳定。
- 在 Spring Cloud 中,可以使用 eureka.server.enable-self-preservation=false 禁用我保护模式。
【不推荐关闭自我保护机制】。
获取微服务信息。
- 启动类上加。
@EnableDiscoveryClient
- controller。
package com.geek.springcloud.controller;
import com.geek.springcloud.pojo.Dept;
import com.geek.springcloud.service.IDeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 提供 RESTful 服务。
*/
@RestController
public class DeptController {
@Autowired
private IDeptService deptService;
@Autowired
private DiscoveryClient discoveryClient;
// 注册进来的微服务,获取一些消息。
@GetMapping("/dept/discovery")
public Object discovery() {
// 获得微服务列表的清单。
List<String> stringList = discoveryClient.getServices();
System.out.println("stringList = " - stringList);
// 得到具体的微服务信息。
List<ServiceInstance> instances = discoveryClient.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
for (ServiceInstance instance : instances) {
System.out.println("instance = " - instance);
}
return this.discoveryClient;
// {"discoveryClients":[{"services":["springcloud-provider-dept"],"order":0},{"services":[],"order":0}],"services":["springcloud-provider-dept"],"order":0}
}
}
Eureka 集群。
7001 7002 7003。
- 修改 hosts。
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
- 集群配置。
server:
port: 7001
# Eureka。
eureka:
instance:
hostname: eureka7001.com # Eureka 服务端的实例名字。
client:
register-with-eureka: false # 是否向 Eureka 注册中心注册。(服务端不用注册自己)。
fetch-registry: false # 不去服务端拿注册信息。表示自己为注册中心。
service-url: # 监控页面。
# 单机。
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
# this.serviceUrl.put("defaultZone", "http://localhost:8761/eureka/");
# 集群。
defaultZone: http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka
server:
port: 7002
# Eureka。
eureka:
instance:
hostname: eureka7002.com # Eureka 服务端的实例名字。
client:
register-with-eureka: false # 是否向 Eureka 注册中心注册。(服务端不用注册自己)。
fetch-registry: false # 不去服务端拿注册信息。表示自己为注册中心。
service-url: # 监控页面。
# 单机。
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
# this.serviceUrl.put("defaultZone", "http://localhost:8761/eureka/");
# 集群。
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7003.com:7003/eureka
server:
port: 7003
# Eureka。
eureka:
instance:
hostname: eureka7003.com # Eureka 服务端的实例名字。
client:
register-with-eureka: false # 是否向 Eureka 注册中心注册。(服务端不用注册自己)。
fetch-registry: false # 不去服务端拿注册信息。表示自己为注册中心。
service-url: # 监控页面。
# 单机。
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
# this.serviceUrl.put("defaultZone", "http://localhost:8761/eureka/");
# 集群。
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
- provider 修改配置。
server:
port: 8001
mybatis:
type-aliases-package: com.geek.springcloud.pojo
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
spring:
application:
name: springcloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 数据源。
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://192.168.142.143:3307/db01?characterEncoding=utf-8
username: root
password: root
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
instance:
instance-id: springcloud-privider-dept8001 # 修改 Eureka 默认描述信息。
RDBMS(MySQL、Oracle、sqlServer)=> ACID。
NoSQL(Redis、MongoDB)=> CAP。
ACID。
A ~ atomicity ~ 原子性。
C ~ consistency ~ 一致性。
I ~ isolation ~ 隔离性。
D ~ durability ~ 持久性。
CAP。
https://baike.baidu.com/item/CAP%E5%8E%9F%E5%88%99/5712863?fr=aladdin
C ~ Consistency ~ 强一致性。
A ~ Availability ~ 可用性。
P ~ Partition Tolerance ~ 分区容错性。
一个分布式系统不可能同时满足一致性,可用性和分区容错性。
CAP 原理。
- CA:单点集群,满足一致性、可用性的系统。通常可扩展性较差。
- CP:满足一致性、分区容错性的系统。通常性能不是特别高。
- AP:满足可用性、分区容错性的系统。通常可能对强一致性要求较低。
Eureka VS Zookeeper。
Zookeeper 保证的是 CP。
Eureka 保证的是 AP。
Zookeeper 保证的是 CP。
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接 down 掉不可用。也就是说,服务注册功能、可用性的要求要高于一致性。但是 zk 会出现这样一种情况,当 master 节点因为网络故障与其他节点失去联系时,其余节点会重新进行 leader 选举。问题在于,选举 leader 的时间太长,30、120 s,且选举期间整个 zk 集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因为网络问题使得 zk 集群失去 master 节点是较大概率会发生的事件,虽然服务最终能够恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
- Eureka 保证的是 AP。
Eureka 看明白了这一点,因此在设计时就优先保证可用性。Eureka 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而 Eureka 的客户端在向某个 Eureka 注册时,如果发现连接失败,则会自动切换至其他节点,只要有一台 Eureka 还在,就能保住注册服务的可用性,只不过查到的信息可能不是最新的。除此之外,Eureka 还有一种自我保护机制,如果在 15 分钟内超过 85% 的节点都没有正常的心跳,那么 Eureka 就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
- Eureka 不再从注册列表中移除因为长时间没有心跳而应该过期的服务。
- Eureka 仍能够接受新的服务注册和查询的请求,但是不会被同步到其他节点上(即保证当前节点依然可用)。
- 当网络稳定时,当前实例新的注册信息会被同步到其他节点中。
因此 Eureka 可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像 Zookeeper 那样使整个注册服务瘫痪。
Ribbon ~ 客户端负责均衡工具。
- Spring CIoud Ribbon 是基于 Netflix Ribbon 实现的一套
客户端
负载均衡的工具。 - 简单的说,Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法, 将 Netflix 的中间层服务连接在一起。Ribbon 的客户端组件提供一系列完整的配置项。eg. 连接超时、重试等等。简单的说,就是在配置文件中列出 LoadBalancer(简称 LB,负载均衡)后面所有的机器,Ribbon 会自动的帮助你基于某种规则(如简单轮询,随机连接等等)去连接这些机器。我们也很容易使用 Ribbon 实现自定义的负载均衡算法!
Ribbon 能干嘛?
-
LB,即负载均衡(Load BaIance) ,在微服务或分布式集群中经常用的一种应用。
-
负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的 HA(高可用)。
-
常见的负载均衡软件有 Nginx,LVS 等等。
-
dubbo、Spring Cloud 中均给我们提供了负载均衡,
Spring Cloud 的负载均衡算法可以自定义
。 -
负载均衡简单分类。
- 集中式 LB。
即在服务的消费方和提供方之间使用独立的 LB 设施,如 Nginx,由该设施负责把访问请求通过某种策略转发至服务的提供方。- 进程式 LB。
将 LB 逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器。
Ribbon 就属于进程内 LB,它只是一个类库,集成于消费方进程,消费方通过 ta 来获取到服务提供方的地址。
- pom.xml。
springcloud-consumer-dept-80。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
server:
port: 80
eureka:
client:
register-with-eureka: false # 消费者不向 eureka 中注册自己。
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka
- @EnableEurekaClient。
package com.geek.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class DeptConsumer_80Application {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80Application.class, args);
}
}
- bean。// 配置负载均衡实现 RestTemplate。
package com.geek.springcloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration// Spring applicationContext.xml。
public class ConfigBean {
// 配置负载均衡实现 RestTemplate。
@LoadBalanced// Ribbon。
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
- REST 访问地址。
package com.geek.springcloud.controller;
import com.geek.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class DeptConsumerController {
// 消费者不应该有 service 层。
// RESTFul。
// RestTemplate。直接调用方法。注册到 Spring 中。
// Ribbon 实现时,这时地址应该是个变量。服务名。
// private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://springcloud-provider-dept";
@Autowired
private RestTemplate restTemplate;
// 提供多种便捷访问远程 http 服务的方法。简单的 RESTful 服务模板。
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX - "/dept/add", dept, Boolean.class);
}
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
// @Nullable
// public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
// @Nullable
// public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
// @Nullable
// public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
return restTemplate.getForObject(REST_URL_PREFIX - "/dept/get/" - id, Dept.class);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list() {
return restTemplate.getForObject(REST_URL_PREFIX - "/dept/list", List.class);
}
}
// 至此,Ribbon 和 Eureka 整合以后,客户端可以直接调用,不用关心地址和端口号。
使用 Ribbon 实现负载均衡。
分别创建 3 个数据库。db01 db02 db03。
CREATE DATABASE IF NOT EXISTS `db01` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `db01`;
-- MySQL dump 10.13 Distrib 8.0.19, for Win64 (x86_64)
--
-- Host: 192.168.142.143 Database: db01
-- ------------------------------------------------------
-- Server version 5.7.29
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `dept`
--
DROP TABLE IF EXISTS `dept`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `dept` (
`deptno` bigint(20) NOT NULL AUTO_INCREMENT,
`dname` varchar(60) DEFAULT NULL,
`db_source` varchar(60) DEFAULT NULL,
PRIMARY KEY (`deptno`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT='部门表。';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `dept`
--
LOCK TABLES `dept` WRITE;
/*!40000 ALTER TABLE `dept` DISABLE KEYS */;
INSERT INTO `dept` (dname, db_source) VALUES ('开发部',database()),('人事部',database()),('财务部',database()),('市场部',database()),('运维部',database()),(NULL,database());
/*!40000 ALTER TABLE `dept` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2020-05-01 0:08:32
同样,创建 3 个 provider 模块。端口分别为 8001,8002,8003。
访问 http://localhost/consumer/dept/list。
可以发现 Ribbon 使用的默认算法是轮询。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.netflix.loadbalancer;
public interface IRule {
Server choose(Object var1);
void setLoadBalancer(ILoadBalancer var1);
ILoadBalancer getLoadBalancer();
}
自己选择要使用的算法 @Bean。
package com.geek.springcloud.config;
import com.netflix.loadbalancer.*;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration// Spring applicationContext.xml。
public class ConfigBean {
// AvailabilityFilteringRule;会先过滤掉,跳闸、访问故障的服务,对剩下的进行轮询。
// BestAvailableRule;
// ClientConfigEnabledRoundRobinRule;
// RandomRule;随机。
// RetryRule;会先按照轮询获取服务,如果服务获取失败,则会在指定的时间内进行重试。
// RoundRobinRule;轮询。
// WeightedResponseTimeRule;
// ZoneAvoidanceRule;
// ResponseTimeWeightedRule;//deprecated.
// 配置负载均衡实现 RestTemplate。
@LoadBalanced// Ribbon。
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@Bean
public IRule myIRule() {
return new RandomRule();
}
}
- 改进。
Bean Rule 不放在启动类中,而是另外一个包内。
https://www.springcloud.cc/spring-cloud-dalston.html#_customizing_the_ribbon_client
FooConfiguration 必须是 @Configuration,但请注意,它不在主应用程序上下文的 @ComponentScan 中,否则将由所有 @RibbonClients 共享。如果您使用 @ComponentScan(或 @SpringBootApplication),则需要采取措施避免包含(例如将其放在一个单独的,不重叠的包中,或者指定要在 @ComponentScan)。
package com.geek.myrule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
public class GeekRule {
@Bean
public IRule myRule() {
return new RandomRule();
}
}
Feign 负载均衡。
feign 是声明式的 web service 客户端,ta 让微服务之间的调用变得更简单。类似 controller 调用 service。Spring Cloud 集成了 Ribbon 和 Eureka,可在使用 Feign 时提供负载均衡 http 客户端。
只需要创建一个接口,然后添加注解即可。
feign,类似于社区版,大家都习惯面向接口编程。这个是很多开发人员的规范。调用微服务访问 2 种方式。
- Ribbon ~ 微服务名称。
n. (用于捆绑或装饰的)带子;丝带;带状物;狭长的东西;绶带;勋带
- Feign ~ 接口和注解。
v. 假装,装作,佯装(有某种感觉或生病、疲倦等)
前面在使用 Ribbon - RestTemplate 时,利用 RestTemplate 对 Http 请求的封装处理。形成了一套模板化的调用方法。但在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端来包装这些依赖服务的调用。所以,Feign 在此基础上做了进一步封装,由 ta 来帮助我们定义和实现依赖服务接口的定义。在 Feign 的实现下,我们只需要创建一个接口并使用注解的方式来配置 ta(类似于以前 Dao 接口上标注 Mapper 注解,现在是一个微服务接口上面标注一个 Feign 注解即可。)即可完成对服务提供方的接口绑定,简化了使用 Spring Cloud Ribbon 时,自动封装服务调用客户端的开发量。
Feign 集成了 Ribbon。
利用 Ribbon 维护了 MicroServiceCloud-Dept 的服务列表信息,并且通过轮询实现了客户端的负载均衡,而与 Ribbon 不同的是,通过 Feign 只需要定义服务绑定接口且以声明式的方式,优雅而且简单实现了服务调用。
在 api 和 feign 模块导入依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
在 api 模块创建一个接口。
@FeignClient(“springcloud-provider-dept”)
指定 Provider 服务名。
package com.geek.springcloud.service;
import com.geek.springcloud.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
@Service
@FeignClient("springcloud-provider-dept")
public interface IDeptClientService {
@GetMapping("/dept/get/{id}")
Dept queryById(@PathVariable("id") Long id);
@GetMapping("/dept/list")
List<Dept> queryAll();
@PostMapping("/dept/add")
boolean addDept(Dept dept);
}
- 在 feign 80 客户端添加 controller。
package com.geek.springcloud.controller;
import com.geek.springcloud.pojo.Dept;
import com.geek.springcloud.service.IDeptClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class DeptConsumerController {
// 消费者不应该有 service 层。
// RESTFul。
// RestTemplate。直接调用方法。注册到 Spring 中。
// Ribbon 实现时,这时地址应该是个变量。服务名。
// private static final String REST_URL_PREFIX = "http://localhost:8001";
// private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
// feign。
@Autowired
private IDeptClientService deptClientService = null;// null,没有实现类。
// @Autowired
// private RestTemplate restTemplate;
// 提供多种便捷访问远程 http 服务的方法。简单的 RESTful 服务模板。
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return this.deptClientService.addDept(dept);
}
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return this.deptClientService.queryById(id);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list() {
return this.deptClientService.queryAll();
}
}
Hystrix 服务熔断。
https://github.com/Netflix/Hystrix/wiki
分布式系统面临的问题。
复杂分布式系统结构中的应用程序有数十全依赖关系,每个依赖关系在某些时刻将不可避免的失败。
服务雪崩。
多个微服务之间调用的时候,假设微服务 A 调用微服务 B 和微服务 C,微服务 B 和 微服务 C 又调用其他的微服务,这就是所谓的“扇出”、如果扇出的链路上某个微服务的调用响应时间过长或不可用,对微服务 A 的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,贤臣和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
我们需要弃车保帅
。
Hystrix ~ what。
在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。Hystrix 是一个库,可通过添加延迟容限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix 通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个服务器预期的,可处理的备选响应(fallback),而不是长时间的等待或者抛出调用方无法处理的异常。这样就可以保证服务调用放的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
能干吗。
服务降级。
服务熔断。
服务限流。
接近实时的监控。
服务熔断。
熔断机制是对应雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。在 Spring Cloud 框架中熔断机制通过 Hystrix 实现。Hystrix 会监控微服务间调用的状况,当失败的调用到一定的阈值,缺省值的 5 秒内 20 次调用失败就会启动熔断机制。熔断机制的注解是@HystrixCommand
。
- pom.xml。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- @HystrixCommand(fallbackMethod = “hystrixGet”)。
package com.geek.springcloud.controller;
import com.geek.springcloud.pojo.Dept;
import com.geek.springcloud.service.IDeptService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
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;
/**
* 提供 RESTful 服务。
*/
@RestController
public class DeptController {
@Autowired
private IDeptService deptService;
@HystrixCommand(fallbackMethod = "hystrixGet")
@GetMapping("/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
Dept dept = deptService.queryById(id);
if (dept == null) {
throw new RuntimeException("id => " - id - "。不存在该用户,或信息无法找到。");
}
return dept;
}
// 备选方案。
public Dept hystrixGet(@PathVariable("id") Long id) {
return new Dept()
.setDeptno(id)
.setDname("id => " - id - "。没有对应的信息。null。~@Hystrix。")
.setDb_source("no this database in MySQL.");
}
}
- @EnableCircuitBreaker// 熔断。
package com.geek.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableCircuitBreaker// 熔断。
@EnableDiscoveryClient
@EnableEurekaClient// 在服务启动后自动注册到 Eureka 中。
@SpringBootApplication
public class DeptProviderHystrix_8001Application {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001Application.class, args);
}
}
访问不存在的数据,不会报 500,而是
http://localhost/consumer/dept/get/8
{“deptno”:8,“dname”:“id => 8。没有对应的信息。null。~@Hystrix。”,“db_source”:“no this database in MySQL.”}
服务降级 ~ 客户端。
- api 模块。
package com.geek.springcloud.service;
import com.geek.springcloud.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
@Service
@FeignClient(value = "springcloud-provider-dept", fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface IDeptClientService {
@GetMapping("/dept/get/{id}")
Dept queryById(@PathVariable("id") Long id);
@GetMapping("/dept/list")
List<Dept> queryAll();
@PostMapping("/dept/add")
boolean addDept(Dept dept);
}
package com.geek.springcloud.service;
import com.geek.springcloud.pojo.Dept;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
// 降级。
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {
@Override
public Object create(Throwable throwable) {
return new IDeptClientService() {
@Override
public Dept queryById(Long id) {
return new Dept()
.setDeptno(id)
.setDname("id => " +id - "没有对应的信息。客户端提供了降级的信息,这个服务现在已经关闭。")
.setDb_source("没有数据。");
}
@Override
public List<Dept> queryAll() {
return null;
}
@Override
public boolean addDept(Dept dept) {
return false;
}
};
}
}
- feign 8080 模块。
server:
port: 80
eureka:
client:
register-with-eureka: false # 消费者不向 eureka 中注册自己。
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka
# 开启降级。
feign:
hystrix:
enabled: true
对比。
- 服务熔断。
服务端。某个服务超时或者异常,引起熔断。
(保险丝)。
- 服务降级。
客户端。
从整体网站请求负载考虑。当某个服务熔断或者关闭后,服务将不再被调用。
此时在客户端,我们可以准备一个 FallbackFactory,返回一个默认的值(缺省值)。
整体的服务水平下降了。但是 ~ 好歹能用 ~ 比直接挂掉强多了。
Hystrix ~ Dashboard 流监控。
- dashboard
n. (汽车上的)仪表盘
- pom.xml。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloudGeek</artifactId>
<groupId>com.geek</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-hystrix-dashboard</artifactId>
<!-- 实体类 - web。-->
<dependencies>
<!-- Hystrix 依赖。-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>com.geek</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
- 端口。
server:
port: 9001
- 启动类。@EnableHystrixDashboard。
package com.geek.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class DeptConsumerDashboard_9001Application {
public static void main(String[] args) {
SpringApplication.run(DeptConsumerDashboard_9001Application.class, args);
}
}
- 确保其他服务模块有。
<!-- 完善监控信息。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- 访问 localhost:9001/hystrix
监控地址:http://localhost:8001/actuator/hystrix.stream
- provider 中启动类添加一个 Servlet Bean。
package com.geek.springcloud;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
@EnableDiscoveryClient
@EnableEurekaClient// 在服务启动后自动注册到 Eureka 中。
@SpringBootApplication
@EnableCircuitBreaker// 添加对熔断的支持。
public class DeptProvider_8001Application {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001Application.class, args);
}
// Dashboard Bean。
// 添加一个 Servlet。
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
registrationBean.addUrlMappings("/actuator/hystrix.stream");
return registrationBean;
}
}
查看流是否存在。
localhost:8001/actuator/hystrix.stream
路由网关 ~ Zuul。
Zuul 包含了对请求的路由和过滤两个最主要的功能。
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础,而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。Zuul 和 Eureka 进行整合,将 Zuul 自身注册为 Eureka 服务治理下的应用,同时从 Eureka 整中获得其他微服务的消息,也即以后的访问微服务都是通过 Zuul 跳转后获得。
注意:Zuul 服务最终还是会注册进 Eureka。
提供:代理 - 路由 - 过滤三大功能。
https://github.com/Netflix/zuul
- pom.xml。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloudGeek</artifactId>
<groupId>com.geek</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-zuul-9527</artifactId>
<dependencies>
<!-- zuul。-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- Hystrix 依赖。-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>com.geek</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
- 配置。
server:
port: 9527
spring:
application:
name: springcloud-zuul-gateway
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka
instance:
instance-id: zuul9527.com
prefer-ip-address: true
info:
app.name: geek-springcloud
company.name: lyfGeek
- 启动类。
package com.geek.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy
public class Zuul_9527Application {
public static void main(String[] args) {
SpringApplication.run(Zuul_9527Application.class, args);
}
}
通过访问 http://www.geek.com:9527/springcloud-provider-dept/dept/get/1。
注意 springcloud-provider-dept 必须小写。
通过设置 zuul 的配置可以自定义访问路径。
server:
port: 9527
spring:
application:
name: springcloud-zuul-gateway
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka
instance:
instance-id: zuul9527.com
prefer-ip-address: true
info:
app.name: geek-springcloud
company.name: lyfGeek
zuul:
routes:
mydept.serviceId: springcloud-provider-dept
mydept.path: /mydept/**
# // http://www.geek.com:9527/springcloud-provider-dept/dept/get/1
则可以通过 http://www.geek.com:9527/mydept/dept/dept/get/1 访问。
zuul:
routes:
mydept.serviceId: springcloud-provider-dept
mydept.path: /mydept/**
ignored-services: springcloud-provider-dept # 不能使用这个路径访问了。
zuul:
routes:
mydept.serviceId: springcloud-provider-dept
mydept.path: /mydept/**
ignored-services: springcloud-provider-dept # 不能使用这个路径访问了。
# ignored-services: "*" # 隐藏全部。
prefix: /geek
# http://www.geek.com:9527/geek/mydept/dept/get/1
# // http://www.geek.com:9527/springcloud-provider-dept/dept/get/1
Config ~ Spring Cloud config 分布式配置。
分布式系统面临的问题 ~ 配置文件的问题。
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务,由于每个服务都需要必要的配置信息才能运行,所以一套集中式的,动态的配置管理设施是必不可少的。Spring Cloud 提供了 Config Server 来解决这个问题,我们每一个微服务自己都带着一个 application.yml,那上百个配置文件修改起来。。。
Spring Cloud Config。
Spring Cloud Config 为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为第四个不同微服务应用的所有环节提供了一个中心化的外部配置。
Spring Cloud Config 分为服务端和客户端两部分。
- 服务端也称为分布式配置中心,ta 是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密,解密信息等访问接口。
- 客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。配置服务器默认采用 git 来存储配置信息,这样就有助于对环境配置进行版本管理。并且可以涌过 git 客户端工具来方便的管理和访问配置内容。
Spring Cloud config 分布式配置中心能干嘛。
-
集中管理配置文件。
-
不同环境不同配置,动态化的配置更新,分环境部署,eg. /dev /test /prod /beta /release。
-
运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置文件中心统一拉取配置自己的信息。
-
当配置发生变化时,服务不需要重启,即可感知到配置的变化,并应用新的配置。
-
将配置信息以 REST 接口的形式暴露。
Spring Cloud Config 分布式配置中心与 Github 整合。
https://www.springcloud.cc/spring-cloud-dalston.html#_spring_cloud_config
由于 Spring Cloud Config 默认使用 Git 来存储配置文件(也有其他方式,比如支持 SVN 和本地文件),但是最推荐的还是 Git。而且使用的是 http / https 访问方式。
geek@LAPTOP-0GJSKR6T MINGW64 /g/geek/geek-config/geek_config (master)
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
application.yml
nothing added to commit but untracked files present (use "git add" to track)
geek@LAPTOP-0GJSKR6T MINGW64 /g/geek/geek-config/geek_config (master)
$ git add .
geek@LAPTOP-0GJSKR6T MINGW64 /g/geek/geek-config/geek_config (master)
$ git commit -m "first"
[master 5939325] first
1 file changed, 17 insertions(+)
create mode 100644 application.yml
geek@LAPTOP-0GJSKR6T MINGW64 /g/geek/geek-config/geek_config (master)
$ git push
libpng warning: iCCP: cHRM chunk does not match sRGB
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 402 bytes | 402.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: Powered by GITEE.COM [GNK-5.0]
To https://gitee.com/lyfGeek/geek_config.git
f970b2b..5939325 master -> master
geek@LAPTOP-0GJSKR6T MINGW64 /g/geek/geek-config/geek_config (master)
$
服务端。springcloud-config-server-3344。
- pom.xml。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloudGeek</artifactId>
<groupId>com.geek</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-config-server-3344</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-config-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
- 配置 application.yml。
server:
port: 3344
spring:
application:
name: spring-cloud-server
# 连接远程仓库,
cloud:
config:
server:
git:
uri: https://gitee.com/lyfGeek/geek_config.git
- 启动类 ~ @EnableConfigServer。
package com.geek.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class Config_Server_3344Application {
public static void main(String[] args) {
SpringApplication.run(Config_Server_3344Application.class, args);
}
}
- 访问地址:http://localhost:3344/application-dev.yml。
spring:
application:
name: springcloud-config-dev
profiles:
active: dev
- 访问地址:http://localhost:3344/application-test.yml。
spring:
application:
name: springcloud-config-test
profiles:
active: dev
- HTTP 服务具有以下格式的资源。
label ——> master。
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
客户端。springcloud-config-client-3355。
- pom.xml。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloudGeek</artifactId>
<groupId>com.geek</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-config-client-3355</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-config-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
- 配置 ~ config-client.yml。
spring:
profiles:
active: dev
---
server:
port: 8201
spring:
application:
name: springcloud-provider-dept
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka
---
server:
port: 8202
spring:
application:
name: springcloud-provider-dept
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
config-client.yml
nothing added to commit but untracked files present (use "git add" to track)
geek@LAPTOP-0GJSKR6T MINGW64 /g/geek/geek-config/geek_config (master)
$ git add .
geek@LAPTOP-0GJSKR6T MINGW64 /g/geek/geek-config/geek_config (master)
$ git commit -m "2"
[master f6bfb23] 2
1 file changed, 29 insertions(+)
create mode 100644 config-client.yml
geek@LAPTOP-0GJSKR6T MINGW64 /g/geek/geek-config/geek_config (master)
$ git push
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 426 bytes | 426.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Powered by GITEE.COM [GNK-5.0]
To https://gitee.com/lyfGeek/geek_config.git
5939325..f6bfb23 master -> master
geek@LAPTOP-0GJSKR6T MINGW64 /g/geek/geek-config/geek_config (master)
$
- controller。
package com.geek.springcloud.comtroller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ConfigClientController {
@Value("${spring.application.name}")
private String applicationName;
@Value("${eureka.client.service-url.defaultZone}")
private String eurekaServer;
@Value("${server.port}")
private String port;
@RequestMapping("/config")
public String getConfig() {
return "applicationName: " - applicationName +
"eurekaServer: " - eurekaServer +
"port: " - port;
}
}
- 配置。
bootstrap.yml。
# 系统级的配置。
spring:
cloud:
config:
uri: http://localhost:3344
label: master
name: config-client # 需要从 Git 上读取的资源名称,不要后缀。
profile: dev
# http://localhost:3344/config-client-dev.yml
application.yml。
# 用户级别的配置。
spring:
application:
name: springcloud-config-client-3355
2020-05-03 19:10:47.616 INFO 14092 — [ main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:3344
因为本地没有配置端口号,使用远程配置端口号 8202。
http://localhost:8202/config
applicationName: springcloud-config-deveurekaServer: http://eureka7001.com:7001/eurekaport: 8202