如何在PHP中为JSON API实现缓存系统

7
我在我的网站上有一些自定义的社交按钮,我使用API从json获取分享数/关注者数量。我尝试实现缓存系统来减少加载时间并消除因过度使用API而被“红旗标记”的风险。然而,我在这个领域没有取得成功,主要是因为我不太理解集成步骤。我希望有人能帮助我集成缓存系统。
以下是Twitter、Google Plus和Instagram的php代码:
Twitter ob_start(); $twittershare = 'http://cdn.api.twitter.com/1/urls/count.json?url='.$product["href"] .''; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $twittershare); curl_setopt($ch, CURLOPT_HEADER, 0); $jsonstring = curl_exec($ch); curl_close($ch); $bufferstr = ob_get_contents(); ob_end_clean(); $json = json_decode($bufferstr);
echo $json->count;
Google Plus $url = ''.$product["href"] .''; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "https://clients6.google.com/rpc?key=xxxxxxxxxx"); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, '[{"method":"pos.plusones.get","id":"p","params":{"nolog":true,"id":"' . $url . '","source":"widget","userId":"@viewer","groupId":"@self"},"jsonrpc":"2.0","key":"p","apiVersion":"v1"}]'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json')); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $curl_results = curl_exec($ch); curl_close($ch); $json = json_decode($curl_results, true); $count = intval($json[0]['result']['metadata']['globalCounts']['count']); $data = array(); $data['plus_count'] = (string) $count; $data['url'] = $url; echo $data['plus_count'];
Instagram(获取关注者数量)
ob_start(); $insta = 'https://api.instagram.com/v1/users/00000000?access_token={token}'; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $insta); curl_setopt($ch, CURLOPT_HEADER, 0); $jsonstring = curl_exec($ch); curl_close($ch); $bufferstr = ob_get_contents(); ob_end_clean(); $json = json_decode($bufferstr);
echo $json->data->counts->followed_by;
希望你们能逐步指导我如何为上述代码片段实现缓存系统。

你提到了“消除过度使用API被标记的风险”,我想知道:这些限制是什么?你的网站会收到那么多请求吗?关于缓存系统:这取决于具体情况:你可以将请求结果与时间戳存储在数据库或memcached中,并且仅在需要时(通过单独的服务,例如通过cron作业)更新它们。你的页面仅从数据后端读取并在不可用时获取它。 - nietonfir
@nietonfir,我真的不知道极限在哪里,但主要问题是页面加载时间太长。是的,我已经读过相关资料了,只是不知道如何实际实现。将结果存储在服务器上的文件中是否会更容易且对数据库压力更小呢?再次强调,这只是我从各处阅读到的猜测。 - Cristi Silaghi
不,你不想把任何东西存储在文件中。如果页面加载时间是你最关心的问题,那么就不要在请求期间从API(或缓存系统)获取数据,而是通过异步方式(通过AJAX)获取数据。这也将允许您独立实现缓存系统。 - nietonfir
1个回答

6

好的,如我在评论中提到的那样,我会使用Memcached和数据库,但我会起草一个仅使用数据库的解决方案(用PDO为Twitter),并将Memcached部分作为额外练习留给你。

当需要更新关注者计数时,我会通过AJAX加载关注者信息以减少页面加载时间。

我将使用以下数据库架构:

CREATE TABLE IF NOT EXISTS `Followers` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `url` varchar(100) NOT NULL,
  `data` longtext NOT NULL,
  `followers` int(5) NOT NULL,
  `last_update` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

首先,我会定义一个接口,这样就不会依赖于任何具体实现:

interface SocialFollowers
{
    public function getFollowers();
}

对于 Twitter 分享 API,我会有一个实现类来获取数据库句柄和目标 URL 进行初始化。如果有可用的数据,则会填充类属性。如果时间戳足够新,则可以立即获取关注者数量;否则将查询 API,存储结果,然后检索关注者数量。

