原创

Java学习(优化)-目之所及

前言

整理记录下遇到的、学到的、想到的理论知识,欢迎评论指教哈!不必为不同而沮丧,碰撞的火花更加神奇美丽!

架构选择

选择项目架构首要的原则是避免过度设计,然后就是对于需求的一个短、中、长期的预测。既要避免造个火箭用来住几个人的情况,又要避免达到一定需求条件时需要推倒重来的情况。技术人员与产品的沟通、对业务背景的了解、对项目目标的了解以及对需求的发掘,至关重要。

单体应用

首先说明一点,用单体应用不一定就是差的,最适合的才是最好的。单体应用业务相对简单、功能也不应该复杂,调试、测试相对来说都比较方便。对于性能要求不高,一台服务可以搞定的事情,单体应用是首选,不用关注session共享、资源缓存等等跨服务的问题,一台机器能折腾起来,爽歪歪。

集群应用

请求量级上来,很多问题就都会暴露出来,单体应用到集群部署不仅仅是代码的堆积,还有会话认证策略、入库策略、缓存策略、高可用、数据一致性、控量等等,主要应对较高的请求并发需求、或过大的任务处理量,在量级上不是单体应用可承受的。集群搭建是应对大量请求的处理最快、也是效果最明显的解决方案。

分布式应用

分布式主要处理一条任务流程要经过多个不同的处理端点、类似于单体应用业务中的各个逻辑业务点独立成端,也就是业务越来越复杂,在进行业务功能模块拆分之后产生的情况,分布式一般的问题在于控制一致性事务:即一个必须的业务端失败,其他执行成功的业务端也要回滚。分布式最终一致性了解下。

分布式服务间通信目前的主要形式有:MQ、接口调用、RPC、长连接,传输内容格式:(Json、ProtoBuf)

拓展

系统优化方向有两点,一是在硬件上下功夫,把单机性能做到极致,也就是水平方向优化,另一种是在规模上做优化,一台机器不行就n台,也就是我们水平拓展,靠堆机器实现的优化效果是最直观的,成本投入也相对较小,另外还有就是在数据结构、算法上的优化也是很有必要的。可参考如下引用的图:

file

垂直拓展

提升单机处理能力。垂直扩展的方式又有两种:

  • 增强单机硬件性能,例如:增加CPU核数如32核,升级更好的网卡如万兆,升级更好的硬盘如SSD,扩充硬盘容量如2T,扩充系统内存如128G;
  • 提升单机架构性能,例如:使用Cache来减少IO次数,使用异步来增加单服务吞吐量,使用无锁数据结构来减少响应时间;

水平拓展

只要增加服务器数量,就能线性扩充系统性能。水平扩展对系统架构设计是有要求的,如何在架构各层进行可水平扩展的设计是本文重点讨论的内容

软性优化

不管垂直拓展还是水平拓展,都只是在处理的规模上下功夫,也算是算法思想中的以空间换时间的思路,对于开发人员来说,真正好的优化应该优先聚焦于高的效率,在可控的范围内下功夫,也就是在数据结构和算法上的优化,当然这是需要保证团队人力充足的情况下去研究实践。好的数据结构配合合适的算法可实现效率上的大幅度提升,开发者也不应该只聚焦于新技术的学习,也要关注数据结构和算法的应用。实际场景中没有好的数据结构和算法,只有适合的场景。

优化

以下记录一下优化点,提供些优化的思考方向,掺杂些个人的学习理解,不认同请评论区指教。

客户端

客户端主要优化在以下几点 :

  • 静态资源缓存,减少页面跳转带来的重复请求,也可指定时段内的缓存,如图片。
  • 静态资源请求尽量合并,较少网络请求次数。
  • 客户端防重复请求。

CDN

CDN加速,为请求提供最短的服务路径,如:将请求分流到不同地域的机房,该操作可降低网络链路时长。请求响应时间=网络链路传输时间 + 机器自身调度耗时(可忽略) + 业务处理耗时。

负载均衡

做集群后不可避免的就是要将大量请求分配到不同的机器上,分配规则可以按照负载均衡器(Nginx、SLB、硬件负载均衡等等)策略:随机、hash、加权、最小链接时间等等。规则都是人定的,要根据实际的场景选择最佳的方式。

同时也需要注意下负载均衡器的高可用性,谈到这里就知道要加一层,哎无穷无尽!前置也是可软可硬。

想要负载均衡前置不加一层也不是不行,我个人认为可以通过在客户端处理,在客户端请求的域名上下功夫,对外暴露的域名或ip可根据情况分为n组,在用户调用前先均衡的将域名分配给用户,负载均衡器和业务服务是多对对的关系,这样可以实现请求自带均衡,相对可减少负载均衡器的压力,(跨域问题需考虑在内)。

