大型网站技术架构,4网站的高性能架构之应用服务器性能优化

4.3 应用服务器性能优化

应用服务器就是处理网站业务的服务器,网站的业务代码都部署在这里,是网站开发最复杂,变化最多的地方,优化手段主要有缓存、集群、异步等。算法

 

4.3.1 分布式缓存

 网站性能优化第必定律:优先考虑使用缓存优化性能。数据库

 

一、缓存的基本原理

缓存速度快,减小访问时间编程

缓存的数据是通过计算获得的,减小计算的时间缓存

 

哈希取余数据长度安全

一致性哈希算法性能优化

 

缓存主要用来存放那些读写比很高、不多变化的数据服务器

如商品的类目信息,热门词的搜索列表信息,热门商品信息等。网络

 

网站数据,80%的访问落在20%的数据上,所以利用Hash表和内存的告诉访问特性,将这20%的数据缓存起来,可很好地改善系统性能,提升数据读取数据,下降存储访问压力。数据结构

 

二、合理使用缓存

缓存滥用:过度依赖低可用的缓存系统,不恰当地使用缓存的数据访问特性等。多线程

 

频繁修改的数据

数据读写比至少在2:1以上,缓存才有意义。

写入一次缓存,在数据更新前至少读取两次。

 

数据不一致与脏读

 

先更新数据,再删除缓存

 

缓存可用性

 

缓存承担系统大部分压力时,缓存的大量失效会致使,访问所有落到数据库,形成数据库宕机,乃至整个系统不可用,这种状况称为缓存雪崩。

缓存热备是一种方案,可是缓存根本就不应被当作一个可靠的数据源来使用。

 

经过分布式缓存服务器集群,将缓存数据分布到集群多台服务器上可在必定程度上改善缓存的可用性。

当一台缓存服务器宕机的时候,只有部分缓存数据失效,从新从数据库加载这部分数据不会对数据库产生很大影响。

 

产品在设计之初就须要一个明确的定位:什么是产品要实现的功能,什么不是产品提供的特性。

在产品漫长的生命周期中,会有形形色色的困难和诱惑来改变产品的发展方向,左右摇摆、什么都想作的产品,最后有可能成为一个失去生命力的四不像。

 

缓存预热

 

缓存中存放的是热点数据,热点数据又是缓存系统利用LRU对不断访问的数据筛选淘汰出来的,这个过程须要花费较长的时间。

新启动的系统,须要缓存预热,加载热点数据。

对于一些元数据如城市列表、类目信息,能够在启动时加载数据库中所有数据到缓存进行预热。

 

缓存穿透

 

若是由于不恰当的业务、或者恶意攻击持续高并发地请求某个不存在的数据,因为缓存没有保存该数据,全部的请求都会落到数据库上,会对数据库形成很大压力,甚至崩溃。一个简单的对策是将不存在的数据也缓存起来(其value值为null)

 

三、分布式缓存架构

分布式缓存指缓存部署在多个服务器组成的集群中,以集群方式提供缓存服务,有两种架构:

JBoss Cache:须要更新同步的分布式缓存,不算真正的分布式,只能算法是集群热备。全部服务器保存相同的缓存数据,当某台服务器有缓存数据更新的时候,会通知集群中其余机器更新缓存数据或清除缓存数据。

缓存的数据受限于单一服务器的内存空间,不具备伸缩性。能够联系前面的软件架构要素分析。

缓存的数据量很大时,这种架构没法应对。

 

Memcached采用一种集中式的缓存集群管理,也被称为互不通讯的分布式架构方式。

缓存和应用分离部署,应用程序经过一致性Hash等路由算法选择缓存服务器远程访问缓存数据,缓存服务器之间不通讯,缓存集群的规模能够很容易地实现扩容,具备良好的可伸缩性。

 

四、Memcached

 

 简单的通讯协议

远程通讯须要考虑两方面要素:

一、通讯协议:TCP/UDP/HTTP

二、序列化协议:数据传输的两端,必须使用彼此但是别的数据序列化方式才能使通讯得以完成,如XML、JSON等文本序列化协议,或者Google Protobuffer等二进制序列化协议。

 

Memcached使用TCP协议(UDP也支持)通讯,其序列化协议则是一套基于文本的自定义协议,很是简单,以一个命令关键字开头,后面是一组命令操做数。例如读取一个数据的命令协议是get<key>。

 

丰富的客户端程序

高性能的网络通讯,Memcached服务端通讯模块基于Libevent,一个支持异步非阻塞,支持事件触发的网络通讯库。稳定的长链接特性。

 

高效的内存管理

内存碎片化管理比较难,经常使用的方法好比压缩、复制等。

 

Memcached使用的是固定空间分配。

Memcached将内存空间分为一组slab,每一个slab里又包含一组chunk,同一个slab里每一个chunk的大小是固定的,拥有相同大小chunk的slab被组织在一块儿,叫作slab_class。

 

 存储数据时,根据数据的Size大小,寻找一个大于Size的最小chunk将数据写入。

这种内存管理方式避免了内存碎片管理的问题,内存的分配和释放都是以chunk为单位的。

Memcached采用LRU算法释放最近最久未被访问的数据占用的空间,释放的chunk被标记为未用,等待下一个合适大小数据的写入。

 

这种方式会带来内存浪费的问题,即数据只能存入一个比它大的chunk里,而一个chunk只能存一个数据,其余空间被浪费了。

启动参数配置不合理,浪费会更加惊人,发现没有缓存多少数据,内存空间就用尽了。

 

互不通讯的服务器集群架构

 一致性哈希算法路由成为数据存储伸缩性架构设计的经典范式。

集群内服务器互不通讯使得集群能够作到几乎无限制的线性伸缩。

 

