Redis如何实现服务端数学计算避免遍历查询提升处理性能?
Redis避免客户端循环遍历的优化方案
以下方案按优先级从高到低排列,可根据业务场景选择:
方案1:Lua服务端脚本实现全链路计算(推荐)
Redis支持服务端执行Lua脚本,你可以把GEO查询、Hash属性拉取、加权求和的逻辑全部封装到Lua脚本中,全程仅需1次客户端与Redis的网络交互,从根本上消除循环调用的IO开销。
示例Lua脚本逻辑:
-- 入参说明:KEYS[1]为GEO存储的key名,ARGV依次为查询类型、经度、纬度、半径、单位、半径数值 local geoPoints = redis.call('GEOSEARCH', KEYS[1], ARGV[1], ARGV[2], ARGV[3], 'BYRADIUS', ARGV[4], ARGV[5], 'WITHDIST') local weightedSum = 0 local radius = tonumber(ARGV[4]) for _, point in ipairs(geoPoints) do local memberId = point[1] local dist = tonumber(point[2]) -- 仅查询需要的cc字段,不用hgetall进一步降低开销 local ccVal = redis.call('HGET', memberId, 'cc') if ccVal then weightedSum = weightedSum + (tonumber(ccVal) * (radius - (dist / radius))) end end return weightedSum
PHP调用示例:
$luaScript = <<<LUA -- 将上述Lua脚本内容粘贴到此处 LUA; // 最后一个参数表示传入的KEYS数量为1 $weightedSum = $redis->eval($luaScript, [ $tableName, $type, $lon, $lat, $radius, $metric ], 1);
该方案性能提升最明显,所有计算都在Redis服务端完成,不需要把大量点位数据传回客户端处理,逻辑轻量不会对Redis单线程性能造成明显影响。
方案2:Pipeline批量请求(适配无法使用Lua的场景)
如果你暂时不能改造为Lua脚本,可以用Redis的Pipeline特性,先拿到GEOSEARCH的结果后,把所有Hash查询请求一次性批量发送给Redis,再批量接收结果,把N次网络往返减少为2次。
示例代码:
$geoPoints= $redis->executeRaw(["GEOSEARCH" ,$tableName , $type ,$lon ,$lat ,"BYRADIUS" ,$radius, $metric, "WITHDIST"]); $weightedSum = 0; // 开启Pipeline批量提交hget请求 $pipe = $redis->multi(\Redis::PIPELINE); foreach ($geoPoints as $point) { $pipe->hget($point[0], 'cc'); } $ccList = $pipe->exec(); // 本地计算加权和 foreach ($geoPoints as $k => $point) { if (!empty($ccList[$k])) { $cc = (float)$ccList[$k]; $dist = (float)$point[1]; $weightedSum += ($cc * ($radius - ($dist / $radius))); } }
方案3:冗余存储属性到GEO成员(适配属性极少的场景)
如果你的业务只需要用到cc这一个属性,可以直接把id:cc拼接为GEO的member值,比如GEO添加点位时成员名为2819483906:2,这样GEOSEARCH返回结果后直接拆分成员名就能拿到cc值,完全不需要查询Hash。
内容的提问来源于stack exchange,提问作者Rajesh
相关产品推荐
相关产品推荐

