NoSQL

什么是nosql

  • not only sql:解决海量数据和大型集群产生的高并发,高可用,高性能问题数据库解决方案。
  • 不是替代关系型数据库,补充关系型数据库性能瓶颈。

为什么需要NoSQL

  • High performance - 高并发读写
  • Huge Storange - 海量数据的高效存储和访问
  • High Scalability && High Availability - 高扩展性和高可用性

NoSQL 的特点

  • 易扩展
  • 灵活的数据模型
  • 大量数据,高性能
  • 高可用

数据分类

  • 结构化数据: 通过统一的格式进行存储管理(RDBMS)

  • 非结构化数据:无法通过统一格式进行存储管理(nosql)

  • 半结构化:介于两者之间(xml)

键值(Key-Value)存储数据库

  • 相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB

  • 典型应用: 内容缓存,主要用于处理大量数据的高访问负载。

  • 数据模型: 一系列键值对

  • 优势: 快速查询

  • 劣势: 存储的数据缺少结构化

列存储数据库

  • 相关产品: Cassandra, HBase, Riak

  • 典型应用: 分布式的文件系统

  • 数据模型: 以列簇式存储,将同一列数据存在一起

  • 优势: 查找速度快,可扩展性强,更容易进行分布式扩展

  • 劣势: 功能相对局限

文档型数据库

  • 相关产品: CouchDB、MongoDB

  • 典型应用: Web应用(与Key-Value类似,Value是结构化的)

  • 数据模型: 一系列键值对

  • 优势: 数据结构要求不严格

  • 劣势: 查询性能不高,而且缺乏统一的查询语法

图形(Graph)数据库(图)

  • 相关数据库: Neo4J、InfoGrid、Infinite Graph

  • 典型应用: 社交网络

  • 数据模型: 图结构

  • 优势: 利用图结构相关算法。

  • 劣势: 需要对整个图做计算才能得出结果,不容易做分布式的集群方案。

Redis

什么是redis

  • c语言编写的键值非关系型数据库。主要用于高速访问的内容缓存,
  • redis也可以实现持久化,支持事务操作。
  • 常用数据类型: string(字符类型),hash(散列类型),lists(列表类型),set(集合类型),sortedSet(有序集合)。

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。

Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

主数据结构 key-value存储
当前最新版本 6.0.5
SQL
在不同节点上存储不同数据 分片
在多个节点上冗余存储数据 多源复制/源副本复制
支付并发处理数据

为什么会出现Redis

  • 形如MYSQL,ORACLE这样的关系型数据库是把数据存储在磁盘中,而Redis这样的非关系型数据库会把数据存储在内存中。

  • 首先磁盘的寻址时间为ms,而内存的寻址时间为ns,磁盘比内存在寻址上慢了10W倍;

  • 其次在建表时关系型数据库需要给出schema(数据类型和结构),存储时为行级存储,当数据大量增加时,如果表存在索引,增删改除了维护本身数据还要维护索引,查询速度在并发大的时候会受磁盘的带宽影响速度;而内存的带宽很大,不存在这个问题;

  • 最后就是关系型数据库存在一个数据从磁盘取出放到内存中的IO的成本问题;

  • 所以就诞生了redis这样类型的数据库,除此之外还有memcached,ehcache等等。

Redis特性

速度快

  • 首先Redis是将数据储存在内存中的,通常情况下每秒读写次数达到千万级别
  • 其次Redis使用ANSI C编写,因为C语言接近操作系统,所以Redis的执行效率很高。
  • 最后Redis的处理网络请求部分采用的是单线程,如果想充分利用CPU资源的话,可以多开几个Redis实例来达到目的

为什么单线程还是速度快的原因呢?

  • 我们知道Redis的读写都是基于内存的,读写速度都是非常快的,不会出现需要等待很长时间,所以瓶颈并不会出现在请求读写上,所以没必要使用多线程来利用CPU
  • 如果使用多线程的话(线程数>CPU数情况下),多线程的创建、销毁、线程切换、线程竞争等开销所需要的时间会比执行读写所损耗的时间还多,那就南辕北辙了,当然这是在数据量小的时候才会这样,如果数据量到达一定量级了,那肯定是多线程比单线程快(线程数<=CPU数情况下)。
类型 每秒读写次数 随机读写延迟 访问带宽
内存 千万级 80ns 5GB
SSD盘 35000 0.1-0.2ms 100~300MB
机械盘 100左右 10ms 100MB左右

持久化

Redis可以通过RDBAOF两种方式将数据持久化到磁盘上,其中这两种方式的区别如下:

  • RDB: 是在 指定的时间间隔内 将 内存中的数据 通过异步生成数据快照并且保存到磁盘中。
  • AOF: 相对于RDB方式,AOF方式的持久化更细粒度,把每次数据变化(写、删除操作)都记录AOF文件中,其中AOF又可以配置为always即实时将记录写到AOF文件中,everysec每隔一秒将记录写到AOF文件中,no由系统决定何时将记录写到AOF文件中。

多种数据结构

redis是一种高级的key:value存储系统,其中value支持五种常用数据类型:

  1. 字符串(strings)
  2. 字符串列表(lists)
  3. 字符串集合(sets)
  4. 有序字符串集合(sorted sets)
  5. 哈希(hashes)

多语言客户端

  • Redis支持多种语言,诸如Ruby,Python, Twisted Python, PHP, Erlang, Tcl, Perl, Lua, Java, Scala, Clojure等。

功能丰富

  • Redis支持发布订阅、Lua脚本、事务、Pipeline等功能。

主从复制

  • 在Redis中,用户可以通过执行SLAVEOF命令或者SLAVEOF选项,让从服务器去复制主服务器,为高可用分布式提供了基础。

高可用和集群

  • 高可用
    • 有了主从复制之后的实现之后,如果想对服务器进行监控
    • 那么在Redis2.6以后提供了一个Sentinel(哨兵机制)。
    • 顾名思义,哨兵的含义就是监控Redis系统的运行状态,可以启动多个哨兵,去监控Redis数据库的运行状态。其功能有以下两点:
      • 监控所有节点数据库是否正常运行
      • 主数据库出现故障时,可以通过自动投票机制,在从数据库选举出新的主数据库,实现将从数据库转为主数据库的自动切换。
  • 集群
    • Redis在3.0版本正式引入了Redis-Cluster集群这个特征。
    • Redis-Cluster采用无中心架构,每个节点保存完整的数据和整个集群的状态,每个节点都和其他所有节点连接。

memcached 和 redis的区别

  • memcached也是key-value为数据结构存储的数据库,早在2003年就发布了,那为什么还会出现redis呢。主要是因为memcached是不存在value类型的概念

  • Redis中的value类型主要存在五种,分别是 string,hashes,lists,sets ,sorted sets。string类型除了字符类型还可以存储数值类型,还延伸出了位图(bitmaps),更重要的是redis对各种类型提供了相应的API

  • 举个例子,假设你要操作某个key下对应value的部分元素,则可以通过相应的api直接操作value;而不存在则没有办法做到,只能取value值然后再操作,有可能还要放回去,这样就又增加了一次IO。所以redis的效率是会比memcached来的高。

