问题
We have multiple instances of the same application deployed behind an ELB (Load Balancer). Whenever a certain job is done, we count some elements and then want to increment a counter's value.
We use ElastiCache to hold these metrics in memory. We have set it up as a Cluster of Redis instances.
I'm having trouble understanding how to properly interact with ElastiCache so that the counter never misses any increment (i.e. an atomic operation). I know INCRBY
seems to be the way to go, but I'm not sure how to set up Spring Data so that I can issue a Redis command to my Master
. As it is, our approach isn't even thread-safe, but here is the code:
@Slf4j
@Service
@RequiredArgsConstructor
public class MetricServiceImpl implements MetricService {
private final IntegerMetricRepository integerMetricRepository;
private static final BigInteger ZERO = BigInteger.ZERO;
@Override
public long countRealJobs(List<Job> newJobs) {
return newJobs.stream()
.filter(job -> !job.isFake())
.count();
}
@Override
public long countRealDrafts(List<Draft> drafts) {
return drafts.stream()
.filter(draft -> !draft.getString(JsonFields.TITLE.getValue())
.contains("FAKE"))
.count();
}
@Override
public IntegerMetric increment(IntegerMetricType integerMetricType, long amount) {
IntegerMetric metric = getOrInitialize(integerMetricType);
BigInteger newValue = metric.getValue().add(BigInteger.valueOf(amount));
metric.setValue(newValue.max(ZERO)); // smallest possible value is 0
return integerMetricRepository.save(metric);
}
@Override
public BigInteger getValue(IntegerMetricType integerMetricType) {
return getOrInitialize(integerMetricType).getValue();
}
@Override
public IntegerMetric setValue(IntegerMetricType integerMetricType, long amount) {
IntegerMetric metric = getOrInitialize(integerMetricType);
if (amount < 0) { // negatives not allowed
log.info("Tried to set a negative value for an IntegerMetric.");
return metric;
}
metric.setValue(BigInteger.valueOf(amount));
return integerMetricRepository.save(metric);
}
/**
* @param integerMetricType the desired Entity
* @return either the Entity which already existed, or a new one initialized to {@code ZERO}.
*/
private IntegerMetric getOrInitialize(IntegerMetricType integerMetricType) {
return integerMetricRepository.findById(integerMetricType).orElseGet(
() -> integerMetricRepository.save(new IntegerMetric(integerMetricType, ZERO)));
}
}
For my Repository
, it seems like the only relevant operations I can issue are the equivalents of get
and set
. How do I set up my code so that I can issue actual Redis command to my Cluster, thus taking advantage of the atomic nature of the primitives (here, INCRBY
) I want to use?
回答1:
The solution resides in the usage of RedisTemplate
. With that class, it becomes possible to use the "AtomicCounter" that Redis natively supports (through operations such as INCRBY
).
来源:https://stackoverflow.com/questions/60305483/atomic-increment-with-spring-data-for-aws-elasticache-redis