架构师训练营第九周学习总结
本周依然是性能优化的主题,围绕数据库原理,JVM底层架构和垃圾回收原理,Java的多线程开发代码优化以及一个秒杀系统实现的核心思路。
1.数据库原理
1.1预编译
数据库这块主要重新认识了一下对PreparedStatement这个预编译的理解。之前虽然也知道SQL执行过程是经过了连接器、分析器、优化器和执行器完成的。通过 PreparedStatement预编译,可以先让数据库执行前三步,最后提交变量执行第四步。这样预编译的SQL会进行缓存,对于后续重复执行这条SQL会有极大的性能提升,除此之外也可以有效防止SQL注入的风险。
1.2表设计
一般说到的关系型数据库,都是行级存储的,当我们查询数据是按行扫描,而我们也知道磁盘上检索尤其是机械硬盘,数据空间越大,检索的范围越大时间也就越久。所以当我们某张表有大容量字段时,例如Blob或Text,我们可以把一张表拆分成两张表,在有必要显示这些大字段时再显示,减少对行扫描时的数据量。
1.3索引设计
数据库的表主键本身就是聚集索引,而索引一般采用B+树的结构进行存储。而其他索引为非聚集索引,通过非聚集索引检索到的是聚集索引,然后再检索到数据,这个过程也叫回表。
由于索引需要占用额外的存储空间,所以并不是索引越多越好,我们一般会在常用的检索字段和排序字段上建立索引,选取散列比较大的字段(如果取值只有两个的则不适合建立索引,例如性别、逻辑删标志位等)。
1.4数据库事务
数据库的事务满足ACID,当事务出现回滚时通过事务日志实现。当事务之间出现对数据操作的冲突时,则使用锁来进行竞争,未竞争到锁的事务将被阻塞。
2.JVM底层架构和垃圾回收原理
2.1JVM内存模型
JVM内存模型是非常重要的知识点,当我们涉及到底层性能优化时,总是考虑在有限的资源做更多的事情,这里的资源就包括内存大小。JVM的运行时数据区域大致分为:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区、运行时常量池(属于方法区)和直接内存。当我们在开发过程中,需要较为清楚的认识到自己在代码运行时申请的对象占用的内存是否能正常释放。例如声明常量的引用或静态变量的引用就是在整个JVM进程中不会被回收的。如果我们希望告知垃圾回收器清理我们不需要的对象,我们可以手动把引用复制为null,这样在一个执行时间很长的方法里,可以尽早回收掉我们不需要的对象内存资源。而是否回收还需要等待垃圾回收器来定时执行或手动调用System.gc()(该方法也不能保证一定完成回收内存)
2.2垃圾回收器
JVM的垃圾回收器主要通过把创建的对像进行标记,被标记的对象会进入待回收对象。创建的对象首先会在Eden区,然后进行From区和To区的来回复制,经过一定的次数会进入Old区(老年代)。在Eden区的对象占满空间无法分配内存时,会触发Minor GC清理Eden区的内存。当Old区无法保持更多对象时,会触发Full GC,这时候整个JVM就会阻塞中断等待垃圾回收线程执行。所以我们应该尽量减少触发Full GC的次数,这个需要根据我们的应用场景来设置我们每个区域的内存比例。
3.Java的多线程开发代码优化
3.1线程池
由于线程的创建是比较大的开销,所以我们会需要保证线程尽可能复用,而线程池就是一个比较好的解决办法,通过设置线程池的数量,当需要线程执行时,从线程池中分配空闲的线程来执行,一方面可以减少线程创建以及销毁带来的开销,另一方面也可以控制线程的数量。我们知道线程栈本身也是需要分配私有内存空间的,大量存活的线程会消耗掉系统的资源。
3.2线程安全
当使用多线程开发时需要注意线程安全问题,当线程操作共享数据时,需要设定临界区,不同线程需要互斥进入,保证整个业务在数据的一致性操作。我们可以使用synchronized关键字来实现对代码块或方法的线程互斥执行。当某个线程进入临界区时,其他线程会阻塞等待,这样的场景会降低系统的性能。所以我们最好在合适的场景去使用多线程,使用合理的容器类执行多线程的代码,例如ConcurrentHashMap就用分段锁来减少锁竞争,同时也尽量使用异步化、无状态的的方式来执行我们的代码。
4.秒杀系统
事实上秒杀系统是一个典型的高并发场景,高并发场景解决的思路就是把流量分摊,这里可以采用横向扩展我们的业务支撑系统,同时把一些可以缓存的资源放到CDN中节省带宽,最重要的就是通过分段限流,减少核心业务执行的次数,保证应该执行核心业务的用户能够较为顺利的完成业务流程。
版权声明: 本文为 InfoQ 作者【Gosling】的原创文章。
原文链接:【http://xie.infoq.cn/article/6bcb78b2969b2452021c8c123】。文章转载请联系作者。
评论