class TwitterFollowers implements SocialFollowers
{
    private $data = null;
    private $url = "";
    private $db = null;
    private $followers = null;

    protected $shareURL = "https://cdn.api.twitter.com/1/urls/count.json?url=";

    public function __construct($db, $url) {
        // initialize the database connection here
        // or use an existing handle
        $this->db = $db;

        // store the url
        $this->url = $url;

        // fetch the record from the database
        $stmt = $this->db->prepare('SELECT * FROM `Followers` WHERE url = :url ORDER BY last_update DESC LIMIT 1');
        $stmt->bindParam(":url", $url);
        $stmt->execute();

        $this->data = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!empty($this->data))
            $this->followers = $this->data["followers"];
    }

    public function getFollowers()
    {
        // create a timestamp that's 30 minutes ago
        // if it's newer than the value from the database -> call the api
        $old = new DateTime();
        $old->sub(new DateInterval("PT30M"));

        if (is_null($this->followers) || (new DateTime($this->data["last_update"]) < $old) ) {
            return $this->retrieveFromAPI();
        }

        return $this->followers;
    }

    private function retrieveFromAPI()
    {
        // mostly untouched
        ob_start();
        $twittershare = $this->shareURL . $this->url;

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $twittershare);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        $jsonstring = curl_exec($ch);
        curl_close($ch);
        $bufferstr = ob_get_contents();
        ob_end_clean();
        $json = json_decode($bufferstr);

        $this->followers = $json->count;

        // store the retrieved values in the database
        $stmt = $this->db->prepare('INSERT INTO Followers (url, data, followers)'
            .'VALUES (:url, :data, :followers)');
        $stmt->execute(array(
            ":url" => $this->url,
            ":data" => $bufferstr,
            ":followers" => $this->followers
        ));

        return $this->followers;
    }
}

对于Facebook、Google+和下一个社交网络,您只需要添加另一个实现。

请记住,此代码未经过测试。它缺少一些针对PDO查询的try/catch块,并且有改进的空间(例如:缺少某种锁定机制以防止同时检索相同的URL,是否必要存储返回的二进制大对象等)。

希望这可以帮助您。

[编辑]我稍微更新了代码(修正了一些拼写错误和转换问题)并进行了测试。您可以在Github上找到一个可工作的版本。唯一缺少的是ajax片段(假设使用jQuery),如下:

$.ajax({
    url: "http://example.com/twitter.php",
    type: "get",
    data: {url: "http://stackoverflow.com"}
    success: function(data, textStatus, jqXHR) {
        // Update the corresponding counter like
        // $("#twitterfollowers").text(data);
        console.log(data);
    }
});

谢谢@nietonfir的回答,但不幸的是它并没有起作用,反而破坏了页面。我不知道,我只是不想去动数据库,因为它是存储我的网站重要信息的主要核心。 - Cristi Silaghi
@CristiSilaghi 我不知道你是如何得出“搞乱数据库”的结论的,因为数据库是用来存储数据的。;-) 如果您不想将该信息保存在与您的网站相同的存储层中,请使用另一个数据库/表。正如我所说,代码未经测试或完整(例如缺少AJAX调用),但应该能给您一个想法。 - nietonfir
你说得对,我没有考虑使用单独的数据库。我太傻了 :) 我不懂这种高级别的PHP编码,所以一个不完整的代码对我来说并没有什么帮助。这就是我发布这个问题的原因,是为了在实现过程中获得帮助。我在互联网上找到了一些教程,但在缓存部分,我还是 PHP 的新手。 - Cristi Silaghi
@CristiSilaghi 我更新了答案并修复了代码示例。我希望这个可工作的代码能够为您提供足够的信息,让您顺利进行。祝你好运。 - nietonfir
1
@MaXi32,你可以这样做,但这将首先破坏缓存的整个概念,因为会话绑定到具有相应会话密钥的用户代理。而且默认情况下会话处理程序是文件,因此数据库连接/redis/memcached/...肯定会更快。 - nietonfir
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接