容器中程序的信号捕捉

一、问题描述

项目中使用了 argo 在 kubernetes 集群中做工作流的调度。argo 提供了工作流的停止功能,其原理大致是检查正在运行的 Pod,向该 Pod 中的 wait 容器发送 USR2 信号,wait 容器收到 USR2 信号后,在主机上的调用 docker kill --signal TERM main_container_id 来停止我们的程序容器, 如果 10s 后容器还未停止,则发送 SIGKILL 来强制终止。但是我在实现 argo 工作流中调度 tfjob 时出现了一些问题。

argo_scheduler_tfjob

在argo停止工作流时,正在运行的 step2 中的 manager 监听了 TERM 信号,以便在工作流停止时同步停止 tfjob。但是事实情况却是 manager 退出了,但是没有收到任何的 TERM 信号。

二、问题剖析

检查这个问题的第一步是弄清楚 docker kill 背后发生了什么,官网的资料中有以下的描述:

Note: ENTRYPOINT and CMD in the shell form run as a subcommand of /bin/sh -c, which does not pass signals. This means that the executable is not the container’s PID 1 and does not receive Unix signals.

当我们用 sh 执行一段 shell script 时,在 shell script 中的可执行文件的 PID 不是1,并且 sh 也不会帮忙转发 TERM 信号,导致我们的可执行文件无法接收到终止信号,并执行清理逻辑。

我们的 manager 确实是用了一段 shell script 来启动的,可能就是因为这个原因导致无法收到 TERM 信号。

三、问题复现

我写了一段很简单的 go 程序,监听了 TERM 信号,然后打印一段文字。

package main

import (
    "log"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT)

    s, ok := <-sigs
    if !ok {
        log.Println("信号接收出错")
        os.Exit(1)
    }

    log.Println("收到信号:", s.String())
}

我的 Dockerfile 如下:

FROM alpine:latest
LABEL maintainr="jiangpengfei <jiangpengfei12@gmail.com>"

COPY main /usr/bin/main
COPY run.sh /usr/bin/run.sh
RUN chmod +x /usr/bin/main && chmod +x /usr/bin/run.sh

CMD ["sh", "-c", "/usr/bin/run.sh"]

run.sh 如下:

#!/bin/sh
/usr/bin/main

执行这个容器后,查看容器内的进程:

PID   USER     TIME  COMMAND
    1 root      0:00 {busybox} ash /usr/bin/run.sh
    6 root      0:00 /usr/bin/main
   12 root      0:00 sh
   17 root      0:00 ps

可以发现,run.sh 是 PID 为1, main 程序是6。此时我们使用 docker kill --signal TERM main_container_id 来停止容器,发现确实是没有反应的。因为 TERM 信号会发送给 PID 为 1 的进程。同时也因为 sh 不响应 TERM 信号,也不会转发该信号给子进程,所以容器也不会退出。如果我们使用 docker stop 退出的话,会发现很慢,这是因为 docker stop 会尝试先用 TERM 信号来终止进程,一段时间后发现没有退出的话再使用 KILL 信号。

四、解决方案

这个问题的解决方案有很多,要么让我们的程序进程成为 PID 1,要么让 PID 为 1 的进程转发这个 TERM 信号给我们的子进程。

方法一: 在 shell script 中使用 exec

将我们的 run.sh 改成如下:

#!/bin/sh
exec /usr/bin/main

然后再查看容器内的进程列表:

PID   USER     TIME  COMMAND
    1 root      0:00 /usr/bin/main
   11 root      0:00 sh
   16 root      0:00 ps

可以发现,main 进程的PID 是 1, 我们使用 docker kill --signal TERM main_container_id 来杀死进程,出现如下打印语句:

2020/01/17 23:46:24 收到信号: terminated

可见,exec 可以让我们的 main 进程成为 PID 为 1, 关于 exec 的作用描述如下:

The exec() family of functions replaces the current process image with a new process image.

即使用新进程的镜像替换当前进程的镜像数据,可以理解为exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。这样我们的 main 进程就顺利成章的替换了 sh 进程成为 PID 为 1 的进程了。

