SpringBoot2.x中使用Redis的bitmap结构(工具类)

  1. 一亿个用户,有的用户频繁登录,也有不经常登录的。
  2. 如何记录用户的登录信息?
  3. 如何查询活跃用户?[如一周内 登录三次的]

Redis中文教程
Redis语法大全

我们可以使用Redis的bitmap(位图)来存储数据。

1. 什么叫做Redis的bitmap

即:操作String数据结构的key所存储的字符串指定偏移量上的,返回原位置的值

1.1 优点:

节省空间:通过一个bit位来表示某个元素对应的值或者状态,其中key就是对应元素的值。实际上8个bit可以组成一个Byte,所以是及其节省空间的。

效率高:setbitgetbit的时间复杂度都是O(1),其他位运算效率也高。

1.2 缺点:

本质上只有01的区别,所以用做业务数据记录,就不需要在意value的值。

1.3 使用场景

  1. 可作为简单的布尔过滤器来判断用户是否执行过某些操作;
  2. 可以计算用户日活、月活、留存率的统计;
  3. 可以统计用户在线状态和人数;

2. Redis的bitmap命令

2.1 setbit命令

设置或修改key上的偏移量(offset)的位(value)的值。

  • 语法:setbit key offset value
  • 返回值:指定偏移量(offset)原来存储的值。
    bitmap的setkey指令

2.2 getbit命令

查询key所存储的字符串值,获取偏移量上的

  • 语法:getbit key offset
  • 返回值:返回指定key上的偏移量,若key不存在,那么返回0。
bitmap的getbit指令

2.3 bitcount命令

计算给定key的字符串值中,被设置为1的位bit的数量

  • 语法:bitcount key [start] [end]
  • 返回值:1比特位的数量

注意:setbit是设置或者清除bit位置。这个是统计key出现1的次数。
(小胖友情提示:)需要注意的是:[start][end](单位)实际是byte,这是什么意思呢?进入redis实际上是乘以8。

SpringBoot2.x中使用Redis的bitmap结构(工具类)_第1张图片
bitcount指令的使用

2.4 bitop命令

对一个或多个保存二进制的字符串key进行元操作,并将结果保存到destkey上。

  • 语法:operation可以是andornotxor的一种。
  • bitop and destkey key [key...],对一个或多个key逻辑并,结果保存到destkey
  • bitop or destkey key [key...],对一个或多个key逻辑或,结果保存到destkey
  • bitop xor destkey key [key...],对一个或多个key逻辑异或,结果保存到destkey
  • bitop xor destkey key,对一个或多个key逻辑非,结果保存到destkey

除了NOT之外,其他操作多可以接受一个或多个key作为输入。

BITOP的时间复杂度是O(N),当处理大型矩阵或者大量数据统计时,最好将任务指派到附属节点(slave)进行,避免阻塞主节点。

3. SpringBoot中使用

@Component
public class SpringUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtils.applicationContext == null) {
            SpringUtils.applicationContext = applicationContext;
        }
    }

    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    public static  T getBean(Class c) {
        return getApplicationContext().getBean(c);
    }

    public static  T getBean(String name, Class c) {
        return getApplicationContext().getBean(name, c);
    }


}

工具类:

mport com.google.common.hash.Funnels;
import com.google.common.hash.Hashing;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.nio.charset.Charset;

/**
 * 工具类-提供静态方法
 */
public  class RedisTemplateUtil {

    private static  StringRedisTemplate stringRedisTemplate = SpringUtils.getBean(StringRedisTemplate.class);


    /*********************************************************************************
     *
     * 对bitmap的操作
     *
     ********************************************************************************/

    /**
     * 将指定param的值设置为1,{@param param}会经过hash计算进行存储。
     *
     * @param key   bitmap结构的key
     * @param param 要设置偏移的key,该key会经过hash运算。
     * @param value true:即该位设置为1,否则设置为0
     * @return 返回设置该value之前的值。
     */
    public static Boolean setBit(String key, String param, boolean value) {
        return stringRedisTemplate.opsForValue().setBit(key, hash(param), value);
    }

    /**
     * 将指定param的值设置为0,{@param param}会经过hash计算进行存储。
     *
     * @param key   bitmap结构的key
     * @param param 要移除偏移的key,该key会经过hash运算。
     * @return 若偏移位上的值为1,那么返回true。
     */
    public static Boolean getBit(String key, String param) {
        return stringRedisTemplate.opsForValue().getBit(key, hash(param));
    }


