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

Cache是以单例模式实现的。构造器会进行如下操作

//根据配置创建指定数目的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);
}
}

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

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

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

addProcess($this->generateProcessName($i),CacheProcess::class);
$this->generateProcessName($i)这个代码很简单就是根据$i来设置进程名称
addProcess 是在processList存储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;
}

那么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的构造方法

$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的任务进程。

__start方法主要是给swoole_table,表名为process_hash_map插入当前CacheProcess的进程名为key,进程IDpid为value。并且注册进程退出的事件。

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);

$this->run($this->swooleProcess)这个函数是CacheProcess如果配置了persistentTime,就会开启一个定时器定时去取$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;
}
}

四 分析easyswoole源码(启动服务&Cache组件原理)的更多相关文章

  1. 一 分析easyswoole源码(启动服务)

    分析easyswoole源码 1以启动为例 //检查是否已经安装 installCheck();//检查锁文件是否存在,不存在结束 //启动服务 serverStart showLogo();//显示 ...

  2. 三 分析easyswoole源码(启动服务&TableManager,略提及Cache工具的原理)

    前文连接,讲了es是如何启动swoole服务的. 里面有一个工具类TableManager.这个类为了处理进程间数据共享.是对swoole_table的一层封装swoole_table一个基于共享内存 ...

  3. 二 分析easyswoole源码(启动服务)

    前文连接,阅读的时候最好参照EasySwoole2.1.2的源码 $inst->run();//启动服务 这里实际调用的是Core的start方法ServerManager::getInstan ...

  4. k8s client-go源码分析 informer源码分析(2)-初始化与启动分析

    k8s client-go源码分析 informer源码分析(2)-初始化与启动分析 前面一篇文章对k8s informer做了概要分析,本篇文章将对informer的初始化与启动进行分析. info ...

  5. Netty源码解析---服务端启动

    Netty源码解析---服务端启动 一个简单的服务端代码: public class SimpleServer { public static void main(String[] args) { N ...

  6. java 日志体系(四)log4j 源码分析

    java 日志体系(四)log4j 源码分析 logback.log4j2.jul 都是在 log4j 的基础上扩展的,其实现的逻辑都差不多,下面以 log4j 为例剖析一下日志框架的基本组件. 一. ...

  7. 【一起学源码-微服务】Nexflix Eureka 源码十三:Eureka源码解读完结撒花篇~!

    前言 想说的话 [一起学源码-微服务-Netflix Eureka]专栏到这里就已经全部结束了. 实话实说,从最开始Eureka Server和Eureka Client初始化的流程还是一脸闷逼,到现 ...

  8. 【一起学源码-微服务】Eureka+Ribbon+Feign阶段性总结

    前言 想说的话 这里已经梳理完Eureka.Ribbon.Feign三大组件的基本原理了,今天做一个总结,里面会有一个比较详细的调用关系流程图. 说明 原创不易,如若转载 请标明来源! 博客地址:一枝 ...

  9. [源码分析] 从源码入手看 Flink Watermark 之传播过程

    [源码分析] 从源码入手看 Flink Watermark 之传播过程 0x00 摘要 本文将通过源码分析,带领大家熟悉Flink Watermark 之传播过程,顺便也可以对Flink整体逻辑有一个 ...

随机推荐

  1. 轻量应用服务器安装 phpMyAdmin

    第一步:在phpMyAdmin官方网站http://www.phpmyadmin.net/downloads/下载源码包并解压 cd /usr/local/src wget https://files ...

  2. python3学习笔记六(元组)

    元组 创建空元组 tup1 = () #空元组print(type(tup1))print(tup1) tup2 = (10)tup3 = (10,)print(type(tup2)) #不加逗号,类 ...

  3. [转][C#]文件流读取

    { internal static class FileUtils { public static string GetRelativePath(string absPath, string base ...

  4. MAC地址表、ARP缓存表以及路由表

    一:MAC地址表详解 说到MAC地址表,就不得不说一下交换机的工作原理了,因为交换机是根据MAC地址表转发数据帧的.在交换机中有一张记录着局域网主机MAC地址与交换机接口的对应关系的表,交换机就是根据 ...

  5. postgresql数据库备份

    一.工具备份数据 打开windows下的命令窗口:开始->cmd->安装数据库的目录->进入bin目录: 导出命令:pg_dump –h localhost –U postgres ...

  6. Problem C: 重复子串(string)

    /* 一个性质? right集合中只有相邻的位置才会有用 那么考虑set启发式合并, 能够求出大概nlogn个有用的对 那么将这些对按照右端点排序, 查询也按照右端点排序就可以离线维护信息 然后需要维 ...

  7. 【Linux命令】Linux下的tar压缩解压缩命令详解(转)

    tar -c: 建立压缩档案 -x:解压 -t:查看内容 -r:向压缩归档文件末尾追加文件 -u:更新原压缩包中的文件 这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个 ...

  8. thinkphp5 Exception类重定义

    重点定义自己的错误信息和错误码: 在TP5的配置文件中有下面一段 // 异常处理handle类 留空使用 \think\exception\Handle 'exception_handle' => ...

  9. Django Forms 表单

    环境 python 3.7 服务端  views.py from django import forms # 引入 froms 模块 from django.forms import widgets ...

  10. python中的多进程与多线程(一)

    进程是一个执行中的程序,每个进程有自己的地址空间.内存.数据栈以及其他用于跟踪执行的辅助数据.操作系统管理其上所有进程,并合理分配时间. 进程也可以通过fork或spawn派生新的进程,每个新进程有自 ...