• Redis总结


    Redis

    一、简介

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

    Redis中文网

    1. 竞品

    a. Memcached介绍

    Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提供动态、数据库驱动网站的速度,现在已被LiveJournal、hatena、Facebook、Vox、LiveJournal等公司所使用。

    b. Memcached工作方式分析

    许多Web应用都将数据保存到 RDBMS中,应用服务器从中读取数据并在浏览器中显示。 但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、 网站显示延迟等重大影响。Memcached是高性能的分布式内存缓存服务器,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web等应用的速度、 提高可扩展性。下图展示了memcache与数据库端协同工作情况:

    image.png

    c. 过程

    • 1.检查用户请求的数据是缓存中是否有存在,如果有存在的话,只需要直接把请求的数据返回,无需查询数据库。
    • 2.如果请求的数据在缓存中找不到,这时候再去查询数据库。返回请求数据的同时,把数据存储到缓存中一份。
    • 3.保持缓存的“新鲜性”,每当数据发生变化的时候(比如,数据有被修改,或被删除的情况下),要同步的更新缓存信息,确保用户不会在缓存取到旧的数据。

    2. 对比优缺点

    • a. Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等。
    • b. Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。
    • c. 虚拟内存–Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘
    • d. 过期策略–memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10
    • e. 存储数据安全–memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化)
    • f. 灾难恢复–memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复
    • g. Redis支持数据的备份,即master-slave模式的数据备份。

    3. 总结

    应该说Memcached和Redis都能很好的满足解决我们的问题,它们性能都很高,总的来说,可以把Redis理解为是对Memcached的拓展,是更加重量级的实现,提供了更多更强大的功能。具体来说:

    • 1.性能上:
      性能上都很出色,具体到细节,由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比 Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。

    • 2.内存空间和数据量大小:
      MemCached可以修改最大内存,采用LRU算法。Redis增加了VM的特性,突破了物理内存的限制。

    • 3.操作便利上:
      MemCached数据结构单一,仅用来缓存数据,而Redis支持更加丰富的数据类型,也可以在服务器端直接对数据进行丰富的操作,这样可以减少网络IO次数和数据体积。

    • 4.可靠性上:
      MemCached不支持数据持久化,断电或重启后数据消失,但其稳定性是有保证的。Redis支持数据持久化和数据恢复,允许单点故障,但是同时也会付出性能的代价。

    • 5.应用场景:
      Memcached:动态系统中减轻数据库负载,提升性能;做缓存,适合多读少写,大数据量的情况(如人人网大量查询用户信息、好友信息、文章信息等)。
      Redis:适用于对读写效率要求都很高,数据处理业务复杂和对安全性要求较高的系统(如新浪微博的计数和微博发布部分系统,对数据安全性、读写要求都很高)。

    需要慎重考虑的部分

    • 1.Memcached单个key-value大小有限,一个value最大只支持1MB,而Redis最大支持512MB
    • 2.Memcached只是个内存缓存,对可靠性无要求;而Redis更倾向于内存数据库,因此对对可靠性方面要求比较高
    • 3.从本质上讲,Memcached只是一个单一key-value内存Cache;而Redis则是一个数据结构内存数据库,支持五种数据类型,因此Redis除单纯缓存作用外,还可以处理一些简单的逻辑运算,Redis不仅可以缓存,而且还可以作为数据库用

    二、安装

    1. Window

    下载地址

    1. Redis 支持 32 位和 64 位。这个需要根据你系统平台的实际情况选择,这里我们下载 Redis-x64-xxx.zip压缩包到 C 盘,解压后,将文件夹重新命名为 redis。

    2. 打开一个 cmd 窗口 使用 cd 命令切换目录到 C: edis 运行:

      redis-server.exe redis.windows.conf
      
    3. 这时候另启一个 cmd 窗口,原来的不要关闭,不然就无法访问服务端了。切换到 redis 目录下运行:

      redis-cli.exe -h 127.0.0.1 -p 6379
      

    2. Linux

    下载地址

    1. 命令安装

      $ wget http://download.redis.io/releases/redis-5.0.7.tar.gz
      $ tar xzf redis-5.0.7.tar.gz
      $ cd redis-5.0.7
      $ make
      
    2. src 目录中现在提供了已编译的二进制文件 。使用以下命令运行Redis:

      $ src/redis-server
      
    3. 运行

      $ src/redis-cli
      redis> set foo bar
      OK
      redis> get foo
      "bar"
      
    4. 关闭

      • 如果是用apt-get或者yum install安装的redis,可以直接通过下面的命令停止/启动/重启redis
      /etc/init.d/redis-server stop
      /etc/init.d/redis-server start
      /etc/init.d/redis-server restart
      
      • 如果是通过源码安装的redis,则可以通过redis的客户端程序redis-cli的shutdown命令来重启redis
      redis-cli -h 127.0.0.1 -p 6379 shutdown
      

      如果上述方式都没有成功停止redis,则可以使用终极武器 kill -9

    三、配置

    1. 配置文件

    语法:

    // 获取
    CONFIG GET CONFIG_SETTING_NAME
    // 编辑
    CONFIG SET CONFIG_SETTING_NAME NEW_CONFIG_VALUE
    

    实例:

    127.0.0.1:6379> CONFIG GET loglevel
    1) "loglevel"
    2) "notice"
    127.0.0.1:6379> config set appendonly yes
    127.0.0.1:6379> config set save ""
    OK
    

    2. 常用配置属性

    1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程

      daemonize no
      
    2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定

      pidfile /var/run/redis.pid
      
    3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字

      port 6379
      
    4. 绑定的主机地址

      bind 127.0.0.1
      
    5. 当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

      timeout 300
      
    6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose

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

      logfile stdout
      
    8. 设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id

      databases 16
      
    9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

      save <seconds> <changes>
      
      Redis默认配置文件中提供了三个条件:
      
      save 900 1
      
      save 300 10
      
      save 60 10000
      

      分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

    10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大

      rdbcompression yes
      
    11. 指定本地数据库文件名,默认值为dump.rdb

      dbfilename dump.rdb
      
    12. 指定本地数据库存放目录

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

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

      masterauth <master-password>
      
    15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,默认关闭

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

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

      maxmemory <bytes>
      
    18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no

      appendonly no
      
    19. 指定更新日志文件名,默认为appendonly.aof

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

      appendfsync everysec
      
    21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中

      vm-enabled no
      
    22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享

      vm-swap-file /tmp/redis.swap
      
    23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0

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

      vm-page-size 32
      
    25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。

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

      vm-max-threads 4
      
    27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

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

      hash-max-zipmap-entries 64
      
      hash-max-zipmap-value 512
      
    29. 指定是否激活重置哈希,默认为开启

      activerehashing yes
      
    30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

      include /path/to/local.conf
      

    四、数据类型

    1. 字符串(String)

    string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。
    string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
    string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。

    使用场景:微博数,粉丝数、缓存、限流、计数器等

    2. 散列(Hash)

    Redis hash 是一个键值(key=>value)对集合。
    Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。

    使用场景:存储用户信息,商品信息、组合查询等

    3. 列表(List)

    list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一
    Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销

    使用场景:微博的关注列表,粉丝列表,消息列表等功能

    4. 集合(Set)

    Redis 的 Set 是 string 类型的无序集合。
    集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

    使用场景:赞、踩、关注、共同关注、共同粉丝、共同喜好等功能

    5. 有序集合(Sorted Set)

    和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列

    使用场景:排行榜、弹幕消息等

    6.其他类型

    其他类型-参考网址

    五、命令

    1. Redis 操作

    redis-server; // 启动服务
    
    redis-cli -h 127.0.0.1 -p 6379 // 连接redis -h 主机地址 -p 端口号
    
    127.0.0.1:6379> PING // 客户端下验证服务器
    PONG
    
    127.0.0.1:6379> exit // 退出客户端
    
    // 关闭服务器
    redis-cli -h 127.0.0.1 -p 6379 shutdown 
    &
    ps -ef | grep redis // 拿到redis的服务进程号如1688
    kill -9 1688
    

    2. Key

    Redis 键命令的基本语法如下:

    127.0.0.1:6379> COMMAND KEY_NAME
    // 例子
    127.0.0.1:6379> set gerf redis
    OK
    127.0.0.1:6379> get gerf
    "redis"
    

    相关命令:

    // DEL key 该命令用于在 key 存在时删除 key
    127.0.0.1:6379> set gerf redis
    OK
    127.0.0.1:6379> del gef
    (integer) 0
    
    // DUMP key 序列化给定 key ,并返回被序列化的值
    127.0.0.1:6379> set gerf redis
    OK
    127.0.0.1:6379> dump gerf // 存在则返回序列化值
    "x00x05redis	x00x15xa2xf8=xb6xa9xdex90"
    127.0.0.1:6379> dump aaa // 不存在则返回 (nil) 
    (nil)
    
    // EXISTS key 检查给定 key 是否存在
    127.0.0.1:6379> EXISTS aaa 
    (integer) 0 // 不存在返回 0
    127.0.0.1:6379> EXISTS gerf
    (integer) 1 // 存在返回 1
    
    // 为给定 key 设置过期时间,以秒计
    127.0.0.1:6379> EXPIREAT gerf 5
    (integer) 1
    127.0.0.1:6379> EXISTS gerf
    (integer) 0
    

    Redis keys 命令

    序号 命令 描述
    1 DEL key 该命令用于在 key 存在时删除 key。
    2 DUMP key 序列化给定 key ,并返回被序列化的值。
    3 EXISTS key 检查给定 key 是否存在。
    4 EXPIRE key seconds 为给定 key 设置过期时间,以秒计。
    5 EXPIREAT key timestamp EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
    6 PEXPIRE key milliseconds 设置 key 的过期时间以毫秒计。
    7 PEXPIREAT key milliseconds-timestamp 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计
    8 KEYS pattern 查找所有符合给定模式( pattern)的 key 。
    9 MOVE key db 将当前数据库的 key 移动到给定的数据库 db 当中。
    10 PERSIST key 移除 key 的过期时间,key 将持久保持。
    11 PTTL key 以毫秒为单位返回 key 的剩余的过期时间。
    12 TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
    13 RANDOMKEY 从当前数据库中随机返回一个 key。
    14 RENAME key newkey 修改 key 的名称
    15 RENAMENX key newkey 仅当 newkey 不存在时,将 key 改名为 newkey 。
    16 TYPE key 返回 key 所储存的值的类型。

    3. String

    在绝大部分编程语言中都有 String 字符串类型,对于作为数据库的 Redis 也是必不可少的。

    • set key value 设置值
    • get key 获取某个key的值
    • mset key1 value1 key2 value2 批量设置并且是原子的,可以用来减少网络时间消耗
    • mget key1 key2 批量获取并且是原子的,可以用来减少网络时间消耗
    127.0.0.1:6379> set user1 aaa
    OK
    127.0.0.1:6379> mset user2 bbb user3 ccc
    OK
    127.0.0.1:6379> get user1
    "aaa"
    127.0.0.1:6379> mget user1 user2 user3
    1) "aaa"
    2) "bbb"
    3) "ccc"
    
    • incr key 自增指定key的值
    • decr key 自减指定key的值
    • incrby key value 自增指定数值
    • decrby key value 自减指定数值
    • incrbyfloat key floatvalue 增加指定浮点数
    127.0.0.1:6379> set count 1
    OK
    127.0.0.1:6379> incr count
    (integer) 2
    127.0.0.1:6379> get count
    "2"
    127.0.0.1:6379> decr count
    (integer) 1
    127.0.0.1:6379> get count
    "1"
    127.0.0.1:6379> incrby count 99
    (integer) 100
    127.0.0.1:6379> get count
    "100"
    127.0.0.1:6379> decrby count 99
    (integer) 1
    127.0.0.1:6379> get count
    "1"
    127.0.0.1:6379> incrbyfloat count 0.5
    "1.5"
    127.0.0.1:6379> get count
    "1.5"
    
    • 前面几个操作就可以用来实现计数器的功能。
    • setnx key value 如果不存在该key则可以设置成功,否则会失败,加上过期时间限制,则是redis实现分布式锁的一种方式(后面会提到)。
    • set key value xx 与前面相反,如果存在则设置成功,否则失败(相当于更新操作)
    127.0.0.1:6379> keys *
    1) "user1"
    2) "user4"
    3) "user2"
    4) "user3"
    5) "count"
    127.0.0.1:6379> setnx user1 11aa
    (integer) 0
    127.0.0.1:6379> get user1
    "aaa"
    127.0.0.1:6379> setnx user5 55aa
    (integer) 1
    127.0.0.1:6379> get user5
    "55aa"
    127.0.0.1:6379> set user4 show xx
    OK
    127.0.0.1:6379> get user4
    "show"
    
    • getset key newvalue 设置新值并返回旧值
    • append key value 为原本内容追加内容
    • strlen key 获取字符串长度
    • getrange key start end 获取指定范围的内容
    • setrange key index value 设置指定范围的内容
    127.0.0.1:6379> keys *
    1) "user1"
    2) "user5"
    3) "user4"
    4) "user2"
    5) "user3"
    6) "count"
    127.0.0.1:6379> get user1
    "aaa"
    127.0.0.1:6379> getset user1 11aa
    "aaa"
    127.0.0.1:6379> get user1
    "11aa"
    127.0.0.1:6379> append user1 22bb
    (integer) 8
    127.0.0.1:6379> get user1
    "11aa22bb"
    127.0.0.1:6379> strlen user
    (integer) 0
    127.0.0.1:6379> strlen user1
    (integer) 8
    127.0.0.1:6379> getrange user1 0 5
    "11aa22"
    127.0.0.1:6379> setrange user1 5 cc
    (integer) 8
    127.0.0.1:6379> get user1
    "11aa2ccb"
    
    • setex key seconds value 设置值且设置过期时间
    • set key value ex seconds nx 为不存在的key设置值且设置过期时间
    127.0.0.1:6379> setex user 10 user111
    OK
    127.0.0.1:6379> ttl user
    (integer) 5
    127.0.0.1:6379> ttl user
    (integer) 1
    127.0.0.1:6379> ttl user
    (integer) 0
    127.0.0.1:6379> set user1 snow ex 10 nx
    (nil)
    127.0.0.1:6379> set user snow ex 10 nx
    OK
    127.0.0.1:6379> get user
    (nil)
    127.0.0.1:6379> set user snow ex 20 nx
    OK
    127.0.0.1:6379> get user
    "snow"
    127.0.0.1:6379> ttl user
    (integer) 7
    127.0.0.1:6379> ttl user
    (integer) 3
    127.0.0.1:6379> ttl user
    (integer) 0
    

    4. Hash

    其实我们可以理解 hash 为 小型Redis ,Redis 在底层实现上和 Java 中的 HashMap 差不多,都是使用 数组 + 链表 的二维结构实现的。

    image.png

    不同的是,在 Redis 中字典的值只能是字符串,而且他们 rehash 的方式不一样,在 Redis 中使用的是 渐进式rehash

    在 rehash 的时候会保留新旧两个 hash 字典,在数据迁移的时候会将旧字典中的内容一点一点迁移到新字典中,查询的同时会查询两个 hash 字典,等数据全部迁移完成才会将新字典代替就字典。

    • hset key field value 设置字典中某个key的值
    • hsetnx key field value 设置字典中某个key的值(不存在的)
    • hmset key field1 value1 field2 value2 ... 批量设置
    • hget key field 获取字典中某个key的值
    • hmget key field1 field2 批量获取
    • hgetall key 获取全部
    127.0.0.1:6379> hset userinfo name gerf
    (integer) 1
    127.0.0.1:6379> hsetnx userinfo name aaa
    (integer) 0
    127.0.0.1:6379> hmset userinfo age 28 email 444@qq.com
    OK
    127.0.0.1:6379> hget userinfo name
    "gerf"
    127.0.0.1:6379> hmget userinfo name age
    1) "gerf"
    2) "28"
    127.0.0.1:6379> hgetall userinfo
    1) "name"
    2) "gerf"
    3) "age"
    4) "28"
    5) "email"
    6) "444@qq.com"
    127.0.0.1:6379> 
    
    • hdel key field 删除某个key
    • hexists key field 判断是否存在
    • hlen key 获取指定key对应的字典中的存储个数
    • hvals key 返回所有的value
    • hkeys key 返回所有的key
    127.0.0.1:6379> hkeys userinfo
    1) "name"
    2) "age"
    3) "email"
    127.0.0.1:6379> hdel userinfo email
    (integer) 1
    127.0.0.1:6379> hkeys userinfo
    1) "name"
    2) "age"
    127.0.0.1:6379> hexists userinfo name
    (integer) 1
    127.0.0.1:6379> hlen userinfo
    (integer) 2
    127.0.0.1:6379> hvals userinfo
    1) "gerf"
    2) "28"
    
    • hincrby key field increValue 增加某个value的值(也可以增加负数)
    • hincrbyfloat key field floatValue 增加某个value的值(浮点数)
    127.0.0.1:6379> hincrby userinfo age 1
    (integer) 29
    127.0.0.1:6379> hget userinfo age
    "29"
    127.0.0.1:6379> hincrbyfloat userinfo age 0.5
    "29.5"
    127.0.0.1:6379> hget userinfo age
    "29.5"
    

    5. List

    Redis 中的列表相当于 Java 中的 LinkedList(双向链表) ,也就是底层是通过 链表 来实现的,所以对于 list 来说 插入删除操作很快,但 索引定位非常慢。

    image.png

    Redis 提供了许多对于 list 的操作,如出,入等操作,你可以充分利用它们来实现一个 栈 或者 队列。

    • lpush key item1 item2 item3... 从左入栈
    • rpush key item1 item2 item3... 从右入栈
    • lpop key 从左出栈
    • rpop key 从右出栈
    • lindex key index 获取指定索引的元素 O(n)谨慎使用
    • lrange key start end 获取指定范围的元素 O(n)谨慎使用
    127.0.0.1:6379> lpush userlist aaa
    (integer) 1
    127.0.0.1:6379> lpush userlist bbb
    (integer) 2
    127.0.0.1:6379> lpush userlist ccc ddd
    (integer) 4
    127.0.0.1:6379> lrange userlist 0 -1
    1) "ddd"
    2) "ccc"
    3) "bbb"
    4) "aaa"
    127.0.0.1:6379> rpush userlist eee
    (integer) 5
    127.0.0.1:6379> lrange userlist 0 -1
    1) "ddd"
    2) "ccc"
    3) "bbb"
    4) "aaa"
    5) "eee"
    127.0.0.1:6379> lpop userlist
    "ddd"
    127.0.0.1:6379> rpop userlist
    "eee"
    127.0.0.1:6379> lrange userlist 0 -1
    1) "ccc"
    2) "bbb"
    3) "aaa"
    127.0.0.1:6379> lindex userlist 1
    "bbb"
    
    • linsert key before|after item newitem 在指定元素的前面或者后面添加新元素
    • lrem key count value 删除指定个数值为value的元素
      count = 0 :删除所有值为value的元素
      count > 0 :从左到右删除 count 个值为 value 的元素
      count < 0 :从右到做删除 |count| 个值为 value 的元素
    • ltrim key start end 保留指定范围的元素
    • lset key index newValue 更新某个索引的值
    127.0.0.1:6379> lrange userlist 0 -1
    1) "ccc"
    2) "bbb"
    3) "a111"
    4) "aaa"
    127.0.0.1:6379> linsert userlist after aaa a222
    (integer) 5
    127.0.0.1:6379> lrange userlist 0 -1
    1) "ccc"
    2) "bbb"
    3) "a111"
    4) "aaa"
    5) "a222"
    127.0.0.1:6379> lrem userlist 0 aaa
    (integer) 1
    127.0.0.1:6379> lrange userlist 0 -1
    1) "ccc"
    2) "bbb"
    3) "a111"
    4) "a222"
    127.0.0.1:6379> lpush userlist bbb
    (integer) 5
    127.0.0.1:6379> lrange userlist 0 -1
    1) "bbb"
    2) "ccc"
    3) "bbb"
    4) "a111"
    5) "a222"
    127.0.0.1:6379> lrem userlist 2 bbb
    (integer) 2
    127.0.0.1:6379> lrange userlist 0 -1
    1) "ccc"
    2) "a111"
    3) "a222"
    127.0.0.1:6379> ltrim userlist 0 1
    OK
    127.0.0.1:6379> lrange userlist 0 -1
    1) "ccc"
    2) "a111"
    127.0.0.1:6379> lset userlist 0 c11
    OK
    127.0.0.1:6379> lrange userlist 0 -1
    1) "c11"
    2) "a111"
    
    • blpop key timeout 没有则阻塞(timeout指定阻塞时间 为0代表永久)
    • brpop key timeout 没有则阻塞(timeout指定阻塞时间 为0代表永久) 这两个可以用来实现消费者生产者

    image.png

    总结来说我们可以使用 左入右出或者右入左出 来实现队列,左入左出或者右入右出 来实现栈。

    • lpush + lpop = Stack
    • rpush + rpop = Stack**
    • lpush + rpop = Queue
    • rpush + lpop = Queue
    • lpush/rpush + ltrim = Capped List (定长列表)
    • lpush + brpop = Message Queue (消息队列)
    • rpush + blpop = Message Queue (消息队列)

    6. Set

    Redis 中的 set 相当于 Java 中的 HashSet(无序集合),其中里面的元素不可以重复,我们可以利用它实现一些去重的功能。我们还有对几个集合进行取交集,取并集等操作,这些操作就可以获取不同用户之间的共同好友,共同爱好等等。

    • sadd key value 添加元素
    • sdel key value 删除某个元素
    • sismember key value 判断是否是集合中的元素
    • srandmember key count 随机获取指定个数的元素(不会影响集合结构)
    • spop key count 从集合中随机弹出元素(会破坏结合结构)
    • smembers key 获取集合所有元素 O(n)复杂度
    • scard key 获取集合个数
    127.0.0.1:6379> sadd userset aaa
    (integer) 1
    127.0.0.1:6379> sadd userset aaa
    (integer) 0
    127.0.0.1:6379> sadd userset nnn
    (integer) 1
    127.0.0.1:6379> sismember userset bbb
    (integer) 0
    127.0.0.1:6379> srandmember userset 1
    1) "nnn"
    127.0.0.1:6379> spop userset 1
    1) "nnn"
    127.0.0.1:6379> smembers userset
    1) "aaa"
    127.0.0.1:6379> scard userset
    (integer) 1
    
    • sinter set1 set2 ... 获取所有集合中的交集
    • sdiff set1 set2 ... 获取所有集合中的差集
    • sunion set1 set2 ... 获取所有集合中的并集
    127.0.0.1:6379> sadd set1 aa bb cc dd
    (integer) 4
    127.0.0.1:6379> sadd set2 11 22 33 44
    (integer) 4
    127.0.0.1:6379> sinter set1 set2
    (empty list or set)
    127.0.0.1:6379> sadd set1 aa11
    (integer) 1
    127.0.0.1:6379> sadd set2 aa11
    (integer) 1
    127.0.0.1:6379> sinter set1 set2
    1) "aa11"
    127.0.0.1:6379> sdiff set1 set2
    1) "dd"
    2) "bb"
    3) "cc"
    4) "aa"
    127.0.0.1:6379> sunion set1 set2
    1) "cc"
    2) "bb"
    3) "aa"
    4) "33"
    5) "22"
    6) "11"
    7) "aa11"
    8) "44"
    9) "dd"
    

    7. Sorted Set

    Redis 中的 zset 是一个 有序集合,通过它可以实现很多有意思的功能,比如学生成绩排行榜,视频播放量排行榜等等。

    image.png

    • zadd key score element 添加,score用于排序,value需要唯一,由于使用的跳表,时间复杂度为 O(logn)。
    • zrem key element 删除某元素 O(1)时间复杂度
    • zscore key element 获取某个元素的分数
    • zincrby key incrScore element 增加某个元素的分数
    • zrange key start end [withscores] 获取指定索引范围的元素 加上withscores则返回分数 O(logn + m)时间复杂度
    • zrangebyscore key minScore maxScore [withscores] 获取指定分数范围的元素 加上withscores则返回分数,O(logn + m)时间复杂度
    • zcard key 获取有序集合长度
    127.0.0.1:6379> zadd mathtest 88 aa
    (integer) 1
    127.0.0.1:6379> zadd mathtest 77 bb
    (integer) 1
    127.0.0.1:6379> zadd mathtest 66 cc
    (integer) 1
    127.0.0.1:6379> zrange mathtest 0 -1
    1) "cc"
    2) "bb"
    3) "aa"
    127.0.0.1:6379> zrem mathtest bb
    (integer) 1
    127.0.0.1:6379> zscore mathtest aa
    "88"
    127.0.0.1:6379> zincrby mathtest 11 aa
    "99"
    127.0.0.1:6379> zrange mathtest 0 -1
    1) "cc"
    2) "aa"
    127.0.0.1:6379> zincrby mathtest 44 bb
    "44"
    127.0.0.1:6379> zrange mathtest 0 -1
    1) "bb"
    2) "cc"
    3) "aa"
    127.0.0.1:6379> zrange mathtest 0 -1 withscores
    1) "bb"
    2) "44"
    3) "cc"
    4) "66"
    5) "aa"
    6) "99"
    127.0.0.1:6379> zcard mathtest
    (integer) 3
    

    8. 排序

    • sort mylist 排序
    • sort mylist alpha desc limit 0 2 字母排序
    • sort list by it:* desc by命令
    • sort list by it:* desc get it:* get参数
    • sort list by it:* desc get it:* store sorc:result sort命令之store参数:表示把sort查询的结果集保存起来
    127.0.0.1:6379> lpush today-cost 30
    (integer) 1
    127.0.0.1:6379> lpush today-cost 1.5
    (integer) 2
    127.0.0.1:6379> lpush today-cost 10
    (integer) 3
    127.0.0.1:6379> lpush today-cost 8
    (integer) 4
    127.0.0.1:6379> sort today-cost
    1) "1.5"
    2) "8"
    3) "10"
    4) "30"
    127.0.0.1:6379> lpush website "www.aaa.com"
    (integer) 1
    127.0.0.1:6379> lpush website "www.bbb.com"
    (integer) 2
    127.0.0.1:6379> lpush website "www.ccc.com"
    (integer) 3
    127.0.0.1:6379> lpush website "www.123.com"
    (integer) 4
    127.0.0.1:6379> sort website alpha
    1) "www.123.com"
    2) "www.aaa.com"
    3) "www.bbb.com"
    4) "www.ccc.com"
    127.0.0.1:6379> sort website alpha desc limit 0 3
    1) "www.ccc.com"
    2) "www.bbb.com"
    3) "www.aaa.com"
    127.0.0.1:6379> 
    

    更多案例

    六、数据备份与恢复

    1. Redis 持久化

    Redis 提供了不同级别的持久化方式:

    • RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
    • AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
    • 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
    • 你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

    aof备份处理

    • config set appendonly yes 开启持久化
    • appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
    • appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘(推荐)
    • appendfsync no #让操作系统决定何时进行同步

    命令:

    • bgsave异步保存数据到磁盘(快照保存)
    • lastsave返回上次成功保存到磁盘的unix的时间戳
    • shutdown同步保存到服务器并关闭redis服务器
    • bgrewriteaof文件压缩处理(命令)
    // 备份
    127.0.0.1:6379> save // RDB 阻塞方式备份
    18951:M 10 Dec 2019 15:10:22.409 * DB saved on disk
    OK
    
    127.0.0.1:6379> bgsave // RDB 后台备份
    18951:M 10 Dec 2019 15:10:32.446 * Background saving started by pid 2026
    Background saving started
    127.0.0.1:6379> 2026:C 10 Dec 2019 15:10:32.449 * DB saved on disk 
    18951:M 10 Dec 2019 15:10:32.456 * Background saving terminated with success
    
    // 恢复
    // 如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可
    127.0.0.1:6379> CONFIG GET dir
    1) "dir"
    2) "/Users/gerunfeng/redis-5.0.5"
    

    2. RDB和AOF持久化对比

    RDB原理

    fork和cow。fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

    RDB的优点

    • RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
    • RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.
    • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
    • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.

    RDB的缺点

    • 如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.
    • RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.

    AOF 优点

    • 使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.
    • AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.
    • Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
    • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

    AOF 缺点

    • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
    • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

    如何选择使用哪种持久化方式?

    • 一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。

    • 如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。

    • 有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快, 除此之外, 使用 RDB 还可以避免之前提到的 AOF 程序的 bug 。

    参考-官网

    七、安全(密码设置)

    我们可以通过 redis 的配置文件设置密码参数,这样客户端连接到 redis 服务就需要密码验证,这样可以让你的 redis 服务更安全

    127.0.0.1:6379> config get requirepass
    1) "requirepass"
    2) ""
    
    127.0.0.1:6379> config set requirepass 123
    OK
    127.0.0.1:6379> keys *
    (error) NOAUTH Authentication required.
    127.0.0.1:6379> auth 123
    OK
    127.0.0.1:6379> keys *
     1) "mathtest"
     2) "user1"
     3) "user5"
    

    九、客户端连接

    //client kill 关闭客户端连接
    //client list 列出所有的客户端

    //给客户端设置一个名称
    client setname myclient1
    client getname

    config get port
    //configRewrite 对redis的配置文件进行改写

    十、面试问题

    1. 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?

    使用keys指令可以扫出指定模式的key列表。

    如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

    Redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长

    2. 缓存穿透

    什么是缓存穿透?

    缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。下面用图片展示一下(这两张图片不是我画的,为了省事直接在网上找的,这里说明一下):

    正常缓存处理流程:

    image.png

    缓存穿透情况处理流程:

    image.png

    有哪些解决办法?

    最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。

    1)缓存无效 key : 如果缓存和数据库都查不到某个 key 的数据就写一个到 redis 中去并设置过期时间,具体命令如下:SET key value EX 10086。这种方式可以解决请求的 key 变化不频繁的情况,如何黑客恶意攻击,每次构建的不同的请求key,会导致 redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。

    另外,这里多说一嘴,一般情况下我们是这样设计 key 的: 表名:列名:主键名:主键值。

  • 相关阅读:
    String的本质是一个char*,只是以类的形式提供,使用起来比较方便
    感觉技术停滞时,该怎么办?(离开舒适区,刻意练习)
    网盘和云存储的区别(面向用户,产品形态,文件组织形式,功能侧重,一共4点区别)
    压力负载测试工具
    模型配置方式实现爬虫
    jquery Deferred
    Server是如何完成针对请求的监听、接收与响应1
    asp.net core 使用 Redis 和 Protobuf
    带进度条的文件上传
    ASP.NET Core Authorization
  • 原文地址:https://www.cnblogs.com/gerf/p/14040836.html
Copyright © 2020-2023  润新知