计算机集群简称集群是一种计算机系统,它通过一组松散集成的计算机软件和/或硬件连接起来高度紧密地协作完成计算工作。在某种意义上,他们可以被看作是一台计算机。集群系统中的单个计算机通常称为节点,通常通过局域网连接,但也有其它的可能连接方式。集群计算机通常用来改进单个计算机的计算速度和/或可靠性。一般情况下集群计算机比单个计算机,比如工作站或超级计算机性能价格比要高得多。
集群的特点:
就相当于把一个服务部署到了多个服务器上在运行,用户可以访问到不同的服务器,这样我们每台服务器的压力就会小很多。而且其中一台出问题,另一台依旧可以正常运行。
总结:多个服务器运行一套相同的代码。
分布式系统是一组计算机,通过网络相互连接传递消息与通信后并协调它们的行为而形成的系统。组件之间彼此进行交互以实现一个共同的目标。
特点:
因此,分布式的概念就是将具体任务进行拆分,不同模块部署在不同的服务器上,各自实现一个小功能,多个模块组合起来就能实现我们想要的功能。
总结:不同服务器运行不同的代码,多台服务器协同工作以实现一个共同的目标。
集群和分布式并不冲突,可以由分布式集群。即做相同的事情多台服务器可以看做是集群的,做不同事情的多台服务器可以看做是分布式的。
参考资料:
在分布式概念中,我们一般把拆分的一个子业务称作是一个节点。CAP理论主要是指以下三个方面:
下面这个集群有三个节点,此时三个节点都能够互相通信:
由于我们的系统是分布式的,节点之间的通信是通过网络来进行的,因此可能会出现因为某些故障使得节点之间不能通信的情况,此时的整个网络就分成了几块区域。数据分散在网络的这些不连通的区域中,这就叫做分区。
例如,在出现了网络分区后,有一个注册账户的请求过来了,此时我们节点一和节点三是不可通信的。
于是就有了抉择:
一般来说,我们的分布式系统要求P:分区容错性是必须的,CAP无法完全兼顾,从上面的例子可以看出,我们可以根据实际情况决定是选择AP还是选择CP。
但是要注意的是,不是说选择了A就完全抛弃了C,选择了C就完全抛弃A。
在CAP理论中,C所表示的一致性是强一致性,即要求每个节点的数据都必须是最新版本,其实一致性还有其他级别:
可用性的值域可以定义成**0到100%**的连续区间:
可用性分类 | 可用水平(%) | 年可容忍停机时间 |
---|---|---|
容错可用性 | 99.9999 | <1 min |
极高可用性 | 99.999 | <5 min |
具有故障自动恢复能力的可用性 | 99.99 | <53 min |
高可用性 | 99.9 | <8.8 h |
商品可用性 | 99 | <43.8 h |
所以CAP理论定义的其实是在容忍网络分区的条件下,“强一致性”和“极致可用性”是无法同时达到的。
参考资料:
扩展阅读:
从分布式和微服务的角度来看,我们把一个大的项目,分解成多个小的模块。这些小的模块组合起来,共同完成某些功能。注:这些模块是独立成一个子系统的(即部署在不同主机上的)
拆分出多个模块后,就会出现各种各样的问题,而SpringCloud提供了一整套完整的解决方案!
SpringCloud是一个全家桶式的技术栈,包含了很多组件。最核心的几个是Eureka、Ribbon、Feign、Hystrix、Zuul这几个组件。
像上面提到的,当我们将项目划分为多个模块后,很可能会遇到子系统之间的通信问题。子系统与子系统之间不是在同一个环境下,那么就需要远程调用。
远程调用就必须要知道IP地址,此时我们可以通过显示IP地址调用,但是如果很多服务之间都存在着远程调用,并且其中某一个服务的IP地址发生了变化,那么我们所有使用到这个IP地址的地方都要进行手动更新。
在服务多的情况下,手动维护这些静态配置简直就是噩梦!
于是为了解决微服务架构中的服务实例维护问题(IP地址),产生了大量的服务治理框架和产品。这些框架和产品的实现都围绕着服务注册和服务发现机制来完成对微服务应用实例的自动化管理。
在SpringCloud中使用的服务治理框架就是Eureka。针对上面提到的服务间的通信问题,Eureka是这样来解决的:
A、B、C、D四个服务都可以拿到Eureka(服务E)上的那份注册清单。A、B、C、D四个服务相互调用不在通过具体的IP地址,而是通过服务名来调用!
其实本质上就是:在代码中通过服务名去Eureka服务器获取到对应的IP地址(IP地址会变,但服务名一般是不会变的)。
Eureka是 Netflix 出品的用于实现服务注册和发现的工具。 SpringCloud 集成了Eureka,并提供了开箱即用的支持。其中,Eureka又可细分为 Eureka Server 和 Eureka Client。
上图是来自eureka的官方架构图,他是基于集群配置的eureka。
Spring Cloud Eureka是Spring Cloud Netflix微服务套件中的一部分,它基于Netflix Eureka做了二次封装。主要负责完成微服务架构中的服务治理功能。
在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,包括服务的主机与端口号、服务版本号、通讯协议等一些附加信息。
注册中心按照服务名分类组织服务清单,同时还需要以心跳检测的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,以达到排除故障服务的效果。
服务提供者在启动的时候会通过REST请求的方式将自己注册到Eureka Server上,同时带上自身服务的一些元数据信息。Eureka Server接收到这个Rest请求之后,将元数据信息存储在一个双层结构的Map中,其中第一层的key是服务名。第二层的key 是具体服务的实例名。
在服务注册时,需要确认一下eureka.client.register-with-eureka=true
参数是否正确,该值默认为true。若设置为fasle将不会启动注册操作。
在服务治理框架下,服务间的调用不再通过指定具体的实例地址来实现,而是通过服务名发起请求调用实现。服务调用方通过服务名从服务注册中心的服务清单中获取服务实例的列表清单,通过指定的负载均衡策略取出一个服务实例位置来进行服务调用。
Eureka专门用于给其他服务注册的称为Eureka Server(服务注册中心),其余注册到Eureka Server的服务称为Eureka Client。
Eureka服务端,即服务注册中心,他支持高可用配置。依托强一致性提供良好的服务实例可用性,可以应对多种不同的故障场景。
Eureka服务端支持集群模式部署,当集群中有分片发生故障时,Eureka会自动转入自我保护模式。它允许在分片发生故障时继续提供服务的发现和注册,当故障恢复时,集群中的其他分片会把他们的状态再次同步回来。集群中的不同服务注册中心通过异步模式互相复制各自的状态,这也意味着在给定的时间点上每个实例关于所有服务的所有服务状态可能存在不一致的情况。
有些时候,我们的服务实例并不一定会正常下线,可能由于内存溢出、网络故障等原因使服务不能正常运作。而服务注册中心并未收到“服务下线”的请求,为了从服务列表中将这些无法正常提供服务的实例剔除掉,Eureka Server在启动的时候会创建一个定时任务(默认60s),默认每隔一段时间将当前服务清单中超时(默认90s)的服务剔除出去。
服务注册在Eureka Server之后,会维护一个心跳连接,告诉Eureka Server自己还活着。Eureka Server在运行期间会统计心跳失败的比例在15分钟内是否低于85%,如果低于,Eureka Server会将当前的实例注册信息保护起来,让这些实例不会过期,这样的做法会导致客户端很容易拿到那些实际已经不存在的服务实例,出现调用失败的情况。因此客户端要有容错机制,比如请求重试、断路器等。
关于自我保护相关的属性配置:
#可以设置改参数值为false,以确保注册中心将不可用的实例删除
eureka.server.enableSelfPreservation=true
当我们启动了Eureka Server,然后在浏览器中输入http://localhost:8761/后,直接回车,就进入了SpringCloud的服务治理页面,这么做在生产环境是极不安全的,下面我们就给Eureka Server加上安全的用户认证:
(1) pom文件中引入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
(2) serviceUrl中加入安全校验信息
eureka.client.serviceUrl.defaultZone=http://:@${eureka.instance.hostname}:${server.port}/eureka/
Eureka客户端,主要处理服务的注册和发现。客户端服务通过注册和参数配置的方式,嵌入在客户端应用程序的代码中。应用程序启动时,Eureka客户端向注册服务中心注册自身提供的服务,并周期性地发送心跳来更新他的服务租约。同时,他也能从服务端查询当前注册的服务信息,并把他们缓存在本地,并周期性地刷新服务状态。
在服务治理框架中,通常每个客户端服务单元向注册中心登记自己提供的服务,包括服务的主机和端口号、服务版本号、通信协议等附加信息。注册中心按照服务名分类组织服务清单,同时还需要以心跳检测的方式去检测清单中的服务是否可用,若不可用需要从服务清单中剔除,以达到排除故障服务的效果。
Eureka Client分为服务提供者和服务消费者。
服务提供者在启动的时候会通过REST请求的方式将自己注册到Eureka Server上,同时带上自身服务的一些元数据信息。Eureka Server接收到这个Rest请求之后,将元数据信息存储在一个双层结构的Map中,其中第一层的key是服务名。第二层的key 是具体服务的实例名。
在服务注册时,需要确认一下eureka.client.register-with-eureka=true
参数是否正确,该值默认为true。若设置为fasle将不会启动注册操作。
从eureka服务治理体系架构图中可以看到,不同的服务提供者可以注册在不同的服务注册中心上,它们的信息被不同的服务注册中心维护。
此时,由于多个服务注册中心互相注册为服务,当服务提供者发送注册请求到一个服务注册中心时,它会将该请求转发给集群中相连的其他注册中心,从而实现服务注册中心之间的服务同步。通过服务同步,提供者的服务信息就可以通过集群中的任意一个服务注册中心获得。
在注册服务之后,服务提供者会维护一个心跳用来持续高速Eureka Server,“我还在持续提供服务”,否则Eureka Server的剔除任务会将该服务实例从服务列表中排除出去。我们称之为服务续约。
下面是服务续约的两个重要属性:
(1)eureka.instance.lease-expiration-duration-in-seconds
leaseExpirationDurationInSeconds
,表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance。
(2)eureka.instance.lease-renewal-interval-in-seconds
leaseRenewalIntervalInSeconds
,表示eureka client发送心跳给server端的频率。如果在leaseExpirationDurationInSeconds
后,server端没有收到client的心跳,则将摘除该instance。除此之外,如果该instance实现了HealthCheckCallback
,并决定让自己unavailable的话,则该instance也不会接收到流量。
在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务关闭操作时,会触发一个服务下线的Rest服务请求给Eureka Server,告诉服务注册中心:“我要下线了”,服务端在接收到该请求后,将该服务状态置位下线(DOWN),并把该下线事件传播出去。
消费者服务启动时,会发送一个Rest请求给服务注册中心,来获取上面注册的服务清单。为了性能考虑,Eureka Server会维护一份只读的服务注册清单来返回给客户端,同时该缓存清单默认会每隔30秒更新一次。
下面是获取服务的两个重要的属性:
(1)eureka.client.fetch-registry
是否需要去检索寻找服务,默认是true
(2)eureka.client.registry-fetch-interval-seconds
表示eureka client间隔多久去拉取服务注册信息,默认为30秒,对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
服务消费者在获取服务清单后,通过服务名可以获取具体提供服务的实例名和该实例的元数据信息。因为有这些服务实例的详细信息,所以客户端可以根据自己的需要决定具体调用哪个实例,在Ribbon中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡。
在默认配置下,Eureka Server 会将自己也作为客户端,即在注册中心注册自己,我们一般需要将其禁用:
#服务注册中心端口号
server.port=8761
#服务注册中心实例的主机名
eureka.instance.hostname=localhost
#是否向服务注册中心注册自己
eureka.client.register-with-eureka=false
#是否检索服务
eureka.client.fetch-registry=false
#服务注册中心的配置内容,指定服务注册中心的位置
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
优秀博文:
现在我们已经可以通过服务名来获取具体服务实例的IP地址了,接下来就是实现远程调用,传统情况下在Java代码里访问RESTful服务,一般使用Apache的HttpClient,不过此种方法使用起来太过繁琐。在使用SpringCloud的时候我们不需要自己创建HttpClient来进行远程调用。可以使用Spring封装好的RestTemplate工具类:
// 传统的方式,直接显示写死IP是不好的!
//private static final String REST_URL_PREFIX = "http://localhost:8001";
// 服务实例名
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";
/**
* 使用 使用restTemplate访问restful接口非常的简单粗暴无脑。 (url, requestMap,
* ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
*/
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/consumer/dept/add")
public boolean add(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
}
Feign是一个声明式http客户端。使用Feign能让编写http客户端更加简单,它的使用方法是定义一个接口,然后在上面添加注解,避免了调用目标微服务时,需要不断的解析/封装json数据的繁琐。Spring Cloud中Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。
// value --->指定调用哪个服务
// fallbackFactory--->熔断器的降级提示
@FeignClient(value = "MICROSERVICECLOUD-DEPT")
public interface DeptClientService {
// 采用Feign我们可以使用SpringMVC的注解来对服务进行绑定
@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);
}
Feign的一个关键机制就是使用了动态代理。一起来看看下面的图,结合图来分析:
为了实现服务的高可用,我们可以将服务提供者集群。比如说,现在一个秒杀系统设计出来了,准备上线了。在双十一的时候为了能够支持高并发,我们多开几台机器来支持并发量。
现在想要这三个秒杀系统合理摊分用户的请求,Feign怎么知道该请求哪台机器呢?
这时Spring Cloud Ribbon就派上用场了。Ribbon就是专门解决这个问题的。它的作用是负载均衡,会帮你在每次请求时选择一台机器,均匀的把请求分发到各个机器上。
负载均衡有两种类型:
所以,我们的图可以画成这样:
Ribbon默认的负载均衡策略是轮询,我们也是可以根据自己实际的需求自定义负载均衡策略的。
@Configuration
public class MySelfRule
{
@Bean
public IRule myRule()
{
//return new RandomRule();// Ribbon默认是轮询,我自定义为随机
//return new RoundRobinRule();// Ribbon默认是轮询,我自定义为随机
return new RandomRule_ZY();// 我自定义为每台机器5次
}
}
实现起来也很简单:继承AbstractLoadBalancerRule
类,重写public Server choose (ILoadBalancer lb, Object key)
即可。
此外,Ribbon是和Feign以及Eureka紧密协作,完成工作的,具体如下:
(贴一篇博文,后面在深入了解Ribbon)
优秀博文:
在微服务架构里,一个系统会有很多的服务。比如以下业务场景:订单服务在一个业务流程里需要调用库存服务、仓储服务、积分服务这三个服务。现在假设订单服务自己最多只有100个线程可以处理请求,然后呢,积分服务不幸的挂了,每次订单服务调用积分服务的时候,都会卡住几秒钟,然后抛出—个超时异常。
这样会导致什么问题呢?
上面这个,就是微服务架构中恐怖的服务雪崩问题,如下图所示:
如上图,这么多服务互相调用,要是不做任何保护的话,某一个服务挂了,就会引起连锁反应,导致别的服务也挂。比如积分服务挂了,会导致订单服务的线程全部卡在请求积分服务这里,没有一个线程可以工作,瞬间导致订单服务也挂了,别人请求订单服务全部会卡住,无法响应。
Hystrix是隔离、熔断以及降级的一个框架。简单来说就是,Hystrix会搞很多个小小的线程池,比如订单服务请求库存服务是一个线程池,请求仓储服务是一个线程池,请求积分服务是一个线程池。每个线程池里的线程就仅仅用于请求那个服务。
此时如果积分服务挂了,那么订单服务里用来调用积分服务的线程也就都卡死不能工作了,但由于订单服务调用库存服务、仓储服务的这两个线程池都是正常工作的,所以这两个服务不会受到任何影响。这个时候如果别人请求订单服务,订单服务还是可以正常调用库存服务扣减库存,调用仓储服务通知发货。只不过调用积分服务的时候,每次都会报错。**但是如果积分服务都挂了,每次调用都要去卡住几秒钟也没有意义!**所以我们可以直接对积分服务熔断,比如在5分钟内请求积分服务直接返回,不再进行网络请求卡住几秒钟,这个过程,就是所谓的熔断!
此时为了记录用户积分,可以在每次调用积分服务时,在数据库里新增一条积分记录,说给某某用户增加多少积分,因为积分服务挂了,导致没增加成功!这样等积分服务恢复了,就可以根据这些记录手工加一下积分。这个过程,就是所谓的降级。
Hystrix隔离、熔断和降级的全流程:
Spring Cloud Hystrix实现了断路器、线程隔离等一系列服务保护功能。
Hystrix提供几个熔断关键参数:滑动窗口大小(20)、 熔断器开关间隔(5s)、错误率(50%)
参考资料:
基于上面的学习,我们现在的架构大致会设计成这样:
这样的架构会有两个比较麻烦的问题:
为了解决上面这些常见的架构问题,API网关的概念应运而生。在SpringCloud中了提供了基于Netfl ix Zuul实现的API网关组件Spring Cloud Zuul。这个组件是负责网络路由的
Spring Cloud Zuul是这样解决上述两个问题的:
Zuul天生就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。也就是说:Zuul也是支持Hystrix和Ribbon。
Zuul支持Ribbon和Hystrix,也能够实现客户端的负载均衡。我们的Feign不也是实现客户端的负载均衡和Hystrix的吗?既然Zuul已经能够实现了,那我们的Feign还有必要吗?
可以这样理解:
参考资料:
随着业务的扩展,我们的服务会越来越多,越来越多。每个服务都有自己的配置文件。
既然是配置文件,给我们配置的东西,那难免会有些改动的。
比如我们的Demo中,每个服务都写上相同的配置文件。万一我们有一天,配置文件中的密码需要更换了,那就得三个都要重新更改。
Spring Cloud Config项目是一个解决分布式系统的配置管理方案。它包含了Client和Server两个部分,server提供配置文件的存储、以接口的形式将配置文件的内容提供出去,client通过接口获取数据、并依据此数据初始化自己的应用。
SpringCloud Config其他的知识:
使用SpringCloud Config可能的疑问:application.yml和 bootstrap.yml区别
上述几个Spring Cloud核心组件,在微服务架构中,分别扮演的角色:
SpringCloud系列文章参考资料: