什么是SpringCloud
Spring Cloud 是一套完整的微服务
解决方案,Spring Cloud 构建于 Spring Boot 之上,使得开发者很容易入手并快速应用于生产中。准确的说,它不是一个框架,而是一个大的容器,它将市面上较好的微服务框架集成进来,从而简化了开发者的代码量。
springCloud和spring boot有什么关系?
springboot可以快速开发单个微服务。springcloud是一个基于springboot实现的一系列框架的集合,用来提供全局的服务治理方案。springcloud要基于springboot来实现,离不开springboot。
springcloud的版本
Spring Cloud 的版本号并不是我们通常见的数字版本号,而是一英国伦敦地铁站的站名。同时根据字母表的顺序来对应版本时间顺序,
SNAPSHOT
快照版,可以稳定使用,且仍在继续改进版本。
GA
General Availability,正式发布的版本,官方开始推荐广泛使用,国外有的用GA来表示release版本。
什么是微服务呢?
单体架构
既然提到微服务那么我们就要提单体架构
,在软件设计的时候经常提到和使用经典的3层模型,即表现层,业务逻辑层,数据访问层。虽然在软件设计中划分了3层模型,但是对业务场景没有划分,一个典型的单体架构就是将所有的业务场景的表现层,业务逻辑层,数据访问层放在一个工程中最终经过编译,打包,部署在一台服务器上。
访问量小的时候这种架构的性价比还是比较高的,开发速度快,成本低,但是随着业务的发展,逻辑越来越复杂,代码量越来越大,代码得可读性和可维护性越来越低。用户的增加,访问量越来越多单体架构的应用并发能力十分有限。在这样的环境下便催生了微服务架构。
微服务架构
微服务架构就是将单一程序开发成一个微服务,每个微服务运行在自己的进程中,并使用轻量级的通信机制进行通信。这些服务围绕业务能力来划分成不同的微服务,并通过自动化部署机制来独立部署。这些服务可以使用不同的编程语言,不同数据库,以保证最低限度的集中式管理。总结起来微服务就是将一个单体架构的应用按业务划分为一个个的独立运行的程序即服务,它们之间可以通过HTTP协议或者消息队列(如RoocketMQ,Kafaka等)进行通信,可以采用不同的编程语言,使用不同的存储技术,自动化部署(如Jenkins)减少人为控制,降低出错概率。服务数量越多,管理起来越复杂,因此采用集中化管理。例如Eureka,Zookeeper等都是比较常见的服务集中化管理框架。
微服务带来了很多好处,但是也带来了很多问题
优点:
针对特定服务发布,影响小,风险小,成本低
频繁发布版本,快速交付需求
低成本扩容,弹性伸缩,适应云环境
缺点:
分布式系统的复杂性
部署,测试和监控的成本问题
分布式事务和CAP的相关问题
springCloud和Dubbo的关系。
Spring Cloud: Spring Cloud 诞生于微服务架构时代,考虑的是微服务治理的方方面面,另外由于依托了 Spirng、Spirng Boot 的优势之上,两个框架在开始目标就不一致,Dubbo定位服务治理、Spirng Cloud 是一个生态。服务提供方和服务消费方通过json方式交互,因此只需要定义好相关json字段即可,消费方和提供方无接口依赖。通过注解方式来实现服务配置,对于程序有一定入侵。
Dubbo:Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断。通过xml配置方式即可方面接入dubbo,对程序无入侵。Dubbo 底层是使用 Netty 这样的 NIO 框架,是基于TCP 协议传输的,配合以 Hession 序列化完成 RPC 通信。
SOA
:面向服务架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和协议联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。
SpringCloud经常用的5个组件
服务注册与发现——Netflix Eureka
Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是基于REST的服务,用于定位服务,以实现云端中间件层服务发现和故障转移,服务注册与发现对于微服务来说是非常重要的,有了服务注册与发现,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了,功能类似于Dubbo的注册中心,比如Zookeeper.
Eureka 实现服务注册与发现的流程:
1:首先我们要搭建一个 Eureka Server 作为服务注册中心;负责管理服务的注册与发现。
2:服务提供者 Eureka Client 启动时,会把当前服务器的信息以服务名(spring.application.name)的方式注册到服务注册中心;便于消费者获取。
3:服务消费者 Eureka Client 启动时,也会向服务注册中心注册;同时服务消费者还会从注册中心获取一份可用服务列表,该列表中包含了所有注册到服务注册中心的服务信息(包括服务提供者和自身的信息);
4:在获得了可用服务列表后,服务消费者通过 HTTP 或消息中间件远程调用服务提供者提供的服务。
创建注册中心
1:导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
2:配置application.yml
在EurekaClientConfigBean里面有
默认的url,我们需要把他修改了
server:
port: 7001
eureka:
instance:
hostname: localhost #Eureka服务端的实例名称
client:
#表示是否向Eureka注册中心注册自己,因为这是注册中心自己,没必要注册自己,服务端才需要注册自己
register-with-eureka: false
fetch-registry: false #false表示自己就是注册中心
service-url:
#Eureka服务的地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
3:编写主启动类,启动Eureka的服务。
package com.dongmu;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
把我们的服务注册到注册中心
1:在服务方添加依赖
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
2:服务端把服务注册进去
application.yml
#eureka的配置
eureka:
client:
register-with-eureka: true
service-url:
#这里写远程注册中心的地址,由于测试的时候注册中心就在本机所以写的是localhost
defaultZone: http://localhost:7001/eureka/
instance:
#这个会显示再远程eureka的面板上
instance-id: 冬木测试使用的id
3:主启动类启动注解支持
@EnableEurekaClient
这时候服务启动的时候就会把我们的服务注册到远程的注册中心
注意先启动注册中心,再启动服务提供者。
查看eureka的面板
可以发现我们的服务的id对应一个超链接,
这个超链接指向我们服务端的一个接口,这个接口就是服务端配置的用来在Eureka中显示当前服务的一些信息的页面。如果我们不进行配置访问的时候就是error。这个其实就是属于服务端自己的配置了,和Eureka没有太大的关系。下面说一下服务端怎么配置这个东西。
加入依赖
<!--这个包可以帮助我们配置当前这个服务在Eureka中监控页面的信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
然后在配置文件中写info
#info的配置,
info:
app.name: dongmu的info的配置
company.name: 北海公司
然后我们再点击这个超链接
看,这里就跳到可以看到我们刚才配置的信息的页面。就是我们配置信息里面的数据的json格式。这有什么用呢?这可以用来注释我们的这个微服务是干嘛的,这时候我们就可以在注册中心看到这个微服务的描述信息。
另外服务端还可以利用DiscoveryClient
这个类然后通过一些配置之后提供这个服务的一些信息,具体不过赘述。参考1。参考2
Eureka的自我保护机制
可以发现Eureka界面里面还有一行的红字,这个是Eureka的自我保护机制。
默认情况下,如果 Eureka Server 在一定的 90s 内没有接收到某个微服务实例的心跳,会注销该实例。但是在微服务架构下服务之间通常都是跨进程调用,网络通信往往会面临着各种问题,比如微服务状态正常,网络分区故障,导致此实例被注销。
固定时间内大量实例被注销,可能会严重威胁整个微服务架构的可用性。为了解决这个问题,Eureka开发了自我保护机制,那么什么是自我保护机制呢?
Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,但是在保护期内如果服务刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。
这个时候上面的红字就可以解释了。由于我是单机测试,很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka 的保护机制,一旦开启了保护机制,则服务注册中心维护的服务实例就不是那么准确了,此时我们可以使用eureka.server.enable-self-preservation=false
来关闭保护机制,这样可以确保注册中心中不可用的实例被及时的剔除,但是不推荐这样做。
意义:因为它不会从注册列表中剔除因长时间没收到心跳导致租期过期的服务,而是等待修复,直到心跳恢复正常之后,它自动退出自我保护模式。这种模式旨在避免因网络分区故障导致服务不可用的问题。
服务续约Renew
Eureka 客户会每隔30秒(默认情况下)发送一次心跳来续约。 通过续约来告知 Eureka Server 该 Eureka 客户仍然存在,没有出现问题。 正常情况下,如果 Eureka Server在90秒没有收到 Eureka 客户的续约,它会将实例从其注册表中删除。
获取注册列表信息 Fetch Registries
Eureka 注册中心从服务提供者那里获取注册表信息,并将其缓存在本地。消费者端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与 Eureka 客户端的缓存信息不同, Eureka 客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka 客户端则会重新获取整个注册表信息。 Eureka 服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka 服务提供者和 Eureka 注册中心可以使用JSON / XML格式进行通讯。在默认的情况下 Eureka 客户端使用压缩 JSON 格式来获取注册列表的信息。
服务下线 Cancel
Eureka中的服务提供者在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:DiscoveryManager.getInstance().shutdownComponent();
Eureka集群
如果我们把所有的服务都只是放在一个Eureka里面,那万一我们这个Eureka服务崩溃了就完了。所以要搭建Eureka集群。
配置方式:
1:在每个注册中心的配置文件中添加其他注册中心的地址(即Eureka服务的地址)
server:
port: 7001
eureka:
instance:
hostname: localhost #Eureka服务端的实例名称
client:
#表示是否向Eureka注册中心注册自己,因为这是注册中心自己,没必要注册自己,服务端才需要注册自己
register-with-eureka: false
fetch-registry: false #false表示自己就是注册中心
service-url:
#Eureka服务的地址
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
defaultZone: http://localhost:7002/eureka/,http://localhost:7003/eureka/
2:服务提供者暴漏自己到多个Eureka注册中心
#eureka的配置
eureka:
client:
register-with-eureka: true
service-url:
#这里写远程注册中心的地址,由于测试的时候注册中心就在本机所以写的是localhost
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
这个时候我们在每一个服务里面就可以看到其他的Eureka服务了。
一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
可用性(A):保证每个请求不管成功或者失败都有响应。
分区容忍性(P):系统中任意信息的丢失或失败不会影响系统的继续运作。 [1]
Eureka保证的是AP。
Zookeeper保证的是CP
客服端负载均衡——SpringCloud Ribbon
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端负载均衡的工具。简单地来说就是把客户的请求比较平均地分配给不同的服务器来处理,减少服务器的压力。
Ribbon是根据Eureka的数据,直接访问不同的服务器,而 Nignx是一种集中式的负载均衡器,请求先到达Nignx然后再到达服务器。
负载均衡的算法
负载均衡的算法很多,这里仅介绍部分算法。
Ribbon默认是使用的 RoundRobinRule 轮询策略。
如果要修改默认的配置使用下面的方式
providerName:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
自定义负载均衡算法
Ribbon 我们还可以自定义负载均衡算法。
首先我们要实现 IRule 接口,然后修改配置文件或者自定义 Java Config 类。
负载均衡实践
1:添加依赖
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
2:配置文件
服务1
server:
port: 8001
servlet:
context-path: /api
mybatis:
type-aliases-package: com.dongmu.pojo
mapper-locations: classpath:com/dongmu/dao/*.xml
config-location: classpath:com/dongmu/mybatis-config.xml
spring:
application:
#这个名字就是我们注册中心会显示的名字,也就是我们服务模块的名字
name: springCloud-Provider-Dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&serverTimezone=GMT%2b8&characterEncoding=UTF-8&useSSL=false&failOverReadOnly=false
username: root
password: 123456
#eureka的配置
eureka:
client:
register-with-eureka: true
service-url:
#这里写远程注册中心的地址,由于测试的时候注册中心就在本机所以写的是localhost
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
instance:
#这个会显示再远程eureka的面板上
instance-id: deptService_8001
#info的配置,
info:
app.name: dongmu的info的配置
company.name: 北海公司
服务2
server:
port: 8002
servlet:
context-path: /api
mybatis:
type-aliases-package: com.dongmu.pojo
mapper-locations: classpath:com/dongmu/dao/*.xml
config-location: classpath:com/dongmu/mybatis-config.xml
spring:
application:
#这个名字就是我们注册中心会显示的名字,也就是我们服务模块的名字
name: springCloud-Provider-Dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db02?useUnicode=true&serverTimezone=GMT%2b8&characterEncoding=UTF-8&useSSL=false&failOverReadOnly=false
username: root
password: 123456
#eureka的配置
eureka:
client:
register-with-eureka: true
service-url:
#这里写远程注册中心的地址,由于测试的时候注册中心就在本机所以写的是localhost
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
instance:
#这个会显示再远程eureka的面板上
instance-id: deptService_8002
#info的配置,
info:
app.name: dongmu的info的配置
company.name: 北海公司
服务3
server:
port: 8003
servlet:
context-path: /api
mybatis:
type-aliases-package: com.dongmu.pojo
mapper-locations: classpath:com/dongmu/dao/*.xml
config-location: classpath:com/dongmu/mybatis-config.xml
spring:
application:
#这个名字就是我们注册中心会显示的名字,也就是我们服务模块的名字
name: springCloud-Provider-Dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db03?useUnicode=true&serverTimezone=GMT%2b8&characterEncoding=UTF-8&useSSL=false&failOverReadOnly=false
username: root
password: 123456
#eureka的配置
eureka:
client:
register-with-eureka: true
service-url:
#这里写远程注册中心的地址,由于测试的时候注册中心就在本机所以写的是localhost
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
instance:
#这个会显示再远程eureka的面板上
instance-id: deptService_8003
#info的配置,
info:
app.name: dongmu的info的配置
company.name: 北海公司
在不同的服务中连接不同的数据库,从而可以看到我们走了哪些路径。但是要注意,这三个服务的名称(spring.application.name: springCloud-Provider-Dept
)要一样,但是id(instance-id: deptService_8003
)不能一样,这样才算是一个服务访问不同的服务器。
3:主启动类
加上 @EnableEurekaClient
然后启动项目,通过消费者进行访问即可。
我们先看一下注册中心,可以看到每个注册中心的同一个服务都有三个不同的服务器。下面仅展示一个注册中心。
访问测试结果如下:
可以发现刷新可以走不同的服务。而且每次切换服务的顺序都是一样的。这是因为我们消费者的配置是下面这样的。它默认的算法就是轮询。
package com.dongmu.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class BeanConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
自定义负载均衡算法
刚才的轮询算法会导致有的服务崩了它还是会访问这个服务,然后报错。
我们可以自己实现负载均衡的算法
首先是创建一个类实现IRule接口,然后把这个类的实例注入容器中。
/**
* IRule:
* RoundRobinRule 轮询策略
* RandomRule 随机策略
* AvailabilityFilteringRule : 会先过滤掉,跳闸,访问故障的服务~,对剩下的进行轮询~
* RetryRule : 会先按照轮询获取服务~,如果服务获取失败,则会在指定的时间内进行,重试
*/
@Bean
public IRule myIUule(){
return new RandomRule();
}
上面的配置会在所有的@RibbonClient中共享,如果我们要不同的服务有不同的负载均衡算法,
如果我们自定义的负载均衡类就不应该在启动类子包或者同级目录下,在其他目录配置类中加上@Configuraction,然后可以在启动类上加注解
@RibbonClient(name = "",configuration = )
实现。name指定我们需要进行负载均衡的服务的名字,configuration指定我们负载均衡类的class文件。
详情参考