方法二: 直接使用 main 作为镜像入口

这是最简单的方法了,但是很多时候会有限制,因为我们希望在 shell script 中写一些逻辑来调用程序。

方法三: 借助第三方程序

一些第三方的程序专门提供了这样的作用,以它们作为启动的入口,这些第三方程序会 watch 所有它产生的子进程,在这些子进程退出后自动退出,并且在其收到 TERM 信号后发送给子进程。

这里我们用 smell-baron 这个应用作为例子

修改 Dockerfile:

FROM alpine:latest
LABEL maintainr="jiangpengfei <jiangpengfei12@gmail.com>"

COPY main /usr/bin/main
COPY run.sh /usr/bin/run.sh
RUN chmod +x /usr/bin/main && chmod +x /usr/bin/run.sh
RUN wget -O /usr/bin/smell-baron https://github.com/insidewhy/smell-baron/releases/download/v0.4.2/smell-baron.musl && chmod +x /usr/bin/smell-baron

CMD ["/usr/bin/smell-baron", "/usr/bin/run.sh"]

查看容器内的进程:

PID   USER     TIME  COMMAND
    1 root      0:00 /usr/bin/smell-baron /usr/bin/run.sh
    6 root      0:00 /usr/bin/main
   14 root      0:00 sh
   19 root      0:00 ps

使用 docker kill 发现 main 收到了 TERM 信号。

1.Multiple commands can be run, smell-baron will exit when all the watched processes have exited.

2.Whether a spawned process is watched can be configured.

3.smell-baron can be told to signal all child processes on termination, this allows it to cleanly deal with processes that spawn a subprocess in a different process group then fail to clean it up on exit.

linux ip 命令的使用

简介

linux 下的 ip 命令是一个很强大的工具,在这之前,我通常只会使用 ifconfig 命令来查看本机网络接口和 ip 地址等等。或者 netstat 命令查看端口占用等等。ip 命令属于 iproute2 套件中的一个命令,关于 iproute2 和 linux net-tools 中的命令对比如下(图片来源:https://linux.cn/article-3144-1.html):

net-tools vs iproute2

可以看出,除了部分 netstat 命令用 ss 来替代,其它都可以用 ip 命令替代。并且,iproute2 已经是大多数 linux 发行版默认安装了,而 net-tools 则需要另外安装。

ip 命令可以分为下面几个模块:

  • 网卡设备相关: ip link
  • 网卡地址相关: ip addr
  • 路由表相关: ip route
  • arp 相关: ip neigh

下面会列出一些常用的操作,最好在虚拟机中操作,防止影响个人机器。

ip link

查看 ip link 的帮助

$ ip link help

查看网络接口

$ ip link list

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:8a:fe:e6 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 08:00:27:15:ee:5c brd ff:ff:ff:ff:ff:ff

这里显示了三个网络接口,lo代表的本机的回环网卡,eth0eth1 分别是两个网卡

添加网络接口

$ sudo ip link add link eth0 mydev type bridge

这里添加了一个网桥,连接在 eth0 上。使用 ip link list 查看可以发现多了下面一个设备

6: mydev: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 5e:0c:36:7b:ce:0d brd ff:ff:ff:ff:ff:ff

删除网络接口

$ sudo ip link delete link dev mydev

关闭网络接口

$ sudo ip link set eth1 down

打开网络接口

$ sudo ip link set eht1 up

ip addr

查看帮助

$ ip addr help

查看网络地址

$ ip addr list

查看某一个网络接口的地址

$ ip addr show eth1

添加 ip 地址

$ sudo ip addr add 192.168.31.131/24 dev eth1

查看 eth1 的地址

$ ip addr show eth1

3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:15:ee:5c brd ff:ff:ff:ff:ff:ff
    inet 192.168.31.77/24 brd 192.168.31.255 scope global noprefixroute dynamic eth1
       valid_lft 42769sec preferred_lft 42769sec
    inet 192.168.31.131/24 scope global secondary eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe15:ee5c/64 scope link 
       valid_lft forever preferred_lft forever

我们也可以 ping 一下这个地址:

$ ping 192.168.31.131

PING 192.168.31.131 (192.168.31.131) 56(84) bytes of data.
64 bytes from 192.168.31.131: icmp_seq=1 ttl=64 time=0.109 ms
64 bytes from 192.168.31.131: icmp_seq=2 ttl=64 time=0.155 ms

删除 ip 地址

$ sudo ip addr del 192.168.31.131/24 dev eth1

改变设备地址的配置

这里有一篇很好的文章: understanding ip addr change and ip addr replace commands

为了演示的方便,我添加了一个网卡设备

$ sudo ip link add link eth0 name dummy0 type dummy

为它分配地址:

$ sudo ip addr add 192.168.31.132/24 dummy0
$ ip addr show dummy0

5: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 9e:dc:6e:0b:70:99 brd ff:ff:ff:ff:ff:ff
    inet 192.168.31.132/24 scope global dummy0
       valid_lft forever preferred_lft forever

如果你想要修改 valid_lftpreferred_lft 配置,可以使用 ip change命令:

$ sudo ip addr change 192.168.31.132 dev dummy0 preferred_lft 300 valid_lft 300
$ ip addr show dummpy0

5: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 9e:dc:6e:0b:70:99 brd ff:ff:ff:ff:ff:ff
    inet 192.168.31.132/24 scope global dynamic dummy0
       valid_lft 299sec preferred_lft 299sec

ip route

查看帮助

$ ip route help

查看路由

$ ip route list

添加路由

添加一条普通的路由

$ sudo ip route add 39.156.0.0/16 via 192.168.31.133 dev dummy0

添加默认路由

$ sudo ip route add default via 192.168.31.133 dev dummy0

删除路由

删除默认路由

$ sudo ip route del default via 192.168.31.133 dev dummy0

删除普通路由

$ sudo ip route del 39.156.0.0/16 via 192.168.31.133 dev dummy0 

**查看一个 ip 地址的路由包来源

$ ip route get 39.156.69.79

39.156.69.79 via 10.0.2.2 dev eth0 src 10.0.2.15 
    cache 

ip neigh

查看帮助

$ ip neigh help

查看同一个网络的邻居设备

$ ip neigh show

192.168.31.1 dev eth1 lladdr 34:ce:00:2e:88:b9 STALE
10.0.2.2 dev eth0 lladdr 52:54:00:12:35:02 REACHABLE
10.0.2.3 dev eth0 lladdr 52:54:00:12:35:03 STALE

用c写php扩展的笔记

编写php扩展的步骤:

1.使用php-src中ext文件夹中的ext_skel生成项目框架
2.编辑config.m4,将其中三句话前面的dnl删除,改成下面这样。

PHP_ARG_WITH(md2pic, for md2pic support,
Make sure that the comment is aligned:
[  --with-md2pic             Include md2pic support])

3.执行phpize
4.执行./configure
5.使用make编译
6.使用make install安装扩展
7.将扩展加入php.ini中
8.使用php -m检查扩展是否正常加载

关于config.m4

config.m4相当于一个构建系统,在php扩展的开发中,我的理解就是它可以用来配置lib,include,flags等编译时的属性以及其他的一些功能。这里给出一个配置了其他的lib和include信息的config.m4文件

dnl $Id$
dnl config.m4 for extension md2pic

dnl Comments in this file start with the string 'dnl'.
dnl Remove where necessary. This file will not work
dnl without editing.

dnl If your extension references something external, use with:

PHP_ARG_WITH(md2pic, for md2pic support,
Make sure that the comment is aligned:
[  --with-md2pic             Include md2pic support])

dnl Otherwise use enable:

dnl PHP_ARG_ENABLE(md2pic, whether to enable md2pic support,
dnl Make sure that the comment is aligned:
dnl [  --enable-md2pic           Enable md2pic support])

if test "$PHP_MD2PIC" != "no"; then
  dnl Write more examples of tests here...

  dnl # --with-md2pic -> check with-path
  dnl SEARCH_PATH="/usr/local /usr"     # you might want to change this
  dnl SEARCH_FOR="/include/md2pic.h"  # you most likely want to change this
  dnl if test -r $PHP_MD2PIC/$SEARCH_FOR; then # path given as parameter
  dnl   MD2PIC_DIR=$PHP_MD2PIC
  dnl else # search default path list
  dnl   AC_MSG_CHECKING([for md2pic files in default path])
  dnl   for i in $SEARCH_PATH ; do
  dnl     if test -r $i/$SEARCH_FOR; then
  dnl       MD2PIC_DIR=$i
  dnl       AC_MSG_RESULT(found in $i)
  dnl     fi
  dnl   done
  dnl fi
  dnl
  dnl if test -z "$MD2PIC_DIR"; then
  dnl   AC_MSG_RESULT([not found])
  dnl   AC_MSG_ERROR([Please reinstall the md2pic distribution])
  dnl fi

  dnl # --with-md2pic -> add include path

  PHP_ADD_INCLUDE(src/libMultiMarkdown/include)

  LIBNAME=gd # you may want to change this
  LIBSYMBOL=gdImageCreate # you most likely want to change this 

  PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
  [
    PHP_ADD_LIBRARY_WITH_PATH(gd,"/usr/lib", MD2PIC_SHARED_LIBADD)
    PHP_ADD_LIBRARY_WITH_PATH(curl,"/usr/lib", MD2PIC_SHARED_LIBADD)
    PHP_ADD_LIBRARY_WITH_PATH(png,"/usr/lib", MD2PIC_SHARED_LIBADD)
    PHP_ADD_LIBRARY_WITH_PATH(z,"/usr/lib", MD2PIC_SHARED_LIBADD)
    PHP_ADD_LIBRARY_WITH_PATH(jpeg,"/usr/lib", MD2PIC_SHARED_LIBADD)
    PHP_ADD_LIBRARY_WITH_PATH(freetype,"/usr/lib", MD2PIC_SHARED_LIBADD)
    PHP_ADD_LIBRARY_WITH_PATH(m,"/usr/lib", MD2PIC_SHARED_LIBADD)
    AC_DEFINE(HAVE_MD2PICLIB,1,[ ])
  ],[
    AC_MSG_ERROR([wrong md2pic lib version or lib not found])
  ],[

  ])


  dnl
  PHP_SUBST(MD2PIC_SHARED_LIBADD)
  PHP_NEW_EXTENSION(md2pic, [md2pic.c \
  src/libMultiMarkdown/aho-corasick.c \
  src/libMultiMarkdown/beamer.c \
  src/libMultiMarkdown/char.c \
  src/libMultiMarkdown/critic_markup.c \
  src/libMultiMarkdown/d_string.c \
  src/libMultiMarkdown/epub.c \
  src/libMultiMarkdown/file.c \
  src/libMultiMarkdown/html.c \
  src/libMultiMarkdown/latex.c \
  src/libMultiMarkdown/lexer.c \
  src/libMultiMarkdown/memoir.c \
  src/libMultiMarkdown/miniz.c \
  src/libMultiMarkdown/mmd.c \
  src/libMultiMarkdown/object_pool.c \
  src/libMultiMarkdown/opendocument-content.c \
  src/libMultiMarkdown/opendocument.c \
  src/libMultiMarkdown/scanners.c \
  src/libMultiMarkdown/stack.c \
  src/libMultiMarkdown/textbundle.c \
  src/libMultiMarkdown/token_pairs.c \
  src/libMultiMarkdown/token.c \
  src/libMultiMarkdown/transclude.c \
  src/libMultiMarkdown/rng.c \
  src/libMultiMarkdown/uuid.c \
  src/libMultiMarkdown/writer.c \
  src/libMultiMarkdown/zip.c \
  src/libMultiMarkdown/parser.c \
  src/libMultiMarkdown/pic.c], $ext_shared,, [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 ] )
