索引表设计
在电商项目中,物理库存系统是个极其重要的系统,订单支付后,就会开始来占用物理库存。一般情况下,库存系统都是要分库的,因为主要的操作是写操作,例如占用/释放/取消等写操作。使用分库可以降低数据库写的压力。尽管写操作为主,但是读操作也是有的。比如说,库存占用的时候,得先查询是否有库存,而这个查询操作并不都会带上分库因子(用于路由到具体的某个数据库),而是一些比较宽松的查询条件,这些查询条件对应的数据可能分布在不同的数据库上。这个时候为了查询的方便,会构建一个索引表。这个索引表是存在另外的单独的一个数据库中,不会再分库了的。
索引表的设计也分不同情况,大体可以分三种。
1、查询字段+数据库主键
把查询字段放到索引表,还需要把对应的数据库主键ID也放置进去。当查询请求到来时,根据查询条件找出对应的数据主键,再根据数据主键路由到对应的存有完整业务数据的数据分库上。这种方案呢。索引表占用空间小,可以支撑很久。但是要找出业务数据,还是需要路由到分库上。另外,如果要把索引表的数据存储到ES搜索引擎上的话,这种方式就不行了。因为索引表中没有外部系统要的业务数据。所以当时的库存系统没有使用这种索引表设计方案。
2、查询字段+数据库主键+需要展示的业务字段
这种方案呢。当请求到来的时候,直接查询索引表即可。无需根据主键路由到分库了。同时如果要结合ES的话。可以直接把索引表的数据弄到ES上即可。后续直接让ES暴露查询接口即可。目前我在唯品会参与的物理库存项目中,是使用这种方式的。但是这个方案也有个缺点。就是索引表的体积比较大,后续数据量一大的话,也是个问题。能不能优化一下呢?
3、索引表拆分
上面说到的第二种方案,索引表的膨胀可能很快,可以考虑将索引表拆分。比如说:索引表只是保存查询条件和主键,而需要展示给外部系统的数据,另外存储在单独的表上。比如叫index_detail表。这个表拥有索引表的主键。这样的话,当查询请求到来的时候,先从索引表查询到主键,再根据主键从index_detail表中查询出数据。当然这样做的话。ES的数据来源就变成多张表了,不过这是可以接受的。
如何把业务数据写入到索引表
使用MQ
一般来说,构建索引数据是使用单独一个应用来做的。比如叫data-index域。这个域会从消息队列中读取消息,用于构建索引数据。当业务数据发生变化后,生产者发送MQ消息到队列上。
这里的消息设计也分两种情况。一种是消息只是带有数据主键和操作类型(ADD/Update/DELETE),消费者拿到主键后再去DB获取完整的数据并插入到索引表中。另一种方案呢,是消息包含了大部分需要的字段,消费者拿到消息后直接把数据插入到索引表中。这两种消息设计,我在实际项目中都有用过。
直接操作DB
这种方案呢就比较粗暴,直接配置一个索引表库的数据源,当业务数据发生变化时,使用Mybatis或者JDBC把数据更新到索引表中。一般不建议这么做,一来构建索引数据的逻辑跟数据的CRUD操作融合在一起了。二来,操作其他数据库的数据,要么通过接口的方式,要么由单独的域来做。建议还是使用MQ的方式来构建索引数据。
如何把索引表数据弄到ES上
监听数据库表的数据变化
像在唯品会这边,自研了一个叫VDP的组件,使用storm job去监听索引表数据的变化,一旦有变化,就把数据同步到队列中,ES直接从队列中获取数据,并存储到ES上。
这种方案的好处是,我们无需写任何代码,数据自动可以同步到ES上。
使用MQ
如果公司内部没有开发VDP这样的组件,可以通过发送MQ消息的方式来将索引表的数据同步数据到ES上。
让ES暴露CUD接口
另外一种方案是,让ES暴露CUD接口,用于同步索引表数据。但是这样就跟ES耦合在一块了。不太推荐这么做。
进一步的思考
1、ES不支持Group By这样的操作,所以在构建索引表的时候,可以事先计算好Group By的一些统计数据,并存储到索引表中;
2、一些后台应用中,如果数据库表的数量已经很大,好几个亿了,并且查询的SQL还非常变态,用数据库已经无法支持了,那么可以使用ES,查询速度快,也支持一些统计操作;
3、使用ES输出数据时,也有个坑。经常会拿到脏数据的。例如当数据发送变化后,构建索引数据并把索引数据同步到ES上是需要时间的,但是我们后台通常有将数据下架的操作,下架的操作操作完后,再次点击查询按钮,可能还是看到脏数据,因为数据同步到ES上没那么快。现在我还没想到很好的办法来解决这个问题。欢迎网友提些建议。