Reids介绍

Redis是由C语言开发的,开源的键值对(key-value)数据库。支持存储的value类型包括string(字符串)、list(链表)、set(集合)、zset(sorted set有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove、交集并集和差集等操作,这些操作都是原子性的——不可分割。
与memcached一样,为了保证效率,数据缓存在内存中。区别是Redis会周期性地把数据写入磁盘或把操作追加到记录文件中,以实现master-slave(主从)同步。

特点

  1. 速度快,虽然Redis是单线程架构,但是数据运行在内存中,所以运行速度非常快。
  2. 支持丰富的数据类型,五种基本数据类型:string、list、set、zset、hash
  3. 支持事务
  4. 丰富的功能特性

Redis单线程架构

  • redis是单线程架构,命令从客户端发到服务端不是立即执行的,所有命令都是进入一个队列中,然后被执行。另外redis采用了I/O多路复用技术解决I/O问题。
  • 为什么单线程架构的Redis速度可以很快。
    • 第一,纯内存访问,Redis将所有数据放在内存中,内存的响应大约是100纳秒,这是redis达到每秒万级别的访问的重要基础。
    • 第二,非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,加上redis自身的实现处理模型将epoll的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。
    • 第三,单线程避免了线程切换和竞态产生的消耗
  • 客户端和服务端请求过程

![~OUDQK(%27_J7T9Q7C%757.png

  • 所有命令在一个队列里等待被执行

4VINP$`ND105_W1}PC1NHY1.png

安装部署

软件安装

  1. [root@localhost ~]# yum install epel-release.noarch -y
  2. [root@localhost ~]# yum install redis -y

相关文件

  1. [root@localhost ~]# rpm -ql redis
  2. /etc/redis.conf # 主配置文件
  3. /usr/bin/redis-benchmark # redis性能检测工具
  4. /usr/bin/redis-check-aof # AOF文件修复工具
  5. /usr/bin/redis-check-rdb # RDB文件检查工具
  6. /usr/bin/redis-cli # redis客户端
  7. /usr/bin/redis-server # redis服务端
  8. /usr/lib/systemd/system/redis.service # 守护进程
  9. /var/lib/redis # redis数据目录
  10. /var/log/redis # redis日志文件

配置文件

  1. [root@server1 ~]# cat /etc/redis.conf | grep -Ev '^#|^$'
  2. ===========================基础配置=======================
  3. bind 127.0.0.1 # 绑定IP地址
  4. protected-mode yes
  5. port 6379 # 绑定端口
  6. tcp-backlog 511
  7. timeout 0 # 客户端连接超时时间
  8. tcp-keepalive 300 # 检测客户端是否健康的周期时间
  9. daemonize no # 是否以守护进程的形式启动
  10. supervised no
  11. pidfile /var/run/redis_6379.pid # pid文件
  12. loglevel notice # 日志等级
  13. logfile /var/log/redis/redis.log # 日志文件
  14. databases 16 # 数据库数目
  15. ==========================RDB============================
  16. save 900 1 # 900秒内至少一个key被修改则发器快照
  17. save 300 10
  18. save 60 10000
  19. stop-writes-on-bgsave-error yes # 当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据
  20. rdbcompression yes # 对于存储到磁盘中的快照,可以设置是否进行压缩存储。
  21. rdbchecksum yes # 存储快照后,可以让redis通过CRC64算法进行校验
  22. dbfilename dump.rdb # 快照文件名
  23. dir /var/lib/redis #快照存放路径
  24. =========================主从复制=============================
  25. slave-serve-stale-data yes
  26. slave-read-only yes # 配置slave实例是否接收写操作
  27. repl-diskless-sync no # 主从数据复制是否启用无硬盘复制功能
  28. repl-diskless-sync-delay 5 # 等待时间
  29. repl-disable-tcp-nodelay no # 同步后是否禁用从站上的TCP_NODELAY
  30. slave-priority 100
  31. ===========================AOF=====================================
  32. appendonly no # redis默认使用的是RDB方式持久化
  33. appendfilename "appendonly.aof" # 文件名
  34. appendfsync everysec # AOF持久化策略的配置
  35. no-appendfsync-on-rewrite no # 在AOF重写或写入RDB文件的时候不执行持久化策略
  36. auto-aof-rewrite-percentage 100 # 当日的AOF文件大小超过上一次重写AOF文件大小的百分之多少进行重写
  37. auto-aof-rewrite-min-size 64mb # 设置允许重写的的最小AOF文件大小
  38. aof-load-truncated yes # 当截断的AOF文件被导入时,会自动发布一个log给客户端load
  39. lua-time-limit 5000 # 一个lua脚本执行的最大时间
  40. slowlog-log-slower-than 10000
  41. slowlog-max-len 128
  42. latency-monitor-threshold 0
  43. notify-keyspace-events ""
  44. hash-max-ziplist-entries 512
  45. hash-max-ziplist-value 64
  46. list-max-ziplist-size -2
  47. list-compress-depth 0
  48. set-max-intset-entries 512
  49. zset-max-ziplist-entries 128
  50. zset-max-ziplist-value 64
  51. hll-sparse-max-bytes 3000
  52. activerehashing yes
  53. client-output-buffer-limit normal 0 0 0
  54. client-output-buffer-limit slave 256mb 64mb 60
  55. client-output-buffer-limit pubsub 32mb 8mb 60
  56. hz 10
  57. aof-rewrite-incremental-fsync yes

Redis的启动与关闭

  • 启动命令是redis-server,参数是配置文件,可以指定配置文件,也可以默认加载。

    1. [root@localhost ~]# redis-server /etc/redis.conf
  • 登录使用redis-cli工具进行登录

    1. [root@localhost ~]# redis-cli --help
    2. redis-cli 3.2.12
    3. Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]
    4. -h <hostname> Server hostname (default: 127.0.0.1).
    5. -p <port> Server port (default: 6379).
    6. -s <socket> Server socket (overrides hostname and port).
    7. -a <password> Password to use when connecting to the server.
    8. -r <repeat> Execute specified command N times.
    9. -i <interval> When -r is used, waits <interval> seconds per command.
    10. It is possible to specify sub-second times like -i 0.1.
    11. -n <db> Database number.
    12. -x Read last argument from STDIN.
    13. -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \n).
    14. -c Enable cluster mode (follow -ASK and -MOVED redirections).
    15. [root@localhost ~]# redis-cli -h 127.0.0.1 -p 6379
    16. 127.0.0.1:6379>
  • 关闭redis服务

    1. 127.0.0.1:6379> SHUTDOWN
    2. not connected>

    Redis基础命令

    命令手册: http://redisdoc.com/index.html

    全局命令

  • KEYS *:查看所有键,该命令在生产环境中要慎用,会一次性读取所有键,可能会导致阻塞。

    1. 127.0.0.1:6379> KEYS *
    2. 1) "k3"
    3. 2) "k1"
    4. 3) "k4"
    5. 4) "k2"
  • dbsize:查看当前数据库中所有键的数量

    1. 127.0.0.1:6379> DBSIZE
    2. (integer) 4
  • exists:检查某个键是否存在,存在返回1,不存在返回0

    1. 127.0.0.1:6379> EXISTS k1
    2. (integer) 1
    3. 127.0.0.1:6379> EXISTS k5
    4. (integer) 0
  • del key:删除键,可以同时删除多个键,删除成功后会返回成功删除键的个数,删除失败会返回0。

    1. 127.0.0.1:6379> DEL k1 k4
    2. (integer) 2
    3. 127.0.0.1:6379> keys *
    4. 1) "k3"
    5. 2) "k2"
  • expire key seconds:给键设置过期时间,当超过过期时间后会自动删除键

  • ttl命令会返回键的过期时间

    • 大于等于0的整数,是键的过期时间
    • -1:键没有设置过期时间
    • -2:键不存在
      1. 127.0.0.1:6379> EXPIRE k3 20
      2. (integer) 1
      3. 127.0.0.1:6379> TTL k3
      4. (integer) 13
      5. 127.0.0.1:6379> TTL k3
      6. (integer) -2
      7. 127.0.0.1:6379> TTL k2
      8. (integer) -1
      9. 127.0.0.1:6379> get k2
      10. "v2"
      11. 127.0.0.1:6379> get k3
      12. (nil)
  • type key:查看键的类型,数据结构类型有五大基本数据类型(string、list、set、zset、hash)

    1. 127.0.0.1:6379> TYPE k2
    2. string

    字符串命令

    字符串类型是redis最基础的数据结构,而且其他集中数据结构都是在字符串类型基础上构建的。

  • 设置值

    1. 127.0.0.1:6379>
    2. 127.0.0.1:6379> SET key value [EX seconds] [PX milliseconds] [NX|XX]
    3. ex seconds:为键设置秒级过期时间
    4. px milliseconds:为键设置毫秒级过期时间
    5. nx:键必须不存在,才可以设置成功,用于添加
    6. xx:与nx相反,键必须存在,才可以设置成功,用于更新
    7. (setex key seconds value setnx key value它们的作用和exnx选项是一样的)
    8. 当前键hello不存在:
    9. 127.0.0.1:6379> exists hello
    10. (integer) 0
    11. 设置键为hello,值为world的键值对:
    12. 127.0.0.1:6379> set hello world
    13. OK
    14. 因为键hello已存在,所以setnx失败,返回结果为0
    15. 127.0.0.1:6379> setnx hello redis
    16. (integer) 0
    17. 因为键hello已存在,所以set xx成功,返回结果为OK
    18. 127.0.0.1:6379> set hello jedis xx
    19. OK
  • 获取值

    1. 下面操作获取键hello的值:
    2. 127.0.0.1:6379> get hello
    3. "world"
    4. 如果要获取的键不存在,则返回nil(空):
    5. 127.0.0.1:6379> get not_exist_key
    6. (nil)
  • 批量设置值

    1. 127.0.0.1:6379> mset a 1 b 2 c 3 d 4
    2. OK
  • 批量获取值

    1. 127.0.0.1:6379> mget a b c d
    2. 1) "1"
    3. 2) "2"
    4. 3) "3"
    5. 4) "4"
  • 计数

    1. incr key
    2. incr命令用于对值做自增操作,返回结果分为三种情况:
    3. ·值不是整数,返回错误。
    4. ·值是整数,返回自增后的结果。
    5. ·键不存在,按照值为0自增,返回结果为1
    6. 例如对一个不存在的键执行incr操作后,返回结果是1
    7. 127.0.0.1:6379> exists key
    8. (integer) 0
    9. 127.0.0.1:6379> incr key
    10. (integer) 1
    11. 再次对键执行incr命令,返回结果是2
    12. 127.0.0.1:6379> incr key
    13. (integer) 2
    14. 如果值不是整数,那么会返回错误:
    15. 127.0.0.1:6379> set hello world
    16. OK
    17. 127.0.0.1:6379> incr hello
    18. (error) ERR value is not an integer or out of range
  • 不常用命令(扩展)

    1. 1)追加值
    2. append key value
    3. append可以向字符串尾部追加值,例如:
    4. 127.0.0.1:6379> get key
    5. "redis"
    6. 127.0.0.1:6379> append key world
    7. (integer) 10
    8. 127.0.0.1:6379> get key
    9. "redisworld"
    10. 2)字符串长度
    11. strlen key
    12. 例如,当前值为redisworld,所以返回值为10
    13. 127.0.0.1:6379> get key
    14. "redisworld"
    15. 127.0.0.1:6379> strlen key
    16. (integer) 10
    17. 下面操作返回结果为6,因为每个中文占用3个字节:
    18. 127.0.0.1:6379> set hello "世界"
    19. OK
    20. 127.0.0.1:6379> strlen hello
    21. (integer) 6
    22. 3)设置并返回原值
    23. getset key value
    24. getsetset一样会设置值,但是不同的是,它同时会返回键原来的值,
    25. 例如:
    26. 127.0.0.1:6379> getset hello world
    27. (nil)
    28. 127.0.0.1:6379> getset hello redis
    29. "world"
    30. 4)设置指定位置的字符
    31. setrange key offeset value
    32. 下面操作将值由pest变为了best
    33. 127.0.0.1:6379> set redis pest
    34. OK
    35. 127.0.0.1:6379> setrange redis 0 b
    36. (integer) 4
    37. 127.0.0.1:6379> get redis
    38. "best"
    39. 5)获取部分字符串
    40. getrange key start end
    41. startend分别是开始和结束的偏移量,偏移量从0开始计算,例如下面操作获取了值best的前两个字符。
    42. 127.0.0.1:6379> getrange redis 0 1
    43. "be"

    列表命令

  • 列表(list)类型用来存储多个有序的字符串,是一种线性结构,可以充当栈和队列的角色

  • 添加操作 ```bash (1)从右边插入元素 rpush key value [value …] 下面代码从右向左插入元素c、b、a: 127.0.0. 1:6379> rpush listkey c b a (integer) 3 lrange0-1命令可以从左到右获取列表的所有元素: 127.0.0.1:6379> lrange listkey 0 -1 1) “c” 2) “b” 3) “a” 108

