从技术角度讲解网站是如何从小成长起来(2)
二、用户的session如何来同步?That’s a good question,TOO!
虽然HTTP协议是一个无状态的服务协议,但是,用户的基本信息是要求能够保证的。比如:登录信息。原来在单机的时候,我们可以很简单的使用类似setSession(“user”, ”XXX”)的函数来解决。当使用多机的时候,该怎么样来解决呢?
其实这个问题也是当年困扰我很久的一个问题。如果用setSession,用户在某一台机器上登录了,当下次请求来的时候,到其他机器了,就变成未登录了。Oh,My God!
Ok,让我们一个个的来看:
1、一台机器登录,其他机器不知道;
2、用户请求可能到多台机器。
对于第一个问题,如果我们在一台机器的登录信息让其他机器知道,不就OK了嘛。或者,大家都在一台机器上登录,不就可以了嘛。 对于第二个问题,如果我们让同一个用户的请求,只落在同一台机器上,不就OK了嘛。因此,我们可以提出三种解决方案:
1、提供session同步机制;
2、提供统一session服务;
3、将同一用户分流到同一机器。
嗯,这三种方式,你会选哪个呢?如果是我,我就选最后一个。因为我是一个懒虫,我会选最简单的一个,我信奉的一个原则既是:简单粗暴有效!哈哈。在WebServer层使用一致Hash算法,按session_id进行分流(如果WebServer没有提供该功能,可以简单写一个扩展,或者干脆在WebServer后面做一个代理即可)。但是这种方案有一个致命的问题,当一台机器宕机了以后,该机器上的所有用户的session信息即会丢失,即使是做了磁盘备份,也会有一段时间出现session失效。
好,那看看第一种方案。其实现在有一些框架已经提供了这样的服务机制。比如Tomcat就提供session同步机制。利用自有的协议,将一台机器上的session数据同步到其他的机器上。这样就有一个问题,我需要在所有的机器上配置需要同步的机器,机器的耦合度瞬间就增加了,烦啊!而且,如果session量比较大的话,同步的实效性还是一个问题。
那再来看看第二种方案,提供统一session服务。这个就是单独再写一个逻辑程序,来管理session,并且以网络服务的方式提供查询和更新。对于这样的一个阶段的服务来讲,显得重了一些。因为,我们如果这样做,又会面临一堆其他的问题,比如:这个服务是否存在单点(一台服务器,如果宕机服务就停止),使用什么样的协议来进行交互等等。这些问题在我们这个阶段都还得不到解决。所以,看起来这个方案也不是很完美。
好吧,三种方案选其一,如果是你,你会选哪一种呢?或者还有更好的方案?如果我没钱没实力(传说中的“屌丝”,哈哈),我就可能牺牲一下服务的稳定性,采用代价最低的。
三、数据访问同步问题。
当多个请求同时到达,并且竞争同一资源的时候(比如:秒杀,或是定火车票),我们怎么来解决呢?
这个时候,因为我们用到了单机数据库,可以很好的利用数据库的“锁”功能来解决这个问题。一般的数据库都提供事务的功能。事务的级别分多种,比如可重复读、串行化等,根据不同的业务需求,可能会选择不同的事务级别。我们可以在需要竞争的资源上加上锁,用于同步资源的请求。但是,这个东东也不是万能的,锁会极大的影响效率,所以尽量的减少锁的使用,并且已经使用锁的地方尽量的优化,并检查是否可能出现死锁。
cache也有对应的解决方案,比如延迟删除或者冻结时间等技术,就是让资源在一段时间处于不可读状态,用户直接从数据库查询,这样保证数据的有效性。
好了,上述三个问题,应该涵盖了我们在这个阶段遇到的大部分问题。那么,我们现在可以把整体的架构图画出来看看。
样的结构,足够我们撑一段时间了,并且因为逻辑程序的无状态性,可以通过增加机器来扩展。而接下来我们要面对的,就是提交增长和查询量增加带来的存储性能的瓶颈。
【第六阶段:写分离,提升IO性能】
好了,到现在这个阶段,我们的单机数据库可能已经逐步成为瓶颈,数据库出现比较严重的读写冲突(即:多个线程或进程因为读写需要,争抢磁盘,使得磁盘的磁头不断变换磁道或盘片,造成读写都很缓慢)。
那我们针对这样的问题,看看有哪些方法来解决。
一、减少读取量。我们所有的问题来源就是因为读写量增加,所以看起来这个是最直接最根源的解决办法。不过,用户有那么大请求量我们怎么可能减少呢?其实,对于越后端的系统,这是越可能的事情。我们可以在每一层都减少一部分往后传输的请求。具体到数据库的话,我们可以考虑通过增加cache命中率,减少数据库压力。增加cache命中率有很多中方法,比如对业务访问模式进行优化、多级cache模式、增加内存容量等等。业务模式的修改不是太好通用,因此这里我们考虑如何通过增加内存容量来解决问题。
对于单机,现在通用的cache服务一般都可以配置内存大小,这个只需要很简单的配置即可。另外,我们也可以考虑多机cache的方案,通过增加机器来扩充内存容量。因此,我们就引入了分布式cache,现在常用的cache(如:memcache),都带有这样的功能,支持多机cache服务,可以通过负载均衡算法,将请求分散到多台不同的机器上,从而扩充内存容量。
这里要强调一点。在我们选择均衡算法的时候,是有考虑的。这个时候,常常选贼一致Hash算法,将某一系列ID分配到固定的机器,这样的话,能放的KV对基本等于所有机器相加。否则,如果不做这样的分配,所有机器内存里面的内容会有大量重复,内存并没有很好的利用。另外,因为采用一致Hash,即使一台机器宕掉,也会比较均匀的分散到其他机器,不会造成瞬间其他机器cache大量失效或不命中的问题。
二、减少写入量。要减少用户的提交,这个看起来是不太现实的。确实,我们要减少写入的量似乎是很难的一件事。不过也不是完全不可能。这里我们会提到一个思想:合并写入。就是将有可能的写入在内存里进行合并,到一定时间或是一定条件后,再一起写入。其实,在mysql等存储引擎内部,都是有这样的机制的。打个比方,比如有一个逻辑是修改用户购买物品的数量,每次用户购买物品后,计数都加一。如果我们现在不是每次都去实时写磁盘,而是到一定的时间或一定次数后,再写入,这样就可以减少大量的写入操作。但是,这里需要考虑,如果服务器宕掉以后,内存数据的恢复问题(这一部分会在后面来描述)。因此,如果想简单的使用数据合并,最好是针对数据重要性不是很强的业务,即使丢掉一部分数据,也没有关系。