Linux 客户端 for ShadowsocksR

有时抓海外的内容,直接 ssh 连,很快就被干掉,好在还有 ssr,暂时比较稳定。

网上有现成的搭建的方式,不难,不过害怕下次再操作,得重新折腾一遍,做了个 docker 镜像。

地址在此:https://hub.docker.com/r/suren1986/docker-ssr-client/。

使用 Docker 部署 php 开发环境

为什么会想起来用 Docker?

去年听说的 Docker,一开始看介绍没有看明白容器技术能做什么。最近有空再次研究了文档,手动试了试,才弄明白是个啥:一种给需要安装、配置操作系统的人的福音
我很喜欢安装操作系统、配置环境这种事情。无论是桌面还是服务器,每次遇到一个新的操作系统、一个新的机器、一个新的服务环境,从什么都没有一直配置到可以正常工作,特别有成就感。进度条不断前进,或者编译器不断输出看不懂的 log,总是特别有魔力。
但再喜欢的事情做个 100 遍,也就枯燥无谓了,盯着进度条变得枯燥无味。如果遇到些问题更是让人烦躁,如果是以前解决过的问题,又要 Google 的海洋里游荡一番才能解决。
桌面的操作系统,我现在没有这个问题,因为 Mac 有 TimeMachine,换新机器时,让 TimeMachine 自己跑个 1 小时,还是那个熟悉的环境,只是性能变得更强。
服务器的配置就没有那么幸运,每次还是得 apt-get update, apt-get install,各种 /etc/ 下面的 config 文件倒腾一番。好在现在的云主机供应商都有镜像,配置完了做一个镜像,下次开新机器就很方便。不过也有一些缺点:1. 我不会记得自己对操作系统做了什么操作,安装了什么,修改了什么,有些服务的镜像不能直接使用以前的,相同的问题还得再解决一次;2. 如果使用新云主机功能商的服务,所有的努力得再来一次。
Docker 很好的解决了这个问题。所有配置环境的操作,都放在文本文件中。做了哪些事情一目了然,通过版本管理系统,也能记录每次修改。而且,文本文件很小,使用新的供应商提供的服务时,直接下载安装即可,还是一模一样的环境。
Docker 对性能的影响很小。Docker 的容器,相当于一个独立的操作系统,但是它不是虚拟机,它依然依赖宿主机内核提供的服务,在宿主机的内核上,添加所需要的服务,再提供服务给使用者。

为什么用 Docker 搭建开发环境

最主要的原因,是直接上生产环境有风险,通过搭建开发环境练练手..不过用 Docker 的确能解决一些问题
我见过的 PHP 开发模式有 2 种:

  1. 本地开发。每个开发人员,在自己的机器上,安装 nginx, php, mysql, redis, etc,配置一番,然后开始开发,将所有工作结果通过版本控制工具合并。这个模式有个问题:大家的环境不一样,因为配置的原因,同样的代码,在不同机器上的表现是不一样的,给开发和 debug 带来不变;
  2. 集中式开发。用一台服务器,部署一个 nginx, php, mysql, redis, etc,每个开发人员将自己的代码上传到不同的目录,每个人分配一个 vhost,这样大家就能各干各的。优势自然是大家的环境都一样,甚至数据都一样,不太容易出现不同机器上的表现不相同的情况。但也带来了问题;一是服务在一起,数据在一起,会互相影响,比如我要测试一下数据库为空的情况,这时别人就没发工作了;二是开发人员得一直连着网,断网了就没法开发,就算现在网络发达,不会断网了,偶尔得在公司外面开发吧,运维得管理一堆 VPN 账号,也是很麻烦的事情。

现在有了新的思路:用 Docker 搭建开发环境。其实还是第一种,本地开发。用 Docker 解决了大家配置不一致的问题。因为 Docker 的配置文件都是一样的,不需要手动操作,理论上安装出来的环境是一样的。

如何使用 Docker 配置开发环境

首先得安装 docker 和 docker-compose。
docker-compose 让你对 container 做一些简单的配置,并且一个文件就能启动多个不同的服务。

**然后配置 docker-compose.yml ** 找个地方放这个文件,下面是配置示例。

version: '2'

services:

    nginx:
        container_name: nginx
        image: nginx
        ports:
            - 8000:80
        volumes:
            - /path/to/your/php_code:/path/to/your/code/in/container
            - ./nginx/sites/your_site.conf:/etc/nginx/conf.d/your_site.conf
    php:
        container_name: php_fpm
        image: suren_php_fpm:latest

    bg-sync:
        image: cweagans/bg-sync
        volumes:
          - /path/to/your/php_code:/source
        volumes_from:
          - php
        environment:
          - SYNC_DESTINATION=/path/to/your/code/in/container
          - SYNC_MAX_INOTIFY_WATCHES=40000
        privileged: true

    mysql:
        image: mysql:5
        volumes:
            - ./mysql/data:/var/lib/mysql
            - ./mysql/init.d/:/docker-entrypoint-initdb.d/
        environment:
            MYSQL_ROOT_PASSWORD: root_password
            MYSQL_DATABASE: ergedd
            MYSQL_USER: ergedd
            MYSQL_PASSWORD: ergedd

    pma:
        container_name: pma
        image: phpmyadmin/phpmyadmin
        environment:
            - PMA_HOST=mysql
        ports:
            - 8080:80

    redis:
        container_name: redis
        image: redis:3
        volumes:
            - ./redis/data:/data

这个文件的说明可以查看说明文档,有些特殊的解释一下:

  • bg-sync:Docker 在 Mac 下的共享磁盘,性能有问题,导致 php 运行特别慢,通过 bg-sync 折中一下。也有其他的解决方式,但正如 bg-sync 的说明文档所述,bg-sync 是能 work 里面最简单的一种。
  • mysql 中的 /docker-entrypoint-initdb.d/,通过共享文件到这个目录下,可以在 container 第一次被创建时,跑一些初始化脚本,比如倒入初始数据库之类的操作。
  • suren_php_fpm:latest:这是我自己做的 php_fpm 镜像,没有使用原版的,因为需要添加 mysql 插件。

有了这个文件后,运行命令 docker-compose up -d,所有服务就启动起来了,非常方便、整洁。

LaraveSupervisorDashbord

做了一个查看 Laravel 进程状态和异步脚本队列数量的小工具:https://github.com/suren1986/laravel-supervisor-dashboard。

截图

MySQL VS Emoji

最近做了个用户登录功能,发现个别用户的名称无法正确存储到数据库中。

开始以为是前端传错了,将前端传的数据直接存到 Redis 中,花了一天的时间终于再次发现错误数据:用户名后中,Emoji 和后面的字符串都没了。手动跑 SQL 语句,直接抛错。

原因是我这个字段使用的 utf8_unicode_ci 无法存储 Emoji。

还有这种事情,utf8 诶。原来 utf8_unicode_ci 只能存储 3 个字节的字符,无法应对 Emoji 这种需要 4 个字节的字符。

弄清楚问题,解决起来就简单了,修改字符集为 utf8mb4。不过修改的时候又碰到 1 个问题:utf8mb4_bin, utf8mb4_unicode_ci, utf8mb4_general_ci 应该选哪个?

参考 MySQL 的 官方文档, ci 后缀表示不区分大小写(case insensitive),bin 因为通过二进制比较,所以区分大小写:参考 stackoverflow 的这篇 回答 unicode 在对很多语言来说排序和比较是准确的,但是相对慢一些,general 没有实现所有的 Unicode 排序规则,有些语言会有问题,但是快一些,不过到具体实践中,中文没什么差别,速度相差无几,我觉得用 unicode 更好一些,少留坑,

还好我的表数据量不大,线上很快就转换完了,几百万数据就得半夜三更弄了。

改完数据表还没结束,代码中连接数据库时指定的字符集也得改为 utf8mb4

Laravel 5.4 已经在配置文件中,默认使用 utf8mb4,赞!

以前用 Redis 和 Mongo 时,从来没遇到这个问题,不禁想问:为什么 MySQL 不只使用 unicode 字符集?为啥弄这么多字符集?utf8 还没支持全。大概是历史过于悠久,早期的时候,unicode 还没有发展成现在这样吧。

另外 这篇 谈字符集的也很不错。

Laravel queue 重复执行脚本的问题

现象

有时跑一些很耗费时间的异步脚本,会在 artisan queue:listen 时通过 --timeout=3600 设置长一点的时间,以避免超时。
如果: 1. 使用 Redis 存放 queue 信息; 2. 跑了多个进程 artisan queue:listen --timeout=3600;3. 监听同一个 queue;4. Job 的时间超过 60 秒。就会发生同一个脚本被运行多次的情况。

原因

  • 在 Laravel 的框架中,通过 Redis 获取下一个 Job 是通过 Illuminate\RedisQueue 完成的。
  • 获取下个 Job 的函数是 public function pop()
  • 假设 queue 的名称为 default,Laravel 在获取到脚本后,会将它放入 default:reserved 中。 default:reserved 的结构是 sorted hash,被放入的 Job 会被赋予一个权值:time() + this->expire,默认的 this->expire = 60,Laravel 官方的配置中(config/queue.php),也默认了 60 秒。
  • 这个函数在从 Redis queue 中 pop 一个数据前,会将所有过期的 Job 放回到原来的 queue 中。
  • 过期 Job 的来源包括 default:reserved,获取所有 default:reserved 中,权值小于当前时间的 Job。
  • 于是,超过 60 秒没有执行完成的 Job 被放入了 default queue 中,等待下一次执行。

解决方案

解决方法很简单,将 config/queue.php,相应配置的 expire 设置为 null 即可。
因为 Illuminate\RedisQueue 中,如果 expire 为 null,是不会做将将所有过期的 Job 放回到原来的 queue 中的操作的。

其它

  • 这个问题在 5.4 版本还有:5.4 版本的代码略有调整,但是原理相同。
  • 如果 queue 的驱动是 Database,应该也有相同的问题。而且看了 5.2 版本的实现,还没有办法通过设置 expire 为 null 来规避。
  • expire 这个设置,和 queue 的 timeout 的配置,是冲突的。Laravel 在这点的设计上没有考虑清楚:让开发者设置 2 个可能互相矛盾的参数。而且,文档中介绍了 timeout,教开发者避免超时,没有介绍 expire,还给 expire 一个默认的 60 秒设置,让开发者掉坑。打算找个时间去提 issue~