(2)从左边插入元素 lpush key value [value …] 使用方法和rpush相同,只不过从左侧插入,这里不再赘述。

(3)向某个元素前或者后插入元素 linsert key before|after pivot value linsert命令会从列表中找到等于pivot的元素,在其前(before)或者后 (after)插入一个新的元素value,例如下面操作会在列表的元素b前插入java 127.0.0.1:6379> linsert listkey before b java (integer) 4 返回结果为4,代表当前命令的长度,当前列表变为: 127.0.0.1:6379> lrange listkey 0 -1 1) “c” 2) “java” 3) “b” 4) “a”

  1. - 查找
  2. ```bash
  3. (1)获取指定范围内的元素列表
  4. lrange key start end
  5. lrange操作会获取列表指定索引范围所有的元素。索引下标有两个特点:第一,索引下标从左到右分别是0到N-1,但是从右到左分别是-1到-N。
  6. 第二,lrange中的end选项包含了自身,这个和很多编程语言不包含end不太相同,例如想获取列表的第2到第4个元素,可以执行如下操作:
  7. 127.0.0.1:6379> lrange listkey 1 3
  8. 1) "java"
  9. 2) "b"
  10. 3) "a"
  11. (2)获取列表指定索引下标的元素
  12. lindex key index
  13. 例如当前列表最后一个元素为a:
  14. 127.0.0.1:6379> lindex listkey -1
  15. "a"
  16. (3)获取列表长度
  17. llen key
  18. 例如,下面示例当前列表长度为4:
  19. 127.0.0.1:6379> llen listkey
  20. (integer) 4
  • 删除 ```bash (1)从列表左侧弹出元素 lpop key 如下操作将列表最左侧的元素c会被弹出,弹出后列表变为java、b、a: 127.0.0.1:6379>t lpop listkey “c” 127.0.0.1:6379> lrange listkey 0 -1 1) “java” 2) “b” 3) “a”

(2)从列表右侧弹出 rpop key 它的使用方法和lpop是一样的,只不过从列表右侧弹出,这里不再赘 述。

(3)删除指定元素 lrem key count value lrem命令会从列表中找到等于value的元素进行删除,根据count的不同 分为三种情况: ·count>0,从左到右,删除最多count个元素。 ·count<0,从右到左,删除最多count绝对值个元素。 ·count=0,删除所有。 例如向列表从左向右插入5个a,那么当前列表变为“a a a a a java b a”, 下面操作将从列表左边开始删除4个为a的元素: 127.0.0.1:6379> lrem listkey 4 a (integer) 4 127.0.0.1:6379> lrange listkey 0 -1 1) “a” 2) “java” 3) “b” 4) “a”

