换个思路, 省下 Redis 10G 内存
知识的积累可以省钱!!!
最近产品流量剧增, 线上问题频发
Redis 的内存也是蹭蹭蹭地往上涨, 钱不知不觉在被燃烧着, 又到了要出手优化的时候了
首先找到了代码中一个很耗内存的业务场景
业务场景
每天只能给用户发送一条推送, 为了限制用户的推送, 减少打扰 , 这里使用了key-value存储用户的推送情况
挖一下: value 存储的是用户推送的次数, 也就是可以灵活地设置用户推送次数的阈值
这里用代码模拟了一下 10w 用户的场景
-
使用 Redis 命令 memory usage key 分析占用的字节数
一个 key 占用 96 byte
10w key 所占用内存大约 10 m
1亿个key 得再乘 1000, 耗费内存 10000 m 大约是 10 个 G
那有没有更好的方案呢?
当然有
当接触这个问题的时候, 脑海中自然而然地浮现出来了布隆过滤器
布隆过滤器的原理这里就不多展开讲了
首先使用布隆过滤器是肯定能节省很多内存(一个天一个地)
当然布隆过滤器也有其不可避免的缺点
就是有一定的误杀率
对于这种大批量用户的推送场景, 本身到达率也不高, 能接受一定程度的丢失
而布隆过滤器所带来的损失相对来说是可以忽略不计了的
如何在项目中使用布隆过滤器
- 引入redisson的依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.6</version>
</dependency>
-
编写代码
- tryInit 方法初始化布隆过滤器
- contains 方法用来判断数据是否存在布隆过滤器中
- put 将数据添加到布隆过滤器中
package com.example.demo import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.redisson.api.RBloomFilter import org.redisson.api.RedissonClient import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import java.util.* import java.util.concurrent.atomic.AtomicInteger @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) // 布隆过滤器方案 useBloomFilter() } private fun useBloomFilter() { val redissonClient = ContextUtils.context!!.getBean(RedissonClient::class.java) val bloomFilter = redissonClient.getBloomFilter<String>("test_bl") // 用户最大的数量 这里可以动态查表计算出来 val useMaxCount = 10_0000L // 初始化布隆过滤器 bloomFilter.tryInit(useMaxCount, 0.0003); val list = (1..100000).map { UUID.randomUUID().toString().replace("-", "") } val containsNum = AtomicInteger(0); test(list, containsNum, bloomFilter) val containsNum2 = AtomicInteger(0); test(list, containsNum2, bloomFilter) println("误杀数:${containsNum.get()}, 第一次过滤数:${containsNum2.get()}") } fun test(list: List<String>, containsNum: AtomicInteger, bloomFilter: RBloomFilter<String>) = runBlocking { list.forEachIndexed { index, it -> launch(Dispatchers.Default) { handleOne(bloomFilter, it, containsNum, index) } } } private fun handleOne( bloomFilter: RBloomFilter<String>, it: String, containsNum: AtomicInteger, index: Int ) { val contains = bloomFilter.contains(it) if (contains) { containsNum.incrementAndGet() } else { bloomFilter.add(it) } println(index) }
从结果来看, 10 w 用户 使用布隆过滤器耗费的内存在 0.39 m 左右, 相对于 key-valu e的 10 m 设计起码节省了 20 几倍内存