Enhance the UUIDTranslator and add an option to increase performance.

This commit is contained in:
Tux 2014-05-30 23:22:31 -04:00
parent 92971fce96
commit a908e313d4
2 changed files with 116 additions and 15 deletions

View File

@ -87,7 +87,7 @@ public class RedisBungeeAPI {
return Collections2.transform(getPlayersOnline(), new Function<UUID, String>() { return Collections2.transform(getPlayersOnline(), new Function<UUID, String>() {
@Override @Override
public String apply(UUID uuid) { public String apply(UUID uuid) {
return getNameFromUuid(uuid); return getNameFromUuid(uuid, false);
} }
}); });
} }
@ -205,24 +205,66 @@ public class RedisBungeeAPI {
* <p> * <p>
* For the common use case of translating a list of UUIDs into names, use {@link #getHumanPlayersOnline()} * For the common use case of translating a list of UUIDs into names, use {@link #getHumanPlayersOnline()}
* as the efficiency of that function is slightly greater as the names are calculated lazily. * as the efficiency of that function is slightly greater as the names are calculated lazily.
* <p>
* If performance is a concern, use {@link #getNameFromUuid(java.util.UUID, boolean)} as this allows you to disable Mojang lookups.
* *
* @param uuid the UUID to fetch the name for * @param uuid the UUID to fetch the name for
* @return the name for the UUID * @return the name for the UUID
* @since 0.3 * @since 0.3
*/ */
public final String getNameFromUuid(@NonNull UUID uuid) { public final String getNameFromUuid(@NonNull UUID uuid) {
return plugin.getUuidTranslator().getNameFromUuid(uuid); return getNameFromUuid(uuid, true);
}
/**
* Fetch a name from the specified UUID. UUIDs are cached locally and in Redis. This function can fall back to Mojang
* as a last resort if {@code expensiveLookups} is true, so calls <strong>may</strong> be blocking.
* <p>
* For the common use case of translating the list of online players into names, use {@link #getHumanPlayersOnline()}
* as the efficiency of that function is slightly greater as the names are calculated lazily.
* <p>
* If performance is a concern, set {@code expensiveLookups} to false as this will disable lookups via Mojang.
*
* @param uuid the UUID to fetch the name for
* @param expensiveLookups whether or not to perform potentially expensive lookups
* @return the name for the UUID
* @since 0.3.2
*/
public final String getNameFromUuid(@NonNull UUID uuid, boolean expensiveLookups) {
return plugin.getUuidTranslator().getNameFromUuid(uuid, expensiveLookups);
} }
/** /**
* Fetch a UUID from the specified name. Names are cached locally and in Redis. This function falls back to Mojang * Fetch a UUID from the specified name. Names are cached locally and in Redis. This function falls back to Mojang
* as a last resort, so calls <strong>may</strong> be blocking. * as a last resort, so calls <strong>may</strong> be blocking.
* <p>
* If performance is a concern, see {@link #getUuidFromName(String, boolean)}, which disables the following functions:
* <ul>
* <li>Searching local entries case-insensitively</li>
* <li>Searching Mojang</li>
* </ul>
* *
* @param name the UUID to fetch the name for * @param name the UUID to fetch the name for
* @return the UUID for the name * @return the UUID for the name
* @since 0.3 * @since 0.3
*/ */
public final UUID getUuidFromName(@NonNull String name) { public final UUID getUuidFromName(@NonNull String name) {
return plugin.getUuidTranslator().getTranslatedUuid(name); return getUuidFromName(name, true);
}
/**
* Fetch a UUID from the specified name. Names are cached locally and in Redis. This function falls back to Mojang
* as a last resort if {@code expensiveLookups} is true, so calls <strong>may</strong> be blocking.
* <p>
* If performance is a concern, set {@code expensiveLookups} to false to disable searching Mojang and searching for usernames
* case-insensitively.
*
* @param name the UUID to fetch the name for
* @param expensiveLookups whether or not to perform potentially expensive lookups
* @return the UUID for the name
* @since 0.3.2
*/
public final UUID getUuidFromName(@NonNull String name, boolean expensiveLookups) {
return plugin.getUuidTranslator().getTranslatedUuid(name, expensiveLookups);
} }
} }

View File

@ -20,24 +20,36 @@ import redis.clients.jedis.exceptions.JedisException;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@RequiredArgsConstructor @RequiredArgsConstructor
public class UUIDTranslator { public class UUIDTranslator {
private final RedisBungee plugin; private final RedisBungee plugin;
private BiMap<String, UUID> uuidMap = Maps.synchronizedBiMap(HashBiMap.<String, UUID>create()); private final BiMap<String, UUID> uuidMap = HashBiMap.create();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public static final Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"); public static final Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}");
public static final Pattern MOJANGIAN_UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{32}"); public static final Pattern MOJANGIAN_UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{32}");
public UUID getTranslatedUuid(@NonNull String player) { public UUID getTranslatedUuid(@NonNull String player, boolean expensiveLookups) {
if (ProxyServer.getInstance().getPlayer(player) != null) if (ProxyServer.getInstance().getPlayer(player) != null)
return ProxyServer.getInstance().getPlayer(player).getUniqueId(); return ProxyServer.getInstance().getPlayer(player).getUniqueId();
UUID uuid = uuidMap.get(player); UUID uuid;
if (uuid != null)
return uuid;
// Check if it exists in the map
lock.readLock().lock();
try {
uuid = uuidMap.get(player);
if (uuid != null)
return uuid;
} finally {
lock.readLock().unlock();
}
// Check if we can exit early
if (UUID_PATTERN.matcher(player).find()) { if (UUID_PATTERN.matcher(player).find()) {
return UUID.fromString(player); return UUID.fromString(player);
} }
@ -53,7 +65,21 @@ public class UUIDTranslator {
return uuid; return uuid;
} }
// Okay, it wasn't locally cached. Let's try Redis. // We could not exit early. Look for the name, ignoring case.
if (expensiveLookups) {
lock.readLock().lock();
try {
for (Map.Entry<String, UUID> entry : uuidMap.entrySet()) {
if (entry.getKey().equalsIgnoreCase(player)) {
return entry.getValue();
}
}
} finally {
lock.readLock().unlock();
}
}
// Okay, it wasn't locally cached and the expensive search didn't help us. Let's try Redis.
Jedis jedis = plugin.getPool().getResource(); Jedis jedis = plugin.getPool().getResource();
try { try {
try { try {
@ -62,11 +88,19 @@ public class UUIDTranslator {
// This is it! // This is it!
uuid = UUID.fromString(stored); uuid = UUID.fromString(stored);
storeInfo(player, uuid, jedis); storeInfo(player, uuid, jedis);
uuidMap.put(player, uuid); lock.writeLock().lock();
try {
uuidMap.put(player, uuid);
} finally {
lock.writeLock().unlock();
}
return uuid; return uuid;
} }
// That didn't work. Let's ask Mojang. // That didn't work. Let's ask Mojang.
if (!expensiveLookups)
return null;
Map<String, UUID> uuidMap1; Map<String, UUID> uuidMap1;
try { try {
uuidMap1 = new UUIDFetcher(Collections.singletonList(player)).call(); uuidMap1 = new UUIDFetcher(Collections.singletonList(player)).call();
@ -76,8 +110,12 @@ public class UUIDTranslator {
} }
for (Map.Entry<String, UUID> entry : uuidMap1.entrySet()) { for (Map.Entry<String, UUID> entry : uuidMap1.entrySet()) {
if (entry.getKey().equalsIgnoreCase(player)) { if (entry.getKey().equalsIgnoreCase(player)) {
uuidMap.put(player, entry.getValue()); try {
storeInfo(player, entry.getValue(), jedis); uuidMap.put(entry.getKey(), entry.getValue());
} finally {
lock.writeLock().unlock();
}
storeInfo(entry.getKey(), entry.getValue(), jedis);
return entry.getValue(); return entry.getValue();
} }
} }
@ -93,11 +131,18 @@ public class UUIDTranslator {
return null; // Nope, game over! return null; // Nope, game over!
} }
public String getNameFromUuid(@NonNull UUID player) { public String getNameFromUuid(@NonNull UUID player, boolean expensiveLookups) {
if (ProxyServer.getInstance().getPlayer(player) != null) if (ProxyServer.getInstance().getPlayer(player) != null)
return ProxyServer.getInstance().getPlayer(player).getName(); return ProxyServer.getInstance().getPlayer(player).getName();
String name = uuidMap.inverse().get(player); String name;
lock.readLock().lock();
try {
name = uuidMap.inverse().get(player);
} finally {
lock.readLock().unlock();
}
if (name != null) if (name != null)
return name; return name;
@ -108,10 +153,18 @@ public class UUIDTranslator {
String stored = jedis.hget("player:" + player, "name"); String stored = jedis.hget("player:" + player, "name");
if (stored != null) { if (stored != null) {
name = stored; name = stored;
uuidMap.put(name, player); lock.writeLock().lock();
try {
uuidMap.put(name, player);
} finally {
lock.writeLock().unlock();
}
return name; return name;
} }
if (!expensiveLookups)
return null;
// That didn't work. Let's ask Mojang. // That didn't work. Let's ask Mojang.
try { try {
name = new NameFetcher(Collections.singletonList(player)).call().get(player); name = new NameFetcher(Collections.singletonList(player)).call().get(player);
@ -122,6 +175,12 @@ public class UUIDTranslator {
if (name != null) { if (name != null) {
storeInfo(name, player, jedis); storeInfo(name, player, jedis);
lock.writeLock().lock();
try {
uuidMap.put(name, player);
} finally {
lock.writeLock().unlock();
}
return name; return name;
} }