Redis初体验

  |   0 评论   |   941 浏览

背景

Redis做为排名第一的key-value存储,一直没有机会去实际使用。

Redis与Memcached对比

  • 性能:Redis单核在小数据量时占优,Memcached多核在大数据量时占优
  • 内存:简单类型Memcached占优,复杂类型Redis占优.
  • 服务端操作:缓存复杂结构,序列化和反序列化可以在Redis服务端完成,对于Memcached只能自己完成

数据类型

包括string,list,set,sorted set和hash,共5种数据类型,不支持嵌套类型。

事务

  • 支持ACID
  • 支持乐观锁

安装

下载

stable版本 4.0.9

redis没有区分mac版本和linux版本。下载下来的是源代码。

编译

参加压缩包中的Readme.md文件,直接一个make命令即可

编辑结果

Hint: It's a good idea to run 'make test' ;)

那就再make test一下吧

结果

\o/ All tests passed without errors!

Cleanup: may take some time... OK

运行

$ cd src
$ ./redis-server

结果

58315:C 22 May 11:08:36.522 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
58315:C 22 May 11:08:36.523 # Redis version=4.0.9, bits=64, commit=00000000, modified=0, pid=58315, just started
58315:C 22 May 11:08:36.523 # Warning: no config file specified, using the default config. In order to specify a config file use ./redis-server /path/to/redis.conf
58315:M 22 May 11:08:36.524 * Increased maximum number of open files to 10032 (it was originally set to 4864).
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 4.0.9 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 58315
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

58315:M 22 May 11:08:36.526 # Server initialized
58315:M 22 May 11:08:36.526 * Ready to accept connections

或者指定配置文件

$ ./redis-server /path/to/redis.conf

或者做为slave来启动

$ ./redis-server --port 9999 --slaveof 127.0.0.1 6379
$ ./redis-server /etc/redis/6379.conf --loglevel debug

安装

make install

或者

$ cd utils
$ ./install_server.sh

使用

cli

体验

使用redis-cli

[abeffect@note src]$ ./redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> get foo
"bar"
127.0.0.1:6379> incr mycounter
(integer) 1
127.0.0.1:6379> incr mycounter
(integer) 2
127.0.0.1:6379>

常用示例

  cat /etc/passwd | redis-cli -x set mypasswd
  redis-cli get mypasswd
  redis-cli -r 100 lpush mylist x
  redis-cli -r 100 -i 1 info | grep used_memory_human:
  redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3
  redis-cli --scan --pattern '*:12345*'

set

127.0.0.1:6379> sadd keywords "note"
(integer) 1
127.0.0.1:6379> sadd keywords "abeffect"
(integer) 1
127.0.0.1:6379> smembers keywords
1) "abeffect"
2) "note"
127.0.0.1:6379> scard keywords
(integer) 2
127.0.0.1:6379> sismember keywords "note"
(integer) 1
127.0.0.1:6379> sscan keywords 0 match ab* count 10
1) "0"
2) 1) "abeffect"
127.0.0.1:6379> spop keywords
"note"
127.0.0.1:6379> spop keywords
"abeffect"
127.0.0.1:6379> spop keywords
(nil)

list

127.0.0.1:6379> lpush l 1
(integer) 1

127.0.0.1:6379> lpush l 2
(integer) 2

127.0.0.1:6379> llen l
(integer) 2

127.0.0.1:6379> lrange l 0 100
1) "2"
2) "1"

127.0.0.1:6379> lindex l 0
"2"

127.0.0.1:6379> lindex l 1
"1"

127.0.0.1:6379> lindex l -1
"1"

127.0.0.1:6379> rpop l
"1"

127.0.0.1:6379> rpop l
"2"

消息通知

Redis的消息通知有两种实现方式:

  • List类型的LPUSH和RPOP, 左进右出
  • Pub/Sub, 发布订阅模型

BRPOP实现通知

订阅端,超时时间为60秒,阻塞模式。

[abeffect@note ~]$ redis-cli
127.0.0.1:6379> BRPOP list 60
1) "list"
2) "1"
(9.24s)

发送端

[abeffect@note ~]$ redis-cli
127.0.0.1:6379> RPUSH list 1
(integer) 1

Pub/Sub实现通知

示例:

订阅端

127.0.0.1:6379> SUBSCRIBE queue
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "queue"
3) (integer) 1
1) "message"
2) "queue"
3) "1"
1) "message"
2) "queue"
3) "2"
1) "message"
2) "queue"
3) "3"
1) "message"
2) "queue"
3) "a"
1) "message"
2) "queue"
3) "b"
1) "message"
2) "queue"
3) "c"

发送端

127.0.0.1:6379> PUBLISH queue 1
(integer) 1
127.0.0.1:6379> PUBLISH queue 2
(integer) 1
127.0.0.1:6379> PUBLISH queue 3
(integer) 1
127.0.0.1:6379> PUBLISH queue a
(integer) 1
127.0.0.1:6379> PUBLISH queue b
(integer) 1
127.0.0.1:6379> PUBLISH queue c
(integer) 1

