早先服务,swoole支持文档

by admin on 2019年2月4日

前文连接,讲了es是怎样启动swoole服务的。

前文提到的在系统装置Cache组件
Cache::getInstance()的时候,会去调用processManager去创立Cache的进度,然后以管道通讯的方式进行安装缓存和获取缓存。

上文:早先服务,swoole支持文档。关于php的共享内存的行使和钻研之由起
下文:
关于php的共享内存的使用和钻研之深深解析swoole
table

入门指点 [编纂本页]

Swoole即使是明媒正娶的PHP增加,实际上与普通的扩充不相同。普通的恢弘只是提供一个库函数。而swoole伸张在运转后会接管PHP的控制权,进入事件循环。当IO事件暴发后,swoole会自动回调指定的PHP函数。

  • 早先服务,swoole支持文档。新手入门教程:

Swoole须要使用者必须持有一定的Linux/Unix环境编程基础,《学习Swoole需求控制怎么着基础知识》
本文列出了基础知识清单。

里面有一个工具类TableManager。这么些类为了处理进程间数据共享。是对swoole_table的一层封装
swoole_table一个基于共享内存和锁完毕的超高质量,并发数据结构。用于缓解多进度/二十四线程数据共享和同步加锁难点。

Cache是以单例方式完成的。构造器会举办如下操作

上文中涉及了针对性php的共享内存方案的品味,最终发现它并不适用于自身的气象,假若想要兼容多进度或二十四线程并发读写的境况下可信赖,一定要有确切的体制来保管资源的唯一性。

swoole_server

强劲的TCP/UDP
Server框架,二十四线程,伊芙ntLoop,事件驱动,异步,Worker进程组,Task异步任务,阿秒定时器,SSL/TLS隧道加密。

  • swoole_http_serverswoole_server的子类,内置了Http的支持
  • swoole_websocket_serverswoole_http_server的子类,内置了WebSocket的支持
  • swoole_redis_serverswoole_server的子类,内置了Redis服务器端协议的支撑

子类可以调用父类的具有办法和总体性

TableManager首要做了下边几件事
add方法
如若$list数组中有其一表名($name是一个表名或者叫做集合名),就初阶化swoole_table,然后配置的字段类型数组进行创办

//根据配置创建指定数目的Cache服务进程,然后启动。
$num = intval(Config::getInstance()->getConf("EASY_CACHE.PROCESS_NUM"));//默认配置数目是1,在Config.php里'EASY_CACHE.PROCESS_NUM'=>1
if($num <= 0){
   return;
}
$this->cliTemp = new SplArray();//这个数组以后会给单元测试时候单独使用,正常模式这个数组是不使用的
//若是在主服务创建,而非单元测试调用
if(ServerManager::getInstance()->getServer()){
    //创建了一个swoole_table ,表名为__Cache,里面存储data(后面就讲到其实这里存储的是操作Cache的指令)作用是用来做GC(防止Cache被撑爆)
    TableManager::getInstance()->add(self::EXCHANGE_TABLE_NAME,[
        'data'=>[
            'type'=>Table::TYPE_STRING,
            'size'=>10*1024
        ],
        'microTime'=>[
            'type'=>Table::TYPE_STRING,
            'size'=>15
        ]
    ],2048);
    $this->processNum = $num;
    for ($i=0;$i < $num;$i++){
        ProcessManager::getInstance()->addProcess($this->generateProcessName($i),CacheProcess::class);
    }
}

加锁肯定是想开的率先精选,对于每便共享内存的时候,先得到一个锁,只有取得成功了未来才允许开展读和写,那应当是最简单易行的方案,不过共同锁对品质的损耗也是比较大的。APC的user
data
cache的囤积机制对数码须求严峻科学,锁比较多,它的功效与地面的memcache突出。既然那样,不如把意见投向业界,看看大牛们使用什么的方案来化解那些标题。

swoole_client

TCP/UDP/UnixSocket客户端,协理IPv4/IPv6,接济SSL/TLS隧道加密,协助SSL客户端整数,支持同步并发调用,也支撑异步事件驱动编程。

if(!isset($this->list[$name])){
    $table = new Table($size);
    foreach ($columns as $column => $item){
        $table->column($column,$item['type'],$item['size']);
    }
    $table->create();
    $this->list[$name] = $table;
}

ProcessManager::getInstance()->addProcess($this->generateProcessName($i),CacheProcess::class)那句话才是Cache的主题逻辑。

YAC

原文链接:http://www.laruence.com/2013/03/18/2846.html

laurance为了化解如下的多少个问题,设计出了那个cache:

  • 想让PHP进度之间共享一些简单的多寡
  • 期望至极高效的缓存一些页面

并且也是依据如下的经历要是:

  • 对于一个使用来说, 同名的Cache键, 对应的Value, 大小大概万分.
  • 今非昔比的键名的个数是少数的.
  • Cache的读的次数, 远远超乎写的次数.
  • Cache不是数据库, 即便Cache失效也不会带来沉重错误.

落实这么些cache,关键是无锁化的设计.
依据laurance的说法,他解决读锁的措施是通过不加锁的读,然后CRC校验。
看了弹指间她代码的贯彻,是对key中蕴藏的固定size的值进行了CRC的精打细算,然后把key中附带存储的crc音信和情节计算出来的crc信息举行校验。假设校验成功了,那么认为查询成功,即使校验战败了,那么认为查询失败。其实本质上这是一种选拔CPU来换锁的艺术,半数以上的服务器是多核的,一旦加锁,对CPU是很大的浪费。上面那张图形象的标志了那或多或少:

bf88必发唯一官网 1

yac_lock.png

以此是个不利的trick,可以解决的题材就是在多少个进度频繁的写入的时候,可能引致的读出错不会带来错误的结果,因为一旦crc校验不通过,那么读出来的结果就是失效了。那明明比上一篇小说中的共享内存的读的办法要得力一些。不过据悉我的考察,使用共享内存的点子,由于直接是向后不停的写入,出现被遮住的票房价值大致平昔不,而laurance那里之所以要校验,则是因为她会举办内存的回收和巡回写入,那一点在下文中会继续注解。

今昔最首要说说这么些YAC的写入的题材,首先启动的时候key空间大小确定,可以透过配备来调动分配给存储key的轻重,从而伸张key的个数。4M大多相当于32768个Cache值。首先第四个很紧要的点就是何许安排哈希方法来幸免写入争辩,那里她采取的是双散列法的
MurmurHash.

对此小于4M的内存块的操作由于key不相同,依据哈希出来的起第二地方也分歧。分裂key之间争论的票房价值,等同于哈希算法争辩的票房价值,这一个依然比较低的。对于大的内存块,那里运用了segment->pos指针来决定内存的分块。和共享内存扩充的贯彻格局依旧比较相近了,反正就是一个pos指针,找获得就update,找不到就向后写。

那就是说一旦发生冲突呢,laurance给出了一个例证:

比如A进度申请了40字节, B进程申请了60字节, 可是Pos只增添了60字节.
这一个时候有如下三种情景:

  1. A写完了多少, 重返成功, 不过B进度又写完了数据重返成功,
    最终B进度的Cache种上了, 而A进度的被踢出了.
  2. B进度写完了数据, 重回成功, A进度又写完了数量重回成功,
    最终A进度的Cache种上了, B进程的被踢出.
  3. A进程写一半, B进度写一半, 然后A进度又写一半, B进程又写一半,
    都再次回到成功, 但最后, 缓存都失效.

看得出, 最沉痛的谬误, 就是A和B的缓存都失效,
不过Yac不会把错误数据再次回到给用户, 当下五遍来查询Cache的时候,
因为存在crc校验, 所以都miss.

看来那儿终于知道了,并没有解决多进程写的标题,多进度的写仍旧唯恐会有争执,不仅仅是单key的争论,差距key之间也可能会有争执。但是争执了就是,会经过校验的点子确保client端可以判明出来自己争论了,这一点对应用程序确实非常关键,因为那不是cache
error,而单单是cache
miss而已,这二种状态的惨重程度和处理机制真正完全分化。

除此以外还有一个亮点是内存的循环分配,如若一个内存块用完了,那么可以重置pos,从而从头起始分配,有了那种体制,尽管出现写导致pos一向后移,也不会产出内存耗尽的气象了,那的确是个正确的表征。

小结来看,yac四个不利的特点:

  • 读的CRC校验,保险最惨重是cache miss
  • 写的pos重置,有限扶助内存不被写满

