Redis十大数据类型
String
String 是redis最基本数据类型,一个key对应一个value. String类型是二进制安全的,意思是Redis的string类型可以包含任何数据,比如jpg图片或者序列化的对象;
String类型是最基本的数据类型,一个redis中字符串value最多是512M;
String类型在redis底层数据结构是SDS(简单动态字符串);
简单应用: incr可以实现 某篇文章点赞量 等功能
常用命令:
set key value //设置指定key值
get key //获取指定key值
getrange key start end //返回key中字符串的子字符 例如: getrange key1 0 -1; 返回value ;getrange key1 0 1 ; 返回v
getset key value //将指定key的值设置为value,并返回key的旧值;
getbit key offset //对key所存储的字符串值,获取指定偏移量上的位(bit)
mget key1 key2 key3 //获取所有(一个或多个给定key的值)
setbit key offset value//对key所储存的字符串值,设置或清楚指定偏移量上的位
setex key seconds value //将值value关联到key,并将key的过期时间设置为seconds(以s为单位)
setnx key value //只有在key不存在时 设置key的值
setrange key offset value//用value参数覆写给定key所存储的字符串值,从偏移量offset开始;
strlen key //返回key所存储的字符串值的长度
mset key value key1 value1 key2 value2. . .//同时设置一个或多个k-v对
msetnx key value[key value . . . ] //同时设置一个或多个key-value对,当且仅当所有给定key都不存在
psetex key milliseconds value // 和setex命令相似 , 但是它以毫秒为单位设置过期时间
incr key //将key中存储的数字值增1;
incrby key increment//将key所存储的值加上给定的增量值(incrment)
decr key;//将key中存储的数字值减一
decrby key decrement//将key所存储的值减去给定的减量值(decrment)
append key value//如果key已经存在并且是一个字符串,append命令将value追加到key原来的值的末尾
SDS
Redis底层是用C来实现的,C语言中没有java里面的string类型,只能靠自身的cha[]来实现,字符串在C语言中的存储方式,想要获取[redis]的长度,需要从头开始遍历,直到遇到'\0' 为止,所以,redis 没有直接使用C语言的字符串标识,而是自己构建了一种名为简单动态字符串SDS(simple dynamic string)的抽象类型,并将SDS作为Redis的默认字符串;
struct _attribute_((_packed_)) sdshdr8{
uint8_t len;
unit8_t alloc;
unsigned char flags;
char buf[];
};
struct _attribute_((_packed_)) sdshdr16{
uint16_t len;
unit16_t alloc;
unsigned char flags;
char buf[];
};
sds与char对比:
SDS类型RedisObject内部对应3大物理编码:
int: 保存long类型的64位有符号整数,最小值-2^63; 最大值是2^63-1; 默认值是0L; 最多19位数字;另外 只有整数才会使用int,如果是浮点数,Redis内部其实先将浮点数转化为字符串值,然后在保存;
当执行 set k1 123 时:
当字符串键值内容可以有那个一个64位有符号整形来表示时,Redis会将键值转化为long类型进行存储,此时即对应OBJ_ENCODING_INT 编码类型; Redis启动会预先建立1W个分别存储0到9999的redisObject变量作为共享对象,折旧意味着如果set字符串的键值在0~10000之间的化,则可以直接使用共享对象,而不需要在建立新对象,此时键值不占空间;
embstr(embedded string):嵌入式的字符串; 代表embstr格式的sds(simple dynamic string 简单动态字符串),保存长度小于44字节的字符串;
当执行set k1 abc 时:
对于长度小于44的字符串,Redi对键值采用OBJ_EnCODING_EMBSTR方式,从内存结构上讲,即字符串sds结构体与对应的Redisobject对象分配在同一块连续的内存空间;
raw:长度超过44字节的字符串
当字符串长度大于44时,redis则会将键值的内部编码方式改为OBJ_ENCODING_RAW格式,这与embstr编码方式不同之处在于,此时动态字符串sds的内存与依赖的redisObject的内存不连续了;
另外,对于embstr,由于其实现是只读的,如果对其进行修改,都是先转为raw在进行修改,因此,只要是修改embstr对象,修改后的对象一定是raw的,无论格式是否到了44字节;判断不出来,就取最大Raw
获取编码格式命令:object encoding key
总结:
List
Redis list 是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边).它的底层实际是个双端链表,最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表超过40亿个元素). 主要功能有push/pop等,一般用在栈、队列、消息队列等场景。left、right都可以插入添加;如果键不存在,创建新的链表;如果键已存在,新增内容;如果值全移除,对应的键也就消失了。
常用命令:
blpop key1 [key2] timeout//移出并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
brpop key1 [key2] timeout//移出并获取列表的最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
lpush/rpush/lrange //在左/右/指定位置 新增元素
lpop/rpop //弹出最左 /最后侧一个元素
lindex key index //按照索引下标获得元素(从上到下) ( lindex list2 3 获取list2 下标是3的元素)
llen //获取列表中元素的个数
lrem key n v1 ; //删除列表中N个值等于v1 的元素
ltrim key 开始index 结束index;//截取指定范围的值后再赋值给key
rpoplpush list2 list 3 //移除list2列表的最后一个元素,并将该元素加到list3列表 并返回
lset key index value// 修改列表指定index的值 为value
linsert key before/after 已有值 插入的新值;//在list某个已有值的前后再添加具体值(linsert list3 before oracle mysql 在list3中 oracle前插入mysql)
简单应用: 关注多个公众号 文章推送(例如: 公众号 1 和公众号2 发布了两篇文章 分别是11和22; 账号A关注了这两个公众号,只要他们发布新文章,就会安装进A的list :lpush likearticle:A-id 11 12 ; A 查看自己订阅号的全部文章,类似于分页 :lrange likearticle:A-id 0 9)
Redis List 底层数据结构
在Redis3.0之前,list采用的底层数据结构是ziplist(压缩列表)+linkedList(双向链表)
然而在高版本的redis中底层数据结构是quicklist(替换了ziplist+linkedList),而quicklist也用到了ziplist;quicklist就是一个链表,而链表中的每个元素又是一个压缩列表;
比较早版本的redis中,list底层两种实现:
1.当列表对象中元素的长度比较小或者数量比较少的时候,采用ziplist存储
2.当列表中元素的长度比较大或者数量比较多 则会转而使用linkedlist存储;
优缺点:
1.ziplist的优点是内存紧凑,访问效率高,缺点是更新效率低,并且数据量较大时,可能导致大量内存复制;
2.linkedlist的优点是 节点修改效率高,但是需要额外的内存开销,并且节点较多时,会产生大量内存碎片;
综上所述,在redis3.2之后,list的底层实现变为快速列表 quicklist;
quicklist
redis6中, quicklist实际上是ziplist和linkedList的混合体,它将linkedlist按段切分,每一段用ziplist来紧凑存储,多个ziplist之间使用双向指针串接起来,*zl指向一个ziplist,一个ziplist可以存放多个元素.
简单来说,一个 quicklist 就是一个链表,而链表中的每个元素又是一个 ziplist。这种设计减少了数据插入时内存空间的重新分配,以及内存数据的拷贝。同时,quicklist 限制了每个节点上 ziplist 的大小,一旦一个 ziplist 过大,就会采用新增 quicklist 节点的方法。
不过,又因为 quicklist 使用 quicklistNode 结构指向每个 ziplist,无疑增加了内存开销。为了减少内存开销,并进一步避免 ziplist 连锁更新问题,Redis 在 5.0 版本中,就设计实现了 listpack 结构。listpack 结构沿用了 ziplist 紧凑型的内存布局,把每个元素都紧挨着放置。
redis7中用listpack代替了ziplist ; 所以,在redis7中 quicklist是listpack和linkedlist的结合体;
ziplist和listpack
ziplist
ziplist结构如下:
privious_entry_length,encoding长度都可以根据编码方式推算,真正变化的是content,而content长度记录在encoding里 ,因此entry的长度就知道了。entry总长度 = privious_entry_length字节数+encoding字节数+content字节数.
链表在内存中,一般是不连续的,遍历相对比较慢,而ziplist可以很好的解决这个问题。如果知道了当前的起始地址,因为entry是连续的,entry后一定是另一个entry,想知道下一个entry的地址,只要将当前的起始地址加上当前entry总长度。如果还想遍历下一个entry,只要继续同样的操作。
从 ziplist 的设计不足出发,ziplist 的最大特点,就是它被设计成一种内存紧凑型的数据结构,占用一块连续的内存空间,以达到节省内存的目的。但是,在计算机系统中,任何一个设计都是有利有弊的。对于 ziplist 来说,这个道理同样成立。虽然 ziplist 节省了内存开销,可它也存在两个设计代价:一是不能保存过多的元素,否则访问性能会降低;二是不能保存过大的元素,否则容易导致内存重新分配,甚至可能引发连锁更新的问题。所谓的连锁更新,简单来说,就是 ziplist 中的每一项都要被重新分配内存空间,造成 ziplist 的性能降低.
连锁更新:
压缩列表每个节点正因为需要保存前一个节点的长度字段,就会有连锁更新的隐患
第一步:现在假设一个压缩列表中有多个连续的、长度在 250~253 之间的节点,如下图:
因为这些节点长度值小于 254 字节,所以 prevlen 属性需要用 1 字节的空间来保存这个长度值.
第二步:这时,如果将一个长度大于等于 254 字节的新节点加入到压缩列表的表头节点,即新节点将成为entry1的前置节点,如下图:
因为entry1节点的prevlen属性只有1个字节大小,无法保存新节点的长度,此时就需要对压缩列表的空间重分配操作并将entry1节点的prevlen 属性从原来的 1 字节大小扩展为 5 字节大小。
第三步:连续更新问题出现
entry1节点原本的长度在250~253之间,因为刚才的扩展空间,此时entry1节点的长度就大于等于254,因此原本entry2节点保存entry1节点的 prevlen属性也必须从1字节扩展至5字节大小。entry1节点影响entry2节点,entry2节点影响entry3节点......一直持续到结尾。这种在特殊情况下产生的连续多次空间扩展操作就叫做「连锁更新」
listpack
listpack 是 Redis 设计用来取代掉 ziplist 的数据结构,它通过每个节点记录自己的长度且放在节点的尾部,来彻底解决掉了 ziplist 存在的连锁更新的问题.
listpack由四部分组成,如下图:
total bytes: 为整个listpack的空间大小,占用4个字节,每个listpack最多占用4294967295Bytes(2^32-1).
number-elements: listpack中的元素个数,即Entry的个数占用2个字节
element-1~element-N : 具体的元素
listpack-end-byte:为listpack结束标志,占用一个字节,内容为0xff
总结:
zipList为了节省内存 采用了紧凑的连续存储,ziplist是一个双向链表,可以再时间复杂度为O(1)下从头部或者尾部进行pop或push; 缺点是:新增或更新元素可能会出现连锁更新现象,不能保存过多的元素否则查询效率会降低,数量小核内容小的情况下可以使用;
和ziplist列表类似,listpack列表项也包含了元数据信息和数据本身,不过为了避免ziplist引起连锁更新问题,listpack中每个列表项不在像ziplist列表项那样保存其前一个列表的长度.
Hash
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)
常用命令:
hset/hget/hmset/hmget/hgetall/hdel //插入/获取/批量插入/批量获取/获取全部/删除
hlen //后续某个key内的全部数量
hexits key filed //key里面是否存在filed的值
hkeys/hvals //hkeys hash2 (返回hash2下所有filed), hvals hash2(返回hash2下所有value)
hincrby/hincrbyfloat key file number//指定key filed 增加number 浮点数用float
hsetnx key file value // 不存在赋值返回1; 存在无效返回0;
简单应用场景: 简单购物车设计
购物车新增商品 - hset shopcar:uid1024 334488 1
新增商品一 hset shopcar:uid1024 334477 1
增加商品数量 一 hincrby shopcar:uid1024 3344771
商品总数 一 hlen shopcar:uid 1024
全部选择一 hgetall shopcar:uid1024
redis6中hash底层是ziplist+hashtable; redis7中 用listpack替代了ziplist;
默认情况向 hash对象保存的键值对数量小宇512个,并且所有键值对的键和值的字符串长度都小宇64byte时用ziplist(redis7用listpack),反之用hashtable; ziplist/listpack 可以升级到hashtable,但是不可以反过来降级; 一旦从ziplist/listpack升级到hashtable ,hash类型就会一直使用hashtable进行保存,而不能转回去; 在节省内存空间方面hashtable没有ziplist/listpack 高效.
这两个参数也可以通过设置来改变:
hash-max-ziplist-entries/hash-max-listpack-entries: 使用ziplist/listpack 保存时哈希集合中的最大元素个数;
hash-max-ziplist-value/hash-max-listpack-value:使用ziplist/listpack 保存时哈希集合中单个元素的最大长度
Set
Redis的set是String类型的无序集合.集合成员是唯一的,这就意味着集合中不能出现重复的数据,集合对象的编码可以是intset或者hashtable.
redis中set集合是通过hashtable实现的,所以添加,删除,查找的复杂度 都是O(1);集合中最大成员数为2^32-1(每个集合可存储40多亿个成员);
常用命令:
sadd key member[member ...] //新增
smembers key //遍历集合中的所有元素
sismember key member //判断元素是否存在集合中
srem key member [member ...] //删除集合中元素
scard //scard set1 获取set1集合中元素个数
srandmember key [数字N] //从集合中随机展现N个元素,元素不删除 如果超过最大值,则全部取出
spop key [数字N] //从集合中随机弹出元素,出一个删一个
smove key1 key2 v1;//将key1 中的v1 赋值给key2
//========= 集合运算 A - abc12 B-123ax
sidff key [key...] // 属于A但不属于B的元素构成的集合
sunion key [key...] //属于A或者属于B的元素合并后的集合
sinter key [key...] //属于A同时也属于B的共同拥有的元素构成的集合
sintercard numkeys key [key...] [limit n] //numkeys 表示几个去重的集合 命令如 sintercard 2 key1 key2 --返回key1 和key2 两个集合交集的元素个数; sintercard 3 key1 key2 key3 limit 4 --返回key 1 2 3 交集元素个数 如果超过4 则返回4 否则返回真实个数
简单应用:
//=====微信抽奖小程序:
sadd key userID//新增用户ID参与抽奖
scard key//显示已经有多少人参与活动
//抽取N个人中奖 :
srandmember key N //不删除抽取 ;
spop key N //删除抽取
//=======微信朋友圈点赞查看同赞朋友:
sadd pub:msgID userID1 userID2//user1 user2点赞
srem pub:msgID userID2 //user2取消点赞
smembers pub:msgID //展现点赞的用户
scard pub:msgID //获取点赞的用户数
sismember pub:msgID userID3//判断user3 是否点过赞
Redis用intset或者hashTable存储set,如果元素都是整数类型,就用intset存储.如果不是整数类型,就用hashtable(数组+链表的结构来存储).key就是元素的值,value为null
集合元素都是long类型,并且元素个数<=set-max-intset-entries 编码就是intset,反之就是hashtable;
ZSet
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复,zset集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 2^32 - 1;
常用命令:
zadd key score member [score member] //添加元素
zrange key start stop [withscores] //按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素
zrevrange key start stop [withscores] //按照元素分数从大到小的顺序返回索引从start到stop之间的所有元素
zrangebyscore key min max [withscores] [limit offset count] //获取指定分数范围的元素
(是不包含的意思; zrangebyscore zset1 (60 90 withscores (返回60到90之间的key和value 不包含60)
zscore key member //获取元素分数
zcard zset1//获取zset1元素个数
zcount zset1 60 90 //获取分数区间内元素个数
zrank // 获取value在zset中的下标位置 zrank zset1 v2 (获取zset1中v2的下标)
zscore zset1 v1 // 按照值获取对应分数
zrem key 某score下对应的value值//删除
zincrby key increment member //增加某个元素的分数
zcount key min max //获取指定分数范围内的元素个数
zmpop //从键名列表中的第一个非空排序集中弹出一个或多个元素,他们是成员分数对(key value score)
zrank key values //获取下标值
zrevrank key values //逆序获取下标值
应用场景:
//=====根据商品销售对商品进行排序显示
//===定义商品销售排行榜(sorted set集合),key为goods:sellsort,分数为商品销售数量
zadd goods:sellsort 9 1001 15 1002 // 商品编号1001 的销量是9,编号1002的销量是15
zincrby goods:sellsort 2 1001 //有一个客户买了2件商品1001,商品编号1001销量加2
zrange goods:sellsort 0 9 withscores //求商品销量前10名
当有序集合中包含的元素数量超过服务器属性 server.zset_max_ziplist_entries 的值(默认值为 128 ),或者有序集合中新添加元素的 member 的长度大于服务器属性server.zset_max_ziplist_value 的值(默认值为 64 )时,redis会使用跳跃表作为有序集合的底层实现。否则会使用ziplist作为有序集合的底层实现(redis7用listpack实现)
跳表
对于一个单链表来讲,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。这样查找效率就会很低,时间复杂度会很高O(N)
对单链表进行升维,也叫空间换时间.如下:
从这个例子里,我们看出,加来一层索引之后,查找一个结点需要遍历的结点个数减少了,也就是说查找效率提高了.
skiplist 是一种以空间换取时间的结构.由于链表无法进行二分查找,因此借鉴数据库索引的思想,提取出链表中关键节点(索引),先在关键节点上查找,在进入下层链表查找,提取多层关键节点,就形成了skiplist. 但是 由于索引也需要占据一定空间,所以,索引添加的越多,空间占用的越多
总结:
跳表=链表+多级索引
跳表的时间复杂度O(logn) ;空间复杂度O(N)
优点:跳表是一个最典型的空间换时间解决方案,而且只有在数据量较大的情况下才能体现出来优势。而且应该是读多写少的情况下才能使用,所以它的适用范围应该还是比较有限的
缺点:维护成本相对要高,在单链表中,一旦定位好要插入的位置,插入结点的时间复杂度是很低的,就是O(1) ,但是,新增或者删除时需要把所有索引都更新一遍,为了保证原始链表中数据的有序性,我们需要先找到要动作的位置,这个查找操作就会比较耗时最后在新增和删除的过程中的更新,时间复杂度也是O(log n)
GEO
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,包括:添加地理位置的坐标。获取地理位置的坐标。计算两个位置之间的距离。根据用户给定的经纬度坐标来获取指定范围内的地理位置集合
常用命令
geoadd key longitude latitude member [longitude latitude member ...]//添加经纬度坐标
geopos key member [member ...]//从给定的key里返回所有的指定名称(member)的位置,不存在的返回null
geohash key member [member...] //获取一个或多个元素的geohash值
geodist key member1 member2 [m|km|ft|mi] // 用于返回两个给定位置之间的距离
georadius //以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
GEORADIUS city 116.418017 39.914402 10 km withdist withcoord count 10 withhash desc //WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。WITHCOORD: 将位置元素的经度和维度也一并返回。WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大 ; COUNT 限定返回的记录数。
georadiusbymember city 天安门 10 km withdist withcoord count 10 withhash//找出位于指定范围内的元素,中心点是由给定的位置元素決定
简单应用:
//=========地图附近的酒店推送
@Service
@Slf4j
public class GeoService
{
public static final String CITY ="city";
@Autowired
private RedisTemplate redisTemplate;
//新增地点
public String geoAdd()
{
Map<String, Point> map= new HashMap<>();
map.put("天安门",new Point(116.403963,39.915119));
map.put("故宫",new Point(116.403414 ,39.924091));
map.put("长城" ,new Point(116.024067,40.362639));
redisTemplate.opsForGeo().add(CITY,map);
return map.toString();
}
//获取经纬度坐标
public Point position(String member) {
List<Point> list= this.redisTemplate.opsForGeo().position(CITY,member);
return list.get(0);
}
//geohash算法生成的base32编码值
public String hash(String member) {
List<String> list= this.redisTemplate.opsForGeo().hash(CITY,member);
return list.get(0);
}
//获取两个给定位置之间的距离
public Distance distance(String member1, String member2) {
Distance distance= this.redisTemplate.opsForGeo().distance(CITY,member1,member2, RedisGeoCommands.DistanceUnit.KILOMETERS);
return distance;
}
//通过经度,纬度查找附近的,北京王府井位置116.418017,39.914402
public GeoResults radiusByxy() {
Circle circle = new Circle(116.418017, 39.914402, Metrics.KILOMETERS.getMultiplier());
//返回50条
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(50);
GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults= this.redisTemplate.opsForGeo().radius(CITY,circle, args);
return geoResults;
}
public GeoResults radiusByMember() {
//通过地方查找附近
String member="天安门";
//返回50条
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(50);
//半径10公里内
Distance distance=new Distance(10, Metrics.KILOMETERS);
GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults= this.redisTemplate.opsForGeo().radius(CITY,member, distance,args);
return geoResults;
}
}
HyperLogLog
HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素
Redis使用了214=16384个桶,误差为0.81%,精度相当高。Redis使用一个long型哈希值的前14个比特用来确定桶编号,剩下的50个比特用来做基数估计。而2^6=64,所以只需要用6个比特表示下标值,在一般情况下,
一个HLL数据结构占用内存的大小为16384*6/8=12kB,Redis将这种情况称为密集(dense)存储。
常用命令:
pfadd key element [element...] //添加指定元素到hyperloglog中
pfcount key [key...] 返回给定hyperloglog 的技术估算值
pfmerge destkey sourcekey [sourcekey...]//将多个hyperloglog合并为一个hyperloglog
demo
//=====获取去重后某个页面的访问量====
//=========
@Service
@Slf4j
public class HyperLogLogService
{
@Resource
private RedisTemplate redisTemplate;
/**
* 模拟后台有用户点击首页,每个用户来自不同ip地址
*/
@PostConstruct
public void init()
{
log.info("------模拟后台有用户点击首页,每个用户来自不同ip地址");
new Thread(() -> {
String ip = null;
for (int i = 1; i <=200; i++) {
Random r = new Random();
ip = r.nextInt(256) + "." + r.nextInt(256) + "." + r.nextInt(256) + "." + r.nextInt(256);
Long hll = redisTemplate.opsForHyperLogLog().add("hll", ip);
log.info("ip={},该ip地址访问首页的次数={}",ip,hll);
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
}
},"t1").start();
}
@ApiOperation("获得IP去重后的首页访问量")
@RequestMapping(value = "/uv",method = RequestMethod.GET)
public long uv()
{
return redisTemplate.opsForHyperLogLog().size("hll");
}
}
bigmap
由0和1状态表现的二进制位的bit数组
说明:用String类型作为底层数据结构实现的一种统计二值状态的数据类型.位图本质是数组,它是基于String数据类型的按位的操作。该数组由多个二进制位组成,每个二进制位都对应一个偏移量(我们称之为一个索引)。
Bitmap支持的最大位数是232位,它可以极大的节约存储空间,使用512M内存就可以存储多达42.9亿的字节信息(232 = 4294967296)
常用命令:
setbit key offset value //设置 setbit zhangsan 1 1 ; setbit zhangsan 4 1; 设置张三1号 4号打卡
getbit key offset //
strlen key //获取占用的字节,超过8位后自己按照8位一组-byte在扩容
bitcount key start end //返回指定key中[start end]中为1的数量 bitcount zhangsan ; 返回2
bitop operation destkey key//对不同的二进制存储数据进行位运算(AND、OR、NOT、XOR) (bittop and destkey 20200808 20200809 返回连续两天签到的bigmap ; bitcount destkey 返回2 )
bitfield
通过bitfield命令可以一次性操作多个比特位域(指的是连续的多个比特位),它会执行一系列操作并返回一个响应数组,这个数组中的元素对应参数列表中的相应操作的执行结果。说白了就是通过bitfield命令我们可以一次性对多个比特位域进行操作。
Stream
Redis Stream 是 Redis 5.0 版本新增加的数据结构。
Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。
简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。
参考文档:https://blog.csdn.net/weixin_45735834/article/details/126559660