消息队列的思考

什么是消息队列

维基百科的定义:在计算机科学中,消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。消息队列提供了异步的通信协议,每一个贮列中的纪录包含详细说明的数据,包含发生的时间,输入设备的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列交互。消息会保存在队列中,直到接收者取回它。

这定义太复杂了。简单的来讲就是排队系统嘛。这好比,每天到了中午吃饭的点,去麦当劳或者肯德基,都是要排队的嘛,毕竟,员工的数量有限,中午想吃饭的人会很多。大家都会先下单,然后拿着号码去排队取餐。这就是队列啊。

消息队列应用场景

消息队列的应用场景是,当前业务处理量遇到瓶颈,无法及时处理业务,将部分耗时业务分离到消息队列异步处理,避免业务处理失败。
这好比有人在麦当劳点了1个经典套餐,另外一个人点了7个儿童套餐过生日,门店的员工会根据下单情况进行处理,做好经典套餐直接让第一个人来取,肯定不会说,先做完7个儿童套餐再处理第一个任务。

消息队列,可以解耦复杂业务的逻辑,但不可避免,降低了用户的体验。如果每次去麦当劳都需要排对下单,取餐,我想你以后也不大想去他们家吃饭了吧。

优势

队列的好处:
1. 增大的系统的吞吐量,可以处理更多的”请求”。
2. 不管是耗时任务还是即时任务,都可以得到较好的处理。

劣势

队列的劣势:
1. 由于消息队列的存在,任务变成了异步处理,处理完成的时间不可预知。
2. 既然有异步的处理,必然会有数据不一致的情况。

常用的消息队列

RabbitMQ

消息模式

支持消息模式 备注
简单队列 支持单一的消费者
工作队列 支持多个消费者
发布订阅 通过exchange获取队列,消费队列的消息
路由队列 exchange_type='direct' 必须exchange和routing_key匹配才可以消费消费。
主题队列 exchange_type='topic' 可实现队列模糊匹配
RPC 支持RPC调用消费

性能

  1. 根据性能测试第二部分 第一部分 大约在4w个消息每秒,开启ack+持久化后,几乎下降了10倍。该测试在2012年发布的,仅供参考。

实现原理

  1. RabbitMQ是基于AMQP(Advanced Message Queuing Protocol,高级消息队列协议)实现的消息中间件。实现的语言是erlang。
  2. 常用较多的是路由模式。可以实现精确的消息匹配,只需要获取自定义的消息。

PHP支持

  1. 安装Composer包 php-amqplib/php-amqplib
  2. PHP扩展

Kafka

消费模式

暂时只支持一种topics。支持多个消费者,支持分组,可以平均分布消息到不同的分区。

性能

  1. 官方在2014年也做了性能测试。单机达到了80k消息每秒。官方性能测试 文章地址
  2. 可以说是为了性能而存在的消息队列。
  3. Kafka并不保证消息顺序,也不支持数据持久化,部署依赖zookeeper组件实现分布式。

实现原理

Kafka底层是Java实现的。

PHP支持

  1. 优先推荐PHP扩展 rdkafka 主要就基于librdkafka开发的PHP客户端。
  2. 也有相关的Composer包,该包不支持snappy压缩方式的消息。

参考链接

  1. 消息队列及常见消息队列介绍
  2. 消息队列
  3. RabbitMQ和Kafka

MaxWell的学习

什么是MaxWell?

MaxWell守护进程是读取MySQL的二进制日志,将更新操作写入到消息队列,例如Kafka,RabbitMQ,Redis的订阅发布的应用。

官网上的示例1,在数据库test的表maxwell插入了一条数据,那么你在名为maxwell的topics会接收到一条消息,含有数据库、表、时间戳、更新类型等字段。

mysql> insert into `test`.`maxwell` set id = 1, daemon = 'Stanislaw Lem';
  maxwell: {
    "database": "test",
    "table": "maxwell",
    "type": "insert",
    "ts": 1449786310,
    "xid": 940752,
    "commit": true,
    "data": { "id":1, "daemon": "Stanislaw Lem" }
  }

示例2,更新语句也是类似的。不同的是,更新的消息会多一个字段old,记录字段尚未被更新之前的数据。

 mysql> update test.maxwell set daemon = 'firebus!  firebus!' where id = 1;
  maxwell: {
    "database": "test",
    "table": "maxwell",
    "type": "update",
    "ts": 1449786341,
    "xid": 940786,
    "commit": true,
    "data": {"id":1, "daemon": "Firebus!  Firebus!"},
    "old":  {"daemon": "Stanislaw Lem"}
  }

生产者

MaxWell支持多种生产者,官网自带配置文档。以Kafka为例,默认会把消息发送到名为maxwell的topics,你也可以自定义配置

踩坑

在MaxWell启动后,Kafka的生产者可以进行高性能的配置。如果添加以下的配置

