1. 订单数据量规模大或查询统计足够复杂时,MySQL查询出现瓶颈
解决方案:
- 第一步:数据库表设计及索引优化(包含严格控制字段长度,表拆分以垂直拆分为主,字段冗余,索引覆盖及优化)
- 第二步:查询业务拆分,将分页数据查询与统计查询分开,降低统计查询对业务操作的影响(一般查询统计结果业务并不感冒)
- 第三步:数据规模大的情况下,查询缓慢主要体现在查询统计上,可针对性对系统比较慢的接口追加结果缓存,缓解慢SQL查询给数据库造成的压力
- 第四步:采用MySQL + NoSQL的架构方案,将MySQL中数据同步到其它介质,比如ES。利用其它搜索引擎辅助查询。实践方案:canal+kafka+Elasticsearch实现订单查询
2.策略数量扣减问题,并发情况下使用redis 分布式锁或mysql for update的方式性能较差
解决方案:
- 第一步:策略扣减采用Redis Incrby增量记录已使用数量。扣减前先使用Incrby增加需扣减数量,再与策略最大限量进行比较。符合则继续进行扣减流程处理,新增扣减流水记录,否则回滚,使用Redis decrBy自减数量
- 第二步:使用定时任务定时同步Redis中值到策略的实际已使用数量(根据实际业务控制频率,保证最终一致性),因程序是先自增再处理后续逻辑,必然存在异常情况下Redis记录的数值大于实际使用的数量情况
-
第三步:可以在数量同步MySQL时,若检查到Redis数值到达使用量上限,则对策略扣减流水记录进行合计,再与Redis中的值进行最终确认。一旦出现发现实际仍有余量,则恢复Redis值,将剩余数量返还。
image.png
3.kafka消费端如何防止消息丢失?
解决方案:
-
建立消息处理失败重试机制和预警通知机制
image.png
4.kafka消费消息如何保证有序?
解决方案:
- 方案1: KafkaConsumer 实例 + 多 worker 线程 + 一条线程对应一个阻塞队列消费线程模型(生产端:kafka可以通过partitionKey,将某类消息写入同一个partition,一个partition只能对应一个消费线程)
- 方案2:消息内容增加消息ID或者消息时间戳,每次消费消息时进行校验(可以用redis缓存最新id),滞后的消息直接丢弃。
5.客户端的抖动,快速操作,网络通信或者服务器响应慢,都可能造成服务器重复处理,最终生成重复数据。例如产生多条配置的策略
解决方案:
- 方案1:数据库表针对性建立唯一索引,由数据库充当最后一道防线(有些功能操作本身就缺乏唯一性规则,方案1无法达到预期效果)
- 方案2:对【MD5(方法路径+入参)】组合key值进行加锁(可以配合AOP注解实现项目快速整合),上锁失败则返回错误提示,内部执行方法仍需按业务规则进行校验
6.大数据量列表分页查询性能优化(limit工作原理就是先读取前面n条记录,然后抛弃前n条,读后面m条想要的,所以n越大,偏移量越大,性能就越差)
解决方案:
- 方案1:使用有索引列表或主键进行Order by 操作,记录上次返回的主键,用于下次查询时使用主键进行过滤;(适用于按页顺序查找场景)
- 方案2:利用子查询实现分页(适用于检查的查询SQL,支持跳页查询,性能比不上方案1)
SQL代码1:平均用时3.3秒 select * from oms_order order by order_code LIMIT 100000, 10
SQL代码2:平均用时0.2秒 SELECT * FROM oms_order WHERE order_code >= (SELECT order_code FROM ec_oms_order ORDER BY order_code LIMIT 100000 , 1) order by order_code LIMIT 10
- 终极方案:减小非必要的分页查询或限制可分页查询最大页码(建议控制在100页内)或只提供顺序分页(如手机app的滑动分页)
7.善于利用数据缓存,减少MySQL查询压力。除了Redis缓存,在同一事务中Mybatis一级缓存、ThreadLocal的运用减少数据库查询操作
解决方案:
- 方案1:使用Redis缓存,对不经常修改的数据进行缓存,如店铺、商品、字典等(如何保证数据库与redis的数据一致性?)
- 方案2:开启mybatis一级缓存,同一个事务中,相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能
- 方案3:使用ThreadLocal实现线程的数据共享,可以减少参数传递,提高代码整洁度(ThreadLocal数据使用后要清理,建议在方法使用前也进行一次清理)
8.多系统数据交互,如何处理分布式事务?
解决方案:
- 方案1:系统A引入本地消息表或者业务表增加状态字段记录系统B调用结果,系统A在自己本地一个事务里操作同时,插入一条数据到消息表(或更新状态),定时任务定时扫描消息表,触发系统B接口返回结果,更新消息表(或更新状态)。如果系统B处理失败则重试N次,最后不行则放弃并触发告警通知【前提:接收方接口保证幂等性,允许延迟,不依赖接收方响应结果立即进行其他业务操作】
- 方案2:可靠MQ。此方案与方案1类型,只是把定时任务和消息表换成MQ消费【难点:如何保证消息不丢失? 同样只适用于允许延迟,不依赖接收方响应结果立即进行其他业务操作的场景】
- 方案3:tcc-transaction 【教程】(缺点就是代码量翻倍,存在一定的侵入性)
- 方案4:Seata(AT 模式 :读未提交的隔离级别(全局锁来保证隔离性),旧项目改造风险极大 XA模式:利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式)
实际经验:99%的分布式接口调用,不要做分布式事务,直接就是监控(发邮件、发短信)、记录日志(一旦出错,完整的日志)、事后快速的定位、排查和出解决方案、修复数据。
9.项目中底层逻辑Jackson会将数字null值转换0(ms-common-web封装逻辑),但在某些场景下0具有特殊意义,需维持原有空值
解决方案:
-
自定义的序列化器JsonSerializer,对需要维持空值的字段追加自定义序列化,实现灵活控制,又不影响现有项目配置。
image.png
image.png
10.新建售后单后立马进行查询,大促期间出现提示找不到数据,经排查原因:数据库主从延迟
解决方案:
- 对于那种写了之后立马就要保证可以查到的场景,采用强制读主库的方式
11.API接口签名验证(防止篡改、重放攻击)
-
定义公共鉴权参数(所有接口必须带有的参数,参数支持存放在HTTP headers【优先级高】或者url链接参数上
image.png - 签名生成方式
①所有业务接口服务 请求方式均为 POST, 请求参数类型Content-Type=application/json
②sign 签名字符生成规则为 MD5( secrect +client + cuid+ format +time + version+ RequstBody(请求参数对象).toJSONString() + secrect).toLowerCase();【secrect秘钥】
具体实现:
- 客户端:按上述要求生成签名sign,并连同上述参数存入请求headers
-
服务端:验证流程
image.png





