技术开发 频道

Netflix的EVCache缓存分布式复制架构

  【IT168 技术】缓存在Netflix无处不在,Netflix大量采用的是微服务架构,可以实现粒度更细的分离关注,大概部署了数百个微服务,每个都是专注做好一件事,这使得整个系统的耦合非常松散,大多数服务是无态的,也就更加易于扩展,这些服务之所以可以无状态,是因为将状态放在了缓存或持久存储中。

  EVCache 是一个非常棒的数据缓存服务,能够专门为netflix的微服务架构提供低延迟 高可靠性的缓存解决方案。

  它是基于memcached的内存存储,专门为云计算优化,EVCache典型地适合对强一致性没有必须要求的场合,过去几年,EVCache已经扩展到可处理非常显著的大流量,同时它提供健壮的key-value编程接口,处理每秒三千万个请求,存储数十亿个对象,跨数千台memcached服务器。整个EVCache集群每天处理近2万亿个请求。

  EVCache是开源项目:Github,已经产品化超过5年时间。

  netflix全局基于云的服务是遍布Amazon各个Web服务地区,北弗吉尼亚、俄勒冈州和爱尔兰,服务于与这些地区最近的会员,但是网络流量会因为各种原因改变,比如关键基础设施出了问题故障,或者地区之间进行失败恢复的练习(Chaos Kong),因此,采取无态应用服务器能够服务于来自任何地区的会员。

  隐藏在需求后面的设计是数据或状态需要为每个请求服务,必须到处跨地区可用的,高可靠性数据库和高性能缓存是支持分布式架构的基础设施,一个使用缓存的案例是将缓存架构于数据库前面或其他持久存储前面,如果没有缓存的全局复制,一个地区的的会员切换到另外一个地区,会在新的地区缓存中没有老地区的数据,称为cold cache,处理这种缓存数据丢失的办法只有重新从数据库加载,但是这种方式会延长响应时间和对数据库形成巨大冲击。

  另外一个问题是,缓存会保存着重新计算需要的临时数据,这些数据如果从持久层存储获得将会非常昂贵(造成频繁的数据库访问),计算系统会将这种数据写入到本地缓存,这些数据必须复制到所有地区的缓存中,以便服务于各个地区的会员用户的请求,底线是:微服务是依赖于缓存的,必须快速可靠地访问多种类型的数据,比如会员观看历史,排行榜和个性化推荐等数据,这些数据的更新与改变都必须复制到全世界各个地区,以便这些地区能够快速可靠地访问。

  EVCache是专门为这些情况而设计的缓存产品,这是基于全局复制基础上的设计,同时也考虑到强一致性,举例,如果爱尔兰和弗吉尼亚的推荐内容会有轻微差别,这些差别不会伤害到用户浏览和观看体验,对于非重要的数据,会严重依赖最终一致性模型进行复制,本地和全局两个缓存的差别保持在一个可以容忍的很短时间内,这就大大简化了EVCache的复制设计。它并不需要处理全局锁,法定人数quorum的读和写(分布式复制的一种算法),事务更新,部分提交回滚或其他分布式一致性有关的复杂性。

  同时也要保证:即使在跨区域复制变慢的情况下,也不会影响性能和本地缓存的可靠性,所有复制都是异步的,复制系统能够在不影响本地缓存操作情况下悄悄地短时间中断。

  复制延迟是另外一个问题,快得足够吗?在两个地区之间切换的会员流量有多频繁?什么情况会冲击导致不一致性?宁愿不从完美主义去设计一个复制系统,EVcache只要能最低限度满足应用和会员用户的要求即可。

