一次搜索引擎数据割接
通过zookeeper获取注册到的需要更新数据服务的客户端。通过该客户端调用服务提供的接口进行数据更新。
割接方案基本可以分解为以下几步:
1、从oracle数据库获取需要的数据,
2、在代码中进行请求数据的拼接(请求的数据为json格式,构造时是先放到map中,然后转换为json字符串),
3、调用服务接口将数据割接到搜索引擎服务。
割接的主要问题点在数据准备方面。
第一版代码中,每更新一条数据,会先链接一遍数据库,走一遍查数据库的方法,
获取源数据后按照服务接口需要的请求参数格式构造服务接口方法需要的请求参数,
调用服务接口请求更新搜索引擎数据。
问题:这一版本割接耗时过长(具体多长时间我忘记了,好像大概带宽机器都正常的情况下,十万级数据割接要24h起),
最后分析,主要耗时点:在于每条数据准备都需要获取一次数据库资源,连接数据库,查询数据(处理查询到的数据),然后关闭本次连接,释放资源。这样的话,十万数量级数据割接就要进行10w+次数据库连接建立和查询。这样就太耗费时间和资源了。
第二版代码,因为第一版中存在多次连接数据库会耗时过长的问题,最终决定先将数据库中的数据汇总到几个文件中,通过在文件中读取数据,进行拼接请求和同步。
这样数据库请求的时间就从多次连接,变成了一次获取所有数据。事实证明,在我们的环境下,这样的策略要快很多。
虽然一次获取所有原始数据的时间是要比单条一次获取数据慢一些,但是总体来说还是要提高很多效率。
但是这样的话会出现另一个问题,就是我们获取数据的脚本中,是用了一个select xx from 表;的语句通过spool将数据输出到文件中。这样就会导致查询的xx字符串结果过长的时候,语句会直接报错字符串超长,因为oracle查询的时候,单个字符串最多允许长度是4000。
当时开发、测试、以及镜像环境都没有问题,因为数据并不复杂。
当到了生产环境执行的时候就炸了,因为生产数据太过复杂且数据长度很长。
最开始的解决思路是使用字符串转clob加拼接一个字符串来查询数据,(语句变为select to_clob(sub_xx1)||sub_xx2 from 表),并将数据输出,但是这样的话发现通过spool输出的每条数据都只有一部分,最终放弃了这种方式。
最终因为升级当晚已经来不及改代码了,而且业务允许对部分字段进行截取,
所以使用了(select max(length(字段名1)),...,max(length(字段名n)) from 表;)查询了每个字段的长度最长的值,
发现有一个字段最长长度到达了3999,还有两个到达了999,
最终由于业务允许,将这3个字段按照取后xx位进行了截取(语句示例:select substr('12345',greatest( -位数,-length('12345')),位数) from dual;)
才成功将数据导出成功到一个文件。
然后通过代码直接通过流读取文件,拼接转换为请求需要的格式后,调用接口,成功将数据刷新。
最终的反思:
1、因为写代码时,可以申请获取到生产数据的查询权限,所以可以先将写完,在开发环境调测完成的查询语句,先到生产上跑一遍看看执行效果,和返回结果。提前对可能因为数据出现问题的点,在代码或者其他层面进行处理。
2、因为sql脚本中,每行输出的数据太长,所以会导致输出的文件中每条数据可能是换行的,
针对这种状况在代码中是有一个每条数据的拼接和两条边界的判断处理的。但是由于代码中使用换行或者说空行来当做两条数据的边界,所以可能导致在一条数据中出现换行和空行的情况就会将此条数据当做两条数据来处理,而我又对每行数据拆分后应有的列数做了判断,所以会到这这条数据被过滤掉。
预想的解决方案:在每行数据输出的前后,加一段字符进行标识,存在该标志才证明上条数据结束本条数据开始,这样就能够去除换行和空行数据的影响。
3、第三点就是之前提到的生产割接时出现的单个字符串数据过长(超过4000个字符)导致语句执行报错,无法执行和输出到文件的问题。这个暂时没有太好的想法,一点初步的想法就是,不再拼接成一个字符串,而是用多个列查询的方式进行输出。但是这样的话,其实也会有问题,比如有些存储为clob类型的字段,转换和输出怀疑也可能会出现问题,这个还需要再想想看。
以上就是对一次生产割接之后的总结和考虑,当做一个记录吧,有想法后续会继续补上。
ps:之前提到割接时,使用spool输出sqlplus执行结果时,由于所有字符都放到了一个字符串中进行输出,出现了ora-01489(字符串连接的结果过长)的错误。
这是因为varchar2在oracle中,最多只支持到4000个字符,也就是32K,||的操作会把“||”后面的字符放入到前面里,最前边的数据作为varchar2,拼接后就导致整个字符串超过了4000的界限。
当时百度到的解决方案有一种是:只需要将输出字符串中连接的第一个(使用to_clob函数)转换成clob就可以。
但是spool输出的文件中出现了一个问题:文件中每条数据只有前80个字符,剩下的都被截断了,这就是clob截断问题。
可以通过设置spool参数来解决clob截断问题:
1)首先是设置spool的long参数,该参数的最大值是2000000000(2G),单位是字节。含义是:Sets maximum width (in bytes) for displaying CLOB, LONG, NCLOB and XMLType values; and for copying LONG values.中文含义大概是设置CLOB、LONG、NLOB和XMLType类型的最大显示字节数。
2)与long参数相关的是longchunksize参数,该参数的值应该比long参数的值要小,单位也是字节。含义是:Sets the size (in bytes) of the increments SQL*Plus uses to retrieve a CLOB, LONG, NCLOB or XMLType value.即sqlplus会按照这个参数的值来一段一段的获取上述类型的数据,直到达到long的值或者数据获取完毕。
举例:
数据有10个字节:0123456789
如果set longchunksize 2,那么显示出来的效果就是
01
23
45
67
89
如果set longchunksize 4,那么显示出来的效果就是
0123
4567
89
3)set linesize 255
这个参数用的很频繁,相信含义大家也都知道:Sets the total number of characters that SQL*Plus displays on one line before beginning a new line.这个参数是用来控制sqlplus显示的,而longchunksize是用来控制sqlplus获取数据的,所以如果这个参数设置不当也不能完全显示。
举例:
数据有10个字节:0123456789
set longchunksize 4
set linesize 3
显示效果如下
012 --3无法显示
456 --7无法显示
89
这里参考了博客:https://www.cnblogs.com/zmlctt/p/3721121.html
最终我脚本里设置参数如下,就可以成功导出了:
set echo off; --显示start启动的脚本中的每个sql命令
set feedback off; --回显本次sql命令处理的记录条数,缺省为on
set verify off; --关闭提示确认信息old 1和new 1的显示
set term off; ----没有查到,可能与termout差不多(set termout off-显示脚本中的命令的执行结果,缺省为on)
set trimspool on; --去除重定向(spool)输出每行的拖尾空格,缺省为off
set linesize 3000000; --输出一行字符个数,缺省为80
set newpage none; --页和页之间隔着空行的个数,none标识两页之间没有任何间隔
set heading off; --屏蔽显示,缺省为on
set long 2000000000; --CLOB、LONG、NLOB和XMLType类型的最大显示字节数,缺省为80
后续记录,因为一些原因,我们需要在现网再次进行数据割接。
本次还是按照之前的分步进行操作,并且尝试使用clob替换varchar,解决拼接字符串超长的问题。
但是最终在割接当晚还是出现了问题,其他环境由于数据量原因使用clob导出数据表现都还不算太差。但是到了生产环境,需要导出的数据量为百万级,此时使用clob引发了灾难性的问题——就是说由于使用clob后,会导致查询效率急速下降,而由于现网数据的复杂性,我在一个select查询结果的拼接中3次使用了clob转换,导致最终4-5个小时都无法完成数据导出到文件。
最终还是使用了上文提到的第二种方案尽快改正,才使割接正常进行。
所以这里重点提醒,做数据导出,在导出数据量达到万级以上时,最好就不要再使用clob字段进行转换了,否则可能导致数据库长期无法完成数据查询和导出,最终导致数据库资源一直占用较高,引起其他问题。
此种情况下,如果业务允许,则依据业务直接进行截取。
业务不允许截取的情况,暂时还没有考虑。如果各位大佬有好的解决方案,欢迎评论,让我好膜拜下。