高并发账户记录查询

问题描述

高并发账户记录查询在银行、互联网企业、通信企业中广泛存在。例如:网上银行、手机银行、电商个人账户查询、互联网游戏账户等等。这类查询有三个共同点:

1、  数据总量非常大。用户数量本身就非常多,再加上多年的账户数据,数据量可以达到几千万甚至上亿条。

2、  访问人数众多。几百万甚至上千万人访问,属于高并发查询。

3、  不能让用户等待。手机、网页要达到秒级响应,否则严重影响用户体验。

下面以某银行账户活期明细查询为例,给出这类问题的解决办法。

某银行共一亿个活期账户,每个账户平均每月有7条数据,每年数据总量84亿条。每条数据中的机构字段,还要关联分支机构表(几千条)记录。在性能上,要求单台服务器支持一千个以上的查询,响应时间不能超过1秒。

有序行存

活期明细数据随着时间增长非常快,一年就有84亿条。如果放到内存中,需要大量内存空间,硬件投入成本太高,所以要放到硬盘上存储。分支机构表只有几千条数据,可以放在内存中存储。

在硬盘上存储,要考虑是行存还是列存。列存数据分块压缩,能减少遍历数据量。但由于账户查询是随机的,整块读取会有额外解压计算。而且每次取数都针对整个分块,复杂度较高,性能不如行存。因此,这个场景要选择行存存储,如下图:

图1:行存和列存

具体的实现可以采用Java、C++、SPL等高级语言。这里我们以代码量最少的SPL语言为例讲解。

A
1 =connect("db").cursor(“select *   from detail order by id")
2 =file("detailR.ctx") 
3 =A2.create@r(ID,CORPID,AMT).append(B1)

代码示例1

A1:连接生产数据库,用游标读取活期存款数据,按照账户id排序。

A2:建立本地组表文件。

A3:建立组表,并从数据库游标读取活期存款明细数据,写入文件。

其中,A3中的@r选项,就是建立行存文件。一年84亿条数据都导出,时间会比较长。但是这是一次性的工作,后续就只需要追加增量数据即可。增量数据的追加方法,后面会有介绍。如果按照账户排序会对生产数据库造成较大压力,可以导出之后基于文件排序。排序使用SPL的sortx函数,具体用法参见函数参考。

利用索引

要利用索引提速,先要对明细文件建立索引。由于明细数据量大,建立的索引文件也会很大。很难全部加载到内存中。可以建立多级索引,如下图:

图2:多级缓存

还是以SPL为例,建立多级索引,只需要在“代码示例1”的基础上,增加一个网格即可:

A
1 =connect("db").cursor(“select *   from detail order by id")
2 =file("detailR.ctx") 
3 =A2.create@r(ID,CORPID,AMT).append(B1)
4 =A2.open().index(index_detailR_id;ID)

代码示例2

A4:对行存文件建立索引文件。

加载的索引级别越多,占用存储空间越大。同时,账户id的跨度变小,加载到内存中后,索引效果也会变好。查询时可以根据内存大小,尽可能加载更多级别的索引,可以有效提高查询速度。

在查询之前,系统初始化或者数据变动时,要预先加载多级索引,以SPL代码为例:

A B
1 if !ifv(detailR) =file("detailR.ctx").open().index@3(index_detailR_id)
2 =env(detailR,B1)

代码示例3

A1:判断全局变量中是否存在detailR,如果存在,表示已经加载了索引。

B1:如果全局变量中没有detailR,那么打开组表加载三级索引。@2或者@3表示加载2或者3级索引。

B2:detailR存入全局变量。

这段预先加载的初始化代码(代码示例3),可以保存成init.dfx,放入集算器主目录(main path),在节点机(unitServer)启动的时候会自动被调用,如下图:

图3:节点机自动调用初始化代码

由于我们提前准备好的活期数据是对账户id物理有序的,查询时根据索引找到账户id后,可以在硬盘连续读取,显著减少磁盘IO,有效提速,如下图:

图4:索引和有序行存

查询账户10100,先利用内存中预先加载的多级索引和索引文件,快速定位到活期明细数据文件中的位置,再连续读取到账户id有变化为止。因为数据是按照账户id物理有序的,所以文件的其他位置不可能再有10100账户的数据了。

利用索引查询的示例代码如下:

A
1 =detailR.icursor   (;ID=="10100",index_detailR_id).fetch()

代码示例4

A1:全程变量detailR已经缓存了三级索引,现在可以基于它利用索引,按照条件取出账户为10100的记录。

每个账户的数据量并不大,所以可以全部读入内存。

活期明细前端应用(网页或者APP),要通过集算器的JDBC驱动调用SPL代码查询。调用的时候,需要将账户id作为参数传给SPL程序。例如:定义一个网格参数countid,传入账户id为10100。A1中的代码就要改为:=detailR.icursor (;ID==countid,index_detailR_id).fetch()。

关联查询

查到指定账户数据装入内存后,可以将机构数据也读入内存。在内存中关联计算,性能可以得到保障。示例代码如下:

A
1 =detailR.icursor (;ID==countid,index_detailR_id).fetch()
2 =file("corp.btx").import@b(corp_id,corp_name).keys(corpid)
3 =A1.switch(corp_id,A2:corp_id)
4 return A3.new(id,corp_id.corp_name:corp_name,amt)

代码示例5

A2:读入机构数据。

A3:账号10100的活期明细数据关联机构数据。

A4:将账户id、分支机构名称和金额返回给前端调用者(网页或者APP)。

机构数据可以在应用系统初始化的时候加载入内存,不必每次读取,查询速度更快。在上面提到的init.dfx中增加代码如下:

A B
1 if !ifv(detailR) =file("detailR.ctx").open().index@3(index_detailR_id)
2 =env(detailR,B1)
3 if !ifv(corp) =file("corp.btx").import@b(corp_id,corp_name).keys(corpid)
4 =env(corp,B3)

代码示例6

预先加载机构数据之后,查询代码要在“代码示例5”的基础上去掉加载机构数据的部分,修改之后的查询代码如下:

A
1 =detailR.icursor (;ID==countid,index_detailR_id).fetch()
2 =A1.switch(corp_id,corp:corp_id)
3 return A3.new(id,corp_id.corp_name:corp_name,amt)

代码示例7

A2:直接用预先加载的全程变量corp和活期明细数据关联计算,省去了每次查询加载机构数据的时间。

数据更新

活期明细存款是按账号有序的,并不是按日期有序。所以,不能在末尾追加当日新增数据。活期明细几十亿条,如果每天有序归并新数据的话,耗时太长。每月归并一次的方案比较理想。如果将活期明细数据看成是主文件,那么更新原理如下图:

图5:数据更新

数据更新的示例代码如下:

A B C
1 if day(now())==1 =file("detailR.ctx").reset() /每月重整
2 =connect("db").cursor(“select *   from detail where date=?",date(now())) /读当日数据
3 =file("detailR.ctx").open().append@a(A2) /每日归并

代码示例8

A1、B1:如果是每月1日,重整文件。

A2:从生产数据库中读入当日数据。

A3:将当日数据有序归并到集算器自动生成的补文件中。

(0)

相关推荐

  • 如何让你的SQL查询快2600倍

    初学者的数据库索引简介 > Congratulations 今天在ORM的时代,我们作为开发人员不必经常触摸数据库.我自己自己建立了我的第一个项目而不在项目内写一行的原始SQL. 它起初工作正常 ...

  • pandas DataFrame的新增行列,修改、删除、筛选、判断元素以及转置操作

    pandas DataFrame的新增行列,修改、删除、筛选、判断元素以及转置操作

  • 太强了,1行python代码干了妹子一天的工作

    事情是这样的,元旦前有朋友向我寻求帮助,吐槽老板在放假前给他安排一个苦逼的差事,想问问我能不能帮个忙,要不然假期都过不好了 工作具体内容如下,主要是想把一个二维表格转成一维表格,如下图(表格为替代品) ...

  • 什么是MongoDB?简介、架构、功能和示例 | MongoDB中文社区

    什么是MongoDB? 什么是MongoDB?MongoDB是一个面向文档的NoSQL数据库,用于大容量数据存储.MongoDB是2000年代中期出现的一个数据库,属于NoSQL数据库. 在这个教学大 ...

  • Pandas Series入门教程

    Series结构,也称Series序列,是Pandas常用的数据结构之一,它是一种类似于一维数组的结构,由一组数据值(value)和一组标签组成,其中标签与数据值之间是一一对应的关系. Series可 ...

  • 万字长文丨大白话带你由浅入深Python编程语言

    Life is short, you need Python.(人生苦短,我用 Python.) --Bruce Eckel 前言 听说现在是全民 Python 的时代,虽然不知道事实如何,但学会 P ...

  • Java高并发21-AQS在共享,独占场景下的源码介绍

    一.AQS--锁的底层支持 1.AQS是什么 AQS是AbstractQueuedSychronizer的简称,即抽象同步队列的简称,这是实现同步器的重要组件,是一个抽象类,虽然在实际工作中很烧用到它 ...

  • 高并发场景下锁的使用技巧

    来源:张飞洪 https://www.cnblogs.com/jackyfei/p/12142840.html 如何确保一个方法,或者一块代码在高并发情况下,同一时间只能被一个线程执行,单体应用可以使 ...

  • 专业的在线考试答题系统,快考题,高并发人数使用流畅

    在线考试的普及,让越来越多的学校,企业,教育机构纷纷加入.在线考试系统的开发也打破了以往传统的考试模式,不受时间限制,不受地域限制.那么一个完善的在线考试系统除了以上两大优势,还有哪些"过人 ...

  • 高并发,我把握不住啊

    慎入,作者高并发搞得少(没搞过),这里面水太深,什么高并发,大流量的东西都是虚拟的,作者还太年轻,没有那个经历,把握不住.系统只有几QPS,开心快乐就行,不PK,文明PK. 我关注的大佬更新了,在干货 ...

  • 高并发场景下,到底先更新缓存还是先更新数据库?

    在大型系统中,为了减少数据库压力通常会引入缓存机制,一旦引入缓存又很容易造成缓存和数据库数据不一致,导致用户看到的是旧数据. 为了减少数据不一致的情况,更新缓存和数据库的机制显得尤为重要,接下来带领大 ...

  • Java高并发24-使用自定义锁生成一个消费模型

    一.使用自定义锁实现生成--消费模型 下面我们使用上节自定义的锁实现一个简单的生产--消费模型,代码如下: package com.ruigege.LockSourceAnalysis6; impor ...

  • 用记录查询

    5.信用记录查询 (1)查询渠道:通过"信用中国"网站(www.creditchina.gov.cn)和"中国政府采购网"(www.ccgp.gov.cn)进行 ...

  • Java高并发16-LongAdder类源码解析(下)

    一.复习 上次连载简单的介绍了其他函数的作用以及功能 二.完整的LongAdder类源码 package com.ruigege.AtomicOperationClass4;import java.u ...

  • 最新战报 | 香港佳士得27件高古瓷成交记录!

    日前香港佳士得『重要中国瓷器及工艺精品』综合专场,作为每季的压轴节目,综合瓷杂专场向来实力很强,瓷器界小编为您带来本场拍卖高古部分器物成交盛况. 『重要中国瓷器及工艺精品』 拍卖时间 | 05月28日 ...