(4)按照索引范围修剪列表 ltrim key start end 例如,下面操作会只保留列表listkey第2个到第4个元素: 127.0.0.1:6379> ltrim listkey 1 3 OK 127.0.0.1:6379> lrange listkey 0 -1 1) “java” 2) “b” 3) “a”

  1. - 修改
  2. ```bash
  3. 修改指定索引下标的元素:
  4. lset key index newValue
  5. 下面操作会将列表listkey中的第3个元素设置为python:
  6. 127.0.0.1:6379> lset listkey 2 python
  7. OK
  8. 127.0.0.1:6379> lrange listkey 0 -1
  9. 1) "java"
  10. 2) "b"
  11. 3) "python"

哈希命令

几乎所有编程语言都提供了hash类型,他们的叫法可能是哈希、字典、关联数组。常常会使用hash数据类型存储关系型数据库中的表。

  • 设置值

    1. hset key field value
    2. 127.0.0.1:6379> hset user:1 name tom
    3. (integer) 1
  • 获取值

    1. hget key field
    2. 127.0.0.1:6379> hget user:1 name
    3. "tom"
  • 删除field

    1. hdel key field [field ...]
    2. hdel会删除一个或多个field,返回结果为成功删除field的个数
    3. 127.0.0.1:6379> hdel user:1 name
    4. (integer) 1
    5. 127.0.0.1:6379> hdel user:1 age
    6. (integer) 0
  • 计算field个数

    1. hlen key
    2. 127.0.0.1:6379> hset user:1 name tom
    3. (integer) 1
    4. 127.0.0.1:6379> hset user:1 age 23
    5. (integer) 1
    6. 127.0.0.1:6379> hset user:1 city tianjin
    7. (integer) 1
    8. 127.0.0.1:6379> hlen user:1
    9. (integer) 3
  • 批量设置或获取field-value

    1. hmget key field [field ...]
    2. hmset key field value [field value ...]
    3. hmsethmget分别是批量设置和获取field-valuehmset需要的参数是key
    4. 和多对field-valuehmget需要的参数是key和多个field
    5. 127.0.0.1:6379> hmset user:1 name mike age 12 city tianjin
    6. OK
    7. 127.0.0.1:6379> hmget user:1 name city
    8. 1) "mike"
    9. 2) "tianjin"
  • 获取所有field

    1. hkeys key
    2. hkeys命令应该叫hfields更为恰当,它返回指定哈希键所有的field
    3. 127.0.0.1:6379> hkeys user:1
    4. 1) "name"
    5. 2) "age"
    6. 3) "city"
  • 获取所有value

    1. hvals key
    2. 下面操作获取user1全部value
    3. 127.0.0.1:6379> hvals user:1
    4. 1) "mike"
    5. 2) "12"
    6. 3) "tianjin"
  • 获取所有的field-value

    1. hgetall key
    2. 下面操作获取user1所有的field-value
    3. 127.0.0.1:6379> hgetall user:1
    4. 1) "name"
    5. 2) "mike"
    6. 3) "age"
    7. 4) "12"
    8. 5) "city"
    9. 6) "tianjin"

    集合命令

    集合(set)类型也是用来保存多个的字符串元素,集合中不允许有重复出现的元素,集合的元素是没有顺序的不能够通过下标来获得值

  • 添加元素

    1. sadd key element [element ...]
    2. 返回结果为添加成功的元素个数
    3. 127.0.0.1:6379> exists myset
    4. (integer) 0
    5. 127.0.0.1:6379> sadd myset a b c
    6. (integer) 3
    7. 127.0.0.1:6379> sadd myset a b
    8. (integer) 0
  • 删除元素

    1. srem key element [element ...]
    2. 返回结果为成功删除元素个数
    3. 127.0.0.1:6379> srem myset a b
    4. (integer) 2
    5. 127.0.0.1:6379> srem myset hello
    6. (integer) 0
  • 计算元素个数

    1. scard key
    2. scard的时间复杂度为O1),它不会遍历集合所有元素,而是直接用Redis内部的变量
    3. 127.0.0.1:6379> scard myset
    4. (integer) 1
  • 判断元素是否在集合中

    1. sismember key element
    2. 如果给定元素element在集合内返回1,反之返回0
    3. 127.0.0.1:6379> sismember myset c
    4. (integer) 1
  • 随机从指定集合中返回指定个数的元素

    1. srandmember key [count]
    2. [count]是可选参数,如果不写默认为1
    3. 127.0.0.1:6379> srandmember myset 2
    4. 1) "a"
    5. 2) "c"
    6. 127.0.0.1:6379> srandmember myset
    7. "d"
  • 集合随机弹出元素

    1. spop key
    2. spop操作可以从集合中随机弹出一个元素,例如下面代码是一次spop后,集合元素变为"d b a"
    3. 127.0.0.1:6379> spop myset
    4. "c"
    5. 127.0.0.1:6379> smembers myset
    6. 1) "d"
    7. 2) "b"
    8. 3) "a"
  • 获取所有元素

    1. smembers key
    2. 下面代码获取集合myset所有元素,并且返回结果是无序的:
    3. 127.0.0.1:6379> smembers myset
    4. 1) "d"
    5. 2) "b"
    6. 3) "a"
  • 交集运算

    1. 127.0.0.1:6379> sadd user:1:follow it music his sports
    2. (integer) 4
    3. 127.0.0.1:6379> sadd user:2:follow it news ent sports
    4. (integer) 4
    5. sinter key [key ...]
    6. 例如下面代码是求user1followuser2follow两个集合的交集,返回结果是sportsit
    7. 127.0.0.1:6379> sinter user:1:follow user:2:follow
    8. 1) "sports"
    9. 2) "it"
  • 并集运算

    1. suinon key [key ...]
    2. 例如下面代码是求user1followuser2follow两个集合的并集,返回结果是sportsit
    3. hisnewsmusicent
    4. 127.0.0.1:6379> sunion user:1:follow user:2:follow
    5. 1) "sports"
    6. 2) "it"
    7. 3) "his"
    8. 4) "news"
    9. 5) "music"
    10. 6) "ent"
  • 差集运算

    1. sdiff key [key ...]
    2. 例如下面代码是求user1followuser2follow两个集合的差集,返回结果是musichis
    3. 127.0.0.1:6379> sdiff user:1:follow user:2:follow
    4. 1) "music"
    5. 2) "his"
  • 将交集 并集 和 差集的结果保存

    1. sinterstore destination key [key ...]
    2. suionstore destination key [key ...]
    3. sdiffstore destination key [key ...]
    4. 集合间的运算在元素较多的情况下会比较耗时,所以Redis提供了上面三个命令(原命令+store)将集合间交集、并集、差集的结果保存在destination key中,例如下面操作将user1followuser2follow两个集合的交集结果保存在user1_2inter中,user1_2inter本身也是集合类型:
    5. 127.0.0.1:6379> sinterstore user:1_2:inter user:1:follow user:2:follow
    6. (integer) 2
    7. 127.0.0.1:6379> type user:1_2:inter
    8. set
    9. 127.0.0.1:6379> smembers user:1_2:inter
    10. 1) "it"
    11. 2) "sports"

    有序集合命令

    有序集合保留了集合不能有重复成员的特性,不同的是有序集合中的元素是可以排序的。但是它和列表使用索引下标作为排序依据不同的是它给每个元素设置一个分数(score)作为排序的依据

  • 添加成员 ```bash zadd key score member [score member …] 下面操作向有序集合user:ranking添加用户tom和他的分数251: 127.0.0.1:6379> zadd user:ranking 251 tom (integer) 1 127.0.0.1:6379> zadd user:ranking 1 kris 91 mike 200 frank 220 tim 250 martin (integer) 5

