nginx 修改 max open files limits

注意:修改 nginx 的 max open files 有个前提,就是你已经修改好了系统的 max open files.

先查看 nginx 的 ulimits:
grep 'open files' /proc/$( cat /var/run/nginx.pid )/limits

修改 nginx.service

sudo vi /lib/systemd/system/nginx.service  # (仅适用于 ubuntu)

添加:

[Service]
LimitNOFILE=100000

重启服务:
sudo systemctl daemon-reload

修改 nginx.conf,
添加:

worker_rlimit_nofile 90000;  # (has to be smaller or equal to LimitNOFILE set above)

重启 nginx:
sudo systemctl restart nginx

上面是网上流传的教程,但是还是不够,你这样改了之后,nginx 的并发能力反而会下降,所以还需要改一个关键的参数:
修改 nginx.conf
添加:

events {
    worker_connections  90000;
}

重启 nginx:
sudo systemctl restart nginx

爬虫数据库一些简单的设计逻辑

场景:爬取某商城的部分商品。

队列设计

这里至少需要爬取2种资源,一种是商品列表,一种是商品信息。
所以要设计1条队列,保存商品信息URL。

爬虫1定期爬前N个列表页 URL,把里面的商品信息URL爬下来,保存到队列里。

爬虫2定期从队列中抽出商品信息URL,爬取商品信息,爬完后把该URL移出队列。

所以呢,简单来说,只要有2张表就行了,一张保存队列信息,一张保存商品信息。

何时停止问题

为了避免每次都把所有商品爬一遍,就要在适当的时候停止。
爬列表页的时候,一般是设定只爬前 N 页。
爬商品信息URL的时候,一般是先检查这个商品是否存在,不存在就入队,存在的话,就表示接下来都是旧数据了,可以停止了。

当然有种情况,就是有些旧的商品,会被人为地置顶,或者排到前面来。

这时候就要设置一个值 M,每次最多爬前 M 个,多了不爬。

数据更新问题:

有新商品进来,直接插入即可,如果是旧商品,那要不要更新数据库里的内容呢?
一般来说是可以更新的,但有种情况例外,就是你的数据库会有人去编辑的情况。

如果你的数据库有专人编辑,那么最好不要更新旧商品,因为会覆盖掉编辑的内容。并且,数据表要采用软删除的方式,避免前面的人刚删除了数据,你的爬虫又把数据写进去了。

laravel-admin 与 vue 结合使用

