一、集群、分布式、微服务、SOA概念

集群

概念:计算机集群简称集群是一种计算机系统,通过一组松散集成的计算机软件或硬件连接起来高度紧密地协作完成计算工作,在某种意义上,它们可以被看做是一台计算机,集群系统中的单个计算机通常称为节点,通常通过局域网连接,集群计算机通常用来改进单个计算机的计算速度和可靠性。

特点:

  • 通过多台计算机完成同一个工作,达到更高的效率;
  • 两台或多台机器,内容、工作过程等完全一样,如果一台不工作,另一台还可以起作用。

分布式

概念:
分布式系统是一组计算机,通过网络相互连接传递消息与通信后协调它们的行为而形成的系统,组件之间彼此进行交互以实现一个共同的目标。
好处:

  • 模块之间独立,各做各的事情,便于扩展,复用性高;
  • 高吞吐量,某个任务需要一个机器运行10个小时,将该任务用10台机器的分布式跑(将这个任务拆分为10个小任务),可能两个小时就跑完了。

SOA(面向服务的架构)

SOA是一种设计方法,其中包含多个服务,而服务之间通过配合最终会提供一系列功能。一个服务通常以独立的形式存在于操作系统进程中。服务之间通过网络调用,而非采用进程内调用的方式进行通信。

二、CAP理论

CAP理论作为分布式系统的基础理论,它描述的是一个分布式系统在以下三个特性中:

  • 一致性(Consistency)
  • 可用性(Availability)
  • 分区容错性(Partition tolerance)

最多满足其中的两个特性,也就是下图所描述的,分布式系统要么满足CP,要么满足AP,无法同时满足CAP。
在这里插入图片描述

什么是一致性、可用性和分区容错性

分区容错性:指的是分布式系统中的某个节点或者网络分区出现了故障的时候,整个系统仍能对外提供满足一致性和可用性的服务,也就是说部分故障不影响整体使用。所以分区容错性是必不可少的。
可用性:一直可以正常地做读写操作,简单而言就是客户单一直可以正常访问并得到系统的正常响应,用户角度来看就是不会出现系统操作失败或者访问超时等问题。
一致性:在分布式系统完成某写操作后任何读操作都应该获取到该写操作写入的那个最新的值,相当于要求分布式系统中的各节点时时刻刻都保持数据的一致性。

如何理解一致性、可用性和分区容错性

如果我们事先保证了分区容错性,也就意味着即使某个节点出现故障,用户还是可以继续访问,只是用户在访问的过程中就会出现一致性和可用性不能同时满足的情况。
在这里插入图片描述
如图假设分布式系统有G1、G2两个节点,初始值都是v0,现在有一个client向系统写入了值v1,这里假设直接写的是节点G1,写完之后client再去读取这个值,这时读到了G2节点,由于G2节点与G1节点失去连接,这时G1节点上的数据还未同步到G2节点,因此客户端读取到的是修改之前的值v0,这就出现了不满足一致性的情况了,相当于满足了可用性,失去了一致性。

类似的,如果系统保证了强的一致性,那么在client 写完G1节点后, 而G1向G2节点同步数据出现了问题,这时如果client再去读取G2节点的数据时,client就会一直处于等待状态,因为系统内各节点数据为同步上,需要等同步上才能使用。这就相当于满足了一致性,而失去了可用性。
在这里插入图片描述
考虑多个客户端访问时,一致性和可用性还可以这么理解:假如client1 向G1 修改某个值的时候, 写操作还未完成,client2就发起来对该值的读操作,读的是G2节点,这时如果要满足一致性,那么就得让client2 暂时无法使用,如果要让client2 使用,那么获取到的数据不是最新的,系统就不满足一致性。

CAP是无法完全兼顾的,从上面的例子也可以看出,我们可以选AP,也可以选CP。但是,要注意的是:不是说选了AP,C就完全抛弃了。不是说选了CP,A就完全抛弃了!
在CAP理论中,C所表示的一致性是强一致性(每个节点的数据都是最新版本),其实一致性还有其他级别的:

  • 弱一致性:弱一致性是相对于强一致性而言,它不保证总能得到最新的值;
  • 最终一致性(eventual consistency):放宽对时间的要求,在被调完成操作响应后的某个时间点,被调多个节点的数据最终达成一致。

