在整个网站应用中,缓存几乎无处不在,既存在于浏览器也存在于应用服务器和数据库服务器;既能够对数据缓存,也能够对文件缓存,还能够对页面片断缓存。合理使用缓存,对网站性能优化意义重大。
网站性能优化第必定律:优先考虑使用缓存。算法
缓存是指将数据存储在相对较高访问速度的存储介质中。数据库
(1)访问速度快,减小数据访问时间;
(2)若是缓存的数据进过计算处理获得的,那么被缓存的数据无需重复计算便可直接使用,所以缓存还起到减小计算时间的做用。编程
缓存的本质是一个内存Hash表,以一对Key、Value的形式存储在内存Hash表中,读写时间复杂度为O(1)。设计模式
缓存主要用来存放那些读写比很高、不多变化的数据,如商品的类目信息,热门词的搜索列表信息,热门商品信息等。应用程序读取数据时,先到缓存中读取,若是读取不到或数据已失效,再访问数据库,并将数据写入缓存。浏览器
网站数据访问一般遵循二八定律,即80%的访问落在20%的数据上,所以利用Hash表和内存的高速访问特性,将这20%的数据缓存起来,可很好地改善系统性能,提升数据存取速度,下降存储访问压力。缓存
不合理使用缓存非但不能提升系统的性能,还会成为系统的累赘,甚至风险。安全
若是缓存中保存的是频繁修改的数据,就会出现数据写入缓存后,应用还来不及读取缓存,数据就已经失效,徒增系统负担。通常来讲,数据的读写比在2:1(写入一次缓存,在数据更新前至少读取两次)以上,缓存才有意义。性能优化
若是应用系统访问数据没有热点,不遵循二八定律,那么缓存就没有意义。服务器
通常会对缓存的数据设置失效时间,一旦超过失效时间,就要从数据库中从新加载。所以要容忍必定时间的数据不一致,如卖家已经编辑了商品属性,可是须要过一段时间才能被买家看到。还有一种策略是数据更新当即更新缓存,不过这也会带来更多系统开销和事务一致性问题。网络
缓存会承担大部分数据库访问压力,数据库已经习惯了有缓存的日子,因此当缓存服务崩溃时,数据库会由于彻底不能承受如此大压力而宕机,致使网站不可用。这种状况被称做缓存雪崩,发生这种故障,甚至不能简单地重启缓存服务器和数据库服务器来恢复。
实践中,有的网站经过缓存热备份等手段提升缓存可用性:当某台缓存服务器宕机时,将缓存访问切换到热备服务器上。但这种设计有违缓存的初衷,缓存根本就不该该当作一个可靠的数据源来使用。
经过分布式缓存服务器集群,将缓存数据分布到集群多台服务器上可在必定程度上改善缓存的可用性。当一台缓存服务器宕机时,只有部分缓存数据丢失,从新从数据库加载这部分数据不会产生很大的影响。
缓存中存放的是热点数据,热点数据又是缓存系统利用LRU(最近最久未用算法)对不断访问的数据筛选淘汰出来,这个过程须要花费较长的时间。新系统的缓存系统若是没有任何数据,在重建缓存数据的过程当中,系统的性能和数据库负载都不太好,那么最好在缓存系统启动时就把热点数据加载好,这个缓存预加载手段叫缓存预热。对于一些元数据如城市地名列表、类目信息,能够在启动时加载数据库中所有数据到缓存进行预热。
若是由于不恰当的业务、或者恶意攻击持续高并发地请求某个不存在的数据,因为缓存没有保存该数据,全部的请求都会落到数据库上,会对数据库形成压力,甚至崩溃。一个简单的对策是将不存在的数据也缓存起来(其value为null)。
分布式缓存指缓存部署在多个服务器组成的集群中,以集群方式提供缓存服务,其架构方式有两种,一种是以JBoss Cache为表明的须要更新同步的分布式缓存,一种是以Memcached为表明的不互相通讯的分布式缓存。
JBoss Cache在集群中全部服务器中保存相同的缓存数据,当某台服务器有缓存数据更新,就会通知其余机器更新或清除缓存数据。 它一般将应用程序和缓存部署在同一台服务器上,但受限于单一服务器的内存空间;当集群规模较大的时候,缓存更新须要同步到全部机器,代价惊人。所以这种方案多见于企业应用系统中。
Memcached采用一种集中式的缓存集群管理(互不通讯的分布式架构方式)。缓存与应用分离部署,缓存系统部署在一组专门的服务器上,应用程序经过一致性Hash等路由算法选择缓存服务器远程访问数据,缓存服务器之间不通讯,集群规模能够很容易地实现扩容,具备良好的伸缩性。详细请看LZ其余文章。
Memcached有如下几个特性:
(1)简单的通讯协议。Memcached使用TCP协议(UDP也支持)通讯;
(2)丰富的客户端程序。
(3)高性能的网络通讯。Memcached服务端通讯模块基于Libevent,一个支持事件触发的网络通讯程序库,具备稳定的长链接。
(4)高效的内存管理。
(5)互不通讯的服务器集群架构。
使用消息队列将调用异步化(生产者–消费者模式),可改善网站的扩展性,还能够改善系统的性能。
在不使用消息队列的状况下,用户的请求数据直接写入数据库,在高并发的状况下,会对数据库形成巨大压力,使得响应延迟加重。在使用消息队列后,用户请求的数据发送给消息队列后当即返回,再由消息队列的消费者进程(一般状况下,该进程独立部署在专门的服务器集群上)从消息队列中获取数据,异步写入数据库。因为消息队列服务器处理速度远快于服务器(消息队列服务器也比数据库具备更好的伸缩性)。
消息队列具备很好的削峰做用–经过异步处理,将短期高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。
须要注意的是,因为数据写入消息队列后当即返回给用户,数据在后续的业务校验、写数据库等操做可能失败,所以在使用消息队列进行业务异步处理后,须要适当修改业务流程进行配合,如订单提交后,订单数据写入消息队列,不能当即返回用户订单提交成功,须要在消息队列的订单消费者进程真正处理完后,甚至商品出库后,再经过电子邮件或SMS消息通知用户订单成功,以避免交易纠纷。有关消息队列的详细信息请参看LZ的其余博客。
任何能够晚点作的事情都应该晚点再作。
在网站高并发访问的场景下,使用负载均衡技术为一个应用构建一个由多台服务器组成的服务器集群,将并发访问请求分发到多台服务器上处理,避免单一服务器因负载压力过大而响应缓慢,使用户请求具备更好的响应延迟特性。
从资源利用的角度看,使用多线程的缘由主要有两个:IO阻塞与多CPU。当前线程进行IO处理的时候,会被阻塞释放CPU以等待IO操做完成,因为IO操做(不论是磁盘IO仍是网络IO)一般都须要较长的时间,这时CPU能够调度其余的线程进行处理。 理想的系统Load是既没有进程(线程)等待也没有CPU空闲,利用多线程IO阻塞与执行交替进行,可最大限度利用CPU资源。 使用多线程的另外一个缘由是服务器有多个CPU。
简化启动线程估算公式:
启动线程数 = [任务执行时间 / (任务执行时间 - IO等待时间)]*CPU内核数
多线程编程一个须要注意的问题是线程安全问题,即多线程并发对某个资源进行修改,致使数据混乱。全部的资源—对象、内存、文件、数据库,乃至另外一个线程均可能被多线程并发访问。
编程上,解决线程安全的主要手段有:
(1)将对象设计为无状态对象。所谓无状态对象是指对象自己不存储状态信息(对象无成员变量,或者成员变量也是无状态对象),不过从面向对象设计的角度看,无状态对象是一种不良设计。
(2)使用局部对象。即在方法内部建立对象,这些对象会被每一个进入该方法的线程建立,除非程序有意识地将这些对象传递给其余线程,不然不会出现对象被多线程并发访问的情形。
(3)并发访问资源时使用锁。即多线程访问资源的时候,经过锁的方式使多线程并发操做转化为顺序操做,从而避免资源被并发修改。
系统运行时,要尽可能减小那些开销很大的系统资源的建立和销毁,好比数据库链接、网络通讯链接、线程、复杂对象等。从编程角度,资源复用主要有两种模式:单例(Singleton)和对象池(Object Pool)。
单例虽然是GoF经典设计模式中较多被诟病的一个模式,但因为目前Web开发中主要使用贫血模式,从Service到Dao都是些无状态对象,无需重复建立,使用单例模式也就天然而然了。
对象池模式经过复用对象实例,减小对象建立和资源消耗。对于数据库链接对象,每次建立链接,数据库服务端都须要建立专门的资源以应对,所以频繁建立关闭数据库链接,对数据库服务器是灾难性的,同时频繁建立关闭链接也须要花费较长的时间。所以实践中,应用程序的数据库链接基本都使用链接池(Connection Pool)的方式,数据库链接对象建立好之后,将链接对象放入对象池容器中,应用程序要链接的时候,就从对象池中获取一个空闲的链接使用,使用完毕再将该对象归还到对象池中便可,不须要建立新的链接。
早期关于程序的一个定义是,程序就是数据结构+算法,数据结构对于编程的重要性不言而喻。在不一样场景中合理使用数据结构,灵活组合各类数据结构改善数据读写和计算特性可极大优化程序的性能。
理解垃圾回收机制有助于程序优化和参数调优,以及编写内存安全的代码。