Yii2 源码学习 Event

30 Oct 2016 Category: PHP

虽然PHP语言本身不能实现异步事件,但是并不代表事件在PHP中不是 一个非常有效的代码注入方式。TinkSNS中就有钩子,允许用户在不修 改主体业务代码的时候执行不同的操作。钩子对于一些活动,比如充 值送积分,充值打折扣。不同的活动,不一样,如果每次都要修改充 值业务的主体代码,不仅麻烦,而且容易产生bug。因此可以在充值成 功之后触发充值成功的事件,在事件处理中去处理相应的返现,优惠打 折操作。

看一下Event类的主体结构


class Event extends Object{

	public $name;
	public $sender;
	public $handled = false;
	private static $_events = [];
	public static function on($class, $name, $handler, $data = null, $append = true) {

	}

	public static function off($class, $name, $handler = null) {

	}

	public static function offAll() {}

	public static function hasHandlers($class, $name) {

	}

	public static function trigger($class, $name, $event = null) {

	}
}

整个类非常简单。事件绑定,事件解绑,解绑所有事件,是否绑定事件,触发事件五个函数,实现 事件处理机制。所有通过静态方法绑定的事件都存储在静态变量_events中。

1.事件绑定

	public static function on($class, $name, $handler, $data = null, $append = true) {
		$class = ltrim($class, '\\');
		if ($append || empty(self::$_events[$name][$class])) {
			self::$_events[$name][$class][] = [$handler, $data];
		} else {
			array_unshift(self::$_events[$name][$class], [$handler, $data]);
		}
	}

事件通过调用Event::on($class,$eventName,$handler,$data,$append)进行绑定。在event内部,相同类名, 相同事件名的事件handler和数据data放入数组中。根据$append的值添加到事件数组最末尾或最前面。

2.事件解绑


	public static function off($class, $name, $handler = null) {
		$class = ltrim($class, '\\');
		if (empty(self::$_events[$name][$class])) {
			return false;
		}
		if ($handler === null) {
			unset(self::$_events[$name][$class]);
			return true;
		} else {
			$removed = false;
			foreach (self::$_events[$name][$class] as $i => $event) {
				if ($event[0] === $handler) {
					unset(self::$_events[$name][$class][$i]);
					$removed = true;
				}
			}
			if ($removed) {
				self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
			}

			return $removed;
		}
	}

事件通过调用Event::off($class,$eventName,$handler)进行事件处理函数解绑。 通过handler的值解绑类的某个事件处理,删除在$_events中的数据。如果事件不存在,则返回false.如果handler 是空的,则解除这个类这个事件的所有事件处理函数。如果事件handler不为空,则删除指定的handler,并重新生 成事件handler数组(事件执行需要循环)。

3.事件触发


	public static function trigger($class, $name, $event = null) {
		if (empty(self::$_events[$name])) {
			return;
		}
		if ($event === null) {
			$event = new static;
		}
		$event->handled = false;
		$event->name = $name;

		if (is_object($class)) {
			if ($event->sender === null) {
				$event->sender = $class;
			}
			$class = get_class($class);
		} else {
			$class = ltrim($class, '\\');
		}

		$classes = array_merge(
			[$class],
			class_parents($class, true),
			class_implements($class, true)
		);

		foreach ($classes as $class) {
			if (!empty(self::$_events[$name][$class])) {
				foreach (self::$_events[$name][$class] as $handler) {
					$event->data = $handler[1];
					call_user_func($handler[0], $event);
					if ($event->handled) {
						return;
					}
				}
			}
		}
	}

在业务中,调用Event::trigger($class,$eventName,$event),触发事件,执行事件处理函数。 从_events数组中找到绑定改事件名的所有类,获取传入$class的类名,所有父类的类名,实现接口的 类名。判断这些类名是否在绑定改事件的类数组中,如果在则获取handler数组,调用call_user_func 执行。事件handler中,可以设置event->handled的值去停止执行后续事件处理函数。

从上面的触发机制可以看出来,子类触发事件,会触发父类的事件。因此在事件定义中,尽量把事 件安排在子类中。同时,如果需要传入在事件处理中传入参数,需要在事件绑定中传入data,不能通 过$event传入.

