Redis如何實(shí)現(xiàn)刷票過(guò)濾
引言
隨著互聯(lián)網(wǎng)的不斷發(fā)展,網(wǎng)站或APP的用戶(hù)流量增加,也衍生出了一些惡意刷量等問(wèn)題,給數(shù)據(jù)分析及運(yùn)營(yíng)帶來(lái)極大的困難,出現(xiàn)的刷票問(wèn)題更是造成了嚴(yán)重的經(jīng)濟(jì)損失,所以網(wǎng)站或APP對(duì)惡意刷票進(jìn)行過(guò)濾是十分必要的。
Redis提供了很好的解決方案,其提供的內(nèi)存存儲(chǔ)和Key-Value的存儲(chǔ)結(jié)構(gòu),能夠高效地實(shí)現(xiàn)刷票過(guò)濾。
本文主要介紹如何使用SpringBoot和Redis實(shí)現(xiàn)刷票過(guò)濾,自定義同一IP每天刷票不得超過(guò)次數(shù)。
一、概述
本文主要分為以下幾個(gè)模塊:
- 1.開(kāi)發(fā)環(huán)境
- 2.使用Redis存儲(chǔ)數(shù)據(jù)
- 3.使用SpringBoot開(kāi)發(fā)應(yīng)用
- 4.實(shí)現(xiàn)同一IP每天刷票不得超過(guò)次數(shù)
二、技術(shù)選型
- SpringBoot2.2.5.RELEASE
- Spring5.2.4.RELEASE
- JDK8
- Redis
三、搭建開(kāi)發(fā)環(huán)境
- 1.安裝JDK8
- 2.安裝Redis(版本不限,最好使用穩(wěn)定版)
- 3.新建SpringBoot項(xiàng)目
使用IDEA新建SpringBoot項(xiàng)目
四、使用Redis存儲(chǔ)數(shù)據(jù)
1. 在pom.xml中加入Redis依賴(lài)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2.在application.yml中配置Redis:
spring: redis: host: 127.0.0.1 port: 6379
3. 在RedisConfig.java中配置RedisTemplate和StringRedisTemplate
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; } @Bean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){ StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(); stringRedisTemplate.setConnectionFactory(redisConnectionFactory); return stringRedisTemplate; } }
四、使用SpringBoot開(kāi)發(fā)應(yīng)用
1.在pom.xml中加入SpringBoot依賴(lài)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
2.新建Controller
@RestController @RequestMapping("/vote") public class VoteController { private final StringRedisTemplate redisTemplate; public VoteController(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } /** * 投票接口 * @param ip * @return */ @PostMapping("/submit") public String submit(@RequestParam String ip){ String key = "ip:" + ip; // 先判斷是否已經(jīng)投票,如果已經(jīng)投票,則返回 if(redisTemplate.opsForValue().get(key) != null){ return "您已經(jīng)投過(guò)票了!"; } // 獲取當(dāng)天的日期 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); String date = sdf.format(new Date()); // 拼接當(dāng)天投票的key String voteKey = "vote:" + date; // 將IP添加到Set中,記錄當(dāng)天所有投票的IP redisTemplate.opsForSet().add(voteKey,ip); // 獲取當(dāng)天已經(jīng)投票的IP數(shù)量 long voteCount = redisTemplate.opsForSet().size(voteKey); // 判斷是否超過(guò)投票限制 if(voteCount > 10){ return "您今天的投票數(shù)已經(jīng)用盡!"; } // 記錄已經(jīng)投票,設(shè)置過(guò)期時(shí)間為1天 redisTemplate.opsForValue().set(key,"已經(jīng)投票", 1, TimeUnit.DAYS); return "投票成功!"; } }
五、 實(shí)現(xiàn)同一IP每天刷票不得超過(guò)次數(shù)
1. 在VoteController的submit接口中實(shí)現(xiàn)同一IP每天刷票不得超過(guò)次數(shù)
每次投票時(shí),先通過(guò)Redis查看是否已經(jīng)投過(guò)票,如果已經(jīng)投過(guò)票,則返回“您已經(jīng)投過(guò)票了!”,否則將該IP添加到當(dāng)天投票的Set中,再通過(guò)Redis查看當(dāng)天投票的IP數(shù)量是否超過(guò)設(shè)定的閾值,如果超過(guò)則返回“您今天的投票數(shù)已經(jīng)用盡!”,否則記錄已經(jīng)投票,并將該條記錄設(shè)置為1天后過(guò)期。
上述邏輯可以采用Redis提供的Set和過(guò)期時(shí)間來(lái)完成,便捷高效。
2. 優(yōu)化方案
上述實(shí)現(xiàn)在高并發(fā)情況下可能存在問(wèn)題,比如多個(gè)用戶(hù)同時(shí)投票,從而同時(shí)訪問(wèn)Redis,產(chǎn)生并發(fā)問(wèn)題或者性能問(wèn)題,為此可以通過(guò)Redis的分布式鎖或者使用Redisson等第三方庫(kù)來(lái)解決。
下面簡(jiǎn)單介紹一下使用Redisson來(lái)實(shí)現(xiàn)分布式鎖。
- a. 在pom.xml中加入Redisson依賴(lài)
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.12.6</version> </dependency>
- b. 在application.yml中加入Redisson配置
spring: redis: host: 127.0.0.1 port: 6379 database: 0 redisson: address: redis://127.0.0.1:6379
- c. 新建RedissonConfig.java
@Configuration public class RedissonConfig { @Autowired private Environment env; @Bean(destroyMethod = "shutdown") RedissonClient redisson() throws IOException { // use "redis://" as the protocol Config config = new Config(); config.useSingleServer().setAddress(env.getProperty("redisson.address")); return Redisson.create(config); } }
- d. 在VoteController中加入Redisson分布式鎖
@RestController @RequestMapping("/vote") public class VoteController { private final StringRedisTemplate redisTemplate; private final RedissonClient redissonClient; public VoteController(StringRedisTemplate redisTemplate, RedissonClient redissonClient) { this.redisTemplate = redisTemplate; this.redissonClient = redissonClient; } /** * 投票接口 * @param ip * @return */ @PostMapping("/submit") public String submit(@RequestParam String ip){ String key = "ip:" + ip; // 使用Redisson加鎖 RLock lock = redissonClient.getLock(key); lock.lock(); try { // 先判斷是否已經(jīng)投票,如果已經(jīng)投票,則返回 if (redisTemplate.opsForValue().get(key) != null) { return "您已經(jīng)投過(guò)票了!"; } // 獲取當(dāng)天的日期 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); String date = sdf.format(new Date()); // 拼接當(dāng)天投票的key String voteKey = "vote:" + date; // 將IP添加到Set中,記錄當(dāng)天所有投票的IP redisTemplate.opsForSet().add(voteKey, ip); // 獲取當(dāng)天已經(jīng)投票的IP數(shù)量 long voteCount = redisTemplate.opsForSet().size(voteKey); // 判斷是否超過(guò)投票限制 if (voteCount > 10) { return "您今天的投票數(shù)已經(jīng)用盡!"; } // 記錄已經(jīng)投票,設(shè)置過(guò)期時(shí)間為1天 redisTemplate.opsForValue().set(key, "已經(jīng)投票", 1, TimeUnit.DAYS); return "投票成功!"; } finally { lock.unlock(); } } }
以上是使用Redisson實(shí)現(xiàn)分布式鎖的思路及代碼,從而在高并發(fā)情況下,避免了多個(gè)用戶(hù)同時(shí)對(duì)Redis進(jìn)行訪問(wèn)的并發(fā)問(wèn)題。
六、總結(jié)
本文介紹了如何使用SpringBoot和Redis實(shí)現(xiàn)刷票過(guò)濾,自定義同一IP每天刷票不得超過(guò)次數(shù)的功能。
通過(guò)使用Redis的Set和過(guò)期時(shí)間,實(shí)現(xiàn)了同一IP每天刷票不得超過(guò)次數(shù)的限制,并且代碼簡(jiǎn)單高效。
在高并發(fā)情況下,通過(guò)使用Redisson等庫(kù)實(shí)現(xiàn)分布式鎖,避免了多個(gè)用戶(hù)同時(shí)訪問(wèn)Redis的性能問(wèn)題。
在實(shí)際應(yīng)用中,除了IP限制和過(guò)期時(shí)間設(shè)置外,還可以根據(jù)具體需求,對(duì)投票做更細(xì)粒度的控制,比如設(shè)置對(duì)投票用戶(hù)的身份驗(yàn)證、對(duì)投票的時(shí)間和場(chǎng)次進(jìn)行限制等等。
最后,需要注意的是,防范惡意刷票是非常重要的,但是過(guò)度的限制可能也會(huì)造成用戶(hù)體驗(yàn)不佳,需要在保障數(shù)據(jù)安全的前提下,兼顧用戶(hù)體驗(yàn)的優(yōu)化。
這些僅為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
參考資料:
相關(guān)文章

redis快照模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

詳解Redis中的List是如何實(shí)現(xiàn)的