由于 Laravel-admin 采用的是 pjax 的方式刷新页面,意味着很多页面刷新的操作,并不是刷新整个 document,而是从服务器拿到部分 document,再通过类似 $(“#pjax-container”).html(newPart) 的方式更新的。

这就造成一个问题,每次 pjax 刷新,都会破坏 vue 的 dom 映射。

所以理论上有2种方法解决:

  1. 重新绑定一下 vue 的映射关系
  2. 在某些页面禁止 pjax

1 太难搞,而且没啥资料,放弃。2 的话比较可行。

部分禁止 pjax

打开 public/vendor/laravel-admin/laravel-admin/laravel-admin.js
添加代码:

// 不使用 pjax 刷新的页面
$(document).on('pjax:beforeReplace', function (e, options) {
  // console.log(arguments)
  var freshPaths = [
    /\/admin.*\/products/,
  ]
  for (let path of freshPaths) {
    if (path.test) {
      if (path.test(e.state.url)) {
        location.reload()
        return false
      }
    }
    else if (options.url.search(path) !== -1) {
      location.reload()
      return false
    }
  }
})

使用自定义 view

很多时候我们并不需要大动干戈地建立一个全部的 view,只需要在内置 view 中稍作修改。
这时候,我们需要先自定义一个 Content 类:

use Encore\Admin\Layout\Content;

class MyContent extends Content {
    public function render() {
        $items = [
            'header'      => $this->header,
            'description' => $this->description,
            'breadcrumb'  => $this->breadcrumb,
            'content'     => $this->build(),
        ];

        return view('admin.content', $items)->render();
    }
}

然后引用它:

    public function index(MyContent $content) {
        return $content
            ->header('product')
            ->description($this->brand)
            ->body($this->grid());
    }

这样一来,每次进入到 index 页面,都会渲染 admin.content 这个 view 。

view 的内容直接 copy 自 vendor/encore/laravel-admin/resources/views/content.blade.php

在 view 里插入 vue 组件

添加2部分代码即可。
第一部分是初始化 vue app:

    

第2部分是插入组件:

        

vue 组件单独一个 js 文件

位置如下:public/vendor/components/gallery-editor.js
定义如下:

这是一个弹出式编辑框,具体作用就不解释了,只是个示例。

然后还需要在 Admin/bootstrap.php 中引用这个 js 文件:

Admin::js('/vendor/components/gallery-editor.js');

为什么不把组件代码直接写进 view 中呢?
因为 pjax 的后端逻辑似乎有 bug,template 的内容无法全部渲染到前端,导致页面出错。

MySQL 千万级数据表 partition 实战应用

目前系统的 Stat 表以每天 20W 条的数据量增加,尽管已经把超过3个月的数据 dump 到其他地方,但表中仍然有接近 2KW 条数据,容量接近 2GB。

Stat 表已经加上索引,直接 select … where … limit 的话,速度还是很快的,但一旦涉及到 group by 分页,就会变得很慢。

据观察,7天内的 group by 需要 35~50s 左右。运营反映体验极其不友好。
于是上网搜索 MySQL 分区方案。发现网上的基本上都是在系统性地讲解 partition 的概念和种类,以及一些实验性质的效果,并不贴近实战。

通过参考 MySQL手册以及自己的摸索,最终在当前系统中实现了分区,因为记录一下。

分区类型的选择

Stat 表本身是一个统计报表,所以它的数据都是按日期来存放的,并且热数据一般只限于当天,以及7天内。所以我选择了 Range 类型来进行分区。

为当前表创建分区

因为是对已有表进行改造,所以只能用 alter 的方式:

ALTER TABLE stat
    PARTITION BY RANGE(TO_DAYS(dt)) (
        PARTITION p0 VALUES LESS THAN(0),
        PARTITION p190214 VALUES LESS THAN(TO_DAYS('2019-02-14')),
        PARTITION pm VALUES LESS THAN(MAXVALUE)
    );

这里有2点要注意:

一是 p0 分区,这是因为 MySQL(我是5.7版) 有个 bug,就是不管你查的数据在哪个区,它都会扫一下第一个区,我们每个区的数据都有几十万条,扫一下很是肉疼啊,所以为了避免不必要的扫描,直接弄个0数据分区就行了。

二是 pm 分区,这个是最大分区。假如不要 pm,那你存 2019-02-15 的数据就会报错。所以 pm 实际上是给未来的数据一个预留的分区。

定期扩展分区

由于 MySQL 的分区并不能自己动态扩容,所以我们要写个代码为它动态的增加分区。

增加分区需要用到 REORGANIZE 命令,它的作用是对某个分区重新分配。
比如明天是 15 号,那我们要给 15 号也增加个分区,实际上就是把 pm 分区拆分成2个分区:

ALTER TABLE stat
    REORGANIZE PARTITION pm INTO (
        PARTITION p190215 VALUES LESS THAN(TO_DAYS('2019-02-15')),
        PARTITION pm VALUES LESS THAN(MAXVALUE)
    );

这里就涉及到一个问题,即如何获得当前表的所有分区?网上有挺多方法,但我试了下感觉还是先 show create table stat 然后用正则匹配出所有分区更方便一点。

定期删除分区

随着数据库越来越大,我们肯定是要清除旧的数据,同时也要清除旧的分区。
这个也比较简单:

ALTER TABLE stat DROP PARTITION p190214, p190215