fi

编写php扩展的资料

我这里主要参考的是 php内核剖析这本书。

php的扩展其实也可以用c++开发。这里有一个很好的项目php-x,并且开发扩展也要容易很多。

ubuntu服务器部署ipv6访问

整个部署过程分为:
1.启用服务器的ipv6支持
2.申请ipv6通道
3.开启nginx的ipv6地址监听

1.ubuntu上开启ipv6支持,需要修改几个地方

1.开启ipv6支持,/etc/sysctl.conf
确保有以下设置:

net.ipv6.conf.all.disable_ipv6 = 0
net.ipv6.conf.default.disable_ipv6 = 0
net.ipv6.conf.lo.disable_ipv6 = 0

设置完毕后使用sysctl -p使设置生效

2.设置服务器的ipv6 dns服务器,/etc/network/interfaces
添加以下设置:

dns-nameserver 2001:4860:4860::8888
dns-nameserver 2001:4860:4860::8844

设置完毕后使用sudo resolvconf -u使设置生效

这样能够保证服务器在连接ipv6地址时有合适的dns服务器

测试:ping6 ipv6.google.com
如果能够ping通则表示已经设置成功

2.申请ipv6通道

因为我的服务器是没有分配ipv6地址的,所以需要去http://tunnelbroker.net申请ipv6的通道。
1.注册账号
2.邮箱激活
3.登录
4.create regular tunnel
5.输入你的服务器的ipv4地址,创建通道
6.在example configurations处选择操作系统,获取配置
7.在 /etc/network/interfaces中拷贝6中的配置
8.sudo resolvconf -u使配置生效
9.通过ifconfig查看是否设置成功,如果出现了ipv6的地址则表示成功

3.开启nginx的ipv6地址监听

在所有的站点配置文件中加上:

listen [::]:80
listen [::]:443

分别监听80端口和443端口(如果有https)
重启nginx即可。
通过http://ipv6-test.com/validate.php测试域名能否在ipv6下访问成功。如果是阿里云,那么有可能在IPv6 DNS server这一项上失败。因为阿里云的DNS服务器没有IPv6 DNS server。这种情况下,如果用户只有ipv6的环境,则会因为无法访问DNS服务器而失败

Swoole Server架构分析

一.简介

首页这里引用一下swoole的官方介绍:

swoole:面向生产环境的 PHP 异步网络通信引擎
使 PHP 开发人员可以编写高性能的异步并发 TCP、UDP、Unix Socket、HTTP,WebSocket 服务。Swoole 可以广泛应用于互联网、移动通信、企业软件、云计算、网络游戏、物联网(IOT)、车联网、智能家居等领域。 使用 PHP + Swoole 作为网络通信框架,可以使企业 IT 研发团队的效率大大提升,更加专注于开发创新产品。

通过上述的介绍,我们是可以得出几点信息的

  • 1.swoole可以投入生产环境
  • 2.使用php编写
  • 3.异步网络通信引擎,支持大量的网络协议,并且具有很高的网络性能

swoole server与传统的php运行模式是完全不同的,它是常驻内存的,省去了大量的php脚本的初始化。

二.swoole server的是怎么运行的

swoole进程/线程模型

这是一张官方的swoole运行时进程/线程模型。

1.Master进程

Master进程是一个多线程模型,其中包括Master线程,Reactor线程组,心跳检测线程,UDP收包线程。

以http server为例,Master线程负责监听(listen)端口,然后接受(accept)新的连接,然后将这个连接分配给一个Reactor线程,由这个Reactor线程监听此连接,一旦此连接可读时,读取数据,解析协议,然后将请求投递到worker进程中去执行。

Master进程是使用select/poll进行IO事件循环的,这是因为Master进程中的文件描述符只有几个(listenfd等),Reactor线程使用的是epoll,因为Reactor线程中会监听大量连接的可读事件,使用epoll可以支持大量的文件描述符。

2.Manager进程

Manager进程是专门用来管理Worker进程组和Task进程组的。它会Fork出指定数量的Worker进程和Task进程,并且有以下职能:

  • 子进程结束运行时,manager进程负责回收此子进程,避免成为僵尸进程。并创建新的子进程
  • 服务器关闭时,manager进程将发送信号给所有子进程,通知子进程关闭服务
  • 服务器reload时,manager进程会逐个关闭/重启子进程