三、Spring Cloud

为什么需要Spring Cloud?

将一个大的项目分解为多个小的模块,这些小的模块组合起来完成功能,但是拆分成多个模块以后,会出现各种各样的问题,而Spring Cloud提供了一整套的解决方案:
Spring Cloud的基础功能:

  • 服务治理:Eureka
  • 客户端负载均衡:Ribbon
  • 服务容错保护:Hystrix
  • 声明式服务调用:Feign
  • API网关服务:Zuul
  • 分布式配置中心:Config

以及一些其他的高级功能:

  • 消息总线:Bus
  • 消息驱动的微服务:Stream
  • 分布式服务跟踪:Sleuth

四、Eureka

Eureka主要是解决子系统之间的通信问题,子系统与子系统之间不是在同一个环境下,所以需要远程调用,远程调用可能就会想到httpClient、webService等这些技术实现,既然是远程调用,就必须知道ip地址,而人为去维护这些静态配置几乎不太现实,所以为了解决微服务架构中的服务实例维护问题(IP地址),产生了大量的服务治理框架和产品,这些框架和产品的实现都围绕着服务注册与服务发现机制来完成对微服务应用实例的自动化管理。在SpringCloud中我们的服务治理框架一般使用的就是Eureka。

问题重现:
现在有A、B、C、D四个服务,它们之间会互相调用(而且IP地址很可能会发生变化),一旦某个服务的IP地址变了,那服务中的代码要跟着变,手动维护这些静态配置(IP)非常麻烦!Eureka是这样解决上面所说的情况的:
创建一个E服务,将A、B、C、D四个服务的信息都注册到E服务上,E服务维护这些已经注册进来的信息:
在这里插入图片描述
A、B、C、D四个服务都可以拿到Eureka(服务E)那份注册清单。A、B、C、D四个服务互相调用不再通过具体的IP地址,而是通过服务名来调用!

  • 拿到注册清单—>注册清单上有服务名—>自然就能够拿到服务具体的位置了(IP)。
  • 其实简单来说就是:代码中通过服务名找到对应的IP地址(IP地址会变,但服务名一般不会变)
    在这里插入图片描述

    Eureka细节

    Eureka专门用于给其他服务注册的称为Eureka Server(服务注册中心),其余注册到Eureka Server的服务称为Eureka Client。
    在这里插入图片描述
    在Eureka Server一般我们会这样配置:
register-with-eureka: false     #false表示不向注册中心注册自己
fetch-registry: false     #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务

Eureka Client分为服务提供者和服务消费者,但是很有可能某服务既是服务提供者又是服务消费者。
如果某个服务配置没有“注册”到Eureka-Server也不用过于惊讶(但是它是可以获取Eureka服务清单的),很有可能只是作者把该服务认作为单纯的服务消费者,单纯的服务消费者无需对外提供服务,也就无需注册到Eureka中了。

下面是Eureka的治理机制:
服务提供者

  • 服务注册: 启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上了自身服务的一些元数据信息;
  • 服务续约:在注册完服务之后,服务提供者会维护一个心跳用来持续告诉Eureka Server: “我还活着 ” ;
  • 服务下线:当服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求给Eureka Server, 告诉服务注册中心:“我要下线了 ”。

服务消费者

  • 获取服务:当我们启动服务消费者的时候,它会发送一个REST请求给服务注册中心,来获取上面注册的服务清单;
  • 服务调用:服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。在进行服务调用的时候,优先访问同处一个Zone中的服务提供方。

Eureka Server(服务注册中心)

  • 失效剔除:默认每隔一段时间(默认60秒)将当前清单中超时(默认90秒)没有续约的服务剔除出去;
  • 自我保护:Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%(通常由于网络不稳定导致),Eureka Server会将当前的实例注册信息保护起来,让这些实例不会过期,尽可能保护这些注册信息。

最后得到下面这个关系图:
在这里插入图片描述

五、RestTemplate和Ribbon

通过Eureka服务治理框架,我们可以通过服务名来获取具体的服务实例的位置了(IP),一般在使用Spring Cloud的时候不需要自己手动创建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);
}

