一、网站架构演变过程
从传统架构(单体应用) 到 分布式架构(以项目进行拆分) 到 SOA架构(面向服务架构) 到 微服务架构
- 传统架构:
其实就是SSH或者SSM,属于单点应用,把整个业务模块都会在一个项目中进行开发,分为MVC架构,会拆分成业务逻辑层、业务逻辑层、数据库访问层
缺点:一般只适合于一个人或者适合小团队开发,耦合度太高,一旦某个模块导致服务不可用,可能会影响到项目
- 分布式架构
其实是基于传统架构演变过来的
分布式架构基于传统架构演变过来的,将传统的项目以项目模块进行拆分成n多个子项目,每个项目都有自己独立的数据库等
总结:分布式架构与传统架构区别:项目粒度分的更加细,耦合度降低
区分是否是分布式架构在于打的jar包或者war是否是多个jvm项目通讯
- SOA架构与微服务架构
SOA架构也是基于分布式架构演变过来的。SOA架构代表面向服务架构,俗称服务化,可以理解为面向于业务逻辑层开发。将共同的业务代码进行抽取出来,提供给其他接口进行调用。服务与服务之间通讯采用rpc远程调用技术。
服务概念:将共同的业务逻辑进行拆分,拆分成独立的项目进行部署,没有视图层,当然也可以理解为接口
SOA架构特点:底层基于SOAP(Http协议+XML:如webservice)或者ESB(消息总线)实现,底层使用HTTP或者Https协议+重量级XML数据交换格式进行通讯
在后面微服务中,以json格式代替xml
rpc远程调用技术框架:如httpClient、springCloud、dubbo、grpc
核心底层socket技术或者netty实现
- 微服务架构产生的原因
4.1) 首先微服务架构基于SOA架构演变过来的
SOA架构缺点:
1、依赖于中心化服务发现机制
2、因为SOA架构采用SOAP协议(Http+XM),因为XML传输协议比较占用宽带,整个XML报文中有非常大冗余数据,所以在微服务架构中以json轻量级方式代替xml报文传输。
3、服务管理非常混乱,缺少服务管理和治理设施不完善
4.2) 微服务架构模式
微服务架构是从SOA架构演变过来的,比SOA架构上粒度更加精细,让专业的人做专业的事,目的是提高效率。每个服务与服务之间是互不影响,每个服务必须独立部署(独立数据库、独立redis等),微服务架构更加体现轻量级,采用restful风格提高API,也就是Http协议+JSON格式进行传输,更加轻巧更加适合于互联网公司敏捷开发、快速迭代产品。
详细分析:
微服务架构从SOA架构演变过来
服务化功能本身从SOA这层已经实现,只不过微服务架构在单独服务层有进行细分服务服务层有进行细分服务
如会员服务:会员服务在微服务有进行细分为:
4.3) 微服务架构与SOA架构区别
1、为服务架构基于SOA架构演变过来,继承SOA架构的优点,在微服务架构中去除了SOA架构中的ESB消息总线,采用http+json(restful)进行传输
2、微服务架构比SOA架构粒度会更加精细,让专业的人去做专业的事,目的提高效率,每个服务于服务之间互不影响,微服务架构中,每个服务必须独立部署,微服务架构更加轻巧,轻量级
3、SOA架构中可能数据库存储会发生共享,微服务强调每个服务都是单独数据库,保证每个服务与服务之间互不影响
4、项目体现特征服务架构比SOA架构更加适合于互联网公司敏捷,快速迭代版本,因为粒度非常精细
二、springCloud简介
1、为什么要使用springCloud
springCloud是目前来说,是一套比较完善的为微服务解决方案框架。它不像其他rpc远程调用框架,只是解决了某个微服务中的问题。可以把springCloud理解为一条龙微服务解决方案。微服务全家桶–SpringCloud比较完善
微服务中:
分布式配置中心
分布式锁
分布式服务治理
分布式任务调用平台
总结:
因为SpringCloud出现,对微服务 技术提供了非常大的帮助,以往内SpringCloud提供了一套完整的微服务解决方案,不像其他框架只是解决了微服务中某个问题
服务治理:阿里巴巴开源的Dubbo和当当网在其基础上扩展的Dubbox、Eureka、Apache的Consul等
分布式配置中心:百度的disconf、Netfix的Archaius、360的Qconf、SpringCloud、携程的阿波罗等
分布式任务:xxl-job、elastic-job、springCloud的task等
服务跟踪:京东的hyra、springCloud的sleuth等
2、什么是SpringCloud
SpringCloud是基于SpringBoot基础上开发的微服务框架,SpringCloud是一套目前非常完整的微服务解决方案框架,其内容包含服务治理、注册中心、配置管理、断路器、智能路由、微代理、控制总线、全局锁、分布式会话等
springCloud包含众多的子项目
SpringCloud config 分布式配置中心
SpringCloud netflix 核心组件
Hystrix:服务保护框架
Ribbon:客户端负载均衡器
feign:基于ribbo和hystrix的声明式服务调用组件
Zuul:网关组件,提供智能路由 、访问过滤等功能
三、SpringCloud服务发现与注册
SpringCloud曾经采用Eureka,现在已经闭源了
1、服务治理SpringCloud eureka
- 什么是服务治理
在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
下图可以解释使用注册中心的理由:
2)什么是服务注册与发现
在服务注册与发现中,有一个注册中心,当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地rpc调用
RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)
在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))
以下是搭建一套基于Eureka作为注册中心的demo,项目层次图如下
- 搭建注册中心
3.1) 新建maven工程
3.2) 引入maven
注意2.0版本区别1版本在于eureka命名更加规范(1是spring-cloud-starter-eureka-server)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--SpringCloud eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
3.3) 新建配置文件application.yml
###服务端口号
server:
port: 8100 ###eureka 基本信息配置
eureka:
instance:
###注册到eurekaip地址(注册中心ip)
hostname: 127.0.0.1
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #多个用,号隔开
###因为自己是为注册中心,不需要自己注册自己(集群需要设置为true)
register-with-eureka: false ###因为自己是为注册中心,不需要检索服务
fetch-registry: false
3.4) 编写启动类
@SpringBootApplication
@EnableEurekaServer //开启EurekaServer服务 开启注册中心
public class AppEureka {
public static void main(String[] args) {
SpringApplication.run(AppEureka.class,args);
}
}
3.5) 访问Eureka注册中心管理平台,通过本机IP+ 配置的端口号:这里是http://localhost:8100
- 搭建服务提供者
4.1) 新建生产者服务(service层)子项目
4.2) 添加pom依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot整合eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
4.3) 添加application.yml配置文件
###会员项目服务启动端口号
server:
port: 8000 ###服务名称(服务注册到eureka名称,如serviceId)
spring:
application:
name: app-producer
###服务注册到eureka地址,8100为注册中心端口号
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka
4.3) 编辑需要注册的服务接口
@RestController
public class MemberAPIController {
@Value("${server.port}")
private String serverPort; //用于区分集群
@RequestMapping("/getMember")
public String getMember() {
return "this is Mr_佳先森的会员服务,端口号为:"+serverPort;
}
}
4.4) 编写启动类
@SpringBootApplication
@EnableEurekaClient //将当前服务注册到eureka上
public class AppMember {
public static void main(String[] args) {
SpringApplication.run(AppMember.class, args);
}
}
4.5)运行注册中心,再运行生成者,此时通过访问控制台,能看到注册后的信息
注意:这里的生产者名是根据生产者配置文件的配置的别名而定(这里是app-producer)
- 编写消费者
5.1) 新建消费者子项目
5.2) 配置pom依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot整合eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
5.3) 编写消费者配置文件和消费方法
###订单服务(消费者)启动端口号
server:
port: 8001
###服务名称(服务注册到eureka名称)
spring:
application:
name: app-order
###服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka,
@RestController
public class OrderController {
/**
* RestTemplate由SpringBoot web组件提供 默认整合ribbo负载均衡器
* rest方式底层是采用httpClient技术
*/
@Autowired
private RestTemplate restTemplate;
/**
* 订单服务调用会员服务:
* 在springCloud当中,有两种方式调用生产的消息
* 1.rest方式 2、fegin(SpringCloud方式)
* @return
*/
@RequestMapping("/getOrder")
public String getOrderInfo() {
//采用服务别名(生产者)+方法映射id:
String url = "http://app-producer/getMember";
String result = restTemplate.getForObject(url, String.class);
System.out.println("订单服务调用会员服务是_:"+result);
return result;
}
}
5.4) 编写启动类
基于这里采用的是ribbon的RestTemplate方式调用生产者,需要启动类中注册该类到容器中,并添加@LoadBalanced注解(注意区别于nginx:nginx负载均衡主要
基于服务器(配合一些服务(如mysql,tomcat等)),而ribbon主要基于本地(主要用于微服务),我们可以通过更改生产者端口号构建集群环境,不断调用消费者@LoadBalanced 注解能实现负载均衡)
@SpringBootApplication
@EnableEurekaClient
public class AppOrder {
public static void main(String[] args) {
SpringApplication.run(AppOrder.class, args);
}
//解决RestTemplate找不到问题:应该把restTemplate注册到springboot容器中
@Bean
@LoadBalanced //开启负载均衡功能,并且支持别名方式调用生产者(否则无法使用别名)
RestTemplate restTemplage() {
return new RestTemplate();
}
}
5.5) 在控制台可以看到消费者信息并调用服务
- Eureka集群高可用环境搭建
6.1)、微服务rpc远程服务调用最核心的是什么?
注册中心。如果注册中心因为某种原因出现故障,有可能导致整个为服务环境不可用
解决办法:搭建注册中心集群–大型互联网公司注册中心都是集群版本。
6.2) 搭建eureka集群环境
思路:Eureka高可用实际上是将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组相互作用的服务注册中心,从而实现服务清单 的互相调用,达到高可用的效果
6.3) 新建两个注册中心项目,参考以上的做法,只是配置文件会发生一点变化
这里的环境是拥有一台8100的注册中心和9100的注册中心,他们相互注册
8100:application.yml
server:
port: 8100
##定义服务名称:集群环境服务名称得相同
spring:
application:
name: springCloud-eureka
eureka:
instance:
hostname: 127.0.0.1
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:9100/eureka/
register-with-eureka: true
fetch-registry: true
9100:application.yml
server:
port: 9100
##定义服务名称:集群环境服务名称得相同
spring:
application:
name: springCloud-eureka
eureka:
instance:
hostname: 127.0.0.1
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:8100/eureka/
register-with-eureka: true
fetch-registry: true
6.4) 启动两个启动类,第一个可能会报如下错误,是因为第二个注册中心未启动,从而报无法识别服务器
6.5) 同时访问两个管理平台可以看到相互注册的信息
6.6) 客户端连接注册中心
此时生成者和消费者只需要分别将服务注册到两个注册中心中去和从两个注册中心拿服务
如生成者
###会员项目服务启动端口号
server:
port: 8000
###服务名称(服务注册到eureka名称,如serviceId)
spring:
application:
name: app-producer
###服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka,http://localhost:9100/eureka
###因为该应用为注册中心,不会注册自己
register-with-eureka: true
###是否需要从eureka上获取注册信息
fetch-registry: true
6.6) 启动
从管理平台可以看出,8100为主,9100为备
在注册过程中,只会保证有一台注册中心服务有对应服务信息数据,当主(8100)注册中心宕机后,自动转移数据到备(9100)注册中心上去
6.7) 验证高可用
如果此时将8100注册中心关闭,那么数据会转移到9100注册中心上去(8100宕机后,默认是30秒左右数据转移到备机上)
四、Eureka自我保护机制
注册中心目的为了做什么?服务治理,服务注册与发现能够实现负载均衡,管理服务与服务之间的依赖关系
1、引入话题:
分为两种角色:EurekaClient(注册中心) 和 EurekaServer(注册中心服务端,即生产者),如果将两个服务端(端口号不同)注册到注册中心(集群),利用消费者从注册中心中拿取消费。然后将其中一个服务端关闭,会出现如下图情况:刚开始服务端能不断从两个服务中进行消费(负载均衡),当其中一个生产者(客户端)宕机,刷新消费时因为负载均衡时而访问不到(因为宕机)时而能访问的到(另外一台没有宕机),但是注册中心在一定时间内还会存在宕机后的客户端服务
2、为什么会产生Eureka自我保护机制?
为了防止EurekaClient可以正常运行,但是 与 EurekaServer网络不通情况下,EurekaServer不会将EurekaClient服务剔除
自我保护机制
3、在什么情况下开启自我保护机制
本地环境:建议在本地环境禁止自我保护机制
生成环境:建议开启,不能误删存活的服务
4、怎么禁止自我保护
4.1) 注册中心客户端增加配置
server:
port: 8100
spring:
application:
name: springCloud-eureka
eureka:
instance:
hostname: 127.0.0.1
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
register-with-eureka: false
server:
###开发时关闭自我保护机制,保证不可用服务及时踢除
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000
4.2) 生产者客户端拿取消费
###会员项目服务启动端口号
server:
port: 8002 ###服务名称(服务注册到eureka名称,如serviceId)
spring:
application:
name: app-producer
###服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka
#心跳检测与续约时间 #开发时设置小些,保证服务关闭后注册中心能即使剔除服务
instance:
###Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端自己会按照该规则)
lease-renewal-interval-in-seconds: 1 ###Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒,超过将剔除(客户端告诉服务端自己会按照该规则)
lease-expiration-duration-in-seconds: 2
五、springCloud整合Zookeeper作为注册中心
因为Eureka已经闭源,但是不影响它作为注册中心,当然也可以利用zookeeper进行代替
zookeeper是一个分布式协调工具,可以实现注册中心功能,采用Zookeeper节点类型–临时节点
整个系统层次结构如下
1、编写生产者:
1.1)建立maven项目,并配置pom依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot整合eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
1.2) 建立application.yml配置文件
###服务端口号
server:
port: 8002
###服务名称
spring:
application:
name: zk-producer
cloud:
zookeeper:
###注册到zookeeper地址
connect-string: 192.168.174.128:2181
1.3) 建立逻辑代码(需要注册的方法)
/**
* @EnableDiscoveryClient
* 作用:如果服务使用consul或者zookeeper,该注解用于向注册中心注册服务
* @author Administrator
*
*/
@RestController
@EnableDiscoveryClient
public class ZkProducerController {
@Value("${server.port}")
private String serverPort;
@RequestMapping("/getProduce")
public String getProduce() {
return "生产者生产服务:端口号为:"+serverPort;
}
}
1.4) 建立启动类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
1.5) 启动zookeeper(这里我采用的是docker启动zookeeper)
[root@bogon ~]# systemctl start docker
[root@bogon ~]# docker run --privileged=true -d --name zookeeper --publish 2181:2181 -d zookeeper:latest
ffc45f9fb92ae045c205407d39375a31c639bbf66d1f9b42339cf5e167a7c467
2、编写消费者
2.1) 创建maven工程,注入依赖,和生成这依赖保持一致
2.2) 编写application.yml配置文件
###服务端口号
server:
port: 8060
###服务名称
spring:
application:
name: zk-consumer
cloud:
zookeeper:
###注册到zookeeper地址
connect-string: 192.168.174.128:2181
2.3) 编写消费服务方法
@RestController
@EnableDiscoveryClient
public class ZkConsumerController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/getOrder")
public String getOrderInfo() {
//采用服务别名(生产者)+方法映射id:
String url = "http://zk-producer/getProduce";
String result = restTemplate.getForObject(url, String.class);
System.out.println("订单服务调用会员服务是_:"+result);
return result;
}
}
2.4) 编写启动类,并运行该类
@SpringBootApplication
public class ApplicationForConsumer {
public static void main(String[] args) {
SpringApplication.run(ApplicationForConsumer.class, args);
}
//解决RestTemplate找不到问题:应该把restTemplate注册到springboot容器中
@Bean
@LoadBalanced //开启负载均衡功能,并且支持别名方式调用生产者(否则无法使用别名)
RestTemplate restTemplage() {
return new RestTemplate();
}
}
2.5) 访问 http://localhost:8060/getOrder 如果能打印指定的返回结果,说明搭建成功
3、关于DiscoveryClient的使用
DiscoveryClient可以获取注册中心中注册的信息列表,只需要通过该引用的getInstances()方法就可以获取指定的列表信息,注意之所以返回值是集合是因为考虑到集群
@RestController
@EnableDiscoveryClient
public class ZkConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired private DiscoveryClient discoveryClient;
@RequestMapping("/getOrder")
public String getOrderInfo() {
//采用服务别名(生产者)+方法映射id:
String url = "http://zk-producer/getProduce";
String result = restTemplate.getForObject(url, String.class);
System.out.println("订单服务调用会员服务是_:"+result);
return result;
}
//如何获取到注册中心上服务列表信息
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@RequestMapping("/discoveryClientMember")
public List<ServiceInstance> discoveryClientMember() {
List<ServiceInstance> instances = discoveryClient.getInstances("zk-producer");//参数来源于生产者配置的服务名
for(ServiceInstance temp:instances) {
System.out.println("url:"+temp.getUri());
}
return instances; }
}
六、使用Consul代替Eureka作注册中心
Consul 是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发。
它具有很多优点。包括: 基于 raft 协议,比较简洁; 支持健康检查, 同时支持 HTTP 和 DNS 协议 支持跨数据中心的 WAN 集群 提供图形界面 跨平台,支持 Linux、Mac、Windows
Consul 整合SpringCloud 学习网站:https://springcloud.cc/spring-cloud-consul.html
Consul下载地址https://www.consul.io/downloads.html
1、搭建consul环境
1.1) 下载consul:下载
1.2) 启动
进入到存放下载好的执行文件,利用命令co
启动consul命令
consul agent -dev -ui -node=cy
-dev开发服务器模式启动,-node结点名为cy,-ui可以用界面访问,默认能访问。
测试访问地址:http://localhost:8500
nsul agent-dev-ui-node=cy启动
一、编写注册者 1、pom依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
2、编写配置application.yml
###服务端口号
server:
port: 8502
spring:
application:
name: consul-producer
####consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
###服务地址直接为ip地址,这个自定义,它将成为生产者ip
hostname: 192.168.174.128
###默认情况下服务注册到注册中心,地址随机生成英文
3、编写逻辑类
@RestController
@SpringBootApplication
@EnableDiscoveryClient
public class Productor {
@Value("${server.port}")
private String serverPort;
@RequestMapping("getMember")
public String getMember() {
return "服务提供者:端口号为:"+serverPort;
}
public static void main(String[] args) {
SpringApplication.run(Productor.class, args);
}
}
4、启动访问:http://localhost:8500
七、SpringCloud之负载均衡
1、springCloud之本地缓存Ribbon
1.1、Ribbon是Springcloud(本地)客户端负载均衡器,当消费者从注册中心获取到serviceId以及多以的服务地址后,会缓存到本地(JVM客户端)。然后在本地实现远程的rpc调用,就如同绕过注册中心直接调用生产者拿取服务
只要加上@LoadBalanced注解即可
@Autowired
private RestTemplate restTemplate;
@Bean
@LoadBalanced public RestTemplate restTemplate() {
return new RestTemplate();
}
2.2、关于负载均衡算法
消费者以服务别名获取到对应的服务接口地址的时候,可能会多个,存放采用list接口。当实现本地负载均衡时,此时涉及到负载均衡算法
负载均衡算法:接口总请求数%服务器数量 = 实际调用服务器位置下标
如:List [0] value = 127.0.0.1:8080
List [1] value = 127.0.0.1:8081
这里是两个相同服务的集群
当总请求数为1时: 1 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8081
当总请求数位2时: 2 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8080
当总请求数位2时: 3 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8081
如此类推。。。
2.3、手写本地负载均衡器
1)编写简单的eureka注册中心,生产者,消费者,最好关闭服务保护
- 在消费者子模块中编写自定义本地缓存器
@RestController
public class ExtRibbonController {
//可以获取注册中心上的服务列表
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
//接口的请求总数
private int requestCount;
@RequestMapping("/ribbonMember")
public String ribbonMember() {
//1、获取对应服务器远程调用地址
String instanceUrl = getInstance() + "/getMember";
System.out.println("instanceUrl" + instanceUrl);
//2.可以直接使用httpClient技术实现远程调用
String result = restTemplate.getForObject(instanceUrl, String.class);
return result;
}
private String getInstance() {
List<ServiceInstance> instances = discoveryClient.getInstances("app-producer");
if(instances == null || instances.size()<=0) {
return null;
}
//获取服务器集群个数
int instanceSize = instances.size();
int index = requestCount % instanceSize;
requestCount++;
return instances.get(index).getUri().toString();
}
}
- 启动eureka注册中心,以集群方式启动两次生产者(这里端口号为8002和8003),启动消费者
4)项目目录层次如下
- 输入请求地址http://localhost:8001/ribbonMember,这里是调用自定义的负载均衡器请求方法,通过它来调用生产者的服务,通过刷新请求可以看到端口号的变化
2.4)Ribbon本地负载均衡客户端与Nginx服务端负载均衡区别
Ribbon本地负载均衡,原理:在调用接口时候,会在eureka注册中心上获取注册信息服务列表之后,会缓存到jvm本地,从而在本地实现rpc远程服务调用技术。
Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡器室友服务端实现的。
应用场景:
本地负载均衡器适用在微服务rpc远程调用,比如dubbo、springCloud
nginx是服务器负载均衡适用于针对于服务端 比如tomcat、jetty
2.5) SpringCloud 声明式Feign客户端
2.5.1)在springCloud中,它支持两种客户端调用工具
1、Rest方式,但是它基本不用
@Autowired
private RestTemplate restTemplate;
2、Feign客户端
其以后实际开发中用的最多,它是一个声明式的Http客户端调用工具,采用接口+注解方式实现,可读性比较强
2.5.2)搭建环境
1)编写eureka注册中心,生产者,消费者(只需要编写配置和启动类)
2)消费者引入feign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
3)编写feign接口
注意:feign客户端是采用接口形式调用服务,其客户端名与生产端配置的服务名保持一致,需要调用的服务名映射与服务端的映射保持一致
@FeignClient(name="app-producer") //此参数取决 于生产者的服务名
public interface ApiFeign {
@RequestMapping("/getMember") //此参数取决于调用生产者的那个方法映射(和生产者保持一致)
public String getMember();
}
4)编写controller层
@RestController
public class FeignController {
@Autowired
private ApiFeign apiFeign;
@RequestMapping("/feignMember")
public String feighMember() {
return apiFeign.getMember();
}
}
5)访问服务http://localhost:8001/feignMember,如果能成功调用说明环境搭建成功
八、feign处理超时
1、什么是服务雪崩效应
默认情况下tomcat只有一个线程池去处理客户端发送的所有的服务请求,这样在高并发情况下,如果客户端所有请求堆积在同一个服务接口上,就会产生tomcat所有线程去处理该服务接口,可能会导致其他服务接口访问产生延迟和等待无法访问。
tomcat有个线程池,每个一个线程去处理客户端发送请求。假设Tomcat最大请求数(同时)20,客户端发送100个请求。会发生80个请求产生延迟等待。
2、环境:
当利用feign客户端调用服务时,如果生产者中的一个消息有1.5秒的延迟,那么在调用服务时会报时间超时
生产者实现类中的一个方法
@RequestMapping("/getUserInfo")
public ResponseBase getUserInfo() {
try {
//服务接口产生1.5秒的延迟
Thread.sleep(1500);
}catch(Exception e) {
}
return setResultSuccess("消费消息成功");
}
异常:
java.net.SocketTimeoutException: Read timed out
3、feign在网络延迟情况下的超时时间解决思路
只需在消费者配置中配置延长超时时间即可
###订单服务(消费者)启动端口号
server:
port: 8001
###服务名称(服务注册到eureka名称)
spring:
application:
name: app-order
###服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka
###是否注册自己
register-with-eureka: true
###是否从eureka上获取注册上信息
fetch-registry: true
###设置feign客户端超时时间
###springCloud默认开启支持ribbon
ribbon:
###指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
###指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
具体代码参考链接:git@gitee.com:MR_JiaXianSen/spring_cloud_template.git
九、服务保护Hystrix
1、微服务高可用技术
大型复杂的分布式系统中,高可用相关的技术架构非常重要。
高可用架构非常重要的一个环节,就是如何将分布式系统中的各个服务打造成高可用的服务,从而足以应对分布式系统环境中的各种各样的问题,,避免整个分布式系统被某个服务的故障给拖垮。
比如:
服务间的调用超时
服务间的调用失败
要解决这些棘手的分布式系统可用性问题,就涉及到了高可用分布式系统中的很多重要的技术,包括:
资源隔离
限流与过载保护
熔断
优雅降级
容错
超时控制
监控运维
服务雪崩效应
服务雪崩效应产生与服务堆积在同一个线程池中,因为所有的请求都是同一个线程池进行处理,这时候如果在高并发情况下,所有的请求全部访问同一个接口,
这时候可能会导致其他服务没有线程进行接受请求,这就是服务雪崩效应效应。
服务降级
在高并发情况下,防止用户一直等待,使用服务降级方式(直接返回一个友好的提示给客户端,调用fallBack方法)
服务熔断
熔断机制目的为了保护服务,在高并发情况下,如果请求达到一定极限(可以自己设置阔值)如果流量超出了设置阈值,让后直接拒绝访问,保护当前服务。使用服务降级方式返回一个友好提示,服务熔断和服务降级一起使用)
服务隔离
因为默认情况下,只有一个线程池会维护所有的服务接口,如果大量的请求访问同意接口,达到tomcat线程池默认极限,可能会导致其他服务无法访问。
解决服务雪崩效应:
1)线程池隔离
使用服务隔离机制(线程池方式和信号量),使用线程池方式实現隔离的原理: 相当于每个接口(服务)都有自己独立的线程池,因为每个线程池互不影响,这样的话就可以解决服务雪崩效应。
线程池隔离:
- 信号量隔离
使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,当请求进来时先判断技术器的数值,若超过设置的最大线程个数则拒绝该请求,若不超过则通过,这时候计数器+1,请求返回成功后技术器-1
服务限流
服务限流就是对接口访问进行限制。技术器也可以进行粗暴限流实现
2、Hystrix简介
Hystrix是国外知名的视频网站Netflix所开源的非常流行的高可用架构框架。Hystrix能够完美的解决分布式系统架构中打造高可用服务面临的一系列技术难题。
Hystrix “豪猪”,具有自我保护的能力。hystrix 通过如下机制来解决雪崩效应问题。
在微服务架构中,我们把每个业务都拆分成单个服务模块。然后当有业务需求是,服务间课互相调用,但是,由于网络原因等因素,有可能出现服务不可用的情况,当某个服务出现问题时,其他服务如果继续调用这个服务,就是可能出现线程阻塞,但如果同时又大量的请求,就会造成线程资源被用完,这样就可能会导致服务瘫痪,由于服务间会相互调用,很容易造成蝴蝶效应导致整个系统宕机。而断路器可以解决这点。
资源隔离:包括线程池隔离和信号量隔离,限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
缓存:提供了请求缓存、请求合并实现。
3、基于Hystrix解决服务雪崩效应原理
1)、服务降级
在高并发情况下,防止用户一直等待,使用服务降级方式(返回一个友好的提示直接给客户端,不会去处理请求,调用fallBack本地方法),在tomcat中没有线程进行处理客户端请求时,不应该让客户一直在转圈等待。目的是为了用户体验
如:秒杀服务降级处理—–提示当前请求人数过多,请稍后重试
2)、服务熔断
服务熔断的目的是为了保护服务,在高并发情况下,如果请求达到了一定的极限(可以自己设置一个阈值),如果流量超出了设置的阈值情况下,会自动开启服务保护功能,使用服务降级方式返回一个友好的提示,服务熔断机制和服务降级是一起使用的。注意Hystrix默认阈值为10个,超过十个则开启服务保护,服务降级
3)、服务隔离
隔离方式分为线程池隔离和信号量隔离。
4、Hystrix环境搭建
1)在父级pom中引入hystrix依赖
<!-- hystrix断路器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2)在消费者配置中开启hystrix断路器
###订单服务(消费者)启动端口号
server:
port: 8001
###服务名称(服务注册到eureka名称)
spring:
application:
name: app-order
###服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka
###是否注册自己
register-with-eureka: true
###是否从eureka上获取注册上信息
fetch-registry: true
###设置feign客户端超时时间
###springCloud默认开启支持ribbon
ribbon:
###指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
###指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
###开启Hystrix断路器
feign:
hystrix:
enabled: true
2)在消费者端定义三个接口及实现类
其中两个方法中一个方法一个有处理雪崩效应方案,另一个没有,还有一个方法时配合前两个方法查看效果的
首先,要使得某个方法有服务保护,得加上@HystrixCommand注解并有处理时回调的提示方法fallbackMethod
Hystrix有两种请求方式去配置服务保护
方式一:通过注解和接口形式:@HystrixCommand(fallbackMethod=””),其中fallbackMethod作用是服务降级
@HystrixCommand默认开启线程池隔离方式、开启服务降级和服务熔断机制
@HystrixCommand(fallbackMethod="consumerInfoHystrixFallback")
@RequestMapping("/consumerUserInfoHystrix")
public ResponseBase consumerUserInfoHystrix() {
System.out.println("consumerUserInfoHystrix:线程池名称:"+Thread.currentThread().getName());
return comsumerFeignService.getUserInfo();
}
public ResponseBase consumerInfoHystrixFallback() {
return setResultSuccess("返回一个友好提示:服务降级,服务器忙,请稍后重试!");
}
全部代码如下:
接口层:
public interface ConsumerService { @RequestMapping(“/consumerInfo”) public String consumerInfo(String name);
@RequestMapping(“/consumerUserInfo”) public ResponseBase consumerUserInfo();
@RequestMapping(“/consumerInfoForHystrix”) public ResponseBase consumerInfoForHystrix(); }
实现层:
方式一:通过注解和接口形式
@RestController
public class ComsumerServiceImpl extends BaseApiService implements ConsumerService{
@Autowired
private ComsumerFeignService comsumerFeignService;
@Override
@RequestMapping("/consumerInfo")
public String consumerInfo(String name) {
UserEntity user = comsumerFeignService.getProduceInfo(name);
return user==null?"没有找到用户信息":user.toString();
}
//该方法时没有解决服务雪崩效应
@Override
@RequestMapping("/consumerUserInfo")
public ResponseBase consumerUserInfo() {
return comsumerFeignService.getUserInfo();
}
//Hystrix有两种请求方式去配置服务保护
//方式一:通过注解和接口形式:@HystrixCommand(fallbackMethod=""),其中fallbackMethod作用是服务降级
//@HystrixCommand默认开启线程池隔离方式、开启服务降级和服务熔断机制
@HystrixCommand(fallbackMethod="consumerInfoHystrixFallback")
@RequestMapping("/consumerUserInfoHystrix")
public ResponseBase consumerUserInfoHystrix() {
System.out.println("consumerUserInfoHystrix:线程池名称:"+Thread.currentThread().getName());
return comsumerFeignService.getUserInfo();
}
public ResponseBase consumerInfoHystrixFallback() {
return setResultSuccess("返回一个友好提示:服务降级,服务器忙,请稍后重试!");
}
/**
* 用于做对比:查看服务保护效果
*/
@Override
@RequestMapping("/consumerInfoForHystrix")
public ResponseBase consumerInfoForHystrix() {
System.out.println("consumerInfoHystrix:线程池名称:"+Thread.currentThread().getName());
return setResultSuccess();
}
}
- 在消费者启动类上添加启动Htstrix注解
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix public class AppComsuApplication {
public static void main(String[] args) {
SpringApplication.run(AppComsuApplication.class,args);
}
}
4)此时通过测压工具以200并发量测试consumerInfoForHystrix方法,再在浏览器中调用consumerUserInfoHystrix方法时,会出现
当然:通过控制台可以看到两个方法调用的线程id是不同的
方式二:通过类的方式
方式一是在每个需要调用的方法上添加注解,这样代码既冗余,并且我们只是针对getUserInfo的方法进行降级,开放独立线程池,但是用方法一是其功能包含可整个方法,这样是不可取的
方式二是通过类的形式实现的
- 首先新增一个类,这个类需要实现feign客户端接口,因为feign客户端接口实现了生产者接口,这样就间接取得到了需要降级的方法,这里只针对getUserInfo作演示
@Component
public class ConsumerFallback extends BaseApiService implements ComsumerFeignService{
@Override
public UserEntity getProduceInfo(String name) {
// TODO Auto-generated method stub
return null;
}
//服务降级的友好提示
@Override
public ResponseBase getUserInfo() {
return setResultError("服务器忙,请稍后重试!以类的方式进行服务降级");
}
}
- 在feign客户端中引入回调类
@FeignClient(value="app-producer",fallback = ConsumerFallback.class)
public interface ComsumerFeignService extends ProducerService{
}
- 此时我们随便请求一个包含需要调用getUserInfo服务的方法,能起到降级作用(注意:因为getUserInfo方法的实现层采用了失眠1.5秒,而Htstrix超时时间默认为1秒,所以才会开启服务保护进行降级给出友好提示)
如请求方法为
//方式二:通过类的方式
@RequestMapping("/consumerUserInfoHystrixForClass")
public ResponseBase consumerUserInfoHystrixForClass() {
System.out.println("consumerUserInfoHystrix:线程池名称:"+Thread.currentThread().getName());
return comsumerFeignService.getUserInfo();
}
5、Hystrix设置超时时间
如果调用其他接口超时的时候(默认是1秒时间),如果在一秒中没有及时响应的话(如调用服务时,服务接口有1.5秒的睡眠),默认情况下业务逻辑是可以执行的,但是直接直接执行服务降级方法(即执行fallbackMethod)
@Override
@RequestMapping("/getUserInfo")
public ResponseBase getUserInfo() {
try {
//服务接口产生1.5秒的延迟
Thread.sleep(1500);
}catch(Exception e) {
}
return setResultSuccess("消费消息成功");
}
当然我们可以禁掉Hystrix超时设置,在消费者配置中配置,去掉后除非是网络蹦了或者延迟严重,才会走fallbackMethod方法,正常情况下会成功调用服务
###订单服务(消费者)启动端口号
server:
port: 8001
###服务名称(服务注册到eureka名称)
spring:
application:
name: app-order
###服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka
###是否注册自己
register-with-eureka: true
###是否从eureka上获取注册上信息
fetch-registry: true
###设置feign客户端超时时间
###springCloud默认开启支持ribbon
ribbon:
###指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
###指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
###开启Hystrix断路器
feign:
hystrix:
enabled: true
###hystrix禁止服务超时时间
hystrix:
command:
default:
execution:
timeout:
enabled: false
此时我们再访问consumerUserInfoHystrix方法(注意该方法调用的 服务就是含有1.5秒睡眠的服务),会正常执行,且返回结果也不是rollbackMethod返回的数据,说明没有调用
十、SpringCloud config分布式配置中心
1、为什么使用分布式的配置中心
在微服务如果使用传统的方式管理配置文件,配置文件管理非常复杂,如果生产环境配置文件,可能需要发生改变时,需要重新打成war包,重新读取配置信息在jvm内存中
2、什么是分布式配置中心
在微服务当中使用同一个服务管理所有服务配置文件信息,能够实现后台可管理,当服务器正在运行的时候,如果配置文件需要发生改变,可以实现不需要重启服务器实时更改配置文件信息
注意:热部署其实底层还是会重启服务器,不适合生产环境只适合本地开发测试
3、分布式框架配置中心框架
-
阿波罗 携程写的分布式配置中心,拥有图形界面可管理配置文件信息,配置文件信息存放在数据库里面
-
springCloud config:没有后台可管理分布式配置中心,配置文件按信息存放在版本控制器里面(git / svn)
-
唯品会:使用Zookeeper实现分布式配置中心,将配置存放在zookeeper,持久节点+事件通知
4、springCloud Config分布式配置中心原理
首先分析:分布式配置中心需要哪些组件:
-
web管理系统—-后台可以使用图形界面管理配置文件 SpringCloud config没有图形管理配置文件
-
存放分布式配置文件按服务器(持久化存储服务器)—-springCloud config使用版本控制器存放配置文件信息(git/svn)
-
ConfigServer:缓存配置文件服务器(临时缓存存放)
-
ConfigClient:读取ConfigServer配置文件信息
5、搭建环境
- 搭建git环境
目的:持久化存储配置文件信息,git环境上文件夹以项目进行区分
1.2) 公司项目中环境是如何区分的
dev 开发环境
sit 测试环境
pre 预发布环境
prd 准生产环境
此演练两个环境sit环境和prd环境,注意在git环境是私有的需要配置密码,否则无法访问
- 新建maven聚合工程
2.1) 新建eureka 注册中心
2.2) 新建配置子项目springCloud2.0_config_server
2.2.1)引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--spring-cloud 整合 config-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!-- SpringBoot整合eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
2.2.2) 编写配置
###注册中心服务地址
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka
###服务注册名称
spring:
application:
name: config-server
cloud:
config:
server:
git:
###config-server读取git环境地址(注意:这里是在git项目中新建的一个config文件夹内的HTTPS地址)
uri: https://gitee.com/MR_JiaXianSen/springCloud-config.git
search-paths:
###存放的文件目录服务地址,即文件夹名,"-"表示多个,用"-"区分
- config
###读取的分支
label: master
server:
port: 8888
2.2.3) 编写启动类
@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer //开启config Server服务器
public class AppConfigApplication {
public static void main(String[] args) {
SpringApplication.run(AppConfigApplication.class, args);
}
}
2.3) 在git config目录中创建配置文件,并测试连接
注意:配置文件夹的命名规范:服务名称-环境.properties(如producer-dev.properties),这里新建两个配置文件,内容采用key-value形式,且key保持一致
2.4) 此时启动注册中心,启动springCloud2.0_config_server,直接访问http://localhost:8888/test-configClient-prd.properties或者http://localhost:8888/test-configClient-sit.properties都可直接访问配置里的内容(注意如果git环境私有并未配置密码会报404无法访问)
2.5) 新建客户端子项目springCloud2.0_config_client
该子项目是通过程序访问config_server服务端的配置信息(即git版本库中的配置文件信息)
2.5.1)引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--spring-cloud 整合 config-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!-- SpringBoot整合eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 注意:一定要添加此依赖,否则会启动不久自动关闭 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
2.5.2) 添加配置bootstrap.yml
###服务名称:注意:这里的服务名要与git配置的文件名(配置名+环境)中的配置名保持一致,项目启动时他是根据
###该服务名称去从git项目目录中找与之配对的配置文件
spring:
application:
name: test-configClient
cloud:
config:
###表示读取的版本环境
profile: sit
discovery:
###表示读取的config-server环境,此要与config-server子项目中 的服务别名要保持一致
service-id: config-server
###表示开启读取权限
enabled: true
###注册中心服务地址
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka
###表示服务端口号
server:
port: 8882
2.5.3)新增访问controller即启动类
@RestController
public class TestClientController {
@Value("${springCloudLearning}") //此要与git中配置文件的key保持一致
private String key;
@RequestMapping("/getInfo")
public String getInfo() {
return key;
}
}
@SpringBootApplication
@EnableEurekaClient
public class AppClientAppliction {
public static void main(String[] args) {
SpringApplication.run(AppClientAppliction.class, args);
}
}
2.5.4)访问:
2.6) 实时刷新配置
当git配置文件中的内容更改后,因为本地缓存原因,客户端不能实时获得更改后的配置信息,平常做法是重启项目
springCloud分布式配置中心可以采用手动刷新或者自动刷新实时更新配置文件更改后的内容,手动刷新和自动刷新都不要重启项目
-
手动刷新–需要人工调用接口读取最新配置(监控中心)
-
自动刷新—消息总线进行实时通知(不建议:对性能不好)
以下演示手动刷新方式
2.6.1) 配置actuator监控中心,先在springCloud_config_client子项目中新增依赖
<!-- actuator监控中心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.6.2) 新增配置
###服务名称:注意:这里的服务名要与git配置的文件名(配置名+环境)中的配置名保持一致,项目启动时他是根据
###该服务名称去从git项目目录中找与之配对的配置文件
spring:
application:
name: test-configClient
cloud:
config:
###表示读取的版本环境
profile: sit
discovery:
###表示读取的config-server环境,此要与config-server子项目中 的服务别名要保持一致
service-id: config-server
###表示开启读取权限
enabled: true
###注册中心服务地址
eureka:
client:
service-url:
defaultZone: http://localhost:8100/eureka
###表示服务端口号
server:
port: 8882
###开启所有的监控端点
management:
endpoints:
web:
exposure:
include: "*"
2.6.3) 在controller类中新增@RefreshScope注解
@RestController
@RefreshScope public class TestClientController {
@Value("${springCloudLearning}") //此要与git中配置文件的key保持一致
private String key;
@RequestMapping("/getInfo")
public String getInfo() {
return key;
}
}
2.6.4) 此时如果git配置文件信息发生改变,需要以post方式手动调用actuator/refresh接口进行刷新本地缓存,如这里是http://localhost:8882/actuator/refresh,从而每次调用请求时(http://localhost:8882/getInfo)都可以获得配置文件中最新的信息
配置中心的源码:git@gitee.com:MR_JiaXianSen/spring_cloud_template.git
十一、微服务网关技术
1、网关API(接口) Gateway(网关) –接口网关注意:接口没有界面
2、接口什么背景下产生的?
在面向服务架构和微服务背景下产生的,目的是为了解耦,rpc远程调用中产生的
3、接口如何分类
开发接口—-提供给其他机构合作伙伴进行调用(必须在外网访问) 如蚂蚁开放平台,微信公众号开发
需要通过appId(目的授权一些接口授权) +appsecret生成accessToken进行通讯
内部接口—-一般只能在居于网中进行访问,服务与服务调用之间关系都在同一个微服务系统中,目的是为了保证安全
4、网关概念
-
相当于客户端请求统一先请求到网关服务器上,然后由网关服务器进行转发到实际服务器地址上。功能类似于nginx
-
过滤器与网关区别是什么?
过滤器是拦截单个tomcat服务器进行拦截请求,网关是拦截整个微服务所有请求
- Nginx与Zuul的区别
相同点: Zuul和Nginx都可以实现负载均衡、反向代理(掩饰真实IP)、过滤请求,实现网关效果
不同点:Nginx采用C语言编写,Zuul采用java语言编写
Zuul负载均衡实现:采用ribbon+eruka实现本地负载均衡;Nginx负载均衡实现:采用服务器端实现负载均衡
性能:Nginx相对于Zuul功能更加强大,因为Nginx整合一些脚本语言(如Lua),Nginx适合于服务器端负载均衡
Zuul更适合微服务中,最好是nginx+zuul实现网关(nginx作反向代理,Zuul对微服务实现网关拦截)
注意:Zuul默认开启Ribbon本地负载均衡
5、环境搭建
5.1) 新建项目,引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--SpringCloud eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
5.2) 新建配置application.yml
###服务注册地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8100/eureka/
###api网关端口号
server:
port: 80
###网关名称
spring:
application:
name: service-zuul
zuul:
routes:
###定义的服务规则,可以自定义
api-a:
###当客户端发送请求127.0.0.1:80/api-producer开头的 都会转发到生产者服务
path: /api-producer/**
###生产者服务别名,zuul网关默认整合ribbon自动实现负载均衡轮询效果
serviceId: app-producer
api-b:
###当客户端发送请求127.0.0.1:80/api-consumer开头的 都会转发到消费者服务
path: /api-consumer/**
serviceId: app-consumer
5.3) 新增启动类,开启Zuul功能
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
5.4) 此时可以测试Zuul的方向代理过程,先启动Zuul子项目,再启动eureka注册中心,再启动生产者,此时可以通过网关代理访问生产者,显示内容一样
http://localhost:80/api-producer/getProduce?name=rose
http://localhost:8003/getProduce?name=rose
6、利用Zuul作过滤器
6.1) 编写过滤器
此过滤功能时请求未传userToken参数,会提示报错进行拦截
/**
* 网关过滤器
* @author 佳先森
*
*/
@Component
public class TokenFilter extends ZuulFilter{
/**
* 编写过滤器拦截业务逻辑代码
* 环境:拦截所有服务接口,判断是否有传userToken
*/
public Object run() throws ZuulException {
//拦截所有的服务接口,判断服务接口是否有传递UserToken参数
//1.首先要获取上下文
RequestContext currentContext = RequestContext.getCurrentContext();
//2.获取request对象
HttpServletRequest request = currentContext.getRequest();
//3.一般获取token的时候 从请求头中获取token参数,这里只是演示方便
String userToken = request.getParameter("userToken");
if(StringUtils.isEmpty(userToken)) {
//不会继续执行...不会调用服务接口,网关服务直接响应给客户端
//返回一个错误提示
currentContext.setSendZuulResponse(false);
currentContext.setResponseBody("userToken is null");
currentContext.setResponseStatusCode(401);
return null;
}
//正行执行调用其他服务接口...
return null;
}
/**
* 判断过滤器是否生效
*/
public boolean shouldFilter() {
return true;
}
/**
* 表示过滤器执行顺序,当一个请求在同一阶段时存在
* 多个过滤器时,多个过滤器执行顺序问题
*/
public int filterOrder() {
return 0;
}
/**
* 表示过滤器类型 "pre"表示请求之前进行
*/
public String filterType() {
return "pre";
}
}
6.2) 继续启动项目
7、网关之集群
7.1) 先配置nginx环境
7.1.1) 找到电脑hosts文件,win7 所在目录为C:\Windows\System32\drivers\etc
目的是对虚拟机ip进行映射
192.168.174.128 www.ibo.com
7.1.2) 在nginx配置文件中添加如下代码
###上游服务器 集群轮询机制
upstream backServer{
server 192.168.2.175:81;
server 192.168.2.175:82;
}
server {
listen 80;
server_name www.ibo.com;
location / {
###指定上游服务器负载均衡服务器
proxy_pass http://backServer/;
index index.html index.htm;
}
此时通过分别开启以端口号为81和82的网关配合nginx可以实现集群后的负载均衡
十二、Swagger
1、引入
随着微服务架构体系的发展和应用,为了前后端能够更好的集成与对接,为了项目方便交互,每个项目都需要提供相应的API文档
2、为什么需要swagger
传统:
-
如前后端分离那些事,如果后端编写的接口文档更新没及时通知前端;API接口返回信息不明确
-
接口文档太多,不便于管理
swagger:
1)、功能丰富;支持多种注解,自动生成接口文档,支持在界面测试API接口功能
2)、及时更新
3)、整合简单。通过添加pom依赖和简单配置,内嵌与应用中就可以同时发布API接口文档界面,不需要部署独立服务
3、搭建环境
1)、引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
- 新增配置application.yml
###服务启动端口号
server:
port: 8000
###服务名称(服务注册到eureka名称)
spring:
application:
name: springboot-swagger
- 新增配置类
/**
* 注意配置的信息可以存放在分布式配置中心里面
* @author 佳先森
*
*/
@Configuration
@EnableSwagger2 //开启swagger2功能
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
//生成API扫包范围(指定哪个包需要生成api)
.apis(RequestHandlerSelectors.basePackage("com.ibo.api")).paths(PathSelectors.any()).build();
}
//创建api文档信息
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("XX项目对接文档").description("平台导入功能")
.termsOfServiceUrl("http://www.ibo.com")
.version("1.0").build();
}
}
- 新增需要生成api的指定controller,注意该controller需要在指定的扫描包中,这里根据上个配置应该是com.ibo.api
@Api("SwaggerDemo控制器")
@RestController
public class SwaggerController {
@ApiOperation("swagger演示接口")//接口描述
//@RequestMapping("/swaggerIndex")//注意在微服务项目中最好用get/post等请求规范写法
@GetMapping("/swaggerIndex")
public String swaggerIndex() {
System.out.println("swaggerIndex");
return "swaggerIndex";
}
@ApiOperation("获取用户信息") //接口描述
@ApiImplicitParam(name="username",value="用户信息参数",required=true,dataType="String")//传参参数描述
@PostMapping("/getUserInfo")
public String getUserInfo(String username) {
System.out.println("用户名为:"+username);
return "userName:"+username;
}
}
- 新建启动类
@SpringBootApplication
public class SwaggerApplication {
public static void main(String[] args) {
SpringApplication.run(SwaggerApplication.class, args);
}
}
6)启动项目, 访问平台管理界面http://localhost:8000/swagger-ui.html#/swagger-controller
4、swagger集群
在微服务中,swagger是每个服务集成的,那么如何将整个微服务中的swagger进行合成。
1、我们可以使用Zuul+Swagger实现管理每个微服务API文档
2、可以使用Nginx + Swagger以项目不同区分跳转不同的接口文档
SpringBoot支持Swagger管理,只需要Zuul网关添加对应服务Sawgger文档即可
集群环境搭建:
4.1) 生产者和消费者和网关子项目中同时引入swagger依赖,此依赖就等同于上面两个,是spring对其两个依赖进行了整合
<!-- 引入swagger依赖 -->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.7.0.RELEASE</version>
</dependency>
4.2) 分别在生产者和消费者配置中添加swagger配置
###定义swagger扫包范围
swagger:
base-package: com.ibo.serviceImpl
4.3) 在生产者和消费者调用类上添加@Api注解并添加方法描述
如生产者:
@RestController
@Api("生产者服务")
public class ProduceServiceImpl extends BaseApiService implements ProducerService{
@Value("${server.port}")
private String serverPort;
@Override
@ApiOperation("获得服务信息")
@ApiImplicitParam(name="name",value="用户名",required=true,dataType="String")
@GetMapping("/getProduce")
public UserEntity getProduceInfo(@RequestParam("name")String name) {
UserEntity userEntity = new UserEntity();
userEntity.setName(name+"端口号:"+serverPort);
userEntity.setAge(24);
return userEntity;
}
4.4) 在启动类中开启swagger注解
@EnableSwagger2Doc //生成swagger注解文档
@SpringBootApplication
public class AppComsuApplication {
public static void main(String[] args) {
SpringApplication.run(AppComsuApplication.class,args);
}
}
4.5) 在网关启动类中增加配置与开启swagger功能
@EnableZuulProxy
@EnableEurekaClient
@EnableSwagger2Doc
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
//zuul配置能够使用config实现实时更新
@RefreshScope
@ConfigurationProperties("zuul")
public ZuulProperties zuulProperties() {
return new ZuulProperties();
}
//添加文档来源
@Component
@Primary
class DocumentationConfig implements SwaggerResourcesProvider{
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resource = new ArrayList<>();
resource.add(swaggerResource("api-producer","/api-producer/v2/api-docs","2.0"));
resource.add(swaggerResource("api-consumer","/api-consumer/v2/api-docs","2.0"));
return resource;
}
/**
*
* @param name 此参数可以随意定义,用于查询接口模块名称
* @param location /api-producer/v2/api-docs 中api-producer取自于网关的配置,这是对所有生产者访问时的域名前缀
* @param version 版本号
* @return
*/
private SwaggerResource swaggerResource(String name, String location,String version) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion(version);
return swaggerResource;
}
}
}
4.6) 此时如果访问http://localhost:82/swagger-ui.html#/swagger-controller 通过select a spec可以查看生产者和消费者各自的接口信息
十三、服务链路追踪
微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。而Zipkin能解决此问题
1、环境搭建
1)下载zkpkin jar包:https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
2)通过java -jar 命令运行jar包
输入http://localhost:9411进入首界面
3)创建两个子项目,maven配置保持差不多一致,只是端口号不一样
3.1) 引入maven
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
3.2) 引入配置
主意运行的jar包端口号为9411
server:
port: 10013
spring:
zipkin:
##对应的运行jar包域名
base-url: http://localhost:9411
application:
name: zipkinServiceOne
3.3) 创年启动类
实例1:
@SpringBootApplication
@RestController
public class ZipKinServiceOneApplication {
private static Logger logger = Logger.getLogger(ZipKinServiceOneApplication.class);
@Autowired
RestTemplate restTemplate;
public static void main(String[] args) {
SpringApplication.run(ZipKinServiceOneApplication.class, args);
}
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@GetMapping("/getInfoOne")
public String callHome() {
logger.log(Level.INFO,"calling trace service-one");
return restTemplate.getForObject("http://localhost:10014/getInfoTwo", String.class);
}
@GetMapping("/info")
public String info() {
logger.log(Level.INFO,"calling trace service-one");
return "i am service-one";
}
@Bean
public Sampler defaultSampler() {
return Sampler.ALWAYS_SAMPLE;
}
}
实例2:
@SpringBootApplication
@RestController
public class ZipKinServiceTwoApplication {
private static Logger logger = Logger.getLogger(ZipKinServiceTwoApplication.class);
@Autowired
RestTemplate restTemplate;
public static void main(String[] args) {
SpringApplication.run(ZipKinServiceTwoApplication.class, args);
}
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@GetMapping("/getInfoTwo")
public String callHome() {
logger.log(Level.INFO,"calling trace service-two");
return restTemplate.getForObject("http://localhost:10013/info", String.class);
}
@GetMapping("/info")
public String info() {
logger.log(Level.INFO,"calling trace service-two");
return "i am service-two";
}
@Bean
public Sampler defaultSampler() {
return Sampler.ALWAYS_SAMPLE;
}
}
3.4) 访问10013端口号的getInfoOne服务:http://localhost:10013/getInfoOne
3.4)此时查看zipkin控制太的依赖分析,可以看到链路调用情况