应用服务器性能优化

前言

网站的业务逻辑都部署在应用服务器上,是主要优化的地方,优化的常见方式是缓存、集群、异步等。

1.分布式缓存

缓存的使用无处不在,ORM框架中的多级缓存,MVC框架中的模板缓存,浏览器缓存,几乎任何框架都提供了缓存功能。

网站性能优化第一定律:优先考虑使用缓存优化性能

1.1 缓存的基本原理

简单而言都是KV,即Key-Value形式,在这之上有Key-Key-Value形式(Redis的Sorted Set,TODO:Redis数据结构实现原理),用的最多还是KV。KV中的Value是二进制安全的。

Java中的hashCode方法可以得到hashcode,这里要说的知识点很多,equals和hashCode任一方法重写都需要重写,准确的说是必须,不然会影响集合类的使用。null对象也是有哈希值的,是0!只有HashTable直接使用hashCode的值,其余都会再扰动计算。只有HashMap允许key或者value为空情况出现,ConcurrentHashMap和淘汰的HashTable都不允许,因为多线程环境无法区分key是put时为null还是key不存在,而且null作为key很容易受到攻击。SynchronizedMap因为可以包装HashMap而可以支持,但是原理和HashTable一样,锁粒度太大导致性能都很差。(TODO:Java并发集合)

1.2 合理使用缓存

  • 频繁修改的数据不适用缓存:读写比小于2:1
  • 热点访问数据适合缓存:尽量保证缓存高频访问的数据
  • 数据不一致与脏读:设置合理的缓存更新策略,一般为缓存设置失效时间
  • 缓存可用性:保证缓存服务的稳定,通过分布式集群等
  • 缓存预热:针对热点数据缓存,比如利用LRU算法等需要较长时间,可以提前人工加载热点数据
  • 缓存穿透、缓存雪崩、缓存击穿:使用缓存的经典问题,见缓存使用

1.3 分布式缓存

分布式缓存指缓存部署在多个服务器组成的集群中,以集群方式提供缓存服务,其架构方式有两种。一种是以JBoss Cache为代表的需要更新同步的分布式缓存,一种是以Memcached为代表的不互相通信的分布式缓存(TODO:Memcached以后填坑)。

2. 异步操作

很多情况下使用异步操作可以改善网站性能,使用消息队列的常见作用就是削峰填谷,将部分操作异步避免前面业务的等待,同时要注意不要把所有后续业务的处理都放在单一的业务逻辑中。如注册后的发邮件就可以放入消息队列,后续处理交由邮件发送消费者。使用消息队列要注意重放攻击,对于哪些操作能放入消息队列,生产者处理失败如何处理要有规定。

任何可以晚点做的事情都应该晚点再做

3. 使用集群

在高并发场景下使用负载均衡技术,通过多台服务器为应用构建集群是最简单的方式。请求太多怎么办,上机器!单一的机器受各种限制,配合微服务使用应用集群能够有效处理并发。

4. 代码优化

代码写的烂再多的机器都不一定能救。不同代码的优化方式有别,诸如Java从Web容器,ORM框架选择,JDK本身的优化,JVM调优等。

4.1 多线程

多线程是基本的措施,但要考虑线程安全问题。使用多线程的原因主要是IO阻塞多CPU,当线程进行IO处理时会被阻塞,这时CPU会被释放可以调度其它线程。

网站的应用程序都是被Web服务器容器管理,不论时Web服务器容器管理的线程还是应用程序自己创建的线程,都不易过多,线程切换虽然比进程切换快,但是过多的线程会带来调度问题。通常启动的线程数:

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

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

多线程带来线程安全问题,常见的解决手段有,我会单独写一片effective java读后感(TODO:线程安全):

  • 对象设计为无状态:无状态对象肯定时线程安全的
  • 使用局部对象:被多线程访问的对象会出现可见性问题,因为工作内存中的对象是共享内存的拷贝
  • 并发访问资源使用锁:强一致性要加锁,并合理降低锁的粒度
  • 防止对象逸出:切勿将内部对象返回给外部

4.2 资源复用

资源复用常见的是单例模式和对象池。对象池有许多种类,如数据库连接池、线程池,如何设计一个线程池也是有趣的问题(TODO:线程池设计),要考虑核心线程池大小、最大大小、阻塞队列设计(有限无限)、拒绝策略、线程存活时间(长时间未使用要回收)。

4.3 数据结构

优化存储结构,合理使用Redis的存储类型,选择合适的MySQl引擎

4.4 垃圾回收

如Java中要对JVM调优和内存泄漏,这里经验不多,改日填坑(TODO:JVM调优)

参考资料

《大型网站技术架构:核心原理与案例分析》