为了实现服务的高可用,我们可以将服务提供者集群。比如说,现在一个秒杀系统设计出来了,准备上线了。在11月11号时为了能够支持高并发,我们开多台机器来支持并发量。
在这里插入图片描述
现在想要这三个秒杀系统合理摊分用户的请求(专业来说就是负载均衡),可能你会想到nginx。
其实Spring Cloud也支持的负载均衡功能,只不过它是客户端的负载均衡,这个功能实现就是Ribbon!负载均衡又区分了两种类型:

  • 客户端负载均衡(Ribbon)
    服务实例的清单在客户端,客户端进行负载均衡算法分配;
    客户端可以从Eureka Server中得到一份服务清单,在发送请求时通过负载均衡算法,在多个服务器之间选择一个进行访问。
  • 服务端负载均衡(Nginx)
    服务实例的清单在服务端,服务器进行负载均衡算法分配;

在这里插入图片描述

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)即可。

六、Hystrix

到目前为止,我们的服务看起来好像挺好的了:能够根据服务名来远程调用其他的服务,可以实现客户端的负载均衡。
在这里插入图片描述
但是,如果我们在调用多个远程服务时,某个服务出现延迟,会怎么样??
在这里插入图片描述
在高并发的情况下,由于单个服务的延迟,可能导致所有的请求都处于延迟状态,甚至在几秒钟就使服务处于负载饱和的状态,资源耗尽,直到不可用,最终导致这个分布式系统都不可用,这就是“雪崩”。
在这里插入图片描述
针对上述问题, Spring Cloud Hystrix实现了断路器线程隔离等一系列服务保护功能。

  • Fallback(失败快速返回):当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应, 而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
  • 资源/依赖隔离(线程池隔离):它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的依赖服务。
  • Hystrix提供几个熔断关键参数:滑动窗口大小(20)、 熔断器开关间隔(5s)、错误率(50%)
  • 每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。
  • 直到5s之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。

Hystrix还有请求合并、请求缓存这样强大的功能,在此我就不具体说明了,有兴趣的同学可继续深入学习~

Hystrix仪表盘

Hystrix仪表盘:它主要用来实时监控Hystrix的各项指标信息。通过Hystrix Dashboard反馈的实时信息,可以帮助我们快速发现系统中存在的问题,从而及时地采取应对措施。
启动时的页面:
在这里插入图片描述
监控单服务的页面:
在这里插入图片描述
我们现在的服务是这样的:
在这里插入图片描述
除了可以开启单个实例的监控页面之外,还有一个监控端点 /turbine.stream是对集群使用的。 从端点的命名中,可以引入Turbine, 通过它来汇集监控信息,并将聚合后的信息提供给 HystrixDashboard 来集中展示和监控。
在这里插入图片描述

七、Feign

为了简化我们的开发,Spring Cloud Feign出现了!它基于 Netflix Feign 实现,整合了 Spring Cloud Ribbon 与 Spring Cloud Hystrix, 除了整合这两者的强大功能之外,它还提供了声明式的服务调用(不再通过RestTemplate)。

Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign,
我们可以做到使用HTTP请求远程服务时能与调用本地方法一样的编码体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。

下面就简单看看Feign是怎么优雅地实现远程调用的:
服务绑定:

// value --->指定调用哪个服务
// fallbackFactory--->熔断器的降级提示
@FeignClient(value = "MICROSERVICECLOUD-DEPT", fallbackFactory = DeptClientServiceFallbackFactory.class)
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中使用断路器
 * 这里主要是处理异常出错的情况(降级/熔断时服务不可用,fallback就会找到这里来)
 */
@Component // 不要忘记添加,不要忘记添加
public class DeptClientServiceFallbackFactory implements FallbackFactory<DeptClientService> {
    @Override
    public DeptClientService create(Throwable throwable) {
        return new DeptClientService() {
            @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 List<Dept> list() {
                return null;
            }

            @Override
            public boolean add(Dept dept) {
                return false;
            }
        };
    }
}

调用:
在这里插入图片描述

八、Zuul

基于上面的学习,我们现在的架构很可能会设计成这样:
在这里插入图片描述
这样的架构会有两个比较麻烦的问题:

  • 路由规则与服务实例的维护间题:外层的负载均衡(nginx)需要维护所有的服务实例清单(图上的OpenService)
  • 签名校验、 登录校验冗余问题:为了保证对外服务的安全性,
    我们在服务端实现的微服务接口,往往都会有一定的权限校验机制,但我们的服务是独立的,我们不得不在这些应用中都实现这样一套校验逻辑,这就会造成校验逻辑的冗余。

