redis 通信协议,php实现redis协议

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: redis 通信协议,php实现redis协议
  • redis通信协议

redis通信协议由tcp协议进行数据交互,默认端口为6379

请求

Redis 服务器接受命令以及命令的参数。

服务器会在接到命令之后,对命令进行处理,并将命令的回复传送回客户端。

命令格式为:

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

命令本身也作为协议的其中一个参数来发送。

例如:

set a tioncico

tcp原始数据打印为:

string(34) "*3
$3
set
$1
a
$8
tioncico
"

实际数据为:

string(34) "*3CRLF$3CRLFsetCRLF$1CRLFa$8CRLFtioncicoCRLF"

CRLF也就是"\r\n"

redis 响应

当redis服务器接收到请求时,会做出响应,redis会根据不同的命令以及数据,返回不同类型的数据

redis响应类型

通过检查redis服务器返回数据的第一个字节,可确定这个回复是什么类型:

1:"+" 状态回复(status reply)
2:"-" 错误回复(error reply)
3:":" 整数回复(integer reply)
4:"$" 批量回复(bulk reply)
5:"*" 多条批量回复(multi bulk reply)

状态回复

一个状态回复(或者单行回复,single line reply)是一段以 “+” 开始、 “\r\n” 结尾的单行字符串。

例如:

当你set a tioncic之后,redis服务器会给你回复:

+OK\\r\\n

错误回复

错误回复第一个字节以"-"开头:

示例:

当你 st a tioncico 发送不存在的"st"命令时:

-ERR unknown command 'st'\\r\\n

在 “-” 之后,直到遇到第一个空格或新行为止,这中间的内容表示所返回错误的类型。剩余内容为错误内容

除了ERR错误这种通用型错误之外,还有更加特定的错误消息,例如:

-WRONGTYPE Operation against a key holding the wrong kind of value

整数回复

由":"开头,\r\n结尾的消息为整数回复,例如:

:1000\\r\\n

":"到\r\n中间的内容即是整数回复

返回值的唯一限制是该数据必须用64位整数存储

批量回复

服务器使用批量回复来返回二进制安全的字符串,字符串的最大长度为 512 MB 。

例如:

get a (在上面的例子中,已经set a的值为tioncico)

将返回

$8\\r\\ntioncico\\r\\n

服务器发送的内容格式为:

1:第一个字符为"$"
2:随后跟着随机回复内容的长度值8
3:然后跟着CRLF(\\r\\n)
4:然后随后的8个字节,都代表是回复的内容
5:然后再跟着CRLF(\\r\\n)
6:该回复完成

特殊情况

1:当你的数据本身带\r\n时,无需顾虑,直接跟着回复内容的长度值进行获取,里面包含了这个的\r\n,但如果你的tcp客户端是通过\r\n进行分批次获取数据,需要额外的进行判断,组装数据

2:如果当你get一个不存在的键时,redis将会给你返回"$-1\r\n" 来表示该数据不存在,注意,它不是表达空字符串,而是表达它为null

多条批量回复

多条批量回复由"*" 开头,后面的数字代表共有几条回复,例如:

smembers listA

listA是本人已经add过的集合键名,数据如下:

image.png

将回复:

*5
$1
a
$1
1
$12
1
 2
 a
f
$0
$1

由于\r\n太多,这里不处理为字符串显示

可看出:

*符号后面,是5,代表是5条回复
\\r\\n
后面跟着$1,代表是批量回复,1是跟着的字节
\\r\\n
读取1字节的a
\\r\\n
继续读取$1,代表是批量回复,1是跟着的字节
...
读取$0,代表是批量回复,0代表该键值为空
\\r\\n

注意事项

1:多条批量回复,也可能后面跟着一个*多条批量回复,比如在geohash里面:  *1后面跟image.png*2,或者可能*2后面跟着*2,代表这个回复,有2条回复,并且回复里面也有2条回复

2:$0代表着空字符串

3:$-1代表着null

php实现

本人使用swoole client协程客户端,已经实现了redis的通信协议,组件地址:https://github.com/easy-swoole/redis

核心处理代码如下:

代码使用swoole tcp客户端,配置为每次根据\r\n读取,每次读取到\r\n时返回

请求:

public function sendCommand(array $commandList)
{
    $argNum = count($commandList);
    $str = "*{$argNum}\\r\\n";
    foreach ($commandList as $value) {
        $len = strlen($value);
        $str = $str . '$' . "{$len}\\r\\n{$value}\\r\\n";
    }
    return $this->send($str);//tcp流发送,本文不做说明,可查看源码
}
$client->sendCommand(\['sadd', "listA", 'a'\]);

响应:

function recv(): ?Response
{
    /*
     *
        用单行回复,回复的第一个字节将是“+”
        错误消息,回复的第一个字节将是“-”
        整型数字,回复的第一个字节将是“:”
        批量回复,回复的第一个字节将是“$”
        多个批量回复,回复的第一个字节将是“*”
     */
    $result = new Response();
    $str = $this->client->recv($this->timeout);
    if (empty($str)) {
        $result->setStatus($result::STATUS_TIMEOUT);
        return $result;
    }
    /**
     * 去除每行的\\r\\n
     */
    $str = substr($str, 0, -2);
    $op = substr($str, 0, 1);
    $result = $this->opHandel($op, $str);
    return $result;
}
/**
 * 字符串处理方法
 * opHandel
 * @param $op
 * @param $value
 * @return Response
 * @author Tioncico
 * Time: 11:52
 */
protected function opHandel($op, $value)
{
    $result = new Response();
    switch ($op) {
        case '+':
            {
                $result = $this->successHandel($value);
                break;
            }
        case '-':
            {
                $result = $this->errorHandel($value);
                break;
            }
        case ':':
            {
                $result = $this->intHandel($value);
                break;
            }
        case '$':
            {
                $result = $this->batchHandel($value);
                break;
            }
        case "*":
            {
                $result = $this->multipleBatchHandel($value);
                break;
            }
    }
    return $result;
}
/**
 * 状态类型处理
 * successHandel
 * @param $value
 * @return Response
 * @author Tioncico
 * Time: 11:52
 */
protected function successHandel($value): Response
{
    $result = new Response();
    $result->setStatus($result::STATUS_OK);
    $result->setData(substr($value, 1));
    return $result;
}
/**
 * 错误类型处理
 * errorHandel
 * @param $value
 * @return Response
 * @author Tioncico
 * Time: 11:53
 */
protected function errorHandel($value): Response
{
    $result = new Response();
    //查看空格位置
    $spaceIndex = strpos($value, ' ');
    //查看换行位置
    $lineIndex = strpos($value, PHP_EOL);
    if ($lineIndex === false || $lineIndex > $spaceIndex) {
        $result->setErrorType(substr($value, 1, $spaceIndex - 1));
    } else {
        $result->setErrorType(substr($value, 1, $lineIndex - 1));
    }
    $result->setStatus($result::STATUS_ERR);
    $result->setMsg(substr($value, 1));
    return $result;
}
/**
 * int类型处理
 * intHandel
 * @param $value
 * @return Response
 * @author Tioncico
 * Time: 11:53
 */
protected function intHandel($value): Response
{
    $result = new Response();
    $result->setStatus($result::STATUS_OK);
    $result->setData((int)substr($value, 1));
    return $result;
}
/**
 * 批量回复处理
 * batchHandel
 * @param $str
 * @param $timeout
 * @return bool|string
 * @author Tioncico
 * Time: 17:13
 */
protected function batchHandel($str)
{
    $response = new Response();
    $strLen = substr($str, 1);
    //批量回复,继续读取字节
    $len = 0;
    $buff = '';
    if ($strLen == 0) {
        $this->client->recv($this->timeout);
        $response->setData('');
    } else {
        while ($len < $strLen) {
            $strTmp = $this->client->recv($this->timeout);
            $len += strlen($strTmp);
            $buff .= $strTmp;
        }
        $response->setData(substr($buff, 0, -2));
    }
    $response->setStatus($response::STATUS_OK);
    return $response;
}
/**
 * 多条批量回复
 * multipleBatchHandel
 * @param $value
 * @return Response
 * @author Tioncico
 * Time: 14:33
 */
