需求
现在有一些表,部分字段存在加密信息,需要解密后存到另一个数据库。
存在一些数据量超大的单表,服务器配置有限无法一次性加载进内存。
解密后的表被下游使用,解密效率上有要求。
加密的表数量可能增加,加密字段也可能增加,开发完后可手动维护一张配置表,自动解密配置的加密表和对应表字段。
实现目标
大数据量情况下服务使用正常
最大化利用资源提升解密速度
自动适配多表(无需根据数据源表结构变化进行额外编码开发)
问题拆解
先从资源利用出手
很明显,一条数据经过服务的生命周期分为三个耗时阶段(数据加载,数据解密,数据写出)
从单线程出发,一个线程加载数据时无法对其它已加载的数据进行解密,同理线程在数据解密时无法将已解密的数据写出。于是采用经典的生产-消费模型,将三个耗时阶段分为三个线程组模块,各个模块的线程处理完自己的任务直接向下游抛出,线程组模块之间通过java内置的线程安全的阻塞队列进行数据交互(阻塞队列是分段锁设计,模块之间的交互依旧是并行的)。
接下来看单表超大数据量会造成什么情况(全量加载进内存会导致内存溢出的那种),假设数据加载线程组模块各自线程分配的任务单位是表,总会有线程拿到单表超大数据量的任务导致服务的直接崩溃,所以数据加载的任务单位应该是表分片,但是配置的表需求方无法提供主键,根据什么分片?postgre数据库有内置字段ctid,在离线场景数据不变化情况下每条数据都有唯一的ctid且恰好数据在块上分布均匀,所以可以根据ctid进行分片而且是从连续的磁盘地址上拿到数据。
看似资源利用和内存溢出情况已经解决了,实际上内置的阻塞队列默认是无界的,也就是说三个线程组模块如果处理速率差异较大就会导致处理速度慢的模块和上级模块之间的阻塞队列超过服务器内存配置使服务崩溃,好在内置的阻塞队列有最大容量限制可以很好的避免此类问题。但是为了提高线程的使用率,需要调整线程组模块参数给耗时模块更多的线程,直到遇到服务器瓶颈(理想情况是服务器带宽打满,且有剩余内存、cpu资源)。
至于适配多表,解决方式就比较好处理了,通过jdbc拉取数据的时候,字段元数据与存储的业务数据索引是一致的,一行数据直接封装成一个map在线程组之间流转就好了,在需要解密的字段也只需要O1时间复杂度的get操作解密放回去就行了。至于java的内存回收机制,生产消费模式频繁的对象创建销毁,通过Object Pool技术,数据加载阶段的数据载体从pool中获取,数据写出阶段的销毁可以替换为pool回收,内存回收的开销基本就可以忽略了。
基础结构
扩展
表分片的几种方式
1.Postgre数据库天然有内置字段ctid,结构为(块号, 元组索引) (0,1)
第一个值(block_number):数据所在的数据块编号(PostgreSQL 中数据按固定大小的块存储,默认块大小为 8KB)。块号从 0 开始,按物理顺序递增。
第二个值(tuple_index):数据块内的元组索引(即行在块内的位置)。每个数据块可以存储多个行(元组),索引从 1 开始。
2.row_number()
3.主键分片