有关zadd命令有两点需要注意: Redis3.2为zadd命令添加了nx、xx、ch、incr四个选项: nx:member必须不存在,才可以设置成功,用于添加。 xx:member必须存在,才可以设置成功,用于更新。 ch:返回此次操作后,有序集合元素和分数发生变化的个数 incr:对score做增加,相当于后面介绍的zincrby。

  1. - 计算成员个数
  2. ```bash
  3. zcard key
  4. 例如下面操作返回有序集合user:ranking的成员数为5,和集合类型的
  5. scard命令一样,zcard的时间复杂度为O(1)。
  6. 127.0.0.1:6379> zcard user:ranking
  7. (integer) 5
  • 计算某个成员的分数

    1. zscore key member
    2. tom的分数为251,如果成员不存在则返回nil
    3. 127.0.0.1:6379> zscore user:ranking tom
    4. "251"
    5. 127.0.0.1:6379> zscore user:ranking test
    6. (nil)
  • 计算某个成员的排名

    1. zrank key member
    2. zrevrank key member
    3. zrank是从分数从低到高返回排名,zrevrank反之。例如下面操作中,tom
    4. zrankzrevrank分别排名第5和第0(排名从0开始计算)。
    5. 127.0.0.1:6379> zrank user:ranking tom
    6. (integer) 5
    7. 127.0.0.1:6379> zrevrank user:ranking tom
    8. (integer) 0
  • 删除成员

    1. zrem key member [member ...]
    2. 下面操作将成员mike从有序集合userranking中删除。
    3. 127.0.0.1:6379> zrem user:ranking mike
    4. (integer) 1
    5. 返回结果为成功删除的个数。
  • 增加成员分数

    1. zincrby key increment member
    2. 下面操作给tom增加了9分,分数变为了260分:
    3. 127.0.0.1:6379> zincrby user:ranking 9 tom
    4. "260"
  • 返回指定排名范围的成员

    1. zrange key start end [withscores]
    2. zrevrange key start end [withscores]
    3. 有序集合是按照分值排名的,zrange是从低到高返回,zrevrange反之。
    4. 下面代码返回排名最低的是三个成员,如果加上withscores选项,同时会返回成员的分数:
    5. 127.0.0.1:6379> zrange user:ranking 0 2 withscores
    6. 1) "kris"
    7. 2) "1"
    8. 3) "frank"
    9. 4) "200"
    10. 5) "tim"
    11. 6) "220"
    12. 127.0.0.1:6379> zrevrange user:ranking 0 2 withscores
    13. 1) "tom"
    14. 2) "260"
    15. 3) "martin"
    16. 4) "250"
    17. 5) "tim"
    18. 6) "220"
  • 返回指定分数范围的成员

    1. zrangebyscore key min max [withscores] [limit offset count]
    2. zrevrangebyscore key max min [withscores] [limit offset count]
    3. 其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。例如下面操作从低到高返回200221分的成员,withscores选项会同时返回每个成员的分数。[limit offset count]选项可以限制输出的起始位置和个数:
    4. 127.0.0.1:6379> zrangebyscore user:ranking 200 tinf withscores
    5. 1) "frank"
    6. 2) "200"
    7. 3) "tim"
    8. 4) "220"
    9. 127.0.0.1:6379> zrevrangebyscore user:ranking 221 200 withscores
    10. 1) "tim"
    11. 2) "220"
    12. 3) "frank"
    13. 4) "200"
    14. 同时minmax还支持开区间(小括号)和闭区间(中括号),-inf和+inf分别代表无限小和无限大:
    15. 127.0.0.1:6379> zrangebyscore user:ranking (200 +inf withscores
    16. 1) "tim"
    17. 2) "220"
    18. 3) "martin"
    19. 4) "250"
    20. 5) "tom"
    21. 6) "260"
  • 返回指定分数范围的成员个数

    1. zcount key min max
    2. 下面操作返回200221分的成员的个数:
    3. 127.0.0.1:6379> zcount user:ranking 200 221
    4. (integer) 2
  • 删除指定排名内的升序元素

    1. zremrangebyrank key start end
    2. 下面操作删除第start到第end名的成员:
    3. 127.0.0.1:6379> zremrangebyrank user:ranking 0 2
    4. (integer) 3
  • 删除指定分数范围的成员

    1. zremrangebyscore key min max
    2. 下面操作将250分以上的成员全部删除,返回结果为成功删除的个数:
    3. 127.0.0.1:6379> zremrangebyscore user:ranking (250 +inf
    4. (integer) 2
  • 交集运算

    1. 127.0.0.1:6379> zadd user:ranking:1 1 kris 91 mike 200 frank 220 tim 250 martin
    2. 251 tom
    3. (integer) 6
    4. 127.0.0.1:6379> zadd user:ranking:2 8 james 77 mike 625 martin 888 tom
    5. (integer) 4
    6. zinterstore destination numkeys key [key ...] [weights weight [weight ...]]
    7. [aggregate sum|min|max]
    8. 这个命令参数较多,下面分别进行说明:
    9. destination:交集计算结果保存到这个键。
    10. numkeys:需要做交集计算键的个数。
    11. key[key...]:需要做交集计算的键。
    12. weights weight[weight...]:每个键的权重,在做交集计算时,每个键中的每个member会将自己分数乘以这个权重,每个键的权重默认是1
    13. aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、min(最小值)、max(最大值)做汇总,默认值是sum
    14. 127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2
    15. (integer) 3
    16. 127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
    17. 1) "mike"
    18. 2) "168"
    19. 3) "martin"
    20. 4) "875"
    21. 5) "tom"
    22. 6) "1139"
    23. 如果想让userranking2的权重变为0.5,并且聚合效果使用max,可以执行如下操作:
    24. 127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1 user:ranking:2 weights 1 0.5 aggregate max
    25. (integer) 3
    26. 127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
    27. 1) "mike"
    28. 2) "91"
    29. 3) "martin"
    30. 4) "312.5"
    31. 5) "tom"
    32. 6) "444"
  • 并集运算

    1. zunionstore destination numkeys key [key ...] [weights weight [weight ...]]
    2. [aggregate sum|min|max]
    3. 该命令的所有参数和zinterstore是一致的,只不过是做并集计算,例如
    4. 下面操作是计算userranking1userranking2的并集,weightsaggregate使用了默认配置,可以看到目标键userranking1_union_2对分值
    5. 做了sum操作:
    6. 127.0.0.1:6379> zunionstore user:ranking:1_union_2 2 user:ranking:1 user:ranking:2
    7. (integer) 7
    8. 127.0.0.1:6379> zrange user:ranking:1_union_2 0 -1 withscores
    9. 1) "kris"
    10. 2) "1"
    11. 3) "james"
    12. 4) "8"
    13. 5) "mike"
    14. 6) "168"
    15. 7) "frank"
    16. 8) "200"
    17. 9) "tim"
    18. 10) "220"
    19. 11) "martin"
    20. 12) "875"
    21. 13) "tom"
    22. 14) "1139"
  • 命令复杂度(了解)

ZU4IRP44H{%V)8RW9SK$L5B.png

Redis丰富特性

redis除了提供五大基本数据类型,还提供了丰富的功能强大的附加功能。

  • 慢查询分析:找到有问题的命令进行优化
  • Redis Shell:功能强大的Shell,具有实用功能
  • Pipeline:通过Pipeline(管道或流水线)机制有效提高客户端性能
  • 事务与Lua:只做自己专属的原子命令
  • Bitmaps:在字符串数据结构上使用位操作,有效节省内存,为开发提供新思路。
  • 发布订阅:基于发布订阅模式的消息通信机制
  • GEO:Redis3.2提供了基于地理位置信息的功能

    慢查询分析

  • config set

Redis Config Set 命令可以动态地调整 Redis 服务器的配置(configuration)而无须重启。你可以使用它修改配置参数,或者改变 Redis 的持久化(Persistence)方式。

  1. # 格式
  2. redis 127.0.0.1:6379> CONFIG Set parameter value
  3. config set slowlog-log-slower-than 20000 # 对执行时间大于多少微秒的查询进行记录
  4. config set slowlog-max-len 1000 # 它决定 slow log 最多能保存多少条日志, slow log 本身是一个 FIFO 队列,当队列大小超过 slowlog-max-len 时,最旧的一条日志将被删除,而最新的一条日志加入到 slow log ,以此类推
  5. config rewrite #将当前配置改写到配置文件中。
  • 获取慢查询日志

    1. slow get [n]
    2. 下面操作返回当前Redis的慢查询,参数n可以指定条数:
    3. 127.0.0.1:6379> slowlog get
    4. 1) 1) (integer) 666 # 标识
    5. 176 # ID
    6. 2) (integer) 1456786500 # 时间戳
    7. 3) (integer) 11615 # 命令耗时
    8. 4) 1) "BGREWRITEAOF" # 执行命令
    9. 2) 1) (integer) 665
    10. 2) (integer) 1456718400
    11. 3) (integer) 12006
    12. 4) 1) "SETEX"
    13. 2) "video_info_200"
    14. 3) "300"
    15. 4) "2"
    16. ...
    17. 可以看到每个慢查询日志有4个属性组成,分别是慢查询日志的标识
    18. id、发生时间戳、命令耗时、执行命令和参数
  • 获取慢查询日志列表当前长度

    1. slowlog len
    2. 例如,当前Redis中有45条慢查询:
    3. 127.0.0.1:6379> slowlog len
    4. (integer) 45
  • 慢查询日志重置

    1. slowlog reset
    2. 实际是对列表做清理操作,例如:
    3. 127.0.0.1:6379> slowlog len
    4. (integer) 45
    5. 127.0.0.1:6379> slowlog reset
    6. OK
    7. 127.0.0.1:6379> slowlog len
    8. (integer) 0

    Redis Shell

    redis-cli详解

  • -r(repeat)选项代表将命令执行多次,例如下面操作将会执行三次ping 命令

    1. [root@server1 ~]# redis-cli -r 3 ping
    2. PONG
    3. PONG
    4. PONG
  • -i(interval)选项代表每隔几秒执行一次命令,但是-i选项必须和-r选 项一起使用,下面的操作会每隔1秒执行一次ping命令,一共执行5次

    1. [root@server1 ~]# redis-cli -r 5 -i 1 ping
    2. PONG
    3. PONG
    4. PONG
    5. PONG
    6. PONG
  • -x选项代表从标准输入(stdin)读取数据作为redis-cli的最后一个参数,例如下面的操作会将字符串world作为set hello的值:

    1. [root@server1 ~]# echo 'world' | redis-cli -x set hello
    2. OK
  • -c(cluster)选项是连接Redis Cluster节点时需要使用的,-c选项可以防止moved和ask异常。

  • 如果Redis配置了密码,可以用-a(auth)选项,有了这个选项就不需要手动输入auth命令。

    redis-server详解

    redis-server- -test-memory可以用来检测当前操作系统能否稳定地分配指定容量的内存给 Redis,通过这种检测可以有效避免因为内存问题造成Redis崩溃,例如下面操作检测当前操作系统能否提供1G的内存给Redis

    1. redis-server --test-memory 1024

    整个内存检测的时间比较长。当输出passed this test时说明内存检测完毕

    redis-benchmark详解

    redis-benchmark可以为Redis做基准性能测试,它提供了很多选项帮助开发和运维人员测试Redis的相关性能

  • c(clients)选项代表客户端的并发数量(默认是50)。

  • n(num)选项代表客户端请求总量(默认是100000)。
  • -q选项仅仅显示redis-benchmark的requests per second信息。

在一个空的Redis上执行了redis-benchmark会发现只有3个键, 如果想向Redis插入更多的键,可以执行使用-r(random)选项,可以向 Redis插入更多随机的键。

Pipeline

  • Redis提供了批量操作命令(例如mget、mset等),有效地节约RTT(Round-Trip Time:往返时延)。但大部分命令是不支持批量操作的,例如要执行n次hgetall命令,并没有 mhgetall命令存在,需要消耗n次RTT。
  • Pipeline(流水线)机制能改善上面这类问题,它能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端

![S28UP5JJ9B1NQ~3BEI`3)K.png

  1. echo -en '*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n*2\r\n$4\r\nincr\r\n$7\r\ncounter\r\n' | redis-cli --pipe

