??今晚终于对周阳老师讲的第一季SpringCloud成功撒花,整体来说对我的收获是很大的,心里的第一道障碍也落下了,在此感谢尚硅谷,感谢周阳老师。而本文章只是我的一种课堂笔记罢了,没做特多的调整,可能不够详细,因为我后面越来越心急了。但前面的理论还是可以看一看的,此后,我会以自己的方式,自己的风格出一篇SpringCloud入门。
 完整项目地址:https://github.com/fenxianxian/springcloud
1. 微服务概述
1.1 架构演变
??在讲dubbo的时候我也简单讲述了架构的发展演变,从单一到微服务,那么我就再絮叨絮叨吧。
简单概述
- 单一架构:所谓的单一架构就是把所有功能都集中在一个项目里,比如用户模块,订单模块,等等等等。像这种开发模式不就是我们在大学里的开发模式吗?好,这种架构有什么缺点?首先,第一个缺点:就是一个功能的修改,导致整个项目都要重新打包,部署。第二个缺点:随着业务的增多,机器负载有限,不足以面对高并发。行了,就说这两个缺点吧。
- 垂直架构:我们都已经发现单一架构的缺点了,那么接下来肯定是要解决它的缺点,比如它的第一个缺点,我们可以这样,把用户模块提取出来,单独部署到一台机器上,订单模块也一样,也是单独部署到一台机器上,这样就有两个tomcat了,可以这么理解,好,经我这么一说,本来用户模块和订单模块是在一个项目里的,现在分到两台机器上,独立部署,不就是说我把项目给拆分了吗?注意,每个模块从页面,业务逻辑,数据库都是完整的,也就是它满足MVC架构,只要你看过官网的那张图,或我dubbo文章里的第二副图,就会看到MVC这三个字母。说白了,就是一个小项目(页面,控制层,服务层都有)。好了,既然模块与模块之间都分开了,那么用户模块进行升级会影响到订单模块吗?是不是就不会了,同时,如果用户模块负载量大,我们还可以以集群的方式再增加一台机器,实现一种动态插拔的感觉,在面对高并发的时候就不会那么吃力了。
- 分布式架构:这时候,如果用户一开始的请求就来到了用户模块,并且完成了登录,验证,那么到其它模块就不应该再登陆了,因为你在用户模块就已经登录过了,毕竟用户模块跟其它模块是一个整体,合起来对外就是一个应用在服务,只是模块分散了,所以,我们是不是得把登陆验证的逻辑单独提取出来,形成一个服务呀,毕竟这样也好让其它模块方便调用,同时其它模块就不用再重复写一次登陆验证逻辑了,到这,就是模块再拆分,把里面的服务提取出来再单独部署,嗯,有种微服务内味了。像这样,如果越来越多的服务从模块中分离出来,那么在管理上就会很麻烦,所以在应用与服务之间就会有一个东西,来负责管理各个服务,那这就是下一个架构,继续往下看。
- SOA架构(面向服务架构):像我在前面说的,应用与服务之间会有一个东西,这个东西可以是ESB,或者说dubbo,它可以帮我们对各个服务进行调度,负载均衡处理,比如说某一个服务在A,B两台机器都有,那么调用的时候应该选用那一台机器呢?是不是得选用负载量小的机器呀,这样在处理速度上也快很多,性能也好,
- 微服务架构:在我的理解就是服务再拆分,拆分到不能再拆分为止,相比前面的,粒度更小了,更微了,可以说,微服务架构就是SOA架构的一种升级版。
??总结:微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事,从技术角度看就是一种小而独立的处理过程,类似进程概念,能够自行单独启动或销毁,拥有自己独立的数据库。
1.2 微服务和微服务架构
- 微服务强调的是一个一个的个体,每个个体完成一个具体的任务或者功能。
- 微服务架构强调的是一个整体,就是用哪些方式把一个一个的微服务组装,拼接起来对外构成一个集体。
1.3 微服务的优缺点
微服务的优点
- 各个服务之间实现了松耦合,彼此之间不需要关注对方是用什么语言开发,什么技术开发,只需要保证自己的接口可以正常访问即可。
- 各个服务之间独立自治,只需要专注于做好自己的业务,开发和维护不会影响到其它的微服务。
- 微服务是一种去中心化的架构方式,相当于用零件来拼接一台机器,如果某个零件出现问题,可以随时进行替换,从而保证机器的正常运行。
- 易于和第三方集成,微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如Jenkins,Hudson,bamboo。
- 微服务只是业务逻辑的代码,不会和HTML,CSS或其它界面组件混合。
- 每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一的数据库。
微服务的缺点
- 开发人员要处理分布式系统的复杂性。
- 多服务运维难度,随着服务的增加,运维的压力也在增大。
- 系统部署依赖。
- 服务间通信成本。
- 数据一致性。
- 系统集成测试。
- 性能监控…
1.4 微服务设计原则
- 服务粒度不能太大也不能太小,提炼核心需求,根据服务间的交互关系找到最合理的服务粒度。
- 各个微服务的功能职责尽量单一,避免出现多个服务处理同一个需求。
- 各个微服务之间要相互独立,自治,自主开发,自主测试,自主部署,自主维护。
- 保证数据的独立性,各个服务独立管理其业务模块下的数据,但可以开放接口让其它微服务去调。
- 使用RESTful协议来完成微服务之间的协作任务,数据交换采用JSON格式,方便调用和整合。
??有时间可以阅读马丁福勒写过的微服务文章:https://martinfowler.com/articles/microservices.html
1.5 微服务技术栈
微服务条目 | 落地实现 |
---|
服务开发 | SpringBoot,Spring,SpringMVC | 服务配置与管理 | Netflix公司的Archaius,阿里的Diamond等 | 服务注册与发现 | Eureka,Consul,Zookeeper等 | 服务调用 | Rest,RPC,gRPC | 服务熔断器 | Hystrix,Envoy等 | 负载均衡 | Ribbon,Nginx等 | 服务接口调用(客户端调用服务的简化工具) | Feign等 | 消息队列 | Kafka,RabbitMQ,ActiveMQ等 | 服务配置中心管理 | SpringCloudConfig,Chef等 | 服务路由(API网关) | Zuul等 | 服务监控 | Zabbix,Nagios,Metrics,Spectator等 | 全链路追踪 | Zipkin,Brave,Dapper等 | 服务部署 | Docker,OpenStack,Kubernetes等 | 数据流操作开发包 | SpringCloud Stream(封装与Redis,Rabbit,Kafka等发送接收消息) | 事件消息总线 | Spring Cloud Bus | … | |
2. SpringCloud简单介绍
概述
??在上表中列出了作为一个微服务架构都应该具备的几个特性,每一个特性都有它的落地实现,而SpringCloud是微服务架构的一站式实现,也就是每一个特性都有它的一套解决方案,有着微服务全家桶的称呼。它能使我们在SpringBoot的基础上轻松地实现微服务项目的构建。相对于dubbo来说,SpringCloud的维度也更加的广,如下表:
| Dubbo | Spring Cloud |
---|
服务注册中心 | Zookeeper | Spring Cloud Netflix Eureka | 服务调用方式 | RPC | REST API | 服务监控 | Dubbo-monitor | Spring Boot Admin | 断路器 | 不完善 | Spring Cloud Netflix Hystrix | 服务网关 | 无 | Spring Cloud Netflix Zuul | 分布式配置 | 无 | Spring Cloud Config | 服务跟踪 | 无 | Spring Cloud Sleuth | 服务总线 | 无 | Spring Cloud Bus | 数据流 | 无 | Spring Cloud Stream | 批量任务 | 无 | Spring Cloud Task |
SpringCloud组件
??首先要介绍一下Netflix ,Netflix 是一个很伟大的公司,在Spring Cloud项目中占着重要的作用,Netflix公司提供了包括Eureka、Hystrix、Zuul、Archaius等在内的很多组件,在微服务架构中至关重要,Spring在Netflix 的基础上,封装了一系列的组件,命名为:Spring Cloud Netflix Eureka、Spring Cloud Netflix Hystrix、Spring Cloud Netflix Zuul等。下面列出来的是SpringCloud常见的一些组件,后面会慢慢涉及到,如下:
- 服务治理Eureka
- 服务通信Ribbon,Feign
- 服务网关Zuul
- 服务容错Hystrix
- 服务配置Config
- 服务监控Actuator
- 服务跟踪Zipkin
学习资料
3. Rest微服务搭建
??我的SpringBoot版本:2.3.7.RELEASE。
创建普通的maven项目
 ??我呢把它作为父项目,所以我得把该项目的src干掉,就是不要src,因为我不往里写代码,写代码是在子模块里写的。 ??然后打开pom.xml,注意打包方式是pom。如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cht</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.7.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
