【译】简单介绍通过Predis库在PHP中使用Redis

本文翻译自 《An Introduction to Redis in PHP using Predis》,已得到作者 @Daniel Gafitescu 的允许。原文版权归作者和 sitepoint.com 所有,如果你有原文使用需求请自行联系作者。

Redis 是一个开源的内存数据库服务器,得益于内建的数据类型,Redis 不仅仅只能做简单的 key/value 存储。

Redis 由 @Salvatore Sanfilippo 在 2009 年发布,因为其受欢迎和增长迅速,被很多大公司比如 VMware(后来聘请作者去全职工作)、GitHub、Craigslist、Disqus、Digg、Blizzard、Instagram 等(详见:https://redis.io/)使用。

你可以使用 Redis 做会话(session)处理程序,当你使用负载均衡的分布式服务时特别有用。Redis 也可以用作发布/订阅系统,很优雅的创建一个在线聊天或者实时订购程序。关于 Redis 的文档,所有的命令以及其它信息都能在项目网站 redis.io 上找到。

一直以来都有 Redis 和 Memcache 哪个更好的争论,文章 as the benchmarks show 显示在相同的基础操作上两者不相上下。Redis 比 Memcache 有更多的特性,比如内存存储、磁盘持久化、原子操作、事务以及不用记录每一次变化到磁盘上而是用服务端的数据结构来代替。

在这篇文章中将介绍如何使用 Predis 库所提供的一部分基础但很有用的 Redis 命令。

容易安装

Redis 容易安装,简明的安装说明发表在产品的下载页。从我个人经验来看,如果你运行在 Ubuntu 上而又没有安装 TCL(只需运行 sudo apt-get install tcl 即可安装)将会报错。一旦 Redis 安装完成,你可以运行服务:

Redis 的网站上显示的有很多语言可用的 Redis 客户端,每种语言都有好几个。对于 PHP 来说有 5 个。在本文中我使用的是 Predis 库,但是你可能也需要作为 PHP 模块编译、安装的 phpredis 扩展

译者注:有些人可能在 Predis 库和 phpredis 扩展中难于选择,但两者在一般场景下相差不大,目前都支持 PHP7。phpredis 扩展在性能上可能有一些优势,而 Predis 库源码更加优雅(适合学习、阅读),支持 PHP 新的语法特征,但文档较少(这也是我翻译这篇文章的主要原因)。还有,Predis 也有扩展支持(但作者好像没精力维护了,目前还不支持 PHP7),用于提高性能和提供一下其它的特性。

如果你向我一样在机器上安装的有 Git,你只需克隆 Predis 仓库。否则你就要下载 ZIP 包然后解压。(译者注:可以使用更加简便的 composer 安装:composer require predis/predis

测试一下,创建一个如下内容的 test.php 文件,测试是否能通过 Predis 连接上运行着的 Redis。

当你运行这个脚本,你应该能如愿的看见(输出)信息:"Successfully connected to Redis"。

译者注:这里可能有点小问题,代码不会按照预期捕获 Redis 连接失败的异常,因为在初始化 Redis 客户端类的时候并没有真正的连接,而是在运行第一个命令时才做连接,所以需要额外运行一下 $redis->connect(); 来触发连接操作。详见:https://github.com/predis/predis/issues/61

使用 Redis

在这个章节你将了解众多 Redis 提供的命令的概况。Memcache 也有相似的命令,如果你熟悉 Memcache,这些列表对你来说看起来都不会陌生。 Continue Reading...

Nginx+PHP-FPM 500 504错误简析

最近新配置的 Nginx + PHP-FPM 环境遇到了几次 Nginx 500(Internal Server Error) 和 504(gateway timeout) 错误。就索性把配置修改的过程记录下来,已备后查。

先说一下 500 错误,这个一般是 PHP 代码错了,看一下 PHP 的 error 日志就清楚了。但有时候是 Nginx 和 PHP-FPM 配置不当导致的,比如说上传文件的场景。我们都知道 php.ini 文件里面 post_max_size 和 upload_max_filesize 两个配置项决定了文件上传的大小。但上调配置后发现大文件上传还是会 500,原来是因为 Nginx 也会限制上传文件的大小,有 client_max_body_size 配置项决定。这里还有一个比较重要的参数:client_body_buffer_size。为了节省资源 Nginx 不会把 request body 里面的数据一口气读到内存中,而是分片处理,这个参数就是设置缓存区的大小(一般也不用改),大于缓冲区大小的数据会以一个个小文件(buffer_size)形式存储到 client_body_temp_path,为了避免高并发下缓存文件冲突和一些文件系统限制(比如 ext3 磁盘格式文件夹下一级文件个数限制为 3.2w 个)以及提高性能可根据业务需要设置多级子目录。还需特别注意的是路径要有被 Nginx 进程写的权限,不然又会 500 错误了。

然后是 504 错误,除了业务代码本身超时更多是配置问题。还是先说 PHP 的配置,很多人都知道 PHP 脚本有执行时间限制(php.ini 中的 max_execution_time),但在 PHP-FPM 却是 php-fpm.conf 中的 request_terminate_timeout 配置项控制的。还有就是 max_input_time 配置项,值比较小的话在接收大文件或大数据量 POST 请求的时候可能会超时,一般不用做改动,因为大部分场景都是上游 web server 接收好数据后传给 PHP-FPM 进程的,传输的非常快,几乎不会超时,但也不能一概而论,要看具体业务场景。然后就是 Nginx 的锅了,刚也说到是 Nginx 接收好(或者说是转发)数据后传给下游,在这个承上启下的环节很容易超时。几个比较重要的配置项:fastcgi_send_timeout 控制向下游(这里是 PHP-FPM)发送数据的超时时间,与之对应的一个配置项是 fastcgi_read_timeout,表示读取下游 response 的超时时间,这里需要注意的是要和前面说的 request_terminate_timeout 保持一致(或大于)。还有一个配置项 fastcgi_connect_timeout,表示和下游 FastCGI 建立连接的超时时间,不过这个值就不用设置的太长了,不然下游某个节点挂了就会夯住 Nginx 的处理线程,影响业务。

我所遇到的场景就是上面这些,可能还有其它一些情况,具体问题具体分析应该也很好解决。

参考资料:

https://nginx.org/en/docs/http/ngx_http_core_module.html

https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html

《PHP-CGI 进程 CPU 100% 与 file_get_contents 函数的关系》 http://zyan.cc/tags/request_terminate_timeout/1/

《PHP超时处理全面总结》 https://segmentfault.com/a/1190000000313184

《PHP file upload affected or not by max_input_time?》 https://stackoverflow.com/questions/11387113/php-file-upload-affected-or-not-by-max-input-time

一个 MySQL 用户名长度的坑

今天使用 PHP 连接一个 MySQL 数据库的时候连不上,提示无权限。

因为 MySQL 是在另外一个机房,首先想到的是防火墙的原因,但使用 MySQL-cli 却能正常连接,遂排除这种可能。

又怀疑是 PHP 框架的问题,写了一个简单的测试脚本,主要语句:mysqli_connect(),并打印出错误。运行一下还是不行,错误如下:

真是奇了怪了,这个用户创建时指定的是通配符 '%',而且在别的机器都可以连接。又把测试脚本放在以前一直运行的环境(PHP 7)中,能运行通过。

看来是是环境问题(PHP 5.4)了,回过头来看那句报错,发现用户名好像被截断了(应该是xxx_user),是不是显示的问题,随便改一个用户名试试,同样报错,用户名却没有截断。这时又想了想是不是 MySQL 的版本太高(5.7.10)了。找了个 MySQL 5.5 的环境,创建相同的用户却发现报错了:String 'xxx_user' is too long for user name (should be no longer than 16)。查询 MySQL 文档发现:MySQL user names can be up to 32 characters long (16 characters before MySQL 5.7.8). https://dev.mysql.com/doc/refman/5.7/en/user-names.html

这样看来应该是老版 PHP 的 mysqli 扩展内部限定了用户名的长度,但新版的 MySQL 却可以创建更长的用户名了。知道原因了就很好办了,创建一个短用户名 OK 了。其实也是阴差阳错,因为新库是多应用共有,所以用户名创建的比较长。_(┐「ε:)_

Nginx HTTPS 配置实践

前言

博客使用 HTTPS 已经有一段时间了,最近把当时的配置过程梳理了一遍,希望能对有需要的同学有所帮助。

概念介绍

最近 HTTPS 是越来越流行了,前一阵子的百度,最近的淘宝、知乎,都已经开启了全站 HTTPS 的时代。HTTPS 即 Hypertext Transfer Protocol Secure,简单理解就是在 HTTP 协议上加了一层加密。具体的细节以后弄透彻了再写写。

无利不起早,那么 HTTPS 的好处是什么呢?我个人觉得有以下几点:

  1. 数据传输加密,防止信息被窃取。使用 HTTP 协议时,用户的密码、银行账户、隐私信息等都是在网络上明文传输,很容易被中间人截取。
  2. 防欺诈。当你使用 EV 级别证书(https://www.wosign.com/EVSSL/index.htm)时,浏览器网址显示和一般 SSL 证书不一样。可以对比下:https://www.paypal.com/signin/ 和 https://www.baidu.com/。使用此类证书的网址,主流的浏览器都会在地址栏显示企业的名称(支持中文),增加了网站信任度,进一步防止用户被钓鱼网站欺骗,当然证书的价格也更贵一些。
  3. 防止劫持。这点可以说是国内各大公司下决心支持 HTTPS 的主要原因了。一旦被劫持,用户访问速度会变慢,看到的内容会被篡改,比如商家在百度投放的广告被替换成竞品的广告,淘宝的商品被带上小尾巴(返利链接),甚至直接跳转到x东。这些不光是损害用户的利益,更是直接影响公司收入。
  4. 使用新的技术。比如 SPDY/HTTP2(头部压缩、连接复用、Server Push等) 的基础都是 HTTPS,新协议对移动端 APP 性能提升帮助很大:《双11手淘前端技术: H5性能最佳实践》

当然 HTTPS 不是银弹,不是用了就保险了,安全是一个整体,这是典型的水桶场景,往往一个系统被攻破是在其薄弱的环节。

证书申请

证书申请的大概流程是,首先在自己的服务器使用 openssl 命令(尽量使用高版本的 OpenSSL,除了能规避漏洞,还能使用更高级的密钥加密/交换算法,提升安全性和性能)生成 csr 和 key 文件,命令如下:

如命令参数所示,私钥 key RSA 的加密强度是 2048 位(rsa:2048)。CSR 文件使用了更安全的 sha256(SHA-2)摘要算法(SHA-1算法在2016年将不被证书厂商和现代浏览器支持)。

-subj 参数指定证书申请者的信息,如果不直接使用此参数,也会有交互式的命令提示填写这些参数。此部分具体参数释义:

Country Name (2 letter code): 输入国家地区代码,例如中国的 CN

State or Province Name (full name): 地区省份

Locality Name (eg, city): 城市名称

Organization Name (eg, company): 公司名称

Organizational Unit Name (eg, section): 部门名称

Common Name (e.g. server FQDN or YOUR name): 申请证书域名,如果是泛域名证书,则应该填写 *.example.com

Email Address: 电子邮箱

命令还会提示输入challenge password,这个不是加密证书,而是相当于一个二次认证密码,如果厂商证书支持challenge password,而且你也设置了的话,后续证书操作需要验证这个密码,提高安全性。

生成的 CSR(Certificate Signing Request,即证书签名请求,不是证书)文件包含了证书的主要信息和公钥,同时生成了私钥 key 文件(注意保存)。然后把 CSR 文件提交给 CA 厂商(不用提交 key 文件),一般提交的时候会让你选择服务器类型,本文指选的 Nginx。中间还会有一个域名所有者验证过程,一般几分钟后(根据证书类型不同,时间不一样)就会发一份邮件给你。

邮件附件一般会带有厂商用 CA 私钥签好的证书文件 example.com.cert.pem(包含 域名、CA 信息,key 对应的公钥,CA 的签名)。同时会给你 CA 的证书链 example.com.cert.ca.pem,如果 CA 证书是多个文件(通常包括 1-2 个中间证书和 1 个根证书),那么需要按照证书信任链由下游到上游,把文件合并成 example.com.cert.ca.pem(cat COMODOECCDomainValidationSecureServerCA.crt COMODOECCAddTrustCA.crt AddTrustExternalCARoot.crt > example.com.cert.ca.pem)。不用把根证书放进 CA 证书链文件里面,因为浏览器和操作系统都内置了根证书,也只认自己内置的根证书。

不过现在更流行的是使用 acme.sh 自动申请和续签 Let’s Encrypt 证书,即免费又好用。acme.sh 提供的方式有很多种,大家按需配置即可,我是 DNSPod + Nginx,仅供参考:

证书处理

后面的步骤各个 HTTP Server 就有点不一样了:

tomcat 则比较特别,证书编码格式需要的不是 pem 而是 JKS,得转换一下。

到这里为止出现了好几个文件,可能有点迷糊,这里总结一下

文件名
释义
example.com.csr
Certificate Signing Request,证书签名请求,不是证书
example.com.key.pem
证书私钥(key),不能泄露(即使是对 CA 厂商),和CSR文件成对存在
example.com.cert.pem
自己的域名证书(pem格式),包含 key 对应的公钥
example.com.cert.ca.pem
CA厂商证书链(pem格式)
example.com.cert.full.pem
合并了自己证书和CA证书的完整证书链(pem格式)
example.com.key.cert.pem
合并了key和域名证书的文件(pem格式)
example.com.cert.pfx
pfx/p12格式的完整证书链(包含了key)
example.com.cert.jks
jks格式(JAVA)的完整证书链(包含了key)

对于这几种文件的区别,可以参考:https://www.cnblogs.com/yjmyzz/p/openssl-tutorial.htmlhttps://blog.freessl.cn/ssl-cert-format-introduce/。实际不用那么麻烦,一般证书厂商会提供不同 webserver 的配置文件,如果没有则需要动手一下,或者使用证书厂商提供的转换工具

TLS握手简述

借由这几个证书文件,简单介绍一下 TLS 的握手过程(RSA 密钥交换方式):

客户端
服务端
ClientHello
明文发送握手请求(上报支持的能力)
生成一个随机数r1
ServerHello
明文返回域名证书(example.com.cert.full.pem)
还会返回一个随机数r2
这里服务端也可以要求客户端提供证书进行校验(流程会有所不同,省略)
对证书进行验证,前面有介绍,证书中包含 CA 信息和签名信息,逐级向上验证即可
ClientKeyExchange
验证通过后从域名证书中拿出公钥,然后使用公钥加密PreMasterSecret(随机生成),发送给服务端(密文)
服务端使用私钥(example.com.key.pem)解密得出PreMasterSecret
客户端使用r1、r2、PreMasterSecret生成session key(会话密钥)
服务端也用同样的方式生成session key,生成算法相同(PRF
使用session key进行对称加密

可以看出核心就是 PreMasterSecret 是密文,其它都是明文。如果中间人想要截取就只能替换证书中的公钥(用自己私钥解密),因为证书本身也有完整性签名,就只能替换整个证书,但证书又是一级级到 Root CA 校验,所以 TLS 握手的结果是安全的。

证书逐级验证的过程(来源:https://security.stackexchange.com/questions/56389/ssl-certificate-framework-101-how-does-the-browser-actually-verify-the-validity

才疏学浅,介绍的非常简陋,建议阅读 https://halfrost.com/https_tls1-2_handshake/ 及系列文章

配置详解

下面正式开始 Nginx 的配置详解。 Continue Reading...

Ubuntu 平滑升级 MySQL 到 5.7

最近 MySQL 发布了 5.7 正式版(https://dev.mysql.com/doc/refman/5.7/en/mysql-nutshell.html)。5.7 可以说是里程碑式的版本,提高了性能,并增加了很多新的特性。不管怎么样,先升级再说。

首先我的 Ubuntu 版本是 14.04,已经通过 ppa 的方式安装了 MySQL 5.6,所以首先得去掉这个源。

手工删除的话可以去 /etc/apt/sources.list.d 目录下干掉类似 xxx_mysql-5.6_xxx.list 的文件即可。

然后再安装上官方 apt 源,先在 https://dev.mysql.com/downloads/repo/apt/ 下载最新的 deb 文件,然后使用 dpkg 命令添加源,最后执行安装 MySQL 命令即可:

需要注意的是在添加源那一步的时候,会叫你选择安装 MySQL 哪个应用,这里选择 Server 即可,再选择 MySQL 5.7 后又会回到选择应用的那个界面,此时选择 Apply 即可。

安装完之后还需执行一下 sudo mysql_upgrade -u root -p 更新数据(重要)。

一般这样就完事了,数据什么的都完整无缺。

但一般就是会出现一点小插曲。我在 MySQL 自动启动的时候起不来了:

d96bb863e4ebd54d13a65ff5c

也没看见日志在哪儿,在 /etc/mysql 目录下找日志的时候发现多了个 my.cnf.dpkg-dist 文件,对比一下发现和原来的 my.cnf 不太一样呀,把原来的 my.cnf 备份后使用这个文件替换掉。再次启动 MySQL 果然 OK 了。

这篇文章写得还是没啥营养,权当做个记录吧。等以后水平高了,再来填 MySQL 5.7 新特征及其优化的坑。

update:

关于配置优化,介绍一个好网站:https://www.percona.com/software/mysql-tools

我 1 CPU,1 GB RAM 的配置如下: Continue Reading...

PHP MySQL 持久连接(mysql_pconnect)

先来一段 PHP 连接 MySQL 的经典代码:

这没什么问题,一直这样用。后来看文档发现有个函数 mysql_pconnect(打开一个到 MySQL 服务器的持久连接)。官方文档是这样介绍的:

首先,当连接的时候本函数将先尝试寻找一个在同一个主机上用同样的用户名和密码已经打开的(持久)连接,如果找到,则返回此连接标识而不打开新连接。

其次,当脚本执行完毕后到 SQL 服务器的连接不会被关闭,此连接将保持打开以备以后使用(mysql_close() 不会关闭由 mysql_pconnect() 建立的连接)。

我们都知道建立 MySQL 连接比较消耗资源,要是能复用连接那不是牛逼了。但是 PHP 不是脚本语言吗?运行完了啥都没了,怎么维持持久连接呢?

实践是检验真理的唯一标准,还是试一试吧:

通过 URL 访问 pconnect.php,页面加载完成后,等待 10s ,新开浏览器再次访问。在 MySQL 命令行运行「show full processlist;」查看建立的连接:

2015-10-15_001413

感觉并没有复用呀,再次访问还是重新建立了连接(从 Time 字段的值可以看出),那么试试访问 connect.php :

2015-10-14_230252

很明显脚本执行完成连接就断了,这也是 mysql_connect 的特征,符合常理。但是怎么没有出现 mysql_pconnect 的特征呢?这种时候没办法,只能仔细再看文档,发现这么一句话:

注意,此种连接仅能用于模块版本的 PHP。

Continue Reading...

巧用七牛CDN的镜像功能使百度分享支持HTTPS

update 2020-01

百度分享已经彻底凉了,这类工具也没人愿意做,其实也很简单,自己弄一个就行。

各种社交网站的分享接口可以参考:https://github.com/overtrue/share.js,其实国内也就新浪微博和微信值得分享。

其中微信特殊一点,桌面端浏览器访问需要生成一个二维码,让用户扫码分享,对于移动端如果支持navigator.share则可以调起系统分享菜单(https://web.dev/articles/web-share),对于不支持的浏览器提示通过右上角或者底部分享。

---分割线---

唉,好懒呀,好久没更新博客了。

最近搞了个 HTTPS 证书,像以前一样给博客添加了个百度分享(http://share.baidu.com/)的组件,但发现百度分享不支持 HTTPS(百度分享图标出不来,console 会提示页面有不安全的脚本元素)。看了其它几家也都不支持,搜索了下发现有人建议把百度分享所需的 js 都保存到自己本地就行了。这也是个办法,分享功能大多是抓取这个页面的 title、摘要、图片等然后起调一个页面完成分享,这些都是本地 js 文件能完成的。

看了下从百度分享获取的代码,里面主要加载了这个:http://bdimg.share.baidu.com/static/api/js/share.js,访问了一下果然还是不支持 HTTPS。然后我就天真的把 share.js 上传到了七牛 CDN(七牛是支持 HTTPS的,在空间设置-域名配置里面设置下就行),然而百度分享的图标还是没出来。看了下控制台,卧槽,又加载了一堆 js,作为一个全栈工程师,我非常灵性的瞅了眼代码里面有一段:domain:{staticUrl:"http://bdimg.share.baidu.com/"},原来是模块化加载,把链接替换成七牛 CDN  的链接后有些请求 404 了,我又天真的以为把这几个 js 文件补全就行,但是补完几个,又有几个文件 404 了,我可没耐心一个个文件补齐呀。

作为一个灵性码农,我马上想到七牛不是有个镜像存储功能嘛,设置一发:

20150817003746

故事就这么结束了吗?怎么可能。百度“幺蛾子”还是比较多。百度分享不光是分享功能,还有分享的数据分析。数据哪里来呢?前端埋点统计的呀,原理简单说就是监控分享时的点击事件,发送数据到后台。这其中的核心就是 http://nsclick.baidu.com/v.gif,需要统计的参数和值都以 GET 参数的形式附在链接后面。然后后端再清洗请求日志或者获取请求的时候就直接把数据入库了。但这个统计小图片也不支持 HTTPS。没办法,只能去掉了,方法也很简单,static/api/js/trans/logger.js 文件为空就行(上传个空文件、占个位)。到此才算大功告成。

上面是授之以渔,不想自己弄的,可以直接抓鱼,当然希望你也能明白其中的风险,文件是我这边的(可能有后门,当然我没有),而且哪天我流量没了可能会把文件删了。

update:2016-05-24,七牛账单已经超支,上面的代码地址已经失效,还请见谅。其实了解原理之后自己设置一个即可,或者自己 Nginx 反代一下。

一点后话:一直感觉百度分享没人维护了,在群里打听了下。应该是有人(部门)维护着(至于不支持 HTTPS 那是百度 CDN 的锅),但是现在不流行打社交牌了,公司也不重视这块了,还是 200 亿糯米 O2O 更实在,而且百度首页貌似也不显示搜索结果页面的分享次数了。

 

高工日常——记资讯民大的一次bug修复

作为一个高工,要做好随时修bug的准备。上1s是好好的,下1s就会有问题。

周六的早晨总是那么惬意,一觉睡到自然醒。用微波炉热了一杯高钙低脂纯牛奶,准备边喝边看一下《我是歌手》,上一场张靓颖排倒数第一,不知道这场会选什么歌。刚点开视频,旁边的手机震动了一下,我瞟了一眼,居然是微信的消息,难道是有妹子找我修电脑?点开一看是x凯,简短的一句话:“资讯民大不能查成绩了”。卧槽,不应该呀,这几天都还好好的。马上看了一下报警信息:

QQ截图20150125011724

靠,看来是真的挂了。作为一个要成为高级工程师的男人,不能慌张。先“淡定”的给x凯回复:“我看下”。公司里的大神就是这样回复别人的,说明这只是小case,分分钟搞定。 Continue Reading...

jquery操纵checkbox/radio selected属性的问题

最近在做项目的时候遇到个问题,有一堆checkbox需要根据数据的不同(Ajax获取数据)每次选中的状态也不同。某一个checkbox可能会经历选中->取消选中->再次选中的过程。

很自然的使用了jquery代码:

但发现取消选中后无法再次选中。而审查元素却发现已经是checked="checked"状态,但没有勾上,提交表单也没有选中。

20140914192042

大家可以运行下面这段代码感受一下:

后来一番谷歌和请教别人发现这是jquery的版本问题,jquery从1.9(1.6开始有prop方法)以后需要使用prop方法来设置checked属性。把上面那段代码引入的jquery版本改为1.8.1就可以运行了。详情:https://stackoverflow.com/questions/426258/setting-checked-for-a-checkbox-with-jquery

attr(attribute)和prop(property)翻译为中文时都有属性的意思,但两者是有区别的:

在一些特殊的情况下,attributes和properties的区别非常大。在jQuery1.6之前,.attr()方法在获取一些attributes的时候使用了property值,这样会导致一些不一致的行为。在jQuery1.6中,.prop()方法提供了一中明确的获取property值得方式,这样.attr()方法仅返回attributes。

http://www.javascript100.com/?p=877

http://api.jquery.com/prop/

顺带提一下判断checkbox/radio是否选中的3种方法:

本 Blog 不支持评论,如有疑问或建议请联系我,以完善内容,期望帮助到更多的同学