redis应用场景

  • 缓存(数据查询、短连接、新闻内容、商品内容等等)。(最多使用

  • 分布式集群架构中的session分离。

  • 聊天室的在线好友列表。

  • 任务队列。(秒杀、抢购、12306等等)

  • 应用排行榜。

  • 网站访问统计。

  • 数据过期处理(可以精确到毫秒)

安装redis

下载

redis安装

  • 准备: gcc环境

    1
    yum -y install gcc-c++
  • make命令:

    1
    yum -y install make
  • 解压安装包

    1
    tar -zxvf redis-5.0.3.tar.gz -C /opt/
  • 切换到目录,编译

    1
    2
    3
    4
    # 进入到Redis的文件夹
    cd /opt/redis-5.0.3
    # 编译
    make
  • 这样我们的Redis就编译好了,为了能全局使用Redis的命令,我们还需要执行安装命令,将可执行文件安装到环境变量中。

    1
    2
    3
    4
    5
    # (默认安装:/usr/local/bin)
    make install

    # 手动指定
    make install PREFIX=/usr/local/redis
  • 复制redis.conf到etc

    1
    cp redis.conf /etc/
  • 环境变量

    1
    2
    3
    export REDIS_HOME=/usr/local/redis
    export PATH=$PATH:$JAVA_HOME/bin:$REDIS_HOME/bin
    source /etc/profile
  • 这样Redis的安装就完成了

redis启动

  • 我们来启动Redis,其中Redis的启动有三种方式
    1. 直接启动 直接执行redis-server即可启动Redis,这个方式启动实际上就是读取Redis的默认配置启动。
    2. 动态参数启动 执行redis-server --port 6380即可指定端口号启动Redis,更多参数可以参考官方的文档。
    3. 配置文件启动 执行redis-server config.conf即可指定名为config.conf的配置文件进行启动Redis。

前端启动(服务器前端运行)

  • 服务器: ./redis-server

    • 关闭: 强制关闭:ctrl+c
    • 正常命令: ./redis-cli shutdown(推荐)
  • 客户端: ./redis-cli

后端启动

  • 修改配置文件etc/redis.conf:

    1
    2
    bind 192.168.2.101(修改绑定ip)
    daemonize yes(修改作为后台守护程序运行)
  • 指定配置文件,运行服务器端:

    1
    ./redis-server /etc/redis.conf
  • 关闭服务器端:

    • kill杀死

    • ./redis-cli shutdown(推荐)

  • 自带客户端连接:

    ./redis-cli -h ip地址 -p 端口

  • 图形客户端:

    • 需要关闭防火墙或开发端口号
    1
    2
    3
    C:\Users\fulsun>redis-cli
    127.0.0.1:6379> ping
    PONG

Redis为什么那么快

  • Redis为什么那么多块呢,其实上文中 为什么会出现redis已给出了部分答案。

基于内存

  • Redis完全基于内存,绝大部分请求都是纯粹的内存操作,执行效率高;数据存储在内存中,不受到硬盘IO的限制。

单线程模型

  • Redis 的网络 IO 和键值对读写是由一个线程来完成的;其次单线程也避免了多线程面临的共享资源并发访问控制问题。

  • Redis的其他功能,如持久化,异步删除,集群数据同步等,还是由额外的线程执行的。

高效的数据结构

IO多路复用模型

  • Redis网络框架基于Linux的IO多路复用机制(select/epoll机制)实现一个Redis线程处理多个IO流的效果,提升了其并发性。

  • 该机制允许内核中同时存在多个监听套接字和已连接套接字。Redis不会一直阻塞在某一个特定的监听或连接套接字上,而是内核会一直监听连接请求或数据请求,一旦请求到达就会基于select/epoll提供的事件回调机制交给Redis线程处理。

  • 即一旦检测到有请求就会触发事件,把这些事件放到一个事件队列,Redis对该事件队列进行不断处理,处理的同时调用相应的处理函数进行回调。

  • 若对Epoll模型不了解可参考此文 此文若说不清Epoll原理,那就过来掐死我!

单线程

  • 从客户端接收请求,到执行Redis命令。

  • Redis处理网络请时候的求单线程可以抽象成这样,通向Redis的路只有一条,且这条路是个单车道,只容的下一辆车同时使用,而我们使用的Redis命令即为这些车辆,当我们执行多个命令的时候,只有等第一个命令执行完成了后面的命令才会执行,否则会一直处于等待状态。

注意点

  • 一次只运行一条命令

  • 拒绝长(慢)命令(keys、flushall、flushdb、slow lua script、mutil/exec、operate、big value)

  • 至于为什么单线程还这么快,这里有个原因,

    • Redis客户端的到Redis服务器的网络请求采用了多路I/O复用模型(非阻塞I/O)
    • 利用selectpollepoll可以同时监听多个流的I/O(客户端到服务器的网络请求)事件的能力
    • 在空闲的时候,会把当前线程阻塞掉,当有一个或者多个流有I/O事件时,就从阻塞态中唤醒,轮训一遍所有的流并且依次处理就绪的流。这样就算出现有的流的I/O因为网络原因很慢,也不会影响别的流的I/O(非阻塞),因为是轮训所有的流的I/O
    • 这里的“多路”指的是多个网络连接,“复用”指的是复用同一个线程。

Redis通用命令

  • Redis一些通用命令,比如删除一个键、计算数据库的大小、设置键的过期时间等,这些命令有很多,这里主要介绍7个,完整的命令可以参考官方文档

查看数据的类型

  • type key

    1
    2
    3
    4
    5
    6
    127.0.0.1:6379> type name
    none
    127.0.0.1:6379> type zs
    hash
    127.0.0.1:6379> type name
    string

rename:重命名key

查看key

  • 基本语法:

    • 查找所有符合给定模式patternkey
    • **返回值: ** 符合给定模式的key列表。
    1
    2
    3
    4
    5
    KEYS [pattern]

    # 查询所有的key
    127.0.0.1:6379> keys *
    1) "name"
  • 模糊匹配

    1
    2
    3
    4
    5
    6
    key *

    # 特殊匹配
    KEYS h?llo 匹配hello、hallo等key。
    KEYS h*llo 匹配hllo和haaaaaallo等key。
    KEYS h[abe]llo 匹配hallo、hbllo和hello。
  • 时间复杂度为**O(N)**,

    • N为数据库中Key的数量。
    • 这个命令由于时间复杂度为O(N)所以一般生产环境不使用,

Scan命令

  • 语法:

    1
    2
    3
    4
    SCAN cursor [MATCH pattern] [COUNT count]
    cursor - 游标。
    pattern - 匹配的模式。
    count - 指定从数据集里返回多少元素,默认值为 10 。
  • 相比于keys命令,scan命令有两个比较明显的优势:

    • scan命令的时间复杂度虽然也是O(N),但它是分次进行的,不会阻塞线程。

    • scan命令提供了limit参数,可以控制每次返回结果的最大条数。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      127.0.0.1:6379> keys *
      1) "db_number"
      2) "key1"
      3) "myKey"
      127.0.0.1:6379> scan 0 MATCH * COUNT 1
      1) "2"
      2) 1) "db_number"
      127.0.0.1:6379> scan 2 MATCH * COUNT 1
      1) "1"
      2) 1) "myKey"
      127.0.0.1:6379> scan 1 MATCH * COUNT 1
      1) "3"
      2) 1) "key1"
      127.0.0.1:6379> scan 3 MATCH * COUNT 1
      1) "0"
      2) (empty list or set)
    • 我们的Redis中有3个key,我们每次只遍历一个一维数组中的元素。如上所示,SCAN命令的遍历顺序是

      • 0->2->1->3

      • 这个顺序看起来有些奇怪。我们把它转换成二进制就好理解一些了。

      • 00->10->01->11

      • 我们发现每次这个序列是高位加1的。普通二进制的加法,是从右往左相加、进位。而这个序列是从左往右相加、进位的。这一点我们在redis的源码中也得到印证。

        • 在dict.c文件的dictScan函数中对游标进行了如下处理

          1
          2
          3
          4
          v = rev(v);
          v++;
          v = rev(v);
          # 意思是,将游标倒置,加一后,再倒置,也就是我们所说的“高位加1”的操作。

