关于mysql的max_allowed_packet

max_allowed_packet 定义的是所允许的单条sql语句的大小。

引用官方的说法:http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_allowed_packet


You must increase this value if you are using large BLOB columns or long strings. It should be as big as the largest BLOB you want to use. The protocol limit for max_allowed_packet is 1GB. The value should be a multiple of 1024; nonmultiples are rounded down to the nearest multiple.

解释一下:

  1. 该值的类型为integer,允许的最大值为1G (理论上4个字节的表达能力的上限是4G,也或许协议实现上硬编码了吧)
  2. 该变量的默认值为4MB,一般来讲是够的,如果存储大的BLOB列,可能不够,需要修改该配置
  3. 该值总是1KB的整数倍,最小值为1KB;修改的值应该是1024的整数倍,如果不是整数倍,则会按照小于该值的最接近的那个1024的整数倍进行截断处理,并产生1个warning;(注意: 如果设置的值小于1024,则自动调整为1024)如下:

    其实,将max_allowed_packet 设置为比 net_buffer_length 小的意义不大,所以,这里同样会出现一个warning,只是不会强制将max_allowed_packet修改为大于等于net_buffer_length的
    比较专业的设置该值的方法为:
  4. session的max_allowed_packet是不允许修改的,修改了全局配置对当前session也不会生效的,只有重新连接才能看到变化

记录一次sql优化

一大早到公司,加班一宿的同事就跟我抱怨,说,一个表只有8w条数据,需要更新一列,根据目前进度估算,大概需要3个小时,即使不需要更新,查询每条数据也需要400ms左右;

或许确实配置太低,或许确实表的字段太多,我并没有过于吃惊,但是3小时确实太多,便一起查了一下:

  1. iops并不高,说明内存够用
  2. cpu很高,很可能没有合适的索引,说是用了索引了,还是确认一下吧
  3. 果然搞错了,没有用到索引
  4. 8w条数据,添加索引也应该很快,于是添加了一个索引,耗时不到10s
  5. 执行sql语句,还是很慢,explain显示可能能用到我们刚才添加的索引,但是rows上来看还是全表扫描,为什么?
  6. sql语句大致为: update table1 set bb=1 where aa=123
  7. 现在已经基本可以怀疑aa的类型应该不是int的了,查看表结构,果然aa的类型为varchar
  8. 修改sql语句:update table1 set bb=1 where aa=’123′
  9. 重新开始更新8w条数据,共耗时不到10s

分析:

因为aa是varchar类型,如果要和一个数字比较,势必要对aa进行转换,如此就用不到索引了;这种错误太容易出现了

用shell试图使用大量内存的方法

一条命令:

如果要持续占用一段时间呢?

能否把1G的数据保存到shell的变量中呢?

测试是不行的,可能是太大了吧

关于docker-runc

docker命令行会有一系列的操作容器的方法,如: create 、run、start、exec等等,这些都是通过docker daemon的sock文件由docker daemon实现的,那么docker daemon又是如何实现的呢?

docker-runc就是最终执行的程序,docker-runc也提供了一系列类似于docker的操作,如: create、start、run、exec等,当然,也不完全一样;docker-runc  exec的时候可以指定环境变量、进程的能力等,所以,我们可以直接使用docker-runc来在已有的容器内部执行命令,需要注意的是,docker create时定义的环境变量需要通过选项指定,因为docker-runc不知道这些(奇怪的是,预定义的进程的能力似乎是知道的)

关于容器中进程的继承关系

下图是一个容器中的进程:

  1. 每个容器总是有一个1号进程
  2. 1号进程的父进程在容器中显示为0号进程,而在容器外表现为进程 docker-containerd-shim
  3. 容器中的1号进程不同于宿主机的1号进程那么NB,宿主机的1号进程直接或间接地领导所有其它进程;而容器中的1号进程并不一定直接或间接地领导所有进程,取决于这些进程是否1号进程来创建的,这些进程的父进程死掉后会最终归属到容器内所谓的0号进程而非1号进程,不过,容器中所谓的0号进程并非都是同一个进程
  4. docker top 显示的容器中的进程可能不太全,与是否该进程归属于1号进程没有任何关系;与进程是否最终归属于该容器的管理进程docker-containerd-shim也没有关系,如果是nsenter进入容器,则启动的进程在docker top中是看不到的,虽然该进程在容器中显示的ppid也是0,其实同样是0的ppid却可能不是同一个进程,因为,只要父进程在容器外部,则容器内部显示的ppid就统一为0; 为什么docker top可能看到的不全?docker top是如何实现的呢?参看: https://phpor.net/blog/post/4420

关于PHP Fatal error时获取更多信息

