redis五大数基本据类型和三大特殊数据类型的使用和应用场景

    科技2024-01-23  90

    redis五大数基本据类型和三大特殊数据类型

    1、什么是NoSQL

    1、NoSQL概述

    NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

    2、NOSQL和关系型数据库比较

    优点: 1)成本:nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。 2)查询速度:nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。 3)存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。 4)扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。

    缺点: 1)维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语。 2)不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本。 3)不提供关系型数据库对事务的处理。

    3、非关系型数据库的优势:

    性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。

    4、关系型数据库的优势:

    复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱势,反之亦然。

    5、总结

    关系型数据库与NoSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的时候使用NoSQL数据库,让NoSQL数据库对关系型数据库的不足进行弥补。一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据

    6、主流的NOSQL产品

    ​ • 键值(Key-Value)存储数据库 ​ 相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB ​ 典型应用: 内容缓存,主要用于处理大量数据的高访问负载。 ​ 数据模型: 一系列键值对 ​ 优势: 快速查询 ​ 劣势: 存储的数据缺少结构化 ​ • 列存储数据库 ​ 相关产品:Cassandra, HBase, Riak ​ 典型应用:分布式的文件系统 ​ 数据模型:以列簇式存储,将同一列数据存在一起 ​ 优势:查找速度快,可扩展性强,更容易进行分布式扩展 ​ 劣势:功能相对局限 ​ • 文档型数据库 ​ 相关产品:CouchDB、MongoDB ​ 典型应用:Web应用(与Key-Value类似,Value是结构化的) ​ 数据模型: 一系列键值对 ​ 优势:数据结构要求不严格 ​ 劣势: 查询性能不高,而且缺乏统一的查询语法 ​ • 图形(Graph)数据库 ​ 相关数据库:Neo4J、InfoGrid、Infinite Graph ​ 典型应用:社交网络 ​ 数据模型:图结构 ​ 优势:利用图结构相关算法。 ​ 劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。

    2、什么是Redis

    1、redis概述

    Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求。

    2、Redis支持的键值数据类型如下:

    字符串类型 string哈希类型 hash列表类型 list集合类型 set有序集合类型 sortedset

    3、redis的应用场景

    ​ 缓存(数据查询、短连接、新闻内容、商品内容等等)​ 聊天室的在线好友列表​ 任务队列。(秒杀、抢购、12306等等)​ 应用排行榜​ 网站访问统计​ 数据过期处理(可以精确到毫秒)​ 分布式集群架构中的session分离

    4、redis基础知识

    1、redis数据库切换

    redis 内置16个数据库

    切换:select (0~15)

    2、redis数据库清除

    flushdb 清楚本数据库全部数据’flushAll 清楚全部数据库数据

    3、自带测试工具(redis-benchmark)

    测试语句

    redis-benchmark -h localhost -p 6379 -c 100 -n 100000

    4、redis是单线程

    Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带 宽,既然可以使用单线程来实现,就使用单线程了!所有就使用了单线程了!

    Redis 是C语言写的,官方提供的数据为100000+的QPS,完全不比同样是使用key-vale的Memecache差! Redis为什么单线程还这么快?

    误区一:高性能服务器一定是多线程! 误区二:多线程(CPU上下文会切换!)一定比单线程效率高! 先去CPU>内存>硬盘的速度要有所了解!

    核心:redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗 没有操作!!!),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上的,在内存情况这个就是最佳的方案!

    3、redis五大数据类型常用操作

    redis存储的是:key,value格式的数据,其中key都是字符串,value有5种不同的数据结构value的数据结构: 字符串类型 string 2) 哈希类型 hash : map格式 3) 列表类型 list : linkedlist格式。支持重复元素 4) 集合类型 set : 不允许重复元素 5) 有序集合类型 sortedset:不允许重复元素,且元素有顺序

    1、String常用命令

    计数器

    string类型的incr和decr命令的作用是将key中储存的数字值加一/减一,这两个操作具有原子性,总能安全地进行加减操作,因此可以用string类型进行计数,如微博的评论数、点赞数、分享数,抖音作品的收藏数,京东商品的销售量、评价数等。

    2. 分布式锁

    string类型的setnx的作用是“当key不存在时,设值并返回1,当key已经存在时,不设值并返回0”,“判断key是否存在”和“设值”两个操作是原子性地执行的,因此可以用string类型作为分布式锁,返回1表示获得锁,返回0表示没有获得锁。例如,为了保证定时任务的高可用,往往会同时部署多个具备相同定时任务的服务,但是业务上只希望其中的某一台服务执行定时任务,当定时任务的时间点触发时,多个服务同时竞争一个分布式锁,获取到锁的执行定时任务,没获取到的放弃执行定时任务。定时任务执行完时通过del命令删除key即释放锁,如果担心del命令操作失败而导致锁一直未释放,可以通过expire命令给锁设置一个合理的自动过期时间,确保即使del命令失败,锁也能被释放。不过expire命令同样存在失败的可能性,如果你用的是Java语言,建议使用JedisCommands接口提供的String set(String key, String value, String nxxx, String expx, long time)方法,这个方法可以将setnx和expire原子性地执行,具体使用方式如下(相信其它语言的Redis客户端也应当提供了类似的方法)。

    jedisCommands.set("IAmAKey", "1", "NX", "EX", 60);//如果"IAmAKey"不存在,则将其设值为1,同时设置60秒的自动过期时间

    存储对象

    利用JSON强大的兼容性、可读性和易用性,将对象转换为JSON字符串,再存储在string类型中,是个不错的选择,如用户信 息、商品信息等。

    string类型的常用命令可参考http://www.runoob.com/redis/redis-strings.html。

    加入string类型的应用场景后的思维导图如下。

    select 3 #切换至3号数据库 dbsize #查看当前数据库的 数据量 一般为 k-v 的对数 keys * #查看当前库中的所有 key flushdb #清空当前数据库 flushall #清空所有数据库 set name zz #存入一个 name-zz的 k-v数据 get name #将返回这个key对应的值 zz exits name #判断当前key是否存在 存在返回1否则返回0 move name 1 #移动 这个k-v 到指定数据库 expire name 10 #设置这个k-v的过期时间为10秒 ttl name #查看这个k-v剩余的有效期时间 type name #查看当前key的类型 append name "hh" #将name对应的value值拼接 再次get name时会返回 zzhh 如果key不存在则等价于set strlen name #查看这个key对应的value值的长度 set views 0 incr views #自增1 下次get views将返回1 decr views #自减1 下次get views将又返回0 incrby views 5 #自增5 下次get views将返回5 decrby views 5 #自减5 下次get views将返回0 getrange name 1 2 #返回name对应value的一部分 从下标1开始到下标2结束 即zh getrange name 0 -1 #返回name对应value的一部分 从下标0开始到末尾 即zzhh setrange name 1 ab #将name下标1开始的位置 替换为ab 返回 zabh setex name1 10 "aaa" #如果name1不存在则创建k-v并指定过期时间10秒,具有原子性。 如果存在则覆盖value指定过期时间10秒 setnx name2 bbb #如果不存在这个key则创建成功返回1,如果存在 则创建失败返回0 mset k1 v1 k2 v2 k3 v3 #批量设置这3个k-v对 mget k1 k2 k3 #批量返回相应k值的相应value值 msetnx k1 v1 k4 v4 #批量不存在时设置,具有原子性,如此时k1存在k4不存在,但会全部失败返回0 set user:1 {name:zhangsan,age:10} #保存对象 mset user:1:name zhangsan user:1:ange 10 #也可有上述效果 getset name ccc #先get再set 不存在时 返回nil,但set仍然会生效!等价与创建了一个新的k-v。存在时返回之前的value值且覆盖掉这个value,下次get会返回刚设置的新值

    2、list常用命令

    list类型是简单的字符串列表,按照插入顺序排序。每个列表最多可以存储 232 - 1 个元素(40多亿) ,list类型主要有以下应用场景。。

    1. 消息队列

    list类型的lpop和rpush(或者反过来,lpush和rpop)能实现队列的功能,故而可以用Redis的list类型实现简单的点对点的消息队列。不过我不推荐在实战中这么使用,因为现在已经有Kafka、NSQ、RabbitMQ等成熟的消息队列了,它们的功能已经很完善了,除非是为了更深入地理解消息队列,不然我觉得没必要去重复造轮子。

    2. 排行榜

    list类型的lrange命令可以分页查看队列中的数据。可将每隔一段时间计算一次的排行榜存储在list类型中,如京东每日的手机销量排行、学校每次月考学生的成绩排名、斗鱼年终盛典主播排名等,下图是酷狗音乐“K歌擂台赛”的昨日打擂金曲排行榜,每日计算一次,存储在list类型中,接口访问时,通过page和size分页获取打擂金曲。但是,并不是所有的排行榜都能用list类型实现,只有定时计算的排行榜才适合使用list类型存储,与定时计算的排行榜相对应的是实时计算的排行榜,list类型不能支持实时计算的排行榜,之后在介绍有序集合sorted set的应用场景时会详细介绍实时计算的排行榜的实现。

    最新列表

    list类型的lpush命令和lrange命令能实现最新列表的功能,每次通过lpush命令往列表里插入新的元素,然后通过lrange命令读取最新的元素列表,如朋友圈的点赞列表、评论列表。

    但是,并不是所有的最新列表都能用list类型实现,因为对于频繁更新的列表,list类型的分页可能导致列表元素重复或漏掉,举个例子,当前列表里由表头到表尾依次有(E,D,C,B,A)五个元素,每页获取3个元素,用户第一次获取到(E,D,C)三个元素,然后表头新增了一个元素F,列表变成了(F,E,D,C,B,A),此时用户取第二页拿到(C,B,A),元素C重复了。只有不需要分页(比如每次都只取列表的前5个元素)或者更新频率低(比如每天凌晨更新一次)的列表才适合用list类型实现。对于需要分页并且会频繁更新的列表,需用使用有序集合sorted set类型实现。另外,需要通过时间范围查找的最新列表,list类型也实现不了,也需要通过有序集合sorted set类型实现,如以成交时间范围作为条件来查询的订单列表。之后在介绍有序集合sorted set类型的应用场景时会详细介绍sorted set类型如何实现最新列表。

    #list的操作 lpush list one #往list顶端插入值 lrange list 0 -1 #返回list中所有值 rpush list four #往list底部插入值 lpop list #移除list顶端元素,并返回该元素 rpop list #移除list底部元素,并返回该元素 lindex list 0 #获取list中指定下标为0的值,同上理解就是顶端 llen list #返回list的长度 lrem list 1 value #移除list中指定个数的value值 ltrim list 1 2 #截断list,保留指定下标中的值 rpoplpush list1 haha2 #从list1底部移除一个元素并返回,且将该元素插入list2的顶端 exists list #判断list是否存在 存在返回1否则返回0 lset list 0 haha #修改列表指定位置的值, 需要列表和该位置元素已存在,否则报错 linsert list before hello new #往指定列表的指定元素的前面插入指定值 linsert list after hello new1 #往指定列表的指定元素的后面插入指定值

    3、set集合

    好友/关注/粉丝/感兴趣的人集合

    set类型唯一的特点使得其适合用于存储好友/关注/粉丝/感兴趣的人集合,集合中的元素数量可能很多,每次全部取出来成本不小,set类型提供了一些很实用的命令用于直接操作这些集合,如

    a. sinter命令可以获得A和B两个用户的共同好友

    b. sismember命令可以判断A是否是B的好友

    c. scard命令可以获取好友数量

    c. 关注时,smove命令可以将B从A的粉丝集合转移到A的好友集合

    需要注意的是,如果你用的是Redis Cluster集群,对于sinter、smove这种操作多个key的命令,要求这两个key必须存储在同一个slot(槽位)中,否则会报出 (error) CROSSSLOT Keys in request don’t hash to the same slot 错误。Redis Cluster一共有16384个slot,每个key都是通过哈希算法CRC16(key)获取数值哈希,再模16384来定位slot的。要使得两个key处于同一slot,除了两个key一模一样,还有没有别的方法呢?答案是肯定的,Redis提供了一种Hash Tag的功能,在key中使用{}括起key中的一部分,在进行 CRC16(key) mod 16384 的过程中,只会对{}内的字符串计算,例如friend_set:{123456}和fans_set:{123456},分别表示用户123456的好友集合和粉丝集合,在定位slot时,只对{}内的123456进行计算,所以这两个集合肯定是在同一个slot内的,当用户123456关注某个粉丝时,就可以通过smove命令将这个粉丝从用户123456的粉丝集合移动到好友集合。相比于通过srem命令先将这个粉丝从粉丝集合中删除,再通过sadd命令将这个粉丝加到好友集合,smove命令的优势是它是原子性的,不会出现这个粉丝从粉丝集合中被删除,却没有加到好友集合的情况。然而,对于通过sinter获取共同好友而言,Hash Tag则无能为力,例如,要用sinter去获取用户123456和456789两个用户的共同好友,除非我们将key定义为{friend_set}:123456和{friend_set}:456789,否则不能保证两个key会处于同一个slot,但是如果真这样做的话,所有用户的好友集合都会堆积在同一个slot中,数据分布会严重不均匀,不可取,所以,在实战中使用Redis Cluster时,sinter这个命令其实是不适合作用于两个不同用户对应的集合的(同理其它操作多个key的命令)。

    随机展示

    通常,app首页的展示区域有限,但是又不能总是展示固定的内容,一种做法是先确定一批需要展示的内容,再从中随机获取。如下图所示,酷狗音乐K歌擂台赛当日的打擂歌曲共29首,首页随机展示5首;昨日打擂金曲共200首,首页随机展示30首。

    set类型适合存放所有需要展示的内容,而srandmember命令则可以从中随机获取几个。

    黑名单/白名单

    经常有业务出于安全性方面的考虑,需要设置用户黑名单、ip黑名单、设备黑名单等,set类型适合存储这些黑名单数据,sismember命令可用于判断用户、ip、设备是否处于黑名单之中。

    # set操作(无序不重复集合) sadd myset zhangsan lisi SMEMBERS myset # set集合中添加值 SMEMBERS myset # 查看set集合的所有值 SISMEMBER myset lisi # 判断set集合中是否有这个值 scard myset # 获取set集合中的元素个数 srem myset lisi # 删除set集合指定的元素 SRANDMEMBER myset # 随机抽取出一个元素 SRANDMEMBER myset 2 # 随机抽选出2个元素 spop myset # 随机删除set集合的元素 spop myset 2 # 随机删除set集合的2ge元素 SDIFF myset myset2 # 两个set集合的差集 SINTER myset myset2 # 两个set集合的交集 SUNION myset myset2 # 两个set集合的并集

    4、Hash(哈希)

    Map集合, key-map!时候这个值是一个map集合 !本质和string没有太大区别,还是一个简单的key-value!

    hash类型是一个string类型的field和value的映射表,每个 hash 可以存储 232 - 1 键值对(40多亿),hash类型主要有以下应用场景。

    购物车

    以用户id为key,商品id为field,商品数量为value,恰好构成了购物车的3个要素。

    存储对象

    hash类型的(key, field, value)的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象。

    在介绍string类型的应用场景时有所介绍,string + json也是存储对象的一种方式,那么存储对象时,到底用string + json还是用hash呢?

    两种存储方式的对比如下表所示。

    string + jsonhash效率很高高容量低低灵活性低高序列化简单复杂

    当对象的某个属性需要频繁修改时,不适合用string+json,因为它不够灵活,每次修改都需要重新将整个对象序列化并赋值,如果使用hash类型,则可以针对某个属性单独修改,没有序列化,也不需要修改整个对象。比如,商品的价格、销量、关注数、评价数等可能经常发生变化的属性,就适合存储在hash类型里。

    当然,不常变化的属性存储在hash类型里也没有问题,比如商品名称、商品描述、上市日期等。但是,当对象的某个属性不是基本类型或字符串时,使用hash类型就必须手动进行复杂序列化,比如,商品的标签是一个标签对象的列表,商品可领取的优惠券是一个优惠券对象的列表(如下图所示)等,即使以coupons(优惠券)作为field,value想存储优惠券对象列表也还是要使用json来序列化,这样的话序列化工作就太繁琐了,不如直接用string + json的方式存储商品信息来的简单。

    # hash操作 #hash变更的数据user name age,尤其是是用户信息之类的,经常变动的信息! hash更适合于对象的存储, String更加适合字符串存储! hset myhash k1 v1 # 设置一个key-value hmget k1 k2 # 根据多个key获取value hmset myhash l1 v2 l3 v4 # 设置多个key-value hgetall myhash # 查询hash表的所有值 hdel myhash k1 # 删除指定的field hlen myhash # 获取hash表的field数量 HEXISTS myhash k2 # 判断hash表中是否存在这个field hkeys myhash # 只获得hash表中的所有field HVALS myhash # 只获得hash表中的所有value HINCRBY myhash k1 1 #自增1 hsetnx myhash k1 v1 # 如果不存在这个key则创建成功返回1,如果存在 则创建失败返回0

    5、Zset(有序集合)

    销量排名、积分排名、成绩排名等… 发表时间作为score存储,这样子获取的时候就是自动排好序的\成绩作为score带权重队列,让重要的任务优先执行 延时队列

    zset 会按 score 进行排序,如果 score 代表想要执行时间的时间戳。在某个时间将它插入zset集合中,它变会按照时间戳大小进行排序,也就是对执行时间前后进行排序。

    起一个死循环线程不断地进行取第一个key值,如果当前时间戳大于等于该key值的score就将它取出来进行消费删除,可以达到延时执行的目的。

    排行榜

    经常浏览技术社区的话,应该对 “1小时最热门” 这类榜单不陌生。如何实现呢?如果记录在数据库中,不太容易对实时统计数据做区分。我们以当前小时的时间戳作为 zset 的 key,把贴子ID作为 member ,点击数评论数等作为 score,当 score 发生变化时更新 score。利用 ZREVRANGE 或者 ZRANGE 查到对应数量的记录。

    限流

    滑动窗口是限流常见的一种策略。如果我们把一个用户的 ID 作为 key 来定义一个 zset ,member 或者 score 都为访问时的时间戳。我们只需统计某个 key 下在指定时间戳区间内的个数,就能得到这个用户滑动窗口内访问频次,与最大通过次数比较,来决定是否允许通过。

    # Zset操作 zadd sarly 5000 wangwu # 添加操作 ZRANGE sarly 0 -1 # 输出zset的所有值 ZRANGEBYSCORE sarly -inf +inf # 升序输出全部的用户 ZRANGEBYSCORE sarly -inf +inf withscores # 序输出全部的用户,且显示工资 ZRANGEBYSCORE sarly -inf +inf 2500 withscores # 序输出全部的用户,且显示大于2500的工资 ZREVRANGEBYSCORE sarly +inf -inf # 降序排序 zrem sarly lisi # 删除用户 zcard sarly # 获取有序集合的元素个数 ZREVRANGE sarly 0 -1 # 反转 zcount sarly 500 2500 # 获取指定区间的元素的数

    4、三大特殊数据类型常用操作

    1、geospatial(地理位置)

    geospatial常用命令

    GEOADD

    # getadd 添加地理位置 #规则:两级无法直接添加,我们一般会下载城市数据,直接通过java程序一 次性导入! #有效的经度从-180度到180度。 #有效的纬度从-85. 05112878度到85.05112878度。 #当坐标位置超出上述指定范围时,该命令将会返回一个错误。 GEOADD china:cipty 31.405 121.4894 shanghai # 添加地理位置

    GEODIST

    geodist china:cipty beijing shanghai km # 获取两个地理之间的距离,单位是km

    GEOHASH

    geohash china:cipty beijing shanghai # 将二维的经纬度转换为-维的字符串,如果两个字符串越接近,那么则距离越近!

    GEOPOS

    geopos china:cipty beijing #获取指定的城市的经度和纬度!

    GEORADIUS

    GEORADIUS china:cipty 110 30 1000 km # georadius以给定的经纬度为中心,找出某一半径内的元素

    GEORADIUSBYMEMBER

    GEORADIUSBYMEMBER china:cipty beijing 10000 km # 找出位于指定元素周围的其他元素!

    总结

    GEO底层的实现原理其实就是Zset !我们可以使用Zset命令来操作geo !

    删除查询…等操作

    2、Hyperloglog(基数统计)

    应用场景

    说明:

    基数不大,数据量不大就用不上,会有点大材小用浪费空间有局限性,就是只能统计基数数量,而没办法去知道具体的内容是什么和bitmap相比,属于两种特定统计情况,简单来说,HyperLogLog 去重比 bitmap 方便很多一般可以bitmap和hyperloglog配合使用,bitmap标识哪些用户活跃,hyperloglog计数

    一般使用:

    统计注册 IP 数统计每日访问 IP 数统计页面实时 UV 数统计在线用户数统计用户每天搜索不同词条的个数 PFadd mykey aIa b C defghij #创建第一组元素mykey PFCOUNT mykey # 统计mykey元素的基数数量 PFadd mykey2 i j z xcvbnm #创建第二组元素mykey2 PFMERGE mykey3 mykey mykey2 #合并两组mykey mykey2 => mykey3 并集 PFCOUNT mykey3 #看并集的数量!

    如果允许容错,那么-定可以使用Hyperloglog ! 如果不允许容错,就使用Iset或者自己的数据类型即可!

    3、Bitmaps (位图)

    统计用户信息,活跃,不活跃!登录、未登录!打卡, 365打卡!两个状态的,都可以使用Bitmaps !Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态!
    使用场景一:用户签到

    很多网站都提供了签到功能(这里不考虑数据落地事宜),并且需要展示最近一个月的签到情况.

    使用场景二:统计活跃用户

    使用时间作为cacheKey,然后用户ID为offset,如果当日活跃过就设置为1那么我该如果计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃),有请下一个redis的命令命令 BITOP operation destkey key [key ...] 说明:对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。

    使用场景三:用户在线状态

    bitmap是一个节约空间效率又高的一种方法,只需要一个key,然后用户ID为offset,如果在线就设置为1,不在线就设置为0.

    SETBIT sig 0 1 # 设置位图 GETBIT sig 6 # 获取位图 ################################ `用户签到`:模拟用户打卡场景,0-6为一周,0为未打卡,1为打卡 BITCOUNT sig # 统计用户一周打卡天数

    Processed: 0.024, SQL: 8