Yii中的事件分三个级别。一个是由Event绑定触发的类级别事件。还有就是由Commponent绑定的对象级别, 还有Yii::$app绑定触发的全局级别事件。

  • 类级别事件绑定触发
class Handler{
	public static afterPay($event){

	}
}
class Recharge{
	$chargeMoney;
	$user;

	public function pay(){
		//to-do pay for charge
		if($res)
		{
			Event::on(get_class($this),"AFTER_PAY",[Handler,afterPay],['money'=>$chargeMoney,'user'=>$user]);
			Event::trigger(get_class($this),"AFTER_PAY");
		}
	}
}

  • Commponent对象级别事件
class Recharge extends Component{


	public function pay(){
		//to-do pay for charge
		if($res)
		{
			$this->on("AFTER_PAY",[Handler,afterPay],['money'=>$chargeMoney,'user'=>$user]);
			$this->trigger("AFTER_PAY");
		}
	}	
}

Component 事件触发函数

    public function trigger($name, Event $event = null)
    {
        $this->ensureBehaviors();
        if (!empty($this->_events[$name])) {
            if ($event === null) {
                $event = new Event;
            }
            if ($event->sender === null) {
                $event->sender = $this;
            }
            $event->handled = false;
            $event->name = $name;
            foreach ($this->_events[$name] as $handler) {
                $event->data = $handler[1];
                call_user_func($handler[0], $event);
                // stop further handling if the event is handled
                if ($event->handled) {
                    return;
                }
            }
        }
        // invoke class-level attached handlers
        Event::trigger($this, $name, $event);
    }

可以看出,Component事件触发跟Event的触发类似,重点在触发函数设置sender。类级别的sender是类名 对象级别的sender是对象。

查看更多

Yii2 源码学习--yii\base\Object

24 Oct 2016 Category: PHP

在Yii2中,所有的类的都集成于基类Object。Object对象通过几个php的魔术方法, 实现属性获取,设置,属性是否存在,属性是否可设置的方法。

1.构造函数

    public function __construct($config = [])
    {
        if (!empty($config)) {
            Yii::configure($this, $config);
        }
        $this->init();
    }

Object实现接口Configurable,因此可以通过构造函数通过传入config配置数组对 对象属性进行注入。对象创建之后,调用init方法执行用户设置的初始化。因此, 在Yii2中,如果我们要在某个类初始化执行相应的操作,应该重写init函数,而不 是构造函数。

2.获取属性

$value = $object->propert

对于对象中定义的public属性,直接返回。如果获取的属性没有定义为public,或者 没有定义,将执行魔术方法_get。

    public function __get($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
            return $this->$getter();
        } elseif (method_exists($this, 'set' . $name)) {
            throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
        } else {
            throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
        }
    }

Object的魔术方法_get通过调用类中定义的get方法获取属性。如果对应的get方法不 存在,则根据对应set方法是否存在抛出属性不存在,或属性不可读的异常。

3.属性设置

    public function __set($name, $value)
    {
        $setter = 'set' . $name;
        if (method_exists($this, $setter)) {
            $this->$setter($value);
        } elseif (method_exists($this, 'get' . $name)) {
            throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
        } else {
            throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
        }
    }

Object的魔术方法_get通过调用类中定义的set方法获取属性。如果对应的set方法不 存在,则根据对应get方法是否存在抛出属性不存在,或属性不可设置的异常。

4.属性检测

    public function hasProperty($name, $checkVars = true)
    {
        return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false);
    }

    public function canGetProperty($name, $checkVars = true)
    {
        return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name);
    }

    public function canSetProperty($name, $checkVars = true)
    {
        return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name);
    }

通过hasProperty判断属性是否存在(包括只读,只写的属性)。如果$checkVars为true,则检查类的property。否 则只检查set方法,get方法是否存在。

4.属性是否设置

当调用isset($object->property),如果property不存在,将会调用。如果该属性未定义或者该属性值为null,将返 回false

    public function __isset($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
            return $this->$getter() !== null;
        } else {
            return false;
        }
    }

5.unset属性

当调用unset($object->property),如果property不存在,将会调用。如果该属性未定义值抛出异常,否则调用set方法 将属性设置成null

    public function __unset($name)
    {
        $setter = 'set' . $name;
        if (method_exists($this, $setter)) {
            $this->$setter(null);
        } elseif (method_exists($this, 'get' . $name)) {
            throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name);
        }
    }

查看更多

PHP 依赖注入

18 Aug 2016 Category: PHP

考虑一个问题,如果一个web应用需要一个日志服务,日志服务可以是文本,数据库或者邮件的形式, 而且日志需要将获取的信息格式化指定的形式。应用可以根据需要,任意切换日志服务是文本还是数 据库还是邮件。如果以传统的方式,日志记录的代码类似下面的形式:



    class Logger{
        public function formatLog($log)
        {
            //TO-DO format logger
            return $log;
        }

        public function log($str,$type)
        {
            $logTxt = $this->format($str);
            swtich($type)
            {
                case 'database':
                    $this->databaseLog($logTxt);break;
                case 'file':
                    $this->fileLog($$logTxt);break;
                case 'email':
                    $this->emailLog($$logTxt);break;
            }
        }

        public function databaseLog($str)
        {
            $db = new DbHelper();
            $db->save('logger',$str);
        }

        public function fileLog($str)
        {
            $file = new FileHelper();
            $file->save('logger',$str);
        }

        public function emailLog($str)
        {
            $email = new EmailHelper();
            $email->send(ADMIN_EMAIL,$str);
        }
        ...
    }


从上面的代码中可以看出来,logger类依赖数据库操作类DbHelper,依赖文件操作类FileHelper, 依赖邮箱操作类FileHelper。但是这些关系都是写在Logger这个类文件中的,耦合性高。如果项目 中希望在不同的页面将日志记录在不同的文件,数据表,或者发送给不同的管理者邮箱呢?这个时 候就需要对类的依赖在不同的地方进行注入。在百度百科上,依赖注入 说的太高深了,就是解除代码之间的耦合关系。

注入有一下几种方式

  • 通过构造函数传递参数
    class Logger{
        public $loggerHnadler;
        public __construct($loggerHandler)
        {
            $this->loggerHandler = $this->loggerHandler;
        }

        public function log($str)
        {
            $this->loggerHnadler->log($str);
        }
    }

    class FileLoggerHandler{
        $public $filePath;
        public __construct($filePath)
        {
            $this->filePath = $this->filePath;
        }

        public function log($str)
        {
            file_put_contents($this->filePath,$str);
        }
    }

    class EmailLoggerHandler{
        $public $adminMail;
        public __construct($adminMail)
        {
            $this->adminMail = $this->adminMail;
        }

        public function log($str)
        {
            sendEmail($this->adminMail,$str);
        }
    }

    //调用

    $fileLoggerHandler = new FileLoggerHandler(WEB_ROOT.'/logger/login.log');

    $logger = new Logger(fileLoggerHandler);
    $logger->log('login error'.getError());

    $emailLoggerHandler = new EmailLoggerHandler(DEV_ABB_EMAIL);
    $logger = new Logger(emailLoggerHandler);
    $logger->log('login error'.getError());


  • 通过set方式进行注入 这种方式跟构造函数类似,只不过是在实例化类之后对其属性进行复制设置。
    $logger = new Logger();
    $logger->setLoggerHandler(fileLoggerHandler);
    $logger->log();
    $logger->setLoggerHandler(emailLoggerHandler);
    $logger->log();

上面的两种注入方式都需要提前准备依赖的类对象。加速文件日志类依赖文件读写类,依赖 目录操作,权限验证等相关类文件,则需要按依赖顺序构造好相应的类对象,并且对各个对 象进行依赖的注入。所以需要重复很多对象创建,set的操作。因此就有了依赖注入容器 container,用于实现类的依赖对象的管理等操作。github上有一个最最简单的Container Twittee,简单而有效。

    class Container{
        public $cont = array();
        function __set($k, $c) { $this->s[$k]=$c; }
        function __get($k) { return $this->s[$k](); }
    }

    class Logger{
        public $container;

        public __construct()
        {
            $this->container = new Container();
        }

        public function addContainerData($key,$class)
        {
            $this->container->set($key,$class);
        }

        public function log($containerKey,$str)
        {
            $this->container->get($containerKey)->log();
        }
    }


    //使用

    $logger = new Logger();
    $logger->addContainnerData('fileLog','FileLoggerHandler');
    $logger->log('error info');

