分类 Linux 下的文章

tool.lu技术架构

背景

一个字,穷!在小流量的情况下,这个应该算是比较经济的解决方案了吧(各种容灾都没有,监控没有,服务的吞吐测试没有)。哈哈哈...

后端的业务处理和服务

整个的网站都放在aliyun的VPS上。

由于工具网站的后端处理比较耗资源,于是将业务处理服务部署到了两台VPS上。(aliyun +1 & 美国 +1)

Redis只是做了少量的缓存作用,所以图中并未给出。

Untitled.png

面对前端的一些优化

cdn 现在全部都放在aliyun的VPS上。

  1. 使用nginx的 nginx-http-concat 扩展合并多个文件请求。
  2. http_image_filter_module 进行一些图片的实时压缩计算

域名分别为 s1.tool.lu, s2.tool.lu, s3.tool.lu

爬虫

现在所有的爬虫均基于Scrapy编写,全部部署在 美国的vps上;数据储存在MariaDB。

虚拟化

主要用于一些不可信任代码的执行。

选型Docker,可限制CPU和Mem,不能限制Disk,但是Docker在CentOS6.x下的问题较多,各种坑;最近使用CentOS7搭建之后貌似很Happy。

supervisor的管理下运行nodejs

背景

supervisor的管理下运行ruby,也是服务的迁移。

而supervisor管理的时候是不会加载/etc/profile的,所以要手动指定环境变量。

解决

设置环境变量

cat /etc/supervisord/htaccess.conf

[program:htaccess]
directory = /data/thrift/Rewrite2Nginx/Server
command = /usr/local/node/bin/node Server.js
environment = NODE_PATH=/usr/local/node/lib/node_modules

supervisor的管理下运行ruby

背景

上周六,准备从杭州去苏州的时候,坐在咖啡馆开始了 tool.lu 的迁移(美国VPS -> 阿里云)。

就在对 ruby工具 后台服务迁移的时候,supervisor管理的ruby服务就是起不来。

解决

一般情况下,我们会这样:

which ruby
# /usr/local/rvm/rubies/ruby-1.9.3-p545/bin/ruby

事实上,上面的路径在美国的VPS上是好的,但是迁到阿里云上的时候就出了问题,最后将ruby的路径改为了wrappers下面的,问题便圆满解决了!
cat /etc/supervisord/ruby.conf

[program:ruby]
directory = /data/thrift/RubyBeauty/Server
command = /usr/local/rvm/wrappers/ruby-1.9.3-p545/ruby /data/thrift/RubyBeauty/Server/Server.rb

与垃圾评论的斗争

我确定我不会和某大厂的员工一样把重要信息写在里面,所以相关参数需要替换一下

# 取出垃圾评论的IP,并从数据库删除
mysql -u[mysql用户名] -p[mysql密码] --database=[typecho数据库] -e "SELECT DISTINCT ip FROM tp_comments WHERE status='spam'" --batch --skip-column-names >> banned_tmp.txt
mysql -u[mysql用户名] -p[mysql密码] --database=[typecho数据库] -e "DELETE FROM tp_comments WHERE status='spam'"
# 阻止ip
cat banned_tmp.txt | while IDF= read -r ip; do /sbin/iptables -I INPUT -s "$ip" -j DROP; done
# 保存路由
service iptables save
# 输出所有blocked ip
sed -n "/-j DROP/p" /etc/sysconfig/iptables | cut -d' ' -f4 | cut -d'/' -f1 > banned.txt

Nginx图片处理

server {
  # ...此处省略N行配置
  # example: /thumb/[md5].png_300x187.png
  location ~ "/thumb/([0-9a-f]{32}\.(png|jpg|gif))_(\d+|-)x(\d+|-)\.(png|jpg|gif)$" {
    access_log off;
    set $image $1;
    set $width $3;
    set $height $4;
    # 建议使用try_files代替if
    try_files /thumb/$image =404;
    image_filter resize $width $height;
    image_filter_buffer 10M;
    # 只会在浏览器端缓存,若要在服务器端缓存,可采用proxy的方案(此处没有给出,可自行google)
    expires 7d;
  }
}

Laravel的Nginx配置

