Rumah  >  Artikel  >  pangkalan data  >  Artikel untuk bercakap tentang strategi mengehadkan semasa di Redis

Artikel untuk bercakap tentang strategi mengehadkan semasa di Redis

青灯夜游
青灯夜游ke hadapan
2021-12-30 10:16:431887semak imbas

Artikel ini akan membawa anda memahami pengehadan semasa dalam Redis, dan memperkenalkan strategi pengehadan semasa yang mudah dan pengehadan arus corong saya harap ia akan membantu semua orang.

Artikel untuk bercakap tentang strategi mengehadkan semasa di Redis

1. Pengehadan arus mudah

Prinsip asas

Cara mengatur rancangan apabila kapasiti pemprosesan sistem adalah Permintaan Luar yang terhad memberi tekanan kepada sistem. Mula-mula, mari kita lihat beberapa strategi pengehadan semasa yang mudah untuk mencegah serangan kekerasan. Sebagai contoh, jika anda ingin mengakses IP, anda hanya boleh mengaksesnya 10 kali setiap 5 saat, dan jika ia melebihi had, ia akan disekat. [Cadangan berkaitan: Tutorial video Redis]

Artikel untuk bercakap tentang strategi mengehadkan semasa di Redis

Seperti yang ditunjukkan di atas, tetingkap gelongsor biasanya digunakan untuk mengira bilangan lawatan dalam selang waktu. Gunakan zset untuk merekodkan bilangan lawatan ke IP Setiap IP disimpan melalui key score menyimpan cap masa semasa value secara unik menggunakan cap waktu atau UUID untuk melaksanakan

. Pelaksanaan kod

public class RedisLimiterTest {
    private Jedis jedis;

    public RedisLimiterTest(Jedis jedis) {
        this.jedis = jedis;
    }

    /**
     * @param ipAddress Ip地址
     * @param period    特定的时间内,单位秒
     * @param maxCount  最大允许的次数
     * @return
     */
    public boolean isIpLimit(String ipAddress, int period, int maxCount) {
        String key = String.format("ip:%s", ipAddress);
        // 毫秒时间戳
        long currentTimeMillis = System.currentTimeMillis();
        Pipeline pipe = jedis.pipelined();
        // redis事务,保证原子性
        pipe.multi();
        // 存放数据,value 和 score 都使用毫秒时间戳
        pipe.zadd(key, currentTimeMillis, "" + UUID.randomUUID());
        // 移除窗口区间所有的元素
        pipe.zremrangeByScore(key, 0, currentTimeMillis - period * 1000);
        // 获取时间窗口内的行为数量
        Response<Long> count = pipe.zcard(key);
        // 设置 zset 过期时间,避免冷用户持续占用内存,这里宽限1s
        pipe.expire(key, period + 1);
        // 提交事务
        pipe.exec();
        pipe.close();
        // 比较数量是否超标
        return count.get() > maxCount;
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        RedisLimiterTest limiter = new RedisLimiterTest(jedis);
        for (int i = 1; i <= 20; i++) {
            // 验证IP  10秒钟之内只能访问5次
            boolean isLimit = limiter.isIpLimit("222.73.55.22", 10, 5);
            System.out.println("访问第" + i + "次, 结果:" + (isLimit ? "限制访问" : "允许访问"));
        }
    }
}

Hasil pelaksanaan

访问第1次, 结果:允许访问
访问第2次, 结果:允许访问
访问第3次, 结果:允许访问
访问第4次, 结果:允许访问
访问第5次, 结果:允许访问
访问第6次, 结果:限制访问
访问第7次, 结果:限制访问
... ...

Kelemahan: Perlu merekodkan semua rekod tingkah laku dalam masa tetingkap, yang sangat besar Sebagai contoh, senario yang mengehadkan bilangan kali kepada tidak lebih daripada 1 juta kali dalam 60 saat tidak sesuai untuk had semasa sedemikian, kerana ia akan menggunakan banyak ruang storan.

2. Had arus corong

Prinsip asas

  • Kapasiti corong adalah terhad. ia akan diisi tidak lagi.
  • Jika anda melepaskan muncung, air akan mengalir ke bawah Selepas sebahagian daripadanya mengalir, anda boleh terus mengisinya dengan air.
  • Jika kadar aliran corong lebih besar daripada kadar pengisian, corong tidak akan pernah penuh.
  • Jika kadar alir corong kurang daripada kadar pengisian, maka setelah corong penuh, pengisian perlu dijeda dan tunggu corong kosong.

Kod sampel

public class FunnelLimiterTest {

    static class Funnel {
        int capacity; // 漏斗容量
        float leakingRate; // 漏嘴流水速率
        int leftQuota; // 漏斗剩余空间
        long leakingTs; // 上一次漏水时间

        public Funnel(int capacity, float leakingRate) {
            this.capacity = capacity;
            this.leakingRate = leakingRate;
            this.leftQuota = capacity;
            this.leakingTs = System.currentTimeMillis();
        }

        void makeSpace() {
            long nowTs = System.currentTimeMillis();
            long deltaTs = nowTs - leakingTs; // 距离上一次漏水过去了多久
            int deltaQuota = (int) (deltaTs * leakingRate); // 腾出的空间 = 时间*漏水速率
            if (deltaQuota < 0) { // 间隔时间太长,整数数字过大溢出
                this.leftQuota = capacity;
                this.leakingTs = nowTs;
                return;
            }
            if (deltaQuota < 1) { // 腾出空间太小 就等下次,最小单位是1
                return;
            }
            this.leftQuota += deltaQuota; // 漏斗剩余空间 = 漏斗剩余空间 + 腾出的空间
            this.leakingTs = nowTs;
            if (this.leftQuota > this.capacity) { // 剩余空间不得高于容量
                this.leftQuota = this.capacity;
            }
        }

