8 应用服务器性能优化

缓存主要用来存放那些读写比很高、很少变化的数据,如商品的类目信息,热门词 的搜索列表信息,热门商品信息等。应用程序读取数据时,先到缓存中读取,如果读取 不到或数据已失效,再访问数据库,并将数据写入缓存,如图4.8所示。

大型 站需要缓存的数据量一般都很庞大,可能会需要数TB的内存做缓存,这时候
就需要另一种分布式缓存,如图4.10所不。Memcached采用一种集中式的缓存集群管理, 也被称作互不通信的分布式架构方式。缓存与应用分离部署,缓存系统部署在一组专门 的服务器上,应用程序通过一致性Hash等路由算法选择缓存服务器远程访问缓存数据, 缓存服务器之间不通信,缓存集群的规模可以很容易地实现扩容,具有良好的可伸缩性。

  1. Memcached

Memcached曾一度是 站分布式缓存的代名词,被大量 站使用。其简单的设计、优异的性能、互不通信的服务器集群、海量数据可伸缩的架构令 站架构师们趋之若鹜。

在不使用消息队列的情况下,用户的请求数据直接写入数据库,在高并发的情况下,
会对数据库造成巨大的压力,同时也使得响应延迟加剧。在使用消息队列后,用户请求 的数据发送给消息队列后立即返回,再由消息队列的消费者进程(通常情况下,该进程 通常独立部署在专门的服务器集群上)从消息队列中获取数据,异步写入数据库。由于 消息队列服务器处理速度远快于数据库(消息队列服务器也比数据库具有更好的伸缩 性),因此用户的响应延迟可得到有效改善。

消息队列具有很好的削峰作用一即通过异步处理,将短时间高并发产生的事务消 息存储在消息队列中,从而削平高峰期的并发事务。在电子商务 站促销活动中,合理 使用消息队列,可有效抵御促销活动刚开始大量涌入的订单对系统造成的冲击。如图4.14所示。

三台Web服务器共同处理来自用户浏览器的访问请求,这样每台Web服务器需要处 理的http请求只有总并发请求数的三分之一,根据性能测试曲线,使服务器的并发请求 数目控制在最佳运行区间,获得最佳的访问请求延迟。


4代码优化

站的业务逻辑实现代码主要部署在应用服务器上,需要处理复杂的并发事务。合 理优化业务代码,可以很好地改善 站性能。不同编程语言的代码优化手段有很多,这 里我们概要地关注比较重要的几个方面。

  1. 多线程

多用户并发访问是 站的基本需求,大型 站的并发用户数会达到数万,单台服务
器的并发用户也会达到数百。CGI编程时代,每个用户请求都会创建一个独立的系统进程 去处理。由于线程比进程更轻量,更少占有系统资源,切换代价更小,所以目前主要的
Web应用服务器都采用多线程的方式响应并发用户请求,因此 站开发天然就是多线程
编程。

从资源利用的角度看,使用多线程的原因主要有两个:10阻塞与多CPUo当前线程 进行10处理的时候,会被阻塞释放CPU以等待10操作完成,由于10操作(不管是磁 盘IO还是 络IO)通常都需要较长的时间,这时CPU可以调度其他的线程进行处理。

前面我们提到,理想的系统Load是既没有进程(线程)等待也没有CPU空闲,利用多 线程10阻塞与执行交替进行,可最大限度地利用CPU资源。使用多线程的另一个原因是服务器有多个CPU,在这个连手机都有四核CPU的时代,除了最低配置的虚拟机,一 般数据中心的服务器至少16核CPU,要想最大限度地使用这些CPU,必须启动多线程。

站的应用程序一般都被Web服务器容器管理,用户请求的多线程也通常被Web服 务器容器管理,但不管是Web容器管理的线程,还是应用程序自己创建的线程,一台服 务器上启动多少线程合适呢设服务器上执行的都是相同类型任务,针对该类任务启 动的线程数有个简化的估算公式可供参考:

启动线程数=[任务执行时间/ (任务执行时间-IO等待时间)]xCPU内核数

最佳启动线程数和CPU内核数量成正比,和IO阻塞时间成反比。如果任务都是CPU
计算型任务,那么线程数最多不超过CPU内核数,因为启动再多线程,CPU也来不及调 度;相反如果是任务需要等待磁盘操作, 络响应,那么多启动线程有助于提高任务并 发度,提高系统吞吐能力,改善系统性能。

多线程编程一个需要注意的问题是线程安全问题,即多线程并发对某个资源进行修 改,导致数据混乱。这也是缺乏经验的 站工程师最容易犯错的地方,而线程安全Bug 又难以测试和重现, 站故障中,许多所谓偶然发生的“灵异事件”都和多线程并发问 题有关。对 站而言,不管有没有进行多线程编程,工程师写的每一行代码都会被多线 程执行,因为用户请求是并发提交的,也就是说,所有的资源——对象、内存、文件、 数据库,乃至另一个线程都可能被多线程并发访问。

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

