最近需要基于一个旧的php项目,构建一个架构为ARM的php-fpm镜像(客户的机器是ARM架构的),目前手头只有x86 64的镜像。找了一个同时支持x86 64和ARM架构的php-fpm7.1的基础镜像后,基于以前的项目的Dockerfile改了一下,准备构建ARM的镜像。构建成功后,发现网站的logout会报错:redis delete method is deprecated.
这个php的项目使用的是thinkPHP5.0.10,session的存储驱动是redis,logout会清理session,调用框架的底层代码,如下图所示:
报错的字面意思是redis的delete方法已经过时了,被废弃了,现在删除key的方法是unlink或del。
疑问1:那为什么我们线上的x86 64架构的php-fpm镜像没有这个报错呢?
线上的php-fpm的版本号也是7.1,redis的版本号也是4.0.14。我们当前ARM架构部署的这套代码的php、redis版本号和线上是保持一致的!
那么造成一个报错,一个不报错,一定还有我没注意到的细节差异!
既然报错redis的delete命令已经过时,那么一定是在某个版本淘汰了这个命令,是不是我把redis版本降低到这个特定版本之前的一个版本,就能绕过这个问题呢?
可惜,google了一圈,redis的官网、github仓库都找不到delete是从哪个版本被废弃的。
其实,就算我们找到了这个版本,貌似也无法解答疑问1。
此时,有同事提出直接修改tp5的框架源码,把delete方法改为del方法。其实,这个也能解决我们当下的问题,但是我们基于别人某个特定版本的框架代码,直接修改本地源码的做法,会不会有什么问题呢?诸如:以后维护的人在没有明确文档告知的情况下,看到这个框架的版本号,却发现自己使用官网的这个框架和我们项目的框架表现不一致,会不会觉得很奇怪。话说,还有什么说法?框架升级如果我们擅自改了以前旧框架的源码,升级会不会有问题?框架升级就是无脑的文件覆盖替换吗?纠结了一下,还是不想改框架的源码!
继续,找寻导致结果不同的原因:
FROM laradock/php-fpm:latest-7.1
COPY ./config/sources.list /etc/apt/
COPY ./config/php.ini /usr/local/etc/php
COPY ./config/phpredis-5.3.4 /tmp/phpredis-5.3.4
#添加源码
COPY ./be-code/ /var/www/html/api/
#额外覆盖源码app的配置文件
COPY ./config/app-config/ /var/www/html/api
#安装ps,vim,ping命令,用于日常运维
RUN apt-get update && \
apt-get install -y procps && \
apt-get install -y vim && \
apt-get install -y iputils-ping
#安装redis扩展
WORKDIR /tmp/phpredis-5.3.4
RUN phpize && \
./configure && \
make && make install && \
echo "extension=redis.so" | tee /usr/local/etc/php/conf.d/redis.ini
#修改目录权限
RUN mkdir /var/www/html/api/runtime && \
chmod -R 777 /var/www/html/api
#进入容器后的默认工作目录
WORKDIR /var/www/html/api
以上这段Dockerfile是我们为ARM架构改写的一个Dockerfile,其中因为基础镜像php-fpm没有redis的扩展,所以我们自己装了个phpredis-5.3.4。
然后,我去到线上想看一下x86 64架构下的php-fpm的phpredis的扩展版本号是多少。得到的答案是:3.1.5
www-data@1e2f16b9f897:~/html/api$ php --ri redis
redis
Redis Support => enabled
Redis Version => 3.1.5
Available serializers => php
emmm,这就是差异嘛,那么现在的局面是:
x86 64架构下的php-fpm的镜像使用的redis的版本是4.0.14,phpredis的版本是3.1.5;
ARM架构下的php-fpm的镜像使用的redis版本也是4.0.14,phpredis的版本是5.3.4;
phpredis是php的一个操作redis的扩展,php借助这个扩展和redis进行交互。其实phpredis的版本号和redis的版本号有映射关系,想一下这个道理,redis版本升级,那么相应的也需要升级phpredis的版本号。
那么,如果我们把上面的Dockerfile改一下,降低phpredis的版本号到3.1.5,应该就能解决ARM架构下的logout的报错问题!
改写Dockerfile如下:
FROM laradock/php-fpm:latest-7.1
COPY ./config/sources.list /etc/apt/
COPY ./config/php.ini /usr/local/etc/php
COPY ./config/phpredis-3.1.5 /tmp/phpredis-3.1.5
#添加源码
COPY ./be-code/ /var/www/html/api/
#额外覆盖源码app的配置文件
COPY ./config/app-config/ /var/www/html/api
#安装ps,vim,ping命令,用于日常运维
RUN apt-get update && \
apt-get install -y procps && \
apt-get install -y vim && \
apt-get install -y iputils-ping
#安装redis扩展
WORKDIR /tmp/phpredis-3.1.5
RUN phpize && \
./configure && \
make && make install && \
echo "extension=redis.so" | tee /usr/local/etc/php/conf.d/redis.ini
#修改目录权限
RUN mkdir /var/www/html/api/runtime && \
chmod -R 777 /var/www/html/api
#进入容器后的默认工作目录
WORKDIR /var/www/html/api
果然,问题解决了!!!
疑问2:
但是,还有个细节没被搞清楚,既然你redis的版本是4.0.14,且4.0.14的redis已经废弃了delete方法,下面这段linux交互结果可以佐证:
进入redis容器,设置test这个键,使用delete删除test时,报错了!
root@section9-2nd:/home/wangzhiping# docker exec -it ce4de98f0230 /bin/bash
root@ce4de98f0230:/data# redis-cli
127.0.0.1:6379> set test test
OK
127.0.0.1:6379> delete test
(error) ERR unknown command `delete`, with args beginning with: `test`,
为啥你使用一个phpredis的低版本3.1.5,却可以使用delete命令和redis4.0.14进行交互,切没有报错delete被废弃呢?!
答案是:
你会看到:
这里触及到知识盲区了,怎么你phpstorm作为一个IDE,你还要操作我的代码逻辑?考虑得这么细致,在当前环境redis的版本已经不支持delete方法时,会自动帮我转化为del方法!
疑问3:
什么是phpstorm-stubs?
https://github.com/JetBrains/phpstorm-stubs
官网的简介:
PhpStorm 的 PHP 运行时和扩展头文件。
STUBS 是正常的、语法正确的 PHP 文件,包含所有内置 PHP 内容和大多数标准扩展的函数和类签名、常量定义等。反正给人的感觉是STUBS这个东西为了phpstorm这个IDE服务的,IDE又是为我们服务的,所以本质上还是为我们服务,有哪些服务呢?IDE可以根据这个STUBS的文件定义,来实现代码检查、类型推断、文档弹出。
这也就解释了,为啥IDE这么智能,能自动帮我们替换redis的delete为del方法!
不对,就算STUBS文件是可以影响PHP运行时的文件,但是我们线上环境是没有IDE的,我们的代码又不是跑在IDE里面的,那么应该是不会受STUBS这个redis命令替换逻辑的影响?!
疑问4:到底是谁帮我们替换了delete命令?
那么,唯一的可能就是低版本的phpredis在于高版本的redis服务器进行通信时,实际发送的就是del命令,delete命令被phpredis这个客户端替换了,那么代码应该是在phpredis扩展的代理里面?
下面这个是phpredis的一段源码:
以下面是phpredis的仓库介绍,我们摘抄了一段del的说明:
大致意思是:
如果你的redis server的版本号(不是指你的phpredis的版本号)是>4的,那么可以用unlink来替代del。另外,delete是del的别名,以后高版本的phpredis会 直接移除delete方法,那么此时你直接使用delete就是delete,不存在delete是del的别名的说法了,例如:phpredis5.3.4使用redis的delete方法就是直接报错了!!!所以,phpredis的扩展的大版本号最好和redis server的大版本号保持一致,避免出些莫名其妙的问题。