    /**
     * 将指定offset偏移量的值设置为1;
     *
     * @param key    bitmap结构的key
     * @param offset 指定的偏移量。
     * @param value  true:即该位设置为1,否则设置为0
     * @return 返回设置该value之前的值。
     */
    public static Boolean setBit(String key, Long offset, boolean value) {
        return stringRedisTemplate.opsForValue().setBit(key, offset, value);
    }

    /**
     * 将指定offset偏移量的值设置为0;
     *
     * @param key    bitmap结构的key
     * @param offset 指定的偏移量。
     * @return 若偏移位上的值为1,那么返回true。
     */
    public static Boolean getBit(String key, long offset) {
        return stringRedisTemplate.opsForValue().getBit(key, offset);
    }

    /**
     * 统计对应的bitmap上value为1的数量
     *
     * @param key bitmap的key
     * @return value等于1的数量
     */
    public static Long bitCount(String key) {
        return stringRedisTemplate.execute((RedisCallback) con -> con.bitCount(key.getBytes()));
    }

    /**
     * 统计指定范围中value为1的数量
     *
     * @param key   bitMap中的key
     * @param start 该参数的单位是byte(1byte=8bit),{@code setBit(key,7,true);}进行存储时,单位是bit。那么只需要统计[0,1]便可以统计到上述set的值。
     * @param end   该参数的单位是byte。
     * @return 在指定范围[start*8,end*8]内所有value=1的数量
     */
    public static Long bitCount(String key, int start, int end) {
        return stringRedisTemplate.execute((RedisCallback) con -> con.bitCount(key.getBytes(), start, end));
    }


    /**
     * 对一个或多个保存二进制的字符串key进行元操作,并将结果保存到saveKey上。
     * 

* bitop and saveKey key [key...],对一个或多个key逻辑并,结果保存到saveKey。 * bitop or saveKey key [key...],对一个或多个key逻辑或,结果保存到saveKey。 * bitop xor saveKey key [key...],对一个或多个key逻辑异或,结果保存到saveKey。 * bitop xor saveKey key,对一个或多个key逻辑非,结果保存到saveKey。 *

* * @param op 元操作类型; * @param saveKey 元操作后将结果保存到saveKey所在的结构中。 * @param desKey 需要进行元操作的类型。 * @return 1:返回元操作值。 */ public static Long bitOp(RedisStringCommands.BitOperation op, String saveKey, String... desKey) { byte[][] bytes = new byte[desKey.length][]; for (int i = 0; i < desKey.length; i++) { bytes[i] = desKey[i].getBytes(); } return stringRedisTemplate.execute((RedisCallback) con -> con.bitOp(op, saveKey.getBytes(), bytes)); } /** * 对一个或多个保存二进制的字符串key进行元操作,并将结果保存到saveKey上,并返回统计之后的结果。 * * @param op 元操作类型; * @param saveKey 元操作后将结果保存到saveKey所在的结构中。 * @param desKey 需要进行元操作的类型。 * @return 返回saveKey结构上value=1的所有数量值。 */ public static Long bitOpResult(RedisStringCommands.BitOperation op, String saveKey, String... desKey) { bitOp(op, saveKey, desKey); return bitCount(saveKey); } /** * guava依赖获取hash值。 */ private static long hash(String key) { Charset charset = Charset.forName("UTF-8"); return Math.abs(Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asInt()); } }

4. 计算日活、月活、留存率的具体方法

具体实施:使用redis的bitmap

  1. 设置一个key专门用来记录用户日活的,可以使用时间来翻滚比如1号的key为active01.
  2. 使用每个用户的唯一标识映射一个偏移量,比如使用id,这里可以把id换算成一个数字或直接使用id的二进制值作为该用户在当天是否活跃偏移量
  3. 用户登录则把该用户偏移量上的位值设置为1
  4. 每天按日期生成一个位图(bitmap)
  5. 计算日活则使用bitcount即可获得一个key的位值为1的量
  6. 计算月活(一个月内登陆的用户去重总数)即可把30天的所有bitmap做or计算,然后再计算bitcount
  7. 计算留存率(次日留存=昨天今天连续登录的人数/昨天登录的人数) 即昨天的bitmap与今天的bitmap做and计算就是连续登录的再做bitcount就得到连续登录人数,再bitcount得到昨天登录人数,就可以通过公式计算出次日留存。

文章参考:
Redis:Bitmap的setbit,getbit,bitcount,bitop等使用与应用场景

你可能感兴趣的:(SpringBoot2.x中使用Redis的bitmap结构(工具类))