3

应对高并发系统有没有通用的解决方案呢?

 3 years ago
source link: https://my.oschina.net/caicaijun/blog/4880805
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

灵魂拷问:

  • 应对高并发系统有没有一些通用的解决方案呢?
  • 这些方案解决了什么问题呢?
  • 这些方案有那些优势和劣势呢?

对性能孜孜不倦的追求是互联网技术不断发展的根本驱动力,从最初的大型机到现在的微型机,在本质上也是为了性能而生。软件系统也存在类似的现象,一个系统从最初的少量访问请求到后期的大并发请求,这都需要我们对性能的提升提供一系列解决方案。像最初的淘宝,也仅仅是一个外包做出来的产品,随着业务的不断发展,淘宝的并发量指数级增加,同时对系统提出了严峻的挑战,这才逐步造就了现在淘宝这样可以支撑数千万人同时在线的高并发系统。

提起应对高并发,每个人都或多或少可以说出几种解决方案,高并发系统的设计魅力在于我们能够凭借程序员的聪明才智设计巧妙的方案,从而应对巨大流量的冲击。从目前已知的方案中,大体可以归纳为以下几种

提升单机性能

尽可能的提升单机的性能是一个永恒的话题,无论是采用分布式还是其他方案,单机性能的提高,对于一个系统来说只有益处。拿编程语言来说,c或者c++语言编写的程序理论上会比java ,net,Python写的程序要高效,当然这需要建立在程序正常运行的情况下。提升单机性能最简单粗暴的方式就是提升硬件性能,举一个简单例子:假如数据库DB的服务器内存为8G,随着数据量的增加,你会发现有些sql执行会慢慢的变慢,原因是数据库的索引或者数据在内存中完全存放不下,需要回写磁盘,有些查询在内存中并不能命中,造成了一些sql会在磁盘中查询数据,这个时候如果把服务器的内存增加到16G,你会发现这些慢sql居然凭空消失了,这是硬件提升性能的一个典型案例。

对于运行的程序也是同样的道理,尽可能的把程序优化到极致,也许单机就可以达到别人分布式部署的性能效果,当然这需要我们在编写代码的时候仔细构思。

无论什么时候,我觉得提升单机性能都有必要

当一个单机系统无法抵抗巨大流量冲击的时候,最简单有效的解决方案之一便是横向扩展,横向扩展是指把巨大的流量分割为数个比较小的流量,从而解决高并发系统的性能问题,本质上,横向扩展属于分而治之的理论,属于分布式的概念范畴。

举一个很简单的例子,假设目前单机处理请求数为200/s,当每秒的请求数到达1000的时候,单台机器肯定会遇到瓶颈,这个时候如果处理请求的服务器增加到5台,甚至更多,这样便轻松解决了性能问题。当然,能否方便的横向扩展还要看具体的系统设计,如果系统是无状态的,理论上横向扩展是没问题的,但是一些有状态的服务,可能会涉及到状态的迁移等工作,这也是为什么很多架构师提倡无状态服务的一个原因。

一个应用程序的横向扩展可以通过负载均衡来实现,像阿里云的SLB服务,nginx的反向代理功能,这些都可以很方便实现应用程序的横向扩展。但是,像数据库比如mysql,这样的DB系统,无限制的横向扩展可能只是一个目标。大多数DB采用的主从或者多主多从来解决横向扩展问题,主节点负责写操作,从节点负责读操作,当然这里涉及到主从同步的机制,主从同步的延迟等问题,有兴趣的同学可以去深入研究一下。

image

那什么时候该选择横向扩展呢?一般来讲,在系统的设计之初便会考虑横向扩展,因为这种方案足够简单,可以用堆砌硬件来解决的问题就不是问题。现在我敢说90%以上的系统在第一版上线的时候就做了类似负载均衡的部署方案,其中有很多就利用了nginx的反向代理功能。

image

当然横向扩展并非没有负面影响,和单机系统一样,横向扩展也要考虑某个节点down掉的问题,所以监控和健康检查是现在一个系统必备的手段,而且在系统设计之初便会在整体架构之中。就像我前几篇的文章所说,横向扩展既然属于分布式范畴,必然需要考虑分布式系统需要考虑的问题:

