数据库
首页 > 数据库> > Redis 详解 (自用)

Redis 详解 (自用)

作者:互联网

Redis

一、集群部署

环境:docker

版本:6.2.5

1、下载并修改配置文件

从 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。

2、编写 docker-compose.yml 文件

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 貌似不支持通过容器名区访问。

3、启动并建立集群

输入 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 建立集群。

4、部署可视化工具

创建文件夹 /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 就可以看到可视化界面了。

二、常用指令

1、字符串

① 设置指定 key 的值

set {key} {value}

② 获取指定 key 的值

get {key}

③ 自增指定 key 的值

incr {key}

④ 自减指定 key 的值

decr {key}

⑤ 自增指定 key 指定值

incrby {key} {incr value}

⑥ 自减指定 key 指定值

decrby {key} {decr value}

⑦ 在指定 key 的 value 后追加字符串

append {key} {value}

使用该命令后会编码方式会直接变为 raw

⑧ 在指定 key 不存在时设置值

setnx {key} {value}

⑨ 获取多个 key 的值

mget {key} [{key}...]

⑩ 设置并返回旧值

getset {key} {value}

⑪ 获取指定 key 的长度

strlen {key}

⑫ 删除指定 key

del {key}

2、列表

① 在头或尾部插入

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}

3、哈希

① 将哈希表 key 中 field 的值设为 value

hset {key} {field} {value} [{field} {value}...]

② 在 field 不存在时设置字段的值

hsetnx {key} {field} {value}

③ 获取值

hget {key} {value}

hmget {key} {value} [{field} {value}...]

④ 获取指定 key 的所有键和值

hgetall {key}

⑤ 获取指定 key 的所有值

hvals key

⑥ 获取指定 key 所有值的数量

hlen {key}

⑦ 获取指定 key 的所有键

hkeys {key}

⑧ 删除一个或多个 key 的键

