| 37907 |
amit |
1 |
package com.spice.profitmandi.dao.config;
|
|
|
2 |
|
|
|
3 |
import org.springframework.data.redis.cache.RedisCache;
|
|
|
4 |
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
|
|
5 |
import org.springframework.data.redis.cache.RedisCacheManager;
|
|
|
6 |
import org.springframework.data.redis.cache.RedisCacheWriter;
|
|
|
7 |
import org.springframework.data.redis.connection.RedisConnection;
|
|
|
8 |
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
|
|
9 |
import org.springframework.data.redis.connection.ReturnType;
|
|
|
10 |
|
|
|
11 |
import java.nio.charset.StandardCharsets;
|
|
|
12 |
import java.time.Duration;
|
|
|
13 |
import java.util.Map;
|
|
|
14 |
import java.util.Set;
|
|
|
15 |
|
|
|
16 |
// An offer write that commits after a reader's transaction began leaves that reader with the old row;
|
|
|
17 |
// without this gate the reader caches it once the writer has evicted and it stays stale until the TTL
|
|
|
18 |
public class OfferCacheGate {
|
|
|
19 |
|
|
|
20 |
private static final byte[] LAST_CHANGE_KEY = "offer.cache.lastChange".getBytes(StandardCharsets.UTF_8);
|
|
|
21 |
// fofo, web and cron share one host clock; the margin absorbs small drift
|
|
|
22 |
private static final long CLOCK_MARGIN_MS = 2000;
|
|
|
23 |
// outside a transaction every statement reads fresh data, so only the loader's own run time matters
|
|
|
24 |
private static final long NO_TX_LOOKBACK_MS = 10000;
|
|
|
25 |
|
|
|
26 |
private static final byte[] MARK_SCRIPT = (
|
|
|
27 |
"local cur = tonumber(redis.call('GET', KEYS[1]) or '0') "
|
|
|
28 |
+ "if tonumber(ARGV[1]) > cur then redis.call('SET', KEYS[1], ARGV[1]) end "
|
|
|
29 |
+ "return 1").getBytes(StandardCharsets.UTF_8);
|
|
|
30 |
|
|
|
31 |
private static final byte[] PUT_IF_FRESH_SCRIPT = (
|
|
|
32 |
"local last = tonumber(redis.call('GET', KEYS[1]) or '0') "
|
|
|
33 |
+ "if tonumber(ARGV[1]) <= last + tonumber(ARGV[2]) then return 0 end "
|
|
|
34 |
+ "if tonumber(ARGV[4]) > 0 then redis.call('SET', KEYS[2], ARGV[3], 'PX', ARGV[4]) "
|
|
|
35 |
+ "else redis.call('SET', KEYS[2], ARGV[3]) end "
|
|
|
36 |
+ "return 1").getBytes(StandardCharsets.UTF_8);
|
|
|
37 |
|
|
|
38 |
private final RedisConnectionFactory connectionFactory;
|
|
|
39 |
|
|
|
40 |
public OfferCacheGate(RedisConnectionFactory connectionFactory) {
|
|
|
41 |
this.connectionFactory = connectionFactory;
|
|
|
42 |
}
|
|
|
43 |
|
|
|
44 |
// Called after an offer write's transaction completes, before its evictions
|
|
|
45 |
public void markChanged() {
|
|
|
46 |
eval(MARK_SCRIPT, 1, LAST_CHANGE_KEY, bytes(System.currentTimeMillis()));
|
|
|
47 |
}
|
|
|
48 |
|
|
|
49 |
private void putIfFresh(byte[] key, byte[] value, Duration ttl) {
|
|
|
50 |
Long txStart = TxStartTrackingTransactionManager.currentTxStartMillis();
|
|
|
51 |
long readerStart = txStart != null ? txStart : System.currentTimeMillis() - NO_TX_LOOKBACK_MS;
|
|
|
52 |
eval(PUT_IF_FRESH_SCRIPT, 2, LAST_CHANGE_KEY, key, bytes(readerStart), bytes(CLOCK_MARGIN_MS), value,
|
|
|
53 |
bytes(ttl.toMillis()));
|
|
|
54 |
}
|
|
|
55 |
|
|
|
56 |
public RedisCacheManager manager(RedisCacheConfiguration defaults, Map<String, RedisCacheConfiguration> initial,
|
|
|
57 |
Set<String> gatedCaches) {
|
|
|
58 |
return new Manager(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory), defaults, initial,
|
|
|
59 |
gatedCaches);
|
|
|
60 |
}
|
|
|
61 |
|
|
|
62 |
private void eval(byte[] script, int numKeys, byte[]... keysAndArgs) {
|
|
|
63 |
RedisConnection connection = connectionFactory.getConnection();
|
|
|
64 |
try {
|
|
|
65 |
connection.scriptingCommands().eval(script, ReturnType.INTEGER, numKeys, keysAndArgs);
|
|
|
66 |
} finally {
|
|
|
67 |
connection.close();
|
|
|
68 |
}
|
|
|
69 |
}
|
|
|
70 |
|
|
|
71 |
private static byte[] bytes(long value) {
|
|
|
72 |
return Long.toString(value).getBytes(StandardCharsets.UTF_8);
|
|
|
73 |
}
|
|
|
74 |
|
|
|
75 |
private class Manager extends RedisCacheManager {
|
|
|
76 |
private final Set<String> gatedCaches;
|
|
|
77 |
|
|
|
78 |
Manager(RedisCacheWriter writer, RedisCacheConfiguration defaults, Map<String, RedisCacheConfiguration> initial,
|
|
|
79 |
Set<String> gatedCaches) {
|
|
|
80 |
super(writer, defaults, initial);
|
|
|
81 |
this.gatedCaches = gatedCaches;
|
|
|
82 |
}
|
|
|
83 |
|
|
|
84 |
@Override
|
|
|
85 |
protected RedisCache createRedisCache(String name, RedisCacheConfiguration config) {
|
|
|
86 |
RedisCache cache = super.createRedisCache(name, config);
|
|
|
87 |
return gatedCaches.contains(name) ? new GatedCache(cache) : cache;
|
|
|
88 |
}
|
|
|
89 |
}
|
|
|
90 |
|
|
|
91 |
private class GatedCache extends RedisCache {
|
|
|
92 |
GatedCache(RedisCache cache) {
|
|
|
93 |
super(cache.getName(), cache.getNativeCache(), cache.getCacheConfiguration());
|
|
|
94 |
}
|
|
|
95 |
|
|
|
96 |
@Override
|
|
|
97 |
public void put(Object key, Object value) {
|
|
|
98 |
Object cacheValue = preProcessCacheValue(value);
|
|
|
99 |
if (cacheValue == null) {
|
|
|
100 |
super.put(key, value);
|
|
|
101 |
return;
|
|
|
102 |
}
|
|
|
103 |
putIfFresh(serializeCacheKey(createCacheKey(key)), serializeCacheValue(cacheValue),
|
|
|
104 |
getCacheConfiguration().getTtl());
|
|
|
105 |
}
|
|
|
106 |
}
|
|
|
107 |
}
|