This commit is contained in:
2025-10-09 17:41:57 +00:00
commit 7a3b2960f8
146 changed files with 34948 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
# GlobalData
进程间数据共享组件,用于分布式数据共享。服务端基于[Workerman](https://github.com/walkor/Workerman)。客户端可用于任何PHP项目。
# 服务端
```php
use Workerman\Worker;
require_once __DIR__ . '/../../Workerman/Autoloader.php';
require_once __DIR__ . '/../src/Server.php';
$worker = new GlobalData\Server('127.0.0.1', 2207);
Worker::runAll();
```
# 客户端
```php
require_once __DIR__ . '/../src/Client.php';
$global = new GlobalData\Client('127.0.0.1:2207');
var_export(isset($global->abc));
$global->abc = array(1,2,3);
var_export($global->abc);
unset($global->abc);
var_export($global->abc);
$global->abc = array(1,2,3);
var_export($global->abc);
var_export($global->cas('abc', array(1,2,3), array(5,6,7)));
var_export($global->abc);
```
+12
View File
@@ -0,0 +1,12 @@
{
"name" : "workerman/globaldata",
"type" : "library",
"homepage": "http://www.workerman.net",
"license" : "MIT",
"require": {
"workerman/workerman" : ">=3.3.0"
},
"autoload": {
"psr-4": {"GlobalData\\": "./src"}
}
}
+251
View File
@@ -0,0 +1,251 @@
<?php
namespace GlobalData;
/**
* Global data client.
* @version 1.0.3
*/
class Client
{
/**
* Timeout.
* @var int
*/
public $timeout = 5;
/**
* Heartbeat interval.
* @var int
*/
public $pingInterval = 25;
/**
* Global data server address.
* @var array
*/
protected $_globalServers = array();
/**
* Connection to global server.
* @var resource
*/
protected $_globalConnections = null;
/**
* Cache.
* @var array
*/
protected $_cache = array();
/**
* Construct.
* @param array | string $servers
*/
public function __construct($servers)
{
if(empty($servers))
{
throw new \Exception('servers empty');
}
$this->_globalServers = array_values((array)$servers);
}
/**
* Connect to global server.
* @throws \Exception
*/
protected function getConnection($key)
{
$offset = crc32($key)%count($this->_globalServers);
if($offset < 0)
{
$offset = -$offset;
}
if(!isset($this->_globalConnections[$offset]) || !is_resource($this->_globalConnections[$offset]) || feof($this->_globalConnections[$offset]))
{
$address = $this->_globalServers[$offset];
if(strpos($address,'unix://')!==0){
$address = "tcp://{$address}";
}
$connection = stream_socket_client($address, $code, $msg, $this->timeout);
if(!$connection)
{
throw new \Exception($msg);
}
stream_set_timeout($connection, $this->timeout);
if(class_exists('\Workerman\Timer') && php_sapi_name() === 'cli')
{
$timer_id = \Workerman\Timer::add($this->pingInterval, function($connection)use(&$timer_id)
{
$buffer = pack('N', 8)."ping";
if(strlen($buffer) !== @fwrite($connection, $buffer))
{
@fclose($connection);
\Workerman\Timer::del($timer_id);
}
}, array($connection));
}
$this->_globalConnections[$offset] = $connection;
}
return $this->_globalConnections[$offset];
}
/**
* Magic methods __set.
* @param string $key
* @param mixed $value
* @throws \Exception
*/
public function __set($key, $value)
{
$connection = $this->getConnection($key);
$this->writeToRemote(array(
'cmd' => 'set',
'key' => $key,
'value' => $value,
), $connection);
$this->readFromRemote($connection);
}
/**
* Magic methods __isset.
* @param string $key
*/
public function __isset($key)
{
return null !== $this->__get($key);
}
/**
* Magic methods __unset.
* @param string $key
* @throws \Exception
*/
public function __unset($key)
{
$connection = $this->getConnection($key);
$this->writeToRemote(array(
'cmd' => 'delete',
'key' => $key
), $connection);
$this->readFromRemote($connection);
}
/**
* Magic methods __get.
* @param string $key
* @throws \Exception
*/
public function __get($key)
{
$connection = $this->getConnection($key);
$this->writeToRemote(array(
'cmd' => 'get',
'key' => $key,
), $connection);
return $this->readFromRemote($connection);
}
/**
* Cas.
* @param string $key
* @param mixed $value
* @throws \Exception
*/
public function cas($key, $old_value, $new_value)
{
$connection = $this->getConnection($key);
$this->writeToRemote(array(
'cmd' => 'cas',
'md5' => md5(serialize($old_value)),
'key' => $key,
'value' => $new_value,
),$connection);
return $this->readFromRemote($connection);
}
/**
* Add.
* @param string $key
* @throws \Exception
*/
public function add($key, $value)
{
$connection = $this->getConnection($key);
$this->writeToRemote(array(
'cmd' => 'add',
'key' => $key,
'value' => $value,
), $connection);
return $this->readFromRemote($connection);
}
/**
* Increment.
* @param string $key
* @throws \Exception
*/
public function increment($key, $step = 1)
{
$connection = $this->getConnection($key);
$this->writeToRemote(array(
'cmd' => 'increment',
'key' => $key,
'step' => $step,
), $connection);
return $this->readFromRemote($connection);
}
/**
* Write data to global server.
* @param string $buffer
*/
protected function writeToRemote($data, $connection)
{
$buffer = serialize($data);
$buffer = pack('N',4 + strlen($buffer)) . $buffer;
$len = fwrite($connection, $buffer);
if($len !== strlen($buffer))
{
throw new \Exception('writeToRemote fail');
}
}
/**
* Read data from global server.
* @throws Exception
*/
protected function readFromRemote($connection)
{
$all_buffer = '';
$total_len = 4;
$head_read = false;
while(1)
{
$buffer = fread($connection, 8192);
if($buffer === '' || $buffer === false)
{
throw new \Exception('readFromRemote fail');
}
$all_buffer .= $buffer;
$recv_len = strlen($all_buffer);
if($recv_len >= $total_len)
{
if($head_read)
{
break;
}
$unpack_data = unpack('Ntotal_length', $all_buffer);
$total_len = $unpack_data['total_length'];
if($recv_len >= $total_len)
{
break;
}
$head_read = true;
}
}
return unserialize(substr($all_buffer, 4));
}
}
+114
View File
@@ -0,0 +1,114 @@
<?php
namespace GlobalData;
use Workerman\Protocols\Frame;
use Workerman\Worker;
/**
* Global data server.
*/
class Server
{
/**
* Worker instance.
* @var worker
*/
protected $_worker = null;
/**
* All data.
* @var array
*/
protected $_dataArray = array();
/**
* Construct.
* @param string $ip
* @param int $port
*/
public function __construct($ip = '0.0.0.0', $port = 2207)
{
if (strpos($ip, 'unix://') === 0) {
$worker = new Worker($ip);
$worker->protocol = Frame::class;
} else {
$worker = new Worker("frame://$ip:$port");
}
$worker->count = 1;
$worker->name = 'globalDataServer';
$worker->onMessage = array($this, 'onMessage');
$worker->reloadable = false;
$this->_worker = $worker;
}
/**
* onMessage.
* @param TcpConnection $connection
* @param string $buffer
*/
public function onMessage($connection, $buffer)
{
if($buffer === 'ping')
{
return;
}
$data = unserialize($buffer);
if(!$buffer || !isset($data['cmd']) || !isset($data['key']))
{
return $connection->close(serialize('bad request'));
}
$cmd = $data['cmd'];
$key = $data['key'];
switch($cmd)
{
case 'get':
if(!isset($this->_dataArray[$key]))
{
return $connection->send('N;');
}
return $connection->send(serialize($this->_dataArray[$key]));
break;
case 'set':
$this->_dataArray[$key] = $data['value'];
$connection->send('b:1;');
break;
case 'add':
if(isset($this->_dataArray[$key]))
{
return $connection->send('b:0;');
}
$this->_dataArray[$key] = $data['value'];
return $connection->send('b:1;');
break;
case 'increment':
if(!isset($this->_dataArray[$key]))
{
return $connection->send('b:0;');
}
if(!is_numeric($this->_dataArray[$key]))
{
$this->_dataArray[$key] = 0;
}
$this->_dataArray[$key] = $this->_dataArray[$key]+$data['step'];
return $connection->send(serialize($this->_dataArray[$key]));
break;
case 'cas':
$old_value = !isset($this->_dataArray[$key]) ? null : $this->_dataArray[$key];
if(md5(serialize($old_value)) === $data['md5'])
{
$this->_dataArray[$key] = $data['value'];
return $connection->send('b:1;');
}
$connection->send('b:0;');
break;
case 'delete':
unset($this->_dataArray[$key]);
$connection->send('b:1;');
break;
default:
$connection->close(serialize('bad cmd '. $cmd));
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
use GlobalData\Client;
require_once __DIR__ . '/../src/Client.php';
$global = new Client('127.0.0.1:2207');
$i = $j = 10000;
$t = microtime(true);
while($i--)
{
$global->a = array(1,3);
}
echo ceil($j/(microtime(true)-$t))."qps\n";
+7
View File
@@ -0,0 +1,7 @@
<?php
use Workerman\Worker;
use GlobalData\Server;
require_once __DIR__ . '/../../Workerman/Autoloader.php';
require_once __DIR__ . '/../src/Server.php';
$worker = new Server();
Worker::runAll();
+40
View File
@@ -0,0 +1,40 @@
<?php
use GlobalData\Client;
require_once __DIR__ . '/../src/Client.php';
$global = new Client('127.0.0.1:2207');
echo "\n\nisset(\$global->abc)=";
var_export(isset($global->abc));
echo "\n\n\$global->add('abc', 10)=";
var_export($global->add('abc', 10));
echo "\nnow \$global->abc=";
var_export($global->abc);
echo "\n\n\$global->increment('abc', 2)=";
var_export($global->increment('abc', 2));
echo "\nnow \$global->abc=";
var_export($global->abc);
echo ";\n\nset \$global->abc=";
$global->abc = array(1,2,3);
var_export($global->abc);
echo ";\n\nunset(\$global->abc)\n";
unset($global->abc);
echo "\nnow \$global->abc=";
var_export($global->abc);
echo "\n\nisset(\$global->abc)=";
var_export(isset($global->abc));
echo ";\n\nset \$global->abc=";
$global->abc = array(1,2,3);
var_export($global->abc);
echo ";\n\n\$global->cas('abc', array(1,2,3), array(5,6,7))=";
var_export($global->cas('abc', array(1,2,3), array(5,6,7)));
echo ";\n\nnow \$global->abc=";
var_export($global->abc);