这个最简单的注入容器解决了依赖对象创建繁杂的问题。但是没有解决注入对象依赖问题。所以 一些框架的依赖注入容器中添加了获取对象依赖的操作:


    class Container{
        public $cont = array();
        function __set($k, $c) { $this->s[$k]=$c; }
        function __get($k) { return $this->s[$k](); }
        function getDepends($key)
        {
            $class = $this->s[$k];
            $dependencies = array();
            $ref = new ReflectionClass($class);//获取对象的实例
            $constructor = $ref->getConstructor();//获取对象的构造方法
            if($constructor !== null)
            {//如果构造方法有参数
                foreach($constructor->getParameters() as $param)
                {//获取构造方法的参数
                    if($param->isDefaultValueAvailable())
                    {//如果是默认 直接取默认值
                        $dependencies[] = $param->getDefaultValue();
                    }
                    else
                    {//将构造函数中的参数实例化
                        $temp = $param->getClass();
                        $temp = ($temp === null ? null : $temp->getName());
                        $temp = Instance::getInstance($temp);//这里使用Instance 类标示需要实例化 并且存储类的名字
                        $dependencies[] = $temp;
                    }
                }
            }
            $this->_reflections[$class] = $ref;
            $this->_dependencies[$class] = $dependencies;
        }
    }

    //使用

    $logger = new Logger();
    $logger->addContainnerData('fileLog','FileLoggerHandler');//这里会自动创建FileLoggerHandler依赖类对象
    $logger->log('error info');

查看更多

PHP GC学习

17 May 2016 Category: PHP

在5.2及更早版本的PHP中,没有专门的垃圾回收器GC(Garbage Collection),引擎在判断 一个变量空间是否能够被释放的时候是依据这个变量的zval的refcount的值,如果refcount 为0,那么变量的空间可以被释放,否则就不释放。思考一个问题:

$a = array('one');
$a[] = &$a;
unset($a);

在执行unset之前,PHP进程中变量的指向:

执行上述代码之后,PHP进程中变量的指向:

那么问题来了,那些在内存里的,没有被任何变量指向,但是指向自身引用的变量容器怎么 被回收?如果采用引用计数器的方式进行GC,那么如果出现上述这种自身引用的情况就容易 造成内存泄漏。PHP5.3之后引入一种新的垃圾回收机制,用于处理类似”垃圾”数据。

  • 如果一个zval的refcount增加,那么此zval还在使用,不属于垃圾
  • 如果一个zval的refcount减少到0, 那么zval可以被释放掉,不属于垃圾
  • 如果一个zval的refcount减少之后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾,进一步判断

为了避免每次refcount减少都进行GC判断,算法会先把所有可能是垃圾的zval节点放入一个节点(root)缓冲区(root buffer),当缓冲区被节点塞满的时候,GC才开始开始对缓冲区中的zval节点进行垃圾判断。并且将这些zval节点标记成紫色

当缓冲区满了之后,算法以深度优先对每一个节点所包含的zval进行减1操作,为了确保不会对同一个zval的refcount重复执行减1操作,一旦zval的refcount减1之后会将zval标记成灰色。需要强调的是,这个步骤中,起初节点zval本身不做减1操作,但是如果节点zval中包含的zval又指向了节点zval(环形引用),那么这个时候需要对节点zval进行减1操作。

算法再次以深度优先判断每一个节点包含的zval的值,如果zval的refcount等于0,那么将其标记成白色(代表垃圾),如果zval的refcount大于0,那么将对此zval以及其包含的zval进行refcount加1操作,这个是对非垃圾的还原操作,同时将这些zval的颜色变成黑色(zval的默认颜色属性)。 D:遍历zval节点,将C中标记成白色的节点zval释放掉。

查看更多

Yii 作为模块被调用

22 Apr 2016 Category: PHP