查找当前数据库的key的数量。

  • 返回值: 返回当前数据库的key的数量

    1
    2
    3
    4
    5
    6
    127.0.0.1:6379> DBSIZE
    (integer) 3
    127.0.0.1:6379> set new_key 5
    OK
    127.0.0.1:6379> DBSIZE
    (integer) 4
  • 时间复杂度为**O(1)**,计算的时候不是扫描整个表,因为Redis有个计数器,实时更新Key总数。

检查给定key是否存在。

  • 返回值: 若key 存在返回1,不存在返回0

    1
    EXISTS key
  • 时间复杂度为**O(1)**。

    1
    2
    3
    4
    5
    6
    7
    8
    127.0.0.1:6379> scan 0
    1) "0"
    2) 1) "new_key"
    2) "age"
    3) "name"
    4) "address"
    127.0.0.1:6379> exists name
    (integer) 1

删除指定的一个或者多个key

  • 不存在的key会被忽略。

  • **返回值: ** 被删除的key的数量.

    1
    DEL key [key ...]
  • 时间复杂度为**O(N)**,N为被删除的key的数量,其中删除单个字符串类型的key,时间复杂度为O(1);删除单个列表、集合、有序集合或哈希表类型的key,时间复杂度为O(M)M为以上数据结构内的元素数量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    127.0.0.1:6379> scan 0
    1) "0"
    2) 1) "new_key"
    2) "age"
    3) "name"
    4) "address"
    127.0.0.1:6379> del name age
    (integer) 2
    127.0.0.1:6379> keys *
    1) "new_key"
    2) "address"

给key 设置过期时间

  • 为给定的key设置生存时间,当key过期时,它会被自动删除。这里设置的时间单位是

  • **返回值: ** 设置成功返回1,当key不存在或者设置失败的时候返回0

  • 时间复杂度为**O(1)**。

    1
    2
    3
    PEXPIRE key milliseconds	生存时间设置单位为: 毫秒
    EXPIRE key seconds 设置key的生存时间(单位: 秒)key在多少秒后会自动删除
    PERSIST key 清除生存时间
  • 示例

    1
    2
    3
    127.0.0.1:6379> set name sun
    OK
    127.0.0.1:6379> expire name 10

查看key的剩余时间

  • 以秒为单位,返回给定key的剩余生存时间(TTL,time to live)。

  • **返回值: **

    • key不存在时,返回-2
    • key存在但是没有设置生存时间时,返回-1,否则返回key的剩余生存时间。
  • 时间复杂度**O(1)**。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    127.0.0.1:6379> set name sun
    OK
    127.0.0.1:6379> ttl name
    (integer) -1
    127.0.0.1:6379> expire name 5
    (integer) 1
    127.0.0.1:6379> ttl name
    (integer) 3
    127.0.0.1:6379> ttl name
    (integer) -2

Redis五种值类型

  • 对于Redis来说,每一种数据结构都有着自己的内部编码,而且是多种实现的,这样Redis会在合适的场景选择合适的内部编码,通过OBJECT ENCODING [key]可以参看指定key的内部编码。

  • **这样做的好处: **

    • 改进内部编码,对外的数据结构和命令没有影响,对用户提供黑箱模型。
    • 多种内部编码可在不同场景下发挥各自的优势。如: ziplist比较节约内存,但是元素比较多的时候,性能会有所下降,此时Redis会将编码自动转换为linkedlist,性能会有所改善。

string类型

  • String类型是我们最常用的一种value数据类型,在上文中我们看到string的encoding有三种类型,即对应的三种数据结构实现(int,embstr,raw)。

  • embstr和raw都是SDS存储(上文已介绍),但embstr只需要一次内存分配空间,而raw需要两次。

  • int编码字符串对象和embstr编码字符串对象在一定条件下会转化为raw编码字符串对象。

  • 如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    127.0.0.1:6379> set test 1
    OK
    127.0.0.1:6379> object encoding test
    "int"
    127.0.0.1:6379> append test 1
    (integer) 2
    127.0.0.1:6379> object encoding test
    "raw"

    127.0.0.1:6379> set test1 aa
    OK
    127.0.0.1:6379> object encoding test1
    "embstr"
    127.0.0.1:6379> append test1 aa
    (integer) 4
    127.0.0.1:6379> object encoding test1
    "raw"
  • 当你存储的是中文时,由于redis是二进制安全的,所以在不同编码下数据的长度是不同的,有兴趣的可以看看这篇文章redis是二进制安全

命令 描述
set key value 赋值
get key 取值
getset key value 取值并赋值
mset k1 v1 k2 v2 赋值多个值
mget k1 k2 获取多个值
del key 删除
incr key 数值增1
incrby key increment 设置增长数
decr key 减少1
decrby key increment 减少整数值
append key value 追加数值
strlen key 获取长度

注意

  • incr 将指定的 key 的 value 值递增 1,如果这个值不存在,就将这个值设置初始值为 0,并且 +1.

  • decr 是将指定的 key 的 value 值递减 1,如果这个值不存在,就将这个值设置初始化为 0,并且 -1.

  • incrby是将指定的 key 的 value 值递增 指定的大学,如果值不存在,则设置为 0,并且加指定的值

  • decrby将指定的 key 的值 减去 指定的值 如果值不存在,则设置为 0,并且减指定的值

  • append指定的 key 的 value 后面拼接给定的值,如果给定的 key 不存在,则创建这个 key 并且这个key 的值为后面给定的值

    1
    2
    3
    4
    5
    6
    127.0.0.1:6379> get num
    (nil)
    127.0.0.1:6379> incr num
    (integer) 1
    127.0.0.1:6379> get num
    "1"
  • 如果这个值不能转成数值,不能增减,则抛出异常

    1
    2
    3
    4
    127.0.0.1:6379> get num
    "ab"
    127.0.0.1:6379> incr num
    (error) ERR value is not an integer or out of range

字符串命令

  • String数据类型中比较常用的就是作为字符串使用,可以作为缓存存储一些信息,比如登录用户的一些信息等。如果对命令不太熟悉的前提下我们在Linux系统中可以在连接redis客户端的前提下使用help @String 来获取各个命令的作用和语法或者在redis命令中心查看。

  • 作为字符串常用的命令包括以下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    ## ex(秒) 和 px(毫秒) 表示key的过期时间  nx 表示不存在才操作 xx表示存在才操作
    SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]
    summary: Set the string value of a key
    since: 1.0.0

    ## set 多个 key value
    MSET key value [key value ...]
    summary: Set multiple keys to multiple values
    since: 1.0.1

    ## 在原value后追加字符串
    APPEND key value
    summary: Append a value to a key
    since: 2.0.0

    ## 在value的某个偏移量上重写新的value值
    SETRANGE key offset value
    summary: Overwrite part of a string at key starting at the specified offset
    since: 2.2.0

    ## 获取value的值长度
    STRLEN key
    summary: Get the length of the value stored in a key
    since: 2.2.0

    ## 获取旧值并设置新值
    GETSET key value
    summary: Set the string value of a key and return its old value
    since: 1.0.0

字符串数值命令

  • 当作为数值使用时,其操作都是原子性,故可以作为规避并发的一种手段,其应用场景包括 秒杀、点赞、收藏,抢购等。

  • 作为数值常用的命令如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    ## value + 1
    INCR key
    summary: Increment the integer value of a key by one
    since: 1.0.0

    ## value - 1
    DECR key
    summary: Decrement the integer value of a key by one
    since: 1.0.0

    ## value + increment
    INCRBY key increment
    summary: Increment the integer value of a key by the given amount
    since: 1.0.0

    ## value - increment
    DECRBY key decrement
    summary: Decrement the integer value of a key by the given number
    since: 1.0.0

    ## value + increment (浮点型)
    INCRBYFLOAT key increment
    summary: Increment the float value of a key by the given amount
    since: 2.6.0

bitmap

  • bitmap不是实际的数据类型,而是在String类型上定义的一组面向位的操作。根据官方给出的文档提示,位图最大的优势就是在存储时能节省大量空间。其常用的应用场景包括统计活跃用户或用户登录天数等。

    1
    2
    3
    4
    5
    6
    7
    8
    127.0.0.1:6379> set hello big
    OK
    127.0.0.1:6379> getbit hello 0
    (integer) 0
    127.0.0.1:6379> getbit hello 1
    (integer) 1
    127.0.0.1:6379> getbit hello 2
    (integer) 1

命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
## 统计key从start到end的被设置的为1的bit数  start end 是字节索引
BITCOUNT key [start end]
summary: Count set bits in a string
since: 2.6.0

## 设置或者清空key的value(字符串)在offset处的bit值 offset 为二进制的索引
SETBIT key offset value
summary: Sets or clears the bit at offset in the string value stored at key
since: 2.2.0

## 返回位图中第一个值为 bit(0或1) 的二进制位的位置 start end 是字节索引
BITPOS key bit [start] [end]
summary: Find first bit set or clear in a string
since: 2.8.7

## 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上
## operation 可以是 AND(并)、OR(或)、NOT(非)、XOR(异或)这四种操作中的任意一种
## 当key不同长度 较短的那个key默认以0补位
BITOP operation destkey key [key ...]
summary: Perform bitwise operations between strings
since: 2.6.0

## 参考 http://redis.cn/commands/bitfield.html
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
summary: Perform arbitrary bitfield integer operations on strings
since: 3.2.0

简单演示

  • 这里对bitmap进行简单演示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    redis> BITCOUNT bits
    (integer) 0

    redis> SETBIT bits 0 1 # 0001
    (integer) 0

    redis> BITCOUNT bits
    (integer) 1

    redis> SETBIT bits 3 1 # 1001
    (integer) 0

    redis> BITCOUNT bits
    (integer) 2

    127.0.0.1:6379> setbit bit 1 1
    (integer) 0
    127.0.0.1:6379> setbit bit 7 1
    (integer) 0

    # 分别在key为bit的二进制索引位置1和二进制位置7设置为1,这样实际上就是1000 0010 根据ASCII编码该值就是A。
    127.0.0.1:6379> get bit
    "A"

    # 表示有2个位被设置为1
    127.0.0.1:6379> bitcount bit
    (integer) 2
  • 当添加start 和 end参数时 该值表示字节索引,从右到左

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    127.0.0.1:6379> setbit bit 16 1
    (integer) 0
    127.0.0.1:6379> get bit
    "A\x00\x80"
    # 在第二进制索引位置16设置为1 即 10000000 01000001
    # 当统计第1个字节到被设置为1的数量时则是2个
    127.0.0.1:6379> bitcount bit 0 1
    (integer) 2
    # 当不添加则表示所有字节上述实例就是3个。
    127.0.0.1:6379> bitcount bit
    (integer) 3
  • 首先在二进制索引5设置为1 即 0001 0000 ,

    • 然后分别获取第一个为0和第一个为1的索引位置;
    • 然后再二进制索引8设置1 即 10010000 ,
    • 当设置start 和 end时 该值表示字节索引
    • 结果即为 5 和 8。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 1 0000
    127.0.0.1:6379> setbit test 5 1
    (integer) 0
    # 获取第一个为1的索引位置;
    127.0.0.1:6379> bitpos test 1
    (integer) 5
    # 获取第一个为0的索引位置;这里显示的都是0)
    127.0.0.1:6379> bitpos test 0
    (integer) 0
    127.0.0.1:6379> setbit test 8 1
    (integer) 0
    # 开始索引分别从第二个字节开始
    127.0.0.1:6379> bitpos test 1 1
    (integer) 8
    # 开始索引分别从第一个字节 10010000
    127.0.0.1:6379> bitpos test 1 0
    (integer) 5

应用场景

  • 用途我们在上面介绍时分别都有提及到,不过作为字符串和数值的用途比较常见,此处不做撰述。我们来仔细看看作为bitmap时的应用场景如何使用。

  • 首先就是统计用户登录天数。假设你用关系型数据库去处理,每个用户登录一天你有相关的登录记录表去录,如果你有1千万的活跃用户每天都在登录你的系统那么你每个人都去记录一条数据,一年365天光数据存储都占用了非常多的资源。如果是使用bitmap你可以这样做,登录人的id作为key,每一位表示一天,即如下表。(1表示登录过 ,0表示未登录)

    1
    2
    3
    4
    5
    6
    127.0.0.1:6379> setbit user01 1 1
    127.0.0.1:6379> setbit user01 2 1
    127.0.0.1:6379> setbit user01 16 1
    # 出用户登录天数
    127.0.0.1:6379> bitcount user01
    (integer) 3
用户\天数 1 2 3 4 5 364 365
userId1 0 1 1 0 1 0 1
userId2 1 1 0 1 1 1 0
userId99 1 1 1 1 0 1 1
  • 当用户登录使用setbit userId offset(哪一天)1 就可以记录该用户当天登录,然后使用bitcount 即可以得出用户登录天数,或者你需要获取某个时间周期内的登录天数只要在bitcount加上startend 即可,最最重要的是它占用的空间非常少比起关系型数据库少太多。

  • 其次就是活跃用户统计,比如你要统计日🔥用户,那么你也可以使用bitmap来做,思路和上面差不多,只不过现在是把日期作为key,用户的id映射为位,即如下表。(1表示活跃,0表示不活跃)

日期\用户 1 2 3 4 5 364 365
20200818 0 1 1 0 1 0 1
20200819 1 1 0 1 0 1 1
  • 当用户活跃时,使用setbit 20200818 offset(用户id映射的位)1 即表示用户活跃,然后使用bitcount即可以得出当天的活跃用户;如果你需要对某个时间周期内进行统计且需要去重则可使用bitop or destkey 时间key1 时间key... 然后再对destKey进行bitcount 即可。

假设每个用户存储需要20B空间, 那么1亿个用户所占用的空间就是 20 * 100000000 = 20000000000B ≈ 18G,看起来也是个不小的内存消耗;如果你对精确度不是很高的要求那么可以考虑Redis提供的Hypeloglog,可以参考下 Redis HyperLogLog介绍及应用

