Redis 必知必会

原文是公司小伙伴内部分享的,后续会续上扩散问题相关资料,欢迎小伙伴在评论区里补充资料。

Redis 必知必会

一、redis需要掌握的知识点

  • 架构:单线程
  • 数据类型及其适用场景:5种
  • 命令的熟悉度(http://doc.redisfans.com/index.html
  • 慢查询分析
  • pipeline的使用
  • redis与lua脚本的使用
  • redis持久化:rdb && aof区别及各自特点
  • redis复制
  • redis内存怎么管理:内存使用统计,内存回收策略,内存优化等
  • redis集群

二、redis常见应用场景 && 一些注意的地方

排行榜,计数器,社交网络,消息队列等

场景1: 遍历一个set || zset || hash 匹配某个pattern的所有元素。

/**
 * 给用户发放奖励
 */
public function runSendReward()
{
    $it = null;
    $this->_redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_RETRY);
    while ($arr_keys = $this->_redis->getRealConnect()->hScan(self::KEY_INVITE_PUPIL_REWARD, $it)) {
        foreach ($arr_keys as $memberId => $time) {
            if ($this->_redis->hExists(self::KEY_INVITE_SEND_REWARD, $memberId)) {
                continue;
            }
            $validPupils = $this->getPupils($memberId);
            $reward = $this->getRewardsByRule($validPupils);
            $reward = $reward['reward'];
            $ret = $this->sendActReward($memberId, $validPupils, $reward);
            if (false == $ret) {
                echo "Faild : member_id : {$memberId} \r\n";
            }
        }
    }
}

2、增量式迭代命令:

SCAN, HSCAN, SSCAN, ZSCAN

优点:

从完整遍历开始直到完整遍历结束期间, 一直存在于数据集内的所有元素都会被完整遍历返回; 这意味着, 如果有一个元素, 它从遍历开始直到遍历结束期间都存在于被遍历的数据集当中, 那么 SCAN 命令总会在某次迭代中将这个元素返回给用户。

缺点:

1、同一个元素可能会被返回多次。 处理重复元素的工作交由应用程序负责, 比如说, 可以考虑将迭代返回的元素仅仅用于可以安全地重复执行多次的操作上。

2、如果一个元素是在迭代过程中被添加到数据集的, 又或者是在迭代过程中从数据集中被删除的, 那么这个元素可能会被返回, 也可能不会, 这是未定义的(undefined)。

注意:

1、遍历的时候处理重复出现的元素。

场景2: 注意分片 && 加锁

public function withdraw($memberId, $amount = '')
{
    if (empty($amount)) {
        $this->_err = self::$ERR_WITHDRAW_ERROR;
        return false;
    }

    //商城限制最高提现额度
    $maxWithdrawOnce = self::MAX_WITHDRAW_ONCE;
    if ($amount > $maxWithdrawOnce) {
        $leftAmount = bcsub($amount, $maxWithdrawOnce);
    } else {
        $leftAmount = 0;
    }

    $oLock = new Lock($this->_redis); //注意一定要枷锁
    $keyLock = $this->_getKeyWithdrawLock($memberId);
    if (!$oLock->acquire($keyLock, 0, 10)) {
        $this->_err = self::$ERR_WITHDRAW_DIFF_TIME;
        return false;
    }
    $params = [];
    $result = Servbox()->Mall_Order()->addOrderScene($memberId, $params);
    if (!$result) {
        $err              = Servbox()->Mall_Order()->getErrMsg();
        $debug['err_msg'] = $err;
        RegBox()->Log()->info("[actMS3] withdraw error . data: " . json_encode($debug));
        $this->_err = $err;
        return false;
    }

    //保存提现记录,扣除余额
    $keyAmount = $this->_getKeyAmount($memberId);
    $this->_redis->hSet($keyAmount, $memberId, bcmul($leftAmount, Model\Balance::UNIT_SCALE));
    $this->_setKeyExpire($keyAmount);
    $oLock->release($keyLock);

    return true;
}

//用户余额key
private function _getKeyAmount($memberId)
{
    $mod = $memberId % self::SLICE_NUM; 
    return sprintf(self::$KEY_ACT_AMOUNT, $mod);
}

场景3: 排行榜 && 队列

/**
 * @desc 添加中奖信息到列表
 * @param int $memberId
 * @param int $coinRewardNum
 */
private function _addTopList($memberId, $coinRewardNum)
{
    $key = $this->_getTopListKey();
    $statExpireDay = (int)$this->_conf['stat_expire_day'];
    if ($this->_redis->exists($key)) {
        $this->_redis->zIncrBy($key, $coinRewardNum, $memberId);
    } else {
        $this->_redis->zAdd($key, $coinRewardNum, $memberId);
    }
    $this->_redis->expire($key, self::DAY_SECONDS * $statExpireDay);
}

