惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
P
Palo Alto Networks Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy & Cybersecurity Law Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
阮一峰的网络日志
阮一峰的网络日志
Recent Announcements
Recent Announcements
P
Proofpoint News Feed
H
Hacker News: Front Page
H
Help Net Security
云风的 BLOG
云风的 BLOG
H
Heimdal Security Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
L
Lohrmann on Cybersecurity
C
Check Point Blog
Google DeepMind News
Google DeepMind News
Forbes - Security
Forbes - Security
P
Proofpoint News Feed
Google DeepMind News
Google DeepMind News
I
InfoQ
The GitHub Blog
The GitHub Blog
The Cloudflare Blog
I
Intezer
L
LINUX DO - 最新话题
K
Kaspersky official blog
Attack and Defense Labs
Attack and Defense Labs
C
CERT Recently Published Vulnerability Notes
aimingoo的专栏
aimingoo的专栏
S
SegmentFault 最新的问题
NISL@THU
NISL@THU
人人都是产品经理
人人都是产品经理
雷峰网
雷峰网
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Vercel News
Vercel News
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Threatpost
Y
Y Combinator Blog
S
Security Affairs
Latest news
Latest news
T
Threat Research - Cisco Blogs
T
Tailwind CSS Blog
C
Cisco Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
P
Privacy International News Feed
Jina AI
Jina AI
Apple Machine Learning Research
Apple Machine Learning Research
Cisco Talos Blog
Cisco Talos Blog
T
Troy Hunt's Blog
S
Securelist
MongoDB | Blog
MongoDB | Blog

祈雨的笔记

安全多方计算MPC spark原理解析 kueue执行源码分析 spark on k8s执行源码分析 spark-operator源码解析 系统压测遇到的缓存击穿问题 我的世界PC与安卓联机 蚂蚁金服流量投放平台的AIG改造 G1大对象致Old区占用率高 日志打印导致接口响应率下跌分析 Groovy加载类导致OOM分析 ERROR日志打印导致CPU满载 记OceanBase死锁超时 应用发版期间服务响应超时 Ark Serverless初探 系统优化复盘一二三 The user specified as a definer does not exist Kong网关初探 API网关选型调研 CPU火焰图常用工具 配置中心选型调研 root操作Nginx导致用户组错误 基于Proxifier使用代理 FastJSON字段智能匹配踩坑 Nacos初探 记一次Nginx服务器CPU满荷载故障 limit不参与SQL成本计算致索引失效 Linux常用性能监控命令 golang低版本http2偶现400 hostname in certificate didn't match 常见对称加密原理以及应用 tcp_tw_recycle引起的TCP握手失败 记一次mysql执行DDL导致锁表 mysql磁盘占用查看 mysql对text字段update致磁盘增长 elasticsearch报错index read-only TIME_WAIT与Http的Keep-Alive 记一次TIME_WAIT导致连接数报警 记一次生产事故OOM问题排查 redis分布式锁RedissonLock的实现细节 webservice复杂加密签名(2)java调用 webservice复杂加密签名(1)SoapUI mysql延时关联 利用中间人拦截实现APP内H5窜改 MySQL表字符集不同导致关联查询索引失效 通过SSH隧道远程办公 数据落盘方案 BeanDefinitionRegistryPostProcessor扩展 mysql空间索引 HTTPS攻击 spring循环依赖过程解析 elasticsearch性能优化 mysql IS NULL 使用索引 mysql字符集utf8mb4失效踩坑 常用加密算法 xml与javaBean转换 初探InnoDB MVCC源码实现 mysql索引原理 redis之list源码分析 redis之key过期源码分析 redis之string源码分析 redis之hash源码分析 线程池之ThreadPoolExecutor mysql数据页结构 Using temporary与Using filesort mysql回表致索引失效 springboot(28)HTTP连接池 定时任务之ScheduledThreadPoolExecutor elasticsearch常用script聚合 elasticsearch实现like查询 elasticsearch实现乐观锁 elasticsearch准实时原理 springboot(27)自定义缓存读写机制CachingConfigurerSupport optimizer tracing arthas常用命令 HTTP和HTTPS详解 redis集群选举机制 kafka消息重试 一点压力测试的经验 kafka架构概念 explain分析sql语句字段的解释 JVM问题分析处理手册 logstash过滤器(2)date logstash过滤器(3)dissect logstash编码器(1)json logstash编码器(2)multiline logstash表达式 logstash输入(1)通用选项 logstash输入(3)file logstash过滤器(1)通用选项 logstash输入(2)stdin logstash安装 记一次前端vConsole导致JSON序列化错误排查 解决多个异步操作嵌套问题 fastjson反序列化失败autoType is not support RTMP串流服务 POI自动调整列宽错误 Nginx+Lua实现动态黑名单 使用curl命令模拟POST和GET请求 ResponseEntity下载文件
基于券系统分库分表的思考
祈雨的笔记 · 2020-09-26 · via 祈雨的笔记

场景

公司线上环境券系统的用户券码表目前是单表,虽然知道业务增长很快,数据量应该已经很大了。但是由于需求排期很紧,sql查询也没有出现性能瓶颈,也就一直没有给该表安排上分库分表的架构优化。

直到上周某业务需求上线,需要给该表加索引,才发现该表已经增长到不得不进行分库分表的地步了。准备加索引时,习惯性看了一下表数据量,单表磁盘占用20G,总记录5000万条,吓得不敢加索引了,生怕mysql因为给这张表搞索引时宕机了。

但需求业务要上线,不得不硬着头皮给用户券码表加索引,结果果然在线翻车。索引变更语句执行后,由于mysql的Online DDL机制并没有导致数据库锁表,mysql正常处理该表的读写请求,仅仅从监控上可见CPU和IO飙升(下图23:55:00 ~ 00:10:00)。在索引变更执行15分钟后,突然mysql无响应,该mysql实例所有库所有表的读写请求全部无响应。执行命令show processlist查看mysql的线程发现当前所有线程的状态都是Killed状态,即mysql正在自动杀掉全部线程。很快,mysql恢复正常,但当前所有的连接会话全部中断(包括索引变更也中断了),从监控也可见00:10:00时刻mysql几乎进行了一次软重启。

或许再执行一次就成功了呢?然后就又执行了一次索引变更。15分钟后一模一样的软重启重现后,不再敢变更该表的索引了,直接放弃对应的业务需求上线,准备后续通过业务场景功能的阉割和妥协来避免该索引。

image

image

image

image

分库分表中间件选型

  • Atlas: 只能分表不能分库分实例
  • DRDS(现更名为PolarDB-X): 阿里商业服务,功能强大,就是贵
  • Mycat: 国产老牌中间件,但官网一言难尽,文档糟糕,社区上的评价褒贬不一
  • ShardingSphere: Apache顶级项目,官方文档自带中文且描述详细,社区活跃度高,好评如潮
  • TiDB: 配置要求太高,杀鸡用牛刀

对比多个分库分表中间件后,其实更倾向于阿里云的DRDS服务,因为曾经有用过该服务的经验,总体来说对该中间件的印象还是非常不错的,除了价格有点贵。

剔除DRDS后对比了其他几个中间件后,暂定选择Apache的ShardingSphere。一来是Apache的项目,无论是社区活跃度还是项目维护频率都无可挑剔,二是官方文档自带中文,且文档详细,不像Elasticsearch的文档,虽然提供了中文版文档,但是中文版文档却比最新英文文档相差了几年的版本了,还不如不提供。

而ShardingSphere下有ShardingSphere-JDBCShardingSphere-Proxy两个成熟的产品,其中前者为Java的加强驱动,限制了客户端语言为java;而后者是个代理中间件,无关客户端语言,因而ShardingSphere-Proxy更适合于我们项目中多语言架构的场景。

ShardingSphere-Proxy目前虽然支持分布式治理,自带配置中心支持和组成中心支持,但是中间件本身还是单点的。一旦节点宕机,则使用该分库分表中间件的所有应用全部不可用。因此为了解决ShardingSphere-Proxy单点的问题,需要引入一个四层反向代理中间件,并且路由规则为基于ip的哈希路由或者更均衡的基于TCP四元组信息的哈希路由,又或者开启TCP会话保持功能。会话保持是为了保证客户端的同一个事务会话连接请求始终路由到同一个ShardingSphere-Proxy节点,保证分布式事务不会因为路由节点切换而错误。

数据分片

在对用户券码表进行业务分析后,确定该表的主要业务场景为读写指定券码和读写指定用户的券码列表,因此最佳的数据分片方案是基于用户ID的数据分片方案。但是基于用户ID的数据分片可以适用于读写指定用户的券码列表,但是对于读写指定券码的场景不太适用。为了解决这个问题,可以针对券码的生成规则进行调整,在券码中隐藏用户ID,当读写指定券码时,先从券码中解析出用户ID,然后通过用户ID和券码两个来读写数据,从而解决数据分片的问题。

券码为字符串类型,规则生成方案如下:

  • 固定前缀: 用于区分自有券码、旧数据券码以及第三方导入券码
  • 时间戳: 基于某个时间点计算时间毫秒差。以券码作为主键,通过主键排序即为用户领券时间排序,从而减少查询排序的性能消耗
  • shardId: 基于用户ID计算出的数据分片值shardId。例如用户ID做哈希后基于1024取模,最终数据分库分表最多分1024个表
  • 随机数: 防止券码被猜中

业务调整

分析现有券系统的代码后,券系统的用户券码表的操作有以下:

  1. 查询指定券码记录
  2. 插入指定券码记录
  3. 更新指定券码的记录
  4. 查询指定用户的券码列表
  5. 更新指定券模板关联的所有券码的记录
  6. 查询指定券模板关联的所有券码的记录
  7. 查询即将过期的券码记录

上述操作中,基于用户ID做数据分片可以适用于前4种读写操作场景,但后3种的数据分片则不适用,会出现对所有分库分表广播的情况。但实际上,由于第5种操作触发频率极低,且每次触发只执行一个SQL语句,因此即使对所有分库分表进行广播的多余性能消耗也能接受。

第7种操作则需要对代码实现方式直接进行重构,不再通过扫描用户券码表来实现,而是新增一个待过期券码表,当用户领券时,如果券模板配置设置该券需要有即将过期提醒,则计算出该券的过期通知时间,将券码和通知时间添加到待过期券码表中。每日固定时间由定时任务扫描待过期券码表,从该表中获取当前需要进行即将过期提醒的券码,并每天凌晨物理删除掉已经通知过期提醒的待通知券码记录。

真正棘手的是第6种场景,具体分析该操作的业务场景,发现主要是用于给商户分析具体活动的用户参与情况,因此对于数据的实时性要求不高。可以通过同步工具将用户券码表的数据同步到数据分析的数据库或者自带数据分片的数据库例如Elasticsearch,以后对于数据的分析和报表全部基于同步后数据库。

导入券码支持

业务不断发展后,时不时会有商户询问这么个问题:能不能将商户自己的券码导入券系统,能不能发放商户自己的券码?

基于上述的分库分表规则,肯定是不能实现导入第三方券码的。因为导入的第三方券码无法基于用户做数据分片,因此如果需要实现该功能,则需要做一层映射关系,将第三方券码映射成自有券码,即需要一个第三方券码和自有券码的映射表。

分析导入第三方券码的业务,如下:

  • 导入第三方券码:商户创建券模板规则,然后将第三方券码导入券系统关联该券模板ID。并规定第三方券码的前缀不能与自有券码的前缀相同,以券码前缀来区分是否是第三方券码
  • 用户领券: 从第三方券码池中获取指定券模板关联的一个无主第三方券码,生成一个自有券码,将第三方券码和自有券码的映射关系添加到第三方券码与自有券码映射表中,再将自有券码记录添加到用户券码表
  • 查用户券列表: 用户券码表中新增一个冗余字段第三方券码,如果自有券码是由第三方券码映射生成的,则在映射券码添加到用户券码表时将原第三方券码值一并补充到该字段中。对前端显示券码时,优先显示第三方券码,非第三方券码才显示自有券码
  • 读写指定第三方券码: 通过第三方券码与自有券码映射表先查出该第三方券码映射的自有券码,再通过映射的自有券码去用户券码表读写券记录

第三方券码池表和第三方券码与自有券码映射表在数据量少时可以合并起来,只用一个表实现。毕竟需要导入第三方券码的商户并不多,数据量也不大。商户需要导入的第三方券码一般是商户和某渠道例如电影院之类进行合作,消费送电影票券码。如果需要导入的第三方券码数据量很大,那必然是个大商户,大概率也是使用商户自家的券系统而非使用我们的券系统了。

但是当数据量膨胀需要进行分库分表时,分析上述的第三方券码业务操作,则需要将第三方券码池表和第三方券码与自有券码映射表分割成两个表:

  1. 第三方券码池表(基于关联的券模板ID哈希取模分片)
  2. 第三方券码与自有券码映射表(第三方券码哈希取模分片)