package io.rebloom.client; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.*; import redis.clients.jedis.Client; import redis.clients.jedis.commands.ProtocolCommand; import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.util.SafeEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; /** * @author TommyYang on 2018/12/17 */ public class ClusterClient extends JedisCluster { public ClusterClient(HostAndPort node) { super(node); } public ClusterClient(HostAndPort node, int timeout) { super(node, timeout); } public ClusterClient(HostAndPort node, int timeout, int maxAttempts) { super(node, timeout, maxAttempts); } public ClusterClient(HostAndPort node, GenericObjectPoolConfig poolConfig) { super(node, poolConfig); } public ClusterClient(HostAndPort node, int timeout, GenericObjectPoolConfig poolConfig) { super(node, timeout, poolConfig); } public ClusterClient(HostAndPort node, int timeout, int maxAttempts, GenericObjectPoolConfig poolConfig) { super(node, timeout, maxAttempts, poolConfig); } public ClusterClient(HostAndPort node, int connectionTimeout, int soTimeout, int maxAttempts, GenericObjectPoolConfig poolConfig) { super(node, connectionTimeout, soTimeout, maxAttempts, poolConfig); } public ClusterClient(HostAndPort node, int connectionTimeout, int soTimeout, int maxAttempts, String password, GenericObjectPoolConfig poolConfig) { super(node, connectionTimeout, soTimeout, maxAttempts, password, poolConfig); } public ClusterClient(HostAndPort node, int connectionTimeout, int soTimeout, int maxAttempts, String password, String clientName, GenericObjectPoolConfig poolConfig) { super(node, connectionTimeout, soTimeout, maxAttempts, password, clientName, poolConfig); } public ClusterClient(Set<HostAndPort> nodes) { super(nodes); } public ClusterClient(Set<HostAndPort> nodes, int timeout) { super(nodes, timeout); } public ClusterClient(Set<HostAndPort> nodes, int timeout, int maxAttempts) { super(nodes, timeout, maxAttempts); } public ClusterClient(Set<HostAndPort> nodes, GenericObjectPoolConfig poolConfig) { super(nodes, poolConfig); } public ClusterClient(Set<HostAndPort> nodes, int timeout, GenericObjectPoolConfig poolConfig) { super(nodes, timeout, poolConfig); } public ClusterClient(Set<HostAndPort> jedisClusterNode, int timeout, int maxAttempts, GenericObjectPoolConfig poolConfig) { super(jedisClusterNode, timeout, maxAttempts, poolConfig); } public ClusterClient(Set<HostAndPort> jedisClusterNode, int connectionTimeout, int soTimeout, int maxAttempts, GenericObjectPoolConfig poolConfig) { super(jedisClusterNode, connectionTimeout, soTimeout, maxAttempts, poolConfig); } public ClusterClient(Set<HostAndPort> jedisClusterNode, int connectionTimeout, int soTimeout, int maxAttempts, String password, GenericObjectPoolConfig poolConfig) { super(jedisClusterNode, connectionTimeout, soTimeout, maxAttempts, password, poolConfig); } public ClusterClient(Set<HostAndPort> jedisClusterNode, int connectionTimeout, int soTimeout, int maxAttempts, String password, String clientName, GenericObjectPoolConfig poolConfig) { super(jedisClusterNode, connectionTimeout, soTimeout, maxAttempts, password, clientName, poolConfig); } private void sendCommand(Connection conn, String key, ProtocolCommand command, String ...args) { String[] fullArgs = new String[args.length + 1]; fullArgs[0] = key; System.arraycopy(args, 0, fullArgs, 1, args.length); conn.sendCommand(command, fullArgs); } /** * Reserve a bloom filter. * @param name The key of the filter * @param initCapacity Optimize for this many items * @param errorRate The desired rate of false positives * * @return true if the filter create success, false if the filter create error. * * Note that if a filter is not reserved, a new one is created when {@link #add(String, byte[])} * is called. */ public boolean createFilter(String name, long initCapacity, double errorRate) { return (new JedisClusterCommand<Boolean>(this.connectionHandler, this.maxAttempts) { public Boolean execute(Jedis connection) { Connection conn = connection.getClient(); conn.sendCommand(Command.RESERVE, name, errorRate + "", initCapacity + ""); return conn.getStatusCodeReply().equals("OK"); } }).run(name); } /** * Adds an item to the filter * @param name The name of the filter * @param value The value to add to the filter * @return true if the item was not previously in the filter. */ public boolean add(String name, String value) { return (new JedisClusterCommand<Boolean>(this.connectionHandler, this.maxAttempts) { public Boolean execute(Jedis connection) { Connection conn = connection.getClient(); conn.sendCommand(Command.ADD, name, value); return conn.getIntegerReply() != 0; } }).run(name); } /** * Like {@link #add(String, String)}, but allows you to store non-string items * @param name Name of the filter * @param value Value to add to the filter * @return true if the item was not previously in the filter */ public boolean add(String name, byte[] value) { return (new JedisClusterCommand<Boolean>(this.connectionHandler, this.maxAttempts) { public Boolean execute(Jedis connection) { Connection conn = connection.getClient(); conn.sendCommand(Command.ADD, name.getBytes(), value); return conn.getIntegerReply() != 0; } }).run(name); } /** * Check if an item exists in the filter * @param name Name (key) of the filter * @param value Value to check for * @return true if the item may exist in the filter, false if the item does not exist in the filter */ public boolean exists(String name, String value) { return (new JedisClusterCommand<Boolean>(this.connectionHandler, this.maxAttempts) { public Boolean execute(Jedis connection) { Connection conn = connection.getClient(); conn.sendCommand(Command.EXISTS, name, value); return conn.getIntegerReply() != 0; } }).run(name); } /** * Check if an item exists in the filter. Similar to {@link #exists(String, String)} * @param name Key of the filter to check * @param value Value to check for * @return true if the item may exist in the filter, false if the item does not exist in the filter. */ public boolean exists(String name, byte[] value) { return (new JedisClusterCommand<Boolean>(this.connectionHandler, this.maxAttempts) { public Boolean execute(Jedis connection) { Connection conn = connection.getClient(); conn.sendCommand(Command.EXISTS, name.getBytes(), value); return conn.getIntegerReply() != 0; } }).run(name); } /** * Add one or more items to a filter * @param name Name of the filter * @param values values to add to the filter. * @return An array of booleans of the same length as the number of values. * Each boolean values indicates whether the corresponding element was previously in the * filter or not. A true value means the item did not previously exist, whereas a * false value means it may have previously existed. * * @see #add(String, String) */ public boolean[] addMulti(String name, byte[]... values){ return (new JedisClusterCommand<boolean[]>(this.connectionHandler, this.maxAttempts) { public boolean[] execute(Jedis connection) { Connection conn = connection.getClient(); return sendMultiCommand(conn, Command.MADD, name.getBytes(), values); } }).run(name); } public boolean[] addMulti(String name, String... values){ return (new JedisClusterCommand<boolean[]>(this.connectionHandler, this.maxAttempts) { public boolean[] execute(Jedis connection) { Connection conn = connection.getClient(); return sendMultiCommand(conn, Command.MADD, name, values); } }).run(name); } /** * Check if one or more items exist in the filter * @param name Name of the filter to check * @param values values to check for * @return An array of booleans. A true value means the corresponding value may exist, false means it does not exist */ public boolean[] existsMulti(String name, byte[]... values) { return (new JedisClusterCommand<boolean[]>(this.connectionHandler, this.maxAttempts) { public boolean[] execute(Jedis connection) { Connection conn = connection.getClient(); return sendMultiCommand(conn, Command.MEXISTS, name.getBytes(), values); } }).run(name); } public boolean[] existsMulti(String name, String... values) { return (new JedisClusterCommand<boolean[]>(this.connectionHandler, this.maxAttempts) { public boolean[] execute(Jedis connection) { Connection conn = connection.getClient(); return sendMultiCommand(conn, Command.MEXISTS, name, values); } }).run(name); } /** * Remove the filter * @param name * @return true if delete the filter, false is not delete the filter */ public boolean delete(String name) { return (new JedisClusterCommand<Boolean>(this.connectionHandler, this.maxAttempts) { public Boolean execute(Jedis connection) { Connection conn = connection.getClient(); ((Client) conn).del(name); return conn.getIntegerReply() != 0; } }).run(name); } /** * TOPK.RESERVE key topk width depth decay * * Reserve a topk filter. * @param key The key of the filter * @param topk * @param width * @param depth * @param decay * * Note that if a filter is not reserved, a new one is created when {@link #add(String, byte[])} * is called. */ public void topkCreateFilter(String key, long topk, long width, long depth, double decay) { (new JedisClusterCommand<Void>(this.connectionHandler, this.maxAttempts){ @Override public Void execute(Jedis jedis) { Connection conn = jedis.getClient(); conn.sendCommand(TopKCommand.RESERVE, SafeEncoder.encode(key), Protocol.toByteArray(topk), Protocol.toByteArray(width), Protocol.toByteArray(depth),Protocol.toByteArray(decay)); String resp = conn.getStatusCodeReply(); if (!resp.equals("OK")){ throw new JedisException(resp); } return null; } }).run(key); } /** * TOPK.ADD key item [item ...] * * Adds an item to the filter * @param key The key of the filter * @param items The items to add to the filter * @return list of items dropped from the list. */ public List<String> topkAdd(String key, String ...items) { return (new JedisClusterCommand<List<String>>(this.connectionHandler, this.maxAttempts){ @Override public List<String> execute(Jedis jedis) { Connection conn = jedis.getClient(); sendCommand(conn, key, TopKCommand.ADD, items); return conn.getMultiBulkReply(); } }).run(key); } /** * TOPK.INCRBY key item increment [item increment ...] * * Adds an item to the filter * @param key The key of the filter * @param item The item to increment * @return item dropped from the list. */ public String topkIncrBy(String key, String item, long increment) { return (new JedisClusterCommand<String>(this.connectionHandler, this.maxAttempts){ @Override public String execute(Jedis jedis) { Connection conn = jedis.getClient(); conn.sendCommand(TopKCommand.INCRBY, SafeEncoder.encode(key), SafeEncoder.encode(item), Protocol.toByteArray(increment)); return conn.getMultiBulkReply().get(0); } }).run(key); } /** * TOPK.QUERY key item [item ...] * * Checks whether an item is one of Top-K items. * * @param key The key of the filter * @param items The items to check in the list * @return list of indicator for each item requested */ public List<Boolean> topkQuery(String key, String ...items) { return (new JedisClusterCommand<List<Boolean>>(this.connectionHandler, this.maxAttempts){ @Override public List<Boolean> execute(Jedis jedis) { Connection conn = jedis.getClient(); sendCommand(conn, key, TopKCommand.QUERY, items); return conn.getIntegerMultiBulkReply() .stream() .map(s -> s != 0) .collect(Collectors.toList()); } }).run(key); } /** * TOPK.COUNT key item [item ...] * * Returns count for an item. * * @param key The key of the filter * @param items The items to check in the list * @return list of counters per item. */ public List<Long> topkCount(String key, String ...items) { return (new JedisClusterCommand<List<Long>>(this.connectionHandler, this.maxAttempts){ @Override public List<Long> execute(Jedis jedis) { Connection conn = jedis.getClient(); sendCommand(conn, key, TopKCommand.COUNT, items); return conn.getIntegerMultiBulkReply(); } }).run(key); } /** * TOPK.LIST key * * Return full list of items in Top K list. * * @param key The key of the filter * @return list of items in the list. */ public List<String> topkList(String key) { return (new JedisClusterCommand<List<String>>(this.connectionHandler, this.maxAttempts){ @Override public List<String> execute(Jedis jedis) { Connection conn = jedis.getClient(); conn.sendCommand(TopKCommand.LIST, key); return conn.getMultiBulkReply(); } }).run(key); } @SafeVarargs private final <T> boolean[] sendMultiCommand(Connection conn, Command cmd, T name, T... value) { ArrayList<T> arr = new ArrayList<>(); arr.add(name); arr.addAll(Arrays.asList(value)); List<Long> reps; if (name instanceof String) { conn.sendCommand(cmd, (String[]) arr.toArray((String[]) value)); reps = conn.getIntegerMultiBulkReply(); } else { conn.sendCommand(cmd, (byte[][]) arr.toArray((byte[][]) value)); reps = conn.getIntegerMultiBulkReply(); } boolean[] ret = new boolean[value.length]; for (int i = 0; i < reps.size(); i++) { ret[i] = reps.get(i) != 0; } return ret; } }