`hdel {key} {field} [{field}...]

⑨ 查看 key 中指定键是否存在

hexitst {key} {field}

4、集合

① 添加

sadd {key} {value} [{value}...]

② 获取个数

scard {key}

③ 返回指定集合的交集

sinter key [{key}...]

④ 判断 key 是否包含 value

sismember {key} {value}

⑤ 获取所有值

smembers {key}

⑥ 所有随机一个或多个成员

srandmember {key} {count}

⑦ 删除

srem {key} {value} [{value}...]

5、有序集合

① 添加

zadd {key} {score} {value} [{score} {value}...]

② 获取个数

zcard {key}

③ 获取指定区间的成员个数

zcount {key} {min} {max}

④ 对成员的 score 增加 increment

zincrby {key} {increment} {member}

⑤ 获取指定区间成员

zrange {key} {start} {stop} [withscores]

⑥ 返回成员索引

zrank {key} {value}

⑦ 移除

zrem {key} {value} [{value}...]

⑧ 获取指定区间的成员(通过索引)

zrevrange {key} {start} {stop} [withscores]

⑨ 获取成员分数

zscore {key} {value}

6、Stream

7、bitmap

8、GeoHash

9、HyperLogLog

三、内部编码

1、动态字符串

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)

作用:

2、链表

双向链表

// 节点
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;

拥有双向链表的所有优点

3、字典

基于哈希表的实现

typedef struct dict{
         //类型特定函数
         void *type;
         //私有数据
         void *privdata;
         //哈希表-见2.1.2
         dictht ht[2];
         //rehash 索引 当rehash不在进行时 值为-1
         int trehashidx; 
}dict;
typedef struct dictht
{
         //哈希表数组,C语言中,*号是为了表明该变量为指针,有几个* 号就相当于是几级指针,这里是二级指针,理解为指向指针的指针
         dictEntry **table;
         //哈希表大小
         unsigned long size;
         //哈希表大小掩码,用于计算索引值
         unsigned long sizemask;
         //该哈希已有节点的数量
         unsigned long used;
}dictht;

rehash

随着操作的进行,散列表中保存的键值对会也会不断地增加或减少,为了保证负载因子维持在一个合理的范围,当散列表内的键值对过多或过少时,内需要定期进行rehash,以提升性能或节省内存。Redis的rehash的步骤如下:

① 为字典的ht[1]散列表分配空间,这个空间的大小取决于要执行的操作以及ht[0]当前包含的键值对数量(即:ht[0].used的属性值)
② 将保存在ht[0]中的键值对重新计算键的散列值和索引值,然后放到ht[1]指定的位置上。
③ 将ht[0]包含的所有键值对都迁移到了ht[1]之后,释放ht[0],将ht[1]设置为ht[0],并创建一个新的ht[1]哈希表为下一次rehash做准备。

rehash 需要满足的条件

  1. 服务器目前没有执行BGSAVE(rdb持久化)命令或者BGREWRITEAOF(AOF文件重写)命令,并且散列表的负载因子大于等于1。
  2. 服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且负载因子大于等于5。
  3. 当负载因子小于0.1时,程序自动开始执行收缩操作。

Redis这么做的目的是基于操作系统创建子进程后写时复制技术,避免不必要的写入操作。

渐进式 rehash

对于rehash我们思考一个问题如果散列表当前大小为 1GB,要想扩容为原来的两倍大小,那就需要对 1GB 的数据重新计算哈希值,并且从原来的散列表搬移到新的散列表。这种情况听着就很耗时,而生产环境中甚至会更大。为了解决一次性扩容耗时过多的情况,可以将扩容操作穿插在插入操作的过程中,分批完成。当负载因子触达阈值之后,只申请新空间,但并不将老的数据搬移到新散列表中。当有新数据要插入时,将新数据插入新散列表中,并且从老的散列表中拿出一个数据放入到新散列表。每次插入一个数据到散列表,都重复上面的过程。经过多次插入操作之后,老的散列表中的数据就一点一点全部搬移到新散列表中了。这样没有了集中的一次一次性数据搬移,插入操作就都变得很快了。

Redis为了解决这个问题采用渐进式rehash方式。以下是Redis渐进式rehash的详细步骤:

  1. ht[1] 分配空间, 让字典同时持有 ht[0]ht[1] 两个哈希表。
  2. 在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 ,表示 rehash 工作正式开始。
  3. 在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值增一。
  4. 随着字典操作的不断执行, 最终在某个时间点上, 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 操作的执行而最终变成空表。

4、跳表

跳表就是链表加了多级索引。

查询:从最顶级索引依次查询

插入:插入的时候会根据幂次定律(powerlaw,越大的数出现的概率越小)随机生成一个介于 1 和 32 之间的值作为索引的层级。例如,插入的时候生成 2 ,表示要生成两级索引,在插入节点的正上放生成两层节点(这说明同层级节点的距离不一定是一样的)

删除:如果删除的节点上有层级索引连索引一起删除

5、整数集合

//每个intset结构表示一个整数集合
typedef struct intset{
    //编码方式
    uint32_t encoding;
    //集合中包含的元素数量
    uint32_t length;
    //保存元素的数组
    int8_t contents[];
} intset;

整数集合升级

每当我们要将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现有所有元素的类型都要长时,整数集合需要先进行升级,然后才能将新元素添加到整数集合里面。升级整数集合并添加新元素主要分三步来进行。

  1. 根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。
  2. 将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变。
  3. 将新元素添加到底层数组里面。

6、压缩表

img

7、快速表

双向链表 + 压缩表

四、数据结构

1、字符串

字符串对象的内部编码有3种 :intrawembstr

长度 >= 20 int -> embstr

长度 >= 45 embstr -> raw

embstr编码是专门用于保存短字符串的一种优化编码方式,我们可以看到embstrraw编码都会使用SDS来保存值,但不同之处在于embstr会通过一次内存分配函数来分配一块连续的内存空间来保存redisObjectSDS。而raw编码会通过调用两次内存分配函数来分别分配两块空间来保存redisObjectSDS。Redis这样做会有很多好处。

应用场景

① 缓存

如上图,Redis经常作为缓存层,来缓存一些热点数据。来加速读写性能从而降低后端的压力。一般在读取数据的时候会先从Redis中读取,如果Redis中没有,再从数据库中读取。在Redis作为缓存层使用的时候,必须注意一些问题,如:缓存穿透、雪崩以及缓存更新问题......

② 计数器、限速器、分布式 ID

计数器\限速器\分布式ID等主要是利用Redis字符串自增自减的特性。

③ 分布式共享 session

把Session存到一个公共的地方,让每个Web服务,都去这个公共的地方存取Session。而Redis就可以是这个公共的地方。(数据库、memecache等都可以各有优缺点)。

2、列表

在Redis3.2版本以前列表类型的内部编码有两种。

而在Redis3.2版本开始对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist.

应用场景

① 文章(商品等)列表

当用户和文章都越来越多时,为了加快程序的响应速度,我们可以把用户自己的文章存入到 List 中,因为 List 是有序的结构,所以这样又可以完美的实现分页功能,从而加速了程序的响应速度。

  1. 每篇文章我们使用哈希结构存储,例如每篇文章有3个属性title、timestamp、content

    Copyhmset acticle:1 title xx timestamp 1476536196 content xxxx
    ...
    hmset acticle:k title yy timestamp 1476512536 content yyyy
    ...
    
  2. 向用户文章列表添加文章,user:{id}:articles作为用户文章列表的键:

    Copylpush user:1:acticles article:1 article3
    ...
    lpush
    ...
    
  3. 分页获取用户文章列表,例如下面伪代码获取用户id=1的前10篇文章

    Copyarticles = lrange user:1:articles 0 9
    for article in {articles}
    {
    	hgetall {article}
    }
    

注意:使用列表类型保存和获取文章列表会存在两个问题。

关于列表的使用场景可参考以下几个命令组合:

3、哈希表

哈希类型的内部编码有两种:ziplist(压缩列表),hashtable(哈希表)。只有当存储的数据量比较小的情况下,Redis 才使用压缩列表来实现字典类型。具体需要满足两个条件:

应用场景

① 存储对象

相比较于使用Redis字符串存储,其有以下几个优缺点:

  1. 原生字符串每个属性一个键。

    Copyset user:1:name Tom
    set user:1:age 15
    

    优点:简单直观,每个属性都支持更新操作。
    缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,所以此种方案一般不会在生产环境使用。

  2. 序列化字符串后,将用户信息序列化后用一个键保存

    Copyset user:1 serialize(userInfo)
    

    优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。
    缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis中。

  3. 序列化字符串后,将用户信息序列化后用一个键保存

    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

4、集合

集合类型的内部编码有两种:

应用场景

① 标签系统

集合类型比较典型的使用场景是标签(tag)。

  1. 给用户添加标签。

    Copysadd user:1:tags tag1 tag2 tag5
    sadd user:2:tags tag2 tag3 tag5
    ...
    sadd user:k:tags tag1 tag2 tag4
    ...
    
  2. 给标签添加用户

    Copysadd tag1:users user:1 user:3
    sadd tag2:users user:1 user:2 user:3
    ...
    sadd tagk:users user:1 user:2
    ...
    
  3. 使用sinter命令,可以来计算用户共同感兴趣的标签

    Copysinter user:1:tags user:2:tags
    

这种标签系统在电商系统、社交系统、视频网站,图书网站,旅游网站等都有着广泛的应用。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。例如一个社交系统可以根据用户的标签进行好友的推荐,已经用户感兴趣的新闻的推荐等,一个电子商务的网站会对不同标签的用户做不同类型的推荐,比如对数码产品比较感兴趣的人,在各个页面或者通过邮件的形式给他们推荐最新的数码产品,通常会为网站带来更多的利益。

5、有序集合

有序集合是由 ziplist (压缩列表)skiplist (跳跃表) 组成的。

当数据比较少时,有序集合使用的是 ziplist 存储的,有序集合使用 ziplist 格式存储必须满足以下两个条件:

如果不能满足以上两个条件中的任意一个,有序集合将会使用 skiplist 结构进行存储。

应用场景

① 排行榜

有序集合比较典型的使用场景就是排行榜系统。例如学生成绩的排名。某视频(博客等)网站的用户点赞、播放排名、电商系统中商品的销量排名等。我们以博客点赞为例。

  1. 添加用户赞数

例如小编Tom发表了一篇博文,并且获得了10个赞。

Copyzadd user:ranking arcticle1 10
  1. 取消用户赞数

这个时候有一个读者又觉得Tom写的不好,又取消了赞,此时需要将文章的赞数从榜单中减去1,可以使用zincrby。

Copyzincrby user:ranking arcticle1 -1
  1. 查看某篇文章的赞数
CopyZSCORE user:ranking arcticle1
  1. 展示获取赞数最多的十篇文章

此功能使用zrevrange命令实现:

Copyzrevrangebyrank user:ranking  0 9
② 电话号码(姓名)排序

使用有序集合的ZRANGEBYLEX(点击可查看该命令详细说明)ZREVRANGEBYLEX可以帮助我们实现电话号码或姓名的排序,我们以ZRANGEBYLEX为例
注意:不要在分数不一致的SortSet集合中去使用 ZRANGEBYLEX和 ZREVRANGEBYLEX 指令,因为获取的结果会不准确。

  1. 电话号码排序

我们可以将电话号码存储到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"
  1. 姓名排序

将名称存储到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"

6、Stream

7、bitmap

应用场景

① 用户签到

很多网站都提供了签到功能,并且需要展示最近一个月的签到情况,这种情况可以使用 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。

8、GeoHash

9、HyperLogLog

应用场景

① 统计日活、月活

日活:pfadd {日期} {ip} {ip}...

月活:pfmerge {本月日期} {本月日期}...

五、Lua 脚本

持续更新...

六、淘汰策略

持续更新...

七、持久化(RDB、AOF)

RDB 内存快照
持续更新...
AOF 追加日志
持续更新...

八、集群问题

1、raft 算法

持续更新...

2、脑裂问题

持续更新...

3、缓存倾斜

持续更新...

九、分布式锁

持续更新...

十、缓存穿透、缓存击穿、缓存雪崩

持续更新...

十一、参考文章

Redis数据结构——简单动态字符串SDS - Mr于 - 博客园 (cnblogs.com)(及其其他 Redis 文章)

Redis

Redis 中 BitMap 的使用场景 - 程序员自由之路 - 博客园 (cnblogs.com)

Redis缓存有哪些淘汰策略 - 掘金 (juejin.cn)

标签:key,Redis,redis,value,详解,自用,ht,哈希
来源: https://www.cnblogs.com/alazydog/p/15948059.html