基于以往项目经验中的设计、优化、重构等方案,加以提炼、总结形成一套系统通用优化策略。目的是为项目开发,提供一些通用的最佳实践。
- 以此为基础形成一套比较完成的方法论,辅助项目开发、选型、优化、重构、测试;提升项目的开发效率和质量。
- 针对常见问题提供一些解决方案和思路,起到抛砖引玉的效果;为大家拓展视野,形成自己的解决方案和最佳实践。
在项目实际推进过程中,不加控制的需求变更往往给项目带来沉重的负担和无法预料的风险。因此,对需求统一管控是必要的。
- 建立接口人制度,明确职责解决信息不对等,过程不可视等问题。
- 需求承接,需要产品、接口人、开发人员通过可行性方案讨论;并评估开发、测试等细化任务需要的时间,形成可实施的任务计划表 。全流程邮件沟通留底;
- 定期对需求进行跟踪、反馈和汇报;对重点需求增加反馈次数,增进和业务方的互动。
- 进行需求管控的宣贯培训,指导各个部门、人员正确理解和执行。
- 选择合适的时机引导团队进行复盘,对需求的管控进行优化和改进。
业务的不断增加,功能的快速迭代,必然会导致项目越来越臃肿。为了保证开发效率,减少沟通成本,有必要制定统一的开发规范;
项目管理
- teambition
后端
- 开发工具 idea; 常见常见插件: log support, lombok, maven hepler, VisualVM Launcher, CheckStyle, SonarLint, FindBugs, .ignore, MyBatis。参考: IntelliJ IDEA 简体中文专题教程 IntelliJ IDEA快捷键大全
- 版本管理 git, 客户端 sourcetree
- 项目文档 markdown, 编辑器 typora
- MySQL 客户端 Navicat for MySQL
- SSH 客户端 SecureCRT
- 数据库ER图 PowerDesigner
- 接口文档 Rap;
- 项目依赖管理 Maven
- 抓包工具 fiddler
- 编辑器 Emeditor
前端
- Visual Studio code
- Chrome;常用插件:Postman, React Developer Tools, Redux DevTools, Wappalyzer, WEB前端助手(FeHelper), 捕捉网页截图, ReRes
- Firefox
- 正则表达式在线测试
- 制定开发手册,可参考以下开发手册:阿里巴巴JAVA开发手册 58沈剑-数据库使用规范 唯品会Java开发手册
- git分支管理规范的最佳实践, 参考: git flow。
- 对常用的数据模型进行统一,减少沟通交流的成本。比如 订单,收货人信息, 发票, 服务费, 优惠,促销 等基础数据。
- 维护系统业务字段对照表,在开发、产品、业务方、各个团队之间达成的共识 ;使得业务系统的数据共享更加方便。
- 定义统一的工程结构; 公关类库,公关的基础功能,中间件, 统一接口层 等。
- 由自带的慢查询日志或者开源的慢查询系统定位到具体的出问题的SQL,然后使用explain、profile等工具来逐步调优,最后经过测试达到效果后上线。
- 通过读写分离调整对数据库的写操作,通过垂直拆分以及水平拆分(分库分表)来解决数据库端连接池瓶颈等问题。
- 通过冗余字段减少多表关联查询。
- 对线程池(ThreadPoolExecutor)进行扩展,增加监控,限流等功能。参考: Monitoring-Friendly ExecutorService
- 通过线程dump 查看线程执行的情况。
可以通过熟悉连接池的原理,以及具体的连接池监控数据,来不断调试出最终的连接池参数。
什么情况适合用缓存?考虑以下两种场景:
- 短时间内相同数据重复查询多次且数据更新不频繁,这个时候可以选择先从缓存查询,查询不到再从数据库加载并回设到缓存的方式。此种场景较适合用单机缓存。
- 高并发查询热点数据,后端数据库不堪重负,可以用缓存来扛。
选型考虑
- 如果数据量小,并且不会频繁地增长又清空(这会导致频繁地垃圾回收),那么可以选择本地缓存。具体的话,如果需要一些策略的支持(比如缓存满的逐出策略),可以考虑Ehcache;如不需要,可以考虑HashMap;如需要考虑多线程并发的场景,可以考虑ConcurentHashMap。
- 其他情况,可以考虑缓存服务。目前从资源的投入度、可运维性、是否能动态扩容以及配套设施来考虑,优先使用Redis。
用户并不关心或者用户不需要立即拿到这些事情的处理结果,这种情况就比较适合用异步的方式处理,这里的原则就是能异步就异步。
一种做法,是额外开辟线程,这里可以采用额外开辟一个线程或者使用线程池的做法,在IO线程(处理请求响应)之外的线程来处理相应的任务,在IO线程中让response先返回。
如果异步线程处理的任务设计的数据量非常巨大,那么可以引入阻塞队列BlockingQueue作进一步的优化。具体做法是让一批异步线程不断地往阻塞队列里扔数据,然后额外起一个处理线程,循环批量从队列里拿预设大小的一批数据,来进行批处理(比如发一个批量的远程服务请求),这样进一步提高了性能。
另一种做法,是使用消息队列(MQ)中间件服务,MQ天生就是异步的。参考: 02.mq异步消息处理
通过监控系统收集系统运行的指标数据(gc time, gc count, thread cpu, thread track, 内存变化, dump)等信息,进行调优。
常用的工具有:Jconsole,jProfile,VisualVM, arthas, vjstar, btrace,greys,async-profiler
常见优化策略:
- 如果发现高峰期CPU使用率与Load值偏大,这个时候可以观察一些JVM的thread count以及gc count(可能主要是young gc count),如果这两个值都比以往偏大(也可以和一个历史经验值作对比),基本上可以定位是young gc频率过高导致,这个时候可以通过适当增大young区大小或者占比的方式来解决。
- 如果发现关键接口响应时间很慢,可以结合gc time以及gc log中的stop the world的时间,看一下整个应用的stop the world的时间是不是比较多。如果是,可能需要减少总的gc time,具体可以从减小gc的次数和减小单次gc的时间这两个维度来考虑,一般来说,这两个因素是一对互斥因素,我们需要根据实际的监控数据来调整相应的参数(比如新生代与老生代比值、eden与survivor比值、MTT值、触发cms回收的old区比率阈值等)来达到一个最优值。
- 如果发生full gc或者old cms gc非常频繁,通常这种情况会诱发STW的时间相应加长,从而也会导致接口响应时间变慢。这种情况,大概率是出现了“内存泄露”,Java里的内存泄露指的是一些应该释放的对象没有被释放掉(还有引用拉着它)。那么这些对象是如何产生的呢?为啥不会释放呢?对应的代码是不是出问题了?问题的关键是搞明白这个,找到相应的代码,然后对症下药。所以问题的关键是转化成寻找这些对象。怎么找?综合使用jmap和MAT,基本就能定位到具体的代码。
系统扩容因在对系统当前性能指标了解基础之上来实施的。
- 收集流量、并发量、带宽、CPU,内存 ,磁盘,数据库,缓存等一系列指标。
- 对访问量,并发数,访问峰值和时长进行评估。根据实际的业务评估,通过以往的一些营销活动的数据进行预估。
- 对系统进行压测,获取 应用服务器, 缓存服务器, 数据库服务器,消息队列 能抗住的并发压力。
- 对系统增加 应用服务器, 缓存服务器, 数据库服务器,消息队列 进行扩容,注意保留一定的冗余。
- 结合java 的 Metrics 和 JMH 对系统进行度量。
- 通过 java 的 APM 工具对应用进行度量。