Redis

Starter

安装

  1. 安装

    > wget http://download.redis.io/releases/redis-3.0.2.tar.gz
    > tar zxvf redis-3.0.2.tar.gz
    > cd  redis-3.0.2
    > make              # 安装完之后,会在src目录下生成几个可执行文件
    > make test         # 检查make有没有问题
    > make install      # 会自动将src下生成的新的可执行文件copy到/usr/local/bin下
    
  2. 配置

    #将默认配置文件redis.conf 拷贝到固定目录下(/etc/redis)
    > mkdir /etc/redis
    > cp redis.conf /etc/redis
    > vi /etc/redis/redis.conf         # 可修改配置 daemonize yes (表示后台运行)
    
  3. 启动

    # Server
    > redis-server /etc/redis/redis.conf
    
    # Client
    # redis-cli [OPTIONS] [cmd [arg [arg ...]]]
    # -h <主机ip>,默认是127.0.0.1
    # -p <端口>,默认是6379
    # -a <密码>,如果redis加锁,需要传递密码
    # --help,显示帮助信息
    > redis-cli
    
  4. 关闭

    # Close Client
    > redis-cli shutdown
    
    # Close Server
    > pkill redis-server
    
    > netstat -tunpl | grep redis
    > kill pid
    

注意:make失败,错误信息如下

cc: error: ../deps/hiredis/libhiredis.a: No such file or directory
cc: error: ../deps/lua/src/liblua.a: No such file or directory
cc: error: ../deps/jemalloc/lib/libjemalloc.a: No such file or directory
make: *** [redis-server] Error 1

解决方案:分别进入redis下的deps下的hiredis、lua和jemalloc下运行make

> cd deps/hiredis
> make
> yum install -y readline-devel pcre-devel
> cd deps/lua
> make
> yum install openssl-devel
> cd deps/jemalloc
> ./configure
> make

安全性

客户端执行命令前先通过密码认证 note: Redis速度很快,一般一个外部用户可以在1s进行150k次的密码尝试

  1. Server端配置

    > vi /etc/redis/redis.conf
    requirepass <password>
    
  2. Client端连接

    > redis-cli -a <password>
    

    或者

    > redis-cli
    > auth <password>
    

事务处理

Redis对事务的支持比较简单,只能保证一个Client发起的事务中的命令连续执行

  • muti : 开始事务
    • 进入一个事务上下文
    • 后续命令会放入一个队列(不立即执行)
  • discard : 取消事务(回滚)
  • exec : 执行事务 (提交)
    • 顺序执行队列中的所有命令
  • 注意: 队列中某个命令执行错误,整个事务并不会回滚,而是将可执行的命令都提交了!
> get age               -> 22
> muti                   -> OK
> set age 10          -> QUEUED
> set age 20          -> QUEUED
> exec                   -> OK OK
> get age              -> 20

使用乐观锁进行复杂事务控制:大多数基于数据版本(version)的记录机制实现

  • watch xxx :监控某key (乐观锁)
  • exec时发现watch的key发生过变化,则整个事务失败
  • exec,discard,unwatch:会清除连接中的所有监视

持久化机制

Redis 是一个支持持久化的内存数据库 (可将内存中数据同步到硬盘来保证持久化) 两种方式:

  • snapshotting 快照方式 (默认)
    • 存储数据,一定时间间隔做一次
      • 默认存储文件:/redis/bin/dump.rdb
      • 可配置自动做快照持久化的方式,eg:
        save 900 10          #900s内如果超过10个key被修改,就发起快照保存
        
  • append-only file 方式 (缩写:aof)
    • 存储操作
    • 默认存储文件: /redis/bin/appendonly.aof
    • 可配置通过fsync函数强制os写入磁盘的时机,eg:
      > vi /etc/redis/redis.conf
      appendonly yes    //启用aof持久化方式
      #appendfsync always //收到命令立即写入磁盘,最慢,但保证完成持久化
      appendfsync everysec    //每秒钟写入磁盘一次,在性能和持久化之间做了折中
      #appendfsync no        //完成依赖os,性能最好,持久化没有保证
      