创建表
create table dept
(
deptno bigint primary key not null auto_increment,
dname varchar(60),
db_source varchar(60)
)engine=InnoDB charset=utf8
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());
在该项目下新建Module
-
创建springcloud-api项目:  ??注意,还是maven项目,名字叫springcloud-api,这个项目它只管实体类,其它不管。结构如下:  ??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>springcloud</artifactId>
<groupId>com.cht</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-api</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
??在src下编写实体类: package com.cht.springcloud.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class Dept implements Serializable {
private Long deptno;
private String dname;
private String db_source;
public Dept(String dname) {
this.dname = dname;
}
}
-
创建springcloud-provider-dept-8001项目: ??项目结构如下:  ??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>springcloud</artifactId>
<groupId>com.cht</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-provider-dept-8001</artifactId>
<dependencies>
<dependency>
<artifactId>springcloud-api</artifactId>
<groupId>com.cht</groupId>
<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>
<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>
??application.yml: server:
port: 8001
mybatis:
type-aliases-package: com.cht.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://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8
username: root
password: root
??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>
??DeptDao类: package com.cht.springcloud.dao;
import com.cht.springcloud.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface DeptDao {
boolean addDept(Dept dept);
Dept queryById(Long id);
List<Dept> queryAll();
}
??DeptService类: package com.cht.springcloud.service;
import com.cht.springcloud.pojo.Dept;
import java.util.List;
public interface DeptService {
boolean addDept(Dept dept);
Dept queryById(Long id);
List<Dept> queryAll();
}
??DeptServiceImpl类: package com.cht.springcloud.service.impl;
import com.cht.springcloud.dao.DeptDao;
import com.cht.springcloud.pojo.Dept;
import com.cht.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptDao 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();
}
}
??DeptMapper.xml: <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cht.springcloud.dao.DeptDao">
<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>
??DeptController: package com.cht.springcloud.controller;
import com.cht.springcloud.pojo.Dept;
import com.cht.springcloud.service.DeptService;
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;
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@PostMapping("/dept/add")
public boolean addDept(Dept dept){
return deptService.addDept(dept);
}
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id){
return deptService.queryById(id);
}
@GetMapping("/dept/list")
public List<Dept> queryAll(){
return deptService.queryAll();
}
}
??DeptProvider_8001: package com.cht.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
??接下来就可以开始测试了。。。 -
创建springcloud-consumer-dept-80项目: ??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>springcloud</artifactId>
<groupId>com.cht</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-dept-80</artifactId>
<dependencies>
<dependency>
<artifactId>springcloud-api</artifactId>
<groupId>com.cht</groupId>
<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>
??application.xml: server:
port: 80
??ConfigBean类: package com.cht.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
??DeptConsumerController类: package com.cht.springcloud.controller;
import com.cht.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 {
@Autowired
private RestTemplate restTemplate;
private static final String REST_URL_PREFIX = "http://localhost:8001";
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
}
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list() {
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
}
}
??DeptConsumer_80启动类: package com.cht.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class,args);
}
}
??然后启动提供者springcloud-provider-dept-8001,再启动消费者springcloud-consumer-dept-80,两者启动后,访问消费者,测试一下是否能够远程调用提供者,如果能成功调用即成功。
4. Eureka服务注册与发现
4.1 什么是Eureka?
??Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移,服务注册与发现对于微服务来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件,功能类型于Dubbo的注册中心,比如Zookeeper。 ??也可以这么说,Eureka是基于RESTful的开源的一个服务治理方案,然后Spring cloud里面集成了Eureka。提供了服务注册和服务发现功能。并且可以基于spingboot搭建的微服务应用轻松完成整合。
4.2 Eureka的基本架构
- Springcloud封装了Netflix公司开发的Eureka模块来实现服务注册与发现 (对比Zookeeper)。
- Eureka采用了C-S的架构设计,EurekaServer作为服务注册功能的服务器,他是服务注册中心。
- 而系统中的其他微服务,使用Eureka的客户端连接到EurekaServer并维持心跳连接。这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行,Springcloud的一些其它模块 (比如Zuul) 就可以通过EurekaServer来发现系统中的其他微服务,并执行相关的逻辑。
4.3 Spring cloud Eureka的组成
??Spring cloud Eureka主要包括服务端和客户端组件。也就是Eureka Server服务端(提供服务注册,服务发现的服务端,也称作我们的注册中心)。Eureka Client客户端(需要注册的服务就是通过Eureka Client来连接到Eureka Server来完成注册的)。
- Eureka Server提供服务注册,各个节点启动后,回在EurekaServer中进行注册,这样Eureka Server中的服务注册表中将会储存所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
- Eureka Client是一个Java客户端,用于简化EurekaServer的交互,客户端同时也具备一个内置的,使用轮询负载算法的负载均衡器。在应用启动后,将会向EurekaServer发送心跳 (默认周期为30秒) 。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除掉 (默认周期为90s)。
4.4 三大角色
- Eureka Server:提供服务的注册与发现
- Service Provider:服务生产方,将自身服务注册到Eureka中,从而使服务消费方能狗找到
- Service Consumer:服务消费方,从Eureka中获取注册服务列表,从而找到消费服务
4.5 编写代码
eureka server端
??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>springcloud</artifactId>
<groupId>com.cht</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-eureka-7001</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
??application.yml:
server:
port: 7001
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
??EurekaServer_7001类:
package com.cht.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class,args);
}
}
??测试,访问http://localhost:7001/,如下:

eureka client端 提供者
??打开springcloud-provider-dept-8001模块: ??pom.xml中加上以下坐标:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
??application.yml下增加以下内容:
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
??启动类上加上以下注解:
@EnableEurekaClient
??测试,再打开http://localhost:7001/,如下:
 ??以上,就表示提供者已成功注册到注册中心去了。其中,Status下的绿色字,可以换成另一个名字,那么就在yml下加上instance-id,如下:
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
instance-id: springcloud-provider-dept8001
??如果我们点进那绿色字进去,就会进入到一个页面,也就是http://localhost:8001/actuator/info,该页面是没有的,我们可以导入以下坐标:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
??加上以上坐标,再打开info页面就不会报错了,只不过一片空白,可以在yml加上以下配置:
info:
app.name: wudongchengxu.springcloud
company.name: shadiao
??如果我们把提供者给关闭了,那么在注册中心这是不会立即把提供者给去除掉的,但过了一段时间,就会出现如下红色字体:

- 默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。
- 在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该Eureka Server节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着。
- 综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
- 在Spring Cloud中,可以使用eureka.server.enable-self-preservation = false 禁用自我保护模式,但不推荐。
??服务发现(discovery): ??对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息。 ??DeptController类增加以下内容:
@Autowired
private DiscoveryClient client;
@GetMapping("/dept/discovery")
public Object discovery() {
List<String> services = client.getServices();
System.out.println("discovery=>services:" + services);
List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
for (ServiceInstance instance : instances) {
System.out.println(
instance.getHost() + "\t" +
instance.getPort() + "\t" +
instance.getUri() + "\t" +
instance.getServiceId()
);
}
return this.client;
}
??然后在启动类上加上以下注解:
@EnableDiscoveryClient
??进行访问测试:http://localhost:8001/dept/discovery。 ??在消费者这边的控制类上加上如下方法:
@RequestMapping("/consumer/dept/discovery")
public Object discovery(){
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/discovery", Object.class);
}
eureka集群配置
??在springcloud-eureka-7001模块的参照上再创建两个模块,如下: ??先看springcloud-eureka-7002模块,pom.xml跟springcloud-eureka-7001是一样的,启动类也没什么好说的,就剩下application.yml了,因为是集群,那么就要让eureka-7001这个注册中心去拥有eureka-7002和eureka-7003,eureka-7002就去拥有eureka-7001和eureka-7003,eureka-7003也是一样的道理,主要修改的就是hostname和defaultZone。但在修改之前先去C:\Windows\System32\drivers\etc目录下的hosts文件,增加如下内容:
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
??以springcloud-eureka-7001模块的application.yml为例,注意只改hostname和defaultZone,如下:
hostname: eureka7001.com
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
??注意不是localhost。那么springcloud-eureka-7002的application.yml也是一样的道理,如下:
hostname: eureka7002.com
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/
??springcloud-eureka-7003的application.yml照葫芦画瓢,完成后,分别启动这三个注册中心,随便打开一个访问,将会看到DS Replicas里就有内容了,如下: ??提供者这边呢,也要对application.yml做个简单修改,如下:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
4.6 CAP原则及对比Zookeeper
回顾CAP原则
- RDBMS (MySQL\Oracle\sqlServer) ===> ACID
- NoSQL (Redis\MongoDB) ===> CAP
ACID是什么
- A (Atomicity) 原子性
- C (Consistency) 一致性
- I (Isolation) 隔离性
- D (Durability) 持久性
传统的ACID分别是什么
??事务在英文中是transaction,和现实世界中的交易很类似,它有如下四个特性:
- A (Atomicity)原子性:原子性很容易理解,也就是说事务里的所有操作要么全部做完,要么都不做,事务成功的条件是事务里的所有操作都成功,只要有一个操作失败,整个事务就失败,需要回滚。比如银行转账,从A账户转100元至B账户,分为两个步骤:1)从A账户取100元;2)存入100元至B账户。这两步要么一起完成,要么一起不完成,如果只完成第一步,第二步失败,钱会莫名其妙少了100元。
- c (Consistency)一致性:一致性也比较容易理解,也就是说数据库要一直处于一致的状态,事务的运行不会改变数据库原本的一致性约束。
- I (Isolation)独立性:所谓的独立性是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。比如现有有个交易是从A账户转100元至B账户,在这个交易还未完成的情况下,如果此时B查询自己的账户,是看不到新增加的100元的。
- D (Durability)持久性:持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。
CAP是什么?
- C (Consistency) 强一致性
- A (Availability) 可用性
- P (Partition tolerance) 分区容错性
??在分布式系统里面有句话,叫CAP的三进二,所谓的三进二就是任何一个分布式系统都没有办法同时满足CAP这三点,最多也就满足两点,这就是三进二,它可以是CA、AP、CP。有如下图:
 ??如上图,看到RDBMS了没有,是不是表示关系型数据库呀,而关系型数据库包括mysql,oracle等,那么也就是说mysql,oracle遵循CA原则。
- CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
- CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
- AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
??对于分布式的系统和结构,只能选CP和AP,也就是P不能丢,如果丢了,分布式节点将受限,没办法部署子节点,就违背了分布式系统设计的初衷。 ??在双十一这么重要的日子里,就得保证AP,为什么呢?首先,P肯定是不能丢的,那么只剩下A和C,所以最后到底是AP?还是CP呢?要知道,在双十一的这个日子里,用户最关注的是你的这个服务能不能用,万一网页打不开了,是不是意味着在可用性方面没达到指标,所以,为了保证可用性,我们选用AP。只有过了双十一,统计数据的时候,就可以用C,来保证数据的一致性。
- zookeeper 保证 CP:当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的信息,但不能接受服务直接 down 掉不可用。也就是说服务注册功能对可用性的要求要高于一致性,但是 zk 会出现这样一种情况,当 master 节点因网络故障和其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30s~120s,且选举期间,整个 zk 集群都是不可用的。这就导致了在选举期间注册服务的瘫痪。在云部署的环境下,因网络问题使 zk 集群失去 master 节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
- eureka 保证 AP:Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务,而Eureka的客户端在向某个Eureka注册时如果发现连接失败,则会自动切换至其它节点,只要有一台 Eureka 存在,就可以保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka 还有一种自我保护机制,如果在 15 分钟内超过 85% 的节点都没有正常的心跳,那么 Eureka 就会认为客户端与注册中心出现了故障,此时会出现以下几种情况:
- Eureka 不再从注册列表中移出因长时间没收到心跳而应该过期的服务。
- Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上(即保证当前节点依然可用)。
- 当网络稳定时,当前实例新的注册信息会被同步到其他节点中。
??因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。
5. Ribbon负载均衡
5.1 Ribbon是什么?
??Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具。简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时, 重试等。简单的说,就是在配置文件中列出Load Balancer (简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。 ??LB,即负载均衡,在微服务或分布式集群中经常用的一种应用。负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。常见的负载均衡软件有Nginx,LVS,硬件 F5等。相应的在中间件,例如:dubbo和Spring Cloud中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义(说白了就是Ribbon的负载均衡算法除了出厂的这几种,我们还可以自定义)。 ??LB分为集中式LB和进程内LB
- 集中式LB:偏硬件。即在服务的消费方和提供方之间使用独立的LB设备(可以是硬件,如F5,也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方。
- 进程内LB:偏软件。将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
??学习资料:https://github.com/Netflix/ribbon
5.2 Ribbon配置初步
??打开springcloud-consumer-dept-80工程。
修改pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
??注意,我强调一遍,消费方和提供方都属于eureka client,只有eureka才属于eureka server。
修改application.yml,追加eureka的服务注册地址
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
对ConfigBean进行新注解@LoadBalanced,获得Rest时加入Ribbon的配置
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
主启动类上添加以下注解
@EnableEurekaClient
修改controller
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
先启动三个eureka集群,再启动提供方进行注册
??注册成功。。。
再启动消费方
??测试http://localhost/consumer/dept/get/1,如果能行,说明成功。
总结
??Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号。
5.3 Ribbon负载均衡
??Ribbon在工作时分成两步:
- 第一步先选择EurekaServer,它优先选择在同一个区域内负载较少的server。
- 第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
再创建两个提供方,分别是8002和8003
 ??打开8002项目,把8001项目下的pom.xml的所有依赖都粘贴到8002的pom.xml。其它的像src下的所有代码,包括resource文件夹下的,是不是都要粘贴过来呀,注意其中的application.yml中的port改为8002,如果有instance-id的话,也要改下。8003就不用说了,跟8002一模一样的操作。
新建8002的数据库和8003的数据库
??微服务的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事,从技术角度看就是一种小而独立的处理过程,类似进程概念,能够自行独立启动或销毁,拥有自己独立的数据库。 ??那么我们可以打开navicat,或者其它,反正我要开始创建db2和db3的数据库了,创建好后,sql语句还是当初我们在为db1数据库创表的语句是一样的,在前面我已经粘贴了,你们复制粘贴即可,好了之后,主要看的是db_source字段,通过该字段,可以判断它来自哪个数据库。 ??既然现在有三个数据库了,而这三个数据库分别对应三个提供方,所以,我们的8002项目是不是得把application.yml中的连接信息改为db2呀,8003的项目是不是改为db3呀。最后注意服务名不能改,都保持一致,也就是spring:application:name:,毕竟在前面的案例我们知道消费方是根据服务名去注册中心找对应的服务的,那么现在一个服务名就对应了三台服务,那么这时候要调用那一台服务不就可以体现出负载均衡的效果吗?
启动测试
??如果怕吃内存,那么我们eureka就只启动两台,分别是7001和7002。提供者也是一样,只启动两台,分别是8001和8002。消费者一台就足够。 ??先测试一下8001和8002是否能成功访问:http://localhost:8001/dept/list和http://localhost:8002/dept/list。没问题我们就开始从消费者的角度去访问咯。 ??访问http://localhost/consumer/dept/list,主要看db_source字段,是不是分别在db1和db2之间切换啊,说白了就是轮询,一个一个来,也就是说,ribbon的默认负载均衡策略就是轮询。
5.4 Ribbon的核心组件IRule
??IRule:根据特定算法中从服务列表中选取一个要访问的服务。默认的七种算法如下:
- RoundRobinRule:轮询
- RandomRule:随机
- AvailabilityFilteringRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问。
- WeightedResponseTimeRule:根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。刚启动时如果统计信息不足,会使用 RoundRobinRule策略,等统计信息足够,会切换WeightedResponseTimeRule。
- RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务。
- BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务。
- ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器。
??ribbon默认是轮询的,如果我想随机,在消费方的ConfigBean类加上如下代码:
@Bean
public IRule iRule(){
return new RandomRule();
}
5.5 Ribbon自定义
主启动类上增加如下注解
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = MySelfRule.class)
注意
??官方文档明确给出了警告,这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是说我们达不到特色化定制的目的了。说白了,就是我的这个自定义类不能跟主启动类在一个包下。因为@SpringBootApplication里就包含了@ComponentScan。 ??所有我们要新建一个包,就叫myself,自定义算法类如下:
package com.cht.myrule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MySelfRule {
@Bean
public IRule iRule(){
return new RandomRule();
}
}
??最终测试。 ??上面测试成功,进入下一个问题:依旧轮询策略,但是加上新需求,每个服务器要求被调用5次,也即以前是每台机器一次,现在是每台机器5次。
package com.cht.myrule;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class RandomRule_ZY extends AbstractLoadBalancerRule {
private int total = 0;
private int currentIndex = 0;
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
if (total < 5) {
server = upList.get(currentIndex);
total++;
} else {
total = 0;
currentIndex++;
if (currentIndex > upList.size()) {
currentIndex = 0;
}
server = upList.get(currentIndex);
}
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
server = null;
Thread.yield();
}
return server;
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
??然后看MySelfRule类,把new RandomRule()换成new RandomRule_ZY()。
6. Feign负载均衡
??Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单,它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可插拔式的编码器和解码器,Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。 ??Feign是一个声明式的Web服务客户端,使得编写Web服务客户端变得非常容易。只需要创建一个接口,然后在上面添加注解即可。 ??Feign旨在使编写Java Http客户端变得更容易。前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法,但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
编码
??参考springcloud-consumer-dept-80,新建springcloud-consumer-dept-feign。好,把dept-80的项目下的代码和依赖都粘贴到新模块里,这不用我说了吧。主要做的就是把ribbon的痕迹抹掉,因为这次我们采用的是feign来做负载均衡。那么就得把@LoadBalanced注解注释掉,启动类也一样,把@RibbonClient注释掉,同时myrule包也给删除掉。接下来就是导依赖了,如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
??修改springcloud-api工程,也就是接下来我们不是要写接口吗?接口呢我就写在api工程里,不写在springcloud-consumer-dept-feign模块里,所以api的pom.xml文件也要导入以上依赖。
package com.cht.springcloud.service;
import com.cht.springcloud.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
public interface DeptClientService {
@PostMapping("/dept/add")
boolean addDept(Dept dept);
@GetMapping("/dept/get/{id}")
Dept queryById(Long id);
@GetMapping("/dept/list")
List<Dept> queryAll();
}
??然后修改springcloud-consumer-dept-feign工程里的controller,添加上一步的DeptClientService接口,如下:
package com.cht.springcloud.controller;
import com.cht.springcloud.pojo.Dept;
import com.cht.springcloud.service.DeptClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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 {
@Autowired
private DeptClientService service;
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return service.queryById(id);
}
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return service.addDept(dept);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list() {
return service.queryAll();
}
}
??启动类要加上如下注解:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class DeptConsumer_80_Feign {
测试
??Feign集成了Ribbon,利用Ribbon维护了SPRINGCLOUD-PROVIDER-DEPT的服务列表信息,并且通过轮询实现了客户端的负载均衡,而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务的调用。 ??启动三个eureka集群。再启动两台提供方,或三台,随便你。最后,启动feign项目。访问:http://localhost/consumer/dept/list。
7. Hystrix断路器
Hystrix断路器断路器是什么
??复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败。 ??服务雪崩:多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。 ??对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。 ??Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败避免级联故障,以提高分布式系统的弹性。 ??“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
服务熔断
??熔断机制是应对雪崩效应的?种微服务链路保护机制。当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回”错误”的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。熔断机制的注解是@HystrixCommand。 ??开始编码。参考springcloud-provider-dept-8001项目新建springcloud-provider-dept-hystrix-8001项目,然后把springcloud-provider-dept-8001的代码拷贝到新项目就不说了。在pom.xml增加以下坐标:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
application.yml对instance-id进行修改:
instance-id: springcloud-provider-hystrix-dept8001
??接下来修改DeptController类里的queryById方法,如下:
@GetMapping("/dept/get/{id}")
@HystrixCommand(fallbackMethod = "processHystrix_Get")
public Dept queryById(@PathVariable("id") Long id){
Dept dept = this.deptService.queryById(id);
if(null == dept){
throw new RuntimeException("该ID:"+id+"没有对应的信息");
}
return dept;
}
public Dept processHystrix_Get(@PathVariable("id") Long id){
return new Dept().setDeptno(id).setDname("该ID:"+id+"没有对应的信息,null--@HystrixCommand")
.setDb_source("no this database in MySQL");
}
??启动类上加上如下注解:
....
@EnableCircuitBreaker
public class DeptProvider_hystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_hystrix_8001.class,args);
}
}
??开始测试,先启动7001号的eureka,启动一个就行了,再启动1个提供方,也就是我们上面写的hystrix-8001模块,最后启动一个消费者dept-80。全部启动后,访问http://localhost/consumer/dept/get/999,注意,我访问的是id为999的,但现在是不是没有id为999的记录呀,没有的话浏览器就会输出这几句话:{“deptno”:999,“dname”:“该ID:999没有对应的信息,null–@HystrixCommand”,“db_source”:“no this database in MySQL”}。 ??到这,我还是不得不强调一下,因为我只启动一台为7001的eureka,那么application.yml里的defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ 就要注释掉,否则怕后面测试的时候出现连接超时,毕竟7002和7003我没开启,所以我们就得换为以前的值。其它的提供方,和消费方也要看一下。
服务降级
??服务降级处理是在客户端实现完成的,与服务端没有关系。 ??整体资源快不够了,忍痛将某些服务先关掉,待度过难关,再开启回来。 ??打开api工程,在DeptClientService接口类上的注解@FeignClient加上fallbackFactory属性,如下:
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {
@PostMapping("/dept/add")
boolean addDept(Dept dept);
@GetMapping("/dept/get/{id}")
Dept queryById(@PathVariable("id") Long id);
@GetMapping("/dept/list")
List<Dept> queryAll();
}
??DeptClientServiceFallbackFactory类如下:
package com.cht.springcloud.service;
import com.cht.springcloud.pojo.Dept;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory<DeptClientService> {
@Override
public DeptClientService create(Throwable throwable) {
return new DeptClientService() {
@Override
public boolean addDept(Dept dept) {
return false;
}
@Override
public Dept queryById(Long id) {
return new Dept().setDeptno(id).setDname("该ID:"+id+"没有对应的信息,Consumer客户端提供的降级信息,此刻服务Provider已经关闭")
.setDb_source("no this database in MySQL");
}
@Override
public List<Dept> queryAll() {
return null;
}
};
}
}
??再打开dept-feign模块,修改application.yml,增加以下配置:
feign:
hystrix:
enabled: true
??最后测试,先启动eureka7001,然后启动提供端的8001模块,再启动消费端的feign_80模块,最后访问http://localhost/consumer/dept/get/1,相信能够正常访问。然后把提供端的8001模块给关闭了,也就是没有提供端了,那么这次再访问http://localhost/consumer/dept/get/1就会出现以上我定义的错误信息。 ??让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器。
服务监控hystrixDashboard
??除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续的记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。 ??开始编码,1,创建springcloud-consumer-hystrix-dashboard工程,pom.xml如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
</dependencies>
??application.yml如下:
server:
port: 9001
??启动类如下:
package com.cht.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class DeptConsumer_DashBoard {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_DashBoard.class,args);
}
}
??最后启动项目,访问:http://localhost:9001/hystrix,如果出现以下界面说明配置完成:  ??提供方要想被监控,得在pom.xml中加入以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
??提供方的application.yml要增加以下配置,如下:
management:
endpoints:
web:
exposure:
include: hystrix.stream
??接下来,测试,启动eureka7001,再启动提供方springcloud-provider-dept-hystrix-8001工程。全部启动后访问http://localhost:8001/actuator/hystrix.stream,一访问,如下: ??当然了,什么都没有,那是我们没有给它发请求,我们给它发请求试试:http://localhost:8001/dept/get/1。如下: ??但是,这种监控出来的方式并不直观,我们是不是更加习惯用一种可视化的方式体现出来呀,也就是把以上数据转化为可视界面,那怎么搞?如下: ??点击按钮进去,如下: 
8. Zuul路由网关
Zuul是什么
??Zuul包含了对请求的路由和过滤两个最主要的功能。 ??其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础,而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时Eureka中获得其它微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
Zuul路由基本配置
??创建springcloud-zuul-gateway-9527模块。pom.xml如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
??application.yml如下:
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: gateway-9527.com
prefer-ip-address: true
??启动类如下:
@SpringBootApplication
@EnableZuulProxy
public class Zuul_9527 {
public static void main(String[] args) {
SpringApplication.run(Zuul_9527.class,args);
}
}
测试
??启动两台eureka集群,一台8001提供者,再启动Zuul。全部成功启动后测试两个地址,第一个地址,是先测试是否能直接访问提供者:http://localhost:8001/dept/list,如果没问题的话,进入下一个访问地址,注意,这是重点,因为这个地址是通过网关zuul来间接访问提供者的,而不像第一个地址那样直接访问,地址为:http://localhost:9527/springcloud-provider-dept/dept/list,需要注意的是,地址上的springcloud-provider-dept就是你提供方的微服务名字,也就是spring:application:name:定义的应用名,你要访问哪个微服务,就写它的应用名,然后后面的/dept/list就不用说了吧。 ??但是我要改进一下,就是不要让地址暴露真实的微服务名称。application.yml如下:
zuul:
routes:
mydept.serviceId: springcloud-provider-dept
mydept.path: /mydept/**
??访问http://localhost:9527/mydept/dept/list,如果成功,那没问题,说明以上我们配置是正确的,但原来的http://localhost:9527/springcloud-provider-dept/dept/list依旧可以访问,那怎么办呢?如下:
zuul:
routes:
mydept.serviceId: springcloud-provider-dept
mydept.path: /mydept/**
ignored-services: springcloud-provider-dept
??如果想把所有的微服务名称都禁掉,可以用ignored-services: "*" 。
9. SpringCloud Config分布式配置中心
??微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的,动态的配置管理设施是必不可少的。SpringCloud提供了ConfigServer来解决这个问题,说白了,就是把公共的配置信息提取出来,为多个微服务共享。
服务端与Github通信
??创建好远程仓库后,进入,复制SSH地址。然后在本地硬盘目录上新建git仓库并clone,如下: ??进入创建好的目录,发现会有一个隐藏目录叫.git。如下: 
-
在该目录下新建application.yml,内容如下: spring:
profiles:
active: dev
---
spring:
profiles: dev
application:
name: springcloud-config-cht-dev
---
spring:
profiles: test
application:
name: springcloud-config-cht-test
??注意保存为utf-8的模式。 -
再把本地的application.yml上传到远程库中。如下命令:  -
新建Module模块springcloud-config-3344,即为cloud的配置中心模块,它也是个微服务。pom.xml如下: <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
-
application.yml如下: server:
port: 3344
spring:
application:
name: springcloud-config
cloud:
config:
server:
git:
uri: https://github.com/fenxianxian/springcloud-config.git
-
启动类如下: @SpringBootApplication
@EnableConfigServer
public class Config_3344 {
public static void main(String[] args) {
SpringApplication.run(Config_3344.class,args);
}
}
-
测试通过Config微服务是否可以从GitHub上获取配置内容。如下: ??如果是test,那地址就是http://localhost:3344/application-test.yml,如果地址为http://localhost:3344/application-333.yml,那么有profiles为333的应用吗?是不是没有,没有就如下: ??读取规则如下: ??application为文件名,profile为开发环境,label为分支名,比如:http://localhost:3344/application/dev/master。
客户端通过config服务端获得github上的配置
??在.git同级目录下再新建一个yml,叫springboot-config-client.yml,内容如下:
spring:
profiles:
active: dev
---
server:
port: 8201
spring:
profiles: dev
application:
name: springcloud-config-client
eureka:
client:
service-url:
defaultZone: http://eureka-dev.com:7001/eureka/
---
server:
port: 8202
spring:
profiles: test
application:
name: springcloud-config-client
eureka:
client:
service-url:
defaultZone: http://eureka-test.com:7001/eureka/
??接着推送到远程git仓库,就不说了。好,打开idea,继续创建新模块,叫springcloud-config-client-3355,pom.xml如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
??开始配置,但在此处引入一个新配置,叫bootstrap.yml,如下:
 ??bootstrap.yml内容如下:
spring:
cloud:
config:
name: springboot-config-client
profile: dev
label: master
uri: http://localhost:3344
??application.yml内容如下:
spring:
application:
name: springcloud-config-client
??编写代码,如下:
package com.cht.springcloud.rest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ConfigClientRest {
@Value("${spring.application.name}")
private String applicationName;
@Value("${eureka.client.service-url.defaultZone}")
private String eurekaServers;
@Value("${server.port}")
private String port;
@RequestMapping("/config")
public String getConfig(){
String str = "applicationName:"+applicationName+"\t eurekaServers:"+eurekaServers+"\t port:"+port;
System.out.println("*******str:"+str);
return str;
}
}
??测试如下:
package com.cht.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Config_Client_3355 {
public static void main(String[] args) {
SpringApplication.run(Config_Client_3355.class,args);
}
}
??最后先启动3344的模块,再启动刚刚写好的3355模块,访问:http://localhost:8201/config。不报错即成功。
|