


Cara menggunakan SpringBoot + Redis untuk melaksanakan pengehadan semasa antara muka
Konfigurasi
Mula-mula kami mencipta projek Spring Boot, memperkenalkan kebergantungan Web dan Redis, dan menganggap bahawa pengehadan semasa antara muka biasanya ditanda melalui anotasi dan anotasi dihuraikan melalui AOP, jadi kami juga memerlukan Termasuk kebergantungan AOP , kebergantungan akhir adalah seperti berikut:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
Kemudian sediakan contoh Redis terlebih dahulu Selepas projek kami dikonfigurasikan, kami boleh mengkonfigurasi secara langsung maklumat asas Redis, seperti berikut:
spring.redis.host=localhost spring.redis.port=6379 spring.redis.password=123
Semasa. mengehadkan anotasi
Seterusnya kami mencipta anotasi mengehadkan semasa Kami membahagikan pengehadan semasa kepada dua situasi:
Penghadan arus global untuk antara muka semasa , contohnya, antara muka. boleh diakses 100 kali dalam 1 minit.
Had kadar untuk alamat IP tertentu, contohnya, alamat IP boleh diakses 100 kali dalam 1 minit.
Untuk kedua-dua situasi ini, kami mencipta kelas penghitungan:
public enum LimitType { /** * 默认策略全局限流 */ DEFAULT, /** * 根据请求者IP进行限流 */ IP }
Seterusnya kami mencipta anotasi mengehadkan semasa:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RateLimiter { /** * 限流key */ String key() default "rate_limit:"; /** * 限流时间,单位秒 */ int time() default 60; /** * 限流次数 */ int count() default 100; /** * 限流类型 */ LimitType limitType() default LimitType.DEFAULT; }
Pertama A parameter semasa- kunci had. Ini hanyalah awalan Pada masa hadapan, kunci lengkap akan menjadi awalan ini ditambah laluan lengkap kaedah antara muka, yang bersama-sama membentuk kunci had semasa.
Tiga parameter lain mudah difahami, jadi saya tidak akan memberitahu lebih lanjut.
Baiklah, jika antara muka perlu dihadkan pada masa hadapan, cuma tambahkan anotasi @RateLimiter
pada antara muka itu, kemudian konfigurasikan parameter yang berkaitan.
Customized RedisTemplate
Dalam Spring Boot, kami sebenarnya lebih terbiasa menggunakan Spring Data Redis untuk mengendalikan Redis, tetapi RedisTemplate lalai mempunyai masalah kecil, iaitu JdkSerializationRedisSerializer digunakan untuk bersiri. Saya tidak tahu Kawan, pernahkah anda perasan bahawa kunci dan nilai yang disimpan ke Redis menggunakan alat bersiri ini entah bagaimana akan mempunyai lebih banyak awalan, yang mungkin menyebabkan ralat apabila anda membacanya dengan arahan.
Contohnya, semasa menyimpan, kuncinya ialah nama dan nilainya adalah ujian, tetapi apabila anda beroperasi pada baris arahan, get name
tidak boleh mendapatkan data yang anda mahukan. Sebabnya ia disimpan ke redis sebelum nama. Terdapat beberapa aksara lagi, jadi anda hanya boleh terus menggunakan RedisTemplate untuk membacanya.
Apabila kami menggunakan Redis untuk pengehadan semasa, kami akan menggunakan skrip Lua Apabila menggunakan skrip Lua, situasi yang dinyatakan di atas akan berlaku, jadi kami perlu mengubah suai skema bersiri RedisTemplate.
Sesetengah rakan mungkin bertanya mengapa tidak menggunakan StringRedisTemplate? StringRedisTemplate tidak mempunyai masalah yang dinyatakan di atas, tetapi jenis data yang boleh disimpannya tidak cukup kaya, jadi ia tidak dipertimbangkan di sini.
Ubah suai skema siri RedisTemplate, kodnya adalah seperti berikut:
@Configuration public class RedisConfig { @Bean public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); // 使用Jackson2JsonRedisSerialize 替换默认序列化(默认采用的是JDK序列化) Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); redisTemplate.setKeySerializer(jackson2JsonRedisSerializer); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); return redisTemplate; } }
Malah, tiada apa yang boleh dikatakan tentang perkara ini Kami menggunakan kaedah siri jackson lalai dalam Spring Boot untuk menyelesaikan kunci dan nilai.
Skrip Lua
Sebenarnya, saya menyebut perkara ini dalam video vhr sebelumnya. Kami boleh menggunakan skrip Lua untuk melaksanakan beberapa operasi atom dalam Redis Untuk memanggil skrip Lua, kami mempunyai Dua idea yang berbeza.
Tentukan skrip Lua pada pelayan Redis, dan kemudian hitung nilai cincang Dalam kod Java, gunakan nilai cincang ini untuk mengunci skrip Lua untuk dilaksanakan.
Tentukan skrip Lua secara langsung dalam kod Java, dan kemudian hantar ke pelayan Redis untuk dilaksanakan.
Spring Data Redis juga menyediakan antara muka untuk mengendalikan skrip Lua, yang agak mudah, jadi kami akan menggunakan pilihan kedua di sini.
Kami mencipta folder lua baharu dalam direktori sumber khusus untuk menyimpan skrip lua Kandungan skrip adalah seperti berikut:
local key = KEYS[1] local count = tonumber(ARGV[1]) local time = tonumber(ARGV[2]) local current = redis.call('get', key) if current and tonumber(current) > count then return tonumber(current) end current = redis.call('incr', key) if tonumber(current) == 1 then redis.call('expire', key, time) end return tonumber(current)
Skrip ini sebenarnya tidak sukar ia digunakan untuk sekilas pandang. KEYS dan ARGV adalah kedua-dua parameter yang dihantar semasa membuat panggilan untuk menukar redis.panggilan adalah untuk melaksanakan arahan redis khusus adalah seperti berikut:
. Pertama, dapatkan kunci masuk dan kiraan had semasa dan masa.
Dapatkan nilai yang sepadan dengan kunci ini melalui get. Nilai ini ialah bilangan kali antara muka ini boleh diakses dalam tetingkap masa semasa.
Sekiranya lawatan pertama, keputusan yang diperolehi pada masa ini adalah nil Jika tidak, keputusan yang diperolehi adalah nombor, jadi langkah seterusnya adalah menilai sama ada keputusan yang diperolehi Nombor, dan nombor ini lebih besar daripada kiraan, ini bermakna had trafik telah melebihi, maka hasil pertanyaan boleh dikembalikan secara langsung.
Jika hasil yang diperolehi adalah sifar, ini bermakna ia adalah akses pertama Pada masa ini, kunci semasa akan dinaikkan sebanyak 1, dan kemudian masa tamat tempoh akan ditetapkan.
Akhir sekali, hanya kembalikan nilai yang dinaikkan sebanyak 1.
Sebenarnya skrip Lua ini mudah difahami.
Seterusnya kami memuatkan skrip Lua ini dalam Bean, seperti berikut:
@Bean public DefaultRedisScript<Long> limitScript() { DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua"))); redisScript.setResultType(Long.class); return redisScript; }
Baiklah, skrip Lua kami kini sedia.
Analisis anotasi
Seterusnya kita perlu menyesuaikan aspek untuk menghuraikan anotasi ini Mari kita lihat definisi aspek:
@Aspect @Component public class RateLimiterAspect { private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); @Autowired private RedisTemplate<Object, Object> redisTemplate; @Autowired private RedisScript<Long> limitScript; @Before("@annotation(rateLimiter)") public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable { String key = rateLimiter.key(); int time = rateLimiter.time(); int count = rateLimiter.count(); String combineKey = getCombineKey(rateLimiter, point); List<Object> keys = Collections.singletonList(combineKey); try { Long number = redisTemplate.execute(limitScript, keys, count, time); if (number==null || number.intValue() > count) { throw new ServiceException("访问过于频繁,请稍候再试"); } log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key); } catch (ServiceException e) { throw e; } catch (Exception e) { throw new RuntimeException("服务器限流异常,请稍候再试"); } } public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) { StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); if (rateLimiter.limitType() == LimitType.IP) { stringBuffer.append(IpUtils.getIpAddr(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest())).append("-"); } MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); Class<?> targetClass = method.getDeclaringClass(); stringBuffer.append(targetClass.getName()).append("-").append(method.getName()); return stringBuffer.toString(); } }
Aspek ini adalah untuk memintas semua. tambahan Kaedah @RateLimiter
anotasi ditambah dan anotasi diproses dalam pra-pemberitahuan.
首先获取到注解中的 key、time 以及 count 三个参数。
获取一个组合的 key,所谓的组合的 key,就是在注解的 key 属性基础上,再加上方法的完整路径,如果是 IP 模式的话,就再加上 IP 地址。以 IP 模式为例,最终生成的 key 类似这样:
rate_limit:127.0.0.1-org.javaboy.ratelimiter.controller.HelloController-hello
(如果不是 IP 模式,那么生成的 key 中就不包含 IP 地址)。将生成的 key 放到集合中。
通过 redisTemplate.execute 方法取执行一个 Lua 脚本,第一个参数是脚本所封装的对象,第二个参数是 key,对应了脚本中的 KEYS,后面是可变长度的参数,对应了脚本中的 ARGV。
判断 Lua 脚本执行后的结果是否超过 count,若超过则视为过载,抛出异常处理即可。
接口测试
接下来我们就进行接口的一个简单测试,如下:
@RestController public class HelloController { @GetMapping("/hello") @RateLimiter(time = 5,count = 3,limitType = LimitType.IP) public String hello() { return "hello>>>"+new Date(); } }
每一个 IP 地址,在 5 秒内只能访问 3 次。
这个自己手动刷新浏览器都能测试出来。
全局异常处理
由于过载的时候是抛异常出来,所以我们还需要一个全局异常处理器,如下:
@RestControllerAdvice public class GlobalException { @ExceptionHandler(ServiceException.class) public Map<String,Object> serviceException(ServiceException e) { HashMap<String, Object> map = new HashMap<>(); map.put("status", 500); map.put("message", e.getMessage()); return map; } }
我将这句话重写成如下: 这个 demo 很小,所以我没有定义实体类,而是直接使用 Map 来返回 JSON。 最后我们看看过载时的测试效果:
Atas ialah kandungan terperinci Cara menggunakan SpringBoot + Redis untuk melaksanakan pengehadan semasa antara muka. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

AI Hentai Generator
Menjana ai hentai secara percuma.

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas



Mod Redis cluster menyebarkan contoh Redis ke pelbagai pelayan melalui sharding, meningkatkan skalabilitas dan ketersediaan. Langkah -langkah pembinaan adalah seperti berikut: Buat contoh Redis ganjil dengan pelabuhan yang berbeza; Buat 3 contoh sentinel, memantau contoh redis dan failover; Konfigurasi fail konfigurasi sentinel, tambahkan pemantauan maklumat contoh dan tetapan failover; Konfigurasi fail konfigurasi contoh Redis, aktifkan mod kluster dan tentukan laluan fail maklumat kluster; Buat fail nodes.conf, yang mengandungi maklumat setiap contoh Redis; Mulakan kluster, laksanakan perintah Buat untuk membuat kluster dan tentukan bilangan replika; Log masuk ke kluster untuk melaksanakan perintah maklumat kluster untuk mengesahkan status kluster; buat

Menggunakan Arahan Redis memerlukan langkah -langkah berikut: Buka klien Redis. Masukkan arahan (nilai kunci kata kerja). Menyediakan parameter yang diperlukan (berbeza dari arahan ke arahan). Tekan Enter untuk melaksanakan arahan. Redis mengembalikan tindak balas yang menunjukkan hasil operasi (biasanya OK atau -r).

