美文网首页
阿里P7之无中生有二

阿里P7之无中生有二

作者: Java及SpringBoot | 来源:发表于2019-11-28 10:31 被阅读0次

    个人专题目录


    中间件系统架构

    (1)Master-Slave架构

    这个中间件系统的本质是希望能够用分布式的方式来处理一些数据,但是具体的作用涉及到核心技术,所以这里不能直接说明。

    但是他的核心思想,就是把数据分发到很多台机器上来处理,然后需要有一台机器来控制N多台机器的分布式处理

    分布式的处理,就肯定涉及到在Master中要维护这个集群的一些核心元数据。

    比如说数据的分发处理是如何调度的,处理的具体过程现在什么进度了,还有就是对集群里存放数据进行描述的一些核心元数据。

    这些核心元数据肯定会不断的频繁的修改,大家此时可以想,无论你是基于外部的文件还是数据库,或者是zookeeper来存放这些元数据的话,其实都会导致他的元数据更新性能降低,因为要访问外部依赖。

    何况这种复杂的元数据其实还不一定能通过zk或者数据库来存放,因为他可能是非格式化的。

    所以这里一个核心的设计,就是将核心元数据直接存放在Master的内存里,这样可以保证高并发更新元数据的时候,他的性能是极高的,而且直接基于内存来提供对外的更新服务。

    如果Master部署在高配置物理机上,比如32核128GB的那种,每秒支持10万+的请求都没问题。

    (2)异步日志持久化机制

    但是这里有一个问题,假如说Master进程重启,或者是突然宕机了,那么内存里的数据不就丢失了么?

    对,所以针对这个问题,既然已经否决掉了基于外部存储来写入元数据,那么这里就可以采取异步持久化日志的机制,来通过异步化的方式把元数据的更新日志写入磁盘文件。

    每次Master收到一个请求,在内存里更新元数据之后,就需要生成一条元数据的更新日志,把这个更新日志需要写入到一个内存缓冲里去。

    然后等内存缓冲满了之后,由一个后台线程把这里的数据刷新到磁盘上去。

    肯定会有人说,那如果一条更新日志刚写入缓冲区,结果Master宕机了,此时不是还是会丢失少量数据吗?因为还没来得及刷入磁盘。

    没错啊,这个为了保证高并发请求都是由内存来处理的,你必须得用异步持久化磁盘的模式,所以必然要容忍极端宕机情况下,可能丢失比如几秒钟的数据。

    那么如果是正常的Master重启呢?

    那简单,必须先把日志缓冲区清空刷入磁盘,然后才能正常重启Master,保证数据都在磁盘上不会丢失。

    接着重启的时候,从磁盘上读取更新日志,每一条都依次回访到内存里,恢复出来核心元数据即可。

    (3)检查点机制:定时持久化全量数据

    但是这里又有一个问题了,那个磁盘上的日志文件越来越大,因为元数据不断的在更新,不断在产生最新的变更日志写入磁盘文件。

    那么系统运行一段时间以后,每次重启都需要从磁盘读取历史全部日志,一条一条回放到内存来恢复核心元数据吗?

    不可能,所以这里一定要配合引入检查点机制。

    也就是说,每隔一段时间,就需要开启一个后台线程,把内存里的全部核心元数据序列化后写入磁盘上的元数据文件,作为这个时间的一个快照文件,同时清空掉日志文件,这个叫做检查点操作。

    下次重启,只要把元数据文件读取出来直接反序列化后方入内存,然后把上次检查点之后的变更日志从日志文件里读出来回放到内存里,就可以恢复出来完整的元数据了。

    这种方式,可以让Master重启很快,因为大部分数据都是在检查点写入的那个元数据文件里。

    (4)引入检查点节点

    大家可以想一下,Master内存里的元数据需要高并发的被人访问和修改,同时每隔一段时间还要检查点写入磁盘。

    那么在检查点过程中,是不是需要把内存数据全部加锁,不允许别人修改?

    在加锁的时候,把不会变动的数据写入磁盘文件中,但是这个过程是很慢的,意味着此时别人高并发的写入操作都需要等待核心元数据的锁。

    因为此时别人锁住了,你无法加锁去写数据进去,这会导致系统在几秒内出现卡顿无法响应请求的问题。

    所以此时需要在架构设计里引入一个检查点节点,专门负责同步Master的变更日志。

    然后在自己内存里维护一份一模一样的核心元数据,每隔一段时间由检查点节点来负责将内存数据写入磁盘,接着上传发送给Master。

    这样做,就不需要Master自己执行检查点的时候对自己内存数据进行加锁了

    img

    在这样的一个架构下,对Master来说,他只需要一个后台线程负责接收Checkpoint进程定时传送过来的元数据文件快照然后写入本地磁盘就可以了,完全规避掉了对自己内存元数据的锁冲突的问题。

    (5)总结 & 思考

    总结一下这个架构设计,其实就是Master基于内存维护元数据,这样一台物理机可以支撑每秒10万+的高并发请求。

    每次元数据出现更新,写一条日志到内存缓冲区,然后后台线程去刷新日志到日志文件里去,同时需要发送一条日志到Checkpoint节点去。

    Checkpoint节点会在自己内存里维护一份一模一样的元数据,然后每隔一段时间执行checkpoint检查点写一份元数据文件快照。

    接着上传给Master节点后清空掉他的日志文件。然后Master节点每次重启的时候直接读取本地元数据文件快照,加上回放上次checkpoint之后的日志即可。

    这里可能大家会提几个问题,比如说Master节点突然宕机会如何?

    那很简单,直接影响就是他内存缓冲里的那些日志丢了,导致少量数据丢失,这个在我们的场景下可以容忍。

    如果Checkpoint节点宕机怎么办?

    那不要紧,因为他之前上传过元数据文件的快照,所以对Master而言最多就是无法同步数据过去。

    但是Master重启,还是可以读取最近一次的元数据快照,然后回放日志即可。

    等Checkpoint节点恢复了,可以继续接着上一次同步日志,然后继续执行checkpoint操作。

    线上系统的JVM内存是越大越好吗

    Kafka和Elasticsearch两种分布式系统的线上部署情况

    1、是否依赖Java系统自身内存处理数据?

    Java、Scala等编程语言底层依赖的都是JVM,那么只要是使用JVM,就可以考虑在JVM进程的内存中来放置大量的数据。

    还是给大家举个例子,大家应该还记得之前聊过消息中间件系统。

    比如说系统A可以给系统B发送一条消息,那么中间需要依赖一个消息中间件,系统A要先把消息发送到消息中间件,然后系统B从这个消息中间件消费到这条消息。

    大家应该都知道,一条消息发送到消息中间件之后,有一种处理方式,就是把这条数据先缓冲在自己的JVM内存里。

    然后过一段时间之后,再从自己的内存刷新到磁盘上去,这样可以持久化保存这条消息。

    2、依赖Java系统自身内存有什么缺陷

    如果用类似上述的方式,依赖Java系统自身内存处理数据,比如说设计一个内存缓冲区,来缓冲住高并发写入的大量消息,那么是有其缺陷的。

    最大的缺陷,其实就是JVM的GC问题,这个GC就是垃圾回收,这里简单说一下他是怎么回事。

    大家可以想一下,如果一个Java进程里老是塞入很多的数据,这些数据都是用来缓冲在内存里的,但是过一会儿这些数据都会写入磁盘。

    那么写入磁盘之后,这些数据还需要继续放在内存里吗?

    明显是不需要的了,此时就会依托JVM垃圾回收机制,把内存里那些不需要的数据给回收掉,释放掉那些内存空间腾出来。

    但是JVM垃圾回收的时候,有一种情况叫做stop the world,就是他会停止你的工作线程,就专门让他进行垃圾回收。

    这个时候,他在垃圾回收的时候,有可能你的这个中间件系统就运行不了了。

    比如你发送请求给他,他可能都没法响应给你,因为他的接收请求的工作线程都停了,现在人家后台的垃圾回收线程正在回收垃圾对象。

    [图片上传失败...(image-ff06ae-1574907424634)]

    虽然说现在JVM的垃圾回收器一直在不断的演进和发展,从CMS到G1,尽可能的在降低垃圾回收的时候的影响,减少工作线程的停顿。

    但是你要是完全依赖JVM内存来管理大量的数据,那在垃圾回收的时候,或多或少总是有影响的。

    所以特别是对于一些大数据系统,中间件系统,这个JVM的GC(Garbage Collector,垃圾回收)问题,真是最头疼的一个问题。

    3、优化为依赖OS Cache而不是JVM

    所以类似Kafka、Elasticsearch等分布式中间件系统,虽然也是基于JVM运行的,但是他们都选择了依赖OS Cache来管理大量的数据。

    也就是说,是操作系统管理的内存缓冲,而不是依赖JVM自身内存来管理大量的数据。

    具体来说,比如说Kafka吧,如果你写一条数据到Kafka,他实际上会直接写入磁盘文件。

    但是磁盘文件在写入之前其实会进入os cache,也就是操作系统管理的内存空间,然后过一段时间,操作系统自己会选择把他的os cache的数据刷入磁盘。

    然后后续在消费数据的时候,其实也会优先从os cache(内存缓冲)里来读取数据。

    相当于写数据和读数据都是依托于os cache来进行的,完全依托操作系统级别的内存区域来进行,读写性能都很高。

    此外,还有另外一个好处,就是不要依托自身JVM来缓冲大量的数据,这样可以避免复杂而且耗时的JVM垃圾回收操作。

    大家看下面的图,其实就是一个典型的Kafka的运行流程。

    [图片上传失败...(image-7b1eb4-1574907424634)]

    然后比如Elasticsearch,他作为一个现在最流行的分布式搜索系统,也是采用类类似的机制。

    大量的依赖os cache来缓冲大量的数据,然后在进行搜索和查询的时候,也可以优先从os cache(内存区域)中读取数据,这样就可以保证非常高的读写性能。

    4、老司机经验之谈:依赖os cache的系统JVM内存越大越好?

    明显不是的,假如说你有一台机器,有32GB的内存,现在你如果在搞不清楚状况的情况下,要是傻傻的认为还是给JVM分配越大内存越好,此时比如给了16G的堆内存空间给JVM,那么os cache剩下的内存,可能就不到10GB了,因为本身其他的程序还要占用几个GB的内存。

    那如果是这样的话,就会导致你在写入磁盘的时候,os cache能容纳的数据量很有限。

    比如说一共有20G的数据要写入磁盘,现在就只有10GB的数据可以放在os cache里,然后另外10GB的数据就只能放在磁盘上。

    此时在读取数据的时候,那么起码有一半的读取请求,必须从磁盘上去读了,没法从os cache里读,谁让你os cache里就只能放的下10G的一半大小的数据啊,另外一半都在磁盘里,这也是没办法的,如下图。

    那此时你有一半的请求都是从磁盘上在读取数据,必然会导致性能很差。

    所以很多人在用Elasticsearch的时候就是这样的一个问题,老是觉得ES读取速度慢,几个亿的数据写入ES,读取的时候要好几秒。

    那能不花费好几秒吗?你要是ES集群部署的时候,给JVM内存过大,给os cache留了几个GB的内存,导致几亿条数据大部分都在磁盘上,不在os cache里,最后读取的时候大量读磁盘,耗费个几秒钟是很正常的。

    5、正确的做法:针对场景合理给os cache更大内存

    所以说,针对类似Kafka、Elasticsearch这种生产系统部署的时候,应该要给JVM比如6GB或者几个GB的内存就可以了。

    因为他们可能不需要耗费过大的内存空间,不依赖JVM内存管理数据,当然具体是设置多少,需要你精准的压测和优化。

    但是对于这类系统,应该给os cache留出来足够的内存空间,比如32GB内存的机器,完全可以给os cache留出来20多G的内存空间,那么此时假设你这台机器总共就写入了20GB的数据,就可以全部驻留在os cache里了。

    然后后续在查询数据的时候,不就可以全部从os cache里读取数据了,完全依托内存来走,那你的性能必然是毫秒级的,不可能出现几秒钟才完成一个查询的情况。

    [图片上传失败...(image-b57b0a-1574907424634)]

    支撑百万连接的系统应该如何设计其高并发架构?

    (1)到底什么是连接?

    假如说现在你有一个系统,他需要连接很多很多的硬件设备,这些硬件设备都要跟你的系统来通信。

    那么,怎么跟你的系统通信呢?

    首先,他一定会跟你的系统建立连接,然后会基于那个连接发送请求给你的系统。

    接着你的系统会返回响应给那个系统,最后是大家一起把连接给断开,释放掉网络资源。

    (2)为什么每次发送请求都要建立连接?

    要知道,网络连接的建立和连接涉及到多次网络通信,本质是一个比较耗费资源的过程。

    所以说咱们完全没必要每次发送请求都要建立一次连接,断开一次连接。

    我们完全可以建立好一个连接,然后设备就不停的发送请求过来,系统就通过那个连接返回响应。

    大家完全可以多次通过一个连接发送请求和返回响应,这就是所谓的长连接。

    也就是说,如果你一个连接建立之后,然后发送请求,接着就断开,那这个连接维持的时间是很短的,这个就是所谓的短连接。

    那如果一个设备跟你的系统建立好一个连接,然后接着就不停的通过这个连接发送请求接收响应,就可以避免不停的创建连接和断开连接的开销了。

    (3)长连接模式下需要耗费大量线程资源

    但是现在问题又来了,长连接的模式确实是不错的,但是如果说每个设备都要跟系统长期维持一个连接,那么对于系统来说就需要搞一个线程,这个线程需要去维护一个设备的长连接,然后通过这个连接跟一个设备不停的通信,接收人家发送过来的请求,返回响应给人家。

    大家看下面的图,每个设备都要跟系统维持一个连接,那么对于每个设备的连接,系统都会有一个独立的线程来维护这个连接。

    因为你必须要有一个线程不停的尝试从网络连接中读取请求,接着要处理请求,最后还要返回响应给设备。

    那么这种模式有什么缺点呢?

    缺点是很显而易见的,假如说此时你有上百万个设备要跟你的系统进行连接,假设你的系统做了集群部署一共有100个服务实例,难道每个服务实例要维持1万个连接支撑跟1万个设备的通信?

    如果这样的话,每个服务实例不就是要维持1万个线程来维持1万个连接了吗?大家觉得这个事儿靠谱吗?

    根据线上的生产经验,一般4核8G的标准服务用的虚拟机,自己开辟的工作线程在一两百个就会让CPU负载很高了,最佳的建议就是在几十个工作线程就差不多。

    所以要是期望每个服务实例来维持上万个线程,那几乎是不可能的,所以这种模式最大的问题就在于这里,没法支撑大量连接。

    (4)Kafka遇到的问题(Reactor多路复用):应对大量客户端连接

    实际上,对于大名鼎鼎的消息系统Kafka来说,他也是会面对同样的问题,因为他需要应对大量的客户端连接。

    有很多生产者和消费者都要跟Kafka建立类似上面的长连接,然后基于一个连接,一直不停的通信。

    举个例子,比如生产者需要通过一个连接,不停的发送数据给Kafka。然后Kafka也要通过这个连接不停的返回响应给生产者。

    消费者也需要通过一个连接不停的从Kafka获取数据,Kafka需要通过这个连接不停的返回数据给消费者。

    Kafka采用的架构策略是Reactor多路复用模型。

    简单来说,就是搞一个acceptor线程,基于底层操作系统的支持,实现连接请求监听。

    如果有某个设备发送了建立连接的请求过来,那么那个线程就把这个建立好的连接交给processor线程。

    每个processor线程会被分配N多个连接,一个线程就可以负责维持N多个连接,他同样会基于底层操作系统的支持监听N多连接的请求。

    如果某个连接发送了请求过来,那么这个processor线程就会把请求放到一个请求队列里去。

    接着后台有一个线程池,这个线程池里有工作线程,会从请求队列里获取请求,处理请求,接着将请求对应的响应放到每个processor线程对应的一个响应队列里去。

    最后,processor线程会把自己的响应队列里的响应发送回给客户端。

    [图片上传失败...(image-fd2ce4-1574907424634)]

    (5)优化后的架构是如何支撑大量连接的?

    processor线程是一个人维持N个线程,基于底层操作系统的特殊机制的支持,一个人可以监听N个连接的请求。

    这是极为关键的一个步骤,就仅此一个步骤就可以让一个线程支持多个连接了,不需要一个连接一个线程来支持。

    而且那个processor线程仅仅是接收请求和发送响应,所有的请求都会入队列排队,交给后台线程池来处理。

    比如说按照100万连接来计算,如果有100台机器来处理,按照老的模式,每台机器需要维持1万个线程来处理1万个连接。

    但是如果按照这种多路复用的模式,可能就比如10个processor + 40个线程的线程池,一共50个线程就可以上万连接。

    在这种模式下,每台机器有限的线程数量可以抗住大量的连接。

    因此实际上我们在设计这种支撑大量连接的系统的时候,完全可以参考这种架构,设计成多路复用的模式,用几十个线程处理成千上万个连接,最终实现百万连接的处理架构。

    消息中间件如何实现每秒几十万的高并发写入?

    Kafka是高吞吐低延迟的高并发、高性能的消息中间件,在大数据领域有极为广泛的运用。配置良好的Kafka集群甚至可以做到每秒几十万、上百万的超高并发写入。

    那么Kafka到底是如何做到这么高的吞吐量和性能的呢?这篇文章我们来一点一点说一下。

    1、页缓存技术 + 磁盘顺序写

    首先Kafka每次接收到数据都会往磁盘上去写。

    那么在这里我们不禁有一个疑问了,如果把数据基于磁盘来存储,频繁的往磁盘文件里写数据,这个性能会不会很差?大家肯定都觉得磁盘写性能是极差的。

    但是实际上Kafka在这里有极为优秀和出色的设计,就是为了保证数据写入性能,首先Kafka是基于操作系统的页缓存来实现文件写入的。

    操作系统本身有一层缓存,叫做page cache,是在内存里的缓存,我们也可以称之为os cache,意思就是操作系统自己管理的缓存。

    你在写入磁盘文件的时候,可以直接写入这个os cache里,也就是仅仅写入内存中,接下来由操作系统自己决定什么时候把os cache里的数据真的刷入磁盘文件中。

    仅仅这一个步骤,就可以将磁盘文件写性能提升很多了,因为其实这里相当于是在写内存,不是在写磁盘。

    接着另外一个就是kafka写数据的时候,非常关键的一点,他是以磁盘顺序写的方式来写的。也就是说,仅仅将数据追加到文件的末尾,不是在文件的随机位置来修改数据。

    普通的机械磁盘如果你要是随机写的话,确实性能极差,也就是随便找到文件的某个位置来写数据。

    但是如果你是追加文件末尾按照顺序的方式来写数据的话,那么这种磁盘顺序写的性能基本上可以跟写内存的性能本身也是差不多的。

    Kafka在写数据的时候,一方面基于了os层面的page cache来写数据,所以性能很高,本质就是在写内存罢了。

    另外一个,他是采用磁盘顺序写的方式,所以即使数据刷入磁盘的时候,性能也是极高的,也跟写内存是差不多的。

    基于上面两点,kafka就实现了写入数据的超高性能。

    那么大家想想,假如说kafka写入一条数据要耗费1毫秒的时间,那么是不是每秒就是可以写入1000条数据?

    但是假如kafka的性能极高,写入一条数据仅仅耗费0.01毫秒呢?那么每秒是不是就可以写入10万条数?

    所以要保证每秒写入几万甚至几十万条数据的核心点,就是尽最大可能提升每条数据写入的性能,这样就可以在单位时间内写入更多的数据量,提升吞吐量。

    2、零拷贝技术

    说完了写入这块,再来谈谈消费这块。

    大家应该都知道,从Kafka里我们经常要消费数据,那么消费的时候实际上就是要从kafka的磁盘文件里读取某条数据然后发送给下游的消费者。

    那么这里如果频繁的从磁盘读数据然后发给消费者,性能瓶颈在哪里呢?

    假设要是kafka什么优化都不做,就是很简单的从磁盘读数据发送给下游的消费者,那么大概过程如下所示:

    先看看要读的数据在不在os cache里,如果不在的话就从磁盘文件里读取数据后放入os cache。

    接着从操作系统的os cache里拷贝数据到应用程序进程的缓存里,再从应用程序进程的缓存里拷贝数据到操作系统层面的Socket缓存里,最后从Socket缓存里提取数据后发送到网卡,最后发送出去给下游消费。

    一次是从操作系统的cache里拷贝到应用进程的缓存里,接着又从应用程序缓存里拷贝回操作系统的Socket缓存里。

    而且为了进行这两次拷贝,中间还发生了好几次上下文切换,一会儿是应用程序在执行,一会儿上下文切换到操作系统来执行。

    所以这种方式来读取数据是比较消耗性能的。

    Kafka为了解决这个问题,在读数据的时候是引入零拷贝技术

    也就是说,直接让操作系统的cache中的数据发送到网卡后传输给下游的消费者,中间跳过了两次拷贝数据的步骤,Socket缓存中仅仅会拷贝一个描述符过去,不会拷贝数据到Socket缓存。

    [图片上传失败...(image-e3d07d-1574907424635)]

    通过零拷贝技术,就不需要把os cache里的数据拷贝到应用缓存,再从应用缓存拷贝到Socket缓存了,两次拷贝都省略了,所以叫做零拷贝。

    对Socket缓存仅仅就是拷贝数据的描述符过去,然后数据就直接从os cache中发送到网卡上去了,这个过程大大的提升了数据消费时读取文件数据的性能。

    而且大家会注意到,在从磁盘读数据的时候,会先看看os cache内存中是否有,如果有的话,其实读数据都是直接读内存的。

    如果kafka集群经过良好的调优,大家会发现大量的数据都是直接写入os cache中,然后读数据的时候也是从os cache中读。

    相当于是Kafka完全基于内存提供数据的写和读了,所以这个整体性能会极其的高。

    如何优化Spring Cloud微服务注册中心架构

    1、Consul服务注册中心的整体架构

    如果要基于Consul作为服务注册中心,那么首先必须在每个服务所在的机器上部署一个Consul Agent,作为一个服务所在机器的代理。

    然后还得在多台机器上部署Consul Server,这就是核心的服务注册中心。

    这个Consul Agent可以用来收集你的服务信息然后发送给Consul Server,还会对你的服务不停的发送请求检查他是否健康。

    然后你要发现别的服务的时候,Consul Agent也会帮你转发请求给Consul Server,查询其他服务所在机器。

    Consul Server一般要求部署3~5台机器,以保证高可用以及数据一致性。

    他们之间会自动实现数据同步,而且Consul Server集群会自动选举出一台机器作为leader,其他的Consul Server就是follower。

    [图片上传失败...(image-7c7c0e-1574907424635)]

    2、Consul如何通过Raft协议实现强一致性?

    Consul Server是部署集群的,而且他会选举出来一台Server作为Leader。

    接下来各个服务发送的注册请求都会落地给Leader,由Leader同步给其他Follower。

    所以首先第一点,Leader Server是绝对有最新的服务注册信息的,是不是?

    比如库存服务发起注册了,那么Leader Server上一定有库存服务的注册信息。

    接着如果比如订单服务要发现库存服务的话,这个查询请求会发送给Leader Server。

    这样服务注册和发现,都是通过一台Leader Server来进行的,就可以保证服务注册数据的强一致性了。

    接着大家想,假如说库存服务在注册的时候数据刚写到Leader Server,结果Leader Server就宕机了,这时候怎么办?

    那么此时这条注册数据就丢失了,订单服务就没法发现那个库存服务了。没关系,这里Consul会基于Raft协议来解决这个问题

    首先,库存服务注册到Leader Server的时候,会采取Raft协议,要求必须让Leader Server把这条注册数据复制给大部分的Follower Server才算成功。

    这就保证了,如果你认为自己注册成功了,那么必然是多台Consul Server都有这条注册数据了。

    如果你刚发送给Leader Server他自己就宕机了,那么这次注册会认为失败。

    此时,Consul Server集群会重新选举一个Leader Server出来,你需要再次重新注册。

    这样就可以保证你注册成功的数据绝对不会丢,然后别人发现服务的时候一定可以从Leader Server上获取到最新的强一致的注册数据。

    3、Consul如何通过Agent实现分布式健康检查?

    集中式的心跳机制,比如传统的Eureka,是让各个服务都必须每隔一定时间发送心跳到Eureka Server。

    如果一段时间没收到心跳,那么就认为这个服务宕机了。

    但是这种集中式的心跳机制会对Eureka Server造成较大的心跳请求压力,实际上平时Eureka Server接收最多的请求之一就是成千上万服务发送过来的心跳请求。

    所以Consul在这块进行了架构优化,引入了Agent概念。

    每个机器上的Consul Agent会不断的发送请求检查服务是否健康,是否宕机。如果服务宕机了,那么就会通知Consul Server。

    怎么样?是不是发现各个服务自己不用再发送心跳请求去Server了?减小了Server这部分的压力吧?

    没错,这就是Consul基于Agent实现的分布式健康检查机制,可以大幅度的减小Server端的压力。

    这样一来,哪怕你就部署个三五台机器,可以轻松支持成千上万个服务。

    [图片上传失败...(image-c57ec4-1574907424635)]

    • 服务注册与发现

    • Consul当然是可以作为服务注册中心的了,可以用做微服务架构的服务注册和发现。

    • 同时这里可以先给大家说一下,Consul的服务注册机制选择的是基于Daft协议的强一致,没有像Eureka那样使用最终一致的效果。

    • 健康检查

    • Consul可以支持非常强大的健康检查的功能,啥叫健康检查?

    • 简单来说就是不停的发请求给你的服务检查他到底死了没有,目前是否还健康,这个就是叫做健康检查。

    • kv存储

    • Consul不光支持服务注册和发现,居然还可以支持简单的kv存储。

    • 他可以让你用key-value对的形式存放一些信息以及提取查询,是不是很神奇?

    • 安全的服务通信

    • Consul支持让你的服务之间进行授权来限制哪些服务可以通信和连接

    • 多数据中心支持

    如果20万用户同时访问一个热点缓存,如何优化你的缓存架构?

    这篇文章,咱们来聊聊热点缓存的架构优化问题。

    其实使用缓存集群的时候,最怕的就是热key、大value这两种情况,那啥叫热key大value呢?

    简单来说,热key,就是你的缓存集群中的某个key瞬间被数万甚至十万的并发请求打爆。

    大value,就是你的某个key对应的value可能有GB级的大小,导致查询value的时候导致网络相关的故障问题。

    这篇文章,我们就来聊聊热key问题。先来看看下面的一幅图。

    简单来说,假设你手头有个系统,他本身是集群部署的,然后后面有一套缓存集群,这个集群不管你用redis cluster,还是memcached,或者是公司自研缓存集群,都可以。

    那么,这套系统用缓存集群干什么呢?

    很简单了,在缓存里放一些平时不怎么变动的数据,然后用户在查询大量的平时不怎么变动的数据的时候,不就可以直接从缓存里走了吗?

    缓存集群的并发能力是很强的,而且读缓存的性能是很高的。

    举个例子,假设你每秒有2万请求,但是其中90%都是读请求,那么每秒1.8万请求都是在读一些不太变化的数据,而不是写数据。

    那此时你把数据都放在数据库里,然后每秒发送2万请求到数据库上读写数据,你觉得合适吗?

    当然不太合适了,如果你要用数据库承载每秒2万请求的话,那么不好意思,你很可能就得搞分库分表 + 读写分离。

    比如你得分3个主库,承载每秒2000的写入请求,然后每个主库挂3个从库,一共9个从库承载每秒1.8万的读请求。

    这样的话,你可能就需要一共是12台高配置的数据库服务器,这是很耗费钱的,成本非常高,而且很不合适。

    所以,此时你完全就可以把平时不太变化的数据放在缓存集群里,缓存集群可以采用2主2从,主节点用来写入缓存,从节点用来读缓存。

    以缓存集群的性能,2个从节点完全可以用来承载每秒1.8万的大量读了,然后3个数据库主库就是承载每秒2000的写请求和少量其他读请求就可以了。

    大家看看下面的图,你耗费的机器瞬间变成了4台缓存机器 + 3台数据库机器 = 7台机器,是不是比之前的12台机器减少了很大的资源开销?

    没错,缓存其实在系统架构里是非常重要的组成部分。很多时候,对于那些很少变化但是大量高并发读的数据,通过缓存集群来抗高并发读,是非常合适的。

    (1)20万用户同时访问一个热点缓存的问题

    我们来做一个假设,你现在有10个缓存节点来抗大量的读请求。正常情况下,读请求应该是均匀的落在10个缓存节点上的,对吧!

    这10个缓存节点,每秒承载1万请求是差不多的。

    然后我们再做一个假设,你一个节点承载2万请求是极限,所以一般你就限制一个节点正常承载1万请求就ok了,稍微留一点buffer出来。

    好,所谓的热点缓存问题是什么意思呢?

    很简单,就是突然因为莫名的原因,出现大量的用户访问同一条缓存数据。

    举个例子,某个明星突然宣布跟某某结婚,这个时候是不是会引发可能短时间内每秒都是数十万的用户去查看这个明星跟某某结婚的那条新闻?

    那么假设那条新闻就是一个缓存,然后对应就是一个缓存key,就存在一台缓存机器上,此时瞬时假设有20万请求奔向那一台机器上的一个key。

    这个时候很明显了,我们刚才假设的是一个缓存Slave节点最多每秒就是2万的请求,当然实际缓存单机承载5万~10万读请求也是可能的,我们这里就是一个假设。

    结果此时,每秒突然奔过来20万请求到这台机器上,会怎么样?

    很简单,上面图里那台被20万请求指向的缓存机器会过度操劳而宕机的。

    那么如果缓存集群开始出现机器的宕机,此时会如何?

    接着,读请求发现读不到数据,会从数据库里提取原始数据,然后放入剩余的其他缓存机器里去。但是接踵而来的每秒20万请求,会再次压垮其他的缓存机器。

    以此类推,最终导致缓存集群全盘崩溃,引发系统整体宕机。

    (2)基于流式计算技术的缓存热点自动发现

    其实这里关键的一点,就是对于这种热点缓存,你的系统需要能够在热点缓存突然发生的时候,直接发现他,然后瞬间立马实现毫秒级的自动负载均衡。

    那么我们就先来说说,你如何自动发现热点缓存问题?

    首先你要知道,一般出现缓存热点的时候,你的每秒并发肯定是很高的,可能每秒都几十万甚至上百万的请求量过来,这都是有可能的。

    所以,此时完全可以基于大数据领域的流式计算技术来进行实时数据访问次数的统计,比如storm、spark streaming、flink,这些技术都是可以的。

    然后一旦在实时数据访问次数统计的过程中,比如发现一秒之内,某条数据突然访问次数超过了1000,就直接立马把这条数据判定为是热点数据,可以将这个发现出来的热点数据写入比如zookeeper中。

    当然,你的系统如何判定热点数据,可以根据自己的业务还有经验值来就可以了。

    当然肯定有人会问,那你的流式计算系统在进行数据访问次数统计的时候,会不会也存在说单台机器被请求每秒几十万次的问题呢?

    答案是否,因为流式计算技术,尤其是storm这种系统,他可以做到同一条数据的请求过来,先分散在很多机器里进行本地计算,最后再汇总局部计算结果到一台机器进行全局汇总。

    所以几十万请求可以先分散在比如100台机器上,每台机器统计了这条数据的几千次请求。

    然后100条局部计算好的结果汇总到一台机器做全局计算即可,所以基于流式计算技术来进行统计是不会有热点问题的。

    (3)热点缓存自动加载为JVM本地缓存

    我们自己的系统可以对zookeeper指定的热点缓存对应的znode进行监听,如果有变化他立马就可以感知到了。

    此时系统层就可以立马把相关的缓存数据从数据库加载出来,然后直接放在自己系统内部的本地缓存里即可。

    这个本地缓存,你用ehcache、hashmap,其实都可以,一切都看自己的业务需求,主要说的就是将缓存集群里的集中式缓存,直接变成每个系统自己本地实现缓存即可,每个系统自己本地是无法缓存过多数据的。

    因为一般这种普通系统单实例部署机器可能就一个4核8G的机器,留给本地缓存的空间是很少的,所以用来放这种热点数据的本地缓存是最合适的,刚刚好。

    假设你的系统层集群部署了100台机器,那么好了,此时你100台机器瞬间在本地都会有一份热点缓存的副本。

    然后接下来对热点缓存的读操作,直接系统本地缓存读出来就给返回了,不用再走缓存集群了。

    这样的话,也不可能允许每秒20万的读请求到达缓存机器的一台机器上读一个热点缓存了,而是变成100台机器每台机器承载数千请求,那么那数千请求就直接从机器本地缓存返回数据了,这是没有问题的。

    (4)限流熔断保护

    除此之外,在每个系统内部,其实还应该专门加一个对热点数据访问的限流熔断保护措施。

    每个系统实例内部,都可以加一个熔断保护机制,假设缓存集群最多每秒承载4万读请求,那么你一共有100个系统实例。

    你自己就该限制好,每个系统实例每秒最多请求缓存集群读操作不超过400次,一超过就可以熔断掉,不让请求缓存集群,直接返回一个空白信息,然后用户稍后会自行再次重新刷新页面之类的。

    通过系统层自己直接加限流熔断保护措施,可以很好的保护后面的缓存集群、数据库集群之类的不要被打死,我们来看看下面的图。

    [图片上传失败...(image-aa461e-1574907424635)]

    相关文章

      网友评论

          本文标题:阿里P7之无中生有二

          本文链接:https://www.haomeiwen.com/subject/iubvwctx.html