最外层start.php,设置全局启动模式,加载Application里的个子服务目录下应用的启动文件(start开头,这些文件都是workman\work类的子类,在载入文件的同时,这些子服务会生成对象,work类的构造方法会把生成的work对象都存入work类的静态变量$_workers,方便主文件后续设置以及启动子服务,全局启动模式子服务是不会启动的),设置时区,注册自动加载,调用workman\Worker:runall启动所有服务。

主文件:

// 标记是全局启动
define('GLOBAL_START', 1); require_once __DIR__ . '/Workerman/Autoloader.php'; // 加载所有Applications/*/start.php,以便启动所有服务
foreach(glob(__DIR__.'/Applications/*/start*.php') as $start_file)
{
require_once $start_file;
}
// 运行所有服务
Worker::runAll();

子服务文件start_gateway,提供接入请求的服务(class Gateway extends Worker):

// gateway 进程,这里使用Text协议,可以用telnet测试
$gateway = new Gateway("Text://0.0.0.0:8282");
// gateway名称,status方便查看
$gateway->name = 'YourAppGateway';
// gateway进程数
$gateway->count = 4;
// 本机ip,分布式部署时使用内网ip
$gateway->lanIp = '127.0.0.1';
// 内部通讯起始端口,假如$gateway->count=4,起始端口为4000
// 则一般会使用4001 4002 4003 4004 4个端口作为内部通讯端口
$gateway->startPort = 2300;
// 心跳间隔
//$gateway->pingInterval = 10;
// 心跳数据
//$gateway->pingData = '{"type":"ping"}'; /*
// 当客户端连接上来时,设置连接的onWebSocketConnect,即在websocket握手时的回调
$gateway->onConnect = function($connection)
{
$connection->onWebSocketConnect = function($connection , $http_header)
{
// 可以在这里判断连接来源是否合法,不合法就关掉连接
// $_SERVER['HTTP_ORIGIN']标识来自哪个站点的页面发起的websocket链接
if($_SERVER['HTTP_ORIGIN'] != 'http://kedou.workerman.net')
{
$connection->close();
}
// onWebSocketConnect 里面$_GET $_SERVER是可用的
// var_dump($_GET, $_SERVER);
};
};
*/ // 如果不是在根目录启动,则运行runAll方法
if(!defined('GLOBAL_START'))
{
Worker::runAll();
}

接入服务除了注册到全局静态$_workers变量,还设置了路由:

    public function __construct($socket_name, $context_option = array())
{
parent::__construct($socket_name, $context_option);
//随机返回一个bussness的连接
$this->router = array("\\GatewayWorker\\Gateway", 'routerRand'); $backrace = debug_backtrace();
$this->_appInitPath = dirname($backrace[0]['file']);
}

子服务文件start_bussinessworker,提供实际的业务处理,和gateway服务内部通讯类似nginx与php(class BusinessWorker extends Worker):

// bussinessWorker 进程
$worker = new BusinessWorker();
// worker名称
$worker->name = 'YourAppBusinessWorker';
// bussinessWorker进程数量
$worker->count = 4; // 如果不是在根目录启动,则运行runAll方法
if(!defined('GLOBAL_START'))
{
Worker::runAll();
}

附带基类work的构造函数:

/**
* worker构造函数
*
* @param string $socket_name
* @param array $context_option
*/
public function __construct($socket_name = '', $context_option = array())
{
// 保存worker实例
$this->workerId = spl_object_hash($this);
self::$_workers[$this->workerId] = $this;
self::$_pidMap[$this->workerId] = array(); // 获得实例化文件路径,用于自动加载设置根目录
$backrace = debug_backtrace();
$this->_appInitPath = dirname($backrace[0]['file']); // 设置socket上下文
if($socket_name)
{
$this->_socketName = $socket_name;
if(!isset($context_option['socket']['backlog']))
{
$context_option['socket']['backlog'] = self::DEFAUL_BACKLOG;
}
$this->_context = stream_context_create($context_option);
}
}

这里可以看到self::$_workers[$this->workerId] = $this;记录全局worker实例,
 self::$_pidMap这个用来记录各个子服务开始fork后的所有子进程id
 $context_option['socket']['backlog'] = self::DEFAUL_BACKLOG;设置嵌套字上下文里的未accept队列长度
 在后面运行实例的listen方法监听的时候会传递到stream_socket_server方法里。

runAll的流程如下:

/**
* 运行所有worker实例
* @return void
*/
public static function runAll()
{
// 初始化环境变量(pid文件,log文件,status文件,定时器信号回调)
self::init();
// 解析命令(运行,重启,停止,重载,状态,从命令判断主进程是否以守护进程启动)
// 启动之后通过php start.php XXX命令会到这里!因为第一步设置了这个文件的pid(这里可以看到pid对应到文件位置的重要性),所以后面的命令会对应为发送信号
self::parseCommand();
// 尝试以守护进程模式运行(fork两次进程,重置进程sid)
self::daemonize();
// 初始化所有worker实例,主要是监听端口(记录所有子服务worker实例的最长名称name长度,最长嵌套字名socket_name长度,最长运行用户名user长度;
// 所有定义了协议的子服务(Gate)开始监听也就是启动服务,子服务实例监听嵌套字对象mainSoctke,还没有注册accept回调)
// 到这里所有Gate子服务启动监听,但是没有accept
self::initWorkers();
// 初始化所有信号处理函数(为主进程注册stop,stats,reload的信号回调signalHandler)
self::installSignal();
// 保存主进程pid(将获取daemonize方法后的新的主进程sid,存入init方法后的pid文件)
self::saveMasterPid();
// 创建子进程(worker进程)并运行(主进程通过self::$_pidMap用来记录子服务进程创建的各自进程号,方便后面发送信号,
        // 生成的子进程置空主进程的全局变量,self::$_pidMap,self::$_workers,如果在子服务文件里定义了self::$stdoutFile文件地址,
        // 会重定向子服务子进程的stdout和stderr,直接运行work实例的run方法)
        // 到这里子服务已经在子进程中运行,后面的代码就只有主服务执行
        
        // 子进程的run方法会通过libevent绑定子服务mainSocket的accept回调,在accept回调方法里才有定义后面怎么处理请求socket
        // 子进程的run方法会通过libevent绑定重新绑定信号量,以及用libevent来注入定时器
        // 子进程的run方法会回调用户在子服务文件里的onWorkStar方法
        // 子进程进入事件监听轮询
        // 上面是基类中的run方法,基于gateway的子服务,会实现自己的onworkStar方法,然后在调用基类的run,这样可以在onWorkStar里实现gate与worker的连接
        // 这里不知道怎么处理子进程对accpet时候的惊群
self::forkWorkers();
// 展示启动界面(打印所有启动的子服务的信息,由于initWorkers获取了各个子服务实例的名称等信息长度可以很好的格式化展示)
self::displayUI();
// 尝试重定向标准输入输出(重定向主服务进程)
self::resetStd();
// 监控所有子进程(worker进程)(处理主进程的信号量;通过pcntl_wait循环监听子进程状态,保持子进程的运行)
/*
什么是平滑重启?
平滑重启不同于普通的重启,平滑重启可以做到在不影响用户的情况下重启服务,以便重新载入PHP程序,完成业务代码更新。
平滑重启一般应用于业务更新或者版本发布过程中,能够避免因为代码发布重启服务导致的暂时性服务不可用的影响。
注意:只有在on{...}回调中载入的文件平滑重启后才会自动更新,启动脚本中直接载入的文件或者写死的代码运行reload不会自动更新。
平滑重启原理
WorkerMan分为主进程和子进程,主进程负责监控子进程,子进程负责接收客户端的连接和连接上发来的请求数据,
做相应的处理并返回数据给客户端。当业务代码更新时,其实我们只要更新子进程,便可以达到更新代码的目的。
当WorkerMan主进程收到平滑重启信号时,主进程会向其中一个子进程发送安全退出(让对应进程处理完毕当前请求后才退出)信号,
当这个进程退出后,主进程会重新创建一个新的子进程(这个子进程载入了新的PHP代码),然后主进程再次向另外一个旧的进程发送停止
命令,这样一个进程一个进程的重启,直到所有旧的进程全部被置换为止。
我们看到平滑重启实际上是让旧的业务进程逐个退出然后并逐个创建新的进程做到的。为了在平滑重启时不影响客用户,这就要求进程中不
要保存用户相关的状态信息,即业务进程最好是无状态的,避免由于进程退出导致信息丢失。
*/
//上面是官网对平滑启动的说明,设计的代码就是reload方法的 $one_worker_pid = current(self::$_pidsToRestart );这里是处理主进程的平滑启动信号的
// 在主进程里获取所有设置为可以平滑启动的子进程的pid,然后取一个发送平滑启动信号信号,这个信号到子进程,其实子进程会通过stopAll方法停止运行
// exit(0);
// 主进程监听到子进程退出,然后重新生成一个新的子进程,然后把这个子进程的id从self::$_pidsToRestart里删除,然后再次调用reload方法去杀掉下一个子进程
//
self::monitorWorkers();
}