页面&静态资源

这里的优化点主要在于用户的体验性效果,也就是目前大部分公司都会用的策略:优化保证可见,资源请求顺序为:HTML、CSS、JS、异步。

安全防护

安全防护一定要做到位,cc攻击、ddos攻击、各种恶意攻击套路,哎!不说了,安全技术做不好的可以用第三方安全防护软件。
自己做的化一般方式就是服务里加黑名单(部分业务需要定时放开)、布隆过滤了解下、redis过滤、缓存防击穿、代码开发做好防注入、上传做好防护、XSS跨站脚本攻击的转义消毒、CSRF跨站请求伪造等等。前端代码混淆、HTTPS证书、数据传输加密(可自定义)等等有兴趣的可以自己了解下。

API接口

API接口优化注意点,可参照下图,有些操作可通过框架技术实现,避免二次开发。

  • 对外暴露接口尽量少、职责鲜明
  • 接口数据尽量简短、包含有效数据内容
  • 服务过载熔断、降级支持
  • 防恶意请求
  • 幂等性
  • 分级
  • 隔离

业务优化

  • 复杂业务模型拆分、微服务化
  • 尽量保持业务模块简单、尽量解耦
  • 降低业务处理路径。
  • 配合设计模式、数据结构、算法优化。
  • 业务代码要保持有效的注释及文档。

中间件MQ

中间件不止MQ一类,主要目的有:数据库中间件、远程过程调用中间件、面向消息中间件、基于对象请求代理、事务处理中间件,这里主要说服务间面向消息调用的MQ:可以有效的解耦,服务双方不需要关注彼此,只需要关注数据传输结构,也起到了一定的削峰作用。MQ也要注意高可用、持久化,不要一阵排山倒海被压垮、数据丢了不可恢复就岌岌了。

批量请求

批量操作不仅仅体现在业务中,在接口、中间件、网络协议、数据库、缓存中都带入有批量思想。可以这样理解批量:批量操作本身会产生定量的资源消耗,或时间、或空间,批量行为本身的资源消耗是固定的,但是量级上来之后那个定量的规模就会指数级增长,通过批量形式减少操作本身的消耗频率可以大大增加服务性能。该思想稳赚不赔。

预处理

预处理也是应用比较多的思想,比如:连接池、spring的容器管理(非懒加载形式)、秒杀系统的预缓存秒杀商品、抢购中的多服务的预分配等等,都是预处理思想的典型代表,
预处理的好处:提前转备好减少使用时的准备耗时(就是加速)。
预处理的弊端:核心类预处理会提前占用资源,秒杀、抢购、投放类不能完全做到绝对公平(这个世界哪有绝对的公平尼)。

池化处理

上面已经说了池化思想中有预处理思想,池化处理起到一个缓冲、提速的作用。其他优点待发掘,总感觉不止这些。

缓存

缓存的主要作用就是减少重复获取指定数据的成本:缓存一份数据,减少数据查询(击穿、雪崩了解下)、减少接口调用,缓存需要考虑的是数据失效、数据一致性的问题,缓存解决方案也主要是研究怎样保证数据一致性、怎样让缓存替代数据获取的耗时操作。预处理也是缓存。
常见缓存形式:预缓存、动态缓存、热点缓存

异步处理

并不是所有操作都要实时的项目,异步思想体现在,浏览器页面操作异步、服务间通信调用异步、包括现在有些语言、技术都会有异步思想。根据具体业务要求去做,能异步别同步。

锁的应用

先说锁

说道锁就是业内老生常谈的问题了。

锁:临界资源访问控制,数据一致性的基础保障。

往小了说,锁是为了解决线程间共同可操作的全局可见区域内数据导致的问题。
往大了说,锁是为了解决服务间访问操作全局可见资源数据导致的问题。

也就是在数据并发操作操作不能保证原子性的情况下的一个下下策,基本思想就是排队抢夺

  • 排队又叫悲观锁:就是认为这个不愉快的事件一定会发生,大家都老实点排队一个一个来。synchronized、Lock
  • 抢夺就是乐观锁:认为这个不愉快的事情不一定会发生,为了避免悲观锁加锁的性能影响使用乐观锁,也就是谁先抢到是谁的。需要注意乐观锁在超高并发情况下性能并不一定乐观,需要控制重试次数。基于CAS的实现。

还有谁?

为什么一定要用锁?有时候不用锁可以过得更开心。

某些场景下锁是可以不用的,对于无锁的单线程性能优势可以借鉴redis思想(当然redis快也不完全是因为单线程,跳表了解下),无锁的状态下单线程是可以很清爽的处理任务,大道至简。

对于高并发的场景怎么实现高效的无锁状态?答案就是前面提到的预处理、预分配,该种形式需要考虑预分配时机、统计对数、汇报等问题,只要能实现高性能,其他都是小问题。

  • 从小粒度可参考Java中的ThreadLocal(需要考虑ThreadLocal弱引用、内存泄漏问题),通过copy一组线程内局部变量来保证避免临界资源的操作不一致问题,ThreadLocal用好了爽歪歪。
  • 从大了说Vertx框架了解下,非spring的操作模式,只需要组织关注线程内的操作及业务控制,性能666。

数据源

走到了数据源,说明前面的缓存、预分配、安全防护都没顶住,就简单介绍下数据库优化点:

  • 单节点优化思想:索引、联合索引、冗余字段设计,就是要让数据库怎样块怎样来。
  • 主备模式:一主一备或多呗,这个备可能真的只是备,一台没了备机选一个顶上,没啥过分的用处
  • 主从模式:一主一从、多主多从,主管写改删、从管读,需要注意同步频率,数据一致性需要琢磨控制。
  • 分表:应对数据表量的增加,单表查询效率不会太好(单表记录千万以下最佳),将数据记录与多个表中、多个库的表中可以提升单表查询效率。
  • 分布式数据库:TiDB(HTAP、兼容MySQL协议、水平扩展、分布式事务)

分库分表需要注意的点:主键生成策略、分表策略、跨库事务、联合查询等,(mycat了解下,还有其他的中间件工具)

服务器

最近看到有个Undertow性能相对tomcat好一些,可以尝试下。

兜底方案

对应某些紧要的业务一定要设置兜底方案,一定要X3,比如秒杀超卖、控量控不住是很可怕的,这类问题不兜底。。。人生故事线就丰富了。

题外话

高效团队

开发规范

团队工作中,一定要有一致接受的开发规范,比如:代码风格、注释理念及风格、命名风格等等。有了规范,相互review代码会顺眼一点、工作冲突相对会少很多、不会有那种一会在平原一会在山区的感觉。开发规范最为重要,开发规范好,可以适当少些文档。

文档规范

文档一定要写

没有人能记住所有的业务细节点,也没有人能记住所有巧妙的设计,保留记录文档是一个公司的永恒资产,人是流水,但文档永存,你能记得自己一个月前的设计细节吗?半年呢?一年呢?不能就去记录下吧。(每次看到业务功能连个毛线的记录都没有,就想如是云云。。。)

从文档的记录上可以看出一个人的责任心,不写文档的人过分自信或者脑子记忆真的好,文档写的敷衍的人多少有些形式化的心态,文档写的好,即为了自己,也在为别人考虑,可以节省更多人的时间。

文档最好规范统一

文档记录是有规范要求的,比如:故事起因(需求描述)、故事经过(需求目标及最终决定效果)、故事结果(设计内容)、Q&A(问答环节),最好不要那种上来就是我加了一个表、我加了一个接口,为啥加的?一脸懵逼,做事要有始有终!

团队共识

共识是团队成员一起讨论、或长期磨合确定的一些工作中共同认识,比如:设计方面怎样好、沟通模式怎样合理、理解问题的思路、以及一些评判问题的标准,我可以不接受,但是我知道、我理解。

团队文化

团队文化的好与坏可以直接影响每个团队成员的发展,团队文化是整体做出来的,而不是说出来的,好的团队文化要团队成员一起努力,不是某个人的事,但是故事开始是要有个起点的。

团队沟通

团队沟通形式很重要,是员工主动汇报、还是领导主动询问、是线下直接对话还是线上邮件消息,每个公司在发展的过程中都会经历沟通的问题,从小团队到上规模的团队,沟通形式要灵活调整,沟通形式的好坏直接影响工作的效率、和员工心态。

高效工作

工具

开发工具、编辑工具等等何种辅助工作的工具,用的好工作效率还是很明显的。

业务

在我看来,团队内业务应该是透明的,而不应该是某几个可见、知晓的,人员流动频繁的公司往往会使用互备模式,你走了我有人顶,但是如果业务是透明的,对每个人都是友好的,花点时间在业务透明化上下点功夫,避免人员的不可替代性,是不是就开心很多,这样的前提就是团队具备良好的沟通氛围。

时间安排

有些程序员都会存在中断的困扰,开会、答疑、过用例、过设计、过总结、过计划。。。

氛围与共识

再次强调氛围与共识很重要。

拓展

忘了要表达什么了。。。以后想到再加

总结

都写完善了再写总结。

文章参考:阿凡卢

正文到此结束