换个思路, 省下 Redis 10G 内存

188 阅读2分钟

换个思路, 省下 Redis 10G 内存

知识的积累可以省钱!!!

最近产品流量剧增, 线上问题频发

Redis 的内存也是蹭蹭蹭地往上涨, 钱不知不觉在被燃烧着, 又到了要出手优化的时候了

首先找到了代码中一个很耗内存的业务场景

业务场景

每天只能给用户发送一条推送, 为了限制用户的推送, 减少打扰 , 这里使用了key-value存储用户的推送情况

挖一下: value 存储的是用户推送的次数, 也就是可以灵活地设置用户推送次数的阈值

这里用代码模拟了一下 10w 用户的场景 image-20220912221541128

  • 使用 Redis 命令 memory usage key 分析占用的字节数

    image-20220912223636278

    image-20220912224256653

    一个 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>
  • 编写代码

    1. tryInit 方法初始化布隆过滤器
    2. contains 方法用来判断数据是否存在布隆过滤器中
    3. 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 DemoApplicationfun 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)
    }
    

image-20220912230808292

image-20220912230917373

image-20220912230827939

从结果来看, 10 w 用户 使用布隆过滤器耗费的内存在 0.39 m 左右, 相对于 key-valu e的 10 m 设计起码节省了 20 几倍内存