前言
网站的业务逻辑都部署在应用服务器上,是主要优化的地方,优化的常见方式是缓存、集群、异步等。
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调优)
参考资料
《大型网站技术架构:核心原理与案例分析》