Yii以及其他PHP框架,通常是通过一个入口文件把框架类库,引入进来。 然后根据route找到指定的控制器执行业务逻辑。一般的框架都可以很容易的集成第三方类库。 可是,如果说,多个项目之间需要相互调用,而且多个项目之间不是用相同的框架写的,但是是同一个语言。 如果不是相同语言,就只好是各个框架之间开放不同的接口,通过rest或者soap的形式进行接口调用。 虽然把各个模块封装成接口,可以很大的降低项目之间的耦合。但是接口同时也包含代码调用的形式。 项目中用到了workman作为消息发送,业务逻辑采用yii处理。那么问题来了,workman如何调用yii

问题

workman是用php命令方式执行的,yii2是跑在apache上面。如果采用rest调用方式, 消息经workman转发到yii2上进行处理,然后在返回到workman中。这个过程就会参数一个内部的网络io, 同时每个请求都需要一个apache进程,以及workman的进程才能进行处理。对主机有一定的压力。 采用node,每秒创建一个socket连接,创建3000个,每次连接上之后,workman根据连接创建一个uuid, 并返回到workman的客户端。如果采用rest的方式请求yii,请求在处理一部分的时候,服务器就挂掉了(本地采用xampp默认配置)。 如果数据不到400个。而如果直接通过workman直接进行数据库操作,数据都可以入库。

解决

workman与yii本来就在一台主机上,如果两个项目能够融合,直接在代码层进行调用,那数据的丢失率就可以下降。 所以要做的是将yii2z作为一个模块被其他项目调用。观察yii2的入口文件:


defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
defined('YII_ENV_TEST') or define('YII_ENV_TEST', true);
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/vendor/yiisoft/yii2/Yii.php';
require __DIR__ . '/common/config/bootstrap.php';

$config = yii\helpers\ArrayHelper::merge(
    require (__DIR__ . '/common/config/main.php'),
    require (__DIR__ . '/common/config/main-local.php'),
    require (__DIR__ . '/frontend/config/main.php'),
    require (__DIR__ . '/frontend/config/main-local.php')
);

$application = new yii\web\Application($config);
$application->run();

yii2在入口创建一个application,调用application的run方法处理请求,run方法代码:

    $this->state = self::STATE_BEFORE_REQUEST;
    $this->trigger(self::EVENT_BEFORE_REQUEST);

    $this->state = self::STATE_HANDLING_REQUEST;
    $response = $this->handleRequest($this->getRequest());

    $this->state = self::STATE_AFTER_REQUEST;
    $this->trigger(self::EVENT_AFTER_REQUEST);

    $this->state = self::STATE_SENDING_RESPONSE;
    $response->send();

    $this->state = self::STATE_END;

    return $response->exitStatus;
    public function handleRequest($request) {
        if (empty($this->catchAll)) {
            list($route, $params) = $request->resolve();
        } else {
            $route = $this->catchAll[0];
            $params = $this->catchAll;
            unset($params[0]);
        }

        try {
            Yii::trace("Route requested: '$route'", __METHOD__);
            $this->requestedRoute = $route;

            $result = $this->runAction($route, $params);

            if ($result instanceof Response) {
                return $result;
            } else {

                $response = $this->getResponse();
                if ($result !== null) {
                    $response->data = $result;
                }

                return $response;
            }
        } catch (InvalidRouteException $e) {
            throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), $e->getCode(), $e);
        }
    }
  • 第一个问题,如何将参数通过传参而非网络请求的形式传进来,同时从参数中获取路由。

可以看到,yii2通过Request的resolve()函数获得请求的地址以及参数。 因此在resolve()函数中添加一下代码

    if ($this->_route) {
            return [$this->_route, $this->getQueryParams()];
    }

并在Request中添加一个_route私有属性,通过函数setRoute设置路由

    public function setRoute($value) {
        $this->_route = $value;
    }

路由参数已经传入,需要传入请求参数。请求参数可以调用Request的

setQueryParams($value),设置GET参数
setBodyParams($value),设置post参数
  • 第二个问题,如何获取返回。

查看Response代码,请求处理之后的结果格式化后的结果内容在Response的content当中, yii\web\Response中通过send向终端输出数据。因此$application->run();之后可以给根 据Yii::$app->response->content获取返回接口,return给调用函数。

  • 第三个问题,异常怎么处理