分布式系统的问题

除了上面所说的横向扩展方案,另外一种行之有效并且足够简单的便是缓存方案。这一点毋庸置疑,缓存可以遍布在一个系统的各个角落,从操作系统到浏览器,从cpu到磁盘,从数据库到消息队列,任何稍微复杂的服务和组件中都有缓存的影子。

缓存为什么可以大幅度提高性能的性能呢?这还需要从系统的瓶颈来说,在客户端一个请求的生命周期中,这个请求的响应时间严重受限于最慢的那个环节,这类似于木桶效应(一个木桶可以存的水量,取决于最短那个木板)。

举一个很简单的例子:当客户端请求商城的一个商品信息的时候,请求经过http协议到达服务器的某个端口,服务端程序把请求解包然后去请求数据库,数据库不单单在另外一台服务器上,而且还需要从磁盘中加载数据,所谓的DB缓存没有命中。在这整个过程中,请求磁盘的过程是最慢的,普通磁盘是由机械手臂,磁头,转轴,盘片组成,磁盘在查询数据的时候,磁头是需要花费很长时间累寻道的,当然SSD的速度要比普通磁盘快的多,但是相比较内存还是要慢几个量级。而我们最想要的流程是这样的:当一个请求到达服务端的时候能尽快的从某个设备上取出信息,然后返给客户端,这个设备绝不可能是磁盘,这个设备在速度和容量上比较均衡,它应该是内存。

缓存在语义上要丰富很多,我们可以把任何可以降低响应时间的中间存储都称之为缓存。比如CPU的一级缓存,二级缓存,三级缓存,浏览器的缓存等。缓存主要解决了上下游设备速度不匹配的问题

image

程序界有一句古话:把数据放在离用户最近的地方才是最快的。CDN本质上就是做的这件事。对于缓存而言,我们经常会听到浏览器缓存,进程内缓存,进程外缓存等概念。目前针对于服务端一般的缓存策略为采用第三方kv存储设备,比如redis,Memcache等。当然在对性能极其苛刻的系统中,我还是推荐使用进程内缓存,具体可见之前的推文:

高并发下为什么更喜欢进程内缓存

谈到异步,必须要说下同步,同步调用是指调用方要阻塞等待被调用方执行完毕才可以返回。系统现在普遍都会采用多线程的方式来提供系统的吞吐量(多进程的方式现在很少,但不代表没有,比如:nodejs,nginx),在同步这种方式下,如果被调用方的响应时间过长,会造成调用方的线程长时间处于等待状态,线程的利用率大幅度降低,线程对于系统来说,是很昂贵的资源,创建大量的线程去应对高并发是不明智的,不仅仅浪费了内存,而且会加大线程上下文cpu切换的成本。

一个高吞吐量的系统,理论上所有的线程都要时时刻刻在工作,而且把cpu资源压榨到最多。对于一个IO密集型操作来说,采用异步方式可以大大提高系统吞吐量。异步不需要等待被调用方执行完成就可以执行其他的逻辑,在被调用方执行完毕之后通过通知回调的方式反馈给调用方。

异步本质上是一种编程思想,一种编程模型。他提高的是系统整体的吞吐量,但是请求的响应时间对比同步方式来说会略微加大。

像平时用的最多的消息队列,在模型上也属于异步编程模型。调用方会把消息丢到队列中,然后直接返回去执行其他业务,被调用方接收到消息然后进行处理,然后根据具体的业务看是否需要给予结果回复。有不少秒杀系统会采用消息队列进行流量削峰,这是异步带来的优势之一。

image

关于异步更加详细的介绍可以查看之前的推文:

问世间异步为何物?

在这里我需要多说一句:异步并不是没有代价,在多数情况下,采用异步会比同步方式编写更多的代码,而且查找bug会花费更多的时间。但是对于一个高并发系统来说,异步带来的益处还是值得的,前提是你正确应用了异步。

更多精彩文章

image


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK