From f95bedb2cba315d611c4a0cffc5cf0243d57157e Mon Sep 17 00:00:00 2001 From: Dominik Date: Tue, 11 Jul 2023 21:37:33 +0200 Subject: [PATCH] Add support for redis clusters --- .../api/DefaultOnlinePlayersProvider.java | 208 ++++++++---------- .../party/api/DefaultPartyProvider.java | 106 ++++----- .../dominik48n/party/redis/RedisManager.java | 56 +++-- 3 files changed, 173 insertions(+), 197 deletions(-) diff --git a/common/src/main/java/com/github/dominik48n/party/api/DefaultOnlinePlayersProvider.java b/common/src/main/java/com/github/dominik48n/party/api/DefaultOnlinePlayersProvider.java index 5073ce9..6abff6c 100644 --- a/common/src/main/java/com/github/dominik48n/party/api/DefaultOnlinePlayersProvider.java +++ b/common/src/main/java/com/github/dominik48n/party/api/DefaultOnlinePlayersProvider.java @@ -35,7 +35,7 @@ import java.util.UUID; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import redis.clients.jedis.Jedis; +import redis.clients.jedis.UnifiedJedis; public class DefaultOnlinePlayersProvider implements OnlinePlayerProvider { @@ -51,44 +51,38 @@ public DefaultOnlinePlayersProvider(final @NotNull RedisManager redisManager, fi @Override public @NotNull Optional get(final @NotNull String username) throws JsonProcessingException { - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - final Set keys = jedis.keys("party_player:*"); - for (final String key : keys) { - final String json = jedis.get(key); - if (json == null) continue; - - final PartyPlayer player = Document.MAPPER.readValue(json, PartyPlayer.class); - if (player.name().equalsIgnoreCase(username)) return Optional.of(new NetworkUser<>(player, this.userManager)); - } + final Set keys = this.redisManager.jedis().keys("party_player:*"); + for (final String key : keys) { + final String json = this.redisManager.jedis().get(key); + if (json == null) continue; + + final PartyPlayer player = Document.MAPPER.readValue(json, PartyPlayer.class); + if (player.name().equalsIgnoreCase(username)) return Optional.of(new NetworkUser<>(player, this.userManager)); } return Optional.empty(); } @Override public @NotNull Optional get(final @NotNull UUID uniqueId) throws JsonProcessingException { - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - final String json = jedis.get("party_player:" + uniqueId); - if (json == null) return Optional.empty(); + final String json = this.redisManager.jedis().get("party_player:" + uniqueId); + if (json == null) return Optional.empty(); - final PartyPlayer player = Document.MAPPER.readValue(json, PartyPlayer.class); - return Optional.of(new NetworkUser<>(player, this.userManager)); - } + final PartyPlayer player = Document.MAPPER.readValue(json, PartyPlayer.class); + return Optional.of(new NetworkUser<>(player, this.userManager)); } @Override public @NotNull Map get(final @NotNull Collection uniqueIds) throws JsonProcessingException { final Map players = Maps.newHashMap(); - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - final String[] keys = uniqueIds.stream() - .map(uuid -> "party_player:" + uuid.toString()) - .toArray(String[]::new); + final String[] keys = uniqueIds.stream() + .map(uuid -> "party_player:" + uuid.toString()) + .toArray(String[]::new); - for (final String json : jedis.mget(keys)) { - if (json == null) continue; + for (final String json : this.redisManager.jedis().mget(keys)) { + if (json == null) continue; - final PartyPlayer player = Document.MAPPER.readValue(json, PartyPlayer.class); - players.put(player.uniqueId(), player); - } + final PartyPlayer player = Document.MAPPER.readValue(json, PartyPlayer.class); + players.put(player.uniqueId(), player); } return players; } @@ -96,110 +90,104 @@ public DefaultOnlinePlayersProvider(final @NotNull RedisManager redisManager, fi @Override public @NotNull List all() throws JsonProcessingException { final List partyPlayers = Lists.newArrayList(); - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - final Set keys = jedis.keys("party_player:*"); - for (final String key : keys) { - final String json = jedis.get(key); - if (json == null) continue; - - final PartyPlayer player = Document.MAPPER.readValue(json, PartyPlayer.class); - partyPlayers.add(new NetworkUser<>(player, this.userManager)); - } + final Set keys = this.redisManager.jedis().keys("party_player:*"); + for (final String key : keys) { + final String json = this.redisManager.jedis().get(key); + if (json == null) continue; + + final PartyPlayer player = Document.MAPPER.readValue(json, PartyPlayer.class); + partyPlayers.add(new NetworkUser<>(player, this.userManager)); } return partyPlayers; } @Override public void login(final @NotNull PartyPlayer player) throws JsonProcessingException { - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - jedis.set("party_player:" + player.uniqueId(), Document.MAPPER.writeValueAsString(player)); - } + this.redisManager.jedis().set("party_player:" + player.uniqueId(), Document.MAPPER.writeValueAsString(player)); } @Override public void logout(final @NotNull UUID uniqueId) { final String playerKey = "party_player:" + uniqueId; - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - // Check if player is logged in - final String json = jedis.get(playerKey); - if (json == null) return; - - // Deserialize player object - final PartyPlayer player; - try { - player = Document.MAPPER.readValue(json, PartyPlayer.class); - } catch (final JsonProcessingException e) { - jedis.del(playerKey); - return; - } - - // Check if player is in a party - if (player.partyId().isPresent()) try { - PartyAPI.get().getParty(player.partyId().get()).ifPresent(party -> { - if (party.allMembers().size() <= 1) { - // Last member leaving the party, delete the party - PartyAPI.get().deleteParty(party.id()); - } else { - // Player is in a party with multiple members - if (party.isLeader(player.uniqueId())) { - // Player is the leader of the party, transfer leadership to another member - final Optional newLeader = party.members().stream().findAny().map(uuid -> { - try { - return PartyAPI.get().onlinePlayerProvider().get(uuid).orElse(null); - } catch (final JsonProcessingException e) { - return null; - } - }); - - if (newLeader.isPresent()) { - try { - // Change party leader - PartyAPI.get().changePartyLeader( - party.id(), - player.uniqueId(), - newLeader.get().uniqueId(), - newLeader.get().memberLimit() - ); - - final List playersToMessage = this.databaseAdapter != null ? - this.databaseAdapter.getPlayersWithEnabledSetting(party.members(), DatabaseSettingsType.NOTIFICATIONS) : - party.members(); - PartyAPI.get().sendMessageToPlayers(playersToMessage, "party.left", player.name()); - - PartyAPI.get().sendMessageToMembers(party, "party.new_leader", newLeader.get().name()); - } catch (final JsonProcessingException ignored) { - } - } else { - // Party has no more members, delete the party - PartyAPI.get().deleteParty(party.id()); + // Check if player is logged in + final String json = this.redisManager.jedis().get(playerKey); + if (json == null) return; + + // Deserialize player object + final PartyPlayer player; + try { + player = Document.MAPPER.readValue(json, PartyPlayer.class); + } catch (final JsonProcessingException e) { + this.redisManager.jedis().del(playerKey); + return; + } + + // Check if player is in a party + if (player.partyId().isPresent()) try { + PartyAPI.get().getParty(player.partyId().get()).ifPresent(party -> { + if (party.allMembers().size() <= 1) { + // Last member leaving the party, delete the party + PartyAPI.get().deleteParty(party.id()); + } else { + // Player is in a party with multiple members + if (party.isLeader(player.uniqueId())) { + // Player is the leader of the party, transfer leadership to another member + final Optional newLeader = party.members().stream().findAny().map(uuid -> { + try { + return PartyAPI.get().onlinePlayerProvider().get(uuid).orElse(null); + } catch (final JsonProcessingException e) { + return null; + } + }); + + if (newLeader.isPresent()) { + try { + // Change party leader + PartyAPI.get().changePartyLeader( + party.id(), + player.uniqueId(), + newLeader.get().uniqueId(), + newLeader.get().memberLimit() + ); + + final List playersToMessage = this.databaseAdapter != null ? + this.databaseAdapter.getPlayersWithEnabledSetting(party.members(), DatabaseSettingsType.NOTIFICATIONS) : + party.members(); + PartyAPI.get().sendMessageToPlayers(playersToMessage, "party.left", player.name()); + + PartyAPI.get().sendMessageToMembers(party, "party.new_leader", newLeader.get().name()); + } catch (final JsonProcessingException ignored) { } } else { - // Player is not the leader of the party, remove player from party - party.members().remove(player.uniqueId()); - - final List playersToMessage = this.databaseAdapter != null ? - this.databaseAdapter.getPlayersWithEnabledSetting(party.allMembers(), DatabaseSettingsType.NOTIFICATIONS) : - party.allMembers(); - PartyAPI.get().sendMessageToPlayers(playersToMessage, "party.left", player.name()); + // Party has no more members, delete the party + PartyAPI.get().deleteParty(party.id()); } + } else { + // Player is not the leader of the party, remove player from party + party.members().remove(player.uniqueId()); - // Save updated party to Redis - try { - jedis.set("party:" + party.id(), Document.MAPPER.writeValueAsString(party)); - } catch (final JsonProcessingException ignored) { - } + final List playersToMessage = this.databaseAdapter != null ? + this.databaseAdapter.getPlayersWithEnabledSetting(party.allMembers(), DatabaseSettingsType.NOTIFICATIONS) : + party.allMembers(); + PartyAPI.get().sendMessageToPlayers(playersToMessage, "party.left", player.name()); } - }); - } catch (final JsonProcessingException ignored) { - } - // Delete player object from Redis - jedis.del(playerKey); + // Save updated party to Redis + try { + this.redisManager.jedis().set("party:" + party.id(), Document.MAPPER.writeValueAsString(party)); + } catch (final JsonProcessingException ignored) { + } + } + }); + } catch (final JsonProcessingException ignored) { } + + // Delete player object from Redis + this.redisManager.jedis().del(playerKey); } - boolean updatePartyId(final @NotNull Jedis jedis, final @NotNull UUID uniqueId, final @Nullable UUID partyId) throws JsonProcessingException { + boolean updatePartyId(final @NotNull UnifiedJedis jedis, final @NotNull UUID uniqueId, final @Nullable UUID partyId) throws JsonProcessingException { final String key = "party_player:" + uniqueId; final String json = jedis.get(key); if (json == null) return false; @@ -214,9 +202,7 @@ boolean updatePartyId(final @NotNull Jedis jedis, final @NotNull UUID uniqueId, @Override public boolean updatePartyId(final @NotNull UUID uniqueId, final @Nullable UUID partyId) throws JsonProcessingException { - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - return this.updatePartyId(jedis, uniqueId, partyId); - } + return this.updatePartyId(this.redisManager.jedis(), uniqueId, partyId); } void databaseAdapter(final @NotNull DatabaseAdapter databaseAdapter) { diff --git a/common/src/main/java/com/github/dominik48n/party/api/DefaultPartyProvider.java b/common/src/main/java/com/github/dominik48n/party/api/DefaultPartyProvider.java index 60ead77..d21b225 100644 --- a/common/src/main/java/com/github/dominik48n/party/api/DefaultPartyProvider.java +++ b/common/src/main/java/com/github/dominik48n/party/api/DefaultPartyProvider.java @@ -36,7 +36,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import org.jetbrains.annotations.NotNull; -import redis.clients.jedis.Jedis; +import redis.clients.jedis.UnifiedJedis; public class DefaultPartyProvider implements PartyProvider { @@ -69,17 +69,15 @@ public DefaultPartyProvider( @Override public void addPlayerToParty(final @NotNull UUID partyId, final @NotNull UUID player) throws JsonProcessingException { - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - final String partyKey = "party:" + partyId; - final String partyJson = jedis.get(partyKey); - if (partyJson != null) { - final Party party = Document.MAPPER.readValue(partyJson, Party.class); - party.members().add(player); - jedis.set(partyKey, Document.MAPPER.writeValueAsString(party)); - } - - this.onlinePlayerProvider.updatePartyId(jedis, player, partyId); + final String partyKey = "party:" + partyId; + final String partyJson = this.redisManager.jedis().get(partyKey); + if (partyJson != null) { + final Party party = Document.MAPPER.readValue(partyJson, Party.class); + party.members().add(player); + this.redisManager.jedis().set(partyKey, Document.MAPPER.writeValueAsString(party)); } + + this.onlinePlayerProvider.updatePartyId(this.redisManager.jedis(), player, partyId); } @Override @@ -88,18 +86,16 @@ public void removePlayerFromParty( final @NotNull UUID player, final @NotNull String username ) throws JsonProcessingException { - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - final String partyKey = "party:" + partyId; - final String partyJson = jedis.get(partyKey); - if (partyJson != null) { - final Party party = Document.MAPPER.readValue(partyJson, Party.class); - party.members().remove(player); - jedis.set(partyKey, Document.MAPPER.writeValueAsString(party)); - } - - this.clearPartyRequest(jedis, username); - this.onlinePlayerProvider.updatePartyId(jedis, player, null); + final String partyKey = "party:" + partyId; + final String partyJson = this.redisManager.jedis().get(partyKey); + if (partyJson != null) { + final Party party = Document.MAPPER.readValue(partyJson, Party.class); + party.members().remove(player); + this.redisManager.jedis().set(partyKey, Document.MAPPER.writeValueAsString(party)); } + + this.clearPartyRequest(this.redisManager.jedis(), username); + this.onlinePlayerProvider.updatePartyId(this.redisManager.jedis(), player, null); } @Override @@ -113,41 +109,35 @@ public void changePartyLeader( maxMembers >= 0 && maxMembers <= Constants.MAXIMUM_MEMBER_LIMIT, "maxMembers cannot be negative!" ); - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - final String json = jedis.get("party:" + partyId); - if (json == null) return; // Party isn't exist. - - final Party party = Document.MAPPER.readValue(json, Party.class); - party.members().remove(newLeader); - party.members().add(oldLeader); - jedis.set("party:" + partyId, Document.MAPPER.writeValueAsString(new Party(partyId, newLeader, party.members(), maxMembers))); - } + final String json = this.redisManager.jedis().get("party:" + partyId); + if (json == null) return; // Party isn't exist. + + final Party party = Document.MAPPER.readValue(json, Party.class); + party.members().remove(newLeader); + party.members().add(oldLeader); + this.redisManager.jedis().set("party:" + partyId, Document.MAPPER.writeValueAsString(new Party(partyId, newLeader, party.members(), maxMembers))); } @Override public @NotNull Optional getParty(final @NotNull UUID id) throws JsonProcessingException { - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - final String json = jedis.get("party:" + id); - if (json == null) return Optional.empty(); + final String json = this.redisManager.jedis().get("party:" + id); + if (json == null) return Optional.empty(); - final Party party = Document.MAPPER.readValue(json, Party.class); - return Optional.of(party); - } + final Party party = Document.MAPPER.readValue(json, Party.class); + return Optional.of(party); } @Override public @NotNull Party createParty(final @NotNull UUID leader, final int maxMembers) throws JsonProcessingException, IllegalArgumentException { Preconditions.checkArgument(maxMembers >= 0, "maxMembers cannot be negative!"); - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - UUID partyId; - do { - partyId = UUID.randomUUID(); - } while (jedis.exists("party:" + partyId)); - - final Party party = new Party(partyId, leader, Lists.newArrayList(), maxMembers); - jedis.set("party:" + partyId, Document.MAPPER.writeValueAsString(party)); - return party; - } + UUID partyId; + do { + partyId = UUID.randomUUID(); + } while (this.redisManager.jedis().exists("party:" + partyId)); + + final Party party = new Party(partyId, leader, Lists.newArrayList(), maxMembers); + this.redisManager.jedis().set("party:" + partyId, Document.MAPPER.writeValueAsString(party)); + return party; } @Override @@ -172,42 +162,32 @@ public void connectPartyToServer(final @NotNull Party party, final @NotNull Stri @Override public void deleteParty(final @NotNull UUID id) { - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - jedis.del("party:" + id); - } + this.redisManager.jedis().del("party:" + id); } @Override public void removePartyRequest(final @NotNull String source, final @NotNull String target) { - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - jedis.del("request:" + source + ":" + target); - } + this.redisManager.jedis().del("request:" + source + ":" + target); } @Override public void createPartyRequest(final @NotNull String source, final @NotNull String target, final int expires) { - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - jedis.setex("request:" + source + ":" + target, expires, ""); - } + this.redisManager.jedis().setex("request:" + source + ":" + target, expires, ""); } - private void clearPartyRequest(final @NotNull Jedis jedis, final @NotNull String source) { + private void clearPartyRequest(final @NotNull UnifiedJedis jedis, final @NotNull String source) { final Set keys = jedis.keys("request:" + source + ":*"); if (!keys.isEmpty()) jedis.del(keys.toArray(String[]::new)); } @Override public void clearPartyRequest(final @NotNull String source) { - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - this.clearPartyRequest(jedis, source); - } + this.clearPartyRequest(this.redisManager.jedis(), source); } @Override public boolean existsPartyRequest(final @NotNull String source, final @NotNull String target) { - try (final Jedis jedis = this.redisManager.jedisPool().getResource()) { - return jedis.exists("request:" + source + ":" + target); - } + return this.redisManager.jedis().exists("request:" + source + ":" + target); } public void databaseAdapter(final @NotNull DatabaseAdapter databaseAdapter) { diff --git a/common/src/main/java/com/github/dominik48n/party/redis/RedisManager.java b/common/src/main/java/com/github/dominik48n/party/redis/RedisManager.java index 56550c2..1ddc652 100644 --- a/common/src/main/java/com/github/dominik48n/party/redis/RedisManager.java +++ b/common/src/main/java/com/github/dominik48n/party/redis/RedisManager.java @@ -20,21 +20,27 @@ import com.github.dominik48n.party.config.RedisConfig; import com.github.dominik48n.party.user.UserManager; import com.google.common.collect.Lists; +import java.util.HashSet; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.jetbrains.annotations.NotNull; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.JedisClientConfig; +import redis.clients.jedis.JedisCluster; +import redis.clients.jedis.JedisPooled; import redis.clients.jedis.JedisPubSub; +import redis.clients.jedis.Protocol; +import redis.clients.jedis.UnifiedJedis; +import redis.clients.jedis.exceptions.JedisClusterException; -public class RedisManager extends JedisPubSub { +public class RedisManager extends JedisPubSub implements AutoCloseable { private final @NotNull ExecutorService executor = Executors.newFixedThreadPool(1); private final @NotNull List subscriptions = Lists.newArrayList(); - private final @NotNull JedisPool jedisPool; + private final @NotNull UnifiedJedis jedis; /** * Constructs a new RedisManager using the specified {@link RedisConfig}. @@ -42,10 +48,20 @@ public class RedisManager extends JedisPubSub { * @param config The {@link RedisConfig} to use. */ public RedisManager(final @NotNull RedisConfig config) { - final JedisPoolConfig poolConfig = new JedisPoolConfig(); - this.jedisPool = config.username().isEmpty() ? - new JedisPool(poolConfig, "1", 1, 3000, config.password()) : // TODO - new JedisPool(poolConfig, "2", 2, 3000, config.username(), config.password()); // TODO + final JedisClientConfig clientConfig = DefaultJedisClientConfig.builder() + .user(config.username()) + .password(config.password()) + .timeoutMillis(Protocol.DEFAULT_TIMEOUT) + .build(); + + UnifiedJedis jedis; + try { + jedis = new JedisCluster(new HashSet<>(config.hosts()), clientConfig); + } catch (final JedisClusterException e) { + final HostAndPort host = config.hosts().stream().findAny().orElseThrow(() -> new IllegalStateException("No Redis node was found in the config.")); + jedis = new JedisPooled(host, clientConfig); + } + this.jedis = jedis; } @Override @@ -58,9 +74,10 @@ public void onMessage(final String channel, final String message) { /** * Disconnects this RedisManager from the Redis server. */ + @Override public void close() { super.unsubscribe(); - this.jedisPool.destroy(); + this.jedis.close(); } /** @@ -82,9 +99,7 @@ public void publish(final @NotNull String channel, final @NotNull Document docum * @param message The message to publish to the channel. */ public void publish(final @NotNull String channel, final @NotNull String message) { - try (final Jedis jedis = this.jedisPool.getResource()) { - jedis.publish(channel, message); - } + this.jedis.publish(channel, message); } /** @@ -100,17 +115,12 @@ public void subscribes(final @NotNull UserManager userManager) { this.subscriptions.add(new RedisSwitchServerSub<>(userManager)); this.subscriptions.add(new RedisUpdateUserPartySub<>(userManager)); - this.executor.execute(() -> { - try (final Jedis jedis = RedisManager.this.jedisPool.getResource()) { - jedis.subscribe( - this, - this.subscriptions.stream().map(RedisSubscription::channel).toArray(String[]::new) - ); - } - }); + this.executor.execute( + () -> RedisManager.this.jedis.subscribe(this, this.subscriptions.stream().map(RedisSubscription::channel).toArray(String[]::new)) + ); } - public @NotNull JedisPool jedisPool() { - return this.jedisPool; + public @NotNull UnifiedJedis jedis() { + return this.jedis; } }