protected function multipleBatchHandel($value)
{
    $result = new Response();
    $len = substr($value, 1);
    if ($len == 0) {
        $result->setStatus($result::STATUS_OK);
        $result->setData(\[\]);
    } elseif ($len == -1) {
        $result->setStatus($result::STATUS_OK);
        $result->setData(null);
    } else {
        $arr = \[\];
        while ($len--) {
            $str = $this->client->recv($this->timeout);
            $str = substr($str, 0, -2);
            $op = substr($str, 0, 1);
            $response = $this->opHandel($op, $str);
            $arr\[\] = $response->getData();
        }
        $result->setStatus($result::STATUS_OK);
        $result->setData($arr);
    }
    return $result;
}
$recv = $client->recv();

注,本文协议内容参考了https://blog.csdn.net/tanswer_/article/details/80846757文章

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
4月前
|
缓存 NoSQL 网络安全
【Azure Redis 缓存】Azure Redis服务开启了SSL(6380端口), PHP如何访问缓存呢?
【Azure Redis 缓存】Azure Redis服务开启了SSL(6380端口), PHP如何访问缓存呢?
|
26天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
37 5
|
1月前
|
存储 NoSQL PHP
PHP与Redis结合使用,提升数据存储性能
随着互联网应用的发展,PHP与Redis的结合成为提升数据存储性能的重要手段。PHP作为流行的服务器端语言,常用于网站开发;Redis作为高性能内存数据库,以其快速读写能力,有效优化数据访问速度,减轻数据库压力。两者结合通过缓存机制显著提升应用响应速度,支持高并发场景下的稳定性和可扩展性。
|
1月前
|
存储 NoSQL 关系型数据库
PHP 使用 Redis
10月更文挑战第22天
35 6
|
2月前
|
设计模式 NoSQL 网络协议
大数据-48 Redis 通信协议原理RESP 事件处理机制原理 文件事件 时间事件 Reactor多路复用
大数据-48 Redis 通信协议原理RESP 事件处理机制原理 文件事件 时间事件 Reactor多路复用
43 2
|
2月前
|
安全 PHP
PHP为协议详解,以及应用
PHP为协议详解,以及应用
33 0
|
2月前
|
缓存 NoSQL 数据处理
原生php实现redis缓存配置和使用方法
通过上述步骤,你可以在PHP项目中配置并使用Redis作为高性能的缓存解决方案。合理利用Redis的各种数据结构和特性,可以有效提升应用的响应速度和数据处理效率。记得在实际应用中根据具体需求选择合适的缓存策略,如设置合理的过期时间,以避免内存过度消耗。
58 0
|
3月前
|
消息中间件 NoSQL Go
PHP转Go系列 | ThinkPHP与Gin框架之Redis延时消息队列技术实践
【9月更文挑战第7天】在从 PHP 的 ThinkPHP 框架迁移到 Go 的 Gin 框架时,涉及 Redis 延时消息队列的技术实践主要包括:理解延时消息队列概念,其能在特定时间处理消息,适用于定时任务等场景;在 ThinkPHP 中使用 Redis 实现延时队列;在 Gin 中结合 Go 的 Redis 客户端库实现类似功能;Go 具有更高性能和简洁性,适合处理大量消息。迁移过程中需考虑业务需求及系统稳定性。
|
24天前
|
前端开发 关系型数据库 MySQL
PHP与MySQL动态网站开发实战指南####
【10月更文挑战第21天】 本文将深入浅出地探讨如何使用PHP与MySQL构建一个动态网站,从环境搭建到项目部署,全程实战演示。无论你是编程新手还是希望巩固Web开发技能的老手,都能在这篇文章中找到实用的技巧和启发。我们将一起探索如何通过PHP处理用户请求,利用MySQL存储数据,并最终呈现动态内容给用户,打造属于自己的在线平台。 ####
32 0
|
15天前
|
存储 关系型数据库 MySQL
PHP与MySQL动态网站开发:从基础到实践####
本文将深入探讨PHP与MySQL的结合使用,展示如何构建一个动态网站。通过一系列实例和代码片段,我们将逐步了解数据库连接、数据操作、用户输入处理及安全防护等关键技术点。无论您是初学者还是有经验的开发者,都能从中获益匪浅。 ####