
com.netflix.dyno.jedis.DynoDualWriterClient Maven / Gradle / Ivy
/**
* Copyright 2016 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.netflix.dyno.jedis;
import com.netflix.dyno.connectionpool.ConnectionPool;
import com.netflix.dyno.connectionpool.ConnectionPoolMonitor;
import com.netflix.dyno.connectionpool.OperationResult;
import com.netflix.dyno.contrib.DynoOPMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Client that provides 'dual-write' functionality. This is useful when clients wish to move from one dynomite
* cluster to another, for example to upgrade cluster capacity.
*
* @author jcacciatore
*/
public class DynoDualWriterClient extends DynoJedisClient {
private static final Logger logger = LoggerFactory.getLogger(DynoDualWriterClient.class);
private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
// Client used for dual-write functionality.
private final DynoJedisClient shadowClient;
// Used to control traffic flow to the dual-write cluster
private final Dial dial;
public DynoDualWriterClient(String name, String clusterName,
ConnectionPool pool,
DynoOPMonitor operationMonitor,
ConnectionPoolMonitor connectionPoolMonitor,
DynoJedisClient shadowClient) {
this(name, clusterName, pool, operationMonitor, connectionPoolMonitor, shadowClient,
new TimestampDial(pool.getConfiguration().getDualWritePercentage()));
}
public DynoDualWriterClient(String name, String clusterName,
ConnectionPool pool,
DynoOPMonitor operationMonitor,
ConnectionPoolMonitor connectionPoolMonitor,
DynoJedisClient shadowClient,
Dial dial) {
super(name, clusterName, pool, operationMonitor, connectionPoolMonitor);
this.shadowClient = shadowClient;
this.dial = dial;
}
public Dial getDial() {
return dial;
}
private Future> writeAsync(final String key, Callable> func) {
if (sendShadowRequest(key)) {
try {
return executor.submit(func);
} catch (Throwable th) {
opMonitor.recordFailure("shadowPool_submit", th.getMessage());
}
// if we need to do any other processing (logging, etc) now's the time...
}
return null;
}
/**
* writeAsync() for binary commands
*/
private Future> writeAsync(final byte[] key, Callable> func) {
if (sendShadowRequest(key)) {
try {
return executor.submit(func);
} catch (Throwable th) {
opMonitor.recordFailure("shadowPool_submit", th.getMessage());
}
// if we need to do any other processing (logging, etc) now's the time...
}
return null;
}
/**
* Returns true if the connection pool
* Is NOT idle
* Has active pools (the shadow cluster may disappear at any time and we don't want to bloat logs)
* The key is in range in the dial
*
* The idle check is necessary since there may be active host pools however the shadow client may not be able to
* connect to them, for example, if security groups are not configured properly.
*/
private boolean sendShadowRequest(String key) {
return this.getConnPool().getConfiguration().isDualWriteEnabled() &&
!this.getConnPool().isIdle() &&
this.getConnPool().getActivePools().size() > 0 &&
dial.isInRange(key);
}
private boolean sendShadowRequest(byte[] key) {
return this.getConnPool().getConfiguration().isDualWriteEnabled() &&
!this.getConnPool().isIdle() &&
this.getConnPool().getActivePools().size() > 0 &&
dial.isInRange(key);
}
public interface Dial {
/**
* Returns true if the given value is in range, false otherwise
*/
boolean isInRange(String key);
boolean isInRange(byte[] key);
void setRange(int range);
}
/**
* Default Dial implementation that presumes no knowledge of the key value
* and simply uses a timestamp to determine inclusion/exclusion
*/
private static class TimestampDial implements Dial {
private final AtomicInteger range = new AtomicInteger(1);
public TimestampDial(int range) {
this.range.set(range);
}
@Override
public boolean isInRange(String key) {
return range.get() > (System.currentTimeMillis() % 100);
}
@Override
public boolean isInRange(byte[] key) {
return range.get() > (System.currentTimeMillis() % 100);
}
@Override
public void setRange(int range) {
this.range.set(range);
}
}
//----------------------------- JEDIS COMMANDS --------------------------------------
@Override
public Long append(final String key, final String value) {
return this.d_append(key, value).getResult();
}
@Override
public OperationResult d_append(final String key, final String value) {
writeAsync(key, new Callable>() {
@Override
public OperationResult call() throws Exception {
return shadowClient.d_append(key, value);
}
});
return DynoDualWriterClient.super.d_append(key, value);
}
@Override
public OperationResult d_hmset(final String key, final Map hash) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_hmset(key, hash);
}
});
return DynoDualWriterClient.super.d_hmset(key, hash);
}
@Override
public Long sadd(final String key, final String... members) {
return this.d_sadd(key, members).getResult();
}
@Override
public OperationResult d_sadd(final String key, final String... members) {
writeAsync(key, new Callable>() {
@Override
public OperationResult call() throws Exception {
return shadowClient.d_sadd(key, members);
}
});
return DynoDualWriterClient.super.d_sadd(key, members);
}
@Override
public Long hset(final String key, final String field, final String value) {
return this.d_hset(key, field, value).getResult();
}
@Override
public OperationResult d_hset(final String key, final String field, final String value) {
writeAsync(key, new Callable>() {
@Override
public OperationResult call() throws Exception {
return shadowClient.d_hset(key, field, value);
}
});
return DynoDualWriterClient.super.d_hset(key, field, value);
}
@Override
public String set(final String key, final String value) {
return this.d_set(key, value).getResult();
}
@Override
public OperationResult d_set(final String key, final String value) {
writeAsync(key, new Callable>() {
@Override
public OperationResult call() throws Exception {
return shadowClient.d_set(key, value);
}
});
return DynoDualWriterClient.super.d_set(key, value);
}
@Override
public String setex(final String key, int seconds, String value) {
return this.d_setex(key, seconds, value).getResult();
}
@Override
public OperationResult d_setex(final String key, final Integer seconds, final String value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_setex(key, seconds, value);
}
});
return DynoDualWriterClient.super.d_setex(key, seconds, value);
}
@Override
public Long del(final String key) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_del(key);
}
});
return DynoDualWriterClient.super.del(key);
}
@Override
public Long expire(final String key, final int seconds) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_expire(key, seconds);
}
});
return DynoDualWriterClient.super.expire(key, seconds);
}
@Override
public Long expireAt(final String key, final long unixTime) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_expireAt(key, unixTime);
}
});
return DynoDualWriterClient.super.expireAt(key, unixTime);
}
@Override
public String getSet(final String key, final String value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_getSet(key, value);
}
});
return DynoDualWriterClient.super.getSet(key, value);
}
@Override
public Long hdel(final String key, final String... fields) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_hdel(key, fields);
}
});
return DynoDualWriterClient.super.hdel(key);
}
@Override
public Long hincrBy(final String key, final String field, final long value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_hincrBy(key, field, value);
}
});
return DynoDualWriterClient.super.hincrBy(key, field, value);
}
@Override
public Double hincrByFloat(final String key, final String field, final double value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_hincrByFloat(key, field, value);
}
});
return DynoDualWriterClient.super.hincrByFloat(key, field, value);
}
@Override
public Long hsetnx(final String key, final String field, final String value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_hsetnx(key, field, value);
}
});
return DynoDualWriterClient.super.hsetnx(key, field, value);
}
@Override
public Long incr(final String key) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_incr(key);
}
});
return DynoDualWriterClient.super.incr(key);
}
@Override
public Long incrBy(final String key, final long delta) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_incrBy(key, delta);
}
});
return DynoDualWriterClient.super.incrBy(key, delta);
}
@Override
public Double incrByFloat(final String key, final double increment) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_incrByFloat(key, increment);
}
});
return DynoDualWriterClient.super.incrByFloat(key, increment);
}
@Override
public String lpop(final String key) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_lpop(key);
}
});
return DynoDualWriterClient.super.lpop(key);
}
@Override
public Long lpush(final String key, final String... values) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_lpush(key, values);
}
});
return DynoDualWriterClient.super.lpush(key, values);
}
@Override
public Long lrem(final String key, final long count, final String value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_lrem(key, count, value);
}
});
return DynoDualWriterClient.super.lrem(key, count, value);
}
@Override
public String lset(final String key, final long count, final String value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_lset(key, count, value);
}
});
return DynoDualWriterClient.super.lset(key, count, value);
}
@Override
public String ltrim(final String key, final long start, final long end) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_ltrim(key, start, end);
}
});
return DynoDualWriterClient.super.ltrim(key, start, end);
}
@Override
public Long persist(final String key) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_persist(key);
}
});
return DynoDualWriterClient.super.persist(key);
}
@Override
public Long pexpireAt(final String key, final long millisecondsTimestamp) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_pexpireAt(key, millisecondsTimestamp);
}
});
return DynoDualWriterClient.super.pexpireAt(key, millisecondsTimestamp);
}
@Override
public Long pttl(final String key) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_pttl(key);
}
});
return DynoDualWriterClient.super.pttl(key);
}
@Override
public String rename(final String oldkey, final String newkey) {
writeAsync(oldkey, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_rename(oldkey, oldkey);
}
});
return DynoDualWriterClient.super.rename(oldkey, oldkey);
}
@Override
public String rpop(final String key) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_rpop(key);
}
});
return DynoDualWriterClient.super.rpop(key);
}
@Override
public Long scard(final String key) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_scard(key);
}
});
return DynoDualWriterClient.super.scard(key);
}
@Override
public Boolean setbit(final String key, final long offset, final boolean value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_setbit(key, offset, value);
}
});
return DynoDualWriterClient.super.setbit(key, offset, value);
}
@Override
public Boolean setbit(final String key, final long offset, final String value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_setbit(key, offset, value);
}
});
return DynoDualWriterClient.super.setbit(key, offset, value);
}
@Override
public Long setnx(final String key, final String value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_setnx(key, value);
}
});
return DynoDualWriterClient.super.setnx(key, value);
}
@Override
public Long setrange(final String key, final long offset, final String value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_setrange(key, offset, value);
}
});
return DynoDualWriterClient.super.setrange(key, offset, value);
}
@Override
public Set smembers(final String key) {
writeAsync(key, new Callable>>(){
@Override
public OperationResult> call() throws Exception {
return shadowClient.d_smembers(key);
}
});
return DynoDualWriterClient.super.smembers(key);
}
@Override
public Long smove(final String srckey, final String dstkey, final String member) {
writeAsync(srckey, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_smove(srckey,dstkey,member);
}
});
return DynoDualWriterClient.super.smove(srckey,dstkey,member);
}
@Override
public List sort(final String key) {
writeAsync(key, new Callable>>(){
@Override
public OperationResult> call() throws Exception {
return shadowClient.d_sort(key);
}
});
return DynoDualWriterClient.super.sort(key);
}
@Override
public String spop(final String key) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_spop(key);
}
});
return DynoDualWriterClient.super.spop(key);
}
@Override
public Long srem(final String key, final String... members) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_srem(key,members);
}
});
return DynoDualWriterClient.super.srem(key,members);
}
@Override
public ScanResult sscan(final String key, final String cursor) {
writeAsync(key, new Callable>>(){
@Override
public OperationResult> call() throws Exception {
return shadowClient.d_sscan(key,cursor);
}
});
return DynoDualWriterClient.super.sscan(key,cursor);
}
@Override
public ScanResult sscan(final String key, final String cursor, final ScanParams params) {
writeAsync(key, new Callable>>(){
@Override
public OperationResult> call() throws Exception {
return shadowClient.d_sscan(key,cursor,params);
}
});
return DynoDualWriterClient.super.sscan(key,cursor,params);
}
@Override
public Long ttl(final String key) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_ttl(key);
}
});
return DynoDualWriterClient.super.ttl(key);
}
@Override
public Long zadd(final String key, final double score, final String member) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_zadd(key, score, member);
}
});
return DynoDualWriterClient.super.zadd(key, score, member);
}
@Override
public Long zadd(final String key, final Map scoreMembers) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_zadd(key, scoreMembers);
}
});
return DynoDualWriterClient.super.zadd(key, scoreMembers);
}
@Override
public Double zincrby(final String key, final double score, final String member) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_zincrby(key, score, member);
}
});
return DynoDualWriterClient.super.zincrby(key, score, member);
}
@Override
public Long zrem(final String key, final String... member) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_zrem(key, member);
}
});
return DynoDualWriterClient.super.zrem(key, member);
}
@Override
public List blpop(final int timeout, final String key) {
writeAsync(key, new Callable>>(){
@Override
public OperationResult> call() throws Exception {
return shadowClient.d_blpop(timeout, key);
}
});
return DynoDualWriterClient.super.blpop(timeout, key);
}
@Override
public List brpop(final int timeout, final String key) {
writeAsync(key, new Callable>>(){
@Override
public OperationResult> call() throws Exception {
return shadowClient.d_brpop(timeout, key);
}
});
return DynoDualWriterClient.super.brpop(timeout, key);
}
/******************* Jedis Dual write for binary commands **************/
@Override
public String set(final byte[] key, final byte[] value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_set(key, value);
}
});
return DynoDualWriterClient.super.set(key, value);
}
@Override
public String setex(final byte[] key, final int seconds, final byte[] value) {
writeAsync(key, new Callable>(){
@Override
public OperationResult call() throws Exception {
return shadowClient.d_setex(key, seconds, value);
}
});
return DynoDualWriterClient.super.setex(key, seconds, value);
}
}