将对象设计为无状态对象:所谓无状态对象是指对象本身不存储状态信息(对象无 成员变量,或者成员变量也是无状态对象),这样多线程并发访问的时候就不会岀现状态 不一致,Java Web开发中常用的Servlet对象就设计为无状态对象,可以被应用服务器多 线程并发调用处理用户请求。而Web开发中常用的贫血模型对象都是些无状态对象。不 过从面向对象设计的角度看,无状态对象是一种不良设计。

使用局部对象:即在方法内部创建对象,这些对象会被每个进入该方法的线程创建, 除非程序有意识地将这些对象传递给其他线程,否则不会出现对象被多线程并发访问的情形。

并发访问资源时使用锁:即多线程访问资源的时候,通过锁的方式使多线程并发操作转为顺序操作,从而避免资源被并发修改。随着操作系统和编程语言的进步,出现各种轻量级锁,使得运行期线程获取锁和释放锁的代价都变得更小,但是锁导致线程同 步顺序执行,可能会对系统性能产生严重影响。

  1. 资源复用
    系统运行时,要尽量减少那些开销很大的系统资源的创建和销毁,比如数据库连接、 络通信连接、线程、复杂对象等。从编程角度,资源复用主要有两种模式:单例 (Singleton )和对象池(Object Pool )。

单例虽然是GoF经典设计模式中较多被诟病的一个模式,但由于目前Web开发中主 要使用贫血模式,从Service到Dao都是些无状态对象,无需重复创建,使用单例模式也 就自然而然了。事实上,Java开发常用的对象容器Spring默认构造的对象都是单例(需 要注意的是Spring的单例是Spring容器管理的单例,而不是用单例模式构造的单例)。

对象池模式通过复用对象实例,减少对象创建和资源消耗。对于数据库连接对象,
每次创建连接,数据库服务端都需要创建专门的资源以应对,因此频繁创建关闭数据库 连接,对数据库服务器而言是灾难性的,同时频繁创建关闭连接也需要花费较长的时间。因此在实践中,应用程序的数据库连接基本都使用连接池(Connection Pool)的方式。数据库连接对象创建好以后,将连接对象放入对象池容器中,应用程序要连接的时候,就 从对象池中获取一个空闲的连接使用,使用完毕再将该对象归还到对象池中即可,不需 要创建新的连接。

前面说过,对于每个Web请求(HTTP Request), Web应用服务器都需要创建一个独 立的线程去处理,这方面,应用服务器也采用线程池(Thread Pool)的方式。这些所谓的 连接池、线程池,本质上都是对象池,即连接、线程都是对象,池管理方式也基本相同。

  1. 数据结构
    早期关于程序的一个定义是,程序就是数据结构+算法,数据结构对于编程的重要性
    不言而喻。在不同场景中合理使用恰当的数据结构,灵活组合各种数据结构改善数据读 写和计算特性可极大优化程序的性能。

前面缓存部分已经描述过Hash表的基本原理,Hash表的读写性能在很大程度上依赖 HashCode的随机性,即HashCode越随机散列,Hash表的冲突就越少,读写性能也就越
高,目前比较好的字符串Hash散列算法有Time33算法,即对字符串逐字符迭代乘以33, 求得Hash值,算法原型为:

hash (i) = hash (i-1) * 33 + str [i]

Time33虽然可以较好地解决冲突,但是有可能相似字符串的HashCode也比较接近, 如字符串“AA”的HashCode是2210,字符串“AB”的HashCode是2211。这在某些应 用场景是不能接受的,这种情况下,一个可行的方案是对字符串取信息指纹,再对信息 指纹求HashCode,由于字符串微小的变化就可以引起信息指纹的巨大不同,因此可以获得较好的随机散列,如图4.16所示。

在JVM分代垃圾回收机制中,将应用程序可用的堆空间分为年轻代(Young Generation )和年老代(Old Generation ),又将年轻代分为 Eden 区(Eden Space )、From 区和To区,新建对象总是在Eden区中被创建,当Eden区空间已满,就触发一次YoungGC ( Garbage Collection,垃圾回收),将还被使用的对象复制到From区,这样整个Eden区都是未被使用的空间,可供继续创建对象,当Eden区再次用完,再触发一次Young GC, 将Eden区和From区还在被使用的对象复制到To区,下一次Young GC则是将Eden区和To区还被使用的对象复制到From区。因此,经过多次Young GC,某些对象会在From区和To区多次复制,如果超过某个阈值对象还未被释放,则将该对象复制到OldGenerationo如果0Id Generation空间也已用完,那么就会触发Full GC,即所谓的全量回收,全量回收会对系统性能产生较大影响,因此应根据系统业务特点和对象生命周期,合理设置Young Generation和Old Generation大小,尽量减少Full GCO事实上,某些Web 应用在整个运行期间可以做到从不进行Full GC。

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年4月7日
下一篇 2021年4月7日

相关推荐