3.Worker进程和Task进程

Worker进程接收Reactor线程投递过来的数据,执行php代码,然后生成数据交给Reactor线程,由Reactor线程通过tcp将数据返回给客户端。(如果是UDP,Worker进程直接将数据发送给客户端)。

Worker进程中执行的php代码和我们平时写php是一样的,它等同于php-fpm。但是众所周知,php-fpm下,php在处理异步操作时是很无力的,swoole提供的Task进程可以很好的解决这个问题。Worker进程可以将一些异步任务投递给Task进程,然后直接返回,处理其他的由Reactor线程投递过来的事件。

Task进程以完全同步阻塞的方式运行,一个Task进程在执行任务期间,是不接受从Worker进程投递的任务的,当Task进程执行完任务后,会异步地通知worker进程告诉它此任务已经完成。

所以介绍完上述的一些概念后,再引用一张官方的swoole执行流程图。
swoole运行流程图

这里需要注意,在文档上说的是:Workder进程组和Task进程组是由Manager进程Fork出来的,但是流程图上画的是在启动服务器时Fork出主进程和Worker进程组以及Tasker进程组。

三、使用swoole和传统php开发的优缺点

在说这个话题之前,需要先了解一下CGI,FASTCGI。

1.CGI
CGI的全称是Common Gateway Interface,通用网关接口,它使得任何一个拥有标准输入输出的程序拥有提供web server的能力。假设我们写了一个Hello World的c++程序,这个程序接受输入{text},输出{text},Hello World。

以nginx作为接受http请求为例,nginx接受一个http请求,Fork出一个进程,将http请求带来的text参数作为输入,执行完hello world程序,将输出{text},Hello World作为输出,销毁这个Fork出来的进程,由nginx返回给客户端。

这种方式虽然简单,但是要不断的Fork进程,销毁进程。

2.FASTCGI
FASTCGI,顾名思义,它是CGI的改进版,是一个常驻型的CGI服务。我们常用的php-fpm就是这种模式运行的,php-fpm负责Forl多个进程,每个进程中都运行了php的解释器。可以在终端下看一下php-fpm的进程:
php-fpm进程

一个php-fpm主进程,pid是1263,Fork出了3个子进程。在nginx+php-fpm的组合中,nginx负责接受http请求,将请求封装好交给php-fpm,php-fpm将请求按照一定的规则交给一个子进程去执行,这个子进程中的php解释器加载php代码运行。也是因为这个原因,传统的php只能作为web server。

然后我们发现,nginx+php-fpm的组合和我们Reactor+Worker子进程的运行方式非常相似。

3.swoole的运行方式
这里以swoole作为http server为例(传统php几乎都是作为web服务)。

首先swoole是实现了http server的,也就是说不需要nginx作为http服务器了,当然swoole并不是为了取代nginx,实际上swoole当前实现的http server功能有限,比如说只支持Get和Post,所有往往swoole前面还要运行一个nginx来作为前端代理服务器。

其次,swoole是内存常驻的。和php-fpm的常驻服务不同,php-fpm中常驻的是php的解释器,这个解释器会重复加载php代码,初始化环境,而swoole只在启动的时候加载,这样一来,性能就自然而然的提高了。这一点可以在开发中很明显的体现出来,php-fpm下,修改的php代码会即时生效,而使用swoole则需要重启swoole的server才能使代码生效。

通过上面的一些说明,就可以很明显的得出swoole和传统php开发的优缺点了。

swoole server优点:
– swoole性能更高
– 可以做为tcp,udp服务器
– 在高io高并发的服务器要求下,swoole的运行模式是完全可以胜任的

