avatar

mdo

Hello

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

thinkphp3 redis序列化和反序列化

发表于 2026-07-26 更新于 2026-07- 26
作者 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
分享

相关文章

10月 2, 2026

iOS 侧载(Sideloading)工具

AltStore 和 Sideloadly 都是目前最流行的 iOS 侧载(Sideloading)工具。 简单来说,它们的作用就是让你绕过苹果的官方 App Store,直接把第三方的 IPA 安装包(也就是 iOS 的应用安装文件)安装到你的 iPhone 或 iPad 上。 通过这两个工具,你

9月 28, 2026

注册 Chat Participant

将当前服务接入 VS Code 的 AI 聊天窗口是完全可行的。VS Code 在近期的版本中正式推出了 Chat Extensions API(github.copilot.chat),允许第三方扩展将自定义的 LLM 服务或工具注册到 Copilot Chat 面板中。 结合当前 code_ge

9月 23, 2026

php intelephense 设置 if 空格

在 VS Code 中,如果你使用的是 PHP Intelephense 插件,控制 if 关键字后面是否加空格的设置是由其内置的格式化引擎(基于 PSR-12 标准)决定的。 你可以通过修改 VS Code 的 settings.json 来配置这个行为: 1. 开启 if 后面的空格(默认/推荐

下一篇

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

上一篇

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

最近更新

  • 比尔盖茨的智慧:想多赚钱,就每天循环做这3件事
  • iOS 侧载(Sideloading)工具
  • 程序员越想创业,越不要急着动手
  • 注册 Chat Participant
  • 中秋给在外游子的一封信

热门标签

API CodeGeex Coding Cursor DeepSeek Docker Gitkraken Harness Laravel Management

目录

©2026 mdo. 保留部分权利。