编辑
2024-06-06
💌中间键
00
请注意,本文编写于 377 天前,最后修改于 225 天前,其中某些信息可能已经过时。

目录

4.1 客户端通信协议
1.发送命令格式
4.3 Python客户端redis-py
4.3.1 获取redis-py
4.3.2 redis-py的基本使用方法
4.3.3 redis-py中Pipeline的使用方法
4.3.4 redis-py中的Lua脚本使用方法

4.1 客户端通信协议

几乎所有的主流编程语言都有Redis的客户端(http://redis.io/clients),

不考虑Redis非常流行的原因,如果站在技术的角度看原因还有两个:

  • 第一,客户端与服务端之间的通信协议是在TCP协议之上构建的。
  • 第二,Redis制定了RESP(REdis Serialization Protocol,Redis序列化协议)实现客户端与服务端的正常交互,这种协议简单高效,既能够被机器解析,又容易 被人类识别。

例如客户端发送一条set hello world命令给服务端,按照RESP 的标准,客户端需要将其封装为如下格式(每行用\r\n分隔):

*3 $3 SET $5 hello $5 world

这样Redis服务端能够按照RESP将其解析为set hello world命令,执行后 回复的格式如下:

+OK

可以看到除了命令(set hello world)和返回结果(OK)本身还包含了 一些特殊字符以及数字,下面将对这些格式进行说明。

1.发送命令格式

RESP的规定一条命令的格式如下,CRLF代表"\r\n"。

*<参数数量> CRLF $<参数1的字节数量> CRLF <参数1> CRLF ... $<参数N的字节数量> CRLF <参数N> CRLF

依然以set hell world这条命令进行说明。 参数数量为3个,因此第一行为:

*3 参数字节数分别是355,因此后面几行为: $3 SET $5 hello $5 world

有一点要注意的是,上面只是格式化显示的结果,实际传输格式为如下 代码,整个过程如图4-1所示:

*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n

2.返回结果格式 Redis的返回结果类型分为以下五种,如图4-2所示:

  • 状态回复:在RESP中第一个字节为"+"。
  • 错误回复:在RESP中第一个字节为"-"。
  • 整数回复:在RESP中第一个字节为":"。
  • 字符串回复:在RESP中第一个字节为"$"。
  • 多条字符串回复:在RESP中第一个字节为"*"。

image.png

我们知道redis-cli只能看到最终的执行结果,那是因为redis-cli本身就是 按照RESP进行结果解析的,所以看不到中间结果,redis-cli.c源码对命令结 果的解析结构如下:

static sds cliFormatReplyTTY(redisReply *r, char *prefix) { sds out = sdsempty(); switch (r->type) { case REDIS_REPLY_ERROR: // 处理错误回复 case REDIS_REPLY_STATUS: // 处理状态回复 case REDIS_REPLY_INTEGER: // 处理整数回复 case REDIS_REPLY_STRING: // 处理字符串回复 case REDIS_REPLY_NIL: // 处理空 case REDIS_REPLY_ARRAY: // 处理多条字符串回复 return out; }

例如执行set hello world,返回结果是OK,并不能看到加号:

127.0.0.1:6379> set hello world OK

为了看到Redis服务端返回的“真正”结果,可以使用nc命令、telnet命 令、甚至写一个socket程序进行模拟。

下面以nc命令进行演示,首先使用 nc127.0.0.16379连接到Redis:

nc 127.0.0.1 6379

状态回复:set hello world的返回结果为+OK:

set hello world +OK

错误回复:由于sethx这条命令不存在,那么返回结果就是"-"号加上错误消息:

sethx -ERR unknown command 'sethx'

整数回复:当命令的执行结果是整数时,返回结果就是整数回复,例如 incr、exists、del、dbsize返回结果都是整数,例如执行incr counter返回结果 就是“:”加上整数:

incr counter :1

字符串回复:当命令的执行结果是字符串时,返回结果就是字符串回复。

例如get、hget返回结果都是字符串,例如get hello的结果为

“$5\r\nworld\r\n”: get hello $5 world

多条字符串回复:当命令的执行结果是多条字符串时,返回结果就是多条字符串回复。

例如mget、hgetall、lrange等命令会返回多个结果,例如下面操作:

首先使用mset设置多个键值对:

mset java jedis python redis-py +OK

然后执行mget命令返回多个结果,第一个*\2代表返回结果的个数,后面的格式是和字符串回复一致的:

mget java python *2 $5 252 jedis $8 redis-py

有一点需要注意,无论是字符串回复还是多条字符串回复,如果有nil值,那么会返回$-1。

例如,对一个不存在的键执行get操作,返回结果为:

get not_exist_key $-1

如果批量操作中包含一条为nil值的结果,那么返回结果如下:

mget hello not_exist_key java *3 $5 world $-1 $5 jedis

有了RESP提供的发送命令和返回结果的协议格式,各种编程语言就可以利用其来实现相应的Redis客户端,后面两节将介绍Java和Python两个编程 语言的Redis客户端。

4.3 Python客户端redis-py

4.3.1 获取redis-py

Redis官网提供了很多Python语言的客户端 (http://redis.io/clients#python),但最被广泛认可的客户端是redis-py。 redis-py需要Python2.7以上版本,有关Python的安装本书不会介绍,主要介绍 一下如何获取安装redis-py,方法有三种:

第一,使用pip进行安装:

pip install redis

第二,使用easy_install进行安装:

easy_install redis

第三,使用源码安装:以2.10.5版本为例子进行说明,只需要如下四 步:

wget https:// github.com/andymccurdy/redis-py/archive/2.10.5.zip unzip redis-2.10.5.zip cd redis-2.10.5 #安装redis-py python setup.py install

4.3.2 redis-py的基本使用方法

redis-py的使用方法也比较简单,下面将逐步骤介绍。

  1. 导入依赖库:
import redis
  1. 生成客户端连接:需要Redis的实例IP和端口两个参数:
client = redis.StrictRedis(host='127.0.0.1', port=6379)
  1. 执行命令:redis-py的API保留了Redis API的原始风格,所以使用起来不会有不习惯的感觉:
# True client.set(key, "python-redis") # world client.get(key)

整个实例代码如下:

python
import redis client = redis.StrictRedis(host='127.0.0.1', port=6379) key = "hello" setResult = client.set(key, "python-redis") print setResult value = client.get(key) print "key:" + key + ", value:" + value 输出结果为: True key:hello, value:python-redis

下面代码给出redis-py操作Redis五种数据结构的示例,输出结果写在注释中:

#1.string #输出结果:True client.set("hello","world") #输出结果:world client.get("hello") #输出结果:1 client.incr("counter") #2.hash client.hset("myhash","f1","v1") client.hset("myhash","f2","v2") #输出结果:{'f1': 'v1', 'f2': 'v2'} client.hgetall("myhash") #3.list client.rpush("mylist","1") client.rpush("mylist","2") client.rpush("mylist","3") #输出结果:['1', '2', '3'] client.lrange("mylist", 0, -1) #4.set client.sadd("myset","a") client.sadd("myset","b") client.sadd("myset","a") #输出结果:set(['a', 'b']) client.smembers("myset") #5.zset client.zadd("myzset","99","tom") client.zadd("myzset","66","peter") client.zadd("myzset","33","james") #输出结果:[('james', 33.0), ('peter', 66.0), ('tom', 99.0)] client.zrange("myzset", 0, -1, withscores=True)

4.3.3 redis-py中Pipeline的使用方法

redis-py支持Redis的Pipeline功能,下面用一个简单的示例进行说明。

  1. 引入依赖,生成客户端连接:
import redis client = redis.StrictRedis(host='127.0.0.1', port=6379)
  1. 生成Pipeline:注意client.pipeline包含了一个参数,如果
transaction=False代表不使用事务: pipeline = client.pipeline(transaction=False)
  1. 将命令封装到Pipeline中,此时命令并没有真正执行:
pipeline.set("hello","world") pipeline.incr("counter")
  1. 执行Pipeline:
#[True, 3] result = pipeline.execute()

和4.2.4小节一样,将用redis-py的Pipeline实现mdel功能:

import redis def mdel( keys ): client = redis.StrictRedis(host='127.0.0.1', port=6379) pipeline = client.pipeline(transaction=False) for key in keys: print pipeline.delete(key) return pipeline.execute();

4.3.4 redis-py中的Lua脚本使用方法

redis-py中执行Lua脚本和redis-cli十分类似,redis-py提供了三个重要的 函数实现Lua脚本的执行:

eval(String script, int keyCount, String... params) script_load(String script) evalsha(String sha1, int keyCount, String... params:

eval函数有三个参数,分别是:

  • script:Lua脚本内容。
  • keyCount:键的个数。
  • params:相关参数KEYS和ARGV。 以一个最简单的Lua脚本为例进行说明:
return redis.call('get',KEYS[1])

在redis-py中执行,方法如下:

import redis client = redis.StrictRedis(host='127.0.0.1', port=6379) script = "return redis.call('get',KEYS[1])" #输出结果为world print client.eval(script,1,"hello")

script_load和evalsha函数要一起使用,首先使用script_load将脚本加载到 Redis中,代码如下:

scriptSha = client.script_load(script)

evalsha函数用来执行脚本的哈希值,它需要三个参数:

  • scriptSha:脚本的SHA1。
  • keyCount:键的个数。
  • params:相关参数KEYS和ARGV。 执行效果如下:
print jedis.evalsha(scriptSha, 1, "hello");

完整代码如下:

python
import redis client = redis.StrictRedis(host='127.0.0.1', port=6379) script = "return redis.call('get',KEYS[1])" scriptSha = client.script_load(script) print client.evalsha(scriptSha, 1, "hello");

本文作者:Eric

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!