Skip to content

Commit fc7ce99

Browse files
committed
client-side caching with a new client impl and connection wrapper
simplify cache impls; some javadocs and tests more javadocs; invalidation handler on client basic support for max age in default impls initial PR feedback; naming consistencies checkpoint on wrapped connection push tracking status down to actual conn impl simplify cache impls to just LRU backed by LinkedHashMap cleanup client wrapper; test simple operation docs update
1 parent a3571b4 commit fc7ce99

18 files changed

+1338
-4
lines changed

src/main/asciidoc/index.adoc

+27-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Redis has a rich API and it can be organized in the following groups:
2424
* Strings - Commands to work with Strings.
2525
* Transactions - Commands to handle transaction lifecycle.
2626
* Streams - Commands to handle streaming.
27+
* Client-side caching - Commands to control client-side caching.
2728
2829
== Using Vert.x-Redis
2930

@@ -182,9 +183,34 @@ And from another place in the code publish messages to the queue:
182183
----
183184

184185
NOTE: It is important to remember that the commands `SUBSCRIBE`, `UNSUBSCRIBE`, `PSUBSCRIBE` and `PUNSUBSCRIBE` are `void`.
185-
This means that the result in case of success is `null` not a instance of response.
186+
This means that the result in case of success is `null` not an instance of response.
186187
All messages are then routed through the handler on the client.
187188

189+
== Client-side Caching
190+
191+
Redis supports client-side caching implementations using a strategy called _Tracking_.
192+
193+
All modes of the client support caching except connections that are in pub/sub mode.
194+
195+
To create a client with client-side caching, one would do:
196+
197+
[source,$lang]
198+
----
199+
{@link examples.RedisExamples#clientCaching1}
200+
----
201+
202+
The default implementation will use a simple Least-Recently-Used (LFU) cache backed by a `LinkedHashMap`.
203+
You can also provide your own implementation of the cache:
204+
205+
[source,$lang]
206+
----
207+
{@link examples.RedisExamples#clientCaching2}
208+
----
209+
210+
NOTE: The cache is not a write-through cache. A value will not be stored in the client-side cache until the value is fetched from Redis for the first time.
211+
To avoid write-then-read race conditions within the same batch, read commands that are part of a batch will not check the cache first.
212+
Additionally, the current implementation does not support the `OPTIN` or `NOLOOP` options.
213+
188214
== Tracing commands
189215

190216
The Redis client can trace command execution when Vert.x has tracing enabled.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package io.vertx.redis.client;
2+
3+
import io.vertx.core.json.JsonObject;
4+
import io.vertx.core.json.JsonArray;
5+
import io.vertx.core.json.impl.JsonUtil;
6+
import java.time.Instant;
7+
import java.time.format.DateTimeFormatter;
8+
import java.util.Base64;
9+
10+
/**
11+
* Converter and mapper for {@link io.vertx.redis.client.CachingRedisOptions}.
12+
* NOTE: This class has been automatically generated from the {@link io.vertx.redis.client.CachingRedisOptions} original class using Vert.x codegen.
13+
*/
14+
public class CachingRedisOptionsConverter {
15+
16+
17+
private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER;
18+
private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER;
19+
20+
public static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, CachingRedisOptions obj) {
21+
for (java.util.Map.Entry<String, Object> member : json) {
22+
switch (member.getKey()) {
23+
case "maxAge":
24+
if (member.getValue() instanceof Number) {
25+
obj.setMaxAge(((Number)member.getValue()).longValue());
26+
}
27+
break;
28+
case "maxAgeUnit":
29+
if (member.getValue() instanceof String) {
30+
obj.setMaxAgeUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue()));
31+
}
32+
break;
33+
case "maxCacheSize":
34+
if (member.getValue() instanceof Number) {
35+
obj.setMaxCacheSize(((Number)member.getValue()).intValue());
36+
}
37+
break;
38+
case "mode":
39+
if (member.getValue() instanceof String) {
40+
obj.setMode(io.vertx.redis.client.ClientSideCacheMode.valueOf((String)member.getValue()));
41+
}
42+
break;
43+
case "prefix":
44+
if (member.getValue() instanceof String) {
45+
obj.setPrefix((String)member.getValue());
46+
}
47+
break;
48+
case "prefixes":
49+
if (member.getValue() instanceof JsonArray) {
50+
java.util.ArrayList<java.lang.String> list = new java.util.ArrayList<>();
51+
((Iterable<Object>)member.getValue()).forEach( item -> {
52+
if (item instanceof String)
53+
list.add((String)item);
54+
});
55+
obj.setPrefixes(list);
56+
}
57+
break;
58+
case "prefixs":
59+
if (member.getValue() instanceof JsonArray) {
60+
((Iterable<Object>)member.getValue()).forEach( item -> {
61+
if (item instanceof String)
62+
obj.addPrefix((String)item);
63+
});
64+
}
65+
break;
66+
}
67+
}
68+
}
69+
70+
public static void toJson(CachingRedisOptions obj, JsonObject json) {
71+
toJson(obj, json.getMap());
72+
}
73+
74+
public static void toJson(CachingRedisOptions obj, java.util.Map<String, Object> json) {
75+
json.put("maxAge", obj.getMaxAge());
76+
if (obj.getMaxAgeUnit() != null) {
77+
json.put("maxAgeUnit", obj.getMaxAgeUnit().name());
78+
}
79+
json.put("maxCacheSize", obj.getMaxCacheSize());
80+
if (obj.getMode() != null) {
81+
json.put("mode", obj.getMode().name());
82+
}
83+
if (obj.getPrefixes() != null) {
84+
JsonArray array = new JsonArray();
85+
obj.getPrefixes().forEach(item -> array.add(item));
86+
json.put("prefixes", array);
87+
}
88+
}
89+
}