虚拟内存

提高数据库容量的方法:

  • 将数据分割到多个redis server
  • 利用虚拟内存,把不经常访问的数据从内存交换到磁盘中,腾出内存空间用于其他需要访问的数据

虚拟内存配置:

> vi /etc/redis/redis.conf
vm-enabled yes                      # 开启vm功能
really-use-vm yes
vm-swap-file /tmp/redis.swap        # 制定保存交换出的value的文件
vm-max-memory 100000                # Redis使用的最大内存上限
vm-page-size 32                     # 每个页面大小 32字节
vm-pages 134217728                  # 最多使用多少页
vm-max-threads 4                    # 执行value对象换入的线程数量

主从复制

  • 主 Master server
  • 从 Slave servers
  • Master 可以拥有多个Slave,Slave可以连接Slave,主从复制不会阻塞Master
  • 主从Server可以在:不同机器 or 同机器不同端口

  • Slave Server端配置

    > vi /etc/redis/redis.conf
    slaveof <master-ip> <master-port>
    # 如果master设置了验证密码,需配置masterauth
    masterauth <master-password>
    
  • Start Master & Slave Servers

    > redis-server /etc/redis/redis.conf
    
  • Client连接测试

    > redis-cli -h xxxx -p xxxx
    > info    # 查看状态
    
    • Master Server info sample:

      # Replication
      role:master
      connected slaves:1
      slave0:ip=192.168.0.110,prot=6379,status:online,offset=00,lag=0
      master_rep1_offset:99
      rep1_backlog_active:1
      ...
      
    • Slave Server info sample:

      #Replication
      role:slave
      master_host:192.168.0.100
      master_port:6379
      master_link_status:up
      ...
      

注意: redis主从复制时提示master_link_status:down

  1. 确定slave的配置文件redis.conf配置正确(slaveofmasterauth 参数)
  2. Unable to AUTH to MASTER: Writing to master: No route to host => 把master和slave防火墙关掉service iptables stop,之后重启
  3. No route to host => iptables -F

发布订阅消息

Redis在订阅者和发布者之间起到消息路由的功能

  • 订阅者使用subscribepsubscribe命令向redis server订阅消息
    • subscribe <channel...> :订阅者监听频道
  • 发布者使用publish命令想redis server发送特定类型的消息
    • publish <channel> <content> :在某频道发布信息

示例:

Client 1:

> subscribe tv1

Client 2:

> subscribe tv1 tv2

Client 3:

> publish tv1 Hello         # return 2 代表有两个客户端正在监听tv1
> publish tv2 World         # return 1

Result:

  • Client 1 收到 message tv1 Hello
  • Client 2 收到 message tv1 Hello message tv2 World

常用命令

键值相关命令

  1. keys : 返回给定pattern的所有key

    > keys *
    > keys my*
    
  2. exists : 确认一个key是否存在=>return 1(存在)/0(不存在)

    > exists name
    > exists age
    
  3. del: 删除一个key => return 1(success)/0(fail)

    > del list2
    > exists list2
    
  4. expire : 设置某个key的过期时间(秒)

    > expire addr 10
    > ttl addr     #查看key还有多少时间过期(有效时长),返回-1表示已过期或取消了过期时间
    > get addr
    
  5. move : 转移key到另一个数据库

    > select 0            # 选择数据库0
    > move age 1          # 将数据库0中age移动到数据库1中
    
  6. persist : 移除key的过期时间

    > persist age        # return 1
    > ttl age            # return -1
    > get age
    
  7. randomkey : 随机返回数据库中一个key

    > randomkey
    > randomkey
    
  8. rename : 重命名key

    > rename age age_new     # return OK
    > keys age*
    
  9. type : 返回key类型

    > type myset       # return set
    > type list1       # return list
    > type kk          # return nil
    

服务器相关命令

  1. select : 选择数据库( Redis数据库编号从0~15)

    > select 1     # return OK
    > select 5     # return OK
    > select 16    # return (error) ERR invalid DB index
    
  2. quit : 退出连接

    > quit
    
  3. dbsize : 返回当前数据库中key的数量

    > dbsize
    
  4. info : 获取服务器的统计信息

    > info
    redis_version:3.0.1
    redis_git_sha1:xxx
    redis_git_dirty:0
    arch_bits:32
    multiplexing_api:epoll
    process_id:xxx
    uptime_in_seconds:xxx
    uptime_in_days:0
    ....
    db0:keys=xxx,expire=xxx
    db1:xxx
    ...
    
  5. config get : 实时存储收到的请求 (获取一些相关配置的参数)

    > config get *              # 获取所有配置参数信息
    > config get timeout
    
  6. flushdb : 删除当前数据库中所有key(清空当前数据库)

    > dbsize
    > flushdb
    > dbsize
    
  7. flushall :删除所有数据库的所有key(清空所有数据库)

    > flushall
    

数据类型

String

  • 二进制安全
  • 一个key对应一个value
  • value可包含任何数据(eg:jpg,序列化对象)

  • set

    > set name Tom      # return "OK"
    > get name          # return "Tom"
    
  • setnx:set not exist => return 0(fail) / 1(success)

    > setnx name Jack         # return 0
    > get name                # return "Tom"
    
  • setex:set expireTime

    > setex color 10 red         # return "OK"
    > get color                  # return "red"
    # 10s later...
    > get color                  # return (nil)
    
  • setrange:replace string by range => return new string length

    > get email                            # return "Tom@gmail.com"
    > setrange email 4 163.com             # return 13
    > get email                            # return "Tom@163.comom"
    
  • mset:muti-set => return "OK" / 0 (one fail => all fail)

    msetnx: muti-set not exist          # return "OK" / 0 (one fail -> all fail)
    > mset key1 a1 key2 a2 key3 a3      # return "OK"
    > get key2                          # return "a2"
    
  • getset:设置key的值,并返回key的旧值

    > get name                   # return "Tom"
    > getset name  Jack          # return "Tom"
    > get name                   # return "Jack"
    
  • getrange:get substring

    > get email                      # return "Tom@163.comom"
    > getrange email 0 5             # return "Tom@16"
    
  • mget: muti-get

    > mget key1 key2 key3 key4         # return "a1" "a2" "a3" (nil)
    
  • append:追加value => return new string length

    > get name                    # return "Jack"
    > append name .net            # 8
    > get name                    # return "Jack.net"
    
  • strlen: return value length

    > strlen name                # return 8
    
  • incr: i++; decr: i--

    > set age 20    # return "20"
    > incr age        # return "21"
    > incr age        # return "22"
    
    > get cc      # return (nil)
    > incr         # return 1
    > incr         # return 2
    
  • incrby: i+n; decrby: i-n

    > incr bb 5     # return 5
    > incr bb 5     # return 10
    > incr bb -2    # return 8
    

hash

  1. hset hash field fieldValue

    > hset user:001 name Tom     # return 1
    > hget user:001 name            # return "Tom"
    
  2. hsetnx

    > hsetnx user:001 name Jack   # return 0
    > hget user:001 name             # return "Tom"
    
  3. hmset

    > hmset user:001 name Lucy age 20     # return "OK"
    > hget user:001 age                            # return "20"
    
  4. hmget

    > hmget user:001 name age    # return "Lucy" "20"
    
  5. hincrby

    > hincrby user:001 age 5      # return "25"
    
  6. hexists: test field exist or not => return 1(exist)/0 (not exist)

    > hexists user:001 age     # return 1
    > hexists user:001 sex     # return 0
    
  7. hlen: field length

    > hlen user:001     # return 2
    
  8. hkeys: return all fields

    > hkeys user:001      # return "name" "age"
    
  9. hvals: return all values

    > hvals user:001     # return "Lucy" "20"
    
  10. hgetall: return all fields and values

    > hgetall user:001     # return "name" "Lucy" "age" "20"
    
  11. hdel: del hash field

    > hdel user:001 age    # return 1
    > hget user:001 age   # return (nil)
    

list

每个子元素都是String类型的双向链表,即可做栈又可做队列 可以通过push,pop操作从头部或尾部添加删除元素

  1. lpush,lpop,lrang

    • lpush 从头部压入一个元素 => return list size
    • lpop 从头部删除一个元素 => return 弹出的元素
    • lrange 从头开始取元素( 0表示头部第一个元素,-1表示尾部第一个元素)
      > lpush list1 "Hello"       # return 1
      > lpush list1 "World"       # return 2
      > lrange list1 0 -1         # return "World" "Hello" (从头取到尾)
      # 类似栈(先进后出)
      > lpop list1                  # return "World"
      > lrange list1                # return "Hello"
      
  2. rpush,rpop

    • rpush 从尾部压入一个元素
    • rpop 从尾部弹出一个元素
      > rpush list2 "Hello"
      > rpush list2 "World"
      > lrange list2 0 -1           # return "Hello" "World"
      # 类似队列(先进先出)
      > rpop list2                   # return "World"
      > lrange list2                 # return "Hello"
      
  3. linsert,lset

    • linsert 在特定位置添加元素
    • lset 替换指定下标的元素

      > lpush list3 "one"                              # return 1
      > lpush list3 "two"                              # return 2
      > lrange list3 0 -1                              # return "two" "one"      
      > linsert list3 before "one" "three"       # return 3
      > lrange list3 0 -1                        # return "two" "three" "one"
      
      > lset list3 1 "four"      # return OK
      > lrange list3 0 -1        # return "two" "four" "one"
      
  4. lrem 删除n个值为value的元素

    • n=0 表示全部删除
    • n>0 从头部开始删除
    • n<0 从尾部开始删除
    • return 删除个数
      > lpush list3 "one"
      > lpush list3 "one"
      > lpush list3 "one"
      > lrange list3 0 -1         # return "one" "one" "one" "two" "four" "one"
      > lrem list3 3 "one"        # return 3
      > lrange list3 0 -1         # return "two" "four" "one"
      > lrem list3 1 "one"        # return 1
      > lrange list3 0 -1         # return "two" "four"
      > lrem list3 1 "one"        # return 0
      
  5. ltrim 保留指定范围的元素,其他全部删除 => return OK

    > rpush list4 "a1"
    > rpush list4 "a2"
    > rpush list4 "a3"
    > rpush list4 "a4"
    > lrange list4 0 -1          # return "a1" "a2" "a3" "a4"
    > ltrim list4 1 2            # return OK
    > lrange list4 0 -1          # return "a2" "a3"
    
  6. rpoplpush list1 list2 => list1从尾部弹出元素,从头压入list2,return 弹出元素

    # list5: a1 a2 a3
    # list6: b1 b2 b3 
    > rpoplpush list5 list6      # a1
    > lrange list5 0 -1          # a1 a2
    > lrange list6 0 -1          # a3 b1 b2 b3
    
  7. lindex list index => return list中index位置的元素

    > lindex list6 3      # return b3
    
  8. llen list -> return list size

    > llen list6     # return 4
    

set

string类型的无序集合,通过hash table实现 集合可取并集,交集,差集等操作 (可用于实现类似好友推荐,blog的tag功能)

  1. sadd,srem,smembers

    • sadd 集合中添加元素 => return 1(success)/0(fail)
    • srem 集合中删除元素 => return 1(success)/0(fail)
    • smembers 列出集合所有元素

      > sadd myset "Hello"         # return 1
      > sadd myset "World"         # return 1
      > sadd myset "World"         # return 0
      > smembers myset             # return "World" "Hello"
      
      > srem myset "Hello"         # return 1
      > srem myset "Hello"         # return 0
      > smembers myset             # return "World"
      
  2. spop,srandmembber

    • spop 从集合中随机弹出一个元素 => return 弹出的元素
    • srandmember 随机返回集合的一个元素,但不删除元素

      # 集合mytest1:"a1","a2","a3","a4","a5"
      > spop mytest1                # return "a3" (随机的一个元素)
      > smembers mytest1            # return "a1" "a2" "a4" "a5"
      
      > srandmember mytest1         # return "a2" (随机的一个元素)
      > smembers mytest1            # return  "a1" "a2" "a4" "a5"
      
  3. sdiff,sdiffstore

    • sdiff 返回两个集合的差集,以前一个集合为标准
    • sdiffstore 将返回的差集存储到另一个集合 -> return 1(success)/0(fail)

      # test1: a1,a2,a3
      # test2: b1,a2,b3
      > sdiff test1 test2          # return "a1" "a3"
      > sdiff test2 test1          # return "b1" "b3"
      
      > sdiffstore mytest3 test1 test2    #  return 1
      > smembers mytest3                  #  return "a1" "a3"
      
  4. sinter,sunion

    • sinter 返回两个集合的交集,并存储到另一个集合 =>return 1(success)/0(fail)
    • sunion 返回两个集合的并集,并存储到另一个集合 => return 元素个数

      # test1: a1,a2,a3
      # test2: b1,a2,b3
      > sinter test1 test2            # return "a2"
      > sinter mytset4 test1 test2    # return 1
      > smembers mytest4              #  return "a2"
      
      > sunion test1 test2                  # return a1 a2 a3 b1 b3
      > sunionstore mytest5 test1 test2     # return 5
      > smembers mytest5                    # return a1 a2 a3 b1 b3
      
  5. smove 将一个元素从集合1移到集合2 => return 1(success)/0(fail)

    > smove test1 test2 a1
    > smembers test1          # return a2 a3
    > smembers test2          # return a1 b1 a2 b3
    
  6. scard 返回集合元素个数

    > scard test1     # return 2
    > scard test2     # return 4
    
  7. sismember 测试某个元素是否在某个集合中=> return 1(yes)/0(no)

    > sismember test1 a2    # return 1
    > sismember test1 a1    # return 0
    

zset

  • sorted set类型,set的升级,有序集合
  • 每次添加修改元素后会自动重新排序
  • 每一个元素有两个值,值value和分数score(专门用来做排序)
  • eg: n1 -> {(value1,score1),(value2,score2),...}
  • rank: index
  • lexicographical: 相同的分值时,有序集合的元素会根据成员的值逐字节对比
rank value score
0 one 1
1 two 2
2 three 3
  1. zadd 向有序集合添加一个元素并指定顺序 => return 1(success)/0(添加失败,但会更新顺序号)

     # zadd name score1 value1 score2 value2 ...
     redis:6379> zadd cat 10 cat-A 20 cat-B 30 cat-C 40 cat-D
     (integer) 4
    
  2. zscan key cursor 查看集合元素们

     # zscan name cursor ...
     redis:6379> zscan cat 0
     1) "0"
     2) 1) "cat-A"
        2) "10"
        3) "cat-B"
        4) "20"
        5) "cat-C"
        6) "30"
        7) "cat-D"
        8) "40"
    
  3. zrange/zrevrange 获取rank范围内的元素; zrangebyscore 获取score范围内的元素

     # zrange name start end [withscores]
     redis:6379> zrange cat 1 3
     1) "cat-B"
     2) "cat-C"
     3) "cat-D"
     redis:6379> zrange cat 1 3 withscores
     1) "cat-B"
     2) "20"
     3) "cat-C"
     4) "30"
     5) "cat-D"
     6) "40"
    
     # zrangebyscore name minscore maxscore [withscores]
     redis:6379> zrangebyscore cat 15 45
     1) "cat-B"
     2) "cat-C"
     3) "cat-D"
    
  4. zrank/zrevrank 获取元素rank; zscore 获取元素score

     # zrank name value
     redis:6379> zrank cat cat-C
     (integer) 2
     redis:6379> zrevrank cat cat-C
     (integer) 1
    
     # zscore name value
     redis:6379> zscore cat cat-B
     "20"
    
  5. zcard 返回集合中所有元素个数; zcount 返回集合指定顺序号范围内的元素数量

     # zcard key
     redis:6379> zcard cat
     (integer) 4
    
     # zcount name minscore maxscore
     redis:6379> zcount cat 15 45
     (integer) 3
    
  6. zinterstore 交集,zunionstore并集

     redis:6379> zadd cat2 15 cat-B 45 cat-D 55 cat-E
     (integer) 3
    
     # zinterstore name destName numKeys key1 key2 ... [aggregate sum|max|min]
     redis:6379> zinterstore cat-inter 2 cat cat2
     (integer) 2
     redis:6379> zscan cat-inter 0
     1) "0"
     2) 1) "cat-B"
        2) "35"
        3) "cat-D"
        4) "85"
    
     # zinterstore name destName numKeys key1 key2 ... [aggregate sum|max|min]
     redis:6379> zunionstore cat-union 2 cat cat2
     (integer) 5
     redis:6379> zscan cat-union 0
     1) "0"
     2)  1) "cat-A"
         2) "10"
         3) "cat-C"
         4) "30"
         5) "cat-B"
         6) "35"
         7) "cat-E"
         8) "55"
         9) "cat-D"
        10) "85"
    
     redis:6379> zunionstore cat-union 2 cat cat2 aggregate max
     (integer) 5
     redis:6379> zscan cat-union 0
     1) "0"
     2)  1) "cat-A"
         2) "10"
         3) "cat-B"
         4) "20"
         5) "cat-C"
         6) "30"
         7) "cat-D"
         8) "45"
         9) "cat-E"
        10) "55"
    
  7. zincrby 增减某个集合元素的score,若元素不存在,则插入

     # zincrby name increment value
     redis:6379> zincrby cat 3 cat-A
     "13"
     redis:6379> zscore cat cat-A
     "13"
    
  8. zrem/zremrangebyrank 按索引/zremrangebyscore按score: 删除集合的元素

     # zrem name value
     redis:6379> zrem cat cat-B
     (integer) 1
     redis:6379> zscan cat 0
     1) "0"
     2) 1) "cat-A"
        2) "13"
        3) "cat-C"
        4) "30"
        5) "cat-D"
        6) "40"
    
     # zremrangebyscore name minscore maxscore
     redis:6379> zremrangebyscore cat 15 35
     (integer) 1
     redis:6379> zscan cat 0
     1) "0"
     2) 1) "cat-A"
        2) "13"
        3) "cat-D"
        4) "40"
    

应用举例

  1. 多个String型键存储一个Object

    key value
    post:42:title Test Article
    post:42:author Tom
    post:42:content This is Test
    posts:count 3
  2. 一个Hash型存储一个Object

    key field value
    post:42 title Test Article
    post:42 author Tom
    post:42 content This is Test
  3. List

    key value
    posts:list 1,2,42
    posts:42:comments "Good","Thanks","Useful",...
  4. Set

    key value
    post:1:tags java
    post:2:tags java,mysql
    post:42:tags java,redis
    tags.java.posts 1,2,42
    tags.mysql.posts 2
    tags.redis.posts 42
  5. ZSet

    key member score
    posts:readCount 1 2
    posts:readCount 2 5
    posts:readCount 42 10