转转回收的持久层架构演进

news2024/10/7 11:32:40

1 前言

我们在大部分开发场景下,对持久层的建设基于单库单表其实就可以实现当前的产品需求。但是随着业务发展越来越久,数据量、请求量也在不断的增加,只是单库单表可能不足以支撑系统的稳定运行,本文主要给大家分享一下笔者在项目实际迭代过程中对持久层稳定性的建设过程。

2 项目简介

简单来讲就是用户在一些活动场景下获取优惠券信息,领取并绑定到关系表里,后续用户去售卖一些商品的时候可以从领取的优惠券列表里选择一个合适的优惠券来使用。

3 面临的问题

3.1 数据越来越多

项目初期,单表完全可以hold住系统的稳定运行,但是由于优惠券的发放门槛特别低,导致优惠券的数量随着业务的发展激增,用户领券的关系表数量也越来越多,为了避免以后单表数据量过大带来的不必要的麻烦,我们对绑定关系表进行分表处理。

image

3.1.1 技术选型

目前市面上对于分库分表的方案大体分为三类:

1.基于JDBC进行代理:该方案不需要运维等人员的介入,技术内部即可进行开发优化。
image

2.基于数据库进行代理:该方案需要DBA或者运维的介入,维护起来不方便。
image

3.TiDB数据库:支持无限的水平扩展,具备强一致性和高可用性,编码层面的使用跟MYSQL无异。

最终选型
以上三种方案,笔者这边最终选择了基于JDBC进行代理,因为这种方案可以纯内部进行消化,不需要外部部门介入,对于开发成本、时间周期来讲都是比较容易弹性调整的,后续有改造也不需要外部介入。

至于框架的选择选择了ShardingJDBC,原因以下几点:
1.社区活跃,遇到问题可以快速收到反馈。
2.框架经过多年演进,已经是很稳定且成熟的产品。
3.公司内部应用广泛,可以协助共建。

分库分表如何设计?
分库分表扩容涉及到重新hash分片的问题,极其麻烦,所以最好一步到位,短期内不进行扩容操作。
我们基于数据当前的增长速度,简单计算下未来十年可能带来的数据量,计算出8库8表即可满足该场景。
查询场景都是基于用户维度,所以拿uid作为分片键即可。

增长速度远超预期怎么办?
及时增长速度远超预期也不打算进行扩容操作,因为成本过高。优惠券过期时间很短,用户在优惠券过期一定时间后就可以考虑将优惠券进行归档操作,这样即可保证数据量稳定在我们预期之内。

为什么不用TiDB?
由于笔者对TiDB了解不深,考虑到遇到问题不易快速定位、解决,且该表对于业务流程至关重要,所以暂不考虑使用TiDB来存储。

3.1.2 数据迁移流程

迁移流程大体如下:
1 延迟双写
我们先插入或修改旧表数据,成功之后再去写入或修改新表,然后发送一个延迟消息,消息触达之后进行新老数据核对,如果数据存在异常则进行修正,令其保持一致。

2 数据清洗
设置一个时间节点,将该时间点前的主键id全部跑出来,然后在脚本任务里,实时去查询该主键id对应的最新数据,写入到新表中。

3 异步纠错
迁移后的一定时间内,查询的时候对新老数据进行校验,如有不一致数据进行异步修复。

具体流程如图:
image

3.2 查询越来越复杂

3.2.1 初期方案

优惠券由于查询条件比较复杂(涉及到数组查询、模糊查询),且随着业务发展不断追加新的查询条件,导致不太适合每个查询条件作为单独的字段存储,故而放到了一个json里统一维护,但是这种存储方式查询的时候就无法直接利用mysql进行过滤。
例如:
小明想查询一个条件为:iPhone13非全新机、价格满1000元可用、以旧换新场景下、邮寄售卖可用的优惠券。
最初数据量不多的时候直接把配置表全部拿出来机型内存过滤,拿着满足条件的配置id去绑定关系表里进行查找。
image

3.2.2 临时改进方案

随着产品不断创建优惠券进行精细化投放,热门机型都会有对应的优惠券,库里的券大概有几百条。这样每次都要从库里全量拉出几百条进行处理的话显然99.9%的数据都是不必要的,因为用户只需要一张券,所以考虑成本最小的临时改进方案就是将优惠券放到内存中进行缓存,通过内存过滤减少每个请求过来造成的不必要的额外查询,降低gc频率。
这里借鉴了一些中间件同步缓存数据的方案,进行推拉结合的方式,一方面实时广播推送保证时效性,另一方面定时去拉数据来进行兜底处理。
image

但是本方案也不是长久之计,随着券的不断创建,内存中过滤的id可能会命中的特别多,这样查询的时候性能也会很糟糕,所以在时间充裕的时候考虑介入其他更适合的中间件,虽然成本高,但是能从根本上是解决问题。

image

3.2.3 接入ElasticSearch中间件

通过调研发现公司内部比较适合的查询中间件只有ElasticSearch,市面上也可能有其他适合的中间件,但还需要考虑额外的搭建、运维维护的成本,使用ElasticSearch就足够解决该问题。
这里实际接入流程不做多赘述,有兴趣的可以参考相关的文章。

不过使用ElasticSearch也有一个缺点,就是数据写入到查询存在一定的延迟,并且我们这边有的场景还对时效性要求很高,例如:系统在请求的开始阶段给用户发一张券,用户拿到后还会再去获取最优券,这张券直接查可能会获取不到。
原来的兼容方案是写入成功后业务内部把id带到上下文在内存中进行过滤,这样需要兼容的地方很多,且每个场景都要单独处理。

那我是如何解决的?
我这边通过Redis+ElasticSearch 联动查询来保证时效性,在写入成功之后将配置id同步保存到Redis的zset结构中,设置个10s的过期时间。
image
当有查询过来的时候,同时查询ElasticSearch与redis中的数据,然后合并过滤获取出最合适的券。
image

一些性能优化手段:
1.查询只返回需要的字段信息。
2.定义索引的时候使用合适的字段。
3.限制数据总量,根据实际场景做数据归档。
4.减少索引范围,强制根据uid进行分片路由。

image

3.3 请求量越来越大

3.3.1 读写分离

随着业务qps越来越高,每逢大促写入、查询的流量都会激增,所以经常收到关于主库流量太高的数据库告警,为了应对各种带来的尖刺流量,保证主库的稳定,进行了读写分离,减缓主库写入的压力。

主从延迟怎么解决?
有一种最简单粗暴的方案,单独提供主库的查询接口,但是这种对于调用方改造成本极大, 况且提供了主库接口之后可能很多人都不会去再使用从库了,从而无法达到读写分离的效果。

Object getByInfoFromMater(Long id);

理想中的方案
我这边调研了下是否有中间件能帮我实现主从选取的能力,即在主从同步成功之后才进行从库的读取,否则都是读取主库。

优点:服务方无感知
缺点:可能对性能造成影响

image
不过没找到这种中间件,所以我这边针对于这种方案用redis做了个一个简化版:

image

通过定义注解来控制是否执行该组件:
设置了写入注解的方法:内部全部使用主库进行读操作,保证一致性,且设置2s左右过期时间的TAG。
设置读注解的方法:内部判断TAG是否存在,存在则走主库,否则从库。

这种方案也会带来负面影响:
带有注解的方法都要查询一次redis,耗时会增高, 且如果2s内主从同步失败,还是会存在查询不一致的情况,当然考虑实际场景,这种概率微乎其微,我们业务是可以接受的。

4 总结

在从0到1做一个项目的时候,没必要过度设计,应该快速上线,保证系统正常运行即可。
项目初期可以先遇到问题再去解决问题,但是项目具备一定的流量之后,需要提前发现项目痛点并规划如何解决,否则等到真正遇到问题,再去解决可能已经来不及了,留给我们解决的时间已经不多了。

以上都是笔者在实际工作中的总结、归纳,各位如果有更好的方案或是不同的见解,欢迎评论区留言,共同讨论、进步。


关于作者

王锐刚,转转线上回收业务后端开发工程师

转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。
关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1883357.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mac安装达梦数据库

参考:mac安装达梦数据库​​​​​​ 实践如下: 1、下载达梦Docker镜像文件 同参考链接 2、导入镜像 镜像可以随便放在某个目录,相当于安装包,导入后就没有作用了。 查找达梦镜像名称:dm8_20240613_rev229704_x86…

EHS管理系统避坑指南!这几个关键点需要注意!

在企业管理中,环境、健康和安全(EHS)占据着举足轻重的地位,而EHS管理系统则是推动EHS管理走向高效与规范的核心工具。因此,选择一个与企业需求相契合的EHS管理系统,对于维护员工健康、保护环境安全以及提升…

C#——Property属性详情

属性 属性(Property)是类(class)、结构体(structure)和接口(interface)的成员,类或结构体中的成员变量称为字段,属性是字段的扩展,使用访问器&am…

算法实验2.2、2.3

2.2主要内容 比较快速排序&#xff0c;归并排序以及堆排序算法的时间效率。了解影响算法执行时间的 主要因素以及如何降低算法的执行时间。 #include<iostream> using namespace std; #include<stdio.h> #include<malloc.h> #include<stdlib.h> #inc…

Gradle学习-4 创建二进制插件工程

二进制插件工程创建有两种方式&#xff1a; 创建独立的工程&#xff0c;调试的时候&#xff0c;需要手动发布成一个二进制插件jar包&#xff0c;给其他工程里面引用&#xff0c;进行功能测试。这种方式是比较麻烦的。创建buildSrc子工程&#xff0c;它是一个大工程中的子工程&…

C语言之线程的学习

线程属于某一个进程 共同点&#xff1a;都能并发 线程共享变量&#xff0c;进程不共享。 多线程任务中&#xff0c;其中某一个线程调用了exit了&#xff0c;其他线程会跟着一起退出 如果是特定的线程就调用pthread_exit 失败返回的是错误号 下面也是

基于JSP技术的校园餐厅管理系统

开头语&#xff1a; 你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果您对校园餐厅管理系统感兴趣或有相关需求&#xff0c;欢迎随时联系我。我的联系方式在文末&#xff0c;期待与您交流&#xff01; 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#x…

Redis 中的通用命令(命令的返回值、复杂度、注意事项及操作演示)

Redis 中的通用命令(高频率操作) 文章目录 Redis 中的通用命令(高频率操作)Redis 的数据类型redis-cli 命令Keys 命令Exists 命令Expire 命令Ttl 命令Type命令 Redis 的数据类型 Redis 支持多种数据类型&#xff0c;整体来说&#xff0c;Redis 是一个键值对结构的&#xff0c;…

深入解析 androidx.databinding.Bindable 注解

在现代 Android 开发中&#xff0c;数据绑定 (Data Binding) 是一个非常重要的技术。它使得我们能够简化 UI 和业务逻辑之间的连接&#xff0c;从而提高代码的可读性和维护性。在数据绑定中&#xff0c;Bindable 注解是一个关键部分&#xff0c;它帮助我们实现双向数据绑定和自…

什么是脏读、幻读、不可重复读

数据库事务 数据库事务是指作为单个逻辑工作单元执行的一系列操作&#xff0c;这些操作要么全部成功执行&#xff0c;要么全部失败回滚&#xff0c;以保持数据库的一致性和完整性。在多线程或多用户同时操作时&#xff0c;难免会出现错乱与冲突&#xff0c;这就需要引入事务的…

零成本、高效率:免费可视化工具的魅力所在

在如今这个数据驱动的时代&#xff0c;免费可视化工具越来越受到人们的欢迎。这些工具不仅降低了数据分析的门槛&#xff0c;还为用户提供了强大的功能和极高的灵活性&#xff0c;使得各行各业的人们都能够轻松地利用数据做出明智的决策。首先&#xff0c;免费可视化工具的零成…

SpringBoot学习06-[SpringBoot与AOP、SpringBoot自定义starter]

SpringBoot自定义starter SpringBoot与AOP SpringBoot与AOP 使用AOP实现用户接口访问日志功能 添加AOP场景启动器 <!--添加AOP场景启动器--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</…

使用Pogo-DroneCAN CANHUB扩展板扩展飞控的CAN口

关键词&#xff1a;Ardupilot&#xff0c;Pixhawk&#xff0c;DroneCAN CANHUB扩展&#xff0c;扩展飞控CAN口 keywords&#xff1a;Ardupilot&#xff0c;Pixhawk&#xff0c;DroneCAN CANHUB Extend 摘要&#xff1a;使用Pogo-DroneCAN CANHUB扩展板扩展飞控CAN口&#xff…

数据挖掘常见算法(分类算法)

K&#xff0d;近邻算法&#xff08;KNN&#xff09; K-近邻分类法的基本思想&#xff1a;通过计算每个训练数据到待分类元组Zu的距离&#xff0c;取和待分类元组距离最近的K个训练数据&#xff0c;K个数据中哪个类别的训练数据占多数&#xff0c;则待分类元组Zu就属于哪个类别…

五分钟了解MQ消息集成

一、MQ消息集成的定义 MQ消息集成是通过消息中间件&#xff08;Message Queue&#xff09;实现的一种数据集成方式。它通过将数据发送到中间件中&#xff0c;再从中间件中接收数据&#xff0c;实现不同系统之间的数据交换。在MQ消息集成中&#xff0c;发送者和接收者之间不需要…

(六)Shader

Shader Shader(着色器)&#xff1a;一种运行在GPU端的类C语言GLSL&#xff0c;用于处理顶点数据以及决定像素片元最终着色。 Shader对三角形数据的处理&#xff0c;分为顶点处理和片元处理&#xff0c;分别称为顶点着色器(Vertex Shader)和片元着色器(Fragment Shader) GLSL …

Ansible 最佳实践:现代 IT 运维的利器

Ansible 最佳实践&#xff1a;现代 IT 运维的利器 Ansible 是一种开源的 IT 自动化工具&#xff0c;通过 SSH 协议实现远程节点和管理节点之间的通信&#xff0c;适用于配置管理、应用程序部署、任务自动化等多个场景。本文将介绍 Ansible 的基本架构、主要功能以及最佳实践&a…

最简单的Qt连接MYSQL的方法

1.报错原因 Qt在某个版本后不在提供编译好的mysql驱动&#xff0c;只在src中提供了驱动源码&#xff0c;使用需要自行编译链接&#xff0c;报错信息如下&#xff1a; QSqlDatabase: QMYSQL driver not loaded QSqlDatabase: available drivers: QSQLITE QODBC QODBC3 QPSQL Q…

BUT000增强字段BAPI结构激活出错(BUPA_CENTRAL_CI_CHANGE)

导语&#xff1a;BP主数据增强字段&#xff0c;需要使用BAPI&#xff1a;BUPA_CENTRAL_CI_CHANGE进行值写入&#xff0c;但是在SAP 2023以后的版本&#xff0c;激活会出错&#xff0c;原因是因为SAP的一个结构同时包含了BUS00_EEW以及BUS00_EEWX两个结构&#xff0c;导致结构字…

A股低开高走,近3000点,行情要启动了吗?

A股低开高走&#xff0c;近3000点&#xff0c;行情要启动了吗&#xff1f; 今天的A股&#xff0c;让人瞪目结舌了&#xff0c;你们知道是为什么吗&#xff1f;盘面上出现2个重要信号&#xff0c;一起来看看&#xff1a; 1、今天两市低开高走&#xff0c;银行板块护盘指数&…