yii2通过设置errorHandler处理程序中的异常,保证返回结果不会太难看。如果程序调用之后呢? 通过跟踪程序发现,抛出异常之后,会执行errorHandler,并执行Response send的方法。 但是,调用函数还是会抛出异常。为什么,我也不知道!!

  • 其他问题:

    如果重写Response的send方法,去掉输出,程序是报错的。。。。。,只要有输出(echo,var_dump,print..),程序有没问题了。

    需要设置scriptUrl,scriptFile;

最终入口文件

defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
defined('YII_ENV_TEST') or define('YII_ENV_TEST', true);
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/vendor/yiisoft/yii2/Yii.php';
require __DIR__ . '/common/config/bootstrap.php';

require __DIR__ . '/common/helper/apiApplication/Application.php';

function callYiiController($headerDatas, $data) {

    $config = \yii\helpers\ArrayHelper::merge(
        require (__DIR__ . '/common/config/main.php'),
        require (__DIR__ . '/common/config/main-local.php'),
        require (__DIR__ . '/rest/config/main.php'),
        require (__DIR__ . '/rest/config/main-local.php')
    );
    $config['components']['request']['scriptUrl'] = "/" . basename(__FILE__);
    $config['components']['request']['scriptFile'] = basename(__FILE__);
    $config['components']['response']['class'] = 'common\helper\apiApplication\Response';
    $config['components']['response']['format'] = \common\helper\apiApplication\Response::FORMAT_JSON;
    $application = new \common\helper\apiApplication\Application($config);

    $headers = Yii::$app->request->getHeaders();
    foreach ($headerDatas as $key => $headerData) {
        $headers->add($key, $headerData);
    }
    Yii::$app->request->setBodyParams(['_json' => $data]);

    $application->request->setRoute('api/device/socket/index');
    try {
        $application->run();

    } catch (\Exception $e) {
        $errorHandler = $application->errorHandler;
        $errorHandler->handleException($e);
    }
    Yii::$app->db->close();

    return $application->response->content;
}

查看更多

ubuntu 14.04 mysql Atlas 读写分离 环境配置安装

09 Mar 2016 Category: Developer

Atlas 是Qihoo 360, Web平台部基础架构团队在mysql_proxy基础上开发维护开发的一款mysql 中间件,360内部使用Atlas运行的mysql业务,每天承载的读写请求数达几十亿条。更多mysql 中间件可以参考 http://www.guokr.com/blog/475765/.

Altas的一些新特性:

  • 1.主库宕机不影响读
  • 2.通过管理接口,简化管理工作
  • 3.实现读写分离
  • 4.实现分表 (1)需带有分表字段。 (2)支持SELECT、INSERT、UPDATE、DELETE、REPLACE语句。 (3)支持多个子表查询结果的合并和排序。 (4)子表必须在同一台DB的同一个database里且所有的子表必须事先建好,Atlas没有自动建表的功能。

注意: Altas 不涉及数据主从同步,需要自己配置mysql 主从同步

mysql主从同步配置

mysql 主从同步配置是基于二进制日志进行的,因此需要开启数据库二进制日志。 查看是否开启二进制日志,可以进入mysql之后,执行SHOW BINARY LOGS;如果执行该 语句之后提示错误,则需要在my.cnf文件中的[mysqld]下添加

log-bin=mysql-bin #开启二进制日志 log-bin = /var/log/mysql/mysql-replication.log #指定二进制日志文件,必须

同时开启二进制日志,还需要注释skip-external-locking

在master数据库中,my.cnf文件配置:

server-id = 1 # 主库设置1
log-bin=mysql-bin #开启二进制日志
binlog-do-db=yii #需要同步的数据库,多个,添加多条binlog-do-db=database
binlog-ignore-db=mysql #不要同步的数据库
log-bin = /var/log/mysql/mysql-replication.log
#skip-external-locking

配置文件修改后,还需要为从库提供一个登录master数据库的用户名和密码,用于同步数据。


 grant replication slave on *.* to 'salveuser'@'%' identified by 'password';

重启master mysql服务,进入mysql,SHWO MASTER STATUS;查看master 服务器状态


+--------------------------+----------+--------------+------------------+
| File                     | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+--------------------------+----------+--------------+------------------+
| mysql-replication.000002 |      333 |              |                  |
+--------------------------+----------+--------------+------------------+

在salva 数据库,my.cnf配置