相关指令

  • PUBLISH channel msg: 将信息 message 发送到指定的频道 channel
  • SUBSCRIBE channel [channel …] 订阅频道,可以同时订阅多个频道
  • UNSUBSCRIBE [channel …] 取消订阅指定的频道, 如果不指定频道,则会取消订阅所有频道
  • PSUBSCRIBE pattern [pattern …] 订阅一个或多个符合给定模式的频道,每个模式以 * 作为匹配符,比如 it* 匹配所有以 it 开头的频道( it.newsit.blogit.tweets 等等), news.* 匹配所有以 news. 开头的频道( news.itnews.global.today 等等),诸如此类
  • PUNSUBSCRIBE [pattern [pattern …]] 退订指定的规则, 如果没有参数则会退订所有规则
  • PUBSUB subcommand [argument [argument …]] 查看订阅与发布系统状态

代码分析

Readme.md中附有Source code layout部分,简单分析了源代码。

目录结构

  • src: redis实现,c语言编写
  • tests: 单元测试,Tcl语言编写
  • deps: 三方库

server.h

了解一个程序,最简单的方式是了解其数据结构。redis server中所有的配置文件和共享状态都保存在server的结构中,即struct redisServer。主要包括:

  • server.db: 由redis数据库组成的数组,是数据存储的地方
  • server.commands: 命令表
  • server.clients: 链表结构,保存了连接的client
  • server.master: 如果server是slave模型的话,其中保存了一个特殊的client,即master
    struct client {
        int fd;
        sds querybuf;
        int argc;
        robj **argv;
        redisDb *db;
        int flags;
        list *reply;
        char buf[PROTO_REPLY_CHUNK_BYTES];
        ... many other fields ...
    }

client部分定义了一个连接上了的客户端。

  • fd: 文件描述符
  • argc, argv: 启动时的参数
  • querybuf: client发出的请求的buffer
  • reply: server发给client的动态buffer
  • buf: server发给client的静态buffer

其中**argv的值的类型是robj结构体,其定义为:

    typedef struct redisObject {
        unsigned type:4;
        unsigned encoding:4;
        unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
        int refcount;
        void *ptr;
    } robj;

其可以表示任意的redis基础数据类型,如strings, lists, sets, sorted sets等。

其中的type字段表示类型,而refcount字段表示引用记数,避免了同样的对象被多个地方引用时分配多次。

最后其中的ptr指针为对象的实际表示,根据encoding的不同,相同的类型其ptr也可以相差很大。

redisObject在整个redis中使用非常广, 但是为了避免间接访问,在redis中开始逐渐使用了基本的类型而不全部是通过redisObject二次包装。

server.c

这里是redis server程序的入口,main方法就在其中。启动过程中的重要步骤有:

  • initServerConfig(): 设置server结构的默认值
  • initServer(): 分配数据结构的内存, 设置监听的端口等
  • aeMain(): 启动event loop,来监听新的连接

event loop中会周期性的调用两个特殊的函数:

  • serverCron(): 根据server.hz的值,来周期性调用,执行定时任务.
  • beforeSleep(): 每次event loop处理了一些请求后,将要继续进入event loop时,执行.

在server.c中,还有一些别的方法:

  • call(): 在context中,调用client发过来的command
  • activeExpireCycle(): 清除由EXPIRE命令设置的, 达到了time to live时间的key.
  • freeMemoryIfNeeded(): 完成write命令时会调用, 否则根据maxmemory设置的值有可能OOM.

networking.c

定义了client所有的I/O函数,包括masters和slaves角色。

  • createClient(): 生成一个新的client
  • addReply():
  • writeToClient()
  • readQueryFromClient()
  • freeClient():

aof.c

实现了AOF持久化

AOF持久化:实时持久化,持久化文件大,易读,恢复慢。

rdb.c

实现了RDB持久化

RDB持久化:定时持久化,持久化文件小,恢复快。

db.c

通用的指令定义在了db中,如DEL, EXPIRE等。

主要函数如下:

  • lookupKeyRead()和lookupKeyWrite()
  • dbAdd()
  • dbDelete()
  • emptyDb()

object.c

所有和robj结构相关的操作都在里面

replication.c

这个是redis中最复杂的一个文件,建议先熟悉了其它文件,再看这个文件。

这个文件中实现了masterslave角色的代码,同时也实现了SYNCPSYNC命令。

其它文件

  • t_hash.c, t_list.c, t_set.c, t_string.c, t_zset.c中包含了redis数据类型的实现
  • ae.c: 实现了redis event loop
  • sds.c: redis string library,更多见http://github.com/antirez/sds
  • anet.c: 比内核中的代码简单一些的POSIX网络库
  • dict.c: 非阻塞的hash表的实现, 包含了自增的redashes.
  • scripting.c: 实现了Lua脚本。如果对Lua API熟悉的话,此文件很容易懂。
  • cluster.c: 实现了Redis集群。熟悉了redis其它文件后,再来读这个。

参考

评论

发表评论

validate