先来看一个问题:
问:有20亿的用户,现在要快速统计出有多少个用户在登录态中,要怎么设计?
答:用MySQL数据库表存起来,当用户登录了设置为1,退出时改为0,然后统计出条件为1的数量即可。
问:如果放在像MySQL这样的数据库中,用户基数很大,这样会很频繁地改动数据库,IO多,拖累系统性能。
答:换成Redis,定义一个set类型的key叫做uid_login,用户登录了就往set中添加,退出了就删除这个uid元素。通过scard命令获取成员数量,即已登陆的uid数量。还能以O(1)的时间复杂度去判断uid是否登录。
问:假设其中有10亿个用户处于登录状态,那集合就有10个uid,按每个uid占用4个字节来计算,这个需要占用多少内存呢?
答:10亿乘以4就就等于40亿字节,换算后大概就是4GB……
问:那有没有更省内存的方法呢?
答:用string类型来做,key命名为uid_login_用户id,如果登录了,就添加uid_login_用户id这个key,值为1;如果不存在,就删掉这个key,这样value就应该不会占用这么多了吧……
问:key的数量会有10个亿,难道key就不占用内存吗?还有你要如何统计处于登录态的uid数量呢?
答:……
现在,主角——BitMap出场
所谓bitmap,就是用每一位来存放某种状态,适用于大规模数据,但数据状态又不是很多的情况。通常是用来判断某个数据存不存在的。
bitmap的开源实现
SETBIT key offset value 给某个key的offset设置value
GETBIT key offset 获取key的offset值
BITCOUNT key [start] [end] 统计key中value为1的数量
BITOP operation destkey key [key...] key的与或非操做等
BITOPS key bit [start] [end]
key就是自定义的redis的key;offset就是偏移量,类似于数组下标;value就是比特位值,只能为0或1
例如:
key: user_login_status
offset: uid
用户10023登录 setbit user_login_status 10023 1
用户10023退出 setbit user_login_status 10023 0
判断用户是否在线:getbit user_login_status 10023
解决统计问题:bitcount user_login_status 得在线用户数
1个用户占1bit,比用set占用的32位优化了32倍,相对于刚开始的4GB现在直接变为125MB
那么如何获取已登陆且手机为iOS系统的用户呢?
给其新建一个key:user_ios代表用了IOS系统的用户
将user_login_status与user_ios做与操做就可以得到IOS系统用户了,即user_ios & user_login_status
所以要统计所有登录用户的数量呢?
user_login_status与user_ios做或操做就可以了
BitMap优点:
- 排序、查找、去重等效率高
- 占用内存空间低
BitMap缺点:
- 结果数据不能重复,相当于就是排重过的
- 如果数据只有两个:1和10000000,使用BitMap空间比较浪费,只有当数据比较密集时才有优势。但是也有开源工具实现了压缩算法。
Reference: