Redis 初体验

Posted by danner on October 22, 2018

简介

Redis 是一种基于键值对( key - value) 的 NoSQL 数据库,面向快速执行场景。

Redis 的数据结构有 Stringhashlistset、zset、Bitmaps、HyperLogLog、GEO,常用的是前四种。 Redis 提供了简单的事务功能,将一组需要一起执行的命令放到 multiexec 两个命令之间。multi 命令代表事务开始,exec 命令代表事务结束,它们之间的命令是原子顺序执行的 。Redis 不支持回滚,搭配 Lua 脚本才能实现相关功能;最大的特点是快,基于以下几个方面

  • 所有数据都是存放在内存( 不适合存放大的数据量 )
  • C 语言实现,执行速度相对会快
  • 使用单线程,减少线程切换的开销( 最新版本的 redis 有些操作也用多线程)
  • 源代码优雅

redis 的数据都放在内存中是不安全的,为了应对异常(断电或者机器故障),也提供持久化方式:RDB 、AOF。

  • RDB:当前进程数据生成快照保存到硬盘,触发 RDB 持久化过程分为手动自动
  • AOF:以独立日志的方式记录每次写命令,重启时执行AOF 文件来恢复数据,解决了数据持久化的实时性是当前主流方式。

值得一提的是,在 redis 3.0 版本开始实现了真正的分布式。

安装

以下为源码方式安装

[hadoop@danner000 software]$ wget http://download.redis.io/releases/redis-3.0.7.tar.gz
[hadoop@danner000 software]$ tar -zxvf redis-3.0.7.tar.gz -C ../app/
[hadoop@danner000 software]$ cd ../app/
# 创建软链接
[hadoop@danner000 app]$ ln -s redis-3.0.7/ redis
[hadoop@danner000 redis]$ cd redis
[hadoop@danner000 redis]$ make
# redis 可执行文件移动到 /usr/local/bin 目录 需要 root 权限
[root@danner000 redis]# make install
[hadoop@danner000 src]$ ls -l /usr/local/bin/ | grep redis
-rwxr-xr-x. 1 root root 4589171 Oct 24 11:19 redis-benchmark
-rwxr-xr-x. 1 root root   22225 Oct 24 11:19 redis-check-aof
-rwxr-xr-x. 1 root root   45443 Oct 24 11:19 redis-check-dump
-rwxr-xr-x. 1 root root 4698386 Oct 24 11:19 redis-cli
lrwxrwxrwx. 1 root root      12 Oct 24 11:19 redis-sentinel -> redis-server
-rwxr-xr-x. 1 root root 6471167 Oct 24 11:19 redis-server
# 安装成功可输出版本号
[hadoop@danner000 src]$ redis-cli -v
redis-cli 3.0.7

编译安装后会在 redis 的 src 目录多几个可执行文件,简单介绍下

可执行文件 作用
redis-server 启动 redis
redis-cli redis 命令行客户端
redis-benchmark redis 基准测试工具
redis-check-aof redis AOF 持久化文件检测和修复工具
redis-check-dump redis RDB 持久化文件检测和修复工具
redis-sentinel 启动 redis sentinel

服务启停

# 启动服务
[hadoop@danner000 redis]$ redis-server redis.conf 
# 指定配置文件启动,占用控制台窗口
# 修改配置 redis.conf,以守护进程方式启动,日志输出到 redis.log
daemonize yes
logfile "/home/hadoop/app/redis/logs/redis.log"

# 此时启动不占用控制台窗口
[hadoop@danner000 redis]$ redis-server redis.conf 
# 关闭服务
[hadoop@danner000 redis]$ redis-cli shutdown
# 查看日志
86567:M 24 Oct 14:42:45.338 # User requested shutdown...
86567:M 24 Oct 14:42:45.338 * Saving the final RDB snapshot before exiting.
86567:M 24 Oct 14:42:45.352 * DB saved on disk
86567:M 24 Oct 14:42:45.352 * Removing the pid file.
86567:M 24 Oct 14:42:45.352 # Redis is now ready to exit, bye bye...
# 正常关闭会端口客户端连接并生成持久化文件
# 不要用 kill 去杀死redis 服务,这样不会生成持久化文件,还有可能造成资源不释放

客户端

客户端有两种方式连接服务:

  • 交互式: redis-cli -h {host} -p {port}
[hadoop@danner000 redis]$ redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> keys *
(empty list or set)
  • 命令式:redis-cli -h {host} -p {port} command
