今天,在工作中遇到项目在查询码表字典转换时,不走redis而是查询后台数据库问题。做一个简单记录,目的是防止以后出现类似问题,同时该码表存在缓存中采用的数据类型也是值得我学习的。
一、简单的背景介绍
该项目以前是一个整体项目,后来因业务需要,把整个项目拆分为多个子工程,这就造成一些类的包路径改变了(这个是问题的根源),项目拆分后,发现在一些涉及到码表转换的业务,都是没有走redis而是直接到后端查询数据库。
我就在debug模式跟踪项目代码,原来在查询redis时报错(具体是查询redis是先查询元数据,在查询码值。在查询元数据时报错了,原因就是因为项目拆分,一个类的包路径变了,在反序列化成对象时报错),而代码捕获了这个异常,在catch块中执行查询后端数据库的逻辑代码。这是不走redis发生穿透现象的原因所在。针对这个问题的分析我写了一个分析报告,报告放在了后面。
我还发现项目在每次项目启动时,系统会自动从后台数据库读取码表相关数据,并初始化到redis中。具体流程是先删除redis中的元数据、码值数据等,再初始化redis中元数据和码值数据,保证redis中数据和后端数据库的一致性。
这种做法是完全没问题的,项目拆分后重新部署,肯定会重启项目,那么redis中数据又是最新的,就不会出现查询码表不走redis发送穿透的问题。问题就出现在删除redis中元数据这一步,由于系统采用的删除方法是先从redis读取,然后在删除。但是在读取中,由于项目拆分一个类的路径变了,无法反序列化成对象,造成代码抛异常,导致后面的删除redis中元数据和码表数据没有执行。那么拆分后的项目,在进行码表转换查询redis,有碰到了获取元数据失败的情况(因为路径还是拆分前的),异常被捕获,就直接查询后端数据库了。
二、解决办法
为了避免以后项目变动,造成redis中元数据无法删除问题,采用了通过key删除的方式,而不是先从redis读再删的方式。
=================正文才开始=================
代码不在贴了,因为都是和具体业务有关,而且没有什么技术含量,大家都能明白。
其实我想写这篇文章的目的是(文章开头有交代),该项目设计者,把数据库中字典码表存到redis中所采用的数据结构的设计思路不错,这才是促使我写这篇文章的原因。
一、数据库表结构简单介绍
包含主键SEQ_NO、字典码dictCode、源系统编号srcSysCode,目标系统编号destSysCode,源系统字典值srcCodeVal,目标系统字典值destCodeVal,字典编码等6个关键字段,另外一个特殊情况是目标系统和源系统中码表对应关系有一对多和多对一关系(这个很关键)。
二、redis中存储结构设计
采用4个set集合集、1个hash、1个元数据的存储结构
具体如下:
set数据结构:
第一个set集合集,key(实际还有其他前缀标识)是数据表中srcSysCode字段下每个值是一个set集合,value是该字段对应的 所有记录的SEQ_NO
第二个set集合集,key(实际还有其他前缀标识)是数据表中destSysCode字段下每个值是一个set集合,value是该字段对应的 所有记录的SEQ_NO
第三个set集合集,key(实际还有其他前缀标识)是数据表中dictCode字段下每个值是一个set集合,value是该字段对应的所有 记录的SEQ_NO
第四个set集合集,key(实际还有其他前缀标识)是数据表中srcCodeVal字段下每个值是一个set集合,value是该字段对应的 所 有记录的SEQ_NO
hash数据结构:
key是数据库中SEQ_NO,value是json串,该json包含上述6个字段及其对应的值
元数据是一个string结构,value是一个json,主要存的是该数据表对应的BO的全限定名,会根据该BO路径反序列化成对象。
三、从redis查询目标系统码值的思路
首先是获取redis中元数据,通过json串反序列成对应的BO
其次是根据条件对4个set求交集,获取keys
再次是根据keys,从hash中求对应的value,并装配到BO中
最后是从BO中获取目标系统的destCodeVal值
最后,让我们思考下,作者为什么这么设计redis?我认为关键一点就是后端数据库中存在一对多关系。如果数据库中的码表全部是一对一关系,即源系统A中,字典码是sex,源系统中字典值是M,在目标系统B中,对应的字典码sex的值只有一个“男”,可以不用这么设计redis,直接采用一个hash存该码表即可,key就是srcSysCode:destSysCode:dictCode:srcCodeVal这样一个格式,value就是包含上述6字段的json就ok,因为这个key不会重复,但是正因为数据库中存在一对多关系,比如目标系统B中字典sex的值有“男”,还有一个是“1”,那redis设计就必须采用作者这种设计结构了。当然如果有其他好的设计思路,麻烦告知下。
*****************************问题分析报告****************************
码表转换时不走redis而直接查询后端数据库的问题分析
一、问题背景
pcms项目因工作需要拆分成多个子工程,码表转换相关代码拆分到pcms-common子工程,其中码表转换时用到的一个类PCodeTransDefPO的包结构的路径和拆分前的包结构有所改变。
二、问题分析
2.1、老码表正向转码方法具体实现步骤:
第一步先从redis中查询码表
具体步骤是先从redis中读取正向码表元数据,再根据查询条件,从redis中求set集合交集获取key,根据key再从 hash结构中获取destCodeVal的值。
第二步如果查询redis产生异常,再从后端数据库查询码表
这里后端数据库查询代码写在了catch块中了,try块中写的从redis查询代码
2.2、问题原因:
在第一步读取redis中读取元数据,并根据元数据反序列成对象时,由于元数据中存储PCodeTransDefPO的包路径为拆分前的,所以反序列成对象失败,则产生了异常,被catch捕获,所以就查询了后端数据库。
三、解决办法
把redis中存储元数据的json串中itemType的值更改为拆分后的PCodeTransDefPO包路径即可。
四、其他说明
4.1、第一,码表中如果某词汇配置了一对多的情况,该词汇转码不能使用这个方法,否则可能会无法获取正确的destCodeVal值。一对多情况例子如:某源系统中某一个srcCodeVal对应某目标系统中多个destCodeVal。
4.2、第二,在2.1第二步查询后端数据库代码写在catch块中,这种写法存在一定几率的风险,如果当第一步中查询redis产生异常而被捕获,那么第二步就不会执行,这样就会出现redis没查询到,也不会查询后端数据库,造成码表没有转换,业务代码中会得到目标系统的码值就是null或者是空串。
在日常开发中,在catch语句块中建议除了一些异常日志打印,不要写其他业务逻辑,除非能保证try语句块中所有代码没有捕获异常。