hash类型

  • 散列类型,支持扩展key,提供字段属性,字段和字段值之间实现映射。

  • 使用场景:存储对象信息(用户,商品信息等)

  • 字段值只支持string类型。

    命令 描述
    HSET key field value 设置值,,0代表更新,1代表插入
    HMSET key field value [field value …] 一次设置多个值
    HSETNX key field value 设置字段的值,如果字段已存在,该操作无效果。
    hget key field 获取值
    hmget key field [field …] 获取多个值
    hgetall key 获取所有的值
    hdel key field [field …] 删除一个属性或多个属性
    del key 删除整个 key
    HINCRBY key field increment 增加数字,不存在给默认值0
    hexists key field 返回hash里面field是否存在
    hlen key 获取某个 key 存在的属性个数
    hkeys key 获取所有属性名称
    hvals key 获取属性所有的值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    192.168.2.101:6379> HSET user:001 name zs
    (integer) 1
    192.168.2.101:6379> HSET user:001 name ls
    (integer) 0
    192.168.2.101:6379> HGET user:001 name
    "ls"
    192.168.2.101:6379> HMSET user:001 name zs age 12
    OK
    192.168.2.101:6379> HMGET user:001 name age
    1) "zs"
    2) "12"
    有该key则不赋值,没有则赋值
    192.168.2.101:6379> HSETNX user:001 name ww
    • Hash数据类型就是 key-value 键值对,对应的encoding可以是ziplist或者hashtable。 Hash对象只有同时满足下面两个条件时,才会使用ziplist(压缩列表):
      1. 哈希中元素数量小于512个;
      2. 哈希中所有键值对的键和值字符串长度都小于64字节。这样会非常节约内存。
      3. 其应用场景包括点赞,收藏等
    • 对其数据结构有兴趣的仍然可以了解Redis为何这么快–数据存储角度的Hash篇。

常用的命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
## 设置多个键值对
HSET key field value [field value ...]
summary: Set the string value of a hash field
since: 2.0.0

## 设置键值对 不存在才设置
HSETNX key field value
summary: Set the value of a hash field, only if the field does not exist
since: 2.0.0

## 设置多个键值对 4.0被弃用
HMSET key field value [field value ...]
summary: Set multiple hash fields to multiple values
since: 2.0.0

## 获取key下某个键对应的值
HGET key field
summary: Get the value of a hash field
since: 2.0.0

## 获取key下多个键对应的值
HMGET key field [field ...]
summary: Get the values of all the given hash fields
since: 2.0.0

## 获取所有key-value键值对
HGETALL key
summary: Get all the fields and values in a hash
since: 2.0.0

## 获取所有的键
HKEYS key
summary: Get all the fields in a hash
since: 2.0.0

## 获取所有的值
HVALS key
summary: Get all the values in a hash
since: 2.0.0

## hash下键所对应的值加 increment值
HINCRBY key field increment
summary: Increment the integer value of a hash field by the given number
since: 2.0.0

## hash下键所对应的值加 increment值(浮点)
HINCRBYFLOAT key field increment
summary: Increment the float value of a hash field by the given amount
since: 2.6.0

## 迭代hash下匹配pattern的键 count指定个数 cursor表示从第几个开始
HSCAN key cursor [MATCH pattern] [COUNT count]
summary: Incrementally iterate hash fields and associated values
since: 2.8.0

应用场景

Hash的数据类型可以用作点赞,收藏,阅读数等。比如你有一个帖子,你需要为它统计点赞,收藏,阅读。如果你使用string类型的话 你肯定是要设置3个key分别对应点赞,收藏,阅读,这样在无形中也浪费了存储空间;但是如果你使用hash的话 一个key就搞定,使用hset 帖子id 点赞数 0 收藏数 0 阅读 0 ,当每增加一个变化时 使用HINCRBY key field increment 对field进行数值计算就可以解决。

其次就是当你缓存一些数据时,比如说用户如果你使用string缓存,那么必然是个json对象,当你只需要某个值时你需要把整个对象取出来然后处理,但hash就可以直接取出对应的值。

list类型

  • 在 redis 中。list 是按照插入排序的一个字符串的链表,和数据结构中链表是一样的,

  • 可以再头部(左侧)或者尾部(右侧)增加或者删除元素。

  • 按照插入顺序排序

    • ArrayList 使用数组方式
    • LinkdList 使用双向链接方式
    • 双向链表增加数据
    • 双向链表删除数据
  • 使用场景:用于商品评论

常用命令

命令 描述
lpush key value [value …] 左端添加,如果不存在,则创建一个 list,如果存在,则添加进 key 的 list
LPUSHX key value 只有当 key 已经存在并且存着一个 list 的时候,在这个 key 下面的 list 的头部插入 value。 与 LPUSH 相反,当 key 不存在的时候不会进行任何操作。
rpush key value 右端添加
rpushx key value 将一个值插入到已存在的列表w尾部,如果不存在,则不会插入,返回 (integer) 0
lrange key [start] [end] 查看列表,index:从0开始,-1代表最后一位
lpop key 左边弹出, 如果存在,返回头部的第一个元素,如果不存在,返回 nil.弹出以后就没有这个元素
rpop key 右边弹出
llen key 获取列表元素个数
lrem key count value 从列表中删除元素
LINDEX key index 查看指定索引的值
ltrim key start stop 修剪片段,list 就会只包含指定范围的指定元素,start 和 stop 都是由0开始计数的
LINSERT key BEFORE/AFTER pivot value 把 value 插入存于 key 的列表中在基准值 pivot 的前面或后面。当 key 不存在时,这个list会被看作是空list,任何操作都不会发生.当 key 存在,但保存的不是一个list的时候,会返回error。
RPOPLPUSH source destination timeout 原子性地返回并移除存储在 source 的列表的最后一个元素(列表尾部元素), 并把该元素放入存储在 destination 的列表的第一个元素位置(列表头部)
BRPOPLPUSH source destination timeout BRPOPLPUSHRPOPLPUSH 的阻塞版本。当 source 包含元素的时候,这个命令表现得跟 RPOPLPUSH 一模一样。 当 source 是空的时候,Redis将会阻塞这个连接,直到另一个客户端 push 元素进入或者达到 timeout 时限。 timeout 为 0 能用于无限期阻塞客户端。
  • lrem 从存于 key 的列表里移除前 count 次出现的值为 value 的元素。 这个 count 参数通过下面几种方式影响这个操作:

    • count > 0: 从头往尾移除值为 value 的元素。

    • count < 0: 从尾往头移除值为 value 的元素。

    • count = 0: 移除所有值为 value 的元素。

    • 比如, LREM list -2 “hello” 会从存于 list 的列表里移除最后两个出现的 “hello”。

    • 需要注意的是,如果list里没有存在key就会被当作空list处理,所以当 key 不存在的时候,这个命令会返回 0。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
127.0.0.1:6379> lpush mylist a b c
(integer) 3
127.0.0.1:6379> lpush mylist 1 2 3
(integer) 6
127.0.0.1:6379> lrange 0 5
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lrange mylist 0 -1
1) "3"
2) "2"
3) "1"
4) "c"
5) "b"
6) "a"
127.0.0.1:6379> lpop mylist
"3"
127.0.0.1:6379> lrange mylist 0 5
1) "2"
2) "1"
3) "c"
4) "b"
5) "a"
127.0.0.1:6379> rpop mylist
"a"
127.0.0.1:6379> lrange mylist 0 5
1) "2"
2) "1"
3) "c"
4) "b"
127.0.0.1:6379> llen mylist
(integer) 4
127.0.0.1:6379> lrem mylist 2 c
(integer) 1
127.0.0.1:6379> lrange mylist 0 -1
1) "2"
2) "1"
3) "b"
127.0.0.1:6379> lpush mylist 1
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "1"
2) "2"
3) "1"
4) "b"
127.0.0.1:6379> lrem mylist 2 1
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1
1) "2"
2) "b"
127.0.0.1:6379> lindex mylist 1
"b"
# 裁剪片段
127.0.0.1:6379> ltrim mylist 0 0
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "2"
127.0.0.1:6379> rpush mylist a b c d
# 插入指定位置
127.0.0.1:6379> linsert mylist after b 3
(integer) 6
127.0.0.1:6379> lrange mylist 0 -1
1) "2"
2) "a"
3) "b"
4) "3"
5) "c"
6) "d"
127.0.0.1:6379> rpoplpush mylist list2
"d"
127.0.0.1:6379> lrange list2 0 -1
1) "d"
127.0.0.1:6379> lrange mylist 0 -1
1) "2"
2) "a"
3) "b"
4) "3"
5) "c"