# 直接获取 hello 键的值
[hadoop@danner000 redis]$ redis-cli -h 127.0.0.1 -p 6379 get hello
"world"

全局命令

  • 查看所有键 keys *
127.0.0.1:6379> keys *
1) "hello"
2) "myCounter"
  • 键总数 dbsize
127.0.0.1:6379> dbsize
(integer) 2

dbsize 是直接获取内置的键数量,复杂度是 O(1)keys * 是去遍历所有键,复杂度是 O(n),所以线上环境禁止使用,建议是使用 scan

  • scan:迭代扫描
# 给一个起点,每次扫描一部分,知道扫描结束
SCAN cursor [MATCH pattern] [COUNT count]
127.0.0.1:6379> scan 0
1) "15"
2)  1) "hello2"
    2) "listkey"
    3) "hello10"
    4) "hello8"
    5) "hello1"
    6) "hello"
    7) "hello9"
    8) "hello5"
    9) "hello4"
   10) "hello3"
   11) "hello7"
# 15 是上次 scan 返回的游标,0 表示扫描完毕
127.0.0.1:6379> scan 15
1) "0"
2) 1) "hello6"

hscansscanzscan 是解决哈希、集合、有序集合遍历造成阻塞问题的命令

  • 检测key 是否存在 exists key
# key 存在返回 1,不存在返回 0
127.0.0.1:6379> exists hello
(integer) 1
127.0.0.1:6379> exists hell
(integer) 0
  • 删除 key del key
127.0.0.1:6379> del hello
(integer) 1
127.0.0.1:6379> exists hello
(integer) 0
  • 对key 设置生命周期 expire key
127.0.0.1:6379> set hello world
OK
# hello key 活 10 s
127.0.0.1:6379> expire hello 10
(integer) 1
# ttl 查看 key 还有多长时间,单位 s
127.0.0.1:6379> ttl hello
(integer) 6
# 返回 -2 表示 key 不存在
127.0.0.1:6379> ttl hello
(integer) -2
# 常用于超大数据集中小数据集的缓存:用到时放到 redis ,过期自动删除
  • 查看key 数据结构
127.0.0.1:6379> type myCounter
string

数据结构

type 命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合),但这些只是 Redis 对外的数据结构 ,如下所示

每种数据结构都有自己底层的内部编码的多种实现,可以在不同的场景选择合适的编码。

# object encoding 命令可查询内部编码
127.0.0.1:6379> object encoding myCounter
"int"

字符串

字符串是 redis 中最基础的数据结构,所有的 key 都是字符串并且其他几种数据结构的都是在字符串的基础上构建的。交互模式下输入 help @string 可输出所有相关指令,下面挑三几个常用来演示

  • set: 给 key 赋值,可以带生命周期
    • SET key value [EX seconds] [PX milliseconds] [NX|XX]
    • SETNX key value,key 不存在才能设置成功
    • SETEX key seconds value ,必须带生命周期(setex = set + ex)
    • MSET key value [key value ...],批量设置 key 值
# redis 生命周期 10 s
127.0.0.1:6379> set redis time ex 10
OK
127.0.0.1:6379> ttl redis
(integer) 2
127.0.0.1:6379> set hello world
OK
# 已存在的key 无法用 setnx 赋值
127.0.0.1:6379> setnx hello world1
(integer) 0
127.0.0.1:6379> setnx java ee
(integer) 1
127.0.0.1:6379>
# mset
127.0.0.1:6379> keys *
1) "java"
2) "hello"
3) "myCounter"
127.0.0.1:6379> mset hello1 world1 hello2 world2
OK
127.0.0.1:6379> keys *
1) "java"
2) "hello2"
3) "hello"
4) "hello1"
5) "myCounter"
  • get :获取 key 值
    • GET key
    • MGET key [key ...]:批量获取,生产中建议使用该方法可以减少多次命令带来的网络消耗
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> mget hello hello1 hello2
1) "world"
2) "world1"
3) "world2"
  • incr :计数
    • INCR key:自增1,若 value 不是数字类型则报错
    • INCRBY key increment: + increment
    • INCRBYFLOAT key increment :+ float increment ,
    • DECR key:自减1
    • DECRBY key decrement:- decrement