Bitmaps

通过一个bit位来表示某个元素对应的值或者状态,其中的key就是对应元素本身,value对应0或1,我们知道8个bit可以组成一个Byte,所以bitmap本身会极大的节省储存空间。

  • 设置值

    1. setb+it key offset value
    2. 127.0.0.1:6379> setbit unique:users:2016-04-05 0 1
    3. (integer) 0
    4. 127.0.0.1:6379> setbit unique:users:2016-04-05 5 1
    5. (integer) 0
    6. 127.0.0.1:6379> setbit unique:users:2016-04-05 11 1
    7. (integer) 0
    8. 127.0.0.1:6379> setbit unique:users:2016-04-05 15 1
    9. (integer) 0
    10. 127.0.0.1:6379> setbit unique:users:2016-04-05 19 1
    11. (integer) 0

    ![S2~2FFVB}EMNN4}J2~H27E.png

  • 获取值

    1. gitbit key offset
    2. 127.0.0.1:6379> getbit unique:users:2016-04-05 8
    3. (integer) 0
  • 获取Bitmaps指定范围为1的个数

    1. bitcount [start][end]
    2. 下面操作计算2016-04-05这天的独立访问用户数量:
    3. 127.0.0.1:6379> bitcount unique:users:2016-04-05
    4. (integer) 5
    5. [start]和[end]代表起始和结束字节数,下面操作计算用户id在第1个字节到第3个字节之间(第8位到第24位之间)的独立访问用户数,对应的用户id111519
    6. 127.0.0.1:6379> bitcount unique:users:2016-04-05 1 3
    7. (integer) 3
  • Bitmaps间的运算

    1. bitop是一个复合操作,它可以做多个Bitmapsand(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在destkey中。
    2. bitop option destkey key[key....]
    3. 计算出2016-04-042016-04-03两天都访问过网站的用户数
    4. 127.0.0.1:6379> bitop and unique:users:and:2016-04-04_03 unique:users:2016-04-03 unique:users:2016-04-04
    5. (integer) 2
    6. 127.0.0.1:6379> bitcount unique:users:and:2016-04-04_03
    7. (integer) 2

    发布订阅

    Redis提供了基于“发布/订阅”模式的消息机制,此种模式下,消息发布者和订阅者不进行直接通信,发布者客户端向指定的频道(channel)发布消息,订阅该频道的每个客户端都可以收到该消息。
    3_T24UIS9HRYQNDUX2UIEJO.png

  • 发布消息

    1. publish channel message
    2. 下面操作会向channelsports频道发布一条消息“Tim won thechampionship”,返回结果为订阅者个数,因为此时没有订阅,所以返回结果为0
    3. 127.0.0.1:6379> publish channel:sports "Tim won the championship"
    4. (integer) 0
  • 订阅消息

    1. subscribe channel [channel ...]
    2. 订阅者可以订阅一个或多个频道,下面操作为当前客户端订阅了channelsports频道:
    3. 127.0.0.1:6379> subscribe channel:sports
    4. Reading messages... (press Ctrl-C to quit)
    5. 1) "subscribe"
    6. 2) "channel:sports"
    7. 3) (integer) 1
  • 取消订阅

    1. unsubscribe [channel [channel ...]]
    2. 客户端可以通过unsubscribe命令取消对指定频道的订阅,取消成功后,不会再收到该频道的发布消息:
    3. 127.0.0.1:6379> unsubscribe channel:sports
    4. 1) "unsubscribe"
    5. 2) "channel:sports"
    6. 3) (integer) 0
  • 按照模式订阅和取消订阅

    1. psubscribe pattern [pattern...]
    2. punsubscribe [pattern [pattern ...]]
    3. 除了subcribeunsubscribe命令,Redis命令还支持glob风格的订阅命令psubscribe和取消订阅命令punsubscribe,例如下面操作订阅以it开头的所有频道:
    4. 127.0.0.1:6379> psubscribe it*
    5. Reading messages... (press Ctrl-C to quit)
    6. 1) "psubscribe"
    7. 2) "it*"
  • 查询订阅 ```bash (1)查看活跃的频道 pubsub channels [pattern] 所谓活跃的频道是指当前频道至少有一个订阅者,其中[pattern]是可以指定具体的模式: 127.0.0.1:6379> pubsub channels 1) “channel:sports” 2) “channel:it” 3) “channel:travel” 127.0.0.1:6379> pubsub channels channel:r 1) “channel:sports” 2) “channel:travel”

(2)查看频道订阅数 pubsub numsub [channel …] 当前channel:sports频道的订阅数为2: 127.0.0.1:6379> pubsub numsub channel:sports 1) “channel:sports” 2) (integer) 2

(3)查看模式订阅数 pubsub numpat 当前只有一个客户端通过模式来订阅: 127.0.0.1:6379> pubsub numpat (integer) 1

  1. <a name="cvGnv"></a>
  2. ## GEO
  3. Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能,对于需要实现这些功能的开发者来说是一大福音。
  4. - 增加地理位置信息 (返回添加地理位置的个数)
  5. ```bash
  6. geoadd key longitude latitude member [longitude latitude member ...]
  7. 127.0.0.1:6379> geoadd cities:locations 116.28 39.55 beijing
  8. (integer) 1
  9. 127.0.0.1:6379> geoadd cities:locations 116.28 39.55 beijing
  10. (integer) 0
  11. 127.0.0.1:6379> geoadd cities:locations 117.12 39.08 tianjin 114.29 38.02 shijiazhuang 118.01 39.38 tangshan 115.29 38.51 baoding
  12. (integer) 4
  • 获取地理位置信息

    1. geopos key member [member ...]
    2. 下面操作会获取天津的经维度:
    3. 127.0.0.1:6379> geopos cities:locations tianjin
    4. 1) 1) "117.12000042200088501"
    5. 2) "39.0800000535766543"
  • 获取两个地理位置的距离

    1. geodist key member1 member2 [unit]
    2. 其中unit代表返回结果的单位,包含以下四种:
    3. mmeters)代表米。
    4. kmkilometers)代表公里。
    5. mimiles)代表英里。
    6. ftfeet)代表尺。
    7. 127.0.0.1:6379> geodist cities:locations tianjin beijing km
    8. "89.2061"
  • 获取指定位置范围内的地理信息位置集合

    1. georadius key longitude latitude radiusm|km|ft|mi [withcoord] [withdist]
    2. [withhash] [COUNT count] [asc|desc] [store key] [storedist key]
    3. georadiusbymember key member radiusm|km|ft|mi [withcoord] [withdist]
    4. [withhash] [COUNT count] [asc|desc] [store key] [storedist key]
    5. withcoord:返回结果中包含经纬度。
    6. withdist:返回结果中包含离中心节点位置的距离。
    7. withhash:返回结果中包含geohash,有关geohash后面介绍。
    8. COUNT count:指定返回结果的数量。
    9. asc|desc:返回结果按照离中心节点的距离做升序或者降序。
    10. store key:将返回结果的地理位置信息保存到指定键。
    11. storedist key:将返回结果离中心节点的距离保存到指定键
    12. 127.0.0.1:6379> georadiusbymember cities:locations beijing 150 km
    13. 1) "beijing"
    14. 2) "tianjin"
    15. 3) "tangshan"
    16. 4) "baoding"
  • 删除地理位置

    1. GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以
    2. 可以借用zrem命令实现对地理位置信息的删除
    3. zrem key member

    数据持久化

    Redis支持RDB和AOF两种持久化机制,持久化功能能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前之间持久化的文件即可实现数据恢复。简单点说,redis是将数据运行在内存中的,如果出现服务挂掉或者服务器宕机都可以导致数据全部丢失,为了解决这个问题redis提供了两种解决方案,分别是rdb、aof

