Uh oh!
There was an error while loading. Please reload this page.
[ZEPPELIN-1233] Support Redis interpreter - #1497
Conversation
cca1102 to
adb7818Compare
anthonycorbacho
left a comment
There was a problem hiding this comment.
Thank you for bringing Redis Interpreter! this is awesome!
I did a quick review on your code please check it and let me know what you think.
| private Jedis client; | ||
| private enum RedisCommand { |
There was a problem hiding this comment.
As a redis user, i am expecting to use other redis command like hkeys, range and stuff.
Maybe its a good idea to add more here or come up with a better approach ?
There was a problem hiding this comment.
Actually I'm not sure is there any good way to running redis command so I just simply create GET and SET :)
There was a problem hiding this comment.
maybe I can use redis.clients.jedis.Protocol.Command to replacement RedisCommand
There was a problem hiding this comment.
yes, i think its better to use this enum
i was thinking about doing something like:
private final HashSet<String> redisCommands;
public static HashSet<String> getRedisCommands() {
HashSet<String> hashSetOfRedisCommands = new HashSet<String>();
for (Command c : Command.values()) {
hashSetOfRedisCommands.add(c.name());
}
return hashSetOfRedisCommands;
}
public RedisInterpreter(Properties property) {
super(property);
redisCommands = getRedisCommands();
}
// then use this like redisCommands.contains(tokens[1]);
what do you think?
There was a problem hiding this comment.
Maybe we don't need a redis commands set to check whether the input command is illegal because there is a default case in switch . Is it a better one?
String[] tokens = splitTokens(input);
String keyword = tokens[0];
switch (Protocol.Command.valueOf(keyword)) {
case GET:
result = getProcess();
break;
case SET:
result = setProcess();
break;
default:
result = new InterpreterResult(ERROR, "key word not found");
break;
}
| @Override | ||
| public void open() { | ||
| client = new Jedis(); |
There was a problem hiding this comment.
I think here we should provide a better Redis client creation, as far as i know, this will assume that you are running redis locally, i think we can exploit more the interpreter setting creation, here an example with JDBC.
So user will be able to set host, port and credential.
Also how about using JedisPool instead of Jedis?
It will look like something like this
private static final int MAX_CONNECTION = 142;
private static final int MAX_IDLE = 10;
private static final int MIN_IDLE = 142;
private static final long MAX_WAIT = TimeUnit.SECONDS.toMillis(30);
private JedisPool jedisPool;
public void open() {
jedisPool = new JedisPool(getPoolConfiguration(), property.getCacheHost(), property.getCachePort());
}
private JedisPoolConfig getPoolConfiguration() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(MAX_CONNECTION);
config.setMaxIdle(MAX_IDLE);
config.setMinIdle(MIN_IDLE);
config.setMaxWaitMillis(MAX_WAIT);
return config;
}
// Utils method :)
private Jedis borrow() {
return jedisPool.getResource();
}
// Then you can use it like
private InterpreterResult getProcess(String key) {
Jedis jedis = borrow();
Pipeline pipe = jedis.pipelined();
Response<String> response = pipe.get(key);
pipe.sync();
jedis.close();
String result = response.get();
return new InterpreterResult(SUCCESS, result);
}
There was a problem hiding this comment.
I read Jedis source code and find a strange case . The returnResource will be deprecated after 3.0 and the resource shoule be close after usage (don't return into source pool ) . Now I'm using 2.9 . I'm not sure it's a good idea .
There was a problem hiding this comment.
Maybe using zeppelin to access Redis will not very frequently so create jedis client after it close is OK .
There was a problem hiding this comment.
I think its better to use a Pool for this job, you only need jedis instance when you actually run the jobs, that is whay i am asking for changing from Redis to JedisPool, and in the doc i didnt find the notice about removing returnResource.
| @@ -0,0 +1,98 @@ | |||
| package org.apache.zeppelin.redis; | |||
There was a problem hiding this comment.
ASF Header is missing here.
| import static org.apache.zeppelin.interpreter.InterpreterResult.Code.ERROR; | ||
| import static org.apache.zeppelin.interpreter.InterpreterResult.Code.SUCCESS; | ||
| public class RedisInterpreter extends Interpreter { |
There was a problem hiding this comment.
Could you also add the class header with a description of this interpreter?
This will help futur contributors.
| public class RedisInterpreter extends Interpreter { | ||
| private Logger LOG = LoggerFactory.getLogger(RedisInterpreter.class); |
There was a problem hiding this comment.
Can we change this field for private static final Logger LOG = LoggerFactory.getLogger(RedisInterpreter.class);?
this will help to have one logger instance :)
f40e716 to
ecfa1b2Comparefelixcheung
commented
Oct 9, 2016
Hi - thank you for your contribution. Could you see the list of steps here: Notably, could you add:
|
anthonycorbacho
commented
Oct 12, 2016
@darionyaphet do you need some help for the change? |
39e42ef to
244fe85Comparedarionyaphet
commented
Oct 12, 2016
Hi @anthonycorbacho thank you for you great help and I'm still working on it . I have implement a lot of command include |
244fe85 to
5b60b8aCompare| String keyword = tokens[0]; | ||
| InterpreterResult result; | ||
| switch (Protocol.Command.valueOf(keyword)) { |
There was a problem hiding this comment.
Humm, that is a very massive switch case, i think we can come up with a more elegant way to to this kind of things,
since it mainly take the same argument String[] token, i guess we can go with the design pattern Strategy Pattern.
This idea here is to extract the logic to smaller classes and register then to a map.
eg:
// In the constructor
Map<Protocol.Command, Command> redisCommand = Maps.newHashMap<>();
// register command and class
redisCommand.put(GEOADD, GeoAdd.createInstance());
... // add other command
// create a java package org.apache.zeppelin.redis.cmd and add every implementation of redis cmd.
// but first you need to create a interface
// eg:
public interface Cmd {
public boolean execute(String[] tokens);
}
// use this interface to implement the actual cmd
public class MyRedisCmd implements Cmd {
public static MyRedisCmd createInstance() {
return new MyredisCmd();
}
@Override
public boolean execute(String[] tokens) {
// do the work here
}
}
// Once you have implemented the redis cmd, you can use this like this:
redisCommand.get(Protocol.Command.valueOf(keyword)).execute(tokens);
let me know what you think
There was a problem hiding this comment.
@anthonycorbacho@kavinkumarks thanks for you reply with great suggest 👍 . Using Strategy Pattern to execute Redis Command will take me make a lot of classes , each Redis Command should have a class to express it . I'm not sure it's a good idea maybe the long switch list is more clearly to execute the simple redis commands in a single Java class .
what do you think :)
PS : I will append some test case about Redis Command later .
There was a problem hiding this comment.
AFAIK, what @anthonycorbacho suggested seems more like a command pattern. (since it is about the action - what to do, not the algorithm - how to do)
Anyway +1 to apply what @anthonycorbacho suggested. Having more classes is not worse than having too long single class, (and also too long switch statement) especially they can be organized by package. We can even use static inner class to reduce the file count if we really want.
Btw, to be clear, I was the other one who want to make the Redis interpreter but at the design phase I was stuck on converting Redis command statement to Jedis method call.
(I was struggling to find a way to not having static mapping like this, but that was not easy as I thought.)
I will come up with clearer way sooner or later if possible.
There was a problem hiding this comment.
Personally, I would prefer to have classes (will make test more easy) than a massive switch case. the main concern i have about big switch case tis that it is really hard to maintain and make the parent class huge. by de coupling the logic into smaller classes will improve the maintainability and make testability more easy.
There was a problem hiding this comment.
@anthonycorbacho@HeartSaVioR It's seems a good idea to split execute Redis Command into classes and this command classes will reused in Redis Cluster Interpreter. I have create a abstract class RedisCommand as a basic class :
public abstract class RedisCommand {
private JedisPool clientPool;
public RedisCommand(JedisPool clientPool) {
this.clientPool = clientPool;
}
private Jedis borrowClient() {
return clientPool.getResource();
}
private void releaseClient(Jedis client) {
client.close();
}
public InterpreterResult execute(String[] tokens) {
Jedis client = borrowClient();
try {
return execute(client, tokens);
} catch (JedisException exception) {
return new InterpreterResult(ERROR, exception.getMessage());
} finally {
releaseClient(client);
}
}
/**
* @param tokens
* @return
* @throws Exception
*/
protected abstract InterpreterResult execute(Jedis client, String[] tokens);
}
I think it's OK and each redis command could extend this one .
kavinkumarks
commented
Oct 14, 2016
I too checked the changes and they look good.The comment from @anthonycorbacho about usage of "Strategy pattern" for selecting the relevant redis command instance and executing them is the better way to implement it. Also, could we add some test cases for this? Thanks, |
HeartSaVioR
commented
Oct 14, 2016
Every command methods have a chance to leak Jedis instance when command is failing and throwing a kind of JedisException. |
anthonycorbacho
commented
Oct 16, 2016
@HeartSaVioR thank you for pointing that out, you are right we better handle the potential |
5ad9542 to
87a22cbComparedarionyaphet
commented
Oct 18, 2016
Hi @anthonycorbacho@HeartSaVioR I have update Please help review this one . If anything is OK , I will clean the commented code and completion other redis commands . Thanks ~~ :) |
e1c2f6c to
3b93b3eCompare
anthonycorbacho
left a comment
There was a problem hiding this comment.
Great to see this interpreter improving day by day!!
@HeartSaVioR what do you think?
| return (String[]) list.toArray(); | ||
| } | ||
| // ////////////////////////// KEYS ////////////////////////// |
There was a problem hiding this comment.
Better to remove comment here if there are not intentions of using the code below.
There was a problem hiding this comment.
Hi I still working on Key commands . I will clean the unnecessary code after finished this PR :)
| try { | ||
| return new InterpreterResult(SUCCESS, execute(client, tokens)); | ||
| } catch (JedisException exception) { | ||
| return new InterpreterResult(ERROR, exception.getMessage()); |
There was a problem hiding this comment.
What do you think about adding a LOG.error here? something likeLOG.error("Couldn't execute redis command {}", token[0], e);
| } | ||
| } | ||
| } | ||
| ] No newline at end of file |
There was a problem hiding this comment.
Need a new line here :)
| client.close(); | ||
| } | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
Need a new line here :)
| if (COMMANDS.containsKey(command)) { | ||
| result = COMMANDS.get(command).execute(tokens); | ||
| } else { | ||
| result = new InterpreterResult(ERROR, "keyword not found"); |
There was a problem hiding this comment.
Maybe we can provide a better error to the user likeKeyword " + tokens + " is not supportted?
What do you think?
There was a problem hiding this comment.
I left review comments but overall direction looks good unless we find brilliant idea to reduce verbose.
Please note that I couldn't check actual call of Jedis method since they're too many. I would like to see all tests instead, but tests don't necessary need to have files for each command.
Some of Jedis methods can return null (for example, if there's no matching result) which should be handled properly. Some of implementations of RedisCommands might throw NPE in some cases since you manipulate the result without null check. I'm not sure interpreter result type NULL is for these case.
@anthonycorbacho Could you clarify above this?
May be better to have generic return type for RedisCommand, and let RedisInterpreter handles null and also type-aware result.
| md org.apache.zeppelin:zeppelin-markdown:0.6.1 Markdown support | ||
| postgresql org.apache.zeppelin:zeppelin-postgresql:0.6.1 Postgresql interpreter | ||
| python org.apache.zeppelin:zeppelin-python:0.6.1 Python interpreter | ||
| redis org.apache.zeppelin:zeppelin-redis:0.6.0 Redis interpreter |
| <version>0.7.0-SNAPSHOT</version> | ||
| <properties> | ||
| <redis.version>2.9.0</redis.version> |
There was a problem hiding this comment.
It can make unneeded confusing. jedis.version would be better.
| LOG.info("Connection to %s:%d", host, port); | ||
| // TODO(darion) : Need more config ? | ||
| config = new JedisPoolConfig(); |
There was a problem hiding this comment.
Since Redis interpreter doesn't need to be super fast at all, testOnBorrow() can relieve the connection issue with ignorable overhead.
| import static org.apache.zeppelin.interpreter.InterpreterResult.Code.SUCCESS; | ||
| /** | ||
| * Redis BLPOPCommand |
| } | ||
| public InterpreterResult execute(String[] tokens) { | ||
| Jedis client = borrowClient(); |
There was a problem hiding this comment.
You can use try-with-resource:
try (Jedis client = clientPool.getResource()) {
...
}
No need to have borrowClient() and releaseClient(), and also no need to explicitly handling closing resource.
| try { | ||
| return new InterpreterResult(SUCCESS, execute(client, tokens)); | ||
| } catch (JedisException exception) { | ||
| return new InterpreterResult(ERROR, exception.getMessage()); |
| @Override | ||
| protected String execute(Jedis client, String[] tokens) { | ||
| String result = client.geoadd(tokens[1], Double.valueOf(tokens[2]), |
There was a problem hiding this comment.
I'd prefer wrapping it with String.valueOf() or call toString() if it's same, but this is simpler so OK to me.
There was a problem hiding this comment.
And I also prefer returning it without having local variable (this style occurs all of RedisCommand implementations), but this way makes it easier to set a breakpoint. I'd like to see other's opinions here.
| @Override | ||
| protected String execute(Jedis client, String[] tokens) { | ||
| String[] keys = new String[tokens.length - 2]; |
There was a problem hiding this comment.
Please extract this logic to method of utility class or method of RedisCommand. This is duplicated from many places. The logics which manipulate keys/parameters are all candidates to extract.
anthonycorbacho
commented
Oct 22, 2016
@HeartSaVioR yeah i would love to have a class that manipulate redis cmd and handle null, ussually i am not a huge fan of returning |
4ecd614 to
2937c92Comparedarionyaphet
commented
Oct 27, 2016
Aha. A lot of files conflict and I can't resolve that . Maybe I will close this one and reopen another . |
Follow this if rebase with master is hard due to lots of conflicts:
|
472c22d to
d319009Compare3e8a5c2 to
03cd13aComparedarionyaphet
commented
Nov 4, 2016
ee3572c to
5204881CompareSupport Redis interpreter Feature Support Redis Cluster - https://issues.apache.org/jira/browse/ZEPPELIN-1233 Outline the steps to test the PR here. - Does the licenses files need update? - Is there breaking changes for older versions? - Does this needs documentation?
477be1a to
0f3cba5Compareclose#83close#86close#125close#133close#139close#146close#193close#203close#246close#262close#264close#273close#291close#299close#320close#347close#389close#413close#423close#543close#560close#658close#670close#728close#765close#777close#782close#783close#812close#822close#841close#843close#878close#884close#918close#989close#1076close#1135close#1187close#1231close#1304close#1316close#1361close#1385close#1390close#1414close#1422close#1425close#1447close#1458close#1466close#1485close#1492close#1495close#1497close#1536close#1545close#1561close#1577close#1600close#1603close#1678close#1695close#1739close#1748close#1765close#1767close#1776close#1783close#1799
What is this PR for?
Support Redis interpreter
What type of PR is it?
Feature
Todos
What is the Jira issue?
How should this be tested?
Outline the steps to test the PR here.
Screenshots (if appropriate)
Questions: