前言
因为业务要求api的一次请求响应时间在10ms以内,所以传统的数据库查询操作直接被排除(网络io和磁盘io)。通过调研,最终使用了bitset,目前已经正常运行了很久
bitset介绍
看JDK中的解释简直一头雾水,用我自己的理解概括一下
- bitset的内部实现是long数组
- set中每一个位的默认值为false(0)
- bitset长度按需增长
- bitset非线程安全
bitset关键方法分析
1 | /** |
设置指定“位”为true,bitIndex参数为非负整数。假设我们执行以下代码,观察上面代码中worIndex,words[wordIndex]值的变化
1 | BitSet bs = new BitSet() |
bitIndex | wordIndex | words[wordIndex] | words[wordIndex]二进制表示 |
---|---|---|---|
0 | 0 | 1 | 0001 |
1 | 0 | 3 | 0011 |
2 | 0 | 7 | 0111 |
3 | 0 | 15 | 1111 |
4 | 0 | 31 | 0001 1111 |
通过上表,我们可以很清晰的根据bitIndex和words[wordIndex]二进制值的对应关系,得到一个结论,即:bitset中每一个long可以表示64个非负整数在bitSet中存在与否。例如:0001可以表示整数0在bitset中存在,1111可以表示整数3,2,1,0在bitset中存在。 | |||
*** |
进入正题,实现bitset毫秒级查询
想象一个场景,我们有一张user表,name唯一。
1 | CREATE TABLE `user` ( |
假设我们要查询“北京市18岁的女生”,那么对应的sql如下:
1 | select * from `user` where address='北京' and age='18' and gender='girl'; |
如何使用bitset实现同样的查询呢?
- 将user表数据加载进内存中
- 为user表建立address,age,gender维度的bitset索引
- 根据索引查询数据
1.将user表数据加载进内存中
将user表从数据库读取出来存入List
User实体类:
1 | public class User implements Cloneable { |
2.建立索引
创建bitset索引模型类
1 | public class BitSetIndexModel { |
为每一个user对象创建address,gender,age维度索引
1 | public class UserIndexStore { |
3.测试bitset
1 | public class BitSetTest { |
测试结果(查询2w次):
数据量(users.size()) | 并发数 | 平均查询时间
—|—|—
20w | 10 | 1ms
50w | 20 | 3ms
100w| 50 | 9ms
测试机为thinkpad x240 i5 8g内存
4.总结
==优点==:
通过测试发现随着数据量的增大和并发数的提高,平均耗时并没有明显升高,并且响应时间都在10ms以内
==缺点==:
因为我们的查询业务比较简单,唯一的要求是速度,并且数据量也不大,每张表的数据量都不超过100w,所以使用这种方式比较合适。
在本篇文章中只谈到了如何创建索引,以及最基本的查询,在下一篇中我会继续说明如何更新索引,以及一些复杂查询,比如<,>,between and。