主要的过程已经描述清楚了,主服务在主进程里,子服务开启监听之后,主服务开始fork,然后记录子服务进程的对应的pid,然后通过信号量来处理用户命令以及管理子服务进程。子服务在子进程里实现accpet监听回调。

work基类的主要代码片段:

子服务listen:
// 获得应用层通讯协议以及监听的地址,udp会转换为传输协议
list($scheme, $address) = explode(':', $this->_socketName, 2);
// 如果有指定应用层协议,则检查对应的协议类是否存在
if($scheme != 'tcp' && $scheme != 'udp')
{
$scheme = ucfirst($scheme);
$this->_protocol = '\\Protocols\\'.$scheme;
if(!class_exists($this->_protocol))
{
$this->_protocol = "\\Workerman\\Protocols\\$scheme";
if(!class_exists($this->_protocol))
{
throw new Exception("class \\Protocols\\$scheme not exist");
}
}
}
elseif($scheme === 'udp')
{
$this->transport = 'udp';
} // flag
$flags = $this->transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
$errno = 0;
$errmsg = '';
$this->_mainSocket = stream_socket_server($this->transport.":".$address, $errno, $errmsg, $flags, $this->_context);
if(!$this->_mainSocket)
{
throw new Exception($errmsg);
}
创建子进程:
// 创建子进程
while(count(self::$_pidMap[$worker->workerId]) < $worker->count)
{
static::forkOneWorker($worker);
}
}
} /**
* 创建一个子进程
* @param Worker $worker
* @throws Exception
*/
protected static function forkOneWorker($worker)
{
$pid = pcntl_fork();
// 主进程记录子进程pid
if($pid > 0)
{
self::$_pidMap[$worker->workerId][$pid] = $pid;
}
// 子进程运行
elseif(0 === $pid)
{
// 启动过程中尝试重定向标准输出
if(self::$_status === self::STATUS_STARTING)
{
self::resetStd();
}
self::$_pidMap = array();
self::$_workers = array($worker->workerId => $worker);
Timer::delAll();
self::setProcessTitle('WorkerMan: worker process ' . $worker->name . ' ' . $worker->getSocketName());
self::setProcessUser($worker->user);
$worker->run();
exit(250);
}
else
{
throw new Exception("forkOneWorker fail");
}
}
子进程执行的基类run方法:
/**
* 运行worker实例
*/
public function run()
{
// 注册进程退出回调,用来检查是否有错误
register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors')); // 设置自动加载根目录
Autoloader::setRootPath($this->_appInitPath); // 如果没有全局事件轮询,则创建一个
if(!self::$globalEvent)
{
if(extension_loaded('libevent'))
{
self::$globalEvent = new Libevent();
}
else
{
self::$globalEvent = new Select();
}
// 监听_mainSocket上的可读事件(客户端连接事件)也只有Gate才有这个事件
if($this->_socketName)
{
if($this->transport !== 'udp')
{
self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection'));
}
else
{
self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection'));
}
}
} // 重新安装事件处理函数,使用全局事件轮询监听信号事件
self::reinstallSignal(); // 用全局事件轮询初始化定时器
Timer::init(self::$globalEvent); // 如果有设置进程启动回调,则执行
if($this->onWorkerStart)
{
call_user_func($this->onWorkerStart, $this);
} // 子进程主循环
self::$globalEvent->loop();
}
主服务监听子服务进程:
pcntl_signal_dispatch();
// 挂起进程,直到有子进程退出或者被信号打断
$status = 0;
$pid = pcntl_wait($status, WUNTRACED);
// 如果有信号到来,尝试触发信号处理函数
pcntl_signal_dispatch();
// 有子进程退出
if($pid > 0)
{
// 查找是哪个进程组的,然后再启动新的进程补上
foreach(self::$_pidMap as $worker_id => $worker_pid_array)
{
if(isset($worker_pid_array[$pid]))
{
$worker = self::$_workers[$worker_id];
// 检查退出状态
if($status !== 0)
{
self::log("worker[".$worker->name.":$pid] exit with status $status");
} // 统计,运行status命令时使用
if(!isset(self::$_globalStatistics['worker_exit_info'][$worker_id][$status]))
{
self::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0;
}
self::$_globalStatistics['worker_exit_info'][$worker_id][$status]++; // 清除子进程信息
unset(self::$_pidMap[$worker_id][$pid]); break;
}
}
// 如果不是关闭状态,则补充新的进程
if(self::$_status !== self::STATUS_SHUTDOWN)
{
self::forkWorkers();
// 如果该进程是因为运行reload命令退出,则继续执行reload流程
if(isset(self::$_pidsToRestart[$pid]))
{
unset(self::$_pidsToRestart[$pid]);
self::reload();
}
}
平滑启动过程:
/**
* 执行平滑重启流程
* @return void
*/
protected static function reload()
{
// 主进程部分
if(self::$_masterPid === posix_getpid())
{
// 设置为平滑重启状态
if(self::$_status !== self::STATUS_RELOADING && self::$_status !== self::STATUS_SHUTDOWN)
{
self::log("Workerman[".basename(self::$_startFile)."] reloading");
self::$_status = self::STATUS_RELOADING;
} // 如果有worker设置了reloadable=false,则过滤掉
$reloadable_pid_array = array();
foreach(self::$_pidMap as $worker_id =>$worker_pid_array)
{
$worker = self::$_workers[$worker_id];
if($worker->reloadable)
{
foreach($worker_pid_array as $pid)
{
$reloadable_pid_array[$pid] = $pid;
}
}
} // 得到所有可以重启的进程
self::$_pidsToRestart = array_intersect(self::$_pidsToRestart , $reloadable_pid_array); // 平滑重启完毕
if(empty(self::$_pidsToRestart))
{
if(self::$_status !== self::STATUS_SHUTDOWN)
{
self::$_status = self::STATUS_RUNNING;
}
return;
}
// 继续执行平滑重启流程
$one_worker_pid = current(self::$_pidsToRestart );
// 给子进程发送平滑重启信号
posix_kill($one_worker_pid, SIGUSR1);
// 定时器,如果子进程在KILL_WORKER_TIMER_TIME秒后没有退出,则强行杀死
Timer::add(self::KILL_WORKER_TIMER_TIME, 'posix_kill', array($one_worker_pid, SIGKILL), false);
}
// 子进程部分
else
{
// 如果当前worker的reloadable属性为真,则执行退出
$worker = current(self::$_workers);
if($worker->reloadable)
{
self::stopAll();
}
}
}

到这里主进程已经准备好了,子进程(Gate)已经开始监听了(还未讲gate与worker的连接通信,以及gate怎么接受请求,然后worker怎么处理请求)。上面说了gate与worker的连接是在OnWorkStart里实现的。后面就来看看

gate的run方法里保存了用户自定义的方法,然后自己的onWorkStart,onConnect,onMessage,onClose,onWorkstop都已定义好

/**
* 运行
* @see Workerman.Worker::run()
*/
public function run()
{
// 保存用户的回调,当对应的事件发生时触发
$this->_onWorkerStart = $this->onWorkerStart;
$this->onWorkerStart = array($this, 'onWorkerStart');
// 保存用户的回调,当对应的事件发生时触发
$this->_onConnect = $this->onConnect;
$this->onConnect = array($this, 'onClientConnect'); // onMessage禁止用户设置回调
$this->onMessage = array($this, 'onClientMessage'); // 保存用户的回调,当对应的事件发生时触发
$this->_onClose = $this->onClose;
$this->onClose = array($this, 'onClientClose');
// 保存用户的回调,当对应的事件发生时触发
$this->_onWorkerStop = $this->onWorkerStop;
$this->onWorkerStop = array($this, 'onWorkerStop'); // 记录进程启动的时间
$this->_startTime = time();
// 运行父方法
parent::run();
}

在看看他的OnworkStart方法,也就是子进程运行后执行的方法

/**
* 当Gateway启动的时候触发的回调函数
* @return void
*/
public function onWorkerStart()
{
// 分配一个内部通讯端口
// 主进程pid-子进程pid+startPort保证每个子进程的内部端口不同
$this->lanPort = function_exists('posix_getppid') ? $this->startPort - posix_getppid() + posix_getpid() : $this->startPort;
if($this->lanPort<0 || $this->lanPort >=65535)
{
$this->lanPort = rand($this->startPort, 65535);
} // 如果有设置心跳,则定时执行
if($this->pingInterval > 0)
{
$timer_interval = $this->pingNotResponseLimit > 0 ? $this->pingInterval/2 : $this->pingInterval;
Timer::add($timer_interval, array($this, 'ping'));
}
//别名内部通讯协议
if(!class_exists('\Protocols\GatewayProtocol'))
{
class_alias('\GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol');
}
// 初始化gateway内部的监听,用于监听worker的连接已经连接上发来的数据
        // 这里内部链接在同一个ip+端口的情况下有两个服务
        // 这个时候listen由于全局的事件self::$globalEvent在子进程的run方法里在回调OnWorkStart之前已经定义,所以不像主进程一样在listen的不监听accept事件
$this->_innerTcpWorker = new Worker("GatewayProtocol://{$this->lanIp}:{$this->lanPort}");
$this->_innerTcpWorker->listen();
$this->_innerUdpWorker = new Worker("GatewayProtocol://{$this->lanIp}:{$this->lanPort}");
$this->_innerUdpWorker->transport = 'udp';
$this->_innerUdpWorker->listen(); // 重新设置自动加载根目录
Autoloader::setRootPath($this->_appInitPath); // 设置内部监听的相关回调
$this->_innerTcpWorker->onMessage = array($this, 'onWorkerMessage');
$this->_innerUdpWorker->onMessage = array($this, 'onWorkerMessage'); $this->_innerTcpWorker->onConnect = array($this, 'onWorkerConnect');
$this->_innerTcpWorker->onClose = array($this, 'onWorkerClose'); // 注册gateway的内部通讯地址,worker去连这个地址,以便gateway与worker之间建立起TCP长连接
if(!$this->registerAddress())
{
$this->log('registerAddress fail and exit');
Worker::stopAll();
} if($this->_onWorkerStart)
{
call_user_func($this->_onWorkerStart, $this);
}
}

可以看到他有新建了两个个work实例,innerTcpWorker,innerUdpWorker,并且在同一个地址{$this->lanIp}:{$this->lanPort}下开启了两个服务,并且注册了与worker通信的回调事件onWorkerConnect,onWorkerMessage,onWorkerClose。使用的协议是内部通讯协议GatewayProtocol。registerAddress方法通过文件锁把这个子进程的内部通讯服务地址{$this->lanIp}:{$this->lanPort}记录到一个公共地方,可能是文件,可能是memcache,可能是redis,后两种支持分布式部署gate与work,不然就要走同一台机器上。用后面两种可以部署多个gate,然后其他机器部署work。这时候定义的内部服务通过listen方法已经有accept监听事件了,如果work跟与gate连接就会进入到设置的Worker的回调方法里,客户端与Gate连接就会进入到Client方法里,因为他们是两种不同的work实例,监听的不同的端口。

到这里Gate子服务已经准备好了,除了自己是work实例提供给客户端的连接服务,被主进程管理之外;每个子Gate进程都会在新建一个work实例来提供对worker子进程的访问服务;对客户端的服务有new Gate的时候指定协议,对子worker进程的服务是默认协议,并且tcp与udp都监听了。后面的步奏应该是子worker进程在workstart方法里从子Gate服务建立内部服务是注册全局的内部通讯服务地址,连接到Gate,这样gate的内部服务监听就把子worker服务的地址记录下来。

子worker进程服务通过tcp与gate进程服务通信,通过在连接上的监听,实现消息的传递,worker进程在通过与Event文件的回调来通知客户端的请求,Event处理完毕之后,通过lib/gateway文件,直接udp到Gate来实现信息的传递。

分布式中的每台机器有主进程管理子进程,子Gate进程处理监听客户度与内部Work进程,Gate记录全局的客户端id与Gate的对应关系到全局储存器,也记录自己的内部服务地址到全局存储器。每个子Gate进程记录连接自己的内部worker地址。然后子worker启动时候从全局内部服务地址取地址进行tcp连接,记录与自己连接的Gate地址,client,gate,work直接的通信就打通了,如果一个客户端要与另一个客户端通信,在Event处理时从全局的client与gate的对以关系里得到要发送的client连接的gate,然后给这个gate发送udp信息,再由gate转发到对的client。

下面看看基类的accept方法:

/**
* 接收一个客户端连接
* @param resource $socket
* @return void
*/
public function acceptConnection($socket)
{
// 获得客户端连接
$new_socket = @stream_socket_accept($socket, 0);
// 惊群现象,忽略
if(false === $new_socket)
{
return;
} // 初始化连接对象
$connection = new TcpConnection($new_socket);
$this->connections[$connection->id] = $connection;
$connection->worker = $this;
$connection->protocol = $this->_protocol;
$connection->onMessage = $this->onMessage;
$connection->onClose = $this->onClose;
$connection->onError = $this->onError;
$connection->onBufferDrain = $this->onBufferDrain;
$connection->onBufferFull = $this->onBufferFull; // 如果有设置连接回调,则执行
if($this->onConnect)
{
try
{
call_user_func($this->onConnect, $connection);
}
catch(Exception $e)
{
ConnectionInterface::$statistics['throw_exception']++;
self::log($e);
}
}
} /**
* 处理udp连接(udp其实是无连接的,这里为保证和tcp连接接口一致)
*
* @param resource $socket
* @return bool
*/
public function acceptUdpConnection($socket)
{
$recv_buffer = stream_socket_recvfrom($socket , self::MAX_UDP_PACKEG_SIZE, 0, $remote_address);
if(false === $recv_buffer || empty($remote_address))
{
return false;
}
// 模拟一个连接对象
$connection = new UdpConnection($socket, $remote_address);
if($this->onMessage)
{
if($this->_protocol)
{
/** @var \Workerman\Protocols\ProtocolInterface $parser */
$parser = $this->_protocol;
$recv_buffer = $parser::decode($recv_buffer, $connection);
}
ConnectionInterface::$statistics['total_request']++;
try
{
call_user_func($this->onMessage, $connection, $recv_buffer);
}
catch(Exception $e)
{
ConnectionInterface::$statistics['throw_exception']++;
}
}
}

整体上来说就是tcp就是新建一个客户端连接,然后使用tcpConnecttion类封装,包括通信协议,然后回调onConnect事件;udp直接从连接获取数据,然后通过协议解析数据,回调onMessage方法。

gate的内部服务建立好了,再看看business子服务的run方法:

    /**
* 运行
* @see Workerman.Worker::run()
*/
public function run()
{
$this->_onWorkerStart = $this->onWorkerStart;
$this->onWorkerStart = array($this, 'onWorkerStart');
parent::run();
} /**
* 当进程启动时一些初始化工作
* @return void
*/
protected function onWorkerStart()
{
if(!class_exists('\Protocols\GatewayProtocol'))
{
class_alias('\GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol');
}
Timer::add(1, array($this, 'checkGatewayConnections'));
$this->checkGatewayConnections();
\GatewayWorker\Lib\Gateway::setBusinessWorker($this);
if($this->_onWorkerStart)
{
call_user_func($this->_onWorkerStart, $this);
}
}

这里business子服务直接就是循环连接所有的Gate了。

连接打通之后,怎么通信呢,这就要看事件驱动管理以及协议了。

gateway-workman的更多相关文章

  1. 记一次nginx部署yii2项目时502 bad gateway错误的排查

    周六闲来无事,就试着安装和部署下yii2,安装过程没什么问题,但部署到nginx上时遇到了502 bad gatewary问题,折腾了半天才搞定.这个问题是我以前在部署yii2时没有遇到过的,因此记在 ...

  2. tomcat 504 gateway time-out

    今天有个环境ajax调用一个请求的时候,出现一个504 gateway time-out响应,原以为是nginx找不到资源的问题,恰当我们的服务器上又配置了nginx,看了配置文件,没有指向tomca ...

  3. linux查看本机IP、gateway、DNS

    IP:     ifconfig gateway:[root@localhost ~]# netstat -rnKernel IP routing tableDestination     Gatew ...

  4. 502 Bad Gateway深究

    早上收到502报警,设置的报警规则是502错误两分钟超过500就报警. 排障流程: 日志分析系统报障-->查看日志系统日志-->nginx错误日志-->php错误日志-->ph ...

  5. Problem with "AnyConnect was not able to establish connection to the specified secure gateway."

    Cisco的VPN客户端最近报"AnyConnect was not able to establish connection to the specified secure gateway ...

  6. Azure Application Gateway (3) 设置URL路由

    <Windows Azure Platform 系列文章目录> 在之前的文章中,笔者介绍了Azure Web App可以设置URL路由.如下图: 在这里笔者简单介绍一下,首先我们还是创建以 ...

  7. Azure Application Gateway (4) 设置URL路由 - PowerShell

    <Windows Azure Platform 系列文章目录> 本文将介绍如果使用Azure PowerShell,创建Azure Application Gateway URL Rout ...

  8. Azure Application Gateway (2) 面向公网的Application Gateway

    <Windows Azure Platform 系列文章目录> 本章将介绍如何创建面向公网的Application Gateway,我们需要准备以下工作: 1.创建新的Azure Reso ...

  9. Azure Application Gateway (1) 入门

    <Windows Azure Platform 系列文章目录> 请读者注意,Azure Application Gateway在ASM模式下,只能通过PowerShell创建 具体可以参考 ...

  10. 解决 502、504 Gateway Time-out(nginx)

    一.504 Gateway Time-out问题常见于使用nginx作为web server的服务器的网站 我遇到这个问题是在升级discuz论坛的时候遇到的 一般看来, 这种情况可能是由于nginx ...

随机推荐

  1. 《mysql必知必会》学习_第13章_20180803_欢

    第13章:分组过滤. P83 select count(*) as num_prods from products where vend_id=1003; #返回vend_id=1003的产品数目总值 ...

  2. Trie树的数组实现原理

    Trie(Retrieval Tree)又称前缀树,可以用来保存多个字符串,并且非常便于查找.在trie中查找一个字符串的时间只取决于组成该串的字符数,与树的节点数无关.因此,它的查找速度通常比二叉搜 ...

  3. DXP快捷键记录(红色为经常用到的)

    dxp快捷键 1.设计浏览器快捷键:鼠标左击                         选择鼠标位置的文档鼠标双击                         编辑鼠标位置的文档鼠标右击   ...

  4. I - Dividing Stones

    Description There are N stones, which can be divided into some piles arbitrarily. Let the value of e ...

  5. nips 2016 吴恩达

    一年一度的 NIPS 又来了.今年举办地是笔者最爱的欧洲城市巴塞罗那.阳光沙滩配学术,确实很爽.这次的会议的第一天开场的大部分时间安排给了 tutorial.其中人数爆满的依旧是吴恩达(AndrewN ...

  6. js css等静态文件版本控制,一处配置多处更新.net版【原创】

    日常web开发中,我们修改了js.css等静态资源文件后,如果文件名不变的话,客户端浏览并不会及时获取最新的资源文件,这就很尴尬了 怎么办呢? 1.小白:让客户清除缓存?,No ,  不靠谱 2.初级 ...

  7. Java基础巩固计划

    3.26-4.1 JVM 虚拟机的内容写五篇博客 解决以下问题: 1. Java的内存模型以及GC算法 2. jvm性能调优都做了什么 3. 介绍JVM中7个区域,然后把每个区域可能造成内存的溢出的情 ...

  8. [学习笔记]Splay

    其实就是一道题占坑啦 [NOI2005]维护数列 分析: 每次操作都要 \(Splay\) 一下 \(Insert\) 操作:重建一棵平衡树,把 \(l\) 变成根,\(l+2\) 变成右子树的根,那 ...

  9. intellij 引入本地库并war打包

    一.引入本地库 1.File -> Project Structure -> Libraries,点击+,新增本地lib库. 2.File -> Project Structure ...

  10. 第十三章 ReentrantLock 简介

    Java 5.0 提供的新的加锁机制:当内置加锁机制不适合时 , 作为一种可选择的高级功能 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一 ...