不过针对自身的施用情况,多并发处境下的同key的多写多读,它并没有很好的解决那些标题,而是相比较适用于低频的用户数量的缓存,比如登陆用户的头像、昵称那类新闻。拉取频次不高,miss了也能向后端请求。所以如何是好呢,只好继续拓展求索。

ps:laurance在篇章开首群嘲了一下APC的属性,相当于本地的memcache,结果文末贴出的质量比较,yac完全比不上apc。。有点莫名

swoole_event

伊芙ntLoop
API,让用户能够一向操作底层的事件循环,将socket,stream,管道等Linux文件加入到事件循环中。

eventloop接口仅可用来socket类型的文本描述符,不可能用于磁盘文件读写

get方法
向来再次来到swoole_table的实例。

ProcessManager::getInstance()这句话主要做了上面的操作
ProcessManager
的__construct构造函数创立了一个swoole_table,表名是process_hash_map

Swoole table

收纳来说说那两年在社区其中相比火,近年来恰巧发布了放置协程2.0本子的swoole.
github地址:https://github.com/swoole/swoole-src

bf88必发唯一官网,swoole
table是swoole中的一个基于共享内存和锁完结的超高质量的出现数据结构,用来解决多进度、多线程数据共享和一起加锁的标题。那不就是我们苦苦寻觅的缓解方案么?

先来看一下swoole table的常见的使用方法,首先它辅助两种为主的系列:

  • swoole_table::TYPE_INT 整形字段
  • swoole_table::TYPE_FLOAT 浮点字段
  • swoole_table::TYPE_STRING 字符串字段

假设想要在各种进度之间共享高质量的当地数据,那么使用的范例如下:

// 新建swoole table,并且指定类型
$table = new swoole_table(1024);
$table->column('id', swoole_table::TYPE_INT, 4);       //1,2,4,8
$table->column('name', swoole_table::TYPE_STRING, 64);
$table->column('num', swoole_table::TYPE_FLOAT);
$table->create();

// 新建swoole的server
$serv = new swoole_server('127.0.0.1', 9501);
//将table保存在serv对象上
$serv->table = $table;
$serv->on('receive', function ($serv, $fd, $from_id, $data) {
    // 使用swoole table存储全局的数据
    $ret = $serv->table->set($key, array('from_id' => $data, 'fd' => $fd, 'data' => $data));
});

// 这里需要注意的就是一定要在server启动之前创建swoole table,从而保证它能够被全局共享
$serv->start();

比方只是你自己的一个历程在分化的呼吁之间共享高质量的本土数据,那么使用的范例如下:

class LocalSwooleTable {
    private static $_swooleTable;// 静态变量,单个进程内共享

    const SWOOLE_TABLE_SET_FAILED = -1001;
    const SWOOLE_TABLE_GET_FAILED = -1002;

    // swoole table初始化
    private function __construct() {
        //预估数据量 100个服务,每个长度30 需要3000个字节,这里申请64k
        self::$_swooleTable = new \swoole_table(65536);
        self::$_swooleTable->column('ip',\swoole_table::TYPE_STRING, 64);
        self::$_swooleTable->column('port',\swoole_table::TYPE_INT, 4);
        self::$_swooleTable->column('timestamp',\swoole_table::TYPE_INT, 4);
        self::$_swooleTable->column('bTcp',\swoole_table::TYPE_INT, 4);
        self::$_swooleTable->create();
    }

    // 获取单个swoole的实例
    public static function getInstance() {
        if(self::$_swooleTable) {
            return self::$_swooleTable;
        }
        else {
            new LocalSwooleTable();
            return self::$_swooleTable;
        }
    }
}
// 获取唯一的实例
$swooleTableIns = LocalSwooleTable::getInstance();
$key = "sample";
$routeInfo['timestamp'] = time();
$routeInfo['ip'] = '10.25.22.33';
$routeInfo['port'] = 1000;
$routeInfo['bTcp'] = 1;

// 设置swoole table中的内容
$flag = $swooleTableIns->set($key,$routeInfo);

// 获取swoole table中的内容
$routeInfo = $swooleTableIns->get($key);

理所当然,第一种格局应该是咱们最好的选拔,不过因为大家利用了TSF框架(或者其余不是温馨开首裸写swoole的框架),都不会把创造server这一步暴光到事情代码中,那就给大家使用全局的swoole的table带来了很大的难度。换句话说,知道好用,不过就是事情用起来极度的不便民,不享有业务伸张性。

据此不得已之下,大家依旧接纳了第三种方案,从性质方面来讲的话,确实是有升迁的,不好的地点就是存储资源浪费了有的,每个过程都用了依附自己的swoole
table,那自然是无奈之举。如故盼望可以之后通过有些改建,把全局的swoole
table那种力量可以开放出来。

bf88必发唯一官网 2

屏幕快照 2017-01-24 早晨5.28.17.png

大多访问三次是0.03ms,这些特性仍然相比较卓越的。

swoole_async

异步IO接口,提供了
异步文件系统IO,定时器,异步DNS查询,异步MySQL等API,异步Http客户端,异步Redis客户端。

  • swoole_timer 异步微秒定时器,可以完成间隔时间或三遍性的定时职务
  • swoole_async_read/swoole_async_write 文件系统操作的异步接口

使用的地点有很多
前文提到的在系统装置Cache组件 Cache::getInstance()的时候

TableManager::getInstance()->add(
    'process_hash_map',[
        'pid'=>[
            'type'=>Table::TYPE_INT,
            'size'=>10
        ]
    ],256
);

swoole_process

进度管理模块,可以便宜的创制子进度,进度间通讯,进度管理。

构造方法做了如下事情

addProcess($this->generateProcessName($i),CacheProcess::class);
$this->generateProcessName($i)那一个代码很简单就是按照$i来设置进程名称
addProcess 是在processList存储CacheProcess::class的实例,具体代码如下

swoole_buffer

强硬的内存区管理工具,像C一样举办指针总计,又无需关怀内存的提请和假释,而且不用担心内存越界,底层全体搞好了。

$num = intval(Config::getInstance()->getConf("EASY_CACHE.PROCESS_NUM"));//Config默认配置是1,如果配置为小于等于0则不开启Cache
if($num <= 0){
   return;
}
$this->cliTemp = new SplArray();
//若是在主服务创建,而非单元测试调用
if(ServerManager::getInstance()->getServer()){
    //创建table用于数据传递
    TableManager::getInstance()->add(self::EXCHANGE_TABLE_NAME,[
        'data'=>[
            'type'=>Table::TYPE_STRING,
            'size'=>10*1024
        ],
        'microTime'=>[
            'type'=>Table::TYPE_STRING,
            'size'=>15
        ]
    ],2048);
    //创建了一个__Cache的swoole_table表,字段为 data String 10240,microTime String 15的表
    $this->processNum = $num;
    for ($i=0;$i < $num;$i++){
        ProcessManager::getInstance()->addProcess($this->generateProcessName($i),CacheProcess::class);
    }
}
$key = md5($processName);
if(!isset($this->processList[$key])){
    try{

        $process = new $processClass($processName,$args,$async);
        $this->processList[$key] = $process;
        return true;
    }catch (\Throwable $throwable){
        Trigger::throwable($throwable);
        return false;
    }
}else{
    trigger_error("you can not add the same name process : {$processName}.{$processClass}");
    return false;
}

swoole_table

据悉共享内存和自旋锁已毕的超高品质内存表。彻底解决线程,进度间数据共享,加锁同步等题材。

swoole_table的属品质够直达单线程每秒读写100W次

现实扶持文档地址:

ProcessManager也是一个很重点的概念。其实就是一个管理职分映射的工具。

那就是说CacheProcess::class的实例话做了什么样操作呢
$this->cacheData = new
SplArray();//那里很紧要,为啥那样说每个Cache进度实际保存的缓存值都是在此地的,每个Cache进程都有投机的一个cacheData数组
$this->persistentTime =
Config::getInstance()->getConf(‘EASY_CACHE.PERSISTENT_TIME’);
parent::__construct($processName, $args);
CacheProcess::class继承于AbstractProcess
AbstractProcess的构造方法

那里可以看出ProcessManager::getInstance()->addProcess($this->generateProcessName($i),CacheProcess::class)

$this->async = $async;
$this->args = $args;
$this->processName = $processName;
$this->swooleProcess = new \swoole_process([$this,'__start'],false,2);
ServerManager::getInstance()->getServer()->addProcess($this->swooleProcess);//然后swoole服务会addProcess一个Cache的任务进程。

实际那里是通过ProcessManager,让swoole服务添加了一个进度。swoole的addProcess方法,文档链接

__start方法紧假设给swoole_table,表名为process_hash_map插入当前CacheProcess的经过名为key,进程IDpid为value。并且注册进度退出的风云。

bf88必发唯一官网 3

if(PHP_OS != 'Darwin'){
    $process->name($this->getProcessName());
}
TableManager::getInstance()->get('process_hash_map')->set(
    md5($this->processName),['pid'=>$this->swooleProcess->pid]
);
ProcessManager::getInstance()->setProcess($this->getProcessName(),$this);
if (extension_loaded('pcntl')) {
    pcntl_async_signals(true);
}
Process::signal(SIGTERM,function ()use($process){
    $this->onShutDown();
    TableManager::getInstance()->get('process_hash_map')->del(md5($this->processName));
    swoole_event_del($process->pipe);
    $this->swooleProcess->exit(0);
});
if($this->async){
    swoole_event_add($this->swooleProcess->pipe, function(){
        $msg = $this->swooleProcess->read(64 * 1024);
        $this->onReceive($msg);
    });
}
$this->run($this->swooleProcess);

超前略带讲解一下Cache的set方法加深概念

$this->run($this->swooleProcess)那一个函数是CacheProcess如若配置了persistent提姆e,就会敞开一个定时器定时去取$file

Config::getInstance()->getConf(‘TEMP_DIR’).”/{$processName}.data”;的数据备份,默许是0也就是不会去做定时数据落地的操作

见到那里才是Cache组件在率先次实例化的时候做的有关事务,统计就是创办了指定数量的Cache进度绑定到swoole服务器上。在全局的process_hash_map表中能找到相应的Cache进程ID。然后Cache进程是可以以管道格局来拓展通讯。

 

set缓存方法

public function set($key,$data)
{
    if(!ServerManager::getInstance()->isStart()){
        $this->cliTemp->set($key,$data);
    }
    if(ServerManager::getInstance()->getServer()){
        $num = $this->keyToProcessNum($key);
        $msg = new Msg();
        $msg->setCommand('set');
        $msg->setArg('key',$key);
        $msg->setData($data);
        ProcessManager::getInstance()->getProcessByName($this->generateProcessName($num))->getProcess()->write(\swoole_serialize::pack($msg));//直接把需要缓存的数据,封装成msg然后write给hash映射到的Cache进程
    }
}

当进程取获得的时候会回调onReceive方法

public function onReceive(string $str,...$agrs)
{
    // TODO: Implement onReceive() method.

    $msg = \swoole_serialize::unpack($str);
    $table = TableManager::getInstance()->get(Cache::EXCHANGE_TABLE_NAME);
    if(count($table) > 1900){
        //接近阈值的时候进行gc检测
        //遍历Table 依赖pcre 如果发现无法遍历table,检查机器是否安装pcre-devel
        //超过0.1s 基本上99.99%为无用数据。
        $time = microtime(true);
        foreach ($table as $key => $item){
            if(round($time - $item['microTime']) > 0.1){
                $table->del($key);
            }
        }
    }
    if($msg instanceof Msg){
        switch ($msg->getCommand()){
            case 'set':{
                $this->cacheData->set($msg->getArg('key'),$msg->getData());
                break;
            }
            case 'get':{
                $ret = $this->cacheData->get($msg->getArg('key'));
                $msg->setData($ret);
                $table->set($msg->getToken(),[
                    'data'=>\swoole_serialize::pack($msg),
                    'microTime'=>microtime(true)
                ]);
                break;
            }
            case 'del':{
                $this->cacheData->delete($msg->getArg('key'));
                break;
            }
            case 'flush':{
                $this->cacheData->flush();
                break;
            }
            case 'enQueue':{
                $que = $this->cacheData->get($msg->getArg('key'));
                if(!$que instanceof \SplQueue){
                    $que = new \SplQueue();
                    $this->cacheData->set($msg->getArg('key'),$que);
                }
                $que->enqueue($msg->getData());
                break;
            }
            case 'deQueue':{

                $que = $this->cacheData->get($msg->getArg('key'));
                if(!$que instanceof \SplQueue){
                    $que = new \SplQueue();
                    $this->cacheData->set($msg->getArg('key'),$que);
                }
                $ret = null;
                if(!$que->isEmpty()){
                    $ret = $que->dequeue();
                }
                $msg->setData($ret);
                //deQueue 有cli 服务未启动的请求,但无token
                if(!empty($msg->getToken())){
                    $table->set($msg->getToken(),[
                        'data'=>\swoole_serialize::pack($msg),
                        'microTime'=>microtime(true)
                    ]);
                }
                break;
            }
            case 'queueSize':{
                $que = $this->cacheData->get($msg->getArg('key'));
                if(!$que instanceof \SplQueue){
                    $que = new \SplQueue();
                }
                $msg->setData($que->count());
                $table->set($msg->getToken(),[
                    'data'=>\swoole_serialize::pack($msg),
                    'microTime'=>microtime(true)
                ]);
                break;
            }
        }
    }
}

