天天看点

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";
            }
        }
    }
}           

增量式迭代命令:

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 高负载下的中断优化?

    作者:PHP Zendo

    出处:http://blog.phpzendo.com/?p=508

51Reboot 2019 最新课程招生信息

Python 零基础入门课程

此课程为面授班和网络班,一共 15 个课时,每周上一个全天,历时4个月。附加:录播视频+笔记+答疑2019-6月份开课

Python 自动化运维进阶课程

此课程为面授班和网络班,一共 15 个课时,每周上一个全天,历时4个月。附加:录播视频+笔记+答疑2019-4月份开课

继续阅读