Netflix的EVCache缓存分布式复制架构

  这张图说明复制操作是在SET操作以后实现,应用程序调用EVCache客户端库包的set()方法,从那以后复制路径对于调用者是透明的:

  1.EVCache客户端库包发送SET到缓存系统的本地地区的一个实例服务器中。

  2.EVCache客户端库包同时也将写入元数据(包括key,但是不包括要缓存的数据本身)到复制消息队列(Kafka)

  3.本地区的“复制转播”的服务将会从这个消息队列中读取消息。

  4.转播服务会从本地缓存中抓取符合key的数据

  5.转播会发送一个SET请求到远地区的“复制代理”服务。

  6.在远地区,复制代理服务会接受到请求,然后执行一个SET到它的本地缓存,完成复制。

  7.在接受地区的本地应用当通过GET操作以后会在本地缓存看到这个已经更新的数据值。

  这是一个简单描述,一件事需要注意,它只会对SET操作有效,对于其他DELETE TOUCH或批mutation等操作不会复制,DELETE和TOUCH是非常类似的,只有一点修改:它们不必从本地缓存中读取已经存在的值。

  跨地区之间的复制主要是通过消息发送进行,一个地区的EVCache客户端不会注意到其他地区的复制情况,读写都是只使用本地缓存,不会和其他地区缓存耦合,通过消息系统松耦合解耦。

  (具体各个组件职责可见原文)

  这里一个设计不同点在于:队列中的复制消息只包含key和一些元数据,并不包括实际需要更新写入的数据本身,主要优点是能够获得小而快速的Kafka部署,Kafka不会保留所有缓存中已经存在的数据,这样不至于导致性能瓶颈,因为保存大量数据会加重Kafka的消耗,相反,复制转播服务会从本地缓存中抓取这些数据,因此不再需要在Kafka中再有一份拷贝了。

  另外一个好处是,只传递元数据,因为有时我们并不需要复制的数据,比如对于一些缓存,一个基于key的SET只需要将其他地区的同样的key无效化,这就不需要发送新的数据,只要发送这个key的DELETE,在这样情况下,其他地区的GET操作会发现缓存中这个key对应的数据没有了,应用就会自己处理Cache miss缓存丢失一样去处理它,这就减少了跨地区之间的流量。

  优化处理需要权衡延迟和吞吐量,99%的端到端复制延迟要求是在1秒以下,我们试图在各个地批处理消息以牺牲一点延迟而得以提高吞吐量,99%的延迟时间大概在400ms左右,因为缓冲buffer填满和flush会很快。

  另外一个重要优化是使用持久连接,我们发现在重播服务和代理服务之间开始使用持久连接以后,延迟和稳定性大大提高了,它消除了创建新的TCP需要3次握手的等待时间,节约了额外创建TLS/SSL会话的网络时间。

  将批处理多个消息到一个请求以填满一个TCP窗口,这种方式会在转播集群与代理集群之间提高吞吐量和降低通讯延迟,将批消息大小变化适合到一个TCP窗口大小,这回改变连接的整个状态,实践中可以微调批大小到一个好的吞吐量,当然这样批处理会增加延迟,它会允许最大化利用每个TCP包,降低连接数量,也就只需要很少的服务器用于复制。

  通过这些措施,跨地区的EVCache复制系统能够每天最高可处理一百万的RPS。

  基于Kafka的复制系统已经在Netflix产品化超过一年,每秒最高复制了一百五十万个消息,整个成长过程也是历经艰难...

  目前还存在以下工作:

  1. Kafka并不易于垂直上下扩展,当一个缓存需要更多复制队列容量时,必须手工加入分区和配置,根据线程数量配置消费者,这会导致重复消息或重新发送消息,这种低效率会导致更多的最终一致性方面的问题。

  2.如果在远地区一台EVCache实例服务器死机,会导致延迟增加,因为代理集群会试图写数据到当机的服务器上并会失败,延迟反馈回转播服务这边,将会等待每个复制请求的确认,这需要及早地发现当机服务器,然后引入协调机制减少这种影响,已经在EVCache客户端做了修改允许代理服务器更能应对缓存服务器可能死机的情况。

  3.Kafka监测,对于丢失的消息,软件bug会导致消息不会出现在Kafka分区中,或不被转播集群接受到,通过监测比较Kafka broker接受到的消息总数和转播集群的复制消息总数,如果超过可接受的阀值,将会人工介入研究。同时也会监测最大延迟时间,因为每个分区处理会因为某些原因显著变慢,这种情况也需要研究,即使平均延迟时间是在可接受范围内,还在持续提高有关更好的侦测报警。

原文地址:techblog.netflix.com/2016/03/caching-for-global-netflix.html

0
相关文章