127.0.0.1:6379> incr myCounter
(integer) 2
127.0.0.1:6379> incrby myCounter 10
(integer) 12
127.0.0.1:6379> incrbyfloat myCounter 6.0
"18"
127.0.0.1:6379> decr myCounter
(integer) 17
127.0.0.1:6379> decrby myCounter 3
(integer) 14
  • 字符串相关操作
    • STRLEN key:获取值长度
    • APPEND key value:追加字符串
    • GETRANGE key start end:substr
    • SETRANGE key offset value:替换某个位置的值
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> strlen hello
(integer) 5
127.0.0.1:6379> append hello redis
(integer) 10
127.0.0.1:6379> get hello
"worldredis
# 注意,redis 都是闭包
127.0.0.1:6379> getrange hello 2 6 
"rldre"
127.0.0.1:6379> setrange hello 3 h
(integer) 10
127.0.0.1:6379> get hello
"worhdredis"

内部编码

  • int:少于八个字节的数字
  • embstr:小于39个字节的字符串
  • raw:大于39个字节的字符串 or 字符串有操作之后
127.0.0.1:6379> set intkey 123
OK
127.0.0.1:6379> object encoding intkey
"int"
127.0.0.1:6379> object encoding java
"embstr"
127.0.0.1:6379> append java web
(integer) 5
# append 后自动转化为 raw
127.0.0.1:6379> object encoding java
"raw"

常用于热数据的缓存

hash

哈希类型是指键值本身又是一个键值对结构,主要是针对 field 操作

哈希的命令都是 h 开头,执行 help @hash 查看相关指令

  • hset:设置 field 值
    • HSET key field value
    • HMSET key field value [field value ...]:设置多个 field
# 相当于是给用户1 设置name = john
127.0.0.1:6379> hset user:1 name john
(integer) 1
  • hget:获取field 值
    • HGET key field
    • HMGET key field [field ...]:获取多个 field
# 获取用户1 的name 值
127.0.0.1:6379> hget user:1 name
"john"
# 无对应key,返回 nil
127.0.0.1:6379> hget user:100 name
(nil)
  • hexists:是否存在 field
    • HEXISTS key field
127.0.0.1:6379> hexists user:1 name
(integer) 1
  • hdel:删除 field
    • HDEL key field [field ...]: 可删除多个 field
127.0.0.1:6379> hdel user:1 name
(integer) 1
  • ` hlen`:计算 field 个数
    • HLEN key
127.0.0.1:6379> hset user:1 age 20
(integer) 1
127.0.0.1:6379> hlen user:1
(integer) 2
  • hvals:获取所有 field 值
    • HVALS key
127.0.0.1:6379> hvals user:1
1) "john"
2) "20"
  • hgetall:获取所有 field-value
    • HGETALL key
127.0.0.1:6379> hgetall user:1
1) "name"
2) "john"
3) "age"
4) "20"

如果有很多 field ,不建议使用 hgetall,可能会造成 redis 堵塞;如果是只需要部分 field ,可使用 hmget;或者使用 hscan

内部编码

  • ziplist
    • 哈希元素个数小于 ` hash-max-ziplist-entries `(默认 512 个)
    • 所有值小于 ` hash-max-ziplist-value `(默认 64 字节)
    • 结构比 hashtable 更紧凑,内存更省
  • hashtable
    • 不满足 ziplist 情况下使用 hashtable
    • hashtable 读写复杂度 O(1)
127.0.0.1:6379> object encoding user:1
"ziplist"
# field 值大于 64字节
127.0.0.1:6379> hset user:1 name 
"adasdsadasdadasdadsadasdasdasdasdasdasdasdsadasdasdasdasdasdffdsfsdf"
(integer) 0
# 变为 hashtable
127.0.0.1:6379> object encoding user:1
"hashtable"

常用于缓存表数据

List

列表类型存储多个有序的字符串,可以当成使用,最多存储 231 个元素。

help @list 查看相关命令,大多数命令是L 开头

  • 添加操作
  • RPUSH key value [value ...]:右边插入数据
  • LPUSH key value [value ...]:左边插入数据
  • LINSERT key BEFORE|AFTER pivot value:指定元素的前/后插入数据
127.0.0.1:6379> lpush listkey q
(integer) 1
127.0.0.1:6379> lpush listkey w
(integer) 2
# listkey 值为 "w,q,a"
127.0.0.1:6379> rpush listkey a
(integer) 3
# listkey 值为 "w,q,123,a"
127.0.0.1:6379> linsert listkey after q 123
(integer) 4
# linsert 只插入一次
  • 查询操作
    • LRANGE key start stop:查询 start - stop 的数据
    • LINDEX key index:获取 index 的值
    • LLEN key:获取 list 长度
# 此方法可以查询整个list
127.0.0.1:6379> lrange listkey 0 -1
1) "w"
2) "66"
3) "q"
4) "66"
5) "123"
6) "a"
7) "q"
127.0.0.1:6379> lindex listkey 3
"66"
127.0.0.1:6379> llen listkey
(integer) 7
  • 删除操作
    • LPOP key:左侧弹出一个元素
    • RPOP key:右侧弹出一个元素
    • LREM key count value:删除指定的元素
    • LTRIM key start stop:获取子集
    • BLPOP key [key ...] timeout:阻塞的方式左侧弹出元素,若无数据等待 timeout 再返回,timerout = 0 则一直等待
    • BRPOP key [key ...] timeout:阻塞的方式右侧弹出元素
127.0.0.1:6379> lpop listkey
"w"
127.0.0.1:6379> rpop listkey
"q"
127.0.0.1:6379> lrange listkey 0 -1
1) "66"
2) "q"
3) "66"
4) "123"
5) "a"
# 删除 3个 66,虽然list 只有2个,但也不会出错
127.0.0.1:6379> lrem listkey 3  66
(integer) 2
127.0.0.1:6379> lrange listkey 0 -1
1) "q"
2) "123"
3) "a
# 相当于是删除 第0 个元素
127.0.0.1:6379> ltrim listkey 1 -1
OK
# 返回值是 key + value
127.0.0.1:6379> blpop listkey 3
1) "listkey"
2) "q"
127.0.0.1:6379> lrange listkey 0 -1
1) "java"
2) "a"
  • 修改操作
    • LSET key index value:修改指定位置元素
127.0.0.1:6379> lset listkey 1 java
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "q"
2) "java"
3) "a"

内部编码

  • ziplist
    • 元素个数小于 ` hash-max-ziplist-entries `(默认 512 个)
    • 所有值小于 ` hash-max-ziplist-value `(默认 64 字节)
    • 结构比 linkedlist 更紧凑,内存更省
  • ` linkedlist `
    • ziplist 无法满足时使用 linkedlist
127.0.0.1:6379> object encoding listkey
"ziplist"
127.0.0.1:6379> lpush listkey aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
(integer) 3
127.0.0.1:6379> object encoding listkey
"linkedlist"

使用场景

  • 消息队列:lpush + brpop
  • 队列: lpush+rpop
  • 栈: lpush+lpop
  • 有限集合:lpush+ltrim

Set

集合也可以可以保存多个元素,但不同 list 不允许重复元素且是无序的不支持下标取元素。help @set

  • 元素操作
    • SADD key member [member ...]:添加元素
    • SREM key member [member ...]:删除元素
    • SCARD key:获取元素个数
    • SISMEMBER key member:集合中元素是否存在
    • SRANDMEMBER key [count]:集合中随机返回几个元素
    • SMEMBERS key:获取几个所有元素
127.0.0.1:6379> sadd setkey a
(integer) 1
127.0.0.1:6379> srem setkey a
(integer) 1
127.0.0.1:6379> scard setkey
(integer) 1
# 返回0 不存在
127.0.0.1:6379> sismember setkey c
(integer) 0
127.0.0.1:6379> srandmember setkey 3
1) "s"
2) "x"
3) "b"
127.0.0.1:6379> smembers setkey
1) "z"
2) "s"
3) "x"
4) "b"
5) "d"
  • 集合操作
    • SINTER key [key ...]:多个集合的交集
    • SUNION key [key ...]:多个集合并集
    • SDIFF key [key ...]:多个集合差集
    • SINTERSTORE destination key [key ...]:多个集合的交集保存
    • SUNIONSTORE destination key [key ...]:多个集合并集保存
    • SDIFFSTORE destination key [key ...]:多个集合差集保存

内部编码

  • intset:整数集合
    • 元素个数小于 ` hash-max-ziplist-entries `(默认 512 个)
    • 元素必须是整数
    • 省内存
  • hashtable:哈希表
    • 不符合intset 的情况
127.0.0.1:6379> sadd setkey1 1
(integer) 1
127.0.0.1:6379> object encoding setkey1
"intset"
127.0.0.1:6379> sadd setkey1 2.9
(integer) 1
# 添加浮点数后变为 hashtable
127.0.0.1:6379> object encoding setkey1
"hashtable"

集合的典型的应用是用户标签

参考资料

Redis 开发与运维