如何挖掘项目中的亮点(多方向带案例)

前言

大数据量、高并发、复杂业务三不沾?没关系,没有什么项目是发掘不出来亮点的,除非你摆烂。其实我现在做项目,大多就是业务的增删改查,可能最近做看板相对应的业务计算或者数据量会存在一些难点,但是和传统的高大上技术基本不沾边。增删改查这东西,很难说亮点,可能我思路比较清晰,手相对快一些,但这些肯定说不上优势,拉开差距的一定是思考,深刻的思考,要做别人轻易做不到的事才行。

之前文章也提过,限于我司的技术架构,除了架构师外大多只能玩玩spring boot和中间件,像是k8s和devops这种不让玩,毕竟你做了让架构师做啥,是吧!当然硬要做,私下花时间自己做就行,只要证明比公司现有的方案好,谁还能拦得住你前进,比如我自己搞的EFK日志收集系统,就成功翻身上位,详情看我第二篇文章。

说了一大堆,核心思想就是让三不沾的读者朋友们不要慌,没法从底层下手,咱就往设计方案、性能调优和自定义组件上面靠。

项目亮点

亮点这个东西,说大可大说小可小,大可到系统设计、底层优化,小也可以是业务流程优化、工具类。我个人的话,比较喜欢玩技术,当然业务方向也会涉及举例,读者们放心,例子都会有的。项目亮点,项目在前,在挖掘时肯定要结合项目现状进行描述,不然也没啥说服力。干说一大堆技术点,不知道的还以为你在卖课,作为开发,玩的就是定制化开发,能力的体现就在于对定制化了解的有多深刻。实在找不出来,咱还可以创造亮点,不是吗?稍微过度设计下,只要不出错就没啥问题。

SQL优化

SQL优化这就马上开始打脸了,这要让我突然举个业务相关的例子还真不好说。但是正因为这是通用技能,所以是你说你把所有SQL都优化过也没人说啥。关键是说说你怎么优化的,这里链接一下我之前的文章从零开始的SQL修炼手册-实战篇,里面有详细的案例和优化步骤,这里就挑重点的说了。

===================================

SQL优化我们可以从最基础的SQL语句本身入手,并进一步从场景、表设计、数据库配置、架构四方面着手优化。

在优化之前,需要判断SQL语句是否是真的很慢。可能因为锁、刷新脏页、高并发、buffer pool设置过小导致SQL偶尔变慢,要判断也很简单,看看是不是频繁出现在慢SQL日志里就行了。

如果确定是SQL语句的问题,那么就可以按照我独创的九步优化套路操作了。

  • 一是规范字段名称,这一步是方便大家理解字段并清晰看到归属表。
  • 二是排除所有的select *语句,包含子查询以及JOIN语句,这一步是减少传输数据。
  • 三是小表驱动大表,这一步需要根据业务来判断是否调整。
  • 四是创建索引,尽量多的使用联合索引,不要创建过多的索引,注意索引失效的情况,比如隐式类型转换、索引列使用函数或参与运算、like左模糊匹配。
  • 五是利用覆盖索引优化。
  • 六是使用where和group by的having来减少查询的数据。
  • 七是通过explain去优化SQL,根据ref key extra等字段的信息来动态调整优化手段。
  • 八是子查询可以换成连接查询,减少临时表创建。
  • 九是调整where语句顺序,过滤数据多的条件放在前面。

场景优化例如深度分页、批量新增。表设计优化例如冷热数据分离、增加冗余字段或中间表。配置优化例如数据库的连接数,buffer pool优化。架构优化就是读写分离、分库分表。

===================================

一般来说这么一套组合拳下去又快又准,由点及面,知识面肯定是够了,而且也有一定的深度。SQL的优化这块重点关注索引,了解下索引优化成本,简单来说就是优化器是否选择使用非主键索引,是要考虑当前索引命中数据在全部数据的大致占比大小,占比小才走索引,否则全表查询。联合索引和覆盖索引需要清楚,索引下推等优化可以了解。

工具组件

再来说点大众的,组件自然是spring boot starter,工具则涉及方方面面。和业务强关联的没点技术性的就不用说了,适用性太低,多说点技术相关的。可以从redis、加密解密等方向入手。Spring Boot Starter开发指北(案例+代码地址)

===================================

我在工作的时候自己整合了一些工具类和配置项通过spring boot starter的方式集成到项目中。比如加密解密工具,我是整合了jasypt(Java Simplified Encryption,缩写词不要纠结读音,念字母即可)作为加密框架,默认使用PBE加密算法,PBE加密算法实际上是对已知算法的组合,比如常用的MD5和DES,在实际加密时会随机使用一种组合中的加密算法进行加密,密文由前八位盐值和后八位真实密文组成,该框架相比传统的加密方式更加简单易用,而且支持配置文件加密。我在项目使用Mybatis作为ORM框架,实现了Mybatis的扩展接口typehandle,对入参和出参拦截后进行加密解密,同时针对。

redis我是引入了redisson框架,封装了大量易用API,并且提供了分布式锁等工具。在此基础上我是封装了一些常用工具,基础思想是利用LUA脚本将多个命令封装在一起,好处有两个,分别是原子性操作以及汇总在一起的命令一次性发送减少了多次发送的网络开销。

  • 发号器就是一个获取递增值的操作,比较简单。
  • 幂等性校验器实际上就是固定时间窗口算法的应用,一段时间内允许固定请求数量进入,多余的请求拒绝或者排队。我这里是做成了注解,开发者可以通过手动输入KEY值或者不填,不填的话我是在切面收集方法入参,为了避免入参过长,最后的KEY值是使用了方法名+入参的MD5值。
  • 限流器则是使用了经典的漏斗算法,限制一个最大速率,它和时间窗口的区别就在于,当前调用完毕后要返还占用的次数,会有一个动态的加减,而不是单纯累加直到最大值。

===================================

平心而论,工具这一块可聊的点真的很少,JAVA太卷了,轮子太多了,比如大名鼎鼎的HuTool。以上都是一些比较常见的小东西,实在没得说了你再说这些。因为这些东西说白了,也是应用别人的轮子,只是做了一个业务封装而已,最关键的要结合业务和你的思考有自己的沉淀。redis这块我引入了LUA脚本作为亮点,毕竟redisson这样的牛逼框架内部都用的LUA脚本来实现合并操作的原子性。

业务模块设计

后端思想-如何设计一个操作和管理Excel的业务模块

和技术工具造轮子更多专注于技术不同,业务模块设计的重点在于设计,这一块我们会同时站在架构师和项目经理的角度去思考,去做一些基于业务模块深度定制的技术选型和流程设计,技术上可能不会太深,这没关系,重点在于你对整个模块的统筹思考。

在与人交流的时候,上来就挖技术深度其实不是上策,很多时候技术一旦过深就会有种曲高和寡的感觉,要用简单易懂的语言体现出你的技术深度,对于大多八股高手并非易事,说的让人听不懂并不是一个良好的沟通方式,因此一个大家都能听懂而且能拿得出手的业务模块设计才是上上之选。

===================================

我的excel模块依托于门户网站,整合了主子站的excel文件,对用户和开发者的体验都做了优化。

对于用户侧的优化体现于,用户可以在任意子系统上传文件,然后在主站的文件列表看到文件的详细信息比如文件校验信息,文件状态,下载次数,还可以直接下载已上传的文件。

开发者部分则是最大限度的封装API,将原本几百行的代码优化为十几行,统一了代码风格和依赖版本。

整个excel模块从业务方向看可以分为导入和导出,根据场景可分为动态和普通,异步和同步。大致业务流是

  1. 用户子站页面导入或者导出
  2. 解析文件(导入)或者生成文件(导出)
  3. 上传到文件服务器获取文件id
  4. 子系统组装文件详情数据并传到主站
  5. 主站展示文件详情

在我设计完成整个excel模块业务流之后,我是用一个spring boot starter来封装了主站、子站和文件服务器的API交互,同时引入easy excel作为解析excel文件的核心框架。这一块封装的好处有两个,一是规范代码减少BUG,方便新老同事快速上手,二是方便我做整体设计,统一代码风格。接下来针对两个设计上的亮点做一下简要描述。

一是展示错误信息,我将错误分成四种。第一种是最基本的错误,也就是excel文件是否为空、格式是否有错、上传到服务器是否异常等准备过程中出现的基础错误。这些基础的错误必须要直接返回给页面展示给用户。第二种是easy excel解析excel文件时的解析错误,比如表头异常、格式转换错误,我是在监听器中内置了一个错误信息处理方法,并且提供给开发同事两种选择,一是抛出一段汇总错误信息,二是在每一行的数据前面生成一个错误信息。第三种错误是excel数据在业务处理时报的业务错误,比如业务校验不通过,这个处理方式和第二种类似。第四种错误就是整个流程中出现的任意异常,catch之后将错误信息抛给主站展示在文件列表。

二是做了easy excel的监听器优化,我将普通的监听器改造成了一个通用读取的监听器。并且内置了错误信息处理方法以及表头信息读取方法。除此之外对文件处理部分做了封装,与easy excel的导入导出API做了结合,比如前端传过来一个MultipartFile文件,只用调用我提供的API,即可完成格式转换、文件解析、流水号生成等复合功能。

===================================

以上就是一个标准的业务模块设计,选用的也是常见的excel导入导出场景,整个设计包含三个服务以及一个技术框架支持,分别是主站、子站、文件服务器以及easy excel。根据个人实际情况,可以砍掉主站或者文件服务器,做一个简版的。可以参照上面文章的看看详细的设计过程,有伪代码以及细节讲解。

性能优化

性能优化-如何爽玩多线程来开发

性能优化这一块绝对是走向大佬的敲门砖,能结合理论和实践讲一个好的案例出来无疑是大大的加分项,我直接叫你大佬。因为我个人对调优这块了解不透,比如服务器调优、中间件或者程序调优都是有一定的理论,但是实践比较少,动态调优对我来说还是比较困难。目前我对JVM、Kafka、ES有一定的调优经验,对性能优化的理论也有一定了解,但为了避免误人子弟,我还是从常见且相对简单的多线程说起。

===================================

我在项目中是大量的用到了多线程编码来提升运行效率。主要运用场景就是并行聚合处理数据,这里是用到了CompletableFuture.allOf()方法,将原本串行运行的业务逻辑改为并行处理,比如处理A逻辑开启线程1,处理B逻辑时开启线程2,等1234所有线程都执行完毕后再汇总数据进行下一步处理,这一块思路类似于CountDownLatch。应用相同的思路,我也可以修改for循环为并行操作,这一步借鉴parallelStream的分治法思想,将list切分成多个小list,然后开启对应数量的线程,最后还是使用CompletableFuture.allOf()方法聚合数据。同理,将Map切成List,也能修改为并行操作,提高循环效率。

除此之外,还有一些小技巧,比如List转Map用空间换时间破解双层循环。常见的场景有,外来数据ListA对比数据库数据ListB,根据唯一值对比,常规做法是A循环嵌套B循环,根据唯一值找匹配数据,这样就是双层循环。我们可以将ListB转换为Map,然后用唯一值做Key,那么只需要一层循环即可。

===================================

多线程这个相对说的比较简单,但是非常实用,而且大多数情况下,开发并不会或者说懒得应用这样的技巧。由此也可以引申一些知识点,比如线程池如何配置的,你就可以说大致分为两种情况。IO密集型的就是多开线程,小队列,因为IO不占用CPU。CPU密集型则是要少线程,避免频繁上下文切换,队列要大,避免高并发的时候任务丢失。

技术模块设计

Filebeat+Kafka+数据处理服务+Elasticsearch+Kibana+Skywalking日志收集系统

技术模块这么说呢,主要是和业务模块区分开,和常规的业务开发肯定是有区别的。一说这种,肯定能联想到常规的Seata分布式事务、Kafka消息队列、Prometheus监控、Redis等等中间件,当然做成这种肯定是大牛中的大牛了,咱不考虑这种情况。在技术深度暂时达不到那种高度的时候,学习国内手机厂商的做法,做一个方案整合商呗,本地化改造或者整合封装都可以,得有你自己的思考。

===================================

我是自己搭建了一套完整的日志收集系统用于收集团队内部的业务日志,服务于XX个项目,月均XX条日志XXGB数据,上线XX年稳定运行。整个日志收集系统的架构设计采用了经典的EFK架构,使用的技术栈有Filebeat+Kafka+Elasticsearch+Kibana+Skywalking,除此之外单独写了一个微服务用于数据处理。日志收集的流程是Filebeat负责采集Log4j2产生的log文件然后抛到Kafka,数据处理服务消费Kafka消息后批量插入到ES,日志展示用的kibana,链路追踪用的skywalking中间件支持

整个架构设计大致是迭代了三个版本。第一个版本是基于注解切面+Kafka+数据处理服务+ES,这个版本本来是配合公司原有的日志收集做一个行为日志的补充,结果后来我发现同事在使用的时候都是默认配置,不喜欢写注解参数,而且注解的方式耦合度太高,一旦变动改的地方太多。因此我在第一版的基础上砍掉了注解切面,直接用log4j2推送到Kafka,并且部署了kibana来展示日志,同时针对Kafka和ES进行了高并发的优化,第二版类似于传统的ELK架构。在第二版上线稳定运行半年后,我就开始想着升级一下,首先是引入了Filebeat做了一个采集的扩展,由此可以做一下Seata、MySQL之类中间件的日志采集,其次是加入了链路追踪,这一块调研了Zipkin和Skywalking等,最终是选择了Skywalking,这个之前接触过,就直接用了,和Log4j2的集成也很方便,而且是基于JavaAgent的无侵入探针技术,做组件比较方便。

优化从各个组件聊起吧。Filebeat本身做了一个环境隔离以及堆栈信息合并优化,同时配置文件调整了Kafka生产者的配置。要点是尽量往高并发的方向去调整,比如批量大小增加、发送时延拉长这样就能减少批量的次数,对应的Kafka缓冲区也要配合着调大,消息大小肯定要调大,部分堆栈信息会超过默认的1MB.ACK的话用1,不怕丢失用0。数据处理服务在Kafka的消费者配置上主要做了一个批量接收以及并发消费的优化。特别是注意手动提交偏移量,避免因为程序处理失误导致数据丢失。ES的优化主要是索引部分,动态索引肯定是必要的,我是按月建索引,然后会定时去删除一年以上的索引,新增肯定也是批量操作,索引还可以调整落盘策略为异步,时间从1s一次改成5s一次,提高新增效率。

===================================

整个日志收集系统,我是先描述了下目前的使用情况,然后聊了下技术栈以及整个日志收集的流程。接下来说了下,整个架构设计以及迭代过程,复盘了下迭代的好处。最后聊了聊整个模块优化的部分,从技术选型应用到性能优化都有涉及,还是比较完整的一次设计过程。

查询接口性能优化

查询接口性能优化实录,讲点新手也能用的

接口性能优化这个其实是回头来对上面性能优化部分的补充,用了一个更加详细的案例来讲解吧。多线程那个案例总的来说还是有些不够高大上,虽然确实是相当厉害的技巧,但是表达起来确实太难了,所以大家说的时候,还是用这个案例吧。

===================================

查询接口性能的优化算是一个比较常见的优化点,正好最近我在重构一个项目的过程中,有一个高频查询接口的优化,接下来我会通过这个案例来具体讲解我使用到的一些技巧。

首先说说接口情况,这是一个高频复杂查询接口,从页面上看,正式系统上会展示九十多个字段,总量不多在1W3左右,采用条件分页查询,默认10条每页,查询时间是15秒左右。通过代码梳理,有接近五百行代码和八个SQL语句,包含几十行的复杂SQL,由于我是不懂业务,直接被调过来做优化,所以还需要从头梳理业务逻辑。

面对这种情况,我有一套自己的接口性能优化方法论,首先我会梳理代码,将代码按照功能性分为数据源、计算、拼接三部分。数据源指通过SQL或者外部接口获取数据的代码块。计算和拼接顾名思义,将代码标记或者拆分好后,整个接口的业务逻辑就会变得十分清晰。

接下来我会去做代码剪枝,按照IDEA的提示以及对代码的理解干掉一些无用和重复的代码,同时重新定义能看懂的变量名,而不是abc123这种意义不明的。如果内层函数并没有其他引用,我会选择将简单的部分合并到主方法,避免乱跳,看着麻烦。除此之外,合并循环、抽取公共变量避免重复操作也是必要的剪枝。

以上代码准备工作完成后,还需要知道具体方法哪里需要优化,这里可以选择代码埋点计时,但是我个人更推荐使用XRebel、Skywalking之类的性能分析框架,能看的更加清晰,最后说说优化手段。

一是缓存,使用分布式缓存Redis或者本地缓存Caffeine,提前或者随查询将固定或者可预期改变时间的结果放入缓存,这是改动最小(项目大多都有Redis)也是最具性价比的优化手段。

二是SQL优化,通过减少传输数据、索引、优化SQL语句本身、优化表设计以及优化数据库来步步推进达到优化目的。(可以适当引申到上面SQL优化部分)

三是空间换时间的经典操作,List转Map。在查询接口中的主要优化目的是,减少数据库查询或者外部接口调用次数。通过批量接口,将所需数据一次性查出来,再转换成Map,Key是条件,Value是对应数据。这一步其实就是将大量的IO操作合并为了一次查询,对于IO密集型任务来说是史诗级的性能提升。

四是充分利用多线程,这里主要是对List转Map的一个补充,因为有些SQL不易拆分或者接口不支持批量,那么使用多线程会得到更好的优化。具体细分为三种手段,并行聚合处理数据、修改for循环为并行操作、修改Map遍历为并行操作。并行聚合处理数据主要运用CompletableFuture.allOf()方法,将原本串行的操作改为并行,来压缩多阶段计算的总时间。修改for循环为并行操作主要针对查询数据库或者调外部接口这种大量IO的场景。修改Map遍历为并行操作则是为了提升Map的遍历效率,和修改for循环同理。

以上就是我对查询接口性能优化的一套方法论,除此之外,还有些不常见的技巧。比如请求合并,其原理是在高并发时,将多个请求合并,按时或者按量统一执行后返回结果,这是一种逻辑上的批量改造。还是一个是优化复杂计算任务的,将计算任务分片发给集群中的机器,最后汇总计算结果返回即可。

===================================

数据库优化

数据库设计-MySQL设计小册 - 掘金 (juejin.cn)

口语化讲解数据库优化 - 掘金 (juejin.cn)

最近更新了一篇MySQL相关的核心知识点文章,所以想着把这块整理一下,做成一个亮点。为啥这块要做出亮点呢,到底亮在哪呢?最近问了一些同事一个问题,如何优化Sql,大部分只能说个索引,然后就没有然后了,我:???可能表设计能说点,毕竟网上类似规范也很多啊,比如阿里小册、早年的什么58数据库军规什么的。但是就感觉就是没有人说的很清楚,这个略显抽象嗷,直白一点就是知识点散碎,没有体系。既然挺多人都不知道,那么这就是一个亮点,简单的、属于你的亮点。

===================================

数据库优化,这个问题有点大,不过我自己总结了三大方向来进行优化,分别是减少数据量、空间换时间、更换架构。

减少数据量具体细分为四种方法。一是在表设计阶段为各个字段选择尽可能小的数据类型,同时压缩字段。比如一张单据表,按业务类型分为检索和非检索字段,那么非检索字段就是我们要压缩的对象。压缩具体可以有两种手段实现,分别是选择将所有字段转换为JSON存到一个字段中,或者选择冷热数据隔离,将非检索字段放到新表中,用单据ID关联。

减少数据的第二种方法是数据归档,如果不增加中间件的话,可以选择增加历史归档表。具体通过代码增加查询历史数据的接口,定时迁移部分主表数据到历史表即可。

第三种方法是生成中间表,中间表真的很常用,其核心思路就是通过定时调度任务,将复杂查询结果单独存到一张额外的物理表中。因为是汇总后的数据,所以相当于是对原有数据源全部数据的一个压缩。

第四种方法就是传统的分库分表,但是开发和运维成本略高了,应该避免使用这种方案,如果硬件足够的话,一步到位使用分布式数据库例如Tidb更好。

空间换时间在我看来,其实也算是有点堆硬件的意思,本质上是用额外的资源来提升性能。不过这个堆法还是有不同方式的,这里分为两种,暴力堆砌和引入中间件。

暴力堆砌细分为两种类型,一是单库上挖掘潜力,比如MySQL内部在buffer pool上进行增删改查,如果buffer pool找不到,就需要读磁盘。众所周知,磁盘肯定没有内存块啊,那么既然知道MySQL性能瓶颈在buffer pool,那么最简单粗暴的就是修改buffer pool大小,还有加内存。第二种是搞集群,做读写分离,将压力分摊到多个机器上,这就是加机器。

引入中间件最常见的就是Redis啦,用来做分布式缓存。缓存策略则一般使用旁路缓存,其思路分为查询和更新两种情况,查询先查缓存没有就查数据库返回并把数据存入缓存,更新则是先修改数据库,再删除缓存,这里如果考虑极端场景可以选择延时双删。缓存的应用场景主要是应对高并发读,同时一些长时间不会变动的数据比如配置信息也可以选择使用缓存。

最后的方向就是更换架构,选择一个符合自己业务场景的非关系型数据,比如Redis、HBase、MongoDb、ES、ClickHouse。有两种更换方式,一种是写在关系型数据库,读在非关系性数据库,另一种就是完全迁移到非关系性数据库。简单举一个经典日志场景,就很适合完全使用ES来存储,因为不需要用到关系型数据库的ACID特性。

===================================

根据我之前的文章再次润色了一把,简化了一些内容吧,部分有歧义的地方做了更改。整体更加丝滑,我自己写起来感觉挺顺,希望大家喜欢。

写在最后

亮点这块一开始我就想好了大致分为上面这几个模块开始写,但是写着写着我发现排除掉高大上的高并发、大流量,剩下的这些常规业务确实是比较难抓亮点。写出来的东西有时候我感觉都说服不了我自己,亮点这东西我感觉还是要结合实际情况来说,不然总有种空中楼阁的感觉,没啥底气。我发现网上确实也是很少有人像我这样细致的来讲亮点,百度必应看了好多页,还是讲思想的比较多,还有就是经典秒杀,哈哈。以上几个仅做参考吧,希望能对大家有所帮助,能发掘一些自己的亮点。更欢迎大家评论指路,聊聊自己的亮点,人多力量大。

来点碎碎念吧,最近在网上看相关文章,发现写这块的是真的少啊,难道是我搜索方式不对,求指教。这种结合实际的东西写起来真是头大,下一篇写项目难点更是头大。我自己的话,最近项目开发太紧张了,整个十月没有更新。自身的懒惰和高强度的加班让我身心俱疲,选题也憋了一段时间,目前剩下的选题有项目难点和如何写一篇优秀的开发文档。项目的话,还是做看板项目,数据量的话没有特别大,计算逻辑复杂但是业务逻辑不复杂,高并发不至于内部看板而已,经典三不沾,但还是在我的技巧下成功出产了两篇文章,哈哈。一篇设计,一篇优化,总的来说,这个项目本身很容易触发OOM,现阶段没做优化,我正愁着呢,但现在首要问题是快速迭代交付,敏捷开发嘛,你们都懂的。

PS(2023.2.21晚):虽然有所预料,但是至今仍有人看,确实让我惊讶不小,到现在已经91收藏了,有希望破百收藏了。最近一直写八股系列,但是也在准备接口优化和老项目重构的相关内容,到时候会贴到这篇文章下面,敬请期待!最后的最后,很高兴本文能给大家带来帮助,感谢大家的点赞评论收藏,谢谢!!!

PS(2023.2.23午):额,破100收藏了,不过这快2:1的收藏点赞比真是给我整懵了。掘金快点出收藏数展示吧,我现在总文章482点赞却有403收藏,太离谱啦。

PS(2023.3.4晚):达到120收藏了,就很快,在前几天更新了接口优化的亮点案例,接下来会再汇总追加更新数据库优化的亮点案例。项目难点一直难产中,预计会在下周或者更远的时间产出,我很急,但我也不是人肉打字机,好的文章需要沉淀。我想给大家带来真正有用的东西,诸君共勉!621赞,520收藏,520不错的数字,恋爱之路漫漫长,哎!

PS(2023.3.5晚):追加更新-数据库优化,一个很实用的知识点,希望大家工作的时候也要用上嗷。用不上也要会说喔,不然一问三不知或者只知道索引,那可太尴尬了,哈哈。

分类:
后端