Redis

本文最后更新于:2020年9月28日 中午

Redis文档

官方网站:https://redis.io/

Redis简介

什么是Redis

Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

什么是BSD开源协议

一个给予使用者很大自由的协议。基本上使用者可以”为所欲为”,可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。

NoSQL

NoSQL,泛指非关系型的数据库
传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题:

  • 高并发读写
  • 海量数据的高效存储访问需求
  • 高可扩展性和和高可用性需求

NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

NoSQL类别

NoSQL类别 相关产品 典型应用 数据类型 优势 劣势
Key-Value存储数据库 Tokyo Cabinet/Tyrant, Redis, Voldemort 内容缓存,处理大量数据的高访负载 键值对 快速查询 存储的数据缺少结构化
列存储数据库 Cassandra, HBase, Riak 分布式的文件系统 以列簇式存储,将同意列数据存在一起 查询速度快,可扩展性强,更容易进行分布式扩展 功能相对局限
文档型数据库 CouchDB, MongoDB Web应用 一系列键值对(与Key-Value类似,Value是结构化的) 数据结构要求不严 查询性能不高,缺乏统一的查询语法
图形数据库 Neo4j, InfoGrid, Infinite Graph 社交网络 图结构 利用图结构相关算法 需要对整个图做计算才能得出结果,不容易做分布式的集群方案

Redis特点

  • 性能极高
    Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型
    Redis支持的类型 String, List, Hash, Set 及 Ordered Set 数据类型操作。
  • 原子
    Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性
    Redis还支持 publish/subscribe, 通知, key 过期等等特性。
  • 高速读写
    使用自己实现的分离器,代码量很短,没有lock(MySQL),因此效率高。

Redis是一个简单的,高效的,分布式的,基于内存的缓存工具。
架设好服务器后,通过网络连接(类似数据库),提供Key-Value式缓存服务。
简单,是Redis突出的特色。
简单可以保证核心功能的稳定和优异。

Redis总结

redis单个key 存入512M大小
redis支持多种类型的数据结构(string,list,hash.set.zset)
redis 是单线程 原子性
redis可以持久化 因为使用了 RDB和AOF机制
redis支持集群 而且redis 支持库(0-15) 16个库
redis 还可以做消息队列 比如聊天室 IM

