乐趣区

关于golang:Redis-如何批量设置过期时间PIPLINE的使用

正当的应用缓存策略对开发同学来讲,就如同孙悟空习得自在极意功个别~

抛出问题

Redis 如何批量设置过期工夫呢?

不要说在 foreach 中通过 set()函数批量设置过期工夫

给出计划

咱们引入 redis 的 PIPLINE,来解决批量设置过期工夫的问题。

PIPLINE 的原理是什么?

  1. 未应用 pipline 执行 N 条命令

  1. 应用 pipline 执行 N 条命令

通过图例能够很显著的看进去 PIPLINE 的原理:

客户端通过 PIPLINE 拼接子命令,只须要发送一次申请,在 redis 收到 PIPLINE 命令后,解决 PIPLINE 组成的命令块,缩小了网络申请响应次数。

网络提早越大 PIPLINE 的劣势越能体现进去

拼接的子命令条数越多应用 PIPLINE 的劣势越能体现进去

留神:并不是拼接的子命令越多越好,N 值也有是下限的,当拼接命令过长时会导致客户端期待很长时间,造成网络梗塞;咱们能够依据理论状况,把大批量命令拆分成几个 PIPLINE 执行。

代码封装

// 批量设置过期工夫
public static function myPut(array $data, $ttl = 0)
{if (empty($data)) {return false;}

    $pipeline = Redis::connection('cache')
        ->multi(\Redis::PIPELINE);
    foreach ($data as $key => $value) {if (empty($value)) {continue;}
        if ($ttl == 0) {$pipeline->set(trim($key), $value);
        } else {$pipeline->set(trim($key), $value, $ttl);
        }
    }
    $pipeline->exec();}

我的项目实战

需要形容

  1. 关上 APP,给喜爱我的人发送我的上线告诉(为了防止打搅,8 小时内反复登录不触发告诉)
  2. 每个人每半小时只会收到一次这类上线告诉(即半小时内就算我喜爱的 1 万人都上线了,我也只收到一次喜爱的人上线告诉)

要点剖析

  1. 正当应用缓存,缩小 DB 读写次数
  2. 不仅要缩小 DB 读写次数,也要缩小 Redis 的读写次数,应用PIPLINE

代码实现解析

  1. canRecall() 写的比拟优雅,先判断是否已发送的标记,再判断 HouseOpen::getCurrentOpen(),因为HouseOpen::getCurrentOpen() 是要查问 DB 计算的,这种代码要尽可能少的被执行到,缩小 DB 查问。
  2. array_diff() 取差集的思路,取得须要推送的人
封装工具类
<?php

namespace App\Model\House;

.
.
.

class HouseLikeRecallUser
{
    protected $_userid = '';
    protected $_availableUser = [];
    protected $_recallFlagKey = '';

    const TYPE_TTL_HOUSE_LIKE_RECALL = 60 * 30; // 半小时后能够再次接管到喜爱的 xxx 进入告诉
    const TYPE_TTL_HOUSE_LIKE_RECALL_FLAG = 60 * 60 * 8; // 8 小时反复登录不触发

    // 初始化 传入 setRecalled 的过期工夫
    public function __construct($userid)
    {
        $this->_userid = $userid;
        // 登录后给喜爱我的人推送校验:同一场次反复登录不反复发送
        $this->_recallFlagKey = CacheKey::getCacheKey(CacheKey::TYPE_HOUSE_LIKE_RECALL_FLAG, $this->_userid);
    }

    // 设置以后用户推送标示
    public function setRecalled()
    {Cache::put($this->_recallFlagKey, 1, self::TYPE_TTL_HOUSE_LIKE_RECALL_FLAG);
    }

    // 获取以后用户是否触发推送
    public function canRecall()
    {
        $res = false;
        if (empty(Cache::get($this->_recallFlagKey))) {$houseOpen = HouseOpen::getCurrentOpen();
            if ($houseOpen['status'] == HouseOpen::HOUSE_STATUS_OPEN) {$res = true;}
        }
        return $res;
    }

    // 获取须要推送用户
    public function getAvailableUser()
    {
        // 取得最近喜爱我的用户
        $recentLikeMeUser = UserRelationSingle::getLikeMeUserIds($this->_userid, 100, Utility::getBeforeNDayTimestamp(7));

        // 取得最近喜爱我的用户的 RECALL 缓存标记
        foreach ($recentLikeMeUser as $userid) {$batchKey[] = CacheKey::getCacheKey(CacheKey::TYPE_HOUSE_LIKE_RECALL, $userid);
        }

        // 取得最近喜爱我的且曾经推送过的用户
        $cacheData = [];
        if (!empty($batchKey)) {$cacheData = Redis::connection('cache')->mget($batchKey);
        }

        // 计算最近喜爱我的用户 和 曾经推送过的用户 的差集:就是须要推送的用户
        $this->_availableUser = array_diff($recentLikeMeUser, $cacheData);
        return $this->_availableUser;
    }

    // 更新曾经推送的用户
    public function updateRecalledUser()
    {
        // 批量更新差集用户
        $recalledUser = [];
        foreach ($this->_availableUser as $userid) {$cacheKey = CacheKey::getCacheKey(CacheKey::TYPE_HOUSE_LIKE_RECALL, $userid);
            $recalledUser[$cacheKey] = $userid;
        }
        // 批量更新 设置过期工夫
        self::myPut($recalledUser, self::TYPE_TTL_HOUSE_LIKE_RECALL);
    }

    // 批量设置过期工夫
    public static function myPut(array $data, $ttl = 0)
    {if (empty($data)) {return false;}

        $pipeline = Redis::connection('cache')
            ->multi(\Redis::PIPELINE);
        foreach ($data as $key => $value) {if (empty($value)) {continue;}
            if ($ttl == 0) {$pipeline->set(trim($key), $value);
            } else {$pipeline->set(trim($key), $value, $ttl);
            }
        }
        $pipeline->exec();}
}
调用工具类
public function handle()
{
    $userid = $this->_userid;
    $houseLikeRecallUser = new HouseLikeRecallUser($userid);
    if ($houseLikeRecallUser->canRecall()) {$recallUserIds = $houseLikeRecallUser->getAvailableUser();
        $houseLikeRecallUser->setRecalled();
        $houseLikeRecallUser->updateRecalledUser();
        // 群发推送音讯
        .
        .
        .
    }
}

总结

不同量级的数据须要不同的解决方法,缩小网络申请次数,正当应用缓存,是性能优化的必经之路。

进一步思考

如果我喜爱的 1 万人同时上线(秒级并发),我只收到一个音讯推送,要防止被告诉轰炸,怎么解决这类并发问题呢?

小伙伴们有没有解决思路,能够在评论区探讨哦~

相干浏览举荐

性能优化反思:不要在 for 循环中操作 DB

性能优化反思:不要在 for 循环中操作 DB 进阶版

最初

👍🏻:感觉有播种请点个赞激励一下!

🌟:珍藏文章,不便回看哦!

💬:评论交换,相互提高!

本文由博客一文多发平台 OpenWrite 公布!

退出移动版