redis高阶第二篇:redis可使用场景

1、存储二元记录

"二元"中的"元"是变量的意思,一般是用户和时间这两个变量,存储某用户在某天是否干了特定的事,如打卡记录,张三在今天是否打了卡,张三在昨天是否打了卡,李四在今天是否打了卡,李四在昨天是否打了卡。。。

采用setbit、getbit命令。操作的是string类型的key,把value当做bit数组,也叫bitmap,中文是位图。setbit key offset value,offset从0开始,value只能是0和1,0代表没干,1表示干了。如setbit clockIn:zhangsan 0 1,表示张三在基准日干了特定的事。需要定某一天为基准日,这一天的offset为0,从这一天开始,offset一直加1,如假定2025年6月23日为基准日,那么当天的offset为0,2025年6月24日的offset为1,2025年6月25日的offset为2,以此类推。。。offset是个uint32,最大值是42亿多。

setbit返回的是执行setbit之前的bit值,如第一次执行setbit clockIn:zhangsan 0 1会返回0,再次执行则会返回1。bit数组可以用set函数初始化,如set clockIn:zhansgan a,则会创建一个byte,也可以说是一个长度为8的bit数组,值分别是0、1、1、0、0、0、0、1,在打卡记录这个场景下,就是只有第二、三、八天打了卡,其他天都没有打卡。set clockIn:zhansgan '人'会创建三个byte,即长度为24的bit数组。在UTF8字符集中,一个英文字符占一个字节。一个汉字占三个字节。所以这也说明redis存储字符串是以UTF8字符集存储的。strlen函数返回的是字符串占用的字节数,如set a 1,则strlen a会返回1,set a f,则strlen a会返回1,set a '人',则strlen a会返回3。setrange、getrange也是以字节为基础的。如set name '张三',则getrange name 0 2会返回'张',setrange name 0 kou,get name会返回'kou三'。

如果想查张三在某天打卡了没有,则需要计算出这天对应的offset,假如是20,则执行getbit clockIn:zhangsan 20即可,如果返回的是1,则表示打卡了,否则表示没有打卡。

如果offset是稀疏的,比如把userId当做offset,那么就需要压缩了,否则不仅不会节省内存,还会浪费内存。roaring bitmap是压缩的bitmap,要想在redis中使用roaring bitmap,直接使用相应镜像即可,见https://github.com/aviggiano/redis-roaring

其他操作还有:

bitcount:bitcount key [start] [end],看有多少处offset的值是1。setbit clockIn:zhangsan 0 1,setbit clockIn:zhangsan 1 1,则bitpos clockIn:zhangsan返回2。

bitpos:bitpos key bit [start] [end],看第一个bit的offset,如setbit clockIn:zhangsan 0 1,setbit clockIn:zhangsan 1 0,则bitpos clockIn:zhangsan 0返回1,bitpos clockIn:zhangsan 1返回0。

bitop:bitop operation destkey key [key ...],对1个或多个bitmap进行逻辑操作,并把结果放到一个新的bitmap中。operation的可选值有and、or、xor、not。

如把bm1设置成1、1、0、0数组、bm2设置成1、0、0、1数组,则

若执行bitop and bm3 bm1 bm2,则bm3数组是1、0、0、0、0、0、0、0。

若执行bitop or bm4 bm1 bm2,则bm4数组是1、1、0、1、0、0、0、0。

若执行bitop xor bm5 bm1 bm2,则bm5数组是0、1、0、1、0、0、0、0。

若执行bitop not bm6 bm1,则bm6数组是0、0、1、1、1、1、1、1。

如果在clockIn:${date}中存储了这一天的打卡人,那么bitop and某2天的key,就可以知道这两天都打卡的人。bitop or最近30天的key,就可以算出月活。

2、存储好友关系

用有序集合来存储粉丝列表和关注列表。

存储某用户的粉丝列表,member是粉丝id,score是粉丝关注该用户的时间的时间戳。

存储某用户的关注列表,member是关注的用户的id,score是关注时间对应的时间戳。

假如zhangsan在2025-06-24 23:30:30(对应的时间戳是1750779030000)关注lisi,则需要

①把zhangsan放到lisi的粉丝列表中,执行zadd followers:lisi 1750779030000 zhangsan。

②把lisi放到zhangsan的关注列表中,执行zadd followings:zhangsan 1750779030000 lisi。

3、存储地理位置

假设A的坐标是(113.92,22.54)、B的坐标是(114.12,22.60),想存储A、B的话,需要执行geoadd locations 113.92 22.54 A 114.12 22.60 B。geoadd支持一次性存储多个位置。

若想查询A的坐标,则需要执行geopos locations A,会返回两个值,第一个值是113.92,第二个值是22.54。

若想计算A、B间的距离,则需要执行geodist locations A B,会返回21598.0950,表示A、B相距21598m。如果想以km为单位,则需要执行geodist locations A B km,会返回21.5981,表示A、B相距21.5981km。

若想查询A周围25km范围内的用户,则需要执行geosearch locations frommember A byradius 25 km

4、构造定长队列、淘汰队列

定长队列是指在队列元素数量超过限制时自动移除新增的元素,淘汰队列是指在队列元素数量超过限制时自动移除最老的元素。两者都是使用列表存储数据,rpush后ltrim,不同的是ltrim时定长队列保留的是前面的元素,淘汰队列保留的是后面的元素。

如果想让列表键list1只保留最老的5个元素,则在每次rpush list1后还需要执行ltrim list1 0 4。

如果想让列表键list2只保留最新的5个元素,则在每次rpush list2后还需要执行ltrim list2 -5 -1。

5、构造优先队列

使用有序集合存储数据,score存储优先级。使用zpopmin拿到优先级最小的元素,使用zpopmax拿到优先级最大的元素。

6、构造循环队列

使用列表存储数据,多次使用lmove函数把同一个key的最左边元素移到最右边,同时返回这个元素,如依次往list2插入1、2、3后,第一次执行lmove list2 list2 left right会返回1,第二次执行lmove list2 list2 left right会返回2,第三次执行lmove list2 list2 left right会返回3,第四次执行lmove list2 list2 left right会返回1,第五次执行lmove list2 list2 left right会返回2,第六次执行lmove list2 list2 left right会返回3。。。这样,就可以循环访问列表中的元素。

7、存储短网址映射

在分享时,如果链接上参数太多,就会导致链接太长,最好分享出去的是一个短链接。短链接生成:把一个10进制的整数转成62进制。如利用分布式自增器得到一个整数,假如是1000000,其转成62进制是4c92,则这个分享链接就是share.xxx.com/4c92。把这个短链接作为key,把长链接作为value,存到redis中。这样,当在浏览器中访问share.xxx.com/4c92时,就调用接口,302重定向到长链接或者在服务器内部直接跳转到长链接。

302跳转:

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func main() {
    r := gin.Default()

    r.GET("/ping", func(c *gin.Context) {
        c.Redirect(http.StatusFound, "/ping1")
    })
    r.GET("/ping1", func(c *gin.Context) {
        c.String(200, "ping1")
    })

    r.Run()
}

服务器内部直接跳转:

package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.GET("/ping", func(c *gin.Context) {
        c.Request.URL.Path = "/ping1"
        r.HandleContext(c)
    })
    r.GET("/ping1", func(c *gin.Context) {
        c.String(200, "ping1")
    })

    r.Run()
}

8、

posted on 2025-06-13 01:00  koushr  阅读(19)  评论(0)    收藏  举报

导航