Redis Sentinel ip address/hostname mapping config
Motivation
It would be really helpful if we could map the ip/hostname sentinel responds with to a custom address for testing/proxying, like how redis cluster supports nodeAddressMap
I can open a PR if this is an acceptable feature to add
Basic Code Example
createSentinel({
...
nodeAddressMap: {
'10.0.0.1:30001': {
host: 'external-host.io',
port: 30001
},
'10.0.0.2:30002': {
host: 'external-host.io',
port: 30002
}
}
}
Hi @pinyht
Thanks a lot for the detailed report and the reproducible code example — that’s really helpful. 🙏 We haven’t had a chance to review it yet, but I’ll try to triage and provide feedback by the end of this week.
@pinyht
This is a limitation of the current implementation. By default, Jedis uses the Gson library for JSON serialization/deserialization. The JSONObject instances returned by Gson are not serializable, which causes JedisCacheException: Failed to serialize object when used with Client-Side Caching (CSC).
Note:
jsonGet(String key, Path2... paths) is still bound to the default Gson implementation and does not use the configured JsonObjectMapper. Until this is resolved, the only alternative I can come up with is the deprecated jsonGet(String key, Path... paths) method instead.
Workaround: Use Custom JsonObjectMapper
Configure a custom JsonObjectMapper (e.g., Jackson) that returns serializable JSON objects:
Click to expand example code
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.csc.CacheConfig;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.json.JsonObjectMapper;
import redis.clients.jedis.json.Path;
class CSCJsonSerialisationError {
public static void main(String[] args) {
CacheConfig cacheConfig = CacheConfig.builder().build();
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().resp3().build();
HostAndPort hp = new HostAndPort("localhost", 6379);
// Using Jackson
System.out.println("======================================================= ");
System.out.println("JedisPooled created with JsonObjectMapper - Jackson");
try (JedisPooled jedis = new JedisPooled(hp, clientConfig, cacheConfig);) {
jedis.setJsonObjectMapper(getCustomJacksonObjectMapper());
jedis.del("testKey-custom-jackson");
String resp1 = jedis.jsonSet("testKey-custom-jackson", Path.of("$"), "{\"key\":\"testKey-custom-jackson-value\"}");
System.out.println(resp1);
Object resp2 = jedis.jsonGet("testKey-custom-jackson", Path.of("$.key"));
System.out.println("testKey-custom-jackson.key: " + resp2);
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
public static CustomJacksonObjectMapper getCustomJacksonObjectMapper() {
ObjectMapper om = new ObjectMapper();
om.registerModule(new JavaTimeModule());
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return new CustomJacksonObjectMapper(om);
}
public static class CustomJacksonObjectMapper implements JsonObjectMapper {
private final ObjectMapper om;
CustomJacksonObjectMapper(ObjectMapper om) {
this.om = om;
}
@Override
public <T> T fromJson(String value, Class<T> valueType) {
try {
return om.readValue(value, valueType);
} catch (JsonProcessingException e) {
throw new JedisException(e);
}
}
@Override
public String toJson(Object value) {
try {
return om.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new JedisException(e);
}
}
}
}