在设计UA池的时候,它的数据结构和环形队列十分类似: 上图中,假设不同颜色的UA是完全不同的UA,它们通过洗牌算法打散放进去环形队列中,实际上每次取出一个UA之后,只需要把游标cursor前进或者后退一格即可(甚至可以把游标设置到队列中的任意元素)。最终的实现就是:需要通过中间件实现分布式队列(只是队列,不是消息队列)。
<dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.2.1.RELEASE</version> </dependency>编写RPOP + LPUSH的Lua脚本,Lua脚本名字暂称为L_RPOP_LPUSH.lua,放在resources/scripts/lua目录下:
local key = KEYS[1] local value = redis.call('RPOP', key) redis.call('LPUSH', key, value) return value这个脚本十分简单,但是已经实现了循环队列的功能。剩下来的测试代码如下:
public class UaPoolTest { private static RedisCommands<String, String> COMMANDS; private static AtomicReference<String> LUA_SHA = new AtomicReference<>(); private static final String KEY = "UA_POOL"; @BeforeClass public static void beforeClass() throws Exception { // 初始化Redis客户端 RedisURI uri = RedisURI.builder().withHost("localhost").withPort(6379).build(); RedisClient redisClient = RedisClient.create(uri); StatefulRedisConnection<String, String> connect = redisClient.connect(); COMMANDS = connect.sync(); // 模拟构建UA池的原始数据,假设有10个UA,分别是UA-0 ... UA-9 List<String> uaList = Lists.newArrayList(); IntStream.range(0, 10).forEach(e -> uaList.add(String.format("UA-%d", e))); // 洗牌 Collections.shuffle(uaList); // 加载Lua脚本 ClassPathResource resource = new ClassPathResource("/scripts/lua/L_RPOP_LPUSH.lua"); String content = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8); String sha = COMMANDS.scriptLoad(content); LUA_SHA.compareAndSet(null, sha); // Redis队列中写入UA数据,数据量多的时候可以考虑分批写入防止长时间阻塞Redis服务 COMMANDS.lpush(KEY, uaList.toArray(new String[0])); } @AfterClass public static void afterClass() throws Exception { COMMANDS.del(KEY); } @Test public void testUaPool() { IntStream.range(1, 21).forEach(e -> { String result = COMMANDS.evalsha(LUA_SHA.get(), ScriptOutputType.VALUE, KEY); System.out.println(String.format("第%d次获取到的UA是:%s", e, result)); }); } }
第1次获取到的UA是:UA-0 第2次获取到的UA是:UA-8 第3次获取到的UA是:UA-2 第4次获取到的UA是:UA-4 第5次获取到的UA是:UA-7 第6次获取到的UA是:UA-5 第7次获取到的UA是:UA-1 第8次获取到的UA是:UA-3 第9次获取到的UA是:UA-6 第10次获取到的UA是:UA-9 第11次获取到的UA是:UA-0 第12次获取到的UA是:UA-8 第13次获取到的UA是:UA-2 第14次获取到的UA是:UA-4 第15次获取到的UA是:UA-7 第16次获取到的UA是:UA-5 第17次获取到的UA是:UA-1 第18次获取到的UA是:UA-3 第19次获取到的UA是:UA-6 第20次获取到的UA是:UA-9可见洗牌算法的效果不差,数据相对分散。