直播广告翻车记

3 篇文章 0 订阅
2 篇文章 0 订阅

关键词:redis slave spire 获取过期数据

周六晚会直播,有人反馈观看过广告后,再也不能触发广告了。第一次值班守护直播,就像守护女朋友一样,小心翼翼胆战心惊如履薄冰,怎奈还是翻船了。
话不多说,这锅我背了,快去找到原因解决问题吧。经过一番努力并没有头绪,经过项目组踩过坑的同事查证,redis cluster readonly=1, 导致了读取slave 过期expire数据的bug;
广告播放后,同一个用户接下来10分钟内不会再出广告。广告播放的标记存储在redis中,expire设置为600,按理10分钟后标记清除,广告系统获取不到播放标记会给用户再次下发广告。当晚有一些用户看过第一广告后,长时间无法第二次播放广告。经过查询相应用户后台日志,发现问题确实是10分钟不重复观看策略导致的。

也就是说redis存在expire过期数据仍可被读取的情况。

经过一番查证,redis曾发起Issue Improve expire consistency on slaves,以下摘录说明了这个情况(坑呀,宝宝心里苦%>_<%)

In order for Redis to ensure consistency between a master and its slaves, eviction of keys 
with an expire are managed by the master, which sends an explicit DEL to its slaves when 
the key gets actually removed.
This means that slaves are not able to directly expire keys, even if these keys are 
logically expired on the master side. So a GET that will return null in the master side, 
may return a stale value in the slave side.
为了保证redis主、从一致性,expire数据的删除由master来进行,当expire数据删除的时候,
master会向slave发送删除命令这意味着,即使这些expire数据从逻辑上应该被master端删除,
slaves也不会直接删除expire数据。在master获取这些过期数据将会获取null,
而在slave端可能仍能获取到旧的数据

凭什么认定我们是读取的从库呢?
翻出武功秘籍,对项目用到的golang redis.v5源码进行分析

func (c *ClusterClient) cmdSlotAndNode(state *clusterState, cmd Cmder) (int, *clusterNode, error) {
	if state == nil {
		node, err := c.nodes.Random()
		return 0, node, err
	}

	cmdInfo := c.cmds[cmd.name()]
	firstKey := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo))
	slot := hashtag.Slot(firstKey)

	if cmdInfo != nil && cmdInfo.ReadOnly && c.opt.ReadOnly {
		if c.opt.RouteByLatency {
			node, err := state.slotClosestNode(slot)
			return slot, node, err
		}

		node, err := state.slotSlaveNode(slot)
		return slot, node, err
	}

	node, err := state.slotMasterNode(slot)
	return slot, node, err
}

redis cluster的readonly字段配置为1的情况下,c.opt.ReadOnly条件成立,会使用slaveNode,反过来则使用masterNode,而使用slaveNode则可能引发上面的问题。

为什么我们测试的时候没有发现这个问题

话说我们也有测试过,几个人没有出现这个问题,脸黑吗(⊙o⊙)
找啊找,低版本Redis expire过期的策略在这里

How Redis expires keys
Redis keys are expired in two ways: a passive way, and an active way.
A key is passively expired simply when some client tries to access it, 
and the key is found to be timed out.
Of course this is not enough as there are expired keys that will never 
be accessed again. These keys should be expired anyway, so periodically 
Redis tests a few keys at random among keys with an expire set. All the 
keys that are already expired are deleted from the keyspace.

Specifically this is what Redis does 10 times per second:
1. Test 20 random keys from the set of keys with an associated expire.
2. Delete all the keys found expired.
3. If more than 25% of keys were expired, start again from step 1.
This is a trivial probabilistic algorithm, basically the assumption is 
that our sample is representative of the whole key space, 
and we continue to expire until the percentage of keys that are likely 
to be expired is under 25%

Redis如何过期密钥
Redis密钥以两种方式过期:被动方式和主动方式。
当某个客户端尝试访问密钥时,密钥被动过期,并且发现密钥超时。
当然这还不够,因为有过期的密钥永远不会被再次访问。这些密钥无论如何都应该过期,
所以周期性地Redis会在具有过期集的密钥中随机测试几个密钥。已经过期的
所有密钥都将从密钥空间中删除。
具体来说,这就是Redis每秒做10次的事情:
1. 从具有相关过期的密钥集中测试20个随机密钥。
2. 删除找到的所有密钥已过期。
3. 如果超过25%的密钥已过期,请从步骤1重新开始。
这是一个简单的概率算法,基本上假设我们的样本代表整个密钥空间,
我们继续到期,直到可能过期的密钥百分比低于25

从以上信息可以归纳出:

  1. 测试的时候,QPS小,Redis主动过期策略1s内可以清楚10*20=200个已过期的key,完全能处理测试好测试时候的expire key;
  2. 到了正式上线,QPS增大,整体上会保留25%已过期的expire key,这也可以解释为什么有些人可以重复看到广告,有些人不可以;
    这种带有随机性质的问题,通常定位起来都会困难一些,脸确实有点黑O__O
解决方案:
  1. 查看我司服务器redis版本是redis_version:3.0.7-m,这个问题在Redis 3.2 中得到解决,升级大法保平安(万能解决之道,搞不定了,试试升级吧)。
  2. 结合go redis.v5库特性,将readonly字段配置为0,使用masterNode节点。当然,你可以直接连master,就不会有这个问题。但要注意这种方案将会增大master的压力,酌情考虑。
  3. 除此之外也有同学提出了另外的解决方案
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值