kafka.acks = 1
kafka.compression.type = snappy
kafka.retries=0

消息会以snappy的方式进行压缩。什么是snappy压缩方式
在采用Composer包nmred/kafka-php消费消息时,遇到MaxWell生产的消息无法读取。
后来定位到是该Composer包不支持snappy压缩的消息,更换为php-rdkafka的扩展即可。

参考链接

  1. Kafka 0.9 Configuration Best Practices
  2. Manually Installing the extension

配置系统的思考

配置系统在整个软件的生命周期里,有着很重要的角色。例如:

在新公司上班,第一天需要配置开发环境。。。

多环境部署,开发、测试、预发布、生产不同环境的配置。。。

项目的数据库,缓存,负载均衡,Nginx各种服务的配置。。。

做这么多年的开发,说来好笑,一直在跟配置系统打交道,但却没有花时间思考配置系统存在的意义。那么如何在目前Linux+Nginx+MySQL+PHP的构架下,尝试合适自己的配置系统呢?

常见的配置系统

既然不知道什么样的配置系统适合自己,那么我们可以看看别人家,是如何使用配置系统的。

Phalcon

Phalcon框架是我使用时间较长的开发框架,先看看它内部的配置如何实现。

<?php

use Phalcon\Config;

$config = new Config(
    [
        'test' => [
            'parent' => [
                'property'  => 1,
                'property2' => 'yeah',
            ],
        ],
    ]
);

echo $config->get('test')->get('parent')->get('property');  // displays 1
echo $config->test->parent->property;                       // displays 1
echo $config->path('test.parent.property');                 // displays 1

#甚至你可以注入配置到di
use Phalcon\Di\FactoryDefault;
use Phalcon\Config;

// Create a DI
$di = new FactoryDefault();

$di->set(
    'config',
    function () {
        $configData = require 'config/config.php';

        return new Config($configData);
    }
);

实现的原理是根据传入的数组转换Phlacon\Config对象的属性。支持不同类型配置器,例如ini、Json、Php、yaml等,你可以理解为Phlacon\Config是一个工厂模式,生产不同的类型的配置实例。

Magento

这是一个流行的开源电子商务框架。在1.x的框架版本都是采用xml去实现的。那时候发现xml做配置,非常“灵活”。注意,我这里用一个非常的形容词,这意味调试开发成本会比较高。那时候我的时间大部分都花费在xml文件里查找对应的配置。幸好,官方终于在2.x版本优化过来了,终于可以解脱了。

The Magento 2 deployment configuration replaces local.xml in Magento 1.x.

config.php 代码实例:

return array (
  'modules' =>
  array (
    'Magento_Core' => 1,
    'Magento_Store' => 1,
    'Magento_Theme' => 1,
    'Magento_Authorization' => 1,
    'Magento_Directory' => 1,
    'Magento_Backend' => 1,
    'Magento_Backup' => 1,
    'Magento_Eav' => 1,
    'Magento_Customer' => 1,
...
  ),
);

json

JSON文件格式是一个伟大的发明。简单的key和value的形式,就解决大部分配置文件,主要应用在不需要复杂逻辑配置,比如Composer、NPM等。

# composer.json
{
    "config": {
        "preferred-install": {
            "my-organization/stable-package": "dist",
            "my-organization/*": "source",
            "partner-organization/*": "auto",
            "*": "dist"
        }
    }
}
# package.josn

{ "name": "ethopia-waza",
  "description": "a delightfully fruity coffee varietal",
  "version": "1.2.3",
  "devDependencies": {
    "coffee-script": "~1.6.3"
  },
  "scripts": {
    "prepare": "coffee -o lib/ -c src/waza.coffee"
  },
  "main": "lib/waza.js"
}

YAML

在GitLab中CI构建配置文件是采用YML。原因是在构建的过程需要存储复杂业务流程,整体逻辑较多,YML适合应用于这类场景。

# see https://gitlab.com/help/ci/yaml/README.md#jobs
# Select image
image: geekwho/code:php
stages:
  - phplint
  - phpfixer
  - build
  - test
  - deploy

# php lint
phplint:
  stage: phplint
  script:
  - bash bin/phplint.sh
  only:
  - master
  - prelease
  - dev

# php code style
phpfixer:
  stage: phpfixer
  script:
  - bash bin/phpfixer.sh
  only:
  - master
  - prelease
  - dev

# unit test
test:
  stage: test
  script:
  - bash bin/test.sh
  only:
  - master
  - prelease
  - dev
  allow_failure: true

PHP数组

最简单的配置系统可以直接采用PHP自带的数组。数组的实现本身也是hash table 。

