I would like to know the possible improvements in design and concurrency of the following load balancer:
- ability to add/register new instance
- keep max of 10 instances for load balancer
- forbid duplicates for available instances
- on add operation check for if instances are alive, per random
- ability to return random instance
Questions:
- Is is better to return boolean value indicating that instance registration was successful, or to return void and throw checked/unchecked exception indicating it went wrong?
- How could this code be improved from concurrency perspective?
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.random.RandomGenerator;
import static java.util.stream.Collectors.toCollection;
public final class SimpleLoadBalancerV1 {
private final ReadWriteLock lock;
private final InstanceHealthChecker healthChecker;
private final InstanceSelector selector;
private final int maxInstances;
private List<Instance> instances;
public SimpleLoadBalancerV1(int maxInstances, InstanceSelector selector, InstanceHealthChecker healthChecker) {
if (maxInstances <= 0) throw new IllegalArgumentException("invalid maxInstances");
this.maxInstances = maxInstances;
this.selector = selector;
this.healthChecker = healthChecker;
this.lock = new ReentrantReadWriteLock();
this.instances = new ArrayList<>();
}
public boolean registerInstance(Instance newInstance) {
lock.writeLock().lock();
try {
if (instances.size() == maxInstances) return false;
if (instances.contains(newInstance)) return false;
var newInstances = detectAliveInstances(instances);
newInstances.add(newInstance);
instances = newInstances;
} finally {
lock.writeLock().unlock();
}
return true;
}
public Instance getInstance() {
lock.readLock().lock();
try {
return selector.select(instances);
} finally {
lock.readLock().unlock();
}
}
private List<Instance> detectAliveInstances(List<Instance> instances) {
return instances.stream().filter(healthChecker::isAlive).collect(toCollection(ArrayList::new));
}
public interface InstanceHealthChecker {
boolean isAlive(Instance instance);
}
public static final class RandomHealthChecker implements InstanceHealthChecker {
private final RandomGenerator generator;
public RandomHealthChecker(RandomGenerator generator) {
this.generator = generator;
}
@Override
public boolean isAlive(Instance instance) {
return generator.nextBoolean();
}
}
public interface InstanceSelector {
Instance select(List<Instance> instances);
}
public static final class RandomInstanceSelector implements InstanceSelector {
private final RandomGenerator generator;
public RandomInstanceSelector(RandomGenerator generator) {
this.generator = generator;
}
@Override
public Instance select(List<Instance> instances) {
return instances.get(generator.nextInt(instances.size()));
}
}
@Getter
public static final class Instance {
private final String name;
public Instance(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Instance instance = (Instance) o;
return Objects.equals(name, instance.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
}