那里一开端会进展缓存GC确保内存不会撑爆

set方法会直接给$this->cacheData,设置缓存值。

 

get方法相比十分,它会去给Cache进度发送get的命令,然后Cache读取到命令会将值写到_Cache,Swoole_table表中。然后再去读取(那些会有一个while循环,类似自旋)出缓存内容。那样的益处,可以保障可以读取到当时的数目缓存,不会因为高并发读取到最新的缓存值内容。而且仍可以更使得的做gc,防止Cache内存撑爆。

public function get($key,$timeOut = 0.01)
{
    if(!ServerManager::getInstance()->isStart()){
        return $this->cliTemp->get($key);
    }
    $num = $this->keyToProcessNum($key);
    $token = Random::randStr(9);//这个是一个凭证,是确保获取到自己此刻想获取的cache数据,和事务类似为了保证可重复读
    $process = ProcessManager::getInstance()->getProcessByName($this->generateProcessName($num));
    $msg = new  Msg();
    $msg->setArg('timeOut',$timeOut);
    $msg->setArg('key',$key);
    $msg->setCommand('get');
    $msg->setToken($token);
    $process->getProcess()->write(\swoole_serialize::pack($msg));
    return $this->read($token,$timeOut);
}

$process->getProcess()->write(\swoole_serialize::pack($msg))发那些包给Cache进度,Cache进度会进展下边那个操作

$ret = $this->cacheData->get($msg->getArg('key'));//获取到当前的缓存值
$msg->setData($ret);
//将当前的内容设置到_Cache表中,token是请求的时候发过来的凭证原样拼装。这有什么好处呢,就是确保在高并发下,在A时刻获取的缓存,不会拿到后面B时刻更新的值。
$table->set($msg->getToken(),[
    'data'=>\swoole_serialize::pack($msg),
    'microTime'=>microtime(true)
]);

$this->read($token,$timeOut);

//这里的操作是直接从_Cache表中获取缓存数据,如果缓存存在并且进程调度没有超时,然后在表中将取过数据的内容删除掉返回
private function read($token,$timeOut)
{
    $table = TableManager::getInstance()->get(self::EXCHANGE_TABLE_NAME);
    $start = microtime(true);
    $data = null;
    while(true){
        usleep(1);
        if($table->exist($token)){
            $data = $table->get($token)['data'];
            $data = \swoole_serialize::unpack($data);
            if(!$data instanceof Msg){
                $data = null;
            }
            break;
        }
        if(round($start - microtime(true),3) > $timeOut){
            break;
        }
    }
    $table->del($token);
    if($data){
        return $data->getData();
    }else{
        return null;
    }
}

 

//讲解一下Cache的set方法加深概念
if(!ServerManager::getInstance()->isStart()){//兼容测试模式。也就是不开启服务的情景下直接是clitemp中取缓存数据
    $this->cliTemp->set($key,$data);
}
if(ServerManager::getInstance()->getServer()){
    $num = $this->keyToProcessNum($key);//这里是通过key然后hash到应该投放的Cache进程中去。
    $msg = new Msg();
    $msg->setCommand('set');
    $msg->setArg('key',$key);
    $msg->setData($data);
    //下面一句话还是挺复杂的,根据key名hash到ProcessManager对应的映射,然后获取到swoole_process的实例,以swoole的write函数向管道内写入数据。
    ProcessManager::getInstance()->getProcessByName($this->generateProcessName($num))->getProcess()->write(\swoole_serialize::pack($msg));
    //在写完数据后,在CacheProcess的onReceive方法中可以看到对应setCommand的操作细节。其实数据都被写到了一个Arr数组中。下篇接着讲一下Cache的实现细节。这节还是主要讲TableManager和它的相关作用.
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图