/**
* 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.demo.redis;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import com.netflix.dyno.connectionpool.*;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import redis.clients.jedis.Response;
import com.netflix.dyno.connectionpool.Host.Status;
import com.netflix.dyno.connectionpool.impl.lb.HostToken;
import com.netflix.dyno.jedis.DynoJedisClient;
import com.netflix.dyno.jedis.DynoJedisPipeline;
public class DynoJedisDemo {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(DynoJedisDemo.class);
public static final String randomValue = "dcfa7d0973834e5c9f480b65de19d684dcfa7d097383dcfa7d0973834e5c9f480b65de19d684dcfa7d097383dcfa7d0973834e5c9f480b65de19d684dcfa7d097383dcfa7d0973834e5c9f480b65de19d684dcfa7d097383";
protected DynoJedisClient client;
protected int numKeys;
protected final String localRack;
protected final String clusterName;
public DynoJedisDemo(String clusterName, String localRack) {
this.clusterName = clusterName;
this.localRack = localRack;
}
public void initWithLocalHost() throws Exception {
final int port = 6379;
final Host localHost = new Host("localhost", port, "localrack", Status.Up);
final HostSupplier localHostSupplier = new HostSupplier() {
@Override
public Collection<Host> getHosts() {
return Collections.singletonList(localHost);
}
};
final TokenMapSupplier tokenSupplier = new TokenMapSupplier() {
final HostToken localHostToken = new HostToken(100000L, localHost);
@Override
public List<HostToken> getTokens(Set<Host> activeHosts) {
return Collections.singletonList(localHostToken);
}
@Override
public HostToken getTokenForHost(Host host, Set<Host> activeHosts) {
return localHostToken;
}
};
init(localHostSupplier, port, tokenSupplier);
}
private void initWithRemoteCluster(final List<Host> hosts, final int port) throws Exception {
final HostSupplier clusterHostSupplier = new HostSupplier() {
@Override
public Collection<Host> getHosts() {
return hosts;
}
};
init(clusterHostSupplier, port, null);
}
public void initWithRemoteClusterFromFile(final String filename, final int port) throws Exception {
initWithRemoteCluster(readHostsFromFile(filename, port), port);
}
public void initWithRemoteClusterFromEurekaUrl(final String clusterName, final int port) throws Exception {
initWithRemoteCluster(getHostsFromDiscovery(clusterName), port);
}
public void init(HostSupplier hostSupplier, int port, TokenMapSupplier tokenSupplier) throws Exception {
client = new DynoJedisClient.Builder()
.withApplicationName("demo")
.withDynomiteClusterName("dyno_dev")
.withHostSupplier(hostSupplier)
// .withCPConfig(
// new ConnectionPoolConfigurationImpl("demo")
//.setCompressionStrategy(ConnectionPoolConfiguration.CompressionStrategy.THRESHOLD)
//.setCompressionThreshold(2)
//.setLocalRack(this.localRack)
// )
.build();
}
public void runSimpleTest() throws Exception {
this.numKeys = 10;
// write
for (int i=0; i<numKeys; i++) {
System.out.println("Writing key/value => DynoClientTest-" + i + " / " + i);
client.set("DynoClientTest-" + i, "" + i);
}
// read
for (int i=0; i<numKeys; i++) {
OperationResult<String> result = client.d_get("DynoClientTest-"+i);
System.out.println("Reading Key: " + i + ", Value: " + result.getResult() + " " + result.getNode());
}
}
public void runPipelineEmptyResult() throws Exception {
DynoJedisPipeline pipeline = client.pipelined();
DynoJedisPipeline pipeline2 = client.pipelined();
try {
byte[] field1 = "field1".getBytes();
byte[] field2 = "field2".getBytes();
pipeline.hset("myHash".getBytes(), field1, "hello".getBytes());
pipeline.hset("myHash".getBytes(), field2, "world".getBytes());
Thread.sleep(1000);
Response<List<byte[]>> result = pipeline.hmget("myHash".getBytes(), field1, field2, "miss".getBytes());
pipeline.sync();
System.out.println("TEST-1: hmget for 2 valid results and 1 non-existent field");
for (int i=0; i < result.get().size(); i++) {
byte[] val = result.get().get(i);
if (val != null) {
System.out.println("TEST-1:Result => " + i + ") " + new String(val) );
} else {
System.out.println("TEST-1:Result => " + i + ") " + val);
}
}
} catch (Exception e) {
pipeline.discardPipelineAndReleaseConnection();
throw e;
}
try {
Response<List<byte[]>> result2 = pipeline2.hmget("foo".getBytes(), "miss1".getBytes(), "miss2".getBytes());
pipeline2.sync();
System.out.println("TEST-2: hmget when all fields (3) are not present in the hash");
if (result2.get() == null) {
System.out.println("TEST-2: result is null");
} else {
for (int i = 0; i < result2.get().size(); i++) {
System.out.println("TEST-2:" + Arrays.toString(result2.get().get(i)));
}
}
} catch (Exception e) {
pipeline.discardPipelineAndReleaseConnection();
throw e;
}
}
public void runKeysTest() throws Exception {
System.out.println("Writing 10,000 keys to dynomite...");
for (int i=0; i<500; i++) {
client.set("DynoClientTest_KEYS-TEST-key"+i, "value-"+i);
}
System.out.println("finished writing 10000 keys, querying for keys(\"DynoClientTest_KYES-TEST*\")");
Set<String> result = client.keys("DynoClientTest_KEYS-TEST*");
System.out.println("Got " + result.size() + " results, below");
System.out.println(result);
}
public void runScanTest(boolean populateKeys) throws Exception {
logger.info("SCAN TEST -- begin");
final String keyPattern = System.getProperty("dyno.demo.scan.key.pattern", "DynoClientTest_key-*");
final String keyPrefix = System.getProperty("dyno.demo.scan.key.prefix", "DynoClientTest_key-");
if (populateKeys) {
logger.info("Writing 500 keys to {} with prefix {}", this.clusterName, keyPrefix);
for (int i=0; i<500; i++) {
client.set(keyPrefix + i, "value-"+i);
}
}
logger.info("Reading keys from {} with pattern {}", this.clusterName, keyPattern);
CursorBasedResult<String> cbi = null;
do {
cbi = client.dyno_scan(cbi, 10000, keyPattern);
List<String> results = cbi.getStringResult();
for (String res: results) {
logger.info(res);
}
} while (!cbi.isComplete());
logger.info("SCAN TEST -- done");
}
public void cleanup(int nKeys) throws Exception {
// writes for initial seeding
for (int i=0; i<nKeys; i++) {
System.out.println("Deleting : " + i);
client.del("DynoDemoTest" + i);
}
}
public void runMultiThreaded() throws Exception {
this.runMultiThreaded(1000, true, 2, 2);
}
public void runMultiThreaded(final int items, boolean doSeed, final int numReaders, final int numWriters) throws Exception {
final int nKeys = items;
if (doSeed) {
// writes for initial seeding
for (int i = 0; i < nKeys; i++) {
System.out.println("Writing : " + i);
client.set("DynoDemoTest" + i, "" + i);
}
}
final int nThreads = numReaders + numWriters + 1;
final ExecutorService threadPool = Executors.newFixedThreadPool(nThreads);
final AtomicBoolean stop = new AtomicBoolean(false);
final CountDownLatch latch = new CountDownLatch(nThreads);
final AtomicInteger success = new AtomicInteger(0);
final AtomicInteger failure = new AtomicInteger(0);
final AtomicInteger emptyReads = new AtomicInteger(0);
startWrites(nKeys, numWriters, threadPool, stop, latch, success, failure);
startReads(nKeys, numReaders, threadPool, stop, latch, success, failure, emptyReads);
threadPool.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
while (!stop.get()) {
System.out.println("Success: " + success.get() + ", failure: " + failure.get() + ", emptyReads: " + emptyReads.get());
Thread.sleep(1000);
}
latch.countDown();
return null;
}
});
Thread.sleep(15 * 1000);
stop.set(true);
latch.await();
threadPool.shutdownNow();
executePostRunActions();
System.out.println("Cleaning up keys");
cleanup(nKeys);
System.out.println("FINAL RESULT \nSuccess: " + success.get() + ", failure: " + failure.get() + ", emptyReads: " + emptyReads.get());
}
protected void executePostRunActions() {
// nothing to do here
}
protected void startWrites(final int nKeys, final int numWriters,
final ExecutorService threadPool,
final AtomicBoolean stop, final CountDownLatch latch,
final AtomicInteger success, final AtomicInteger failure) {
for (int i=0; i<numWriters; i++) {
threadPool.submit(new Callable<Void>() {
final Random random = new Random();
@Override
public Void call() throws Exception {
while (!stop.get()) {
int key = random.nextInt(nKeys);
int value = random.nextInt(nKeys);
try {
client.set("DynoDemoTest"+key, ""+value);
success.incrementAndGet();
} catch (Exception e) {
System.out.println("WRITE FAILURE: " + e.getMessage());
failure.incrementAndGet();
}
}
latch.countDown();
return null;
}
});
}
}
protected void startReads(final int nKeys, final int numReaders,
final ExecutorService threadPool,
final AtomicBoolean stop, final CountDownLatch latch,
final AtomicInteger success, final AtomicInteger failure, final AtomicInteger emptyReads) {
for (int i=0; i<numReaders; i++) {
threadPool.submit(new Callable<Void>() {
final Random random = new Random();
@Override
public Void call() throws Exception {
while (!stop.get()) {
int key = random.nextInt(nKeys);
try {
String value = client.get("DynoDemoTest"+key);
success.incrementAndGet();
if (value == null || value.isEmpty()) {
emptyReads.incrementAndGet();
}
} catch (Exception e) {
System.out.println("READ FAILURE: " + e.getMessage());
failure.incrementAndGet();
}
}
latch.countDown();
return null;
}
});
}
}
public void stop() {
if (client != null) {
client.stopClient();
}
}
private List<Host> readHostsFromFile(String filename, int port) throws Exception {
List<Host> hosts = new ArrayList<Host>();
File file = new File(filename);
BufferedReader reader = new BufferedReader(new FileReader(file));
try {
String line = null;
while ((line = reader.readLine()) != null) {
if (line.trim().isEmpty()) {
continue;
}
String[] parts = line.trim().split(" ");
if (parts.length != 2) {
throw new RuntimeException("Bad data format in file:" + line);
}
Host host = new Host(parts[0].trim(), port, parts[1].trim(), Status.Up);
hosts.add(host);
}
} finally {
reader.close();
}
return hosts;
}
public void runBinarySinglePipeline() throws Exception {
for (int i=0; i<10; i++) {
DynoJedisPipeline pipeline = client.pipelined();
Map<byte[], byte[]> bar = new HashMap<byte[], byte[]>();
bar.put("key__1".getBytes(), "value__1".getBytes());
bar.put("key__2".getBytes(), "value__2".getBytes());
Response<String> hmsetResult = pipeline.hmset(("hash__" + i).getBytes(), bar);
pipeline.sync();
System.out.println(hmsetResult.get());
}
System.out.println("Reading all keys");
DynoJedisPipeline readPipeline = client.pipelined();
Response<Map<byte[], byte[]>> resp = readPipeline.hgetAll("hash__1".getBytes());
readPipeline.sync();
StringBuilder sb = new StringBuilder();
for (byte[] bytes: resp.get().keySet()) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(new String(bytes));
}
System.out.println("Got hash :" + sb.toString());
}
public void runSinglePipelineWithCompression(boolean useBinary) throws Exception {
for (int i=0; i<3; i++) {
DynoJedisPipeline pipeline = client.pipelined();
// Map
// Map<String, String> map = new HashMap<String, String>();
// String value1 = generateValue(3);
// String value2 = generateValue(4);
// map.put("key__1", value1);
// map.put("key__2", value2);
// Response<String> hmsetResult = pipeline.hmset(("hash__" + i).getBytes(), bar);
// Strings
String value1 = generateValue(3);
Response<String> resp = pipeline.set("DynoJedisDemo__key__" + i, value1);
pipeline.sync();
System.out.println(resp.get());
}
System.out.println("Reading keys");
for (int i=0; i<3; i++) {
DynoJedisPipeline readPipeline = client.pipelined();
Response<String> resp = readPipeline.get("DynoJedisDemo__key__" + i);
readPipeline.sync();
System.out.println("Result => " + i + ") " + resp.get());
}
}
public void runSandboxTest() throws Exception {
Set<String> keys = client.keys("zuulRules:*");
System.out.println("GOT KEYS");
System.out.println(keys.size());
}
private static String generateValue(int kilobytes) {
StringBuilder sb = new StringBuilder(kilobytes * 512); // estimating 2 bytes per char
for (int i = 0; i < kilobytes; i++) {
for (int j = 0; j < 10; j++) {
sb.append("abcdefghijklmnopqrstuvwxzy0123456789a1b2c3d4f5g6h7"); // 50 characters (~100 bytes)
sb.append(":");
sb.append("abcdefghijklmnopqrstuvwxzy0123456789a1b2c3d4f5g6h7");
sb.append(":");
}
}
return sb.toString();
}
public void runPipeline() throws Exception {
int numThreads = 5;
final ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);
final AtomicBoolean stop = new AtomicBoolean(false);
final CountDownLatch latch = new CountDownLatch(numThreads);
for (int i=0; i<numThreads; i++) {
threadPool.submit(new Callable<Void>() {
final Random rand = new Random();
@Override
public Void call() throws Exception {
final AtomicInteger iter = new AtomicInteger(0);
while (!stop.get()) {
int index = rand.nextInt(5);
int i = iter.incrementAndGet();
DynoJedisPipeline pipeline = client.pipelined();
try {
Response<Long> resultA1 = pipeline.hset("DynoJedisDemo_pipeline-" + index, "a1", constructRandomValue(index));
Response<Long> resultA2 = pipeline.hset("DynoJedisDemo_pipeline-" + index, "a2", "value-" + i);
pipeline.sync();
System.out.println(resultA1.get() + " " + resultA2.get());
} catch (Exception e) {
pipeline.discardPipelineAndReleaseConnection();
throw e;
}
}
latch.countDown();
return null;
}
});
}
Thread.sleep(5000);
stop.set(true);
latch.await();
threadPool.shutdownNow();
}
private String constructRandomValue(int sizeInKB) {
int requriredLength = sizeInKB * 1024;
String s = randomValue;
int sLength = s.length();
StringBuilder sb = new StringBuilder();
int lengthSoFar = 0;
do {
sb.append(s);
lengthSoFar += sLength;
} while (lengthSoFar < requriredLength);
String ss = sb.toString();
if(ss.length()>requriredLength) {
ss= sb.substring(0,requriredLength);
}
return ss;
}
private List<Host> getHostsFromDiscovery(final String clusterName) {
String env = System.getProperty("netflix.environment", "test");
String discoveryKey = String.format("dyno.demo.discovery.%s", env);
if (!System.getProperties().containsKey(discoveryKey)) {
throw new IllegalArgumentException("Discovery URL not found");
}
String region = System.getProperty("EC2_REGION");
final String discoveryUrl = String.format(System.getProperty(discoveryKey), region);
final String url = String.format("http://%s/%s", discoveryUrl, clusterName);
HttpClient client = new DefaultHttpClient();
try {
HttpResponse response = client.execute(new HttpGet(url));
InputStream in = response.getEntity().getContent();
SAXParserFactory parserFactor = SAXParserFactory.newInstance();
SAXParser parser = parserFactor.newSAXParser();
SAXHandler handler = new SAXHandler("instance", "public-hostname", "availability-zone", "status", "local-ipv4");
parser.parse(in, handler);
List<Host> hosts = new ArrayList<Host>();
for (Map<String, String> map : handler.getList()) {
String rack = map.get("availability-zone");
Status status = map.get("status").equalsIgnoreCase("UP") ? Status.Up : Status.Down;
Host host = new Host(map.get("public-hostname"), map.get("local-ipv4"), rack, status);
hosts.add(host);
System.out.println("Host: " + host);
}
return hosts;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
public void runLongTest() throws InterruptedException {
final ExecutorService threadPool = Executors.newFixedThreadPool(2);
final AtomicBoolean stop = new AtomicBoolean(false);
final CountDownLatch latch = new CountDownLatch(2);
final AtomicInteger success = new AtomicInteger(0);
final AtomicInteger failure = new AtomicInteger(0);
final AtomicInteger emptyReads = new AtomicInteger(0);
threadPool.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
while (!stop.get()) {
System.out.println("Getting Value for key '0'");
String value = client.get("0");
System.out.println("Got Value for key '0' : " + value);
Thread.sleep(5000);
}
latch.countDown();
return null;
}
});
threadPool.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
while (!stop.get()) {
System.out.println("Success: " + success.get() + ", failure: " + failure.get() + ", emptyReads: " + emptyReads.get());
Thread.sleep(1000);
}
latch.countDown();
return null;
}
});
Thread.sleep(60*1000);
stop.set(true);
latch.await();
threadPool.shutdownNow();
}
private class SAXHandler extends DefaultHandler {
private final List<Map<String, String>> list = new ArrayList<Map<String, String>>();
private final String rootElement;
private final Set<String> interestElements = new HashSet<String>();
private Map<String, String> currentPayload = null;
private String currentInterestElement = null;
private SAXHandler(String root, String ... interests) {
rootElement = root;
for (String s : interests) {
interestElements.add(s);
}
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (qName.equalsIgnoreCase(rootElement)) {
// prep for next instance
currentPayload = new HashMap<String, String>();
return;
}
if (interestElements.contains(qName)) {
// note the element to be parsed. this will be used in chars callback
currentInterestElement = qName;
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// add host to list
if (qName.equalsIgnoreCase(rootElement)) {
list.add(currentPayload);
currentPayload = null;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String value = new String(ch, start, length);
if (currentInterestElement != null && currentPayload != null) {
currentPayload.put(currentInterestElement, value);
currentInterestElement = null;
}
}
public List<Map<String, String>> getList() {
return list;
}
};
/**
*
* @param args Should contain:
* <ol>dynomite_cluster_name, e.g. dyno_sandbox_quorum</ol>
* <ol>
* test number, in the set:
* 1 - simple test
* 2 - keys test
* 3 - multi-threaded
* 4 - scan test
* 5 - pipeline
* </ol>
*/
public static void main(String args[]) throws IOException {
if (args.length < 2) {
System.out.println("Incorrect number of arguments.");
printUsage();
System.exit(1);
}
String clusterName = args[0];
int testNumber = Integer.valueOf(args[1]);
Properties props = new Properties();
props.load(DynoJedisDemo.class.getResourceAsStream("/demo.properties"));
for (String name: props.stringPropertyNames()) {
System.setProperty(name, props.getProperty(name));
}
if (!props.containsKey("EC2_AVAILABILITY_ZONE") && !props.containsKey("dyno.dyno_demo.lbStrategy")) {
throw new IllegalArgumentException("MUST set local for load balancing OR set the load balancing strategy to round robin");
}
String rack = props.getProperty("EC2_AVAILABILITY_ZONE", "us-east-1e");
String hostsFile = props.getProperty("dyno.demo.hostsFile");
int port = Integer.valueOf(props.getProperty("dyno.demo.port", "8102"));
DynoJedisDemo demo = new DynoJedisDemo(clusterName, rack);
try {
if (hostsFile != null) {
demo.initWithRemoteClusterFromFile(hostsFile, port);
} else {
demo.initWithRemoteClusterFromEurekaUrl(clusterName, port);
}
System.out.println("Connected");
switch (testNumber) {
case 1: {
demo.runSimpleTest();
break;
}
case 2: {
demo.runKeysTest();
break;
}
case 3: {
demo.runMultiThreaded();
break;
}
case 4: {
final boolean writeKeys = Boolean.valueOf(props.getProperty("dyno.demo.scan.populateKeys"));
demo.runScanTest(writeKeys);
break;
}
case 5: {
demo.runPipeline();
break;
}
}
//demo.runSinglePipeline();
//demo.runPipeline();
//demo.runBinarySinglePipeline();
//demo.runPipelineEmptyResult();
//demo.runSinglePipelineWithCompression(false);
//demo.runLongTest();
//demo.runSandboxTest();
Thread.sleep(1000);
demo.cleanup(demo.numKeys);
} catch (Throwable e) {
e.printStackTrace();
} finally {
demo.stop();
System.out.println("Done");
System.out.flush();
System.err.flush();
System.exit(0);
}
}
protected static void printUsage() {
// todo
}
}