/**
 *  获取排行榜
 * @return array
 */
public function getTopList($num = 10)
{
    $key = $this->_getTopListKey();
    $list = $this->_redis->zRevRangeByScore($key,
        '+inf',
        '-inf',
        ['limit' => [0, $num + 5], 'withscores' => true]);

    $data = [];
    if ($list) {
        foreach ($list as $key => $val) {
            $nickname = $this->_container->Member()->getMemberInfoByField($key, 'nickname');
            if (!$nickname) continue;
            if (count($data) == $num) break;
            $temp['member_id'] = 'A'.$this->_hideMemberId($key);
            $temp['coin'] = $val;
            $temp['nickname'] = $nickname;
            $data [] = $temp;
        }
    }
    return $data;
}

三、redis常见问题 1、先读缓存还是先写数据库

2、缓存更新策略(使用场景、一致性、维护成本)

  • LRU/LFU/FIFO 算法剔除
  • 超时剔除
  • 主动更新(消息系统或其他方式通知)

3、缓存穿透解决

缓存空对象(场景:数据频繁变化实时性高; 缺点:更多的空间,短时间内有不一致的情况-可以利用消息系统主动清除) 布隆过滤器拦截(场景:数据相对固定实时性低)

4、缓存雪崩优化

  • 保证缓存层高可用
  • 降级限流
  • 提前演练

5、热点key

重建热点key

  • 通过互斥锁只允许一个线程重建
  • 快过期的时候后台脚本自动延续缓存时间

    热点key寻找

  • facebook的redis-faina
  • 通过客户端,代理,monitor命令,机器抓包来寻查找热点key

    热点key解决办法

  • 拆分复杂数据结构(如使用的hash则可以考虑对hash进行拆分)
  • 迁移热点key到性能好的机器上
  • 本地缓存(更新时通过发布订阅机制处理redis本地缓存不一致)

6、bigkey(胃寒:数据倾斜,超时足额,)

要求:

  • 字符串类型:不能超过10kb
  • 非字符串类型:元素个数不能过多(一般小于10000个)

发现及删除注意事项:

  • 利用scan类命令渐进式删除,防止出现redis阻塞。

四、扩散问题: redis为啥单线程模型会达到每秒万级别的处理能力?

  • redis 分布式锁怎么实现
  • redis 缓存怎么监控?怎么告警
  • redis中 的虚拟内存
  • master节点双机热备 && sentinel
  • Redis的回收策略
  • redis的优点有哪些
  • 高可用&&集群(一共就两种做法:1 主从+哨兵 2 redis-cluster/codis)
  • redis常见性能问题优化
  • redis是否可以完全替换memeche?
  • redis多线程的话为啥和memcache多线程相比差别不大?
  • redis协议
  • redis 的内存分配有几种方法?
  • 如何解决redis高并发客户端频繁connect timeout? 怎么解决这个问题?
  • redis踩过的坑有哪些?(从大公司的ppt里找)
  • 引起redis阻塞的有哪些命令?
  • TcpListenOverflows报警解决过程
  • Codis原理
  • Codis集群的搭建与使用(https://www.cnblogs.com/xuanzhi201111/p/4425194.html
  • redis怎么持久化? 比如存到redis里的数据,怎么存到mysql库里
  • 客户端链接超时
  • redis的tcp-backlog 出现的问题; tcp 3次握手中的 accept queue队列跟这个有什么关系?
  • 客户端连接数过大
  • redis内存陡增,客户端出现oom
  • 客户端周期性超时
  • 什么是复制缓存区?
  • redis怎么重启,关闭
  • redis 不同db之间键名可以重复吗? db有啥优缺点?
  • redis连接池怎么做?(https://www.jianshu.com/p/2639549bedc8)怎么查看redis连接池中建立的链接?(https://www.u3v3.com/ar/1346
  • redis事物的原理是啥? redis事物有像mysql的隔离级别吗? redis 支持事物回滚吗?如果一个事务中的某个命令执行出错,Redis会怎样处理呢?如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行?如果采用aof持久化,假如redis执行事务的过程中,进程被杀掉了,事物中的命令会部分成功吗?
  • WATCH命令
  • redis如何批量删除符合某个规则的key?
  • hgetall 的字段数过多会有什么影响?
  • redis 能对hash中的字段加过期时间吗?
  • redis原子操作命令有哪些?
  • redis悲观锁
  • redis 删除单个列表、集合、有序集合或哈希表类型的 key ,时间复杂度为O(M)
  • redis 分页查询怎么解决?
  • Redis 高负载下的中断优化?

发表评论

电子邮件地址不会被公开。 必填项已用*标注