ListAPI

List数据类型就是有序(插入顺序)元素的序列,其对应的encoding为上文表格中的ziplist和linkelist。Redis中的列表支持两端插入和弹出,并且可指定位置获取元素,可以充当数组,队列,栈等。因此,其应用场景包括微博的时间轴列表,阻塞队列等。

这里不对底层数据结构进行分析,若想了解zipList和linkedList可参考 Redis为何这么快–数据存储角度的List篇。

  • 常用的命令如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
## 获取列表index的值(从左开始)
LINDEX key index
summary: Get an element from a list by its index
since: 1.0.0

## 列表插入某个元素
LINSERT key BEFORE|AFTER pivot value
summary: Insert an element before or after another element in a list
since: 2.2.0

## 获取列表长度
LLEN key
summary: Get the length of a list
since: 1.0.0

## 从列表左侧弹出(删除并获取)
LPOP key
summary: Remove and get the first element in a list
since: 1.0.0

## 从列表左侧插入多个元素
LPUSH key value [value ...]
summary: Prepend one or multiple values to a list
since: 1.0.0

## 当列表存在时 插入元素
LPUSHX key value
summary: Prepend a value to a list, only if the list exists
since: 2.2.0

## 获取列表指定范围的元素值 start和end为元素索引
LRANGE key start stop
summary: Get a range of elements from a list
since: 1.0.0

## 移除列表count数量的value元素 (从左开始) count为0表示全删
LREM key count value
summary: Remove elements from a list
since: 1.0.0

## 设置列表index下的值(从左开始)
LSET key index value
summary: Set the value of an element in a list by its index
since: 1.0.0

## 截取列表指定范围的list start和end为元素索引
LTRIM key start stop
summary: Trim a list to the specified range
since: 1.0.0

## 右侧弹出 左侧插入 source 和 destination 都为列表 可为同一个
RPOPLPUSH source destination
summary: Remove the last element in a list, prepend it to another list and return it
since: 1.2.0
  • 以上罗列的是从list的左侧开始,同样的redis也提供了从list的右侧开始。redis同样提供了阻塞式的一些命令。如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
## 阻塞获取列表元素并删除(左侧) 可指定多个列表 同时可设置timeout时间(单位秒)
BLPOP key [key ...] timeout
summary: Remove and get the first element in a list, or block until one is available
since: 2.0.0

## 阻塞获取列表元素并删除(右侧) 可指定多个列表 同时可设置timeout时间(单位秒)
BRPOP key [key ...] timeout
summary: Remove and get the last element in a list, or block until one is available
since: 2.0.0

## RPOPLPUSH source destination 阻塞版本 timeout时间(单位秒)
BRPOPLPUSH source destination timeout
summary: Pop a value from a list, push it to another list and return it; or block until one is available
since: 2.2.0

这里要注意的是当有多个连接对同一个list进行阻塞监听时,redis的处理方法是维护了一个阻塞队列,提供先阻塞先服务,当多个阻塞同时满足唤醒条件时,先阻塞的优先唤醒。

应用场景

  • 上面我们说List可以充当栈,队列。栈的特征是先进后出,那么在使用Redis的同一个方向命令(lpush lpop)时就可以实现栈的特点;队列的特征是先进先出,那么同样的在使用Redis的反向命令(lpush rpoprpush lpop )时就可以实现队列的特点。

  • 微博的时间轴列表也比较简单,每次新增一个微博时只需要lpush 进去即可,通过lrange 来获取最新的微博消息。

  • 阻塞队列的实现就需要使用blpopbrpop ,首先一定要先调用blpop 监听,然后再另外一个客户端进行lpush 操作,如果先lpushblpop 那么先lpush 的数据就不会被监听到。

set

  • 无序,不重复
  • Set是String的无序去重排列,对应的encoding是intset(整数集合)或hashtable。intset(整数集合)当一个集合只含有整数,并且元素不多时会使用intset(整数集合)作为Set集合对象的底层实现。其应用场景包括共同关注,随机事件等

常用的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# 集合内操作
## 添加元素
SADD key member [member ...]
summary: Add one or more members to a set
since: 1.0.0

## 集合大小
SCARD key
summary: Get the number of members in a set
since: 1.0.0

## 随机获取集合内count个元素
## count为正 取出一个去重的结果集
## count为负 取出一个不去重的结果集
##
SRANDMEMBER key [count]
summary: Get one or multiple random members from a set
since: 1.0.0

## 随机弹出(获取并删除)集合内count个元素
SPOP key [count]
summary: Remove and return one or multiple random members from a set
since: 1.0.0

## 取出所有元素
SMEMBERS key
summary: Get all the members in a set
since: 1.0.0

## 判断member是否在集合中
SISMEMBER key member
summary: Determine if a given value is a member of a set
since: 1.0.0

## 移除元素
SREM key member [member ...]
summary: Remove one or more members from a set
since: 1.0.0

# 集合间操作
## 集合间差集
SDIFF key [key ...]
summary: Subtract multiple sets
since: 1.0.0

## 集合间差集并存储到destination
SDIFFSTORE destination key [key ...]
summary: Subtract multiple sets and store the resulting set in a key
since: 1.0.0

## 迭代集合下匹配pattern的键 count指定个数 cursor表示从第几个开始
SSCAN key cursor [MATCH pattern] [COUNT count]
summary: Incrementally iterate Set elements
since: 2.8.0

## 集合间交集
SINTER key [key ...]
summary: Intersect multiple sets
since: 1.0.0

## 集合间交集并存储到destination
SINTERSTORE destination key [key ...]
summary: Intersect multiple sets and store the resulting set in a key
since: 1.0.0

## 集合间并集
SUNION key [key ...]
summary: Add multiple sets
since: 1.0.0

## 集合间并集并存储到destination
SUNIONSTORE destination key [key ...]
summary: Add multiple sets and store the resulting set in a key
since: 1.0.0

## 把member从source集合移动到destination集合
SMOVE source destination member
summary: Move a member from one set to another
since: 1.0.0

应用场景

  • Set的应用场景包括随机事件,公共好友。这其实就对应了Set的集合内命令和集合间命令。集合内命令的spop keysrandmember key count 就可以应对随机事件,比如抽奖系统,key为奖品,member为用户id,如果希望中奖的人不会再中就可以使用spop,如果希望重复中奖就可以使用srandmember ;集合间命令获取交集sinter 就可以应对共同好友,共同关注,key为用户,member分别为用户的关注。
命令 描述
SADD key member [member …] 添加一个或多个指定的member元素到集合的 key中
spop key 删除元素
SMEMBERS key 查看所有元素
SMEMBER key member 判断是否存在,是集合key的成员,则返回1,集合key不存在,则返回0
scard key 长度
SUNION k1 k2 并集
SINTER k1 k2 交集
SDIFF k1 k2 差集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
127.0.0.1:6379> sadd set1 a
(integer) 1
127.0.0.1:6379> spop set1
"a"