企业级开发中: 可以用作数据库、缓存(热点数据(经常会被查询,但是不经常被修改或者删除的数据)和消息中间件等大部分功能。

优点:

  1. 丰富的数据结构
  2. 高速读写
    redis使用自己实现的分离器,代码量很短,没有使用lock(MySQL),因此效率非常高。

缺点:

  1. 持久化
    Redis直接将数据存储到内存中,要将数据保存到磁盘上,Redis可以使用两种方式实现持久化过程。定时快照(snapshot):每隔一段时间将整个数据库写到磁盘上,每次均是写全部数据,代价非常高。第二种方式基于语句追加(aof):只追踪变化的数据,但是追加的log可能过大,同时所有的操作均重新执行一遍,回复速度慢。
  2. 耗内存
    占用内存过高。

Redis安装

一般安装

安装gcc语言编译环境

Redis是C语言开发,下载的源码需要编译,编译依赖gcc环境

1
yum -y install gcc automake autoconf libtool make 

安装Redis

  1. 下载源码
    1
    wget http://download.redis.io/releases/redis-5.0.8.tar.gz
    建议官网获取下载地址
  2. 解压
    1
    tar zxvf redis-5.0.8.tar.gz
  3. 进入解压出来的目录
    1
    cd redis-5.0.8
    注意版本导致的文件夹名称不同
  4. 编译
    1
    make
    看到它说It's a good idea to run 'make test' ;) 就代表成了
  5. 安装到指定目录
    1
    make PREFIX=/usr/local/redis install
    这里安装到了/usr/local/redis

Docker安装

  1. 拉取镜像

    1
    docker pull redis
  2. 查看是否拉取成功

    1
    docker images
  3. 创建设置文件夹

    1
    2
    mkdir -p etc/docker/redis/conf
    mkdir -p etc/docker/redis/data

    一个用于映射设置,一个用于映射数据

  4. 创建配置文件

    1
    vim etc/docker/redis/conf/redis.conf

    写入redis配置并保存,官方默认配置文件
    下载完以后可以自行修改配置

  5. 运行容器

    1
    docker run -itd --name redis -p 6378:6379  -v /etc/docker/redis/conf/redis.conf:/etc/redis/redis.conf  -v /etc/docker/redis/data:/data  redis  --requirepass 65535 --appendonly yes  
    • –name redis
      容器名设置为redis
    • -p 6378:6379
      映射容器服务的 6379 端口到宿主机的 6378 端口。外部可以直接通过宿主机ip:6378 访问到 Redis 的服务
    • -v /docker/redis/redis.conf:/etc/redis/redis.conf
      映射配置文件
    • -v /docker/redis/data:/data
      映射数据目录
    • –requirepass 65535
      设置访问密码为65535
    • –appendonly yes
      开启数据持久化
  6. 访问控制台

    1
    docker exec -it redis redis-cli

    如果设置了账户密码

    1
    docker exec -it redis redis-cli -a your_password

    根据自己设置的信息来更改命令

Redis基础

Redis基本操作

启动Redis服务端

  1. 来到安装目录
    1
    cd /usr/local/redis
  2. 启动Redis服务
    1
    2
    cd /usr/local/redis 
    ./bin/redis-server

    看到这个蛋糕,就说明你启动成功了

关闭服务端

  • 杀进程方式
    会造成数据丢失
    1
    2
    ps -ef | grep -i redis  # 查询redis进程
    kill -9 PID # 通过进程id杀进程
  • 正常关闭方式
    客户端执行
    1
    shutdown

    启动客户端

  1. 来到安装目录
    1
    cd /usr/local/redis
  2. 启动Redis客户端
    1
    ./bin/redis-cli
    命令参考:redis-cli –h IP地址 –p 端口 -a 密码

退出客户端

1
键盘上按 Ctrl+C

远程连接

必须设置密码, 注意防火墙的问题

RedisDesktopManager(收费):https://github.com/uglide/RedisDesktopManager
AnotherRedisDesktopManager(免费):
https://github.com/qishibo/AnotherRedisDesktopManager/

Redis配置

Redis定义了很多默认配置
但一般我们都会通过手动配置完成
Redis的配置文件位于根目录下,文件名为reids.conf

配置文件复制

1
cp redis-5.0.8/redis.conf /usr/local/redis/

将配置文件从解压目录,复制到安装目录下

redis.conf 配置文件详解

  • 绑定的主机地址

    1
    bind 127.0.0.1

    想要什么ip能连上来,那就绑定好了

  • 是否为守护进程

    1
    daemonize no

    Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
    守护进程:在后台运行并且不受任何终端控制的进程。
    你用终端打开一个进程,终端被你关了。
     如进程为非守护进程,进程会被清除。
     如进程为守护进程,则在你关闭终端后,会继续运行。

  • 指定pidfile路径

    1
    pidfile /var/run/redis.pid

    当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件

  • 指定Redis监听端口

    1
    port 6379

    默认端口为6379
    为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字

  • 当客户端闲置多长时间后关闭连接

    1
    timeout 300

    如果指定为0,表示关闭该功能

  • 指定日志记录级别

    1
    loglevel verbose

    Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose

  • 日志记录方式,默认为标准输出

    1
    logfile stdout

    如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null

  • 设置数据库的数量,默认数据库为0

    1
    databases 16

    可以使用SELECT <dbid>命令在连接上指定数据库id
    id是从0开始的

  • 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件

    1
    2
    3
    save 900 1  # 900秒(15分钟)内有1个更改
    save 300 10 # 300秒(5分钟)内有10个更改
    save 60 10000 # 60秒内有10000个更改

    可以多个条件配合
    save <seconds> <changes>
    由于东西都存在内存里,断电数据全没,需要定时保存
    Redis默认配置文件中设置了三个条件

  • 指定存储至本地数据库时是否压缩数据

    1
    rdbcompression yes

    默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大

  • 指定本地数据库文件名

    1
    dbfilename dump.rdb

    默认值为dump.rdb
    在你关闭Redis的时候,数据会被存到这个文件里

  • 指定本地数据库存放目录

    1
    dir ./

    默认是当前目录

  • 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步

    1
    slaveof <masterip> <masterport>
  • 当master服务设置了密码保护时,slav服务连接master的密码

    1
    masterauth <master-password>
  • 设置Redis连接密码

    1
    requirepass foobared

    如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭(无密码)

  • 设置同一时间最大客户端连接数

    1
    maxclients 128

    默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数
    如果设置 maxclients 0,表示不作限制。
    当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

  • 指定Redis最大内存限制

    1
    maxmemory <bytes>

    Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区

  • 是否在每次更新操作后进行日志记录

    1
    appendonly no

    Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no

  • 指定更新日志文件名

    1
    appendfilename appendonly.aof

    默认为appendonly.aof

  • 指定更新日志条件

    1
    appendfsync everysec

    共有3个可选值:
    no:表示等操作系统进行数据缓存同步到磁盘(快)
    always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
    everysec:表示每秒同步一次(折中,默认值)

  • 指定是否启用虚拟内存机制

    1
    vm-enabled no

    默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中

  • 虚拟内存文件路径

    1
    vm-swap-file /tmp/redis.swap

    默认值为/tmp/redis.swap,不可多个Redis实例共享

  • 将所有大于vm-max-memory的数据存入虚拟内存

    1
    vm-max-memory 0

    无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0

  • Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值

    1
    vm-page-size 32
  • 设置swap文件中的page数量

    1
    vm-pages 134217728

    由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。

  • 设置访问swap文件的线程数

    1
    vm-max-threads 4

    最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4

  • 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

    1
    glueoutputbuf yes
  • 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法

    1
    2
    hash-max-zipmap-entries 64
    hash-max-zipmap-value 512
  • 指定是否激活重置哈希

    1
    activerehashing yes

    默认为开启(后面在介绍Redis的哈希算法时具体介绍)

  • 指定包含其它的配置文件

    1
    include /path/to/local.conf

    可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

最基本自定义配置文件

  1. 进入对应的安装目录 /usr/local/redis

  2. 启动守护进程

    1
    daemonize no -> daemonize yes
  3. 允许本机以外的主机访问

    1
    bind 127.0.01 -> # bind 127.0.01 

    若是要特定的ip才能访问,也可以设置。

  4. 设置密码

    1
    requirepass 设置密码 设置数据库密码

    Redis速度很快,在一台好的服务器里,一个外部用户能进行150000次/秒 的密码尝试,这意味着你需要设置好密码来防止暴力破解。

  5. 用自己的配置文件启动
    Redis根目录下

    1
    ./bin/redis-server ./redis.conf

    注意配置文件路径的问题,这里设置的是当前路径下的reids.conf配置文件

Redis内存维护策略

redis作为优秀的中间缓存件,时常会存储大量的数据,即使采取了集群部署来动态扩容,也应该即使的整理内存,维持系统性能。

在redis中有两种解决方案,

数据设置超时时间

1
2
expire key time(以秒为单位)  # 最常用方式
setex(String key, int seconds, String value) # 字符串独有方式
  • 除了字符串有自己独有设置过期时间的方式外,其他方法都需要依靠expire方法来设置过期时间
  • 如果没有设置时间,那么换成永不过期
  • 如果设置了过期时间,只有又想让缓存永不过期,使用persist key

采用LRU算法动态将不用的数据删除

LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。

  1. volatile-lru:设定超时时间的数据中,删除最不常使用的数据.
  2. allkeys-lru:查询所有的key中最近最不常使用的数据进行删除,这是应用最广泛的策略.
  3. volatile-random:在已经设定了超时的数据中随机删除.
  4. allkeys-random:查询所有的key,之后随机删除.
  5. volatile-ttl:查询全部设定超时时间的数据,之后排序,将马上将要过期的数据进行删除操作.
  6. noeviction:如果设置为该属性,则不会进行删除操作,如果内存溢出则报错返回.
    • volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
    • allkeys-lfu:从所有键中驱逐使用频率最少的键

Redis命令

Redis命令 用于在 Redis 上执行操作

Redis支持数据类型:String(字符串),hash(哈希),list(列表),set(集合),zset(sortedset)(有序集合)……

常用命令

key管理

删除
  • del <key_name>
    删除key
修改
  • rename <key_name> <new_key_name>
    重命名keynew_key
  • MOVE <key_name> <db>
    将当前数据库的key移动到给定的数据库db中
查询相关
  • keys *
    返回所有满足条件的key
    可以模糊匹配,比如keys aaa* 代表abc开头的所有key
    通配符:
     *:代表所有
     ?: 代表一个字符
  • exists <key_name>
    是否存在指定key,存在返回1,不存在返回0
  • type <key_name>
    返回key对应的值存储的数据类型
过期时间相关
  • expire <key_name> <second>
    设置某个key的过期时间,时间单位为 秒
  • PEXPIRE <key_name> <millisecond>
    设置某个key的过期时间,时间单位为 毫秒
  • ttl <key_name>
    查看剩余时间,返回key剩余生存时间,时间单位为 秒
    key不存在,返回-2
    key存在,但没有设置过期时间时,返回-1
  • pttl <key_name>
    查看剩余时间,返回key剩余生存时间,时间单位为 毫秒
  • persist <key_name>
    取消过期时间

key命名规范

单个key只允许存入512M

  • 一般用:来分隔信息
    例如 学校:班级:学号
  • 不要太长,不要太短
    太长消耗内存,也降低查找效率,太短可读性会降低
  • 同一个项目中,key要有统一的命名模式
  • 建议全部大写
    注意是会区分大小写的

参考:

  1. 第一段放置项目名或缩写
  2. 第二段把表名转换为key前缀
  3. 第三段放置用于区分区key的字段, 对应mysql中的主键的列名
  4. 第四段放置主键值

Redis数据类型

String类型

信息

String类型是最基本的数据类型,一个键最大能存512MB
String数据结构是简单的key-value类型,value值不仅可以是String,也可以是数字,是包含很多种类型的特殊类型
String类型是二进制安全的,可以包含任何数据

场景

保存字符串
保存图片
统计数量(点赞数,浏览数之类)

其自增自减指令具有原子操作的特性,而且redis性能很好

命令

赋值

  • set <key_name> <value>
    设置值。同一key多次赋值会覆盖,无视类型
  • mget <key_name1> <value1> <key_name2>.....
    一次性设置多个值
  • setnx <key_name> <value>
    设置值。分布式锁的方案之一
     如果key不存在,则设值并返回1
     如果key存在,则不设值并返回0
  • setnx <key_name> <life_time> <value>
    设置值。并设置过期时间,单位秒

取值

  • get <key_name>
    获取指定key的值
     若不存在,返回nil
     若key的值不是字符串类型,返回一个错误
  • mget <key_name1> <key_name2> <key_name3>.....
    获取多个key的值
  • getrange <key_name> <start> <end>
    获取存储在指定key中的字符串的子串。
    字符截取范围有startend两个偏移量决定(包括其本身)
    相当于数组下标切片
  • getbit <key_name> offset
    获取存储在指定key中的字符串的指定偏移量上的bit
  • getset <key_name> <value>
    设定key的值,并返回key的旧值
    key不存在时,返回nil
  • strlen <key_name>
    获取key所存储的字符串值的长度

删除

  • del <key_name>
    删除key

字符串拼接

  • append <key_name> <value>
    将value追加到指定key的末尾
    若不存在,则为之赋值

自增/自减

使用自增或自减,键值必须为数字类型,否则报错

  • incr <key_name>
    key中存储的数字值加1
    key不存在,那么key的值初始化为0,然后再执行incr操作
  • decr <key_name>
    key中存储的数字值减1
  • incrby <key_name> <int_step>
    key中存储的数字值加int_step
  • decrby <key_name> <int_step>
    key中存储的数字值减int_step

Hash类型

Hash类型的键值存放的是field(域)和value(值)的映射表

没错,套娃的感觉

Hash特别适合用于存储对象的信息
相比于将对象类型存储在String类型中,存储在Hash类型中能节约更多的内存空间
每个Hash能存储2^32-1键值对

场景

存储一个对象
Hash是最接近关系型数据库结构的数据类型。

为什么不用String存储一个对象?
存储对象最重要是通过id找到对象。

  • 若是在key中增加id的信息
  • 当对象的字段变多,因为存储/传递key中的id所消耗的资源会变多。
  • 若是在key的值单纯为拼接了很多信息的String
  • 每次更改,查询都需要进行 序列化或反序列化(指字符串转为对象 或 对象转为字符串),浪费资源。
  • 查询时一返回就返回整个字符串,浪费资源。
  • 修改时会将整个字符串锁住,无法访问信息,浪费资源。

Hash命令

赋值

  • hset <key_name> <field> <value>
    指定key,存放field-value
  • hmset <key_name> <field_1> <value_1> <field_2> <value_2>......
    同时设置多个field-value
  • hsetnx <key_name> <field> <value>
    只在字段field不存在时,设置Hash字段的值

取值

  • hget <key_name> <field>
    获取存储在Hash中的指,并根据Field得到value
  • hmget <key_name> <field_1> <field_2> <field_3>.......
    获取存储在Hash中的指,并根据多个Field得到多个value
  • hgetall <key_name>
    返回Hash中所有的字段
  • hlen <key_name>
    返回Hash中字段的数量
  • hexists <key_name> <field>
    查看Hash中的指定Field是否存在

删除

  • del <key_name>
    删除key
  • hdel <key_name> <field_1> <field_2>......
    删除key中的一些field

自增、自减

  • hincrby <key_name> <field> <int_step>
    为对应key的对应field字段加上int类型增量int_step
  • hincrbyfloat <key_name> <field> <float_step>
    为对应key的对应field字段加上float类型增量float_step

List类型

链表结构集合。
既可以作为队列,也可以作为栈

场景

数据量大的数据删减
任务队列

List命令

赋值

  • lpush <key_name> <value_1> <value_2>......
    将一个或多个值插入到列表左侧
  • rpush <key_name> <value_1> <value_2>......
    将一个或多个值插入到列表右侧
  • lpushx <key_name> <value>
    将一个或多个值插入到列表左侧,若列表不存在,操作无效。
  • rpushx <key_name> <value>
    将一个或多个值插入到列表右侧,若列表不存在,操作无效。

取值

  • llen <key_name>
    获取列表长度
  • lindex <key_name> <index>
    通过索引获取列表中的指定元素
  • lrange <key_name> <start> <end>
    通过索引范围获取列表中的元素

删除

  • del <key_name>
    删除key
  • lpop <key_name>
    删除并返回列表左侧第一个元素
  • rpop <key_name>
    删除并返回列表右侧第一个元素
  • blpop <key_name> <timeout>
    删除并返回列表左侧第一个元素,若当前List中没有元素,那么会阻塞列表,直到等待超时 或 发现可弹出元素为之

    若timeout不设置,那么会永久等待

  • brpop <key_name> <timeout>
    删除并返回列表右侧第一个元素,若当前List中没有元素,那么会阻塞列表,直到等待超时 或 发现可弹出元素为之
    timeout不设置,那么会永久等待
  • ltrim <key_name> <int_start> <int_stop>
    让列表只保留int_startint_stop区间内的元素,区间外的元素删除

修改

  • lset <key_name> <index> <value>
    通过索引设置List元素的值
  • linsert <key_name> before|after <element> <value>
    在指定key对应的列表元素的 前或后 插入一个值
    element为值,并非序号
  • rpoplpush <key_name_1> <key_name_2>
    key_name_1对应列表最右侧元素弹出,并添加到key_name_2对应列表最左侧
    可以指定相同的key,形成列表循环
  • brpoplpush <key_name_1> <key_name_2> timeout
    key_name_1对应列表最右侧弹出一个值,将弹出的值插入key_name_2对应列表的最左侧。
    若列表没有元素会阻塞列表直到超时或发现可弹元素为止

Set类型

Set类型是String类型的无序集合
每一个集合成员是唯一的,不会也不能出现重复的数据
集合中最多能有 2^32 -1 个成员(约40亿)

场景

需要用到差,并,交这种运算的地方
避免重复

Set命令

赋值

  • sadd <key_name> <menber_1> <menber_2> ......
    向集合添加一个或多个成员

取值

  • scard <key_name>
    获取集合的成员数
  • smembers <key_name>
    返回集合的所有成员
  • sismember <key_name> <member>
    判断member是否为key集合的成员
  • srandmamber <key_name> <int_count>
    返回int_count个结合中的元素

删除

  • del <key_name>
    删除key
  • srem <key_name> <menber_1> <menber_2> ......
    删除集合中的一个或多个成员
  • spop <key_name> <int_count>
    移除并返回集合中的int_count个随机元素
  • smove <key_name_1> <key_name_2> <member>
    将成员menberkey_name_1集合移动到key_name_2集合中去

集合操作

差集
  • sdiff <key_name_1> <key_name_2> ......
    返回给定所有集合的差集
  • sdiffstore <final_key_name> <key_name_1> <key_name_2>......
    返回给定的集合的差集,并保存在final_key_name
交集
  • sinter <key_name_1> <key_name_2> ......
    返回给定所有集合的交集
  • sinterstore <final_key_name> <key_name_1> <key_name_2>......
    返回给定的集合的交集,并保存在final_key_name
并集
  • sunion <key_name_1> <key_name_2> ......
    返回给定所有集合的并集
  • sunionstore <final_key_name> <key_name_1> <key_name_2>......
    返回给定的集合的并集,并保存在final_key_name

ZSet类型

有序集合
每一个元素都会关联一个double类型的分数,Redis通过记录的分数来对集合进行 从小到大 的排序
Set类型一样,同一个集合内,不允许出现重复元素
分数允许重复
最多成员数为2^32 -1(约40亿)

场景

排行榜

Set命令

赋值

  • zadd <key_name> <socre_1> <score_1> <socre_2> <score_2>......
    向有序集合添加一个或多个成员,或更新已存在成员的分数

取值

  • zcard <key_name>
    获取有序集合成员数
  • zcount <key_name> <min> <max>
    计算在有序集合中,分数处于minmax之间的成员数
  • zrank <key_name> <menber>
    返回有序集合中指定menber的索引
  • zrange <key_name> <start> <stop>
    返回有序集合指定 索引区间 内的成员(低到高)
    start=0 stop=-1则为所有
  • zrevrange <key_name> <start> <stop>
    返回有序集合指定 索引区间 内的成员(高到低)
  • zrangebyscore <key_name> <min> <max>
    返回有序集合指定 分数区间 内的成员(低到高)
  • zrevrangebyscore <key_name> <max> <min>
    返回有序集合指定 分数区间 内的成员(高到低)

删除

  • del <key_name>
    删除key
  • zrem <key_name> <member_1> <member_2>
    删除有序集合中的一个或多个成员
  • zremrangebyrank <key_name> <start> <stop>
    删除有序集合中给定的 排名区间 的所有成员(第一名为0,从低到高)
  • zremrangebyscore <key_name> <min> <max>
    删除有序集合中给定的 分数区间 的所有成员(第一名为0,从低到高)

自增

  • zincrby <key_name> <increment> <member>
    增加menber元素分数increment

HyperLogLog类型

用来做基数统计

什么是基数
数据集{1, 3,5, 7, 5, 1, 9} 的 基数集为{1, 3, 5, 7, 9}

  • 在输入袁术的数量或体积非常非常大时,计算基数所需的空间总是固定的,并且很小。
  • 每个HyperLogLog只需要 12KB内存 ,可以计算接近2^64个不同元素的基数
  • 核心是基数估计算法,最终数值会有一定误差
  • HyperLogLog 只会根据输入的元素来计算基数,并不会存储元素本身

场景

数据量大的数据统计
注意,如果数据量小,反而会浪费空间

HyperLogLog命令

  • pfadd <key_name> <element_1> <element_2>
    添加指定元素到HyperLogLog
  • pfcount <key_name_1> <key_name_2>
    返回给定的HyperLogLog基数估算值
  • pfmerge <key_name_1> <key_name_2> <key_name_2>
    将多个HyperLogLog合并到key_name_1

发布订阅(pub/sub)

Redis发布订阅(pub/sub)是一种消息通信模式
发送者(pub)发送消息,订阅者(sub)接受消息
客户端可以订阅任意数量的频道

三个客户端订阅频道channel1

订阅了以后,有新的消息来的话会通过PUBLISH命令发送给频道channel1

常用命令

订阅

  • SUBSCRIBE channel [channel1.....]
    订阅给定的一个或多个频道
  • PSUBSCRIBE pattern [pattern]
    订阅一个或多个符合给定模式的频道

消息发布

  • PBULISH channel1 message
    将消息发布到指定频道

退订

  • UNSUBSCRIBE [channel....]
    退订给定的频道
  • PUNSUBSCRIBE [pattern]
    退订给定模式的频道

应用场景

博客订阅,微信公众号订阅,新闻订阅等

多数据库

数据库是由一个整数索引标识的,而不是数据库名称
默认情况下链接到数据库0
你可以自己在配置文件下设置数据库数量

常用命令

  • select 数据库索引数
    切换数据库
  • move key 目标数据库索引数
    移动数据到另一个库中
  • flushdb
    清空当前数据库所有的key
  • flushall
    清空整个Redis数据库所有key

Redis事务

Redis事务可以一次执行多个命令(单独步骤中执行一组命令)

批量操作在发送EXEC命令前被放入队列缓存
收到EXEC命令后,进入事务执行,事务中的任意命令执行失败,其余命令依然执行
事务进行过程中,客户端提交的命令不会插入到事务执行命令序列中

Redis会将一个事务中的所有命令序列化,然后按顺序执行
执行中不会被其他命令插入,不允许加塞行为
并没有回滚机制

常用命令

DISCARD
取消事务
EXEC
执行所有事务块内的命令
MULTI
标记一个事务块的开始
UNWATCH
取消WATCH命令对所有key的监视
WATCH key [key.....]
监视一个或多个Key,如果这些key被改动,那么事务将被打断

事务流程

  1. 开始事务
  2. 命令入队
  3. 执行事务

示例1 A向B转50元

1
2
3
4
5
6
7
multi  // 事务开始
get account:a // 获取a账户金额
incrby account:b 50 // b账户增加50元
decrby account:a 50 // a账户减少50元
get account:a // 得到a账户余额
get account:b // 得到b账户余额
exec // 执行队列

示例2 DISCARD放弃队列运行

1
2
3
4
multi  // 事务开始
set aa 123
get aa
discard

实际上什么都不会执行,事务将命令传入,并没有执行就解散了队列

示例3 事务错误处理

1
2
3
4
5
multi
set aa hello
get aa
incr aa
exec

如果某个命令报错,则有报错的命令不会被执行,其他命令照样执行,且不会回滚

示例4 事务的WATCH

某一账户在事务内进行操作,在提交事务前,另一个进程对账户进行操作

1
2
3
4
5
watch a  // 开启监视,如果目标在事务开启前被改动,则打断事务(不会执行)
multi
get a
incr a
exec // 执行事务

应用场景

想要保证一组命令执行过程中不被其它命令插入
原子性
诸如商品秒杀之类的

Redis持久化

持久化:把内存的数据写到磁盘中去,防止服务器宕机后数据丢失
Redis提供了两种持久化方式

  • RDB(默认)
  • AOF

RDB

Redis DataBase的缩写
功能是rdbSave(生成RDB文件到磁盘)和rdbLoad(从RDB文件中载入内存)两个函数

优点:快照保存速度快,还原也快
缺点:需要占用内存

快照条件 见配置redis.conf文件

AOF

Append-only file的缩写
这种方式,会将每一个收到的命令写到文件中(默认为appendoly.aof),redis重启后,会通过重新执行文件中的命令来重建数据库内容

每当执行任务或函数时,flushAppendOnlyFile函数都会被调用,这个函数执行以下两个工作aof写入保存
WRITE:判断条件,将aof_buf中的缓存写入到AOF文件
SAVE:根据条件,调用fsync或fdatasync函数,将AOF文件保存到磁盘中

实时同步-异步同步

实时同步

对于一致性要求高的,应采用实时同步方案

  • 查询缓存查询不到再从DB查询,查询到后,顺便将数据保存到缓存
  • 更新数据到缓存时,先更新数据库,缓存中的数据设置过期

异步队列

对于并发高的,可采用异步队列的方式同步
东西不直接存到数据库中,而是存到中间件队列里,等数据库什么时候有空了,在从队列里处理数据
能实现的中间件比较多:ActiveMQ, RabbitMQ, ZeroMQ, Kafaka

UDF自定义函数

面对mysql接口编程,利用触发器进行缓存同步
学习成本高

缓存穿透

查询一个不存在的数据。由于缓存时不命中,需要从数据库查询,查不到数据则不写入缓存。这将导致这个不存在的数据每次请求都要查两次,查完缓存查数据库,造成缓存穿透

解决方法

布隆过滤器


布隆过滤器是一种数据结构
对所有可能查询的参数以hash形式存储,当用户想要查询的时候,使用布隆过滤器发现不在集合中,就直接丢弃,不再对持久层查询

适用场景

  • 数据命中不高
  • 数据相对固定,实时性低

缺点

  • 代码维护
  • 一定的缓存空间

缓存空对象


持久层查询不到就缓存空结果
查询时,先判断缓存中是否存在(exists(key)),如果有直接返回空,没有则查询后返回

注意insert时需要清除查询的key,否则就算数据库有值,也只查不到

适用场景

  • 数据命中不高
  • 数据频繁变化,实时性高

缺点

  • 存了空值,要浪费很多的缓存空间
  • 对于需要保持一致性的业务会有影响

缓存雪崩

缓存集中大量失效时,引发大量的数据库查询

解决方法

redis高可用

这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群

限流降级

这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待

数据预热

数据加热的含义就是在正式部署之前,把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀

热点key

某个key有大量线程访问,其失效的瞬间,有大量线程来构建缓存,造成后端负载加大,甚至可能会让系统崩溃

解决方法

互斥锁(mutex key)

只让一个线程构建缓存,其他线程等待构建缓存的线程执行

缓存时间设置

不会失效就不会有问题,时间长点也会让发生频率降低


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!