代码重复、接口设计、缓存设计、生产就绪、异步处理常见问题

极客时间《Java业务开发常见错误100例》学习笔记
解决代码重复

  • 利用工厂模式 + 模板方法模式,消除 if…else 和重复代码,有多个并行的类实现相似的代码逻辑。可以考虑提取相同逻辑在父类中实现,差异逻辑通过抽象方法留给子类实现。使用类似的模板方法把相同的流程和逻辑固定成模板,保留差异的同时尽可能避免代码重复。同时,可以使用 Spring 的 IoC 特性注入相应的子类,来避免实例化子类时的大量 if…else 代码。
  • 使用硬编码的方式重复实现相同的数据处理算法。可以考虑把规则转换为自定义注解,作为元数据对类或对字段、方法进行描述,然后通过反射动态读取这些元数据、字段或调用方法,实现规则参数和规则定义的分离。也就是说,把变化的部分也就是规则的参数放入注解,规则的定义统一处理。
  • 业务代码中常见的 DO、DTO、VO 转换时大量字段的手动赋值,遇到有上百个属性的复杂类型,非常非常容易出错。不要手动进行赋值,考虑使用 Bean 映射工具进行。此外,还可以考虑采用单元测试对所有字段进行赋值正确性校验。
接口设计
  • 针对响应体的设计混乱、响应结果的不明确问题,服务端需要明确响应体每一个字段的意义,以一致的方式进行处理,并确保不透传下游服务的错误。
  • 针对接口版本控制问题,主要就是在开发接口之前明确版本控制策略,以及尽量使用统一的版本控制策略两方面。
  • 针对接口的处理方式,我认为需要明确要么是同步要么是异步。如果 API 列表中既有同步接口也有异步接口,那么最好直接在接口名中明确。
  • 一个良好的接口文档不仅仅需要说明如何调用接口,更需要补充接口使用的最佳实践以及接口的 SLA 标准。
缓存设计
  • 不能把诸如 Redis 的缓存数据库完全当作数据库来使用。不能假设缓存始终可靠,也不能假设没有过期的数据必然可以被读取到,需要处理好缓存的回源逻辑;而且要显式设置 Redis 的最大内存使用和数据淘汰策略,避免出现 OOM 的问题。
  • 缓存的性能比数据库好很多,需要考虑大量请求绕过缓存直击数据库造成数据库瘫痪的各种情况。对于缓存瞬时大面积失效的缓存雪崩问题,可以通过差异化缓存过期时间解决;对于高并发的缓存 Key 回源问题,可以使用锁来限制回源并发数;对于不存在的数据穿透缓存的问题,可以通过布隆过滤器进行数据存在性的预判,或在缓存中也设置一个值来解决。
  • 当数据库中的数据有更新的时候,需要考虑如何确保缓存中数据的一致性。“先更新数据库再删除缓存,访问的时候按需加载数据到缓存”的策略是最为妥当的,并且要尽量设置合适的缓存过期时间,这样即便真的发生不一致,也可以在缓存过期后数据得到及时同步。
生产就绪
  • 应用信息以及 Actuaor 提供的各种端点,可以帮查看应用内部情况,甚至对应用的一些参数进行调整;而指标监控,则有助于整体观察应用运行情况,帮助快速发现和定位问题。
  • 完整的应用监控体系一般由三个方面构成,包括日志 Logging、指标 Metrics 和追踪 Tracing
  • 追踪也叫做全链路追踪,比较有代表性的开源系统是SkyWalking和Pinpoint。一般而言,接入此类系统无需额外开发,使用其提供的 javaagent 来启动 Java 程序,就可以通过动态修改字节码实现各种组件的改写,以加入追踪代码(类似 AOP)。
异步处理
  • 要考虑异步流程丢消息或处理中断的情况,异步流程需要有备线进行补偿。比如全量补偿方式,即便异步流程彻底失效,通过补偿也能让业务继续进行。
  • 异步处理的时候需要考虑消息重复的可能性,处理逻辑需要实现幂等,防止重复处理。
  • 微服务场景下不同服务多个实例监听消息的情况,一般不同服务需要同时收到相同的消息,而相同服务的多个实例只需要轮询接收消息。我们需要确认 MQ 的消息路由配置是否满足需求,以避免消息重复或漏发问题。
  • 要注意始终无法处理的死信消息,可能会引发堵塞 MQ 的问题。一般在遇到消息处理失败的时候,可以设置一定的重试策略。如果重试还是不行,那可以把这个消息扔到专有的死信队列特别处理,不要让死信影响到正常消息的处理。
【代码重复、接口设计、缓存设计、生产就绪、异步处理常见问题】

    推荐阅读