server-id = 2 #从库编号,大于1,一直往上加
log-bin=mysql-bin #开启二进制日志
replicate-ignore-db=mysql#不要同步的数据库
replicate-do-db=yii #开启二进制日志
log-bin = /var/log/mysql/mysql-replication.log
#skip-external-locking

在salve 数据mysql中执行下列sql语句,


CHANGE MASTER TO MASTER_HOST='master host',
MASTER_USER='salveuser',
MASTER_PASSWORD='111111',
MASTER_LOG_FILE='mysql-replication.000002',
MASTER_LOG_POS=333;

重启master mysql服务,进入mysql,start salve; show salve status\g;salve 服务器状态, 确认从服务器连接上主服务器。

Altas 安装配置

ubuntu 下载Atlas-2.2-debian7.0-x86_64.deb,下载之后,执行dpkg -i Atlas-2.2-debian7.0-x86_64.deb。 配置文件/usr/local/mysql-proxy/conf/test.conf

根据注释修改配置文件。默认注释的都不用修改,只有一项,#pwds = user1:+jKsgB3YAG8=, user2:GS+tr4TPgqc= 这一项如果不配置,无法登陆到数据库。

这里配置的是主库,从库的用户名以及密码,密码需要用/usr/local/mysql-proxy/bin/encrypt password 得到加密后的值

例如我的数据库用户名密码是 datauser , 111111

通过 /usr/local/mysql-proxy/bin/encrypt 111111得到的输出是kOVJsquUepY=

因此pwds=datauser:kOVJsquUepY=

配置完之后就可以使用了。

默认管理入口mysql -uusr -ppwd -P2345 -h127.0.0.1 对外sql接口 mysql -udatauser -p111111 -P1234 -h127.0.0.1 在程序中的数据库连接地址也采用对外接口的地址,Altas 会根据执行的sql语句分发到主库和从库当中

查看更多

http,tcp,udp协议

07 Mar 2016 Category: 基础

  • tcp协议是面向连接的传输层网络协议
  • tcp数据传输之前需要与接收方建立连接,进行三次握手之后才可以传输数据
  • tcp传输是可靠的,因为数据传输之前,发送接收方需要建立连接,进行数据传输同步,应用于大量数据传输的场景,传输速度慢
  • tcp连接是有状态的,长连接,除非网络中断或主动断开,连接才会中断
  • tcp,客户端向服务器发送syn(syn=j)进入SYN_SEND状态,服务器回复ACK(ack=j+1),同时发送SYN(syn=k)进入SYN_RECV状态,客户端发送ACK(ack=k+1),进入ESTABLISTION状态

udp协议

  • udp也是属于传输层网络协议
  • udp协议是非面向连接的,传输数据之前不需要与接收方建立连接
  • udp传输不可靠,应用于少量数据传输的场景,传输速度快

http协议

  • http协议是建立在tcp协议基础之上的应用层网络协议
  • http连接是无状态的,短连接,Http是一个基于请求/响应模式的、无状态的协议,每次请求完之后,连接就关闭
  • http连接,请求-响应式消息发送,服务器数据需要客户端请求才能发送到客户端。

HTTP的Request/Response:

先看Request 消息的结构,Request 消息分为3部分

  • 第一部分叫Request
    GET http://www.cnblogs.com/ HTTP/1.1
    

    Request line 指明请求方式,请求地址,以及http协议版本

  • 第二部分叫Request header

Accept: text/html 指明浏览器端可以接受的媒体类型,Accept: / 代表浏览器可以处理所有类型

Referer:http://translate.google.cn/?hl=zh-cn&tab=wT 提供了Request的上下文信息的服务器,告诉服务器我是从哪个链接过来的

Accept-Language: en-us 作用: 浏览器申明自己接收的语言。

Content-Type: application/json 作用: 浏览器申明请求数据的格式。

Accept-Encoding: gzip 浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩

User-Agent 告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本

Connection: keep-alive 当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭, 如果close,则请求之后立即关闭

Content-Length:888 作用:发送给HTTP服务器数据的长度。

Host(发送请求时,该报头域是必需的) 请求报头域主要用于指定被请求资源的Internet主机和端口号,它通常从HTTP URL中提取出来的

  • 第二部分叫Response

