环境:docker
版本:6.2.5
从 Redis 官网下载好最新的配置文件并修改:
appendonly yes cluster-enable yes cluster-config-file nodes.conf cluster-node-timeout 5000
除官方文档写的修改这几个配置外,还需要修改:
protect-mode no
并且注释掉:
# bind 127.0.0.1 -::1
否则在集群的过程中会报错。
内存不够需要指定最大内存 maxmemory 307200
。
解决脑裂情况下出现脏数据:
# 一个 master 最少有一个 slave (填的值要大于等于集群数量的一半,本次部署六个 Redis 采用一主一从,三个分片,所以填 1),这个参数会一定程度上影响可用性,slave 要是少于 1 个,这个集群就算 leader 正常也不能提供服务了 min-replicas-to-write 1 # slave 连接到 master 的最大延迟时间 min-replicas-max-lag 10
内存淘汰机制:
maxmemory-policy allkeys-lru # maxmemory-policy一共有8个值,当内存不足时: # noeviction: 不删除,直接返回报错信息。 # allkeys-lru:移除最久未使用(使用频率最少)使用的key。推荐使用这种。 # volatile-lru:在设置了过期时间key中,移除最久未使用的key。 # allkeys-random:随机移除某个key。 # volatile-random:在设置了过期时间的key中,随机移除某个key。 # volatile-ttl: 在设置了过期时间的key中,移除准备过期的key。 # allkeys-lfu:移除最近最少使用的key。 # volatile-lfu:在设置了过期时间的key中,移除最近最少使用的key。
version: "3.9" services: redis_1: image: redis container_name: redis_1 ports: - "6379:6379" volumes: - "/usr/local/docker/redis/redis_1/conf/redis.conf:/usr/local/etc/redis/redis.conf" - "/usr/local/docker/redis/redis_1/data:/data" restart: always privileged: true command: ["redis-server", "/usr/local/etc/redis/redis.conf"] networks: redis_cluster: ipv4_address: 172.28.0.2 redis_2: image: redis container_name: redis_2 ports: - "6380:6379" volumes: - "/usr/local/docker/redis/redis_2/conf/redis.conf:/usr/local/etc/redis/redis.conf" - "/usr/local/docker/redis/redis_2/data:/data" restart: always privileged: true command: ["redis-server", "/usr/local/etc/redis/redis.conf"] networks: redis_cluster: ipv4_address: 172.28.0.3 redis_3: image: redis container_name: redis_3 ports: - "6381:6379" volumes: - "/usr/local/docker/redis/redis_3/conf/redis.conf:/usr/local/etc/redis/redis.conf" - "/usr/local/docker/redis/redis_3/data:/data" restart: always privileged: true command: ["redis-server", "/usr/local/etc/redis/redis.conf"] networks: redis_cluster: ipv4_address: 172.28.0.4 redis_4: image: redis container_name: redis_4 ports: - "6382:6379" volumes: - "/usr/local/docker/redis/redis_4/conf/redis.conf:/usr/local/etc/redis/redis.conf" - "/usr/local/docker/redis/redis_4/data:/data" restart: always privileged: true command: ["redis-server", "/usr/local/etc/redis/redis.conf"] networks: redis_cluster: ipv4_address: 172.28.0.5 redis_5: image: redis container_name: redis_5 ports: - "6383:6379" volumes: - "/usr/local/docker/redis/redis_5/conf/redis.conf:/usr/local/etc/redis/redis.conf" - "/usr/local/docker/redis/redis_5/data:/data" restart: always privileged: true command: ["redis-server", "/usr/local/etc/redis/redis.conf"] networks: redis_cluster: ipv4_address: 172.28.0.6 redis_6: image: redis container_name: redis_6 ports: - "6384:6379" volumes: - "/usr/local/docker/redis/redis_6/conf/redis.conf:/usr/local/etc/redis/redis.conf" - "/usr/local/docker/redis/redis_6/data:/data" restart: always privileged: true command: ["redis-server", "/usr/local/etc/redis/redis.conf"] networks: redis_cluster: ipv4_address: 172.28.0.7 networks: redis_cluster: ipam: driver: default config: - subnet: 172.28.0.0/16
需要建立虚拟网卡,指定 IP 地址,因为 Redis 貌似不支持通过容器名区访问。
输入 docker-compose up -d
启动 6 个 Redis。
输入 docker exec -it redis_1 redis-cli --cluster create 172.28.0.2:6379 172.28.0.3:6379 172.28.0.4:6379 172.28.0.5:6379 172.28.0.6:6379 172.28.0.7:6379 --cluster-replicas 1
建立集群。
创建文件夹 /usr/local/docker/redis/redis_insight/data
。
并给 data 文件夹赋权限 chmod 777 -R /usr/local/docker/redis/redis_insight/data
。
输入 docker run --name redis_insight -d -p 8001:8001 -v /usr/local/docker/redis/redis_insight/data:/db --restart=always --privileged=true redislabs/redisinsight
创建容器。
打开防火墙端口(云服务器需要添加安全组):
firewall-cmd --zone=public --add-port=8001/tcp --permanent firewall-cmd --zone=public --add-port=6379/tcp --permanent
访问 http://{服务器 ip}:8001
就可以看到可视化界面了。
set {key} {value}
get {key}
incr {key}
decr {key}
incrby {key} {incr value}
decrby {key} {decr value}
append {key} {value}
使用该命令后会编码方式会直接变为
raw
setnx {key} {value}
mget {key} [{key}...]
getset {key} {value}
strlen {key}
del {key}
lpush {key} {value} [{value}...]
rpush {key} {value} [{value}...]
lpop {key} [{count}]
rpop {key} [{count}]
lrange {key} {start} {stop}
llen {key}
lindex {key} {index}
lrem {key} {count} {value}
count > 0:从表头开始,移除与 value 相同的值,数量为 count
count < 0:从表尾开始,移除与 value 相同的值,数量为 count
count = 0:移除表中所有与 count 相同的值
lset {key} {index} {value}
hset {key} {field} {value} [{field} {value}...]
hsetnx {key} {field} {value}
hget {key} {value}
hmget {key} {value} [{field} {value}...]
hgetall {key}
hvals key
hlen {key}
hkeys {key}
`hdel {key} {field} [{field}...]
hexitst {key} {field}
sadd {key} {value} [{value}...]
scard {key}
sinter key [{key}...]
sismember {key} {value}
smembers {key}
srandmember {key} {count}
srem {key} {value} [{value}...]
zadd {key} {score} {value} [{score} {value}...]
zcard {key}
zcount {key} {min} {max}
zincrby {key} {increment} {member}
zrange {key} {start} {stop} [withscores]
zrank {key} {value}
zrem {key} {value} [{value}...]
zrevrange {key} {start} {stop} [withscores]
zscore {key} {value}
Redis 自己定义的对象,类似 Java 一个存放字符的集合
struct sdshdr{ //int 记录buf数组中未使用字节的数量 如上图free为0代表未使用字节的数量为0 int free; //int 记录buf数组中已使用字节的数量即sds的长度 如上图len为5代表未使用字节的数量为5 int len; //字节数组用于保存字符串 sds遵循了c字符串以空字符结尾的惯例目的是为了重用c字符串函数库里的函数 char buf[]; }
时间复杂度:
操作 | 时间复杂度 |
---|---|
获取长度 | O(1) |
获取未使用空间 | O(1) |
清除保存内容 | O(1) |
创建长度为 n 的字符串 | O(n) |
拼接长度为 n 的字符串 | O(n) |
作用:
杜绝缓冲区溢出
C字符串,如果程序员在字符串修改的时候如果忘记给字符串重新分配足够的空间,那么就会发生内存溢出。
减少字符串操作中的内存重分配次数
在C字符串中,如果对字符串进行修改,那么我们就不得不面临内存重分配。因为C字符串是由一个N+1长度的数组组成,如果字符串的长度变长,我们就必须对数组进行扩容,否则会产生内存溢出。而如果字符串长度变短,我们就必须释放掉不再使用的空间,否则会发生内存泄漏。
二进制安全
C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。
双向链表
// 节点 typedef struct listNode { // 前置节点 struct listNode *prev; // 后置节点 struct listNode *next; // 节点的值 void *value; } listNode; // 链表 typedef struct list{ //表头节点 listNode *head; //表尾节点 listNode *tail; //链表所包含的节点数量 unsigned long len; //节点值复制函数 void *(*dup)(void *ptr); //节点值释放函数 void *(*free)(void *ptr); //节点值对比函数 int (*match)(void *ptr,void *key); }list;
拥有双向链表的所有优点
基于哈希表的实现
typedef struct dict{ //类型特定函数 void *type; //私有数据 void *privdata; //哈希表-见2.1.2 dictht ht[2]; //rehash 索引 当rehash不在进行时 值为-1 int trehashidx; }dict;
type属性是一个指向dictType结构的指针,每个dictType用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
privdata属性则保存了需要传给给那些类型特定函数的可选参数。
typedef struct dictType { //计算哈希值的函数 unsigned int (*hashFunction) (const void *key); //复制键的函数 void *(*keyDup) (void *privdata,const void *key); //复制值的函数 void *(*keyDup) (void *privdata,const void *obj); //复制值的函数 void *(*keyCompare) (void *privdata,const void *key1, const void *key2); //销毁键的函数 void (*keyDestructor) (void *privdata, void *key); //销毁值的函数 void (*keyDestructor) (void *privdata, void *obj); }dictType;
typedef struct dictht { //哈希表数组,C语言中,*号是为了表明该变量为指针,有几个* 号就相当于是几级指针,这里是二级指针,理解为指向指针的指针 dictEntry **table; //哈希表大小 unsigned long size; //哈希表大小掩码,用于计算索引值 unsigned long sizemask; //该哈希已有节点的数量 unsigned long used; }dictht;
table属性是一个数组,数组中的每个元素都是一个指向dict.h/dictEntry结构的指针,每个dictEntry结构保存着一个键值对
size属性记录了哈希表的大小,也是table数组的大小
used属性则记录哈希表目前已有节点(键值对)的数量
sizemask属性的值总是等于 size-1(从0开始),这个属性和哈希值一起决定一个键应该被放到table数组的哪个索引上面(索引下标值)。
//哈希表节点定义dictEntry结构表示,每个dictEntry结构都保存着一个键值对。 typedef struct dictEntry { //键 void *key; //值 union{ void *val; uint64_tu64; int64_ts64; }v; // 指向下个哈希表节点,形成链表 struct dictEntry *next; }dictEntry;
key属性保存着键值中的键,而v属性则保存着键值对中的值,其中键值(v属性)可以是一个指针,或uint64_t整数,或int64_t整数。 next属性是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一起,解决键冲突问题。
随着操作的进行,散列表中保存的键值对会也会不断地增加或减少,为了保证负载因子维持在一个合理的范围,当散列表内的键值对过多或过少时,内需要定期进行rehash,以提升性能或节省内存。Redis的rehash的步骤如下:
Redis这么做的目的是基于操作系统创建子进程后写时复制技术,避免不必要的写入操作。
对于rehash我们思考一个问题如果散列表当前大小为 1GB,要想扩容为原来的两倍大小,那就需要对 1GB 的数据重新计算哈希值,并且从原来的散列表搬移到新的散列表。这种情况听着就很耗时,而生产环境中甚至会更大。为了解决一次性扩容耗时过多的情况,可以将扩容操作穿插在插入操作的过程中,分批完成。当负载因子触达阈值之后,只申请新空间,但并不将老的数据搬移到新散列表中。当有新数据要插入时,将新数据插入新散列表中,并且从老的散列表中拿出一个数据放入到新散列表。每次插入一个数据到散列表,都重复上面的过程。经过多次插入操作之后,老的散列表中的数据就一点一点全部搬移到新散列表中了。这样没有了集中的一次一次性数据搬移,插入操作就都变得很快了。
Redis为了解决这个问题采用渐进式rehash方式。以下是Redis渐进式rehash的详细步骤:
ht[1]
分配空间, 让字典同时持有 ht[0]
和 ht[1]
两个哈希表。rehashidx
, 并将它的值设置为 0
,表示 rehash 工作正式开始。ht[0]
哈希表在 rehashidx
索引上的所有键值对 rehash 到 ht[1]
, 当 rehash 工作完成之后, 程序将 rehashidx
属性的值增一。ht[0]
的所有键值对都会被 rehash 至 ht[1]
, 这时程序将 rehashidx
属性的值设为 -1
, 表示 rehash 操作已完成。**说明: **
1.因为在进行渐进式 rehash 的过程中,字典会同时使用 ht[0]
和 ht[1]
两个哈希表,所以在渐进式 rehash 进行期间,字典的删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行。
2. 在渐进式 rehash 执行期间,新添加到字典的键值对一律会被保存到 ht[1]
里面,而 ht[0]
则不再进行任何添加操作:这一措施保证了 ht[0]
包含的键值对数量会只减不增,并随着 rehash 操作的执行而最终变成空表。
跳表就是链表加了多级索引。
查询:从最顶级索引依次查询
插入:插入的时候会根据幂次定律(powerlaw,越大的数出现的概率越小)随机生成一个介于 1 和 32 之间的值作为索引的层级。例如,插入的时候生成 2 ,表示要生成两级索引,在插入节点的正上放生成两层节点(这说明同层级节点的距离不一定是一样的)
删除:如果删除的节点上有层级索引连索引一起删除
//每个intset结构表示一个整数集合 typedef struct intset{ //编码方式 uint32_t encoding; //集合中包含的元素数量 uint32_t length; //保存元素的数组 int8_t contents[]; } intset;
每当我们要将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现有所有元素的类型都要长时,整数集合需要先进行升级,然后才能将新元素添加到整数集合里面。升级整数集合并添加新元素主要分三步来进行。
双向链表 + 压缩表
字符串对象的内部编码有3种 :
int
、raw
和embstr
。
长度 >= 20 int -> embstr
长度 >= 45 embstr -> raw
embstr
编码是专门用于保存短字符串的一种优化编码方式,我们可以看到embstr
和raw
编码都会使用SDS
来保存值,但不同之处在于embstr
会通过一次内存分配函数来分配一块连续的内存空间来保存redisObject
和SDS
。而raw
编码会通过调用两次内存分配函数来分别分配两块空间来保存redisObject
和SDS
。Redis这样做会有很多好处。
embstr
编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次embstr
编码的字符串对象同样只需要调用一次内存释放函数embstr
编码的字符串对象的所有数据都保存在一块连续的内存里面可以更好的利用CPU缓存提升性能。如上图,Redis经常作为缓存层,来缓存一些热点数据。来加速读写性能从而降低后端的压力。一般在读取数据的时候会先从Redis中读取,如果Redis中没有,再从数据库中读取。在Redis作为缓存层使用的时候,必须注意一些问题,如:缓存穿透、雪崩以及缓存更新问题......
计数器\限速器\分布式ID等主要是利用Redis字符串自增自减的特性。
把Session存到一个公共的地方,让每个Web服务,都去这个公共的地方存取Session。而Redis就可以是这个公共的地方。(数据库、memecache等都可以各有优缺点)。
在Redis3.2版本以前列表类型的内部编码有两种。
ziplist(压缩列表)
:当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。linkedlist(链表)
:当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。而在Redis3.2版本开始对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist.
当用户和文章都越来越多时,为了加快程序的响应速度,我们可以把用户自己的文章存入到 List 中,因为 List 是有序的结构,所以这样又可以完美的实现分页功能,从而加速了程序的响应速度。
每篇文章我们使用哈希结构存储,例如每篇文章有3个属性title、timestamp、content
Copyhmset acticle:1 title xx timestamp 1476536196 content xxxx ... hmset acticle:k title yy timestamp 1476512536 content yyyy ...
向用户文章列表添加文章,user:{id}:articles作为用户文章列表的键:
Copylpush user:1:acticles article:1 article3 ... lpush ...
分页获取用户文章列表,例如下面伪代码获取用户id=1的前10篇文章
Copyarticles = lrange user:1:articles 0 9 for article in {articles} { hgetall {article} }
注意:使用列表类型保存和获取文章列表会存在两个问题。
关于列表的使用场景可参考以下几个命令组合:
哈希类型的内部编码有两种:ziplist(压缩列表),hashtable(哈希表)。只有当存储的数据量比较小的情况下,Redis 才使用压缩列表来实现字典类型。具体需要满足两个条件:
ziplist
使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable
更加优秀。当哈希类型无法满足ziplist
的条件时,Redis会使用hashtable
作为哈希的内部实现,因为此时ziplist
的读写效率会下降,而hashtable
的读写时间复杂度为O(1)。相比较于使用Redis字符串存储,其有以下几个优缺点:
原生字符串每个属性一个键。
Copyset user:1:name Tom set user:1:age 15
优点:简单直观,每个属性都支持更新操作。
缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,所以此种方案一般不会在生产环境使用。
序列化字符串后,将用户信息序列化后用一个键保存
Copyset user:1 serialize(userInfo)
优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。
缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis中。
序列化字符串后,将用户信息序列化后用一个键保存
Copyhmset user:1 name Tom age 15
优点:简单直观,如果使用合理可以减少内存空间的使用。
缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。
很多电商网站都会使用 cookie实现购物车,也就是将整个购物车都存储到 cookie里面。这种做法的一大优点:无须对数据库进行写入就可以实现购物车功能,这种方式大大提高了购物车的性能,而缺点则是程序需要重新解析和验证( validate) cookie,确保cookie的格式正确,并且包含的商品都是真正可购买的商品。cookie购物车还有一个缺点:因为浏览器每次发送请求都会连 cookie一起发送,所以如果购物车cookie的体积比较大,那么请求发送和处理的速度可能会有所降低。
购物车的定义非常简单:我们以每个用户的用户ID(或者CookieId)作为Redis的Key,每个用户的购物车都是一个哈希表,这个哈希表存储了商品ID与商品订购数量之间的映射。在商品的订购数量出现变化时,我们操作Redis哈希对购物车进行更新:
如果用户订购某件商品的数量大于0,那么程序会将这件商品的ID以及用户订购该商品的数量添加到散列里面。
Copy//用户1 商品1 数量1 127.0.0.1:6379> HSET uid:1 pid:1 1 (integer) 1 //返回值0代表改field在哈希表中不存在,为新增的field
如果用户购买的商品已经存在于散列里面,那么新的订购数量会覆盖已有的订购数量;
Copy//用户1 商品1 数量5 127.0.0.1:6379> HSET uid:1 pid:1 5 (integer) 0 //返回值0代表改field在哈希表中已经存在
相反地,如果用户订购某件商品的数量不大于0,那么程序将从散列里面移除该条目。
Copy//用户1 商品1 127.0.0.1:6379> HDEL uid:1 pid:2 (integer) 1
Redis 哈希表作为计数器的使用也非常广泛。它常常被用在记录网站每一天、一月、一年的访问数量。每一次访问,我们在对应的field上自增1
Copy//记录我的 127.0.0.1:6379> HINCRBY MyBlog 202001 1 (integer) 1 127.0.0.1:6379> HINCRBY MyBlog 202001 1 (integer) 2 127.0.0.1:6379> HINCRBY MyBlog 202002 1 (integer) 1 127.0.0.1:6379> HINCRBY MyBlog 202002 1 (integer) 2
也经常被用在记录商品的好评数量,差评数量上
Copy127.0.0.1:6379> HINCRBY pid:1 Good 1 (integer) 1 127.0.0.1:6379> HINCRBY pid:1 Good 1 (integer) 2 127.0.0.1:6379> HINCRBY pid:1 bad 1 (integer) 1
也可以实时记录当天的在线的人数。
Copy//有人登陆 127.0.0.1:6379> HINCRBY MySite 20200310 1 (integer) 1 //有人登陆 127.0.0.1:6379> HINCRBY MySite 20200310 1 (integer) 2 //有人登出 127.0.0.1:6379> HINCRBY MySite 20200310 -1 (integer) 1
集合类型的内部编码有两种:
集合类型比较典型的使用场景是标签(tag)。
给用户添加标签。
Copysadd user:1:tags tag1 tag2 tag5 sadd user:2:tags tag2 tag3 tag5 ... sadd user:k:tags tag1 tag2 tag4 ...
给标签添加用户
Copysadd tag1:users user:1 user:3 sadd tag2:users user:1 user:2 user:3 ... sadd tagk:users user:1 user:2 ...
使用sinter命令,可以来计算用户共同感兴趣的标签
Copysinter user:1:tags user:2:tags
这种标签系统在电商系统、社交系统、视频网站,图书网站,旅游网站等都有着广泛的应用。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。例如一个社交系统可以根据用户的标签进行好友的推荐,已经用户感兴趣的新闻的推荐等,一个电子商务的网站会对不同标签的用户做不同类型的推荐,比如对数码产品比较感兴趣的人,在各个页面或者通过邮件的形式给他们推荐最新的数码产品,通常会为网站带来更多的利益。
有序集合是由 ziplist (压缩列表) 或 skiplist (跳跃表) 组成的。
当数据比较少时,有序集合使用的是 ziplist 存储的,有序集合使用 ziplist 格式存储必须满足以下两个条件:
如果不能满足以上两个条件中的任意一个,有序集合将会使用 skiplist 结构进行存储。
有序集合比较典型的使用场景就是排行榜系统。例如学生成绩的排名。某视频(博客等)网站的用户点赞、播放排名、电商系统中商品的销量排名等。我们以博客点赞为例。
例如小编Tom发表了一篇博文,并且获得了10个赞。
Copyzadd user:ranking arcticle1 10
这个时候有一个读者又觉得Tom写的不好,又取消了赞,此时需要将文章的赞数从榜单中减去1,可以使用zincrby。
Copyzincrby user:ranking arcticle1 -1
CopyZSCORE user:ranking arcticle1
此功能使用zrevrange命令实现:
Copyzrevrangebyrank user:ranking 0 9
使用有序集合的ZRANGEBYLEX(点击可查看该命令详细说明)或ZREVRANGEBYLEX可以帮助我们实现电话号码或姓名的排序,我们以ZRANGEBYLEX为例
注意:不要在分数不一致的SortSet集合中去使用 ZRANGEBYLEX和 ZREVRANGEBYLEX 指令,因为获取的结果会不准确。
我们可以将电话号码存储到SortSet中,然后根据需要来获取号段:
Copyredis> zadd phone 0 13100111100 0 13110114300 0 13132110901 (integer) 3 redis> zadd phone 0 13200111100 0 13210414300 0 13252110901 (integer) 3 redis> zadd phone 0 13300111100 0 13310414300 0 13352110901 (integer) 3
获取所有号码:
Copyredis> ZRANGEBYLEX phone - + 1) "13100111100" 2) "13110114300" 3) "13132110901" 4) "13200111100" 5) "13210414300" 6) "13252110901" 7) "13300111100" 8) "13310414300" 9) "13352110901"
获取132号段:
Copyredis> ZRANGEBYLEX phone [132 (133 1) "13200111100" 2) "13210414300" 3) "13252110901"
获取132、133号段:
Copyredis> ZRANGEBYLEX phone [132 (134 1) "13200111100" 2) "13210414300" 3) "13252110901" 4) "13300111100" 5) "13310414300" 6) "13352110901"
将名称存储到SortSet中:
Copyredis> zadd names 0 Toumas 0 Jake 0 Bluetuo 0 Gaodeng 0 Aimini 0 Aidehua (integer) 6
获取所有人的名字:
Copyredis> ZRANGEBYLEX names - + 1) "Aidehua" 2) "Aimini" 3) "Bluetuo" 4) "Gaodeng" 5) "Jake" 6) "Toumas"
获取名字中大写字母A开头的所有人:
Copyredis> ZRANGEBYLEX names [A (B 1) "Aidehua" 2) "Aimini"
获取名字中大写字母C到Z的所有人:
Copyredis> ZRANGEBYLEX names [C [Z 1) "Gaodeng" 2) "Jake" 3) "Toumas"
很多网站都提供了签到功能,并且需要展示最近一个月的签到情况,这种情况可以使用 BitMap 来实现。
根据日期 offset = (今天是一年中的第几天) % (今年的天数),key = 年份:用户id。
如果需要将用户的详细签到信息入库的话,可以考虑使用一个一步线程来完成。
使用日期作为 key,然后用户 id 为 offset,如果当日活跃过就设置为1。具体怎么样才算活跃这个标准大家可以自己指定。
假如 20201009 活跃用户情况是: [1,0,1,1,0]
20201010 活跃用户情况是 :[ 1,1,0,1,0 ]
统计连续两天活跃的用户总数:
bitop and dest1 20201009 20201010 # dest1 中值为1的offset,就是连续两天活跃用户的ID bitcount dest1
统计20201009 ~ 20201010 活跃过的用户:
bitop or dest2 20201009 20201010
如果需要提供一个查询当前用户是否在线的接口,也可以考虑使用 BitMap 。即节约空间效率又高,只需要一个 key,然后用户 id 为 offset,如果在线就设置为 1,不在线就设置为 0。
日活:pfadd {日期} {ip} {ip}...
月活:pfmerge {本月日期} {本月日期}...
持续更新...
持续更新...
RDB 内存快照
持续更新...
AOF 追加日志
持续更新...
持续更新...
持续更新...
持续更新...
持续更新...
持续更新...
Redis数据结构——简单动态字符串SDS - Mr于 - 博客园 (cnblogs.com)(及其其他 Redis 文章)
Redis
Redis 中 BitMap 的使用场景 - 程序员自由之路 - 博客园 (cnblogs.com)
Redis缓存有哪些淘汰策略 - 掘金 (juejin.cn)