电商订单经典案例分享

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

你可能感兴趣的:(电商订单经典案例分享)