Cara terbaik untuk memahami kod sumber REDIS adalah dengan langkah demi langkah: Dapatkan akrab dengan asas -asas Redis. Pilih modul atau fungsi tertentu sebagai titik permulaan. Mulakan dengan titik masuk modul atau fungsi dan lihat baris kod mengikut baris. Lihat kod melalui rantaian panggilan fungsi. Berhati -hati dengan struktur data asas yang digunakan oleh REDIS. Kenal pasti algoritma yang digunakan oleh Redis.

Redis menggunakan satu seni bina berulir untuk memberikan prestasi tinggi, kesederhanaan, dan konsistensi. Ia menggunakan I/O multiplexing, gelung acara, I/O yang tidak menyekat, dan memori bersama untuk meningkatkan keserasian, tetapi dengan batasan batasan konkurensi, satu titik kegagalan, dan tidak sesuai untuk beban kerja yang berintensifkan.

Cara Mengosongkan Data Redis: Gunakan perintah Flushall untuk membersihkan semua nilai utama. Gunakan perintah flushdb untuk membersihkan nilai utama pangkalan data yang dipilih sekarang. Gunakan Pilih untuk menukar pangkalan data, dan kemudian gunakan FlushDB untuk membersihkan pelbagai pangkalan data. Gunakan perintah DEL untuk memadam kunci tertentu. Gunakan alat REDIS-CLI untuk membersihkan data.

Untuk melihat semua kunci di Redis, terdapat tiga cara: Gunakan perintah kunci untuk mengembalikan semua kunci yang sepadan dengan corak yang ditentukan; Gunakan perintah imbasan untuk melangkah ke atas kunci dan kembalikan satu set kunci; Gunakan arahan maklumat untuk mendapatkan jumlah kunci.

Langkah -langkah untuk memulakan pelayan Redis termasuk: Pasang Redis mengikut sistem operasi. Mulakan perkhidmatan Redis melalui Redis-server (Linux/macOS) atau redis-server.exe (Windows). Gunakan redis-cli ping (linux/macOS) atau redis-cli.exe ping (windows) perintah untuk memeriksa status perkhidmatan. Gunakan klien Redis, seperti redis-cli, python, atau node.js untuk mengakses pelayan.

Untuk membaca giliran dari Redis, anda perlu mendapatkan nama giliran, membaca unsur -unsur menggunakan arahan LPOP, dan memproses barisan kosong. Langkah-langkah khusus adalah seperti berikut: Dapatkan nama giliran: Namakannya dengan awalan "giliran:" seperti "giliran: my-queue". Gunakan arahan LPOP: Keluarkan elemen dari kepala barisan dan kembalikan nilainya, seperti LPOP Queue: My-Queue. Memproses Baris kosong: Jika barisan kosong, LPOP mengembalikan nihil, dan anda boleh menyemak sama ada barisan wujud sebelum membaca elemen.
