生产级系统框架涉及的细节
幂等性(保证数据的一次性)
- 什么是幂等?
何为幂等,用户对同一个操作发起一次或者N此请求,结果都是一致 的。
什么地方需要幂等?
用户多次请求,重复点击提交按钮
网络异常,用于网络卡顿导致请求被多次重新发送
- 页面回退再次提交 - 程序上的重试机制
数据库幂等的分析 ?
新增 ,如果主键不是唯一的则不具备幂等性
读取 无论读取多少次请求 结果都是一样的 所以读 具备天然的幂等
更新 条件语句当中带计算型的更新则是非幂等的,反之则是幂等
删除 无论删除多少次 结果都是一样的 所以删除是天然幂等的
如何避免重估提交?
- 利用全局唯一ID 防止重复提交
- 利用全局唯一ID防止重复提交利用数据库主键特性可以解决重复提交
- 搭建一个生成全局唯一ID的服务,在提交请求的时候调用服务生成全局唯一ID,在提交的时候带上全局唯一ID号。将数据库的ID和数据信息进行映射,如果写入成功则直接提交,如果ID冲突则,则还是保留之前的记录不会重复生成。
- 利用"Toekn+redis"机制防止重复提交
- 提供一个发放token的服务,这个token 作为防重令牌
- 在服务调用的时候返回给前端并且token写入缓存并根据业务场景设置一定时效性
- 在前端点击保存的时候把token通过头部携带参数的方式封装进接口当中
- 在后端收到请求的时候先判断token是否在系统当中存在,如果存在 则代表这是第一次请求,会删除这个token。如果不存在表示这不是第一次请求,而是重复的请求,终止执行下面的业务逻辑。
- 在并发场景的情况下思考?是应该在执行业务逻辑前删除Token,还是再执行完成之后删除?
- 如果是前置删除 那么在用户多次请求到达下单情况下(极端的恶劣情况下)发现在redis当中已经删除了这个Token,这会导致重复提交
- 如果是操作完在删除 那么用户多次请求到达的情况下,第一个请求的删除token没有成功,之后的还会发现token,并且比对他们 这也会导致重复提交。
- 在应对并发情况下,对于Token的获取和比对删除需要原子性操作,在Redis中可以使用Lua脚本实现。
幂等性的解决方案不止这些,可以利用数据库的悲观锁和乐观锁、或者分布式锁等情况实现。
如何避免更新中的ABA问题
- 什么是ABA问题?
- 在订单系统当中,处于并发情况下,用户在和商家进行议价的过程当中,商品在商品页上将订单价格从原来的100元修改为80元。改完后,商家发现改错了,于是又重新修改到90元,订单系统的两次修改都成功了。但是由于网络异常的问题给,订单系统未及时将前一次修改为80元成功的结果返给订单页面,所以触发了重试逻辑,所以商品价格在被修改为90元后又被修改为80元。即最终的价格变成了80元。这就是一个典型的ABA问题。
- 如何解决ABA问题?
- 使用数据库的乐观锁解决,在订单系统表当中增加一个版本号,在每次更新的时候都判断版本号是否相等,如果不相等则不更新,如果相等则更新。
- 在订单页面获取订单的时候,同时将版本号作为参数返回给前端
- 在前端订单页面修改的时候,将订单关联信息,及版本号一起传到接口当中
- 订单系统修改接口在收到修改价格后首先判断当前传过来的版本号是否和数据库一致,如果不一致则拒绝更新该数据。
接口参数校验 增强服务的健壮性
对于接口参数校验可以采用SpringBoot的 validation进行参数校验
统一异常的处理
在日常开发单中有些程序会因为某些原因爆出异常,只要抛出异常就会中断程序的正常执行。可以采用SpringBoot的
@RestControllerAdvice
构建全局处理异常。
统一封装Response
如果没有统一的响应规范,则每个开发者都会采用自我习惯的方式,一个系统的接口会出现不同的返回格式。所以需要统一制定响应规范。
编写高质量的异步任务
在分布式系统当中或者微服务系统,大部分请求的和响应都是同步的,但是会有一些场景在业务主流程完成之后需要调用另外一个系统,或者需要做一些辅助的事情,如发短信、邮件等 。对于这类操作则需要发起异步任务
为什么要编写异步任务?
- 降低性能开销,为用户带来更好的体验
- 没有时序上的严格关系,即被调用方和调用方无须在执行顺序保持一致
- 不影响主流程执行,被调用方的结果不会对主流程产生任何影响
- 不涉及资源共享、或只是对共享资源进行读操作
- 不需要保证原子操作,或可以通过其他方式控制原子性
- I/O操作之类的好事操作
异步任务的好处?
- 可以给客户端立刻返回结果,不必等待后续流程结果
- 可以延迟返给调用发最终结果,在此期间可以进行其他的额外的逻辑处理
- 在异步任务执行的过程中,可以释放占用的线程资源避免阻塞
- 对于聚合性的计算结果,异步任务可以等到所有结果产生之后,再统一聚合,提高响应效率。
异步任务的开发 在Spring 当中可以通过Java的线程池来支持异步任务的开发, 在Spring中封装了一个ThreadPoolTaskExecutor类,在进行进程的异步任务时就可以使用该类操作。 (此处不做过多描述)
SpringBoot当中使用异步任务需要注意哪些?
调用方和被调用方不可以在一个Bean当中
如何复制线程上下文域
// https://stackoverflow.com/questions/23732089/how-to-enable-request-scope-in-async-task-executor public class ContextCopyingDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { //复制请求线程的上下文 RequestAttributes context = RequestContextHolder.currentRequestAttributes(); return () -> { try { //将请求线程上下文复制给新的线程(线程池的线程、被调用者线程) RequestContextHolder.setRequestAttributes(context); runnable.run(); } finally { //线程结束运行之后恢复上下文 RequestContextHolder.resetRequestAttributes(); } }; } } public class AppConfig { public static final String ASYNC_EXECUTOR_NAME = "asyncExecutor"; @Bean(name=ASYNC_EXECUTOR_NAME) public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //用于传递请求范围上下文 executor.setTaskDecorator(new ContextCopyingDecorator()); executor.setCorePoolSize(3); executor.setMaxPoolSize(5); executor.setQueueCapacity(100); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setThreadNamePrefix("AsyncThread-"); executor.initialize(); return executor; } }
DTO 与PO的互相转换
- 什么是DTO?什么是PO?
DTO数据传输对象,PO持久化对象。
- 实现DTO与PO的互相转换
- ModelMapper自动映射,ModelMapper是一个旨在简化对象映射的框架,它根据约定的处理对象之间的映射方式,为处理特定用例提供一个简单的、安全的可重构的API。
- ModelMapper的工作原理,在调用map方法的时候会先分析源类型和目标类型,以根据匹配策略和其他配置确定哪些属性需要隐士匹配,然后根据这些匹配映射数据。即使源对象和目标对象的属性不同,ModelMapper也会尽最大努力匹配。
- 类似ModelMapper的框架
- Dozeer
- Orika
- MapStruct
- JMapper
优雅的API设计应考虑哪些?
- API功能尽量单一
- API版本兼容
- 安全和幂等性
- 采用接口文档治理杂乱无章的接口如swagger
网友评论