Spring Cloud入门(含案例介绍及源码)
Spring Cloud入门(含案例介绍及源码)
1 微服务概述
就目前而言,对于微服务,在业界并没有 一个统一的、标准的定义。
微服务架构:通常而言,微服务架构是一种架构模式或者说是一种架构风格,它提倡将单一的应用程序划分成一组小的服务,每个服务运行在其独立的只属于它自己的进程中,各服务之间互相协调、互相配合,为用户提供最终价值。
微服务:从技术的角度来讲,微服务的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底的去耦合,进而保证每一个微服务提供单个业务功能的服务,一个服务做一件事。这种小而独立的处理过程类似进程的概念,其拥有自己独立的数据库,能够自行单独启动或销毁1。
微服务与微服务架构的区别:
微服务是微观的,强调的是服务的大小,关注的是某一点,针对拆分后的单个服务,而微服务架构是宏观的,针对的是多个微服务构成的一个整体,举个例子就是,微服务对应医院的一个个科室,微服务架构对应医院这个整体。
微服务的特点:
1. 每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。
2. 不同的服务可以使用不同的语言开发,也可以使用不同的数据存储。对一个具体的服务而言,应根据服务上下文,选择合适的语言、工具对其进行构建,尽量避免统一的、集中式的服务管理机制,而使用一个非常轻量级的集中式集中式管理来协调这些服务。
3. 服务之间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API,这一点与Doube基于RPC远程过程调用有着本质的区别)。
更为详细的内容可以参看马丁·福勒有关微服务的个人博客,博客地址:
https://martinfowler.com/articles/microservices.html
由于博客中有链接数目限制,博客中的部分链接加入了空格,请自行删除空格。
1.1 优缺点分析
优点:
1. 每个服务足够内聚、足够小、代码容易理解,这样能很容易的聚焦于一个指定的业务功能或业务需求。
2. 单个服务的开发简单,一个服务可能就是单一的只干一件事,因此相对于单体项目开发效率得到很大程度的提高。
3. 微服务能够被由2到5人组成的小团队单独开发。
4. 微服务是松耦合的,具有功能意义的服务,无论是在开发阶段还是部署阶段都是独立的。
5. 不同的服务可以使用不同的语言进行开发。
6. 易于和第三方集成,能够使用容易且灵活的方式集自动部署。
7. 微服务易于开发人员理解、修改和维护,小团队也更能够关注到自己的工作成果,不需要再只能通过合作体现价值。
8. 微服务只关注业务逻辑的代码,不会和HTML、CSS或其他界面组件混合。
9. 每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一的数据库。
缺点:
1. 开发人员要处理分布式系统的复杂性。
2. 多服务运维难度会随着服务的增加而增大,运维的压力也会随之增大。
3. 系统部署的依赖关系也会增加部署过程的成本。
4. 服务间的通信成本。
5. 数据的一致性。
6. 系统集成测试。
7. 性能监控。
2 SpringCloud概述
SpringCloud基于SpringBoot提供了一套微服务解决方案,包括服务注册与发现、配置中心、全链路监控、服务网关、负载均衡、熔断器等组件。
SpringCloid是分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶。
相关资料:
https ://springcloud.cc/spring-cloud-netflix.html
可用于查阅相关技术点
http ://www.springcloud.cn/
SpringCloud中国社区
https ://springcloud.cc/
SpringCloud中文网
2.1 SpringCloud和SpringBoot的区别
1. SpringBoot专注于快速、方便的开发单个个体微服务。
2. SpringCloud是关注全局的微服务协调治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来。为各个微服务之间提供配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务。
3. SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖的关系。
2.2 SpringCloud与Dubbo的区别
严格来说,两者各有优劣,而他们最大的区别就是SpringCloud抛弃了Dubbo的RPC通信,采用的是HTTP的REST方式。虽然从一定程度上来说 SpringCloud牺牲了服务调用的性能,但也避免了原生RPC带来的一系列问题,而且REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下显得更为合适。
SpringCloud能够与Spring Framework、 SpringBoot、SpringData等项目完美融合,进而提供分布式微服务架构下的一站式解决方案,而Dubbo需要借助第三方的框架来构建微服务架构。
相对于Dubbo,SpringCloud的涵盖面更广,有着更为强大的功能。虽然Dubbo在各环节的选择自由度很高,但是也存在着各种各样的问题,当然如果使用者的技术很高,那所有的问题都不再是问题。
SpringCloud是品牌机而Dubbo是组装机。
3 案例——基础构建
该案例的码云链接:https://gitee.com/sk_anruo/microservicecloud.git
基础构建的代码在base分支。
3.1 创建父项目(microservicecloud)
注意:
1. 在Eclipse新建项目时新建New Maven project
2. 父项目是一个聚合项目,所以Packaging应该选pom。
该项目的主要作用就是定义POM文件,将后续各个子模块公用的jar包等统一的提出来,类似一个抽象父类。
pom文件的具体内容请查阅源码。
3.2 创建公共模块(microservicecloud)
该模块用于抽取公共部分,在之后的模块中如果要使用这一部分内容,只需要引用该模块即可,无需再独自定义。
选中上述新建的父项目,鼠标右键,选择New–>Maven Module。
注意:
1. 在Eclipse新建项目时新建New Maven Module
2. 该项目继承父项目将,Packaging为jar。
3. 3 观察父项目的变化
- 目录结构变化,在microservicecloud项目下多了一个microservicecloud-api
- 在父项目的pom文件中有如下部分,表示microservicecloud包含于microservicecloud-api。
<modules>
<module>microservicecloud-api</module>
</modules>
3.4 在公共模块中创建实体类
在microservicecloud-api项目中创建Dept这一实体类。
/**
* @AllArgsConstructor:全参构造
* @NoArgsConstructor:无参构造
* @Data:Get和Set方法
* @Accessors(chain=true):允许链式风格访问
*/
@SuppressWarnings("serial")
//@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain=true)
public class Dept implements Serializable {
private Long deptno; // 主键
private String dname; // 部门名称
private String db_source;// 来自那个数据库,因为微服务架构可以一个服务对应一个数据库,同一个信息被存储到不同数据库
}
注意:
1. 必须实现序列化接口。
2. 在微服务中,数据可能会存在多个数据库,所以需要一个标识来记录所在数据库。
3. 执行mvn clean install命令,以达到可以被其他模块引用引用也就是公用的目的。
4. 如果其他模块也需要部门实体的话只需要引用本模块即可,不需要再独立定义。
链式访问:dept.setDeptno(11L).setdname(“AA-01”);
解决Eclipse中lombok不起作用的问题:https ://www.cnblogs.com/cccx/p/9669834.html
3.5 创建部门服务提供者模块(microservicecloud-provider-dept-8001)
除了模块名称不同,创建步骤和公共模块完全一致。
约定>配置>编码,因此,我们应先关注pom文件和yml配置文件,之后在关注编码。
pom文件具体内容请参看源码
server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路径
type-aliases-package: com.sk.springcloud.entities # 所有Entity别名类所在包
mapper-locations:
- classpath:mybatis/mapper/**/*.xml # mapper映射文件
spring:
application:
name: microservicecloud-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/cloudDB01 # 数据库名称
username: root
password: 123456
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
在src/main/resources/mybatis路径下创建mybatis.cfg.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>
由于Mybatis和Spring整合后几乎将所有相关配置信息都写到了Sping的配置文件中,所以这里的这个文件更多的只是一个摆设,但是以构建的规范来讲该文件必不可少。
运行MySql的数据库脚本,创建部门表。
CREATE DATABASE cloudDB01 CHARACTER SET utf8;
use cloudDB01;
CREATE TABLE dept(
deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
dname VARCHAR(60),
db_source VARCHAR(60)
);
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());
SELECT * FROM dept;
创建部门接口DeptDao。
import org.apache.ibatis.annotations.Mapper;
// 一定不能忘记加注解
@Mapper
public interface DeptDao {
public boolean addDept(Dept dept);
public Dept findById(Long id);
public List<Dept> findAll();
}
创建src/main/resources/mybatis/mapper/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">
<!-- 注意这里的namespace -->
<mapper namespace="com.sk.springcloud.dao.DeptDao">
<!-- 注意这里***Type如果没有特别指明所在的包需要写全类名 -->
<select id="findById" resultType="com.sk.springcloud.entitys.Dept" parameterType="java.lang.Long">
select deptno,dname,db_source from dept where deptno=#{deptno};
</select>
<select id="findAll" resultType="com.sk.springcloud.entitys.Dept">
select deptno,dname,db_source from dept;
</select>
<insert id="addDept" parameterType="com.sk.springcloud.entitys.Dept">
INSERT INTO dept(dname,db_source) VALUES(#{dname},DATABASE());
</insert>
</mapper>
创建DeptService.java
public interface DeptService {
public boolean add(Dept dept);
public Dept get(Long id);
public List<Dept> list();
}
创建DeptServiceImpl.java
创建DeptController.java
创建主启动类DeptProvider8001_App.java
具体源码请参看自行下载
3.6 服务者构建遇到的错误
- 1、错误: 找不到或无法加载主类 com.len.Application
解决方法:https ://blog.csdn.net/u012303775/article/details/81409652
- 2、Maven项目build时出现No compiler is provided in this environment
解决方法:https ://blog.csdn.net/lslk9898/article/details/73836745
3.7 部门服务消费者模块(microservicecloud-consumer-dept-80)
1. 除了模块名称不同,创建步骤和公共模块完全一致。
2. pom文件内容请见源码
3. yml配置文件内容如下:
server:
port: 80
4. 创建src/main/java/com/sk/springcloud/cfgbeans/ConfigBean.java
@Configuration
public class ConfigBean {//该类相当于Spring中applicationContext.xml
@Bean
public RestTemplate geRestTemplate() {
return new RestTemplate();
}
}
RestTemplate:提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集。
使用:
使用RestTemplate访问restful接口简单,(url,requestMap,ResponseBean.class)这三个参数分别代表REST请求地址,请求参数,HTTP响应转换被转换成的对象类型,下方有代码示例。
5. 创建DeptController_Consumer.java
由于这是一个消费者端,所以不存在service层,而是使用controller通过restful调用其他服务。
@RestController
public class DeptController_Consumer {
private static final String REST_URL_PREFIX = "http://localhost:8001";
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
// (url,requestMap,ResponseBean.class)
//这三个参数分别代表REST请求地址,请求参数,HTTP响应转换被转换成的对象类型
return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add", dept, Boolean.class);
}
@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/list")
public List<Dept> list() {
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list", List.class);
}
}
6. 创建主启动类DeptConsumer80_App.java
到这里,微服务的案例已经构建完成,之后会在该基础上加上Eurela、Feign等技术点。
4 Eureka服务注册与发现
Eureka是Netflix的一个子模块,也是核心模块之一,Netflix在设计Eureka时遵守的就是AP原则。Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务架构来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了。功能类似于dubbo的注册中心,比如Zookeeper。
更多有关Eureka的详细内容请查阅GitHub中的相关内容,网址如下:
https ://github.com/Netflix/eureka/wiki/Eureka-at-a-glance
4.1 Eureka基本架构
Eureka是Netflix公司采用C-S的设计架构开发的,Spring Cloud封装了Eureka模块来实现服务注册和发现。
Eureka Server作为服务注册功能的服务器,是服务注册中心,系统中的其它微服务使用Eureka的客户端连接到Eureka Server并维持心跳连接,这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。SpringCloud的一些其它模块(如Zuul)就可以通过Server来发现系统中的其它微服务,并执行相关的逻辑。
Eureka Client是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
- Eureka与Dubbo的架构对比
- Eureka三大角色
1. Eureka Server提供服务注册与发现。
2. Service Provider服务提供方将自身服务注册到Eureka,从而使服务消费方能够找到。
3. Service Consumer服务消费方从Eureka获取注册服务列表,从而能够消费服务。
4.2 Eureka的构建
源码仓库地址:https://gitee.com/sk_anruo/microservicecloud.git
在addEureka分支。
4.2.1 创建Eureka服务注册中心
- 创建注册到中心子模块
在之前完成基础构建的案例基础上创建子模块microservicecloud-eureka-7001,其创建步骤和公共模块一致,具体步骤可翻阅相关章节。 - 定义pom文件
如果我们想让该子模块作为Eureka注册中心就需要在pom文件中引入下面的依赖。
<!--eureka-server服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
pom文件的具体内容请参看源码。
以上述内容为例,在Spring Cloud中如果要引入一个技术组件基本都需要以下两步:
1. 新增一个相关的Maven坐标。
2. 在主启动类上标注的启动该新组件技术的相关注解标签。
- 创建application.yml配置文件并完成配置
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址(单机)。
- 创建EurekaServer7001_App主启动类
@EnableEurekaServer // 标注这是一个EurekaServer服务器端启动类,接受其它微服务注册进来
@SpringBootApplication
public class EurekaServer7001_App {
public static void main(String[] args) {
SpringApplication.run(EurekaServer7001_App.class, args);
}
}
- 测试
启动项目,在浏览器访问http://localhost:7001/ 如果出现如下的界面则注册中心构建成功。
4.2.2 服务注册
注册中心构建完成之后我们就可以将我们的部门服务提供者(microservicecloud-provider-dept-8001)注册到注册中心。
我们只需要在之前的microservicecloud-provider-dept-8001基础上进行一定的修改就可以达到服务注册的目的。
- 修改pom文件
<!-- 将微服务provider侧注册进eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<!-- 注意和服务端spring-cloud-starter-eureka-server的区别 -->
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
- 修改application.yml配置文件
在原有配置文件的基础上添加如下配置。
eureka:
client: #客户端注册进eureka服务列表内
service-url:
defaultZone: http://localhost:7001/eureka
# defaultZone对应的是注册中心对外暴露的地址,指定服务注册中心的地址
- 修改主启动类
在主启动类上添加@EnableEurekaClient注解。
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
public class DeptProvider8001_App {
对比注册中心的@EnableEurekaServer,可以很明显的看出Eureka是C-S结构。
- 测试
先启动注册中心,即microservicecloud-eureka-7001,再启动服务提供者,即microservicecloud-provider-dept-8001,之后访问http://localhost:7001/
这个注册的服务名是因为microservicecloud-provider-dept-8001中的yml配置文件中配置了服务名称,详情参见基础构建部分相应工程的配置文件(点击跳转),部分配置内容如下:
spring:
application:
name: microservicecloud-dept
在完成服务注册之后,若长时间未操作Eureka的管理界面会出现如下的红色提示信息。
这里的红色提示信息不是错误也不是异常而是Eureka的自我保护机制。有关保护机制请参看本文章后续章节。
4.2.3 actuator与注册微服务信息完善
- 主机名称:服务名称修改
在服务注册到Eureka的注册中心时除了配置的服务名外Eureka还会生成一个服务标识,默认情况下该标识较长不便于辨认,效果如下图所示。
DESKTOP-4PSLEP8是主机名,即计算机属性中的计算机名。
为了更好的辨认该标识,我们可以对该名称进行自定义。只需要对microservicecloud-provider-dept-8001中的yml配置文件进行一些修改就可达到自定义服务标识名称的目的,更改如下:
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 添加下方内容以自定义Eureka中服务的标识名称为microservicecloud-dept8001
instance:
instance-id: microservicecloud-dept8001
修改之后在Eureka中服务的标识名就会不在使用默认的值而是使用我们自定义的,效果请看下图。
- 访问信息有IP信息提示
从上图可以看到左下角的状态提示信息是以电脑名开头的,但我们更希望能够看到IP信息,此时我们可以在microservicecloud-provider-dept-8001中的yml配置文件中添加prefer-ip-address: true这一项配置。
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
instance-id: microservicecloud-dept8001
prefer-ip-address: true #添加该行配置以使访问路径可以显示IP地址
修改配置后的效果如下图所示,可以明显的看到红色方框中已经显示IP信息。
- 微服务info内容详细信息
如上图所示,服务注册到Eureka注册中心之后可以在管理界面查看,默认情况下点击上图红色方框处的服务标识会跳转到一个错误界面,界面如下:
我们更想要的是点击服务标识时跳转到一个服务信息介绍页面,为此我们需要对上面构建的项目进行修改,步骤如下。
- 修改microservicecloud-provider-dept-8001的pom文件,添加actuator依赖。
<!-- actuator监控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 修改父工程microservicecloud的pom文件,添加构建build信息。
<build>
<!-- microservicecloud是父工程的名字 -->
<finalName>microservicecloud</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<delimiters>
<delimit>$</delimit>
</delimiters>
</configuration>
</plugin>
</plugins>
</build>
上面的代码定义了一些构建信息,是通过maven-resources-plugin插件来实现解析和解读的,效果是解析src/main/resources路径下配置文件中以$
开头且以$
结尾的信息,下面将给出例子辅助理解。
3. 修改microservicecloud-provider-dept-8001的yml配置文件
info:
app.name: sk-microservicecloud
company.name: www.sk.com
build.artifactId: $project.artifactId$
build.version: $project.version$
解析:
1. 我们在父工程中添加了一段构建信息,那么其子类也会继承该构建信息。
2. 我们添加的构建信息规定会解析src/main/resources路径下配置文件中以$
开头且以$
结尾的信息,所以microservicecloud-provider-dept-8001中我们在yml配置文件中添加的配置信息最后两行会被解析。
$project.artifactId$
: 获取当前项目的artifactId信息(项目Maven坐标信息中的一项)。$project.version$
: 获取当前项目的版本信息
3. 当我们点击Eureka管理界面上的项目表示超链接时跳转的页面链接是:localhost:8001/info,而我们在配置文件中添加的配置信息的根标签也是info,也就是说点击超链接后会解析我们在配置文件中添加的信息,之后将信息显示到页面。
4.2.4 Eureka自我保护机制
在上面的章节提到过,当服务注册到Eureka注册中心之后若长时间没有操作,其管理界面会出现红色的提示信息。
还有一种情况是如果我们将instance-id: microservicecloud-dept8001修改为instance-id: microservicecloud-dept8001XXX之后再改回来,那么Eureka管理界面也会给出提示。
instance-id: microservicecloud-dept8001 : 该配置定义服务在Eureka注册中心的标识名称为microservicecloud-dept8001,详情参见4.2.3章节。
我们可以看到,对于掉线的服务(microservicecloud-dept8001XXX名称被改后算是这个名称的服务掉线了)Eureka没有抛弃,而是依旧保留。
默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,如此以上注销的行为就可能会变得危险了,因为微服务本身其实是健康的,此时本不应该注销这个服务。
针对上述问题,Eureka通过“自我保护机制”来解决。当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式,该模式下Eureka Server会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何服务)。当网络故障恢复后,该Eureka Server节点自动退出自我保护模式。
Eureka这种宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例的特性说简单点就是好死不如赖活着。
综上,自我保护机制就是一种应对网络异常的安全保护措施,使用该机制能够是Eureka集群更加健壮、稳定。
在Spring Cloud中我们可以在配置文件中添加eureka.server.enable-self-preservation = false来禁用自我保护机制。
4.2.5 Discovery——服务发现
对于注册进Eureka里面的微服务,可以通过微服务发现来获得该服务的信息。
- 修改microservicecloud-provider-dept-8001的DeptController
注入入DiscoveryClient
@Autowired
private DiscoveryClient client;
注意
DiscoveryClient
的包是org.springframework.cloud.client.discovery.DiscoveryClient
添加discovery方法
@RequestMapping(value = "/dept/discovery", method = RequestMethod.GET)
public Object discovery()
{
List<String> list = client.getServices();
System.out.println("**********" + list);
List<ServiceInstance> srvList = client.getInstances("MICROSERVICECLOUD-DEPT");
for (ServiceInstance element : srvList) {
System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t"
+ element.getUri());
}
return this.client;
}
- 主启动类添加@EnableDiscoveryClient注解
@EnableDiscoveryClient
@SpringBootApplication
@EnableEurekaClient
public class DeptProvider8001_App {
- 自测
- 启动注册中心
- 启动服务提供者
- 访问http://localhost:8001/dept/discovery
网页显示信息:
{
"services":["microservicecloud-dept"],
"localServiceInstance":{
"host":"192.168.1.5","port":8001,
"uri":"http://192.168.1.5:8001",
"metadata":{},
"serviceId":"microservicecloud-dept",
"secure":false
}
}
控制台输出内容:
**********[microservicecloud-dept]
MICROSERVICECLOUD-DEPT 192.168.1.5 8001 http://192.168.1.5:8001
- 1
- 2
从结果可以看出,Discovery是可以发现到Eureka注册中心的服务信息的。当然这只是提供者自身访问自身的接口,之后将对消费者进行修改,以使其能够访问该接口。
- 修改microservicecloud-consumer-dept-80的DeptController_Consumer
private static final String REST_URL_PREFIX = "http://localhost:8001";
// 测试@EnableDiscoveryClient,消费端可以调用服务发现
@RequestMapping(value = "/consumer/dept/discovery")
public Object discovery()
{
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/discovery", Object.class);
}
之后访问http://localhost/consumer/dept/discovery,可以同样访问到注册的服务信息。
- 总结
虽然点击上图红色框中的超链接可以查看服务信息,但这只限于自己人,其他人是没办法通过点击这个超链接来查看服务信息的,此时就可以通过Discovery实现服务发现,以供其他人查看服务信息。
4.3 Eureka集群配置
集群就是指在不同的机器或者服务器上配置相同的服务对外做一个超大运算的整体。
将一套系统拆分成不同子系统部署在不同服务器上(这叫分布式),然后部署多个相同的子系统在不同的服务器上(这叫集群),部署在不同服务器上的同一个子系统应做负载均衡。
分布式:一个业务拆分为多个子业务,部署在多个服务器上 。
集群:同一个业务,部署在多个服务器上 。
详情可参考https://blog.csdn.net/jiangyu1013/article/details/80417961
- 新建microservicecloud-eureka-7002/microservicecloud-eureka-7003
创建步骤和之前所有子项目一致,这里就不在重复。 - 按照7001为模板粘贴POM到新建的两个子项目中
注意只需要粘贴依赖,即如下内容。
<dependencies>
<!--eureka-server服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
- 修改7002和7003的主启动类
我们可以直接将7001项目中的主启动类文件复制到7002和7003中,之后修改主类的名称以便区分。 - 修改映射配置
我们知道在之前的案例中,microservicecloud-eureka-7001项目的配置文件中配置的服务端的示例名称为localhost,这么配置在只有一个Eureka Server时(也就是单机)没问题,如果有多个就会出现重名的问题,此时我们可以通过修改映射配置来解决,步骤如下:
- 找到C:\Windows\System32\drivers\etc路径下的hosts文件
- 修改映射配置添加进hosts文件
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
只需要将上述内容粘贴到host文件中就可以,作用是给127.0.0.1三个别名。
- 3台eureka服务器的yml配置
之前我们只有一台Eureka,如果它挂了,也就是出现异常无法正常工作了,那我们就没有注册中心了。为了保证某一台注册中心挂掉之后系统的使用不会受到影响,我们就需要保证注册中心的高可用。
Eureka Server的设计一开始就考虑了高可用问题,在Eureka的服务治理设计中,所有节点即是服务提供方,也是服务消费方,服务注册中心也不例外。
Eureka Server的高可用实际上就是将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可用的效果。
7001的配置文件修改如下,被注释的是单机版时的配置,注释下方是多台时的配置。
server:
port: 7001
eureka:
instance:
#hostname: localhost #eureka服务端的实例名称,单机时可以命名为localhost
hostname: eureka7001.com #在有多台Eureka时我们就需要为每一台单独命名
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址(单机)。
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
7002的配置文件修改如下:
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/
7003的配置文件修改如下:
server:
port: 7003
eureka:
instance:
hostname: eureka7003.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
由于我们之前做了映射配置,所以虽然我们三台Eureka的名称不同,但是所访问的IP是一样的。做映射的原因也是因为我们是在同一台计算机上布置了三台Eureka,这也导致每个Eureka的IP都是一样的,为了防止冲突,我们就利用映射配置为IP取了三个别名。
- 修改microservicecloud-provider-dept-8001的配置文件
我们在配置好三台Eureka之后还需要对服务提供者进行配置,以将微服务发布到上面三台eureka集群配置中。
对提供者yml配置文件的修改如下:
eureka:
client: #客户端注册进eureka服务列表内
service-url:
#defaultZone: http://localhost:7001/eureka # 单机时使用这个配置,多台时使用下方的配置,将会注册到所有列举的Eureka
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
- 测试
先将三台Eureka服务器分别启动,之后启动8001即提供者,接着我们就可以在浏览器中分别访问三个Eureka,截图如下:
7001:
7002:
7003:
至此,Eureka的集群已经搭建完成。
对于控制台报的com.sun.jersey.api.client.ClientHandlerException: java.net.SocketTimeoutException: Read timed out错误可不用理会,这是Eureka的自我保护机制造成的。
4.4 Eureka和Zookeeper对比
著名的CAP理论指出,一个分布式系统不可能同时满足C(强一致性)、A(可用性)、和P(分区容错性)。由于分区容错性P在分布式系统中是必须要保证的,因此我们只能在A和C之间进行权衡。
官网提供的经典CAP图片如下:
更多有关CAP和ACID的信息请参考之后的博客,地址为:(暂无)
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务不可用。因此服务注册功能对可用性的要求要高于一致性。
Zookeeper保证的是CP,Eureka是AP。下面将对两者做一些简介。
Zookeeper: 在Zookeeper中,当master节点因为网络故障与其它节点失去联系时剩余节点会重新进行leader选举,而这个选举所持续的时间较长(30~120s)且选举期间整个Zookeeper都是不可用,也就是说在选举期间注册服务处于瘫痪状态。虽然最终能够恢复服务,但漫长的选举时间导致的注册长期不可用是不能被容忍的,而且在云部署的情况下,有着很大的概率出现因网络问题导致Zookeeper集群失去master节点。
Eureka: 在Eureka中,优先保证可用性,其各个节点都是平等的,因此就算几个节点挂掉也不会影响正常节点的工作。当Eureka客户端向某个Eureka注册时,如果连接失败,其就会自动切换到其它节点,因此只要有一台Eureka还在,注册服务就可用,这也就保证了Eureka的可用性。当然,在保证可用性的同时,Eureka失去了强一致性,也就是说虽然只要有一台Eureka存在,注册服务就可用,但并不能保证查到的信息是最新的。除此之外,Eureka还有一套保护机制,即如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时Eureka会做出如下处理。
- 不再移除注册列表中因长时间没收到心跳过期的服务。
- 仍接受新服务的注册和查询请求,但不会同步到其它节点上,只是保证当前节点可用。
- 网络稳定时,当前示例新的注册信息会被同步到其它节点中。
综上:
Eureka能够很好的应对因网络故障导致的部分节点失去联系的情况,但舍弃了强一致性。Zookeeper保证了强一致性,但在因网络问题导致master节点丢失时会引起注册服务的瘫痪。
5 Ribbon负载均衡
负载均衡(Load Balancej简称LB)简单的说就是将用户的请求平摊的分配到多个服务上,进而保证系统的HA(High Available 指高可用性集群)。在微服务或分布式集群中负载均衡是经常用的一种应用,常见的负载均衡有软件Nginx,LVS,硬件 F5等。
负载均衡(LB):
1. 集中式LB:
在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方。
2. 进程内LB
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
注:Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供了一系列完善的配置项,如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会基于某种规则自动的帮助你(如简单轮询,随机连接等)去连接这些机器,我们也很容易使用Ribbon实现自定义的负载均衡算法。
SpringCloud的负载均衡算法可以自定义。
更多的Ribbon资料可以到其GitHub的wiki翻阅,链接:https://github.com/Netflix/ribbon/wiki/Getting-Started
5.1 Ribbon基础配置
源码地址:https://gitee.com/sk_anruo/microservicecloud.git
在addRibbon分支。
注意Ribbon是一套客户端的负载均衡工具,所以我们的目标项目应是客户端,对于这里的案例来说就是microservicecloud-consumer-dept-80,我们只要在目标项目上进行相应修改就能加入Ribbon,具体步骤如下:
- 修改pom.xml文件
在目标项目引入Ribbon的的依赖,依赖信息如下:
<!-- Ribbon相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
Ribbon需要和Eureka整合,所以需要Eureka的依赖,而Eureka又和spring-cloud-starter-confi有联系,所以这里就把这些依赖放到了一起。
- 修改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注解
@Configuration
public class ConfigBean {
@Bean
@LoadBalanced //开启负载均衡
public RestTemplate geRestTemplate() {
return new RestTemplate();
}
}
- 在主启动类DeptConsumer80_App添加@EnableEurekaClient注解
@SpringBootApplication
@EnableEurekaClient // 开启Eureka,这里是客户端。
public class DeptConsumer80_App {
- 修改DeptController_Consumer客户端访问类
@RestController
public class DeptController_Consumer {
//在没有使用Eureka时我们使用具体的地址信息来访问接口
//private static final String REST_URL_PREFIX = "http://localhost:8001";
//使用Eureka之后我们就可以通过服务名来访问接口了
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";
- 测试
先启动3个eureka集群后,再启动microservicecloud-provider-dept-8001并注册进eureka,之后启动microservicecloud-consumer-dept-80,最后打开浏览器访问http://localhost/consumer/dept/get/1和http://localhost/consumer/dept/list这两个链接。如果一切正常,我们就能够成功调用接口,因为此时我们已经将详细的接口地址信息改成了服务名,故此时是通过服务名来访问到提供者接口的。 - 总结
Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号。
5.2 Ribbon负载均衡
Ribbon在工作时分成两步:
第一步:选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.
第二步:根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。
只有在拥有多个服务提供者的基础上负载均衡才能起到作用,下面开始实现Ribbon的负载均衡。
- 保证有多个服务提供者
参考microservicecloud-provider-dept-8001,新建两个项目,这就是另外两个服务提供者,端口分别为8002和8003。
pom文件的依赖,业务逻辑代码,配置文件可以从8001中复制,但是一定要注意端口、名称等的修改,之后会给出详细的修改规则。
- 为每个服务创建各自的数据库
DROP DATABASE IF EXISTS cloudDB02;
CREATE DATABASE cloudDB02 CHARACTER SET UTF8;
USE cloudDB02;
CREATE TABLE dept
(
deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
dname VARCHAR(60),
db_source VARCHAR(60)
);
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());
SELECT * FROM dept;
对应三个服务的数据库名称分别为cloudDB01、cloudDB02、cloudDB03,这三个数据库的创建脚本除了数据库名称不同,其它内容一致。
- 修改8002和8003中的yml配置文件
1. 修改端口,为不同的提供者(这里的三个项目)指定不同的端口
server:
port: 8001 #根据需求制定服务端口,在本案例中在三个项目中分别为8001、8002、8003
2. 修改数据库链接,使得每个提供者服务都有各自的项目
url: jdbc:mysql://localhost:3306/cloudDB01 #在本案例中,有cloudDB01、cloudDB02、cloudDB03
3. 修改服务在Eureka中的标识以区别不同的服务
instance-id: microservicecloud-dept8001 #定义Eureka中的服务标识,本案例有***01、***02、***03
4. 对外暴露的统一的服务实例名!!!
spring:
application:
name: microservicecloud-dept #一定要注意对外暴露的服务实例名一致
5. 测试
先启动3个eureka,再启动3个Dept提供者服务,完成后发出http://localhost:8001/dept/list
、http://localhost:8002/dept/list
、http://localhost:8003/dept/list
三个请求完成服务提供者的自测,最后启动microservicecloud-consumer-dept-80并在浏览器访问http://localhost/consumer/dept/list,注意观察返回的数据库名字是否相同,若不同则负载均衡实现。
- 总结
在该案例中我们有三个服务提供者即microservicecloud-provider-dept-8001、microservicecloud-provider-dept-8002、microservicecloud-provider-dept-8003,他们能向外提供一样的服务。需要强调的是这三个提供者向外暴露的服务接口是一致的,也就是在Eureka中有着同样的外部能识别的服务名,而从Eureka的角度来看,在一个服务名下挂了三个服务实例,当外部请求方位这个服务名时就可以通过负载均衡将请求分配给三个实例中的一个进行处理。
Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
5.3 Ribbon核心组件IRule
Spring Cloud结合Ribbon默认提供了七种负载均衡策略,而根据特定算法从服务列表中选取一个要访问的服务,下面将对其中轮询策略进行简介。
策略名 | 描述 |
---|---|
RoundRobinRule | 轮询 |
RandomRule | 随机 |
AvailabilityFilteringRule | 先过滤掉由于多次访问故障而处于断路器断开状态的服务以及并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问。 |
WeightedResponseTimeRule | 根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。刚启动时如果统计信息不足则使用RoundRobinRule策略,等统计信息足够,会切换到WeightedResponseTimeRule |
RetryRule | 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内重试,获取好的服务而避开不可使用的。 |
BestAvailableRule | 先过滤掉由于多次访问故障而处于断路器断开状态的服务,然后选择一个并发量最小的服务。 |
ZoneAvoidanceRule | 默认规则,复合判断server所在区域的性能和server的可用性选择服务器。 |
- 切换负载均衡策略
Ribbon是一套客户端负载均衡工具,因此我们切换负载均衡策略要在消费者端进行。基于上面的案例,接下来将切换默认的轮询策略为RandomRule策略。
我们只需要在microservicecloud-consumer-dept-80中添加一个目标负载均衡策略组件皆可以达到切换负载均衡策略的目的。在Spring Cloud中,如果我们没有指定Ribbon的负载均衡策略,默认会使用轮询,如果我们指定了就会使用我们指定的。
@Configuration
public class ConfigBean {// 该类相当于Spring中applicationContext.xml
@Bean
@LoadBalanced // 开启负载均衡
public RestTemplate geRestTemplate() {
return new RestTemplate();
}
/**
* 指定负载均衡算法,注意要使用@Bean注解将该组件添加到容器中。
*
* @return
*/
@Bean
public IRule myIRule() {
return new RandomRule();
}
}
在Spring Cloud中切换负载均衡策略只需要如上添加一个myIRule方法即可。
5.4 Ribbon自定义负载均衡策略
在上述案例的基础上修改microservicecloud-consumer-dept-80。
- 在主启动类添加@RibbonClient注解
//在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效
//name指定目标服务,configuration指向我们自定义的负载均衡规则
//需要注意的是我们自定义的MySelfRule配置类不能放在@ComponentScan所扫描的当前包下以及子包下即不能放在主启动类所在包或其子包下。
@RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=MySelfRule.class)
public class DeptConsumer80_App {
- 创建自定义配置类MySelfRule
一定要注意,该配置类不能放在@ComponentScan所扫描的当前包下以及子包下即不能放在主启动类所在包或其子包下(com.sk.springcloud包)。因为在Spring Cloud中,当启动服务时就会去加载我们的自定义Ribbon配置类,如果我们的配置类放在@ComponentScan扫描的包下,配置类会被所有的Ribbon客户端共享,这样我们就无法达到特殊化定制的目的了。
在本案例中创建com.sk.com.sk.myrule包放置自定义的配置类。
@Configuration
public class MySelfRule {
@Bean
public IRule myRule() {
return new RandomRule();// Ribbon默认是轮询,我自定义为随机
}
}
- 自定义规则+
需求: 使用轮询策略,但是要加上新需求,要求每台服务器被调用五次后才会有轮询策略选择下一个服务器,即每五次换一台服务器。
思路: 在官网轮询策略的源码的基础上做出修改,进而定制自己的规则。
官网源码链接:https://github.com/Netflix/ribbon/blob/2.x/ribbon-loadbalancer/src/main/java/com/netflix/loadbalancer/RandomRule.java
源码如下:
public class RandomRule extends AbstractLoadBalancerRule {
Random rand;
public RandomRule() {
rand = new Random();
}
/**
* Randomly choose from all living servers
*/
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
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.getServerList(true);
List<Server> allList = lb.getServerList(false);
int serverCount = allList.size();
if (serverCount == 0) {
/*
* No servers. End regardless of pass, because subsequent passes
* only get more restrictive.
*/
return null;
}
int index = rand.nextInt(serverCount);
server = upList.get(index);
if (server == null) {
/*
* The only time this should happen is if the server list were
* somehow trimmed. This is a transient condition. Retry after
* yielding.
*/
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
根据需求,对源码进行部分修改,如下:
public class RandomRule_ZY extends AbstractLoadBalancerRule
{
// total = 0 // 当total==5以后,我们指针才能往下走,
// index = 0 // 当前对外提供服务的服务器地址,
// total需要重新置为零,但是已经达到过一个5次,我们的index = 1
// 分析:我们5次,但是微服务只有8001 8002 8003 三台,OK?
//
private int total = 0; // 总共被调用的次数,目前要求每台被调用5次
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) {
/*
* No servers. End regardless of pass, because subsequent passes only get more
* restrictive.
*/
return null;
}
// int index = rand.nextInt(serverCount);// java.util.Random().nextInt(3);
// server = upList.get(index);
// private int total = 0; // 总共被调用的次数,目前要求每台被调用5次
// private int currentIndex = 0; // 当前提供服务的机器号
if(total < 5)
{
server = upList.get(currentIndex);
total++;
}else {
total = 0;
currentIndex++;
if(currentIndex >= upList.size())
{
currentIndex = 0;
}
}
if (server == null) {
/*
* The only time this should happen is if the server list were somehow trimmed.
* This is a transient condition. Retry after yielding.
*/
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key)
{
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig)
{
// TODO Auto-generated method stub
}
}
这里虽然是使用RandomRule作为模板,但是修改之后的算法实现的是轮询,这里只是使用RandomRule作为模板。
详细代码可去代码仓库下载,链接在本章开始(第五章的开始处)。
6 Feign负载均衡
根据官网的介绍可知,Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
官网地址:http://projects.spring.io/spring-cloud/spring-cloud.html#spring-cloud-feign
GitHub地址:https://github.com/OpenFeign/feign
Ribbon的功能强大,甚至能够自定义算法,我们使用Ribbon就能够进行负载均衡,为什么Feign会出现呢?因为Feign是面向接口的,使用更为便捷,大多数人更能接受面向接口编程,所以Feign出现了。
Feign旨在使编写Java Http客户端变得更容易。前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(只需要在微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign集成了Ribbon,利用Ribbon维护了MicroServiceCloud-Dept的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
6.1 Feign的使用
接下来会在之前案例的基础上添加Feign,步骤如下:
- 新建microservicecloud-consumer-dept-feign
1. 可以参考microservicecloud-consumer-dept-80项目创建我们的microservicecloud-consumer-dept-feign,之后进行修改来加入Feign。新建项目的主类名为DeptConsumer80_Feign_App。
2. pom文件添加Feign的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
- 修改microservicecloud-api工程
1. 在pom文件中添加Feign的依赖,坐标如上。
2. 新建DeptClientService接口并新增注解@FeignClient
package com.sk.springcloud.service;
import java.util.List;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.sk.springcloud.entitys.Dept;
@FeignClient(value = "MICROSERVICECLOUD-DEPT")
public interface DeptClientService {
@RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
public Dept get(@PathVariable("id") long id);
@RequestMapping(value = "/dept/list", method = RequestMethod.GET)
public List<Dept> list();
@RequestMapping(value = "/dept/add", method = RequestMethod.POST)
public boolean add(Dept dept);
}
3. 由于microservicecloud-api工程是公共的,所以我们更新之后为了其他模块能够正常使用需要执行mvn clean之后执行mvn install。
- 修改microservicecloud-consumer-dept-feign工程
1. 修改Controller,添加上一步新建的DeptClientService接口
package com.sk.springcloud.controller;
import java.util.List;
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 com.sk.springcloud.service.DeptClientService;
import com.sk.springcloud.entitys.Dept;
@RestController
public class DeptController_Consumer {
@Autowired
private DeptClientService service = null;
@RequestMapping(value = "/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return this.service.get(id);
}
@RequestMapping(value = "/consumer/dept/list")
public List<Dept> list() {
return this.service.list();
}
@RequestMapping(value = "/consumer/dept/add")
public Object add(Dept dept) {
return this.service.add(dept);
}
}
2. 主启动类添加@EnableFeignClients注解
@SpringBootApplication
@EnableEurekaClient //开启Eureka,这里是客户端。
@EnableFeignClients
public class DeptConsumer80_Feign_App {
- 测试
先启动3个eureka集群,之后启动3个部门微服务8001/8002/8003,接着启动新建的feign工程,如果启动正常,在浏览器访问http://localhost/consumer/dept/list则一切正常。 - 总结
Feign通过接口的方法调用Rest服务(之前是Ribbon+RestTemplate),请求发送给Eureka服务器(http://MICROSERVICECLOUD-DEPT/dept/list
),之后通过Feign直接找到服务接口。由于在进行服务调用的时候融合了Ribbon技术,所以也支持负载均衡作用。
7 Hystrix断路器
在复杂的分布式体系结构中,应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败,此时就有可能会发生服务雪崩。
服务雪崩:多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,这就是所谓的“雪崩效应”.
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不会影响整个应用程序或系统。
为解决上述问题,Spring Cloud整合了Hystrix,它是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hystrix的GitHub地址:https://github.com/Netflix/Hystrix/wiki
7.1 服务熔断
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回"错误"的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。熔断机制的注解是@HystrixCommand。下面将给出相关案例的构建步骤。
- 新建microservicecloud-provider-dept-hystrix-8001
参考microservicecloud-provider-dept-8001新建microservicecloud-provider-dept-hystrix-8001工程。
需要注意的是Ribbon和Feign是客户端的负载均衡工具,它们是在消费者端使用,而Hystrix则是提供者端的保护机制。
- 在pom文件添加依赖
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
- 修改yml配置文件
这里修改yml配置文件只是修改服务在Eureka中的实例名,以便于区分。
instance:
instance-id: microservicecloud-dept8001-hystrix #自定义Hystrix相关的服务名称信息
- 修改DeptController
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
/**
* @HystrixCommand :在需要做熔断处理的方法上添加该注解
* fallbackMethod = "processHystrix_Get":指定出现错误或异常后的回调函数
*/
@RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
@HystrixCommand(fallbackMethod = "processHystrix_Get")
public Dept get(@PathVariable("id") Long id) {
Dept dept = this.deptService.get(id);
// 如果要查询的ID不存在则抛出异常,此时Hystrix的熔断机制会进行处理,调用fallbackMethod指定的方法。
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注解
@EnableCircuitBreaker//对hystrixR熔断机制的支持
public class DeptProvider8001_App {
- 测试
1. 启动三台Eureka。
2. 启动新建的microservicecloud-provider-dept-hystrix-8001。
3. 启动消费者microservicecloud-consumer-dept-80。
4. 浏览器访问http://localhost/consumer/dept/get/112
7.2 服务降级
服务降级我们可简单的理解为当资源紧缺时先关掉一些不是太重要的服务以节约资源,待度过资源紧缺期后再重新开启。当然被暂时关掉的服务不能没有一点的响应,应该在被访问时给出响应的提示。
服务降级处理是在客户端实现完成的,与服务端没有关系。
在上一节中,虽然能够在服务出现异常时进行熔断处理,但是每添加一个方法我们都需要添加个@HystrixCommand(fallbackMethod = “processHystrix_Get”)并添加与之对应的处理方法,此时就很容易发生方法膨胀,为解决这一问题,我们可以结合Spring的切面编程思想实现业务方法和熔断处理方法的解耦。
上面我们处理服务熔断时是在Controller做的绑定,如果我们在service的接口处做绑定,当被调用的接口出问题时将使用绑定的处理方法进行处理,此时业务代码和处理代码就能够很好的解耦,由于服务降级是在客户端实现的,所以我们可以将公共模块中的service层与我们定义的服务降级处理做绑定来完成服务降级,下面将给出具体内容。
- 修改microservicecloud-api工程
根据已经有的DeptClientService接口新建一个实现FallbackFactory接口的类DeptClientServiceFallbackFactory。
@Component//不要忘记添加!!!
public class DeptClientServiceFallbackFactory implements FallbackFactory<DeptClientService> {
@Override
public DeptClientService create(Throwable arg0) {
return new DeptClientService() {
@Override
public List<Dept> list() {
// TODO Auto-generated method stub
return null;
}
@Override
public Dept get(long id) {
return new Dept().setDeptno(id).setDname("该ID:" + id + "没有没有对应的信息,Consumer客户端提供的降级信息,此刻服务Provider已经关闭")
.setDb_source("no this database in MySQL");
}
@Override
public boolean add(Dept dept) {
// TODO Auto-generated method stub
return false;
}
};
}
}
注意:一定不要忘记加@Component注解!!!
- 修改microservicecloud-api工程
在DeptClientService接口的@FeignClient注解中添加fallbackFactory属性值,以此关联之前新建的类,进而绑定相应的处理方法。
@FeignClient(value = "MICROSERVICECLOUD-DEPT",fallbackFactory=DeptClientServiceFallbackFactory.class)
public interface DeptClientService {
注意在公共模块microservicecloud-api改动之后重新install。
在Feign的starter整合包中包含Hystrix的依赖。
- 修改microservicecloud-consumer-dept-feign工程的YML配置文件
feign:
hystrix:
enabled: true # 开启hystrix
- 测试
1. 先启动3个eureka
2. 启动microservicecloud-provider-dept-8001
3. 启动microservicecloud-consumer-dept-feign
4. 浏览器访问http://localhost/consumer/dept/get/1
5. 故意关闭微服务microservicecloud-provider-dept-8001后在访问上述链接。
在故意关闭服务后,客户端自己调用提示,此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器。
从上面的代码我们可以很清楚的看到业务代码就单纯的是业务逻辑,和我们的断路器的相关代码完全的解耦。
7.3 熔断和降级的区别
服务熔断:一般是某个服务故障或者异常引起,类似现实中的“保险丝”。当某个异常条件被触发,熔断机制会直接熔断整个服务而不是一直等到此服务超时。
服务降级:降级一般是从整体负荷考虑,就是当某个服务被熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值,这样做,虽然服务水平下降,但总比直接不可用要好一些。
7.4 服务监控Hystrix Dashboard
除了隔离依赖服务的调用以外,Hystrix还提供了实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面,下面将给出一个使用案例。
- 新建工程microservicecloud-consumer-hystrix-dashboard
该工程是一个单独的服务,就是通过该服务来提供实时的调用监控并以统计报表和图形的形式展示,所以这次新建的是一个全新的子工程。 - 在pom文件添加依赖
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.sk.springcloud</groupId>
<artifactId>microservicecloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>microservicecloud-consumer-hystrix-dashboard</artifactId>
<dependencies>
<!-- 自己定义的api -->
<dependency>
<groupId>com.sk.springcloud</groupId>
<artifactId>microservicecloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- Ribbon相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>