当前位置: 首页 > 科技观察

程序员过关——是否有应对高并发系统的通用方案?

时间:2023-03-20 12:29:20 科技观察

灵魂拷问:高并发系统有通用的解决方案吗?这些解决方案解决了哪些问题?这些解决方案的优点和缺点是什么?对性能的不懈追求是互联网技术不断发展的根本动力,从最初的大型机到现在的微型计算机,本质上也是为性能而生。类似的现象存在于软件系统中。一个系统从最初的少量访问请求,到后来的大量并发请求,都需要提供一系列性能提升的解决方案。和当初的淘宝一样,只是一个外包产品。随着业务的不断发展,淘宝的并发量呈指数级增长,同时对系统提出了严峻的挑战。这就逐渐造就了淘宝支撑数据的能力。千万级同时在线的高并发系统。说到应对高并发,大家或多或少可以想出几种解决方案。高并发系统设计的魅力在于我们可以凭借程序员的聪明才智设计出巧妙的方案来应对巨大流量的冲击。从目前已知的解决方案来看,大致可以归纳为以下几种。尽可能提高单机的性能是一个永恒的话题。无论是分布式还是其他方案,都提升了单机的性能。对于一个系统只有好处。以编程语言为例,用c或c++语言编写的程序理论上比用java、net、python编写的程序效率更高。当然,这需要在程序正常运行的情况下建立。提高单机性能最简单粗暴的方法就是提高硬件性能。这里举一个简单的例子:如果数据库DB的服务器内存是8G,随着数据量的增加,你会发现有些sql的执行会逐渐变慢。原因是数据库的索引或者数据根本无法存入内存,需要写回磁盘。有些查询在内存中打不中,导致有些SQL查询到磁盘中的数据。如果此时将服务器的内存增加到16G,你会发现这些慢SQL凭空消失了。这是硬件提升性能的典型案例。运行程序也是如此。尽可能把程序优化到极致。也许单机就可以达到别人分布式部署的性能效果。当然,这需要我们在写代码的时候仔细思考。“任何时候,我都认为有必要扩展单机系统的性能。当单机系统无法承受巨大流量的冲击时,最简单有效的解决方案之一就是横向扩展。水平扩展指的是将巨大的流量分成一个相对较小的流量,从而解决高并发系统的性能问题,本质上水平扩展属于分而治之的理论,属于分布式概念的范畴。举一个很简单的例子,假设当前单机处理的请求数是200/s,当每秒请求数达到1000时,单机肯定会遇到瓶颈。如果此时处理请求的服务器增加到5台,甚至更多,性能问题就轻松解决了。当然,横向扩展的便利性取决于具体的系统设计。如果系统是无状态的,理论上水平扩展是没有问题的,但是有些有状态的服务可能会涉及到状态迁移等工作。这也是很多架构师提倡无状态服务的原因之一。应用的水平扩展可以通过负载均衡来实现,比如阿里云的SLB服务和nginx的反向代理功能,可以轻松实现应用的水平扩展。然而,对于像mysql这样的数据库系统,无限水平扩展可能只是一个目标。大多数DB都是采用master-slave或者多主多log来解决横向扩展的问题。主节点负责写操作,从节点负责读操作。当然,这涉及到主从同步的机制和主从同步的延迟。有兴趣的同学可以去深入研究。image那么什么时候应该选择横向扩展呢?一般来说,在系统设计之初就会考虑横向扩展,因为这种方案已经足够简单了,能够通过堆叠硬件来解决的问题都不是问题。现在我敢说,90%以上的系统在第一版上线的时候都做过类似的负载均衡部署方案,而且很多都使用了nginx的反向代理功能。image当然,横向扩展也不是没有负面影响。和单机系统一样,水平扩展也需要考虑一个节点宕机的问题。因此,监控和健康检查是现在一个系统的必要手段,在系统设计之初就会实现。在整体结构上。我在之前的文章中说过,由于水平扩展属于分布式的范畴,所以需要考虑分布式系统需要考虑的问题:除了上面提到的水平扩展方案外,还有一种方法可以解决分布式系统的问题缓存有效且足够简单的是缓存方案。毫无疑问,缓存存在于系统的每一个角落,从操作系统到浏览器,从cpu到磁盘,从数据库到消息队列,任何稍微复杂的服??务和组件都有缓存的影子。为什么缓存可以大大提高性能?这也需要从系统的瓶颈角度来考虑。在一个客户端请求的生命周期中,这个请求的响应时间被最慢的链接严重限制,这类似于木桶效应(一个木桶能装多少水取决于最短的木板)。举一个很简单的例子:客户端在商城中请求商品的信息,请求通过http协议到达服务器的某个端口,服务器程序将请求解包,然后请求数据库。数据库不仅在另一台服务器上,还需要从磁盘加载数据,即所谓的DBcachemisses。整个过程中,请求磁盘的过程是最慢的。普通磁盘由机械臂、磁头、轴和磁盘组成。磁盘在查询数据时,磁头需要很长时间来查找。当然SSD的速度比普通磁盘快很多,但是比内存还是慢了几个数量级。我们最想要的过程是这样的:当一个请求到达服务器时,能够第一时间从某个设备上获取信息,然后返回给客户端。该设备永远不能是磁盘。比较平衡的,应该是内存。“缓存在语义上要丰富得多。我们可以把任何可以减少响应时间的中间存储称为缓存。例如CPU的一级缓存、二级缓存、三级缓存、浏览器缓存等。”缓存主要解决图像编程界有一句老话:最快的就是把数据放在离用户最近的地方,CDN本质上就是干这个的,对于缓存,我们经常听到浏览器缓存的概念,in-process缓存,进程外缓存等。目前服务器一般的缓存策略是使用第三方的kv存储设备,比如redis,memcache等,当然在对性能要求极高的系统中,我还是建议使用进程内缓存,说异步的时候一定要说异步,同步调用就是调用者必须阻塞,等待被调用方执行完再返回,现在的系统一般使用多线程提供de系统吞吐量。(现在多进程的方法很少,但不代表不存在,比如:nodejs,nginx)。在同步方法中,如果被调用者的响应时间过长,调用者的线程会长时间处于等待状态。线程的使用率大大降低。线程是系统非常昂贵的资源。创建大量线程来应对高并发是不明智的。不仅浪费内存,还会增加线程上下文cpu切换的开销。在高吞吐量的系统中,理论上所有的线程都必须一直工作,最大限度地压榨cpu资源。对于一个IO密集型的操作,采用异步的方式可以极大的提高系统的吞吐量。异步不需要等待被调用者执行完再执行其他逻辑,在被调用者执行完成后,通过通知回调反馈给调用者。“异步本质上是一种编程思想,一种编程模型。他提高的是系统整体的吞吐量,但是请求的响应时间相对于同步方式会略有增加。和最常用的消息队列一样,在模型上也属于异步编程模型。调用者会将消息丢入队列,然后直接返回执行其他业务。被叫方收到消息并进行处理,然后根据具体业务判断是否需要给出结果回复。很多秒杀系统使用消息队列来进行流量削峰,这也是异步带来的优势之一。image这里我需要再说一件事:异步不是免费的。在大多数情况下,使用异步会比使用同步编写更多的代码,并且需要更多的时间来查找错误。但是对于一个高并发的系统来说,异步的好处还是值得的,前提是你正确的应用了异步。本文转载自微信公众号《建筑师实践之路》,可通过以下二维码关注。转载本文请联系架构师修炼之路公众号。