swoole server缺点:
– 更难上手。这要求开发人员对于多进程的运行模式有更清晰的认识
– 更容易内存泄露。在处理全局变量,静态变量的时候一定要小心,这种不会被GC清理的变量会存在整个生命周期中,如果没有正确的处理,很容易消耗完所有的内存。而以往的php-fpm下,php代码执行完内存就会被完全释放。
– 无法做密集计算。当然这一点是php甚至是所有动态语言都存在的问题。写在这里是因为防止误导读者以为使用swoole后,php可以用来做密集计算。

LINUX下修改php.ini配置报错输出

首先是找到php.ini文件

输入

find / -name php.ini

总共有两个结果

/etc/php5/cli/php.ini
/etc/php5/apache2/php.ini

cli/php.ini指的是在控制台环境下运行php脚本使用的配置文件

apache2/php.ini是apache2环境下运行php脚本使用的配置文件

如果你还不确定是用哪一个,可以新建一个php代码

使用 echo phpinfo(); 来输出php信息,其中有一项是加载的php.ini路径。我的就是apache2/php.ini

然后编辑apache2/php.ini,我这里是要开启他的报错,不然如果代码中有错,浏览器访问就会直接报500错误

找到display_errors这一行,去除前面的;

display_errors=On

配置错误级别

error_reporting=E_ALL & ~E_NOTICE

修改完成后要使php.ini配置生效,网上普遍的说法是重启apache服务生效

service apache2 restart

代码出错的地方仍然报500错误

准备去看apache的日志文件,同样使用find / -name error.log找到日志,由于日志太多,就把之前的error.log删掉,重启apache,浏览器打开出错页面,然后查看error.log。发现日志中是记录了代码出错的信息的。确定就是没有开启报错,导致500错误。

修改apache2.conf,在/etc/apache2下面。

添加

php_flag display_errors on
php_value error_reporting 2039

重启apache
service apache2 restart

浏览器打开错误页面,成功报错!

linux下一些好用的指令记录

查找当前目录下包含指定字符串的文件

grep -rn “hello,world!” *

  • : 表示当前目录所有文件,也可以是某个文件名
    -r 是递归查找
    -n 是显示行号
    -R 查找所有文件包含子目录
    -i 忽略大小写

mysql数据库相关操作
创建用户
mysql> insert into mysql.user(Host,User,Password) values(“localhost”,”phplamp”,password(“1234”));
授权

grant all privileges on phplampDB.* to phplamp@localhost identified by ‘1234’;
删除用户
mysql>DELETE FROM user WHERE User=”phplamp” and Host=”localhost”;
mysql>flush privileges;
修改密码
mysql>update mysql.user set password=password(‘新密码’) where User=”phplamp” and Host=”localhost”;
mysql>flush privileges;
刷新系统权限表
mysql>flush privileges;

cinnamon桌面图标重复
gsettings set org.gnome.desktop.background show-desktop-icons false

ubuntu下linux创建桌面启动器以及菜单栏点击失效的解决方法

创建桌面启动器

进入/usr/share/applications

vi eclipse.desktop

输入一下内容

[Desktop Entry]
Encoding=UTF-8
Name=eclipse
Comment=Eclipse IDE
Exec=/home/jiang/eclipse/eclipse
Icon=/home/jiang/eclipse/icon.xpm
Terminal=false
StartupNotify=true
Type=Application
Categories=Application;Development;
Exec=env UBUNTU_MENUPROXY=0 /home/jiang/eclipse/eclipse

保存后,将这个文件复制到桌面文件夹
cp /usr/share/applications/eclipse.desktop /home/jiang/桌面

对于eclipse打不开菜单的情况,可以修改eclipase文件夹下的eclipse.ini为

-startup
plugins/org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar
–launcher.GTK_version
2
–launcher.library
plugins/org.eclipse.equinox.launcher.gtk.linux.x86_64_1.1.300.v20150602-1417
-product
org.eclipse.epp.package.jee.product
–launcher.defaultAction
openFile
-showsplash
org.eclipse.platform
–launcher.XXMaxPermSize
256m
–launcher.defaultAction
openFile
–launcher.appendVmargs
-vmargs
-Dosgi.requiredJavaVersion=1.7
-XX:MaxPermSize=256m
-Xms256m
-Xmx2048m

————————————

