最简单的套餐,设计到属性比如有初始值、剩余值、生效期的条件。 目前我是这么设计表 package,字段 init、current、expire。 但是这么做,每次扣除次数的时候都要去查表寻找剩余值、再扣除值,涉及到锁 package 表,在并发很高的情况下非常影响性能。假设套餐是流量套餐,扣流量很频繁,数据库压力很大。 想法是加缓存层,但是每次扣流量缓存层就会失效,相当于没缓存;如果更新缓存值又会涉及到缓存一致性的问题。 有没有什么简单粗暴的方法?
1
opengps 2020-02-24 23:29:37 +08:00 via Android
算法上每次只扣减当前值,只有出现小于 0 才按照过期选最近一条,填充初始值为当前值,然后执行第二次扣剩余流量。
操作最密集的是当前值,可以用缓存实时配合定时落盘持久化。如果这一条数据也要求强一致性,那么就只能堆快硬盘的机器来硬抗了 |
2
yuankui 2020-02-25 11:09:01 +08:00
有没有发现,移动公司经常会告诉你超流量了,超了多少,现在剩余流量为-100MB
可见他并不是实时扣费的,而是延迟批量累加扣费,这样对数据库没压力。 实操中可以将所有的消费信息全部推倒一个消息队列,然后一个消费程序批量消费,累加 1 分钟内的数据,然后再从总数中减去。 |
3
PiersSoCool OP 感谢楼上各位
|
4
alya 2020-02-25 16:02:57 +08:00
流量很大的话得上 spark 或者 flink 了
|
5
ElmerZhang 2020-03-24 21:29:42 +08:00 1
为什么要查剩余值?如果只是为了确保够扣的话,只要加在扣除的 where 语句里就可以了,不需要用事务去锁表:
UPDATE package SET current = current - ${VALUE} WHERE current >= ${VALUE} AND expire > NOW(); 也可以用 REDIS 来做,key 的值为 current, 过期时间为 expire,每次请求来了直接去 decr,如果返回的结果是小于 0 的,就说明原来的余量是不够扣的,把刚才扣的值 incr 回去,当作是没扣过,然后返回一个扣失败。如果过期的话,decr 的结果一定是小于 0 的,也是扣失败。 |