如下图:
在这里插入图片描述
每个服务都有自己的IP地址,Nginx想要正确请求转发到服务上,就必须维护着每个服务实例的地址!
更是灾难的是:这些服务实例的IP地址还有可能会变,服务之间的划分也很可能会变。
购物车和订单模块都需要用户登录了才可以正常访问,基于现在的架构,只能在购物车和订单模块都编写校验逻辑,这无疑是冗余的代码。
为了解决上面这些常见的架构问题,API网关的概念应运而生。在SpringCloud中了提供了基于Netfl ix Zuul实现的API网关组件Spring Cloud Zuul。
Spring Cloud Zuul是这样解决上述两个问题的:

  • SpringCloud Zuul通过与SpringCloud Eureka进行整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有其他微服务的实例信息。外层调用都必须通过API网关,使得将维护服务实例的工作交给了服务治理框架自动完成。
  • 在API网关服务上进行统一调用来对微服务接口做前置过滤,以实现对微服务接口的拦截和校验。

Zuul天生就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。也就是说:Zuul也是支持Hystrix和Ribbon。
关于Zuul还有很多知识点(由于篇幅问题,这里我就不细说了):

  • 路由匹配(动态路由)

  • 过滤器实现(动态过滤器)

  • 默认会过滤掉Cookie与敏感的HTTP头信息(额外配置)

    可能对Zuul的疑问

    Zuul支持Ribbon和Hystrix,也能够实现客户端的负载均衡。我们的Feign不也是实现客户端的负载均衡和Hystrix的吗?既然Zuul已经能够实现了,那我们的Feign还有必要吗?
    在这里插入图片描述
    或者可以这样理解:

  • zuul是对外暴露的唯一接口相当于路由的是controller的请求,而Ribbonhe和Fegin路由了service的请求

  • zuul做最外层请求的负载均衡 ,而Ribbon和Fegin做的是系统内部各个微服务的service的调用的负载均衡

有了Zuul,还需要Nginx吗?他俩可以一起使用吗?
我的理解:Zuul和Nginx是可以一起使用的(毕竟我们的Zuul也是可以搭成集群来实现高可用的),要不要一起使用得看架构的复杂度了(业务)~

九、Config

随着业务的扩展,我们的服务会越来越多,越来越多。每个服务都有自己的配置文件。
既然是配置文件,给我们配置的东西,那难免会有些改动的。
比如我们的Demo中,每个服务都写上相同的配置文件。万一我们有一天,配置文件中的密码需要更换了,那就得三个都要重新更改。
在这里插入图片描述
在分布式系统中,某一个基础服务信息变更,都很可能会引起一系列的更新和重启,
Spring Cloud Config项目是一个解决分布式系统的配置管理方案。它包含了Client和Server两个部分,server提供配置文件的存储、以接口的形式将配置文件的内容提供出去,client通过接口获取数据、并依据此数据初始化自己的应用。
简单来说,使用Spring Cloud Config就是将配置文件放到统一的位置管理(比如GitHub),客户端通过接口去获取这些配置文件。
在GitHub上修改了某个配置文件,应用加载的就是修改后的配置文件。
在这里插入图片描述
SpringCloud Config其他的知识:

  • 在SpringCloud Config的服务端, 对于配置仓库的默认实现采用了Git,我们也可以配置SVN。
  • 配置文件内的信息加密和解密
  • 修改了配置文件,希望不用重启来动态刷新配置,配合Spring Cloud Bus 使用~

使用SpringCloud Config可能的疑问:application.yml和 bootstrap.yml区别
https://www.cnblogs.com/BlogNetSpace/p/8469033.html

十、总结

本文主要写了SpringCloud的基础知识,希望大家看完能有所帮助~
SpringCloud的资料也很多,我整理一些我认为比较好,想要深入的同学不妨看看下边的资源~
SpringCloud系列文章参考资料:

参考书籍:
《SpringCloud 微服务实战》


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

LRU算法 Previous
Netty面试题汇总 Next