        boolean watering(int quota) {
            makeSpace();
            if (this.leftQuota >= quota) { // 判断剩余空间是否足够
                this.leftQuota -= quota;
                return true;
            }
            return false;
        }
    }

    // 所有的漏斗
    private Map<String, Funnel> funnels = new HashMap<>();

    /**
     * @param capacity    漏斗容量
     * @param leakingRate 漏嘴流水速率 quota/s
     */
    public boolean isIpLimit(String ipAddress, int capacity, float leakingRate) {
        String key = String.format("ip:%s", ipAddress);
        Funnel funnel = funnels.get(key);
        if (funnel == null) {
            funnel = new Funnel(capacity, leakingRate);
            funnels.put(key, funnel);
        }
        return !funnel.watering(1); // 需要1个quota
    }

    public static void main(String[] args) throws Exception{
        FunnelLimiterTest limiter = new FunnelLimiterTest();
        for (int i = 1; i <= 50; i++) {
            // 每1s执行一次
            Thread.sleep(1000);
            // 漏斗容量是2 ,漏嘴流水速率是0.5每秒,
            boolean isLimit = limiter.isIpLimit("222.73.55.22", 2, (float)0.5/1000);
            System.out.println("访问第" + i + "次, 结果:" + (isLimit ? "限制访问" : "允许访问"));
        }
    }
}

Hasil pelaksanaan

访问第1次, 结果:允许访问    # 第1次,容量剩余2,执行后1
访问第2次, 结果:允许访问    # 第2次,容量剩余1,执行后0
访问第3次, 结果:允许访问    # 第3次,由于过了2s, 漏斗流水剩余1个空间,所以容量剩余1,执行后0
访问第4次, 结果:限制访问    # 第4次,过了1s, 剩余空间小于1, 容量剩余0
访问第5次, 结果:允许访问    # 第5次,由于过了2s, 漏斗流水剩余1个空间,所以容量剩余1,执行后0
访问第6次, 结果:限制访问    # 以此类推...
访问第7次, 结果:允许访问
访问第8次, 结果:限制访问
访问第9次, 结果:允许访问
访问第10次, 结果:限制访问
  • Kami perhatikanFunnel Beberapa medan objek, kami mendapati bahawa kami boleh menyimpan kandungan objek Funnel ke dalam struktur hash mengikut medan Apabila mengisi, keluarkan medan struktur hash dan lakukan operasi logik, dan kemudian isi semula nilai baharu dalam struktur hash, pengesanan kekerapan tingkah laku selesai.
  • Tetapi ada masalah, kami tidak dapat menjamin atomicity keseluruhan proses. Dapatkan nilai daripada struktur hash, kemudian kendalikannya dalam memori, dan kemudian isi semula ke dalam struktur hash Ketiga-tiga proses ini tidak boleh menjadi atom, yang bermaksud kawalan penguncian yang sesuai diperlukan. Setelah kunci dikunci, ini bermakna akan berlaku kegagalan kunci Jika kunci gagal, anda perlu memilih untuk mencuba semula atau menyerah.
  • Jika anda mencuba lagi, prestasi akan menurun. Jika anda berputus asa, ia akan menjejaskan pengalaman pengguna. Pada masa yang sama, kerumitan kod juga telah meningkat dengan banyak. Ini adalah pilihan yang sangat sukar, bagaimana kita menyelesaikan masalah ini? Redis-Cell Penyelamat ada di sini!

Redis-Cell

Redis 4.0 menyediakan modul Redis terhad semasa yang dipanggil redis-cell. Modul ini juga menggunakan algoritma corong dan menyediakan arahan mengehadkan arus atom. Modul ini hanya mempunyai satu arahan cl.throttle, dan parameter serta nilai pulangannya agak rumit. Seterusnya, mari kita lihat cara menggunakan arahan ini.

> cl.throttle key:xxx 15 30 60 1
  • 15 : 15 kapasiti Ini ialah kapasiti corong
  • 30 60 : 30 operasi / 60 saat Ini ialah kadar kebocoran air
  • 1 : memerlukan 1 kuota (parameter pilihan, nilai lalainya juga 1)
> cl.throttle laoqian:reply 15 30 60
1) (integer) 0   # 0 表示允许,1表示拒绝
2) (integer) 15  # 漏斗容量capacity
3) (integer) 14  # 漏斗剩余空间left_quota
4) (integer) -1  # 如果拒绝了,需要多长时间后再试(漏斗有空间了,单位秒)
5) (integer) 2   # 多长时间后,漏斗完全空出来(left_quota==capacity,单位秒)

Apabila melaksanakan perintah had semasa, jika ia ditolak, ia perlu dibuang atau dicuba semula. cl.throttle Arahan ini sangat bernas, malah ia mengira masa mencuba semula untuk anda. Hanya dapatkan nilai keempat tatasusunan hasil yang dikembalikan dan laksanakan sleep Jika anda tidak mahu menyekat benang, anda juga boleh menggunakan tugas berjadual tak segerak untuk mencuba semula.

Untuk lebih banyak pengetahuan berkaitan pengaturcaraan, sila lawati: Video Pengaturcaraan! !

Atas ialah kandungan terperinci Artikel untuk bercakap tentang strategi mengehadkan semasa di Redis. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:juejin.cn. Jika ada pelanggaran, sila hubungi admin@php.cn Padam