PHP 输出错误时的堆栈情况:

如果需要添加更多的信息,只需要在php_error_cb 函数里面添加就行了

关于C语言之字符串拼接(堆与栈)

且看一个demo:

问题: 如何在errstr信息前面添加一个HOSTNAME 环境变量信息?

  1. 考虑到errstr有可能写到标准错误,也有可能写到标准输出,也有可能根本就不输出,所以,提前输出HOSTNAME 信息显然不合适
  2. 考虑到引用errstr的地方确实不少,最好一开始就拼接HOSTNAME到该errstr 上面去,那么如何拼接呢?

如果是在脚本语言或者带有gc的语言中,完全可以直接在errstr变量前面拼接那个动态获取到的环境变量,如:

但是c中却不能这样,为什么呢?字符串拼接的函数不是有的吗,如:strcat;

事实却并非如此简单,为什么呢?

上面的errstr变量是存储在栈上的,不需要考虑free的问题,原作者考虑到后续一大堆复杂的if分支,如果把errstr放在堆上,处处要考虑是不是该把errstr free掉,得死多少脑细胞啊!

对于HOSTNAME是个运行时的信息,显然没法放到栈上,如果非要和errstr拼接起来的话,errstr必须在堆上,即要动态的malloc,由此可见,对于脚本语言来讲,一个非常简单的逻辑,在C中却如此的麻烦

 

解决办法:

  1. 为了既不需要free,又能拼接字符串,可以定义一个字符数组,如: errstr[255],然后,把需要的信息加进去;为了避免信息太多而不小心越界,可以使用snprintf函数,自动丢弃超长的部分

关于php-fpm reload的优雅实现

我们知道,nginx的reload是不会影响服务的,php-fpm也有reload 功能,而且确实不是restart,但是却是影响服务的,下面给出一种改进方式,是的php-fpm的reload不影响服务

修改源代码:

 

重写init脚本:

 

注意:

  1. 该功能要求linux内核版本较高,需要支持socket的SO_REUSEPORT 特性,我的3.10的内核已经支持了
  2. php-fpm 的pid文件是php-fpm进程来维护的,同时启动多个php-fpm不太方便,所以需要玩一些手段;虽然不指定pid文件参数也可以,但是我不知道如何才能方便地拿到php-fpm的pid信息,所以还是指定了pid参数

关于PHP中的长连接

长连接(也叫持久连接)是啥?

对于PHP来讲,如果是运行在web模式,每次请求完成,php会回收所有需要的申请的资源,但是也可以申请一些特殊的资源,php可以不回收,比如:长连接。

优点: 长连接省却了每次连接的麻烦,也有效减少了timewait的数量

长连接真的好吗?

  1. PHP中没有提供关闭(销毁)长连接的方法,当长连接出现意外时,将无所适从,更常见的是请求超时,都很不方便处理
  2. 同一个服务只能存在一个连接,如果想非阻塞地并发请求同一个服务(相同的目标ip、port),做不到!第二次的pfsockopen得到的是第一次pfsockopen的那个连接,php不会标识一个长连接是否在使用中

鉴于上面两点,感觉长连接真的好鸡肋

关于docker daemon占用大量内存的问题

问题现象:

问题环境:

dockerd 打开的文件数太多了, ~ 4w

类似如下:

和哪个容器相关还是和特定容器无关?

主要和docker-registry的那个容器相关(应该其他容器的个数少,不一定完全正常)

杀掉docker-registry这个容器,dockerd内存使用并未减少

但是相应的文件是不存在的了:

然后,只好重启dockerd试试了:

重启dockerd,问题解决(还好我们的dockerd重启不会影响容器的正常工作)

 

问题的非最终原因(但是也进了一步):

我们对所有的容器都是有监控的,基本手段是使用docker exec 在容器内执行命令,docker-registry这个容器比较特殊,没有我们要执行的命令,除了我们无法获取该容器信息外,没执行一次不存在的命令,dockerd就会在目录/run/docker/libcontainerd/ 下创建一个FIFO文件,并且始终打开着,不关闭(如果要执行的命令存在,则不会存在不关闭的情况)

详细测试结果如下:

对于一个返回值非零的命令,如果使用exec -i 选项,则不会残留打开的文件,如果不使用 exec -i 选项,会残留打开的文件(但是继续跟踪发现,该残留的时间不会太长)

对于一个不存在的命令,不管是否使用 -i 选项,总是会残留打开的文件;

解决办法:

为确保要执行的命令存在,可以使用如下方式:

该方式不但不会产生多余的未关闭文件描述符,而且可以执行一个命令序列,而直接docker exec -it cmd 只能执行一条命令

参考资料: https://github.com/docker/docker/issues/22095