src/main/java/examples/RedisExamples.java

+25-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ private Future<RedisConnection> createRedisClient() {
143143

144144
// make sure to invalidate old connection if present
145145
if (redis != null) {
146-
redis.close();;
146+
redis.close();
147147
}
148148

149149
if (CONNECTING.compareAndSet(false, true)) {
@@ -246,4 +246,28 @@ public void example13(Vertx vertx) {
246246
public void tracing1(RedisOptions options) {
247247
options.setTracingPolicy(TracingPolicy.ALWAYS);
248248
}
249+
250+
public void clientCaching1(Vertx vertx) {
251+
CachingRedis
252+
.create(vertx)
253+
.connect()
254+
.onSuccess(conn -> {
255+
// get the value for a key, returning from a local in-memory cache if
256+
// it exists, or fetching from Redis if not. if the value is fetched from
257+
// Redis, it will be stored in the local cache
258+
conn.send(Request.cmd(Command.GET).arg("key"));
259+
});
260+
}
261+
262+
public void clientCaching2(Vertx vertx, RedisClientCache customCache) {
263+
CachingRedis
264+
.create(vertx, customCache)
265+
.connect()
266+
.onSuccess(conn -> {
267+
// get the value for a key, returning from the custom cache if
268+
// it exists, or fetching from Redis if not. if the value is fetched from
269+
// Redis, it will be stored in the local cache
270+
conn.send(Request.cmd(Command.GET).arg("key"));
271+
});
272+
}
249273
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2019 Red Hat, Inc.
3+
* <p>
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License v1.0
6+
* and Apache License v2.0 which accompanies this distribution.
7+
* <p>
8+
* The Eclipse Public License is available at
9+
* http://www.eclipse.org/legal/epl-v10.html
10+
* <p>
11+
* The Apache License v2.0 is available at
12+
* http://www.opensource.org/licenses/apache2.0.php
13+
* <p>
14+
* You may elect to redistribute this code under either of these licenses.
15+
*/
16+
package io.vertx.redis.client;
17+
18+
import io.vertx.core.Future;
19+
import io.vertx.core.Handler;
20+
import io.vertx.core.Vertx;
21+
import io.vertx.redis.client.impl.CachingRedisClient;
22+
import io.vertx.redis.client.impl.cache.CacheKey;
23+
24+
import java.util.Collection;
25+
26+
/**
27+
* A {@link Redis} client wrapper that implements client-side caching.
28+
*
29+
* @see <a href="https://redis.io/docs/manual/client-side-caching/">Client-side caching in Redis</a>
30+
*/
31+
public interface CachingRedis extends Redis {
32+
33+
/**
34+
* Create a new caching client using default client and cache options.
35+
*
36+
* @param vertx the vertx instance
37+
* @return the caching client
38+
*/
39+
static CachingRedis create(Vertx vertx) {
40+
return create(vertx, Redis.createClient(vertx));
41+
}
42+
43+
/**
44+
* Create a new caching client wrapping an existing redis client with default caching options.
45+
*
46+
* @param vertx the vertx instance
47+
* @param redis the redis client to wrap
48+
* @return the caching client
49+
*/
50+
static CachingRedis create(Vertx vertx, Redis redis) {
51+
return create(vertx, redis, RedisClientCache.lru(new CachingRedisOptions()));
52+
}
53+
54+
/**
55+
* Create a new caching client using default client and cache options, backed by a given cache.
56+
*
57+
* @param vertx the vertx instance
58+
* @param cache the backing cache
59+
* @return the caching client
60+
*/
61+
static CachingRedis create(Vertx vertx, RedisClientCache cache) {
62+
return create(vertx, Redis.createClient(vertx), cache);
63+
}
64+
65+
/**
66+
* Create a new caching client wrapping an existing redis client and backed by a given cache.
67+
*
68+
* @param vertx the vertx instance
69+
* @param redis the redis client to wrap
70+
* @param cache the backing cache
71+
* @return the caching client
72+
*/
73+
static CachingRedis create(Vertx vertx, Redis redis, RedisClientCache cache) {
74+
return create(vertx, redis, cache, new CachingRedisOptions());
75+
}
76+
77+
/**
78+
* Create a new caching client wrapping an existing redis client and using the given cache options.
79+
*
80+
* @param vertx the vertx instance
81+
* @param redis the redis client to wrap
82+
* @param options the cache options
83+
* @return the caching client
84+
*/
85+
static CachingRedis create(Vertx vertx, Redis redis, CachingRedisOptions options) {
86+
return create(vertx, redis, RedisClientCache.lru(options), options);
87+
}
88+
89+
/**
90+
* Create a new caching client wrapping an existing redis client, using the given cache and cache options.
91+
*
92+
* @param vertx the vertx instance
93+
* @param redis the redis client to wrap
94+
* @param cache the backing cache
95+
* @param options the cache options
96+
* @return the caching client
97+
*/
98+
static CachingRedis create(Vertx vertx, Redis redis, RedisClientCache cache, CachingRedisOptions options) {
99+
return new CachingRedisClient(vertx, redis, cache, options);
100+
}
101+
102+
/**
103+
* Flush the local cache.
104+
*
105+
* <p>
106+
* This operation only clears the local cache and has no interaction with the server.
107+
*
108+
* @return a future indicating the status of the operation
109+
*/
110+
Future<Void> flush();
111+
112+
/**
113+
* Set a handler to be called when invalidation is performed.
114+
*
115+
* <p>
116+
* The client will clear the keys before this handler is invoked. It is not recommended to modify
117+
* the cache as a part of this handler. The primary function is for instrumentation.
118+
*
119+
* @param handler a handler that accepts the keys which were invalidated
120+
* @return fluent self
121+
*/
122+
CachingRedis invalidationHandler(Handler<Collection<CacheKey>> handler);
123+
}

0 commit comments

Comments
 (0)