4.3.2 异步操做

 使用子消息队列将调用异步化,可改善网站的扩展性。还能够改善网站系统的性能。

 

使用消息队列后,用户请求的数据发送给消息队列后当即返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。

 

因为消息队列服务器处理速度远快于数据库(消息队列服务器也比数据库具备更好的伸缩性),所以用户的响应延迟可获得有效改善。

 

消息队列具备很好的削峰做用——即经过异步处理,将短期高并发产生的事务消息存储在消息队列中,从而削平高峰期并发事务。

 

电商业务促销活动中,合理使用消息队列,可有效抵御促销活动刚开始大量涌入的订单对系统形成的冲击。

 

 

须要注意的是,因为数据写入消息队列后当即返回给用户,数据在后续的业务校验、写数据库等操做可能失败,所以在使用消息队列进行业务异步处理后,须要适当修改业务流程进行配合,如订单提交后,订单数据写入消息队列,不能当即返回用户订单提交成功,须要在消息队列的订单消费者进程真正处理完该订单,甚至商品出库后,再经过电子邮件或SMS消息通知用户订单成功,以避免交易纠纷。

 

任何能够晚点作的事情都应该晚点再作。

 

4.3.3 使用集群

使用负载均衡技术为一个应用构建一个由多台服务器组成的服务器集群,将并发请求分发到多台服务器上处理,避免单一服务器因负载压力过大而响应缓慢,使用户请求具备更好的响应延迟特性

 

4.3.4 代码优化

一、多线程

从资源利用角度,使用多线程的缘由主要有两个:IO阻塞与多CPU。

当前线程处理IO的时候,会被阻塞释放CPU以等待IO操做完成,因为IO操做须要时间较长,这时CPU能够调度其余线程进行处理。

另:使用现代操做系统提供的诸如epoll等系统调用,能够实现异步非阻塞IO,能够同时处理更多的IO请求

多CPU时代充分利用系统CPU资源

 

最佳启动线程数和CPU内核数量成正比,和IO阻塞时间成反比。

若是任务都是CPU计算型任务,那么线程数最多不超过CPU核数;

若是是IO密集型任务,等待磁盘操做,网络响应,那么多启动线程有助于提升任务并发度,提升系统吞吐能力,改善系统性能。

 

多线程编程须要注意线程安全问题,即多线程并发对某个资源进行修改,致使数据混乱。

 

对网站而言,无论有没有进行多线程编程,工程师写的每一行代码都会被多线程执行,由于用户请求是并发提交的,也就是说,全部的资源——对象、内存、文件、数据库,乃至另外一个线程均可能被多线程并发访问。

 

编程上,解决线程安全的主要手段有以下几点:

将对象设计为无状态对象

无状态是指对象自己不存储状态信息(对象无成员变量,或者成员变量也是无状态对象),这样多线程并发访问的时候就不会出现状态不一致,Java Web开发中经常使用的Servlet对象就设计为无状态对象,能够被应用服务器多线程并发调用处理用户请求。

而Web开发中经常使用的贫血模型对象都是些无状态对象。不过从面向对象设计的角度看,无状态对象是一种不良设计。

使用局部对象(使用局部变量,线程本地变量ThreadLocal)

方法内部建立的对象的生命周期是随着方法的进入退出,方法是线程线程私有的,除非对象在方法内部建立后,被有意识的传递给其余线程,不然不会出现对象被多线程并发访问的情形。

并发访问资源时使用锁

使用锁将并发操做转化为顺序操做,从而避免资源被并发修改。锁致使线程同步顺序执行,可能会对系统性能产生严重影响。

二、资源复用

系统运行时,要尽可能减小那些开销很大的系统资源的建立和销毁,好比数据库链接、网络通讯链接、线程、复杂对象等。从编程角度,资源复用主要有两种模式:单例和对象池。

单例模式:

 

Spring的单例

 

对象池模式经过复用对象实例,减小对象建立和资源消耗。

数据库链接池对象

线程池对象

池管理方式:

 

三、数据结构

在不一样场景中合理使用恰当的数据结构,灵活组合各类数据结构改善数据读写和计算特性可极大优化程序的性能。

 

什么场景使用什么数据结构,优化了哪些方面呢?上面这句话是捕捉不到这些信息的。

 

举个例子:缓存使用的数据结构是hash表,hash表的读写性能依赖HashCode的随机性,越随机冲突越少,读写性能越高。

time33算法+md5

 

四、垃圾回收

为何要理解GC和调优?

由于Java Web应用运行在JVM等具备垃圾回收功能的环境中,垃圾回收对系统的性能产生巨大影响。理解垃圾回收机制有助于程序优化和参数调优,以及编写内存安全的代码。

 

JVM将内存划分为堆和栈。

 

栈用于存储线程上下文信息,如方法参数、局部变量

 

堆存储对象,对象的建立、释放垃圾收集都在堆上进行。

 

JVM的垃圾收集是分代收集,堆空间分为年轻代和老年代,年轻代分为Eden区、From区和To区。

 

对象建立都在Eden区,当Eden区满,就触发一次YGC,将还被使用的对象复制到From区,回收Eden区。

当Eden区再满了,再次触发YGC,将Eden区和From区还在被使用的对象复制到To区,下一次YGC则是将Eden区和To区还被使用的对象复制到From区。

 

通过屡次YGC,某些对象会在From区和To区复制屡次,若是超过某个阈值还未被释放,则将该对象复制到老年代。

 

若是老年代用完,就会触发Full GC,即所谓的全量回收,全量回收会对整个系统产生较大影响,由于应根据系统业务特色和对象生命周期,合理设置年轻代和老年代的大小,尽可能减小Full GC。

相关文章
相关标签/搜索