RDB持久化

简介

  • RDB(Redis DataBase)持久是把当前数据生成快照保存到硬盘的过程
  • 触发RDB持久化过程为手动触发和自动触发

  • 优点

    • RDB是一个非常紧凑(compact)的文件,它保存了redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。
    • RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
  • 缺点

    • RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于 重量级操作(内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑),频繁执行成本过高(影响性能)
    • RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无 法兼容新版RDB格式的问题(版本不兼容)
    • 在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改(数据有 丢失)

      触发方式

  • 自动触发:主配置文件中的save字段

    1. 分别表示每900秒数据发生一次改变、每300秒数据发生10次改变、每60秒数据发生10000次改变会自动触发rdb持久化机制
    2. save 900 1
    3. save 300 10
    4. save 60 10000
  • 手动触发

  1. 执行bgsave命令,父进程检查是否有正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回。
  2. 父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞,通过info stats命令查看latest_fork_usec选项,可以获取最近一个fork操作的耗时,单位为微秒。
  3. 父进程fork完成后,bgsave命令返回“Background saving started”信息并不再阻塞父进程,可以继续响应其他命令。
  4. 子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后 对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成RDB的时间,对应info统计的rdb_last_save_time选项。
  5. 进程发送信号给父进程表示完成,父进程更新统计信息。

    运维提示

  • 当遇到坏盘或磁盘写满等情况时,可以通过config set dir{newDir}在线修改文件路径到可用的磁盘路径,之后执行bgsave进行磁盘切换,同样适用于AOF持久化文件。
  • Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远远小于内存大小,默认开启,可以通过参数config set rdbcompression{yes|no}动态修改。
  • 如果想要恢复相关数据,只需要将相关的RDB文件拷贝到相关的目录下面即可,redis启动时会自动将rdb文件里的内容加载到内存中。

    AOF持久化

    简介

  • AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。

  • AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。

  • 优点

    • AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,Redis 最多也就丢失 1 秒的数据而已。
    • AOF 文件使用 Redis 命令追加的形式来构造,因此,即使 Redis 只能向 AOF 文件写入命令的片断,使用 redis-check-aof 工具也很容易修正 AOF 文件。
    • AOF 文件的格式可读性较强,这也为使用者提供了更灵活的处理方式。例如,如果我们不小心错用了 FLUSHALL 命令,在重写还没进行时,我们可以手工将最后的 FLUSHALL 命令去掉,然后再使用 AOF 来恢 复数据。
  • 缺点

    • 对于具有相同数据的的 Redis,AOF 文件通常会比 RDB文件体积更大。
    • 虽然 AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。但在 Redis 的负载较高时,RDB 比 AOF 具好更好的性能保证。
    • RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF 文件中,因此从理论上说,RDB 比 AOF 方式更健壮。官方文档也指出,AOF 的确也存在一些 BUG,这些 BUG 在 RDB 没有存在。

      使用AOF

  • 开启AOF功能需要设置配置:appendonly yes,默认不开启。AOF文件名 通过appendfilename配置设置,默认文件名是appendonly.aof。保存路径同 RDB持久化方式一致,通过dir配置指定。

    1. appendonly yes # 将配置文件中appendonly字段设置为yes即可

    工作流程

    %S63_3(D$4C8N7TLDHCO1E9.png

  1. 所有写入命令会加入到aof_buff缓冲区中
  2. AOF缓冲区根据对应的策略向硬盘做同步操作。
  3. 随着AOF文件越来越大,需要对AOF文件定期重写,以达到压缩的目的
  4. 当Redis服务器重启时,可以加载AOF文件进行数据恢复。

重写机制

随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis 引入AOF重写机制压缩文件体积。

  • 重写后的AOF文件会变小,原因如下
    • 进程内已经超时的数据不再写入文件
    • 旧的AOF文件含有无效命令,如del key1、hdel key2、srem keys、set a111、set a222等。重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令
    • 多条写命令可以合并为一个,如:lpush list a、lpush list b、lpush list c可以转化为:lpush list a b c。为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset等类型操作,以64个元素为界拆分为多条。
  • 手动触发:直接调用bgrewriteaof命令
  • 自动触发:根据auto-aof-rewrite-min-sizeauto-aof-rewrite-percentage参数确定自动触发时机。

$89EV0{K_]N)T`%_L3(U$)D.png

  1. 执行AOF重写请求
  2. 父进程执行fork创建子进程,开销等同于bgsave过程。

    3.1 主进程fork操作完成后继续响应其他命令。所有修改命令继续写入AOF缓冲区,并根据appendfsync策略同步到硬盘,保证原有AOF机制正确性。
    3.2 由于fork操作运用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然响应命令,Redis使用“AOF重写缓冲区”保存这部分新数据,防止新AOF文件生成期间丢失这部分数据。

  3. 子进程根据内存快照,按照命令合并规则写入到新的AOF文件。每次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync控制,默认为32MB,防止单次刷盘数据过多造成硬盘阻塞。

    5.1 新AOF文件写入完成后,子进程发送信号给父进程,父进程更新统计信息,具体见info persistence下的aof_*相关统计。
    5.2 父进程把AOF重写缓冲区的数据写入到新的AOF文件。
    5.3 使用新AOF文件替换老文件,完成AOF重写。

重启加载持久文件

VXDQNXXS34G$C6XBO4DGP(K.png

  1. AOF持久化开启且存在AOF文件时,优先加载AOF文件。
  2. AOF关闭或者AOF文件不存在时,加载RDB文件
  3. 加载AOF/RDB文件后,Redis启动成功
  4. AOF/RDB文件存在错误时,redis启动失败并打印错误信息。

    架构——主从复制

    简介

    在分布式系统中为了解决单点问题,通常会把数据复制多个副本部署到其他机器,满足故障恢复和负载均衡等需求。Redis也是如此,它为我们提供了复制功能,实现了相同数据的多个Redis副本。复制功能是高可用Redis的基础,后面章节的哨兵和集群都是在复制的基础上实现高可用的。
  • 优点:满足故障和负载均衡等需求
  • 缺点

    • 若主节点出现问题,则不能提供服务,需要人工配置从节点为主节点,无法实现高可用
    • 主从复制的主节点写能力有限
    • 单机节点的存储能力有限

      数据同步方式

      全量同步

  • Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。步骤如下:

    • 从服务器连接主服务器,发送SYNC命令
    • 主服务器接收到SYNC命令后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
    • 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
    • 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照
    • 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令
    • 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令

      增量同步

  • Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。

  • 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

    同步策略

  • 主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

    实验部署

    该实验就在一台主机上配置。

  • 一台主服务器的配置文件

    1. [root@server1 ~]# mkdir /redis/6380/{data,conf,log} -pv
    2. [root@server1 ~]# cd /redis/6380/conf
    3. [root@server1 conf]# vim redis.conf
    4. bind 127.0.0.1
    5. port 6380
    6. daemonize yes
    7. pidfile /redis/redis_6380.pid
    8. loglevel notice
    9. logfile /redis/6380/log/redis_6380.log
    10. dir /redis/6380/data/
  • 两台从服务器的配置文件

    1. [root@server1 ~]# mkdir /redis/638{1,2}/{data,conf,log} -pv
    2. [root@server1 ~]# vim /redis/6381/conf/redis.conf
    3. bind 127.0.0.1
    4. port 6381
    5. daemonize yes
    6. pidfile /redis/redis_6381.pid
    7. loglevel notice
    8. logfile /redis/6381/log/redis_6381.log
    9. dir /redis/6381/data/
    10. slaveof 127.0.0.1 6380
    11. [root@server1 ~]# vim /redis/6382/conf/redis.conf
    12. bind 127.0.0.1
    13. port 6382
    14. daemonize yes
    15. pidfile /redis/redis_6382.pid
    16. loglevel notice
    17. logfile /redis/6382/log/redis_6382.log
    18. dir /redis/6382/data/
    19. slaveof 127.0.0.1 6380
  • 启动则完成主从复制架构的部署。

    1. [root@server1 conf]# redis-server /redis/6380/conf/redis.conf
    2. [root@server1 conf]# redis-server /redis/6381/conf/redis.conf
    3. [root@server1 conf]# redis-server /redis/6382/conf/redis.conf
  • 验证测试 ```bash [root@server1 ~]# redis-cli -h 127.0.0.1 -p 6380 127.0.0.1:6380> info Replication

    Replication

    role:master connected_slaves:2 slave0:ip=127.0.0.1,port=6381,state=online,offset=352,lag=1 slave1:ip=127.0.0.1,port=6382,state=online,offset=352,lag=1 master_repl_offset:352 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:2 repl_backlog_histlen:351 127.0.0.1:6380> set time 22:16 OK