127.0.0.1:6379> sadd s1 a b c
(integer) 3
127.0.0.1:6379> sadd s2 d b c
(integer) 3
# 并集
127.0.0.1:6379> sunion s1 s2
1) "a"
2) "b"
3) "c"
4) "d"

# 交集
127.0.0.1:6379> sinter s1 s2
1) "b"
2) "c"

# 差集
127.0.0.1:6379> sdiff s1 s2
1) "a"
127.0.0.1:6379> sdiff s2 s1
1) "d"

# 长度
127.0.0.1:6379> scard s1
(integer) 3

sortedset

  • 又名zset,唯一且可排序。

  • 为每个元素设置分数,根据分数实现排序。

  • 应用场景: 排行榜

  • Sorted Set是在Set上添加一个分数(score)维度的集合,通过分数来为集合中的元素进行排序。相比于Set来说Sorted Set可理解为一个有序无重复的序列,其encoding为ziplist或skiplist。当一个有序集合的元素数量比较多或者成员是比较长的字符串时,Redis就使用skiplist(跳跃表)作为ZSet对象的底层实现。其应用场景主要包括排行榜等。

常用命令如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
## 添加集合元素 NX 不更新 只添加 XX只更新 不添加 CH 返回值为修改总数(默认是新增总数) INCR 分值添加
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
summary: Add one or more members to a sorted set, or update its score if it already exists
since: 1.2.0

## 删除集合元素
ZREM key member [member ...]
summary: Remove one or more members from a sorted set
since: 1.2.0

## 指定分数区间删除集合元素
ZREMRANGEBYSCORE key min max
summary: Remove all members in a sorted set within the given scores
since: 1.2.0

## 指定成员区间删除集合元素
ZREMRANGEBYLEX key min max
summary: Remove all members in a sorted set between the given lexicographical range
since: 2.8.9

## 指定排名区间删除集合元素
ZREMRANGEBYRANK key start stop
summary: Remove all members in a sorted set within the given indexes
since: 2.0.0

## 获取集合元素分数
ZSCORE key member
summary: Get the score associated with the given member in a sorted set
since: 1.2.0

## 增加集合元素分数
ZINCRBY key increment member
summary: Increment the score of a member in a sorted set
since: 1.2.0

## 返回集合个数
ZCARD key
summary: Get the number of members in a sorted set
since: 1.2.0

## 返回元素排名 默认升序
ZRANK key member
summary: Determine the index of a member in a sorted set
since: 2.0.0

## 返回元素排名 降序
ZREVRANK key member
summary: Determine the index of a member in a sorted set, with scores ordered from high to low
since: 2.0.0

## 指定元素索引返回集合值 WITHSCORES表示是否带分值 升序
ZRANGE key start stop [WITHSCORES]
summary: Return a range of members in a sorted set, by index
since: 1.2.0

## 根据元素分值大小返回集合值 WITHSCORES表示是否带分值 LIMIT 指定索引和数量 升序
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
summary: Return a range of members in a sorted set, by score
since: 1.0.5

## 指定元素索引返回集合值 WITHSCORES表示是否带分值 降序
ZREVRANGE key start stop [WITHSCORES]
summary: Return a range of members in a sorted set, by index, with scores ordered from high to low
since: 1.2.0

## 指定元素分值大小返回集合值 WITHSCORES表示是否带分值 LIMIT 指定索引和数量 降序
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
summary: Return a range of members in a sorted set, by score, with scores ordered from high to low
since: 2.2.0

## 指定元素区间返回集合值 LIMIT 指定索引和数量 降序
ZREVRANGEBYLEX key max min [LIMIT offset count]
summary: Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.
since: 2.8.9

## 根据分数区间返回集合元素数量
ZCOUNT key min max
summary: Count the members in a sorted set with scores within the given values
since: 2.0.0

## 返回成员区间返回集合元素数量
ZLEXCOUNT key min max
summary: Count the number of members in a sorted set between a given lexicographical range
since: 2.8.9

## 返回集合下成员在min到max的数量 Limit 指定索引和数量
ZRANGEBYLEX key min max [LIMIT offset count]
summary: Return a range of members in a sorted set, by lexicographical range
since: 2.8.9

## 弹出集合中分数最低元素
ZPOPMIN key [count]
summary: Remove and return members with the lowest scores in a sorted set
since: 5.0.0

## 弹出集合中分数最高元素
ZPOPMAX key [count]
summary: Remove and return members with the highest scores in a sorted set
since: 5.0.0

## 集合间并集 WEIGHTS 使用聚合函数前的乘法因子 AGGREGATE 指定聚合方式
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
summary: Add multiple sorted sets and store the resulting sorted set in a new key
since: 2.0.0

## 集合间交集 WEIGHTS 使用聚合函数前的乘法因子 AGGREGATE 指定聚合方式
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
summary: Intersect multiple sorted sets and store the resulting sorted set in a new key
since: 2.0.0

## 按照参数中key的顺序,返回第一个非空key中分数最大的成员和对应的分数 timeout为阻塞时间 单位秒
BZPOPMAX key [key ...] timeout
summary: Remove and return the member with the highest score from one or more sorted sets, or block until one is available
since: 5.0.0

## 按照参数中key的顺序,返回第一个非空key中分数最小的成员和对应的分数 timeout为阻塞时间 单位秒
BZPOPMIN key [key ...] timeout
summary: Remove and return the member with the lowest score from one or more sorted sets, or block until one is available
since: 5.0.0

## 迭代集合下匹配pattern的键 count指定个数 cursor表示从第几个开始
ZSCAN key cursor [MATCH pattern] [COUNT count]
summary: Incrementally iterate sorted sets elements and associated scores
since: 2.8.0

应用场景

  • Sorted Set最常见的应用场景便是排行榜,使用zadd key score member 就可以实现自动排序,默认按照从小到大排序,如果你希望倒序那就使用zrevrangebyscore 根据分数倒序即可,想获取某一个元素的排名使用zrank ,但要记住这仍是正序的,因为sorted set物理内存不随命令变化,想要倒序的排名使用zrevrank