–launcher.GTK_version
2

这一部分是新增的部分

linux下编译安装mysql-connector-cpp

首先在github上下载mysql-connector-cpp

git clone https://github.com/mysql/mysql-connector-cpp.git

git上面有两个分支,2.0版本是支持mysql作为文档存储的接口,1.1版本是支持正常的关系数据库存储接口

首先我们编译安装2.0版本,进入代码目录执行

cmake .

cmake –build . –config CCC

cmake –build . –target install –config CCC

到这里已经安装完毕.

然后到testapp下编译测试样例

cmake .

提示出错,WITH-CONCPP没有设置,我试了编辑/etc/profile文件,设置这个变量为/usr/local/mysql/connector-c++-2.0/,但是仍然提示没有设置,于是我将CMakeLists中的

set(WITH_CONCPP $ENV{WITH_CONCPP} CACHE PATH “MySQL Connector/C++ 2.0 install location”)

改为

set(WITH_CONCPP “/usr/local/mysql/connector-c++-2.0/” CACHE PATH “MySQL Connector/C++ 2.0 install location”)

继续cmake .

提示出错,在/usr/local/mysql/connector-c++-2.0/lib64下找不到依赖库

那么将目录下生成的 libmysqlcppconn2.so libmysqlcppconn2.so.1 libmysqlcppconn2.so.1.0 拷贝到/usr/local/mysql/connector-c++-2.0/lib64中,没有的创建一个即可。

cmake .

make

在run文件夹下面生成了两个可执行文件

devapi_test xapi_test

这两个文件的区别在于,xapi_test实现了c的接口,是通过mysql的一个叫做x plugin的插件连接mysql的,而devapi则是使用了c++的接口。所以如果使用xapi接口的话,mysql安装时必须安装x plugin

接下来编译安装1.1版本,先将当前的改动都commit了,然后

git checkout 1.1,

cmake .

如果出错。

报缺少boost库错误,可以

sudo apt-get install libboost-all-dev

报缺少mysql.h等错误,可以通过sudo apt-get install libmysql++-dev

然后make

sudo make install

然后将生成的动态库拷贝的/usr/local/lib下

sudo cp libmysqlcppconn.so* /usr/local/lib/

linux下使用crontab进行网站内容和数据库定期备份

crontab有几个常用的命令

crontab -l 列举crontab的任务

crontab -e 编辑crontab的任务

crontab -r 删除crontab的任务

crontab -h crontab的帮助

crontab -i 删除crontab前进行提示

crontab -e进行编辑任务时会使用nano进行编辑,编辑命令如下示例。

分 时 每月的第几天 每周的周几 命令

前5个参数可以使用通配符 *

m h dom mon dow command

设置每两分钟扫描一次,清理过期记录

*/2 * * * * /usr/bin/php /var/www/poker/scan.php >> /$

设置每周二23:29分备份一次

59 23 * * 2 /bin/bash /home/back.sh
59 23 * * 2 /bin/bash /home/back.sh就是我用来定期备份的指令,主要就是配合一个shell脚本

#!/bin/bash
cd /home
mv backup/* oldbackup/
echo “old backup file has been moved to oldbackup folder”;
cd /home/backup
Now=date "+%Y-%m-%d-%H-%M-%S"
File_emlog=”backup-emlog-“${Now}”.sql”
mysqldump -u me -ppassword emlog > $File_emlog
File_java=backup-java-$Now.sql
mysqldump -u me -ppassword java > $File_java
File_plan=backup-plan-$Now.sql
mysqldump -u me -ppassword plan > $File_plan
File_sms=backup-sms-$Now.sql
mysqldump -u me -ppassword sms > $File_sms
File_tu=backup-tu-$Now.sql
mysqldump -u me -ppassword tu > $File_tu
File_wordpress=backup-wordpress-$Now.sql
mysqldump -u me -ppassword wordpress > $File_wordpress

echo “your databases backup successfully completed”;
#数据文件到这里备份完毕

#下面备份网站的文件
tar -czf /home/backup/back_www.tar.gz /var/www