第一部分叫request line, 第二部分叫request header,第三部分是body

request line:协议版本、状态码、message

  • 第二部分叫Response header

    Location响应报头域用于重定向接受者到一个新的位置。Location响应报头域常用在更换域名的时候。 Server响应报头域包含了服务器用来处理请求的软件信息。与User-Agent请求报头域是相对应的。 Content-Encoding用于记录文档的压缩方法 Content-Language实体报头域用于指明实体正文的长度,以字节方式存储的十进制数字来表示。 Content-Type实体报头域用语指明发送给接收者的实体正文的媒体类型 Last-Modified实体报头域用于指示资源的最后修改日期和时间。 Expires实体报头域给出响应过期的日期和时间。

  • http code
"200" : OK

"201" : Created 已创建

"202" : Accepted 接收

"203" : Non-Authoritative Information 非认证信息

"204" : No Content 无内容

"205" : Reset Content 重置内容

"206" : Partial Content 部分内容

重定向

"300" : Multiple Choices 多路选择

"301" : Moved Permanently  永久转移

"302" : Found 暂时转移

"303" : See Other 参见其它

"304" : Not Modified 未修改

"305" : Use Proxy 使用代理

"307" : Temporary Redirect

客户方错误

"400" : Bad Request 错误请求

"401" : Unauthorized 未认证

"402" : Payment Required 需要付费

"403" : Forbidden 禁止

"404" : Not Found 未找到

"405" : Method Not Allowed 方法不允许

"406" : Not Acceptable 不接受

"407" : Proxy Authentication Required 需要代理认证

"408" : Request Time-out 请求超时

"409" : Conflict 冲突

"410" : Gone 失败

"411" : Length Required 需要长度

"412" : Precondition Failed 条件失败

"413" : Request Entity Too Large 请求实体太大

"414" : Request-URI Too Large 请求URI太长

"415" : Unsupported Media Type 不支持媒体类型

"416" : Requested range not satisfiable

"417" : Expectation Failed

服务器错误

"500" : Internal Server Error 服务器内部错误

"501" : Not Implemented 未实现

"502" : Bad Gateway 网关失败

"503" : Service Unavailable

"504" : Gateway Time-out 网关超时

"505" : HTTP Version not supported  HTTP版本不支持

查看更多

php 设计模式 一

24 Feb 2016 Category: 基础

程序运行期间只有一个实例对象。单例模式类似于全局变量,在整个应用运行期间,共同操 作通一个对象。

class App{
    public static $appInstance;

    private $name;


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

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}

使用

$app = App::getInstance();
$app->setName('news');
echo $app->name;

##2.工厂模式

通过给工厂类指定不同的创建需求创建不同的类实例;

class People{
    function __construct($sex)
    {
        switch($sex){
            case 'male':
                return new Male();
            case 'female':
                return new Female();
        }
    }
}

class Male{
    public $sex = 'male';
}

class Female{
    public $sex = 'female';
}

使用:

$male = new People('male');
$female = new People('female');

##3.观察者模式

观察者模式是一个对象被多个对象观察(订阅),当这个被观察者对象改变时需要通知订阅者

class Subject{
    public $message;
    public $observes = [];
    /**
     * 被观察对象
     */
    public function addObserve($observe)
    {
        if(!in_array($observe,$this->observes))
        {
            $this->observes[] = $observe;
        }
    }

    public function deleteObserve($observe)
    {
        $index = array_search($observe,$this->observes);
        if($index !== false)
        {
            unset($this->observes[$index]);
        }
    }

    private function notifyObserve()
    {
        foreach($this->observes as $observe)
        {
            $observe->notify($this);
        }
    }

    public function setMessage($message)
    {
        $this->message = $message;
        $this->notify();
    }
}

class ObserveNews()
{
    public function notify($subject)
    {
        //TO-DO something
        echo 'news new message '.$subject->message;
    }
}

class ObserveWeibo()
{
    public function notify($subject)
    {
        //TO-DO something
        echo 'weibo new message '.$subject->message;
    }
}

使用

$subject = new Subject();
$subject->addObserve(new ObserveNews());
$subject->addObserve(new ObserveWeibo());
$subject->setMessage('a');//这里将会输出两条数据news new message a,weibo new message a;

查看更多