<?php
// 直接返回数组元素
return [
    'database' => [
      'adapter'     => 'Mysql',
      'host'        => 'localhost',
      'username'    => 'root',
      'password'    => '',
      'dbname'      => '@@name@@',
      'charset'     => 'utf8',
    ],
    'version' => '1.0',

    /**
     * if true, then we print a new line at the end of each execution
     *
     * If we dont print a new line,
     * then the next command prompt will be placed directly on the left of the output
     * and it is less readable.
     *
     * You can disable this behaviour if the output of your application needs to don't have a new line at end
     */
    'printNewLine' => true,
];
// 可以在配置里做一些逻辑判断
<?php

$dsn = 'mysql:host=localhost;dbname=yii2basic';
return [
    'class' => 'yii\db\Connection',
    'dsn' => $dsn,
    'username' => 'root',
    'password' => '',
    'charset' => 'utf8',
];
// 甚至还可以使用静态变量
<?php

class Config {
    public static $db = 'mysql';
    public static $user = 'root';
    public static $passwd = '';
    public static $host = 'locahost';

}

Bash

甚至有时候在Bash环境也需要添加部分文件,我是这样做的。

# @Author: geekwho
# @Date:   2018-09-18 11:11:11
# @Last Modified by:   geekwho
# @Last Modified time: 2018-09-17 11:11:11
# 配置文件
project="wiki"

domain=".xbc.me"

user="geekwho"

host="your host"

port="your port"

dest=""

# 然后在你需要使用变量的地方进行加载
source ./.config.sh

环境变量

很多时候我们需要判断的环境变量,比如dev、stg、pre、pro等不同环境。

项目目录

这类方式就是直接在当前项目下建一个隐藏文件,类似 .env.dev 代表当前的环境。不好的地方,因为在当前项目下,容易被误修改或删除,导致环境判断异常。

php.ini

直接在php.ini添加自定义的环境变量,例如env=dev。那么在bash环境下,还需要单独去做一次环境变量的解析。

/etc

直接在系统文件下建立一个环境变量的文件。例如/etc/.env.dev。这类操作需要运维配置设置对应环境的变量文件。

两者结合

如何把环境变量和配置系统结合起来呢?

根据环境变量独立配置文件

推荐的配置目录结构如下:

app
    config
        dev
            config.php
        test
            config.php
        pre
            config.php
        prd
            config.php
// 实例
<?php

class Config {
    public static $db = 'mysql';
    public static $user = 'root';
    public static $passwd = '';
    public static $host = 'locahost';

}

All in ONE

全部环境变量全部都写同一个文件里。

#app/config/db.php
<?php

class Config {
    public static $db = [
      'dev' => 'mysql',
      'test' => 'mysql',
      'pre' => 'mysql',
      'prd' => 'mysql',
    ];
    public static $user_dev = [
      'dev' => 'root',
      'test' => 'root',
      'pre' => 'root',
      'prd' => 'root',
    ];
    public static $passwd = [
      'dev' => '',
      'test' => '',
      'pre' => '',
      'prd' => '',
    ]
    public static $hos = [
      'dev' => 'localhost',
      'test' => 'localhost',
      'pre' => 'localhost',
      'prd' => 'localhost',
    ]

}

参考链接

  1. phalcon config
  2. magento config
  3. composer config
  4. npm config
  5. GitLabv CI YAML
  6. PHP Array
  7. LaraDock

OOP深入

以前只是在课堂学习过面向对象的技术,而在实际的编程时基本没有实践过的

呵呵 现在只好拣起来了 看看咯 oop详细解释见百度百科

面向对象:

封装
就是把类的内部隐藏起来
好处:减少耦合 ,类内部的实现可以自由地修改,类具有清晰的对外接口

数据隐藏
实现方法就是 访问限制修饰符
public
protected internal
internal
protected
private

继承性   inheritance
一个类可以有能力直接从另一个类获得其代码和数据
派生类从基类那里获得其所有的成员
C#只支持单继承
防止继承  public sealed class  classname

何时使用继承

代码重用,减少编写的代码量
设计重用 公用的字段和方法可以放到父类中,然后由其派生新的子类
子类有自己的字段和方法

多态性
是面向对象程序设计中的重要概念。
在运行时,可以通过指向基类的应用,来调用实现派生类中的方法。
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
多态性通过派生类覆写基类中的虚函数型方法来实现。

重载Overload 存在于同一类中
方法名必须相同
参数列表必须不相同
返回类型可以不相同

覆写Override 存在与继承关系的类中
只有虚方法和抽象方法才能覆写
相同的方法名称
相同的参数列表
相同的返回值类型

抽象方法 abstract
是必须被派生类覆写的方法
可以看成没有实现体的虚方法

虚函数
使用virtual关键字public virtual bool withdraw()

接口 interface
接口为类提供了蓝图
接口只提供定义
实现接口的数据类型必须提供接口成员的实现
接口本身可以从多个基接口派生

ps:

这里有一篇实例的OOP思想、工厂模式和重构

C#的接口