存储 API 请求数据库与内存缓存
Storing API requests database vs memcached
我处于拿不定主意的境地。我有一个客户端管理系统 (Whmcs),我们公司用它来销售虚拟主机,vps,...目前我正在编写一个新的独立客户端界面,它使用 WHMCS 的 API 来检索客户的详细信息、发票、支持、门票和产品。所有其他逻辑都发生在新应用程序中(配置产品、支付发票...)
这些 API 请求发生在专用网络上,速度不是这里的问题。但是,我想将它们存储在 Memcached 或单独的数据库中,以将这些请求减少到最低限度。
如果我使用数据库,我会使用某种 webhook,WHMCS 将调用该 webhook,例如,当用户有新发票时。这将被发送到 webhook,只有当 WHMCS 中发生某些变化时,数据库才会更新。我在这里看到的唯一缺点是一些数据存在于两个系统上,必须编写大量事件和 webhook 才能实现这一点。
另一种解决方案是使用我们的 memcached 集群并将所需的 API 请求存储在那里,如果用户登录,则返回所需的数据然后存储在 memcached 中。这样,只有一个 API 请求,直到发生变化。这种方式比较容易实现,例子如下:
public function getInvoices($status = null)
{
if (!Cache::has('invoices_' . $status . $this->id)){
$data = $this->fetchInvoices($this->id, $status);
Cache::put('invoices_' . $status . $this->id, $data, 30);
}
$data = Cache::get('invoices_' . $status . $this->id);
//Check if user owns the invoices
if (Gate::denies('owns-data', $this->id)){
abort(401);
}
return $data;
}
最好的方法是什么?
在 WHMCS 上创建新客户端界面的有趣想法!
我不会选择使用挂钩和在两个系统中拥有数据的第一条路线。感觉迟早系统会不同步,您不能相信数据库中的数据。
在开始优化和缓存之前,您是否尝试过一直使用 API 作为 "source of truth"?可能是您的应用程序足够快,即使您每次需要数据时都点击 API?
在那种情况下,您的应用程序将非常简单,永远不必担心缓存和使缓存数据失效。例如,如果您将有关发票的信息缓存了 30 分钟,会发生什么情况。然后发票由 WHMCS 内部的管理员或它自己的系统更改。现在您的应用程序将不同步 30 分钟,因为它不知道发票已更改。
最终,我想出了一个解决方案。这个想法是将客户数据和应用程序数据保存在不同的数据库中。应用程序数据库包含一些基本用户信息、博客 posts、论坛数据……第二个数据库是 WHMCS 数据库本身,我从中提取发票、产品、支持票……所需的所有数据
任何需要发送到 WHMCS 的更新(发票支付、升级产品...)都通过 API 进行。这样 WHMCS 就可以对传入的数据执行自己的逻辑。
我实现的第二部分是一个强大的自定义缓存系统,它限制了对我们的基础设施进行的 API 查询。 (Openstack、cPanel,...)这个想法非常简单:应用程序的后端使用 __call
方法侦听函数名称中包含的创建、更新、读取和删除。
例如:
$data = array(
'hostname' => $r['host'],
'username' => $r['username'],
);
Cpanel::listAddonDomain($data);
这列出了用户在 cPanel 中添加到其托管帐户的所有插件域。减少请求量这需要缓存,直到用户进行了更改或他已注销。
首先,有一些逻辑会检查用户是否拥有 cPanel 帐户,然后将 listAddondomains
拆分成一个数组,如果第一个键等于 list, read or get
,则数据会使用定义数据所属用户的标签:
public function __call($name, $arguments)
{
// Check if the user owns the account
if(Gate::denies('owns-data', $this->search($this->getProducts(), 'username', $arguments['username'])[0]['clientid']))
{
if ($request == 'api'){
return Api::respondNotAllowed('Not Allowed!');
}
abort(404);
}
$nameSplit = preg_split('/(?=\p{Lu})/u', $name);
// Check for arguments to flush the cache
if($nameSplit[0] == 'create' || $nameSplit[0] == 'delete' || $nameSplit[0] == 'add' || $nameSplit[0] == 'install')
{
Cache::tags(['cpanel', $arguments['username']])
->flush();
}
// If is already present in cache
if(Cache::tags(['cpanel', $arguments['username']])->get($name . '_' . Auth::user()->id))
{
return Cache::tags(['cpanel', $arguments['username']])
->get($name . '_' . Auth::user()->id);
}
// Do the query
try{
$data = $this->send($arguments, $this->getClass($nameSplit), $name);
$response = Api::respondSuccess($data);
}catch (Exception $e){
return Api::respondInternalError($e);
}
Cache::tags(['cpanel', $arguments['username']])
->put($name . '_' . Auth::user()->id, $response, $this->cacheTime);
return $response;
}
private function send($arguments, $className, $functionName)
{
$do = new $className($arguments);
return $do->$functionName();
}
上面的代码被减少到最低限度。在这里 post 太长了,不过你会明白的。
现在如何在正确的时间清除缓存?假设用户创建了一个新的插件域,需要清除现有的缓存。
$data = array(
'hostname' => $r['host'],
'username' => $r['username'],
'domain' => $r['domain']
);
Cpanel::createAddonDomain($data);
$nameSplit = preg_split('/(?=\p{Lu})/u', 'createAddonDomain');
if($nameSplit[0] == 'create' || $nameSplit[0] == 'delete' || $nameSplit[0] == 'add' || $nameSplit[0] == 'install')
{
Cache::tags(['cpanel', $arguments['username']])
->flush();
}
如果插件域创建成功,则该 cPanel 帐户的缓存会被刷新。
我已经测试了一个星期了,效果非常好。我已经看到启用缓存并直接从 WHMCS 数据库获取数据后页面加载时间大大减少。
下一站,将所有 laravel 视图重写为独立的 Angular 应用程序。
我处于拿不定主意的境地。我有一个客户端管理系统 (Whmcs),我们公司用它来销售虚拟主机,vps,...目前我正在编写一个新的独立客户端界面,它使用 WHMCS 的 API 来检索客户的详细信息、发票、支持、门票和产品。所有其他逻辑都发生在新应用程序中(配置产品、支付发票...)
这些 API 请求发生在专用网络上,速度不是这里的问题。但是,我想将它们存储在 Memcached 或单独的数据库中,以将这些请求减少到最低限度。
如果我使用数据库,我会使用某种 webhook,WHMCS 将调用该 webhook,例如,当用户有新发票时。这将被发送到 webhook,只有当 WHMCS 中发生某些变化时,数据库才会更新。我在这里看到的唯一缺点是一些数据存在于两个系统上,必须编写大量事件和 webhook 才能实现这一点。
另一种解决方案是使用我们的 memcached 集群并将所需的 API 请求存储在那里,如果用户登录,则返回所需的数据然后存储在 memcached 中。这样,只有一个 API 请求,直到发生变化。这种方式比较容易实现,例子如下:
public function getInvoices($status = null)
{
if (!Cache::has('invoices_' . $status . $this->id)){
$data = $this->fetchInvoices($this->id, $status);
Cache::put('invoices_' . $status . $this->id, $data, 30);
}
$data = Cache::get('invoices_' . $status . $this->id);
//Check if user owns the invoices
if (Gate::denies('owns-data', $this->id)){
abort(401);
}
return $data;
}
最好的方法是什么?
在 WHMCS 上创建新客户端界面的有趣想法!
我不会选择使用挂钩和在两个系统中拥有数据的第一条路线。感觉迟早系统会不同步,您不能相信数据库中的数据。
在开始优化和缓存之前,您是否尝试过一直使用 API 作为 "source of truth"?可能是您的应用程序足够快,即使您每次需要数据时都点击 API?
在那种情况下,您的应用程序将非常简单,永远不必担心缓存和使缓存数据失效。例如,如果您将有关发票的信息缓存了 30 分钟,会发生什么情况。然后发票由 WHMCS 内部的管理员或它自己的系统更改。现在您的应用程序将不同步 30 分钟,因为它不知道发票已更改。
最终,我想出了一个解决方案。这个想法是将客户数据和应用程序数据保存在不同的数据库中。应用程序数据库包含一些基本用户信息、博客 posts、论坛数据……第二个数据库是 WHMCS 数据库本身,我从中提取发票、产品、支持票……所需的所有数据
任何需要发送到 WHMCS 的更新(发票支付、升级产品...)都通过 API 进行。这样 WHMCS 就可以对传入的数据执行自己的逻辑。
我实现的第二部分是一个强大的自定义缓存系统,它限制了对我们的基础设施进行的 API 查询。 (Openstack、cPanel,...)这个想法非常简单:应用程序的后端使用 __call
方法侦听函数名称中包含的创建、更新、读取和删除。
例如:
$data = array(
'hostname' => $r['host'],
'username' => $r['username'],
);
Cpanel::listAddonDomain($data);
这列出了用户在 cPanel 中添加到其托管帐户的所有插件域。减少请求量这需要缓存,直到用户进行了更改或他已注销。
首先,有一些逻辑会检查用户是否拥有 cPanel 帐户,然后将 listAddondomains
拆分成一个数组,如果第一个键等于 list, read or get
,则数据会使用定义数据所属用户的标签:
public function __call($name, $arguments)
{
// Check if the user owns the account
if(Gate::denies('owns-data', $this->search($this->getProducts(), 'username', $arguments['username'])[0]['clientid']))
{
if ($request == 'api'){
return Api::respondNotAllowed('Not Allowed!');
}
abort(404);
}
$nameSplit = preg_split('/(?=\p{Lu})/u', $name);
// Check for arguments to flush the cache
if($nameSplit[0] == 'create' || $nameSplit[0] == 'delete' || $nameSplit[0] == 'add' || $nameSplit[0] == 'install')
{
Cache::tags(['cpanel', $arguments['username']])
->flush();
}
// If is already present in cache
if(Cache::tags(['cpanel', $arguments['username']])->get($name . '_' . Auth::user()->id))
{
return Cache::tags(['cpanel', $arguments['username']])
->get($name . '_' . Auth::user()->id);
}
// Do the query
try{
$data = $this->send($arguments, $this->getClass($nameSplit), $name);
$response = Api::respondSuccess($data);
}catch (Exception $e){
return Api::respondInternalError($e);
}
Cache::tags(['cpanel', $arguments['username']])
->put($name . '_' . Auth::user()->id, $response, $this->cacheTime);
return $response;
}
private function send($arguments, $className, $functionName)
{
$do = new $className($arguments);
return $do->$functionName();
}
上面的代码被减少到最低限度。在这里 post 太长了,不过你会明白的。
现在如何在正确的时间清除缓存?假设用户创建了一个新的插件域,需要清除现有的缓存。
$data = array(
'hostname' => $r['host'],
'username' => $r['username'],
'domain' => $r['domain']
);
Cpanel::createAddonDomain($data);
$nameSplit = preg_split('/(?=\p{Lu})/u', 'createAddonDomain');
if($nameSplit[0] == 'create' || $nameSplit[0] == 'delete' || $nameSplit[0] == 'add' || $nameSplit[0] == 'install')
{
Cache::tags(['cpanel', $arguments['username']])
->flush();
}
如果插件域创建成功,则该 cPanel 帐户的缓存会被刷新。
我已经测试了一个星期了,效果非常好。我已经看到启用缓存并直接从 WHMCS 数据库获取数据后页面加载时间大大减少。
下一站,将所有 laravel 视图重写为独立的 Angular 应用程序。