从函数参数生成Memcached键

5
这个问题与一个关于Java的问题类似,但我正在使用PHP,所以我认为它不符合重复的要求。
当调用此函数时,我希望有一种生成确定性键的方法。该函数应像读取缓存一样运行。如果键存在,则检索数据。如果不存在,则调用函数存储数据,然后返回数据。
这是我目前的代码,它可以工作,但我不确定它是否安全、确定性足够或唯一性足够,因为我对这些主题一窍不通。
// $call = function being called $args = arguments to that function
// $force = force cache to bypassed, then updated
public function cachedCall($call,$args = [],$force = false)
{
    $cache = \App\App::getInstance()->cache;
    $key = md5($call) . md5(serialize($args));
    $res = $cache->get($key);
    if($res === -1 || $force){
        $res = call_user_func_array([$this,$call],$args);
        if(!empty($res) && $res !== false && $res !== 0 && !is_null($res)){
            $cache->set($key,$res,0); //never set empty data in the cache.
        }
    }
    return $res;
}

我的问题只涉及第三行,其中密钥是通过调用函数和提供给该函数的参数计算出来的。在某些情况下,我遇到了碰撞。我正在寻找改进方法,使其更有用且哈希值一致但不太可能发生碰撞。第三个参数可以忽略,因为它只是一种绕过缓存的方式。
此函数的调用示例: $data = $db->cachedCall('getUserByEmail',[$this->email],true); $data = $db->cachedCall('getCell',['SELECT id FROM foobar WHERE foo=:bar',[':bar'=>55]]);
如果可能的话,我希望同时保证密钥具有一致的长度。

我给出的密钥方案 $key = md5($caller . md5($call) . md5(serialize($args))); 有什么问题吗?它使用了参数和调用的起点。看看 "$caller" 是如何生成的,将其打印出来并决定这是否是您想要的。 - Harry
调用者无关紧要。底线是,如果将相同的参数传递给函数,则会进行相同的数据库调用并返回相同的记录。您的答案没有任何有用的内容,而且会导致重复记录的副作用。我的目标是在保持确定性键的同时减少冲突的概率。 - r3wt
我更新了我的帖子。我只是想帮忙。 - Harry
$this是什么?它是单例还是您的应用程序中有多个对象实例? - user149341
它是一个单例,但即使有多个对象实例,也不会有影响。 - r3wt
2个回答

1
如果您的参数在每个查询中保证唯一,并且仍然出现冲突,则我认为您的代码可能存在错误。使用MD5发生碰撞的可能性很小... 有多少随机元素才能使MD5产生碰撞? 如果您看到有冲突,那么就有问题了。PHP序列化一个数组将按顺序进行序列化,因此md5(serialize($array_here)应该是安全的。I曾遇到过这样的问题,在尝试传递单个数组时未对来自调用函数的参数进行包装。如果在调用之前您的参数已经在数组中,则不会出现任何问题。

如果您以正确的格式传递参数(即以盒装格式$db->cachedCall('someMethod',[$args])),那么cachedCall方法会如何中断呢?调用范围负责正确调用该方法,cachedCall方法不负责安全保护代码。我完全不明白您试图表达什么意思。 - r3wt

1
这是因为在不同的实例中,密钥可能相同,例如当调用方法“cachedCall”具有相同的参数时。我想你应该为每个实例共享相同的“memcached”服务器,这就是为什么会发生缓存冲突的原因。

演示

如我所读,变量$call将具有与代码的任何其他部分共享的有限值,因为它包含包含方法“cachedCall”的类的方法名,这意味着很容易出现两个不同的调用共享此值。
此外,您可以使用空数组参数调用此方法。
因此,在两个不同的实例中拥有相同的方法调用非常容易:
cachedCall('methodX', array()); <- From instance A
cachedCall('methodX', array()); <- From instance B

这将把此内容存储在同一个Memcached键中。

解决方案

在方法内,以某种方式考虑实例名称。例如,您可以使用当前URL作为键的一部分,或者使用域名(取决于您的情况):

$key = md5($call) . md5(serialize($args)) . md5($_SERVER['HTTP_HOST']);
$key = md5($call) . md5(serialize($args)) . md5($_SERVER['REQUEST_URI']);

在上面,您可以看到两个示例,说明如何根据您的实例更改memcached键

在这个解决方案中,我会包括类名和方法,因为两个类可能有相同的方法。 $key = md5(__CLASS__ . $call) . md5(serialize($args)) . md5($_SERVER['REQUEST_URI']); - Steve E.
如果使用相同的参数调用该函数,它应该生成相同的密钥。$call 是在数据库类上被调用的方法的名称,而 $args 则是在调用该函数时要提供的参数。目标是为 $call + $args 的组合生成一致的密钥,与 md5/序列化组合相比,冲突的可能性较小。似乎有一些可能发生 md5 冲突的情况。您的想法不是解决方案,因为它会导致数据集的不必要的额外副本。 - r3wt
你的问题不是MD5碰撞,我建议你尝试其他哈希算法或者不使用哈希算法来检查是否仍然发生碰撞。你的问题是在不同实例中使用相同参数调用cachedCall,然后发生了碰撞,你能否尝试一下?然后我们就可以排除疑虑了 :) - Miguel
如果使用相同的参数调用cacheCall,则它是相同的调用,应该返回相同的结果... 哎呀 - r3wt
1
取决于实例是否使用相同的数据库,你是正确的,但这是你没有具体说明的一个点。如果每个实例使用不同的数据库,则将是相同的方法调用,但并不意味着结果应该相同。例如,如果我有两个使用不同数据库且共享memcache服务器的WordPress实例,则使用相同参数调用该方法应返回不同的值。请礼貌一些,因为我正在试图帮助你,你的技术问题不是我的问题。谢谢。 - Miguel

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