存储 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 应用程序。