server {
    listen 80;
    server_name tool.lu;
    root /data/html/tool.lu/public;
    index index.html index.php;

    location / {
        try_files $uri $uri/ /index.php$is_args$query_string;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

if (!-f $request_filename)貌似并不是一个好的做法

安装mailcatcher

安装rvm & ruby

\curl -sSL https://get.rvm.io | bash -s stable
rvm install 1.9
rvm use 1.9 --default

更改gem源

gem sources
gem sources -r https://rubygems.org/
gem sources -a http://ruby.taobao.org/

安装mailcatcher

gem install mailcatcher
mailcatcher --http-ip=0.0.0.0
# browser visit http://[ip]:1080/

配置php

# vim /usr/local/php/etc/php.ini
send_mail_path = /usr/bin/env catchmail
# service php-fpm restart

测试

# php -a
mail('245565986@qq.com', 'test email', 'email\'s content is written by xiaozi.');

vim 杂记

粘贴到vim中

直接insert模式下粘贴会有问题,如:自动的缩进,自动的注释。

可以使用

:r !cat
// 粘贴
// [ctrl] + [d]

在 mac 下,方便一点,可以使用

:r !pbpaste

多次块缩进

使用shift + v之后选择了多行,并且进行缩进>,但是可能需要缩进多次;需要用到重复上次命令的指令.

shift + v
shift + .
// 重复多少次就按多少次
.

vagrant的坑

vagrant project和 虚拟机 关系丢失

额,这个问题不知道怎么产生的,但它确实出现了。解决办法:

VBoxManage list vms
# "vagrant_default_1389674864" {aea24237-3b9e-45b2-8593-59e2b63b34b8}
# 修改id文件
vim .vagrant/machines/default/virtualbox/id
# 或者直接
echo 'aea24237-3b9e-45b2-8593-59e2b63b34b8' > .vagrant/machines/default/virtualbox/id

vagrant 共享目录的权限

#...
config.vm.synced_folder "/Applications/MAMP/htdocs/plus.tool.lu", "/var/www/html"
#...

好吧,在虚拟机里面chmod -R 777 /var/www/html没用,google之后发现,需要在Vagrantfile里面设置。Vagrant Synced Folders Permissions

# ...
config.vm.synced_folder "/Applications/MAMP/htdocs/plus.tool.lu", "/var/www/html",
    id: "vagrant-root",
    owner: "nobody",
    group: "nobody",
    mount_options: ["dmode=775,fmode=664"]
# ...

php正则匹配出错的问题

发现

因为用了laravel4的

Response::json(array())->setCallback('callback')

然后始终报

The callback name is not valid.

然后单独写了个test.php文件测试该语句在不同的机器上测下来的结果不一样。

var_dump(preg_match('/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*+$/u', 'sad'));

解决

本以为是系统哪边的设置的问题,于是将envphp -i的结果都比对了一遍,都一样。后来想到看一下pcre的版本是否一致pcre-config --version,结果都是6.6;最后没办法rmp -qa | grep pcre,好家伙最后的小版本不一样;再然后你懂的,升级了一下pcre就正常了。

附注

  • 有问题的版本: pcre-6.6-2.el5_1.7
  • 升级后的版本: pcre-6.6-9.el5

在CENTOS上编译pidgin-lwqq,配置qq机器人

首先,yum安装各种需要的东西,比如pidgin,以及后面需要用到的依赖包,其中dbus是用来通信的

yum install pidgin-devel finch screen dbus dbus-devel dbus-x11 libev-devel zlib-devel glibc-devel cmake sqlite-devel libcurl-devel

其次,通过git clone pidgin-lwqq来进行编译,详细的见https://github.com/xiehuc/pidgin-lwqq,不过这里需要注意两点:

  1. 切换到dev分支,不然没法编译,master分支需要依赖一个mozjs的东西,centos上可以搞,但是相当麻烦
  2. 因为是在一个没有图形界面的系统(比如VPS)上编译,需要将验证码输出到一个web可访问的路径去,否则登录不了。修改方法大致如下:
diff --git a/lib/login.c b/lib/login.c
index 8fe25c0..a1ae11b 100644
--- a/lib/login.c
+++ b/lib/login.c
@@ -141,6 +141,7 @@ static LwqqAsyncEvent* check_need_verify(LwqqClient *lc,const char* appid)
 static int request_captcha_back(LwqqHttpRequest* req,LwqqVerifyCode* code)
 {
     int err = 0;
+       FILE *captcha_fp;
     if(req->http_code!=200){
         err = -1;
         goto done;
@@ -148,6 +149,9 @@ static int request_captcha_back(LwqqHttpRequest* req,LwqqVerifyCode* code)
     LwqqClient* lc = req->lc;
     code->data = req->response;
     code->size = req->resp_len;
+       captcha_fp = fopen("/path/to/qqcaptcha.png", "wb");
+       fwrite(code->data, code->size, 1, captcha_fp);
+       fclose(captcha_fp);
     req->response = NULL;
     lwqq_call_action(lc,need_verify2)(lc,code);
 done:

然后,编译好之后,就可以先试试登录了,直接敲入finch试试,目测就能看到登录界面了
1.png

选择webqq进行登录,使用快捷键还是很方便的,不会使用可以man一下

下面就是机器人部分了,pidgin支持dbus通信,于是我们可以用利用dbus的接口来监听qq的消息,并作出回应,抑或是主动发出消息。要使用dbus,那么finch需要运行在screen下,这也是前面安装screen的原因,简单点,在命令行里敲入

dbus-launch screen

即可启动dbus,并进入screen,然后在screen下运行finch即可。友情提示,某些terminal里,screen下的finch排版混乱,至今没找到解决方案。另外,screen的使用方法可以man一下,你的通信脚本也必须运行在screen里。

dbus通信可以用多个语言实现,c、python、php等等都可以,个人熟悉php,因此使用php进行编写机器人脚本。首先安装php扩展dbus:

pecl install dbus-0.1.1

装完记得在php.ini里加入dbus的配置

[dbus]
extension=dbus.so

网上能找到很多示例代码,比如PHP官网就有很多example:传送门在此

下面贴个简单自动重复消息的机器人代码

<?php

Robot::getInstance()->run(200);
/**
 * class Robot
 * @author Baiqiang Dong<qiyuuu@gmail.com>
 */
class Robot {

    protected $interface = 'im.pidgin.purple.PurpleInterface';
    protected $signals = array(
        'ReceivedImMsg',
        'ReceivedChatMsg',
    );

    private static $_instance;
    private $_dbus;
    private $_proxy;

    public static function getInstance() {
        if (self::$_instance === null) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }

    private function __construct() {
        //connect to dbus
        $this->_dbus = new IDBus(DBus::BUS_SESSION);
        $this->_proxy = $this->_dbus->createProxy(
            'im.pidgin.purple.PurpleService',
            '/im/pidgin/purple/PurpleObject',
            'im.pidgin.purple.PurpleInterface'
        );
        //listen to specified signals
        foreach ($this->signals as $signal) {
            $this->_dbus->addSignalReceiver(array($this, lcfirst($signal)), $this->interface, $signal);
        }
    }

    public function receivedImMsg($signal) {
        $this->responseMessage($signal);
    }

    public function receivedChatMsg($signal) {
        $this->responseMessage($signal);
    }

    public function responseMessage($signal) {
        list($receiver, $sender, $message, $conversation, $flags) = $signal->getData()->getData();
        //send what you received
        $this->sendMessage($conversation, $message, $sender);
    }

    public function sendMessage($conversation, $message) {
        $proxy = $this->_proxy;
        try {
            $type = $proxy->PurpleConversationGetType($conversation);
            switch ($type) {
                //im
                case 1:
                    $im = $proxy->PurpleConvIm($conversation);
                    $proxy->PurpleConvImSend($im, $message);
                    break;
                //chat
                case 2:
                    $chat = $proxy->PurpleConvChat($conversation);
                    $proxy->PurpleConvChatSend($chat, $message);
                    break;
                
                default:
                    # code...
                    break;
            }
        } catch (Exception $e) {
            echo $e->getMessage(), "\n";
            return false;
        }
        return true;
    }

    public function run($time = 1000) {
        $this->_dbus->mainLoop($time);
    }

}

/**
 * extend the DBus class to implement methods like mainLoop and addSignalReceiver
 */
class IDBus extends DBus {
    private $_signalReceivers = array();

    public function __construct($type) {
        parent::__construct($type);
    }

    public function addSignalReceiver($callback, $interface, $signal) {
        if (!is_callable($callback)) {
            return;
        }
        if (!isset($this->_signalReceivers[$interface])) {
            $this->addWatch($interface);
        }
        $this->_signalReceivers[$interface][$signal] = $callback;
    }

    public function mainLoop($time) {
        while (true) {
            $signal = parent::waitLoop($time);
            $called = false;
            if ($signal instanceof DbusSignal) {
                foreach ($this->_signalReceivers as $interface=>$callbacks) {
                    foreach ($callbacks as $method=>$callback) {
                        if ($signal->matches($interface, $method) && is_callable($callback)) {
                            $called = call_user_func($callback, $signal);
                            //break to main loop if callback return true
                            if ($called) {
                                break 2;
                            }
                        }
                    }
                }
            }
        }
        return $signal;
    }
}

php重启apache服务

这里有个矛盾的地方,就是 php是作为Apache的一个模块来运行的,所以一旦重启Apache,在Apache停掉的时候,php调用的shell命令就不会继续执行了;也就是说,只能关闭Apache,不能启动Apache。

我想到的办法就是将命令交给其他程序去运行,于是就想到了at命令。man at只看到at -f [file] now从文件读取,但是我就一个命令,不想再生成一个文件,然后就想到了管道,试一下,成功了。

echo 'sudo service httpd restart' | at now

注:at命令,apache用户需要有登录权限(/etc/passwd)

<time datetime="2013-11-03">2013-11-03 修改</time>

不知为何用apache去启动的服务,在apache服务退出的时候会接管apache的端口

CentOS6.4 上编译 pdf2htmlex

编译fontforge

直接git clone下来的代码,貌似和glib2的版本不太匹配。于是我是下的他的一个tag,然后编译的。

wget https://github.com/fontforge/fontforge/archive/v20120731-b.tar.gz
./configure --without-libzmq --without-x --without-iconv --disable-python-scripting --disable-python-extension

编译pdf2htmlex

我在编译pdf2htmlex的时候遇到了下面的错误

Linking CXX executable pdf2htmlEX
/usr/local/lib/libfontforge.so: undefined reference to `PyTuple_SetItem'
/usr/local/lib/libfontforge.so: undefined reference to `PyObject_SelfIter'

修改一下link.txt就可以了

vim CMakeFiles/pdf2htmlEX.dir/link.txt
# 加上一个编译参数 -lpython2.6