何 川
随着互联网越来越普及,我国的网民也不断增加。2022年6月,我国的互联网普及率甚至达到了74.4%的高峰,我国的网民数量在10.51亿左右。随着人们的生活质量越来越高,人们的要求也不断增加,互联网高并发场景也越来越多,常见的就有618、双11等各种网络购物节,当然还有各种节假日的火车票网上抢票等,这些都让互联网服务器产生了极大的压力,每逢这些节点,互联网峰值流量就会达到甚至超过TB级[1]。如果想要让用户拥有更好的使用体验,就需要让服务器基于更好的服务,可以从几个方面一一考量,比如负载均衡、业务拆分和让用户在这些节点分流等。不过以上这些解决方法并不能解决本质的问题,最重要的是构建一个可以支持大开发业务的Web服务器。
在提升系统相应速度的各项技术中,较为重要与关键的一项是缓存,该项技术能将未来可以用到的数据暂时保存下来,从技术架构设计方面来看,缓存这项技术属于非功能性约束。计算机缓存在运用的时候要求并不高,并非只有系统架构的某个固定位置才能够使用,而是在多个位置都能被运用。缓存一般分成三大类:一是浏览器缓存;二是服务端缓存;三是网络中的缓存。当缓存技术被运用到系统中的多个部分,系统的整体性能会有极大的提升。当缓存技术被顺利运用之后,系统开发的工作量将会得到降低,也会让系统的并发性与吞吐量得到提升。
早在0.7.48版本里面,Nginx就已经存在属于自己的缓存功能,当运用该功能时,其对应的缓存值是Value值。在Nginx里面,其反向代理常会用到proxy_cache相关指令集,此时进行内容缓存时,其设计不但具有可扩展性,更具有稳定性。它将过去运用的多线程服务器以及多进程服务器开发模式彻底抛弃,而是选择了新的开发模式,即全异步网络I/O处理机制和事件驱动架构,这让整个技术具有了更高的性能[2]。所以,很多知名网站在碰上大流量服务的时候,会更愿意运用Nginx缓存的方式来让用户的体验得以提高。如果出现了低并发压力时,运用Nginx缓存能让使用的所有用户拥有较快的服务;如果出现了高并发压力,要想让系统的吞吐率得到提升,则可以运用降低部分访问速度的方式来实现。
Redis缓存是一种分布式缓存系统,其往往运行的进程是单进程。当运行Redis缓存时,所需要的数据都将会加载于内存里面,所有缓存数据的操作都会在内存里面顺利完成。但把数据持久化内存数据导入磁盘这一行为,系统也是支持的。Redis缓存和MySQL等关系型数据库之间有着特别明显的区别,这是因为前者的IO速度会更快,而且其内存使用的效率也更高,除此之外,前者的吞吐量以及响应速度都有较大的提升。在Redis里面,其具有的value拥有结构化的特征,所以其value的数据类型是能够被自定义的,灵活性较大,所以说其value的特性是极其多样的。
随着人们的需求越来越高,电商网站秒杀活动也越来越多,仅针对这一场景,该课题选择从以下几个方面来分析高并发Web服务器系统的需求特点。首先分析连接数量方面,当网站服务器属于高并发的情况时,其面对的用户数量是极其庞大的,很多时候用户数量会达到万人级别。特别是电商“秒杀”的瞬间,通过数据统计能得出,活动期间的平均并发访问量甚至超过了百万QPS,所以在最初设计系统的时候,就要将高峰期用户分流、负载均衡以及业务拆分等措施考虑在内。其次是分析用户请求的数据类型,从用户的视角看,其请求的商品页面的数据其实大部分都是Json数据,这会让客户端解析更加便利。由于用户请求的内容很多都是重复的,且请求的数据类型也并不广泛,为了让响应速率得到提升,可以把用户请求的热点数据进行缓存。最后分析请求数据量的大小,很多情况下都是请求在客户端将动态Json数据进行显示,此时的数据大多数都是约束到KB级别以下,就算数据达到了MB级别,其通常也是运用数据拆分合并的方式。
1.一级缓存架构
要想在分布式系统里面同时将高并发性能进行满足,最常运用的方式是将分布式缓存Redis引入进去,在缓存数据库里存储热点数据[3]。具体的过程如下图1所示,主要分为三个部分:用户表示、数据源服务以及中间件缓存服务。
图1 请求查询缓存过程
图2 二级缓存请求过程
当用户发送了请求以后,服务器会率先和集中式缓存通信,此时会将用户请求的相关数据在缓存集群里面进行查找。如果在缓存集群里找到了所需要的数据信息,那么就直接返回,如果没有在缓存集群里找到所需要的数据信息,则请求源数据库,且最终获得的结果要存储到缓存数据库里。尽管运用一级缓存的方式能够让数据库的请求压力得以舒缓,可如果出现了宕机等问题让Redis集群崩溃,后端数据库就会因为受到大量请求冲击而出现崩溃的情况,这时候系统灾难的爆发就在所难免。
2.二级缓存架构
对于上面所提及的一级缓存架构可能会出现的问题,为更有效地解决这些问题,将Ehcache引入Tomcat服务器中,以构建第二级缓存。对于那些热点商品来说,其被用户访问的数量是很大的,假如所有的用户在获取相应信息时都要用到Tomcat服务器去连接,大量的访问量一级并发量会让Redis缓存服务器上的负载压力大幅增加,同时也会出现网络宽带受到限制等问题,不仅使得系统的吞吐量下降,也加长了服务端的响应耗时。对于以上所提及的问题,可以通过配置一主多从的服务器集群架构来解决[4],这样客户端的请求就能够均分到不同的服务器节点上,不会造成同一个服务器节点出现极大访问量的情况。除了这种方式外,还有一种更好的方式,即运用服务器本地平台,把热点数据存储成缓存的状态,这样就能将远程缓存的网络开销访问大大减少。
将以上所提及的Redis分布式缓存以及Ehcache二级缓存架构进行结合之后,缓存数据的传输开销以及网络连接都会得以降低。即使是前者在缓存的过程中有故障发生,后者也依旧能够通过本地缓存的方式继续提供缓存服务。这不但能将系统发生缓存穿透以及缓存雪崩情况下的负面影响有所降低,也让系统的高可用性以及健壮性得到提升。
3.多级缓存架构
当运用多级缓存架构时,也就是将所有的数据缓存在不一样的系统组件里面,利用组件里缓存的协同合作能让系统拥有更好的高并发性以及高可用性。当使用多级缓存时,在最初的缓存查找中,需要运用到Nginx和Lua脚本一起结合的方式在代理服务器里面查找。假如能够直接在缓存中找到数据,那就直接返回,假如没有办法找到数据,则只能先去Tomcat服务器寻找,如果还找不到数据,则要在Redis分布式集群里面寻找[5]。假如通过上面这些寻找途径还是没有办法找到数据,最终的办法就是请求关系型数据库,当找到所需要的请求数据后,将其缓存在所有的组件里面。具体的请求过程如下图3所示。
图3 多级缓存请求过程
4.数据一致性
所谓数据一致性,其实就是在系统运行期间,处于源数据库里面的数据和缓存里面的数据应当始终保持一致性。因为有缓存这一技术,很多的内容副本在网络的很多地方都有所分散。假如源服务器里面的内容出现了变化,那些存储在其他网络各处的缓存副本将会自动失去效果。确保缓存数据和源数据库的数据一致,进而确保用户最终缓存得到的数据是有效的数据。
详细的流程如下所示:
(1)先将缓存下来的请求数据进行删除。
(2)等缓存里面的请求数据被彻底删除后,再将处于源数据库里面的数据进行自动更新。假如在对源数据库里面的数据更新失败了,就需要立即写请求失败而返回,这个时候的数据并不会发生任何变化[6]。这个时候会发现缓存里面并不存在与之对应的请求数据,那么请求数据会直接从源数据库里面读取。同一时间该请求数据也会被放入缓存里面,这时候两边的数据是一致的,自然不会有数据一致性的问题发生。
(3)当源数据库里面的请求数据得到了更新,就需要对缓存里的数据进行更新。如果对缓存里面的数据进行更新出现了失败的情况,则立即返回。这时候数据库里面的数据已经更新过,当读请求传送过来之后,在缓存里面并未找到相应的数据时,就要从数据库里面将对应的数据读取下来,当然这个请求数据将会直接存入缓存里面。这时候两边的数据是一致的,也不会有数据一致性的问题发生。
(4)等以上的步骤全部成功之后,不管是源数据库里面的数据还是缓存里面的数据都是一致的,均不会产生数据一致性的问题。
当处于高并发Web场景时,服务器会有较长的读请求响应时间,从而导致用户没有很好的使用体验。将分布式缓存引入其中,尽管存在不少优势,可当运用到分布式应用里面时,用户的所有请求都会让缓存服务器和应用服务器之间产生网络连接,因为多种因素的影响,其性能优化将会被网络时间开销所抵消。不仅如此,还会出现源数据库和缓存一致性的问题。所以该课题主要是针对单一缓存的局限性来构建多级缓存策略(基于Nginx本地缓存与应用缓存之上的),其目的是让缓存的性能得到提升,同时减少读请求响应时间,最终让用户得到更好的使用体验,