[root@server1 ~]# redis-cli -h 127.0.0.1 -p 6381 127.0.0.1:6381> get time “22:16” 127.0.0.1:6381>

  1. - 建议可以使用三台机器部署实验,ip地址分别改为新的地址,因为主从复制在一台机器上没有现实意义
  2. <a name="ZyolG"></a>
  3. # 架构——哨兵
  4. <a name="fDd0A"></a>
  5. ## 简介
  6. Redis的主从复制模式下,一旦主节点由于故障不能提供服务,需要**人工**将从节点晋升为主节点,同时还要通知应用方更新主节点地址,对于很多应用场景这种故障处理的方式是无法接受的。<br />**Redis较难支持在线扩容**,在集群容量达到上限时在线扩容会变得很复杂。
  7. <a name="qHBij"></a>
  8. ## 工作原理
  9. 每个哨兵(sentinel) 会向其它哨兵(sentinel)、masterslave定时发送消息,以确认对方是否“活”着,如果发现对方在指定时间( down-after-milliseconds )内未回应,则暂时认为对方已挂(主观宕机:sdown);<br />若“哨兵群”中的多数 sentinel,都报告某一master没响应,系统才认为该master“彻底死亡”(即:客观上的真正down机:odown),通过一定的 vote 算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。<br />![VUHJHON{@ICAVWZ1}9HR4AJ.png](https://cdn.nlark.com/yuque/0/2021/png/614741/1616394412971-0e98ef21-8658-48df-b986-c48e888c05f7.png#align=left&display=inline&height=332&margin=%5Bobject%20Object%5D&name=VUHJHON%7B%40ICAVWZ1%7D9HR4AJ.png&originHeight=441&originWidth=695&size=94862&status=done&style=none&width=524)
  10. <a name="PPqY6"></a>
  11. ## 实验部署
  12. - 哨兵配置文件
  13. ```bash
  14. [root@server1 ~]# mkdir /redis/263{78,79,80}/{data,conf,log} -pv
  15. [root@server1 ~]# cd /redis
  16. [root@server1 redis]# vim 26378/conf/sentinel_26378.conf
  17. port 26378 # 监听端口26378
  18. daemonize yes # 以守护线程的方式启动,可以在后台运行
  19. dir "/redis/26378/data"
  20. sentinel monitor mymaster 127.0.0.1 6380 2 # 监听的master的名字、ip、端口号,2表示集群中两个
  21. # 哨兵主观认为master挂掉,才会真正客观认为master挂掉
  22. sentinel down-after-milliseconds mymaster 60000 # 60000毫秒没有回应哨兵则认为master失效
  23. sentinel failover-timeout mymaster 180000 # 180秒没有完成failover,则认为主备切换/故障转移失败
  24. sentinel parallel-syncs mymaster 1 # 在进行failover时最多可以有多少个slave对新的master同步操作
  25. logfile "/redis/26378/log/sentinel.26378.log"
  26. [root@server1 redis]# vim 26379/conf/sentinel_26379.conf
  27. port 26379
  28. daemonize yes
  29. dir "/redis/26379/data"
  30. sentinel monitor mymaster 127.0.0.1 6380 2
  31. sentinel down-after-milliseconds mymaster 60000
  32. sentinel failover-timeout mymaster 180000
  33. sentinel parallel-syncs mymaster 1
  34. logfile "/redis/26379/log/sentinel.26379.log"
  35. [root@server1 redis]# vim 26380/conf/sentinel_26380.conf
  36. port 26380
  37. daemonize yes
  38. dir "/redis/26380/data"
  39. sentinel monitor mymaster 127.0.0.1 6380 2
  40. sentinel down-after-milliseconds mymaster 60000
  41. sentinel failover-timeout mymaster 180000
  42. sentinel parallel-syncs mymaster 1
  43. logfile "/redis/26380/log/sentinel.26380.log"
  • 启动哨兵服务

    1. [root@server1 redis]# redis-sentinel /redis/26378/conf/sentinel_26378.conf
    2. [root@server1 redis]# redis-sentinel /redis/26379/conf/sentinel_26379.conf
    3. [root@server1 redis]# redis-sentinel /redis/26380/conf/sentinel_26380.conf

    ps:通常情况下我们尽量启动3个哨兵(奇数)及以上监听一个主节点。
    关闭服务:ps -ef | grep redis , kill -9 pid

  • 验证测试 ```bash

    启动主从服务器

    [root@server1 redis]# redis-server /redis/6380/conf/redis.conf [root@server1 redis]# redis-server /redis/6381/conf/redis.conf [root@server1 redis]# redis-server /redis/6382/conf/redis.conf

模拟主节点挂掉

[root@server1 ~]# redis-cli -h 127.0.0.1 -p 6380 127.0.0.1:6380> shutdown not connected>

或者使用kill进程的方式模拟

ps -ef |grep 6380 kill -9 pid

登录从节点查看,发现主节点变成6381了

[root@server1 ~]# redis-cli -h 127.0.0.1 -p 6382 127.0.0.1:6382> info replication

Replication

role:slave master_host:127.0.0.1 master_port:6381 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_repl_offset:54281 slave_priority:100 slave_read_only:1 connected_slaves:0 master_repl_offset:0 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0

重新登录6380,发现6380变成了从节点

[root@server1 ~]# redis-server /redis/6380/conf/redis.conf [root@server1 ~]# redis-cli -h 127.0.0.1 -p 6380 127.0.0.1:6380> info replication

Replication

role:slave master_host:127.0.0.1 master_port:6381 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_repl_offset:91677 slave_priority:100 slave_read_only:1 connected_slaves:0 master_repl_offset:0 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0

  1. 查看日志文件
  2. ```bash
  3. [root@server1 redis]# vim /redis/26378/log/sentinel.26378.log
  4. # 主观认为master挂掉了
  5. 7448:X 22 Mar 16:32:31.687 # +sdown master mymaster 127.0.0.1 6380
  6. 7448:X 22 Mar 16:32:31.758 # +new-epoch 1
  7. # 投票选举
  8. 7448:X 22 Mar 16:32:31.761 # +vote-for-leader abd3d9e1dd1b1843ec11223c7efcaa51699150f5 1
  9. # 客观认为master挂了
  10. 7448:X 22 Mar 16:32:31.764 # +odown master mymaster 127.0.0.1 6380 #quorum 2/2
  11. 7448:X 22 Mar 16:32:31.765 # Next failover delay: I will not start a failover before Mon Mar 22 16:38:31 2021
  12. # 重新配置哨兵配置文件
  13. 7448:X 22 Mar 16:32:32.965 # +config-update-from sentinel abd3d9e1dd1b1843ec11223c7efcaa51699150f5 127.0.0.1 26379 @ mymaster 127.0.0.1 6380
  14. # 主备切换
  15. 7448:X 22 Mar 16:32:32.965 # +switch-master mymaster 127.0.0.1 6380 127.0.0.1 6381
  16. 7448:X 22 Mar 16:32:32.965 * +slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6381

架构——集群

简介

redis的哨兵模式基本已经可以实现高可用读写分离 ,但是在这种模式下每台redis服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了cluster模式,实现的redis的分布式存储,也就是说每台redis节点上存储不同的内容。

  • 优点:将Redis的写操作分摊到了不同节点上,提高写的并发能力,扩容简单。
  • 缺点:每个节点都要相互监听、高并发数据写入和读出,工作任务繁重。

    工作原理

  • 对象保存到Redis之前先经过CRC16哈希到一个指定的Node上。

  • 每个Node被平均分配了一个Slot段,对应着0-16384,Slot不能重复也不能缺失,否则会导致对象重复存储或无法存储。
  • Node之间也互相监听,一旦有Node退出或者加入,会按照Slot为单位做数据的迁移。例如Node1如果掉线了,0- 3276这些Slot将会平均分摊到Node2、3、4、5上,由于Node2、3、4、5本身维护的Slot还会在自己身上不会被重新分配,所以迁移过程中不会影响到3277-16384Slot段的使用。

J4B@BXRGN1T}NX6PU$}8UU5.png

实验部署

官方文档:https://redis.io/topics/cluster-tutorial

  • 安装5.0版本的redis(旧版本需要用redis-trib.rb工具创建集群)

    1. [root@server1 ~]# yum install gcc-c++ -y
    2. [root@server1 ~]# wget http://download.redis.io/releases/redis-5.0.4.tar.gz
    3. [root@server1 ~]# tar xzvf redis-5.0.4.tar.gz
    4. [root@server1 redis-5.0.4]# make install PREFIX=/usr/local/redis
    5. [root@server1 redis-5.0.4]# PATH=$PATH:/usr/local/redis/bin
  • 创建Redis集群 ```bash [root@server1 redis]# mkdir /redis-cluster/{7000..7005} -pv

[root@server1 redis]# vim /redis-cluster/7000/redis.conf port 7000 daemonize yes cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes

改一下端口号和nodes文件,其他配置文件相同

[root@server1 redis]# vim /redis-cluster/7001/redis.conf port 7001 daemonize yes cluster-enabled yes cluster-config-file nodes-7001.conf cluster-node-timeout 5000 appendonly yes [root@server1 redis]# vim /redis-cluster/7002/redis.conf [root@server1 redis]# vim /redis-cluster/7003/redis.conf [root@server1 redis]# vim /redis-cluster/7004/redis.conf [root@server1 redis]# vim /redis-cluster/7005/redis.conf

[root@server1 ~]# for i in {7000..7005};do redis-server /redis-cluster/$i/redis.conf;done [root@server1 redis]# ps -ef | grep redis root 7618 1 0 17:49 ? 00:00:01 redis-server :7000 [cluster] root 7651 1 0 17:54 ? 00:00:00 redis-server :7001 [cluster] root 7669 1 0 17:56 ? 00:00:00 redis-server :7003 [cluster] root 7673 1 0 17:56 ? 00:00:00 redis-server :7004 [cluster] root 7677 1 0 17:56 ? 00:00:00 redis-server *:7005 [cluster] root 7681 7389 0 17:56 pts/0 00:00:00 grep —color=auto redis

[root@server1 ~]# redis-cli —cluster create 127.0.0.1:7000 127.0.0.1:7001 \

127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \ —cluster-replicas 1

选项—cluster-replicas 1意味着我们要为每个创建的主控节点创建一个从属节点。

输入yes,即按照此方式分配master和slave

M: 6eeea311f17374abebde4dff97fa957db8b3ddd3 127.0.0.1:7000 slots:[0-5460] (5461 slots) master M: a7856400b2e82b8d18fe64d908a4f9cd46dbad41 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master M: 0832bbcd7d6843f7da8c38a8683293b58e12fbc2 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master S: 5b6794e2944d0d45e316f0bf5bc6fe0f1d51ae47 127.0.0.1:7003 replicates 0832bbcd7d6843f7da8c38a8683293b58e12fbc2 S: f12f469b0489b51cee4d1748a489cf6b21766853 127.0.0.1:7004 replicates 6eeea311f17374abebde4dff97fa957db8b3ddd3 S: 39baf1cdb1afec29f8a1005db841e262d3b6b7da 127.0.0.1:7005 replicates a7856400b2e82b8d18fe64d908a4f9cd46dbad41 [OK] All nodes agree about slots configuration.

Check for open slots… Check slots coverage… [OK] All 16384 slots covered. ```

  • 验证测试 ```bash [root@server1 ~]# redis-cli -c -p 7000

    查询集群信息

    127.0.0.1:7000> cluster info cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:6 cluster_size:3 cluster_current_epoch:6 cluster_my_epoch:1 cluster_stats_messages_ping_sent:112 cluster_stats_messages_pong_sent:114 cluster_stats_messages_sent:226 cluster_stats_messages_ping_received:109 cluster_stats_messages_pong_received:112 cluster_stats_messages_meet_received:5 cluster_stats_messages_received:226

查询各集群节点

127.0.0.1:7000> cluster nodes 0832bbcd7d6843f7da8c38a8683293b58e12fbc2 127.0.0.1:7002@17002 master - 0 1616410133502 3 connected 10923-16383 690b1befaa5580005da180a13428c836a0eae3b5 127.0.0.1:7001@17001 master - 0 1616410133000 2 connected 5461-10922 9e2f1c705ee048b9894f0b602301eb2d41328866 127.0.0.1:7005@17005 slave 08527700d9fbe3f5e554dd33a1137004b0fc7783 0 1616410135000 6 connected 770468e89568cbc2ab84a909217fa4abfee3b8d1 127.0.0.1:7003@17003 slave 690b1befaa5580005da180a13428c836a0eae3b5 0 1616410134609 4 connected 2b9299ae7450c2a13f443f6914f380cb71533ae8 127.0.0.1:7004@17004 slave 0832bbcd7d6843f7da8c38a8683293b58e12fbc2 0 1616410135113 5 connected 08527700d9fbe3f5e554dd33a1137004b0fc7783 127.0.0.1:7000@17000 myself,master - 0 1616410133000 1 connected 0-5460

数据分配

redis 127.0.0.1:7000> set foo bar -> Redirected to slot [12182] located at 127.0.0.1:7002 OK redis 127.0.0.1:7002> set hello world -> Redirected to slot [866] located at 127.0.0.1:7000 OK redis 127.0.0.1:7000> get foo -> Redirected to slot [12182] located at 127.0.0.1:7002 “bar” redis 127.0.0.1:7002> get hello -> Redirected to slot [866] located at 127.0.0.1:7000 “world” ```