命令 描述
zadd key score member [score member … ] 添加元素
ZSCORE key member 查看排序的分数
ZREM key score member [score member … ] 删除元素
ZRANGE key start stop [WITHSCORES] startstop都是基于零的索引,顺序输出[start,stop]包含的区间
ZREVRANGE key start stop [WITHSCORES] 倒序输出
ZRANK key member 返回有序集key中成员member的排名,其中有序集成员按score值递增(从小到大)顺序排列。排名以0为底,也就是说,score值最小的成员排名为0
ZREVRANK key member
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 如果M是常量(比如,用limit总是请求前10个元素),你可以认为是O(log(N))。返回key的有序集合中的分数在min和max之间的所有元素(包括分数等于max或者min的元素)。元素被认为是从低分到高分排序的。min和max可以是-inf和+inf,这样一来,你就可以在不知道有序集的最低和最高score值的情况下,使用ZRANGEBYSCORE这类命令。
ZINCRBY key increment member 增加某个成员分数
zcard key 查看元素个数
ZCOUNT key min max 统计范围内的元素个数
ZREMRANGEBYRANK key start stop 根据排名删除范围内元素
ZREMRANGEBYSCORE key start stop 根据分数删除范围内元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# 添加元素
127.0.0.1:6379> ZADD zset1 80 zs 90 ls 70 ww
(integer) 3
# 添加元素和分数,如果该元素存在则修改分数并返回0;如果不存在返回1.
127.0.0.1:6379> zadd zset1 75 ww
(integer) 0
# 查看排序的分数
127.0.0.1:6379> zscore zset1 ww
"75"
127.0.0.1:6379> zrange zset1 0 -1 withscores
1) "ww"
2) "75"
3) "zs"
4) "80"
5) "ls"
6) "90"
127.0.0.1:6379> zrevrange zset1 0 -1
1) "ww"
2) "zs"
3) "ls"
# 查询排名
127.0.0.1:6379> ZRANK zset1 ww
(integer) 0
# 倒序查询
127.0.0.1:6379> zrevrange zset1 0 -1 withscores
1) "ls"
2) "90"
3) "zs"
4) "80"
5) "ww"
6) "75"
127.0.0.1:6379> ZrevRANK zset1 ww
(integer) 2
# 指定分数范围查找,默认闭区间
127.0.0.1:6379> ZRANGEBYSCORE zset1 80 90 withscores
1) "zs"
2) "80"
3) "ls"
4) "90"
# 通过给参数前增加(符号来使用可选的开区间
# 返回所有符合条件80 < score <= 90的成员
127.0.0.1:6379> ZRANGEBYSCORE zset1 (80 90 withscores
1) "ls"
2) "90"
# min和max可以是-inf和+inf
127.0.0.1:6379> ZRANGEBYSCORE zset1 -inf +inf withscores
1) "ww"
2) "75"
3) "zs"
4) "80"
5) "ls"
6) "90"
# limit 1 1 从1开始,查找一个
127.0.0.1:6379> ZRANGEBYSCORE zset1 80 90 withscores limit 1 1
1) "ls"
2) "90"

#增加某个成员分数
127.0.0.1:6379> ZINCRBY zset1 5 ww
"80"

# 查看元素个数
127.0.0.1:6379> zcard zset1
(integer) 3
# 统计范围内的元素个数 闭区间
127.0.0.1:6379> ZCOUNT zset1 85 90
(integer) 1

RedisObject

  • redisObject是redis中封装value对象的数据结构。任何一个value都会被包装成一个redisObject。redisObject能指定value的类型,编码格式,内存回收,数据指针等

  • 这样设计的好处是在5种常用类型设置多种不同的的数据结构实现,优化对象在不同场景下的效率。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    typedef struct redisObject {
    // 刚刚好32 bits
    // 对象的类型,字符串/列表/集合/哈希表
    unsigned type:4;
    // 未使用的两个位
    unsigned notused:2; /* Not used */
    // 编码的方式,Redis 为了节省空间,提供多种方式来保存一个数据
    // 譬如: “123456789” 会被存储为整数123456789
    unsigned encoding:4;
    // 当内存紧张,淘汰数据的时候用到
    unsigned lru:22; /* lru time (relative to server.lruclock) */
    // 引用计数
    int refcount;
    // 数据指针
    void *ptr;
    } robj
  • 这里介绍几个比较关键的属性:

    • type: 标记了value对象的数据类型,使用type 命令来获取。所有数据类型如下所示:

      1
      2
      3
      4
      5
      6
      /* Object types */
      #define REDIS_STRING 0
      #define REDIS_LIST 1
      #define REDIS_SET 2
      #define REDIS_ZSET 3
      #define REDIS_HASH 4
    • encoding: 标记了value对象的编码,也就是使用了什么数据结构,使用 object encoding 命令来获取。编码如下所示:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      /* Objects encoding. Some kind of objects like Strings and Hashes can be
      * internally represented in multiple ways. The 'encoding' field of the object
      * is set to one of this fields for this object. */
      #define REDIS_ENCODING_INT /* Encoded as integer */
      #define REDIS_ENCODING_EMBSTR /* Encoded as embstr */
      #define REDIS_ENCODING_RAW /* Raw representation */
      #define REDIS_ENCODING_HT /* Encoded as hash table */
      #define REDIS_ENCODING_LINKEDLIST /* Encoded as regular linked list */
      #define REDIS_ENCODING_ZIPLIST /* Encoded as ziplist */
      #define REDIS_ENCODING_INTSET /* Encoded as intset */
      #define REDIS_ENCODING_SKIPLIST /* Encoded as skiplist */
    • redis为优化内存,对数据类型提供了多种底层实现方式,type和encoding对应关系如下表格所示:

      类型(type属性) 编码(encoding属性) 注释
      REDIS_STRING REDIS_ENCODING_INT 整数值实现的字符串
      REDIS_STRING REDIS_ENCODING_EMBSTR embstr编码的简单动态字符串实现的字符串
      REDIS_STRING REDIS_ENCODING_RAW 简单动态字符串实现的字符串
      REDIS_LIST REDIS_ENCODING_ZIPLIST 压缩列表实现的列表
      REDIS_LIST REDIS_ENCODING_LINKEDLIST 双向链表实现的列表
      REDIS_HASH REDIS_ENCODING_ZIPLIST 压缩列表实现的哈希表
      REDIS_HASH REDIS_ENCODING_HT 字典实现的哈希表
      REDIS_SET REDIS_ENCODING_INTSET 整数集合实现的集合
      REDIS_SET REDIS_ENCODING_HT 字典实现的集合
      REDIS_ZSET REDIS_ENCODING_ZIPLIST 压缩列表实现的有序集合
      REDIS_ZSET REDIS_ENCODING_SKIPLIST 使用跳跃表+字典实现的有序集合
    • lru: redis对数据集占用内存的大小由周期性的计算,当超出限制时,会淘汰超时的数据。即淘汰的标准为: oversize & overtime。

  • 举个栗子,当set hello world时会有以下数据模型:

    • dictEntry: Redis给每个key-value键值对分配一个dictEntry,里面有着key和val的指针,next指向下一个dictEntry形成链表,这个指针可以将多个哈希值相同的键值对链接在一起,由此来解决哈希冲突问题(链地址法)。
    • **sds: **键key “hello” 是以SDS(简单动态字符串)存储。
    • redisObject: 值val是存储在redisObject。
  • 简单动态字符串: 长度动态可变,可类比为Java中的ArrayLIst。Redis中不仅Key是以SDS形式存储,String类型value对象也有以SDS存储。

    1
    2
    3
    4
    5
    struct sdshdr {
    int len; // buf数组中已经使用的字节的数量,也就是SDS字符串长度
    int free; // buf数组中未使用的字节的数量
    char buf[]; // 字节数组,字符串就保存在这里面
    };
    • **常数复杂度获取字符串长度: **len字段存储字符串长度
    • **预空间分配: **
      • SDS长度(len的值)小于1MB,那么程序将分配和len属性同样大小的未使用空间,这时free和len属性值相同。
      • SDS长度(len的值)大于等于1MB,程序会分配1MB的未使用空间。
    • 惰性释放空间: 当执行sdstrim(截取字符串)之后,SDS不会立马释放多出来的空间,如果下次再进行拼接字符串操作,且拼接的没有刚才释放的空间大,则那些未使用的空间就会排上用场。通过惰性释放空间避免了特定情况下操作字符串的内存重新分配操作。
    • 杜绝缓冲区溢出: 使用C字符串的操作时,如果字符串长度增加(如strcat操作)而忘记重新分配内存,很容易造成缓冲区的溢出;而SDS由于记录了长度,相应的操作在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。