avatar

mdo

Hello

  • 首页
  • 知识库
  • 归档
  • 标签
  • 关于
主页 thinkphp3 redis序列化和反序列化
文章

thinkphp3 redis序列化和反序列化

发表于 13天前 更新于 13天前
作者 mdo
14~18 分钟 阅读

在老系统重构迁移至 ThinkPHP 6 (TP6) 的过程中,ThinkPHP 3 (TP3) 的 Redis 序列化历史遗留问题是最核心的连环坑之一。

TP3 的 S('key', $value) 缓存方法和底层 Redis 驱动,在存储非字符串(如数组、对象)时,有一套独特的序列化行为。如果不了解它的底层逻辑,新系统重构后就会出现“TP6 读不出 TP3 写入的数据(报反序列化失败)”或者“TP3 读不出 TP6 写入的数据”的现象。

下面为你深度拆解 TP3 的 Redis 序列化机制,以及如何实现新旧系统完美互通与平滑重构。


一、 TP3 的 Redis 序列化与反序列化机制

TP3 的系统内置 Redis 驱动(Think\Cache\Driver\Redis.class.php)在读写时有着非常直接的逻辑:

1. 写入(序列化)

当执行 S('name', $array) 存储一个数组或对象时,TP3 的底层逻辑如下:

  • 如果是数组或对象:直接调用 PHP 原生的 serialize($value) 转为类似 a:2:{s:3:"uid";i:1;...} 的二进制文本字符串。

  • 如果是标量(字符串/数字/布尔值):不进行任何处理,直接原样存入 Redis。

2. 读取(反序列化)

当执行 S('name') 获取数据时,TP3 采用“盲猜/试错法”:

php

// TP3 底层核心伪代码
$value = $this->handler->get($key);
$jsonData = json_decode($value, true);

if (!is_null($jsonData)) {
    return $jsonData; // 1. 优先尝试 JSON(虽然TP3默认不以json存,但框架做了兼容)
}

// 2. 如果不是 JSON,尝试用原生的 unserialize 进行反序列化
$unserializeData = @unserialize($value);
if ($unserializeData !== false || $value === 'b:0;') {
    return $unserializeData;
}

// 3. 如果反序列化也失败,说明原本存的就是普通字符串,原样返回
return $value; 

请谨慎使用此类代码。


二、 为什么重构到 TP6 会冲突?(致命断层)

TP6 的缓存设计改变了规则。TP6 追求严谨性,它的 Redis 缓存驱动默认对所有数据类型(包括普通字符串)强制进行序列化加工:

  • 在 TP6 中存入字符串 "hello",Redis 里实际存的并不是 hello,而是被 PHP 序列化后的 s:5:"hello";(或者是根据配置进行了 JSON / Igbinary 序列化)。

  • 冲突点:

    • 旧读新失败:TP3 去读 TP6 写入的字符串,会因为拿到了被包裹的序列化串而解析混乱。

    • 新读旧失败:TP6 去读 TP3 写入的普通字符串,由于它不满足 PHP 序列化的特定格式(如缺少类型前缀 s:),TP6 内部执行反序列化会直接返回 false 导致无法拿到数据。


三、 完美的重构兼容解决方案

为了配合你之前提到的 unsession 清除逻辑,以及让 TP3 和 TP6 共用同一个 Redis 完美互通,请根据你的业务架构采用对应的处理方案:

方案 A:让 TP6 “降级”去迁就 TP3 的数据格式(最推荐)

如果旧系统的数据极其庞大,无法做大规模数据清洗,最安全的方法是定制或扩展 TP6 的缓存驱动,使其读写行为与 TP3 保持 100% 一致。

  1. 在 TP6 中新建一个自定义的兼容驱动 app\common\cache\driver\LegacyRedis.php:

php

<?php
namespace app\common\cache\driver;

use think\cache\driver\Redis;

/**
 * 专门兼容 TP3 数据的 Redis 驱动
 */
class LegacyRedis extends Redis
{
    /**
     * 写入数据:模拟 TP3 行为(字符串不序列化,数组对象采用原生 PHP 序列化)
     */
    protected function serialize($data): string
    {
        if (is_scalar($data)) {
            return (string)$data; // 标量直接转字符串,不包裹序列化
        }
        return serialize($data); // 数组、对象用标准 PHP 序列化
    }

    /**
     * 读取数据:模拟 TP3 的试错反序列化
     */
    protected function unserialize(string $data)
    {
        // 1. 尝试原生反序列化
        $unserializeData = @unserialize($data);
        if ($unserializeData !== false || $data === 'b:0;') {
            return $unserializeData;
        }

        // 2. 尝试 JSON 解码
        $jsonData = json_decode($data, true);
        if (json_last_error() === JSON_ERROR_NONE) {
            return $jsonData;
        }

        // 3. 兜底返回原样字符串
        return $data;
    }
}

请谨慎使用此类代码。

  1. 随后在 TP6 的 config/cache.php 中,将驱动指向你的自定义类:

php

return [
    'default' => 'redis',
    'stores'  => [
        'redis' => [
            // 指向你刚刚写的自定义兼容驱动类
            'type'   => \app\common\cache\driver\LegacyRedis::class,
            'host'   => '127.0.0.1',
            'port'   => 6379,
            'prefix' => '', // 保持和 TP3 的 C('DATA_CACHE_PREFIX') 一致!
        ],
    ],
];

请谨慎使用此类代码。

方案 B:在重构的 unsession 中手动解决“原生字符串”问题

如果在某些极其敏感的旧核心业务中(比如 Session Token 解析),你不走 TP6 的 Cache 门面,而是直接用原生 PhpRedis 扩展进行解绑,你可以利用 PHP 原生的方法手动处理:

php

// 在 TP6 的业务代码、或者是你重构的 unsession 助手函数中
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);

// 读取 TP3 写入的某个用户 Session 原始串
$rawValue = $redis->get('think:unsession_token_xxxx');

if ($rawValue) {
    // 手动进行 TP3 式的反序列化校验
    $userData = @unserialize($rawValue);
    if ($userData === false && $rawValue !== 'b:0;') {
        // 说明 TP3 当时存的是个没有任何序列化的纯字符串 (如纯明文 UID 或 MD5 串)
        $userData = $rawValue;
    }
    
    // 执行你的 unsession 重构清除逻辑
    $redis->del('think:unsession_token_xxxx');
}

请谨慎使用此类代码。

总结避坑原则

  1. 前缀(Prefix)对齐:TP3 默认没有前缀(除非配置了 DATA_CACHE_PREFIX),而 TP6 默认会带 think:这样的前缀。请确保在 config/cache.php 中把前缀和老系统对齐,或者在代码里补齐。

  2. 退出机制:旧系统 unsession 删完 Redis 键后习惯直接 exit;,如果你迁移到 TP6,请尽量改为 return,让 TP6 的中间件能顺畅走完整个生命周期。

技术
许可协议:  CC BY 4.0
分享

相关文章

7月 26, 2026

完美地解决 TP3 老系统数据的平滑读取

为了彻底、完美地解决 TP3 老系统数据的平滑读取,必须解决两个核心痛点: TP3 的盲猜反序列化机制(标量存原样,数组/对象存原生 serialize,同时兼容 json)。 TP3 与 TP6 的缓存前缀(Prefix)不一致。 以下是为你量身定制的终极落地解决方案,通

7月 26, 2026

thinkphp3 redis序列化和反序列化

在老系统重构迁移至 ThinkPHP 6 (TP6) 的过程中,ThinkPHP 3 (TP3) 的 Redis 序列化历史遗留问题是最核心的连环坑之一。 TP3 的 S('key', $value) 缓存方法和底层 Redis 驱动,在存储非字符串(如数组、对象)时,有一套独特的序列化行为。如果不

7月 24, 2026

Table 空间极易发生哈希冲突并溢出

既然你没有在业务中主动调用 Room,只使用了全局广播,那么这个问题就非常明确了:即使你不用房间功能,think-swoole 只要开启了 WebSocket,底层就会强行初始化并注入一个名为 Room 的驱动组件。 [1] 框架底层默认使用 table 类型来作为 Room 的存储介质。当进行全局

下一篇

Table 空间极易发生哈希冲突并溢出

上一篇

完美地解决 TP3 老系统数据的平滑读取

最近更新

  • 完美地解决 TP3 老系统数据的平滑读取
  • thinkphp3 redis序列化和反序列化
  • Table 空间极易发生哈希冲突并溢出
  • 将监控程序直接跑在云端
  • AI 驱动型 Facebook 群组关键词监控 Chrome 浏览器插件

热门标签

API CodeGeex Gitkraken Management Manticore Premiere Sublime Swoole ThinkPHP ThinkPHP5

目录

©2026 mdo. 保留部分权利。