系列文章目录
第一章 AOP后端控制接口调用次数第二章 前后端配合控制接口调用次数
文章目录
- 系列文章目录
- 背景
- 一、整体方案
- 二、注意点
- 1.规则编辑
- 2.批量插入数据
- 2.1 insert ignore into
- 2.2 replace into
- 2.3 insert into ... on duplicate key update
- 3.接口速度调优
- 3.1 批量更新
- 3.1.1 foreach
- 3.1.2 CASE WHEN
- 3.2 优化查询条件
- 3.3 拆分统计类型
- 总结
背景
在限制后端接口调用次数的项目,由于自己是前端小白,加上评估工作量的时候,时间紧急,方案设计好,也和公司老同事将方案过了一遍,但是没有目前公司没有完善的技术方案评审流程,所以当时自己定的是使用AOP技术手段,在需要限制的接口上面加上注解,然后通过AOP来限制,代码实现后,AOP技术可以实现限制次数的功能,但是前端交互太差,特别是图片预览的接口,前端是将url直接绑定在img空间上面,后端接口报错,前端也是一点效果没有,所以没有办法,只能将整体的方案改成,后端提供统计接口,前端先调用统计接口。统计接口返回成功,才进行正常的业务。
一、整体方案
如图上图所示:在原有流程的基础上,新增请求统计接口的流程。统计接口收到请求后,先请求redis缓存的规则数据,再查询统计数据,判断是否超限,若超限,返回固定错误码;否则,更新统计次数。前端封装一个统一的组件,先触发统计接口,统计接口返回成功之后,再执行真正的业务。
二、注意点
1.规则编辑
由于规则数据目前存储在db和redis,所以编辑规则的时候,需要考虑数据db和redis数据的一致性,采用延迟双删。
2.批量插入数据
2.1 insert ignore into
insert ignore into
insert ignore into会根据主键或者唯一索引进行判断,若数据库中没有该数据,就插入新的数据,和普通的insert into 一样。若数据库中有该数据,就忽略这条插入语句,不执行该插入操作。
但是这个目前的业务场景,明显不符合,所以也放弃该方案。
2.2 replace into
replace into
replace into会根据根据主键或者唯一索引进行判断。若数据库中存在该数据,则先删除该数据,然后插入新的数据,相当于先执行delete,再执行insert 。若不存在该数据,则直接插入新的数据,和普通的insert into一样。这个和目前的业务场景也不一样,放弃该方案。
2.3 insert into … on duplicate key update
insert into ... on duplicate key update
在insert into 语句末尾指定的 on duplicate key update,会根据主键或者唯一索引进行判断。若数据库有该条数据,则直接更新原生数据,相当于update。若数据库没有该条数据,跟普通的insert into 一样。
当前的业务场景和这个比较匹配,开始先选用的这个方案,大概的sql类似如下:
insert into `t_data`(`rule_id`, `object_type`, `object_id`, `object_source`, `start_time`, `end_time`, `num`, `operate_type`, `create_time`, `update_time`) values
(7, 1, 'b', 'c', STR_TO_DATE('2022-01-06 00:00:00','%Y-%m-%d %H:%i:%s'), STR_TO_DATE('2022-01-06 23:59:59','%Y-%m-%d %H:%i:%s'), 1, 1, now(), now()) ,
(7, 1, 'b', 'c', STR_TO_DATE('2022-01-06 00:00:00','%Y-%m-%d %H:%i:%s'), STR_TO_DATE('2022-01-06 23:59:59','%Y-%m-%d %H:%i:%s'), 2, 1, now(), now()) ,
(7, 1, 'b', 'c', STR_TO_DATE('2022-01-06 00:00:00','%Y-%m-%d %H:%i:%s'), STR_TO_DATE('2022-01-06 23:59:59','%Y-%m-%d %H:%i:%s'), 3, 1, now(), now()) ,
(7, 1, 'b', 'c', STR_TO_DATE('2022-01-06 00:00:00','%Y-%m-%d %H:%i:%s'), STR_TO_DATE('2022-01-06 23:59:59','%Y-%m-%d %H:%i:%s'), 4, 1, now(), now()) ,
(7, 1, 'b', 'c', STR_TO_DATE('2022-01-06 00:00:00','%Y-%m-%d %H:%i:%s'), STR_TO_DATE('2022-01-06 23:59:59','%Y-%m-%d %H:%i:%s'), 5, 1, now(), now()) ,
(7, 1, 'b', 'c', STR_TO_DATE('2022-01-06 00:00:00','%Y-%m-%d %H:%i:%s'), STR_TO_DATE('2022-01-06 23:59:59','%Y-%m-%d %H:%i:%s'), 6, 1, now(), now()) ,
(7, 1, 'b', 'c', STR_TO_DATE('2022-01-06 00:00:00','%Y-%m-%d %H:%i:%s'), STR_TO_DATE('2022-01-06 23:59:59','%Y-%m-%d %H:%i:%s'), 7, 1, now(), now()) ,
(7, 1, 'b', 'c', STR_TO_DATE('2022-01-06 00:00:00','%Y-%m-%d %H:%i:%s'), STR_TO_DATE('2022-01-06 23:59:59','%Y-%m-%d %H:%i:%s'), 8, 1, now(), now()) ,
(7, 1, 'b', 'c', STR_TO_DATE('2022-01-06 00:00:00','%Y-%m-%d %H:%i:%s'), STR_TO_DATE('2022-01-06 23:59:59','%Y-%m-%d %H:%i:%s'), 9, 1, now(), now())
on duplicate key update num = num + values(num);
但是在自测的过程中,jmeter测试经常出现死锁的情况,原因就是该语句插入时容易造成死锁,具体可以参考:面试官:MySQL 唯一索引为什么会导致死锁,此外,由于加了唯一索引,接口的请求速度太慢。考虑到当前的业务场景,一个是实际的业务场景没有多少并发的场景,但是接口的调用影响客户的交互,此外不需要对统计数据如此精确,所以最终直接使用insert into,另外在查询统计数据是,只获取id最小的一条数据。此外还有两个优化接口速度的地方见下面***3接口速度调优***
3.接口速度调优
3.1 批量更新
将原来代码中的多次update合并成一条update语句,提高效率。
3.1.1 foreach
此方法需要sql连接allowMultiQueries=true
<update id="updateBatch" parameterType="com.xxx.xxx">
<foreach collection="list" item="item" index="index" separator=";">
update xxx set
<if test="item.name != null">
name = #{item.name,jdbcType=VARCHAR}
</if>
where id = #{item.id,jdbcType=BIGINT}
</foreach>
</update>
3.1.2 CASE WHEN
选用此方案
<update id="updateBatch" parameterType="com.xxx.xxx">
update xxx set
<trim prefix="set" suffixOverrides=",">
<trim prefix="num = case" suffix="end,">
<foreach collection="list" item="item" index="index">
<if test="item.num != null">
when id = #{item.id, jdbcType=BIGINT} then #{item.num,jdbcType=INTEGER}
</if>
</foreach>
</trim>
</trim>
where id in (
<foreach collection="list" item="item" index="index" separator=",">
#{item.id, jdbcType=BIGINT}
</foreach>
)
</update>
3.2 优化查询条件
由于配置的规则时间维度可以按日、按月、按年来统计,所以统计数据的时候,需要记录统计的开始时间,统计的结束时间。然后在查询统计数据的时候,根据开始时间 <= 当前时间 <= 结束时间 来过滤,但是由于这样的范围查询容易产生间隙锁,所以将当前时间,换成统计的开始时间,统计的结束时间,来查询,避免范围查询造成的间隙锁。
3.3 拆分统计类型
当前业务诉求,有不同的操作类型,刚刚开始的时候,将不同的类型的统计次数放在不同的字段,查询统计的数据时,查询某个操作类型,会锁定其他的操作类型,所以新增操作类型字段,将不同操作类型拆分为不同记录,拆分锁的粒度。
总结
(1)需求评审的时候,需要提前多看看,多想几个What,How,进行并进行详细方案设计,特别是涉及自己不了解的领域,例如本次的前端内容,能够考虑到方案的方方面面,开发的时候直接根据方案开发即可。
(2)方案需要进行评审,最好把涉及的各方拉倒一起,一起过一下,明确下来。
(3)自己的主职工作是后端开发,虽然不给自己设置限制,但是还是要以后端为主,前端的开发,一方面自己也没有精力投入学习,另外这块也不是自己的主要点,完全是不讨好的工作。能够拒绝前端的开发工作,就尽量拒绝。
(4)要有自己的想法,有自己的原则,学会拒绝别人,不要做好好先生。