一 redis java client
redis客户端介绍
- redis官网整理了如下java客户端,并推荐Jedis,lettuce,Redisson。
- Jedis的gitHub关注比较高,spring-data-redis也是基于Jedis封装。所以推荐采用Jedis来访问redis server。
Jedis使用介绍
- 轻量,简洁,便于集成和改造
- 支持连接池
- 支持pipelining、事务、LUA Scripting、Redis Sentinel、Redis Cluster
- 不支持读写分离,需要自己。
- 下面例子详细介绍
github:https://github.com/xetorthio/jedis
文档:https://github.com/xetorthio/jedis/wiki
Redisson(了解)
- 基于Netty实现,采用非阻塞IO,性能高
- 支持异步请求
- 支持连接池
- 支持pipelining、LUA Scripting、Redis Sentinel、Redis Cluster
- 不支持事务,官方建议以LUA Scripting代替事务
- 支持在Redis Cluster架构下使用pipelining
- 支持读写分离,支持读负载均衡,在主从复制和Redis Cluster架构下都可以使用
- 内建Tomcat Session Manager,为Tomcat 6/7/8提供了会话共享功能
- 可以与Spring Session集成,实现基于Redis的会话共享
- 文档较丰富,有中文文档
- 对于Jedis和Redisson的选择,同样应遵循前述的原理,尽管Jedis比起Redisson有各种各样的不足,但也应该在需要使用Redisson的高级特性时再选用Redisson,避免造成不必要的程序复杂度提升。
Redisson:
github:https://github.com/redisson/redisson
文档:https://github.com/redisson/redisson/wiki
Lettuce(了解)
Lettuce是一个可伸缩的线程安全的Redis客户端,用于同步,异步和反应使用。 多个线程可以共享同一个RedisConnection。它利用优秀netty NIO框架来高效地管理多个连接。 支持先进的Redis功能,如Sentinel,集群,流水线,自动重新连接和Redis数据模型
- 同步,异步和反应使用
- Redis Sentinel
- Redis 集群
- SSL 和 Unix 域套接字连接
- 流媒体 API
- CDI 和 Spring 集成
- 编解码器(用于 UTF8 / bit / JSON 等数据表示)
- 多个命令接口
- 与 Java 8 和 9 兼容(自动模块不带描述符)
文档:https://github.com/lettuce-io/lettuce-core/wiki
一 Jedis功能介绍
Jedis的使用可以通过wiki与github源代码的测试例子来学习使用。
如下例子介绍JedisPool构建,事物操作,pipelining多命令批量执行,lua脚本操作。其它数据结构
- redis3.0服务已支持集群,所以ShardedJedis操作了解。
/**
* @author andrexu
* @Description
* @email xuhc@kerunyun.com
* @created 下午8:12 2017/11/24
*/
@Slf4j
public class RedisUtilsTest {
/**
* 连接pool构建
*
* @return
*/
public static JedisPool getTestJedisPools() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
JedisPool jedisPool = new JedisPool(
poolConfig,
"test.redis.cnhz.shishike.com",
6379,
600000,
"d64ffed6b62e4e7c:CcPEtGSkIXSESQ6R",
18);
return jedisPool;
}
/**
* 事物内执行
*/
@Test
public void transactionTest() {
Jedis jedis = getTestJedisPools().getResource();
// batchDel(jedis);
// JedisUtilEx
//
Transaction transaction = jedis.multi();
transaction.set("test", "33333");
transaction.set("test2", "33333");
List<Object> result = transaction.exec();
log.info(result.toString());
jedis.close();
}
/**
* Pipelining 一起执行一批command,减少通讯连接
*/
@Test
public void pipelineTest() {
Jedis jedis = getTestJedisPools().getResource();
Pipeline p = jedis.pipelined();
p.set("test", "33333");
p.set("test2", "33333");
Response<Set<String>> allJobKey = p.keys("kry_job*");
Response<String> testKey = p.get("test2");
p.sync();
log.info(String.valueOf(allJobKey.get()));
log.info(testKey.get());
jedis.close();
}
/**
* redis中的lua脚本做了很多限制,防止随机性的发生。比如lua脚本中返回的总是有序的集合。
* 详情见 http://doc.redisfans.com/script/eval.html - 纯函数脚本
*/
@Test
public void scriptFuc() throws InterruptedException {
Jedis jedis = getTestJedisPools().getResource();
String key = "luaTest";
System.out.println(jedis.del(key));
System.out.println(jedis.sadd(key, "10","3","7","40","6"));
System.out.println(jedis.smembers(key));//这里怎么返回的值是有序的? [3, 6, 7, 10, 40]
System.out.println(jedis.eval("return redis.call('smembers', KEYS[1])", 1, key));//根据字母序排序 [10, 3, 40, 6, 7]
}
private void batchDel(Jedis jedis) {
Set<String> set = jedis.keys("kry_job*");
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String keyStr = it.next();
System.out.println(keyStr);
jedis.del(keyStr);
}
}
/**
* 多集群
*
* @return
*/
private ShardedJedis getShardedJedisForCluster() {
try {
List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
String host = "redis://test.redis.cnhz.shishike.com:6379/";
JedisShardInfo si = new JedisShardInfo(host + 1);
si.setPassword("d64ffed6b62e4e7c:CcPEtGSkIXSESQ6R");
shards.add(si);
si = new JedisShardInfo(host + 2);
si.setPassword("123456");
shards.add(si);
JedisPoolConfig poolConfig = new JedisPoolConfig();
ShardedJedis jedis = new ShardedJedis(shards);
return jedis;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Test
public void shardExpTest() {
ShardedJedis jedis = getShardedJedisForCluster();
for (int i = 0; i < 10; i++) {
jedis.set("test" + i, "33333:" + i);
}
jedis.set("a", "bar");
JedisShardInfo ak = jedis.getShardInfo("testl1");
assertEquals("bar", ak);
jedis.set("b", "bar1");
JedisShardInfo bk = jedis.getShardInfo("testl2");
assertEquals("testl2", bk);
}
}
- jedis操作命令介绍
1.对value操作的命令
exists(key):确认一个key是否存在
del(key):删除一个key
type(key):返回值的类型
keys(pattern):返回满足给定pattern的所有key
randomkey:随机返回key空间的一个key
rename(oldname, newname):将key由oldname重命名为newname,若newname存在则删除newname表示的key
dbsize:返回当前数据库中key的数目
expire:设定一个key的活动时间(s)
ttl:获得一个key的活动时间
select(index):按索引查询
move(key, dbindex):将当前数据库中的key转移到有dbindex索引的数据库
flushdb:删除当前选择数据库中的所有key
flushall:删除所有数据库中的所有key
2.对String操作的命令
set(key, value):给数据库中名称为key的string赋予值value
get(key):返回数据库中名称为key的string的value
getset(key, value):给名称为key的string赋予上一次的value
mget(key1, key2,…, key N):返回库中多个string(它们的名称为key1,key2…)的value
setnx(key, value):如果不存在名称为key的string,则向库中添加string,名称为key,值为value
setex(key, time, value):向库中添加string(名称为key,值为value)同时,设定过期时间time
mset(key1, value1, key2, value2,…key N, value N):同时给多个string赋值,名称为key i的string赋值value i
msetnx(key1, value1, key2, value2,…key N, value N):如果所有名称为key i的string都不存在,则向库中添加string,名称key i赋值为value i
incr(key):名称为key的string增1操作
incrby(key, integer):名称为key的string增加integer
decr(key):名称为key的string减1操作
decrby(key, integer):名称为key的string减少integer
append(key, value):名称为key的string的值附加value
substr(key, start, end):返回名称为key的string的value的子串
3.对List操作的命令
rpush(key, value):在名称为key的list尾添加一个值为value的元素
lpush(key, value):在名称为key的list头添加一个值为value的 元素
llen(key):返回名称为key的list的长度
lrange(key, start, end):返回名称为key的list中start至end之间的元素(下标从0开始,下同)
ltrim(key, start, end):截取名称为key的list,保留start至end之间的元素
lindex(key, index):返回名称为key的list中index位置的元素
lset(key, index, value):给名称为key的list中index位置的元素赋值为value
lrem(key, count, value):删除count个名称为key的list中值为value的元素。count为0,删除所有值为value的元素,count>0 从头至尾删除count个值为value的元素,count<0从尾到头删除|count|个值为value的元素。
lpop(key):返回并删除名称为key的list中的首元素
rpop(key):返回并删除名称为key的list中的尾元素
blpop(key1, key2,… key N, timeout):lpop 命令的block版本。即当timeout为0时,若遇到名称为key i的list不存在或该list为空,则命令结束。如果 timeout>0,则遇到上述情况时,等待timeout秒,如果问题没有解决,则对key i+1开始的list执行pop操作。
brpop(key1, key2,… key N, timeout):rpop的block版本。参考上一命令。
rpoplpush(srckey, dstkey):返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部
4.对Set操作的命令
sadd(key, member):向名称为key的set中添加元素member
srem(key, member) :删除名称为key的set中的元素member
spop(key) :随机返回并删除名称为key的set中一个元素
smove(srckey, dstkey, member) :将member元素从名称为srckey的集合移到名称为dstkey的集合
scard(key) :返回名称为key的set的基数
sismember(key, member) :测试member是否是名称为key的set的元素
sinter(key1, key2,…key N) :求交集
sinterstore(dstkey, key1, key2,…key N) :求交集并将交集保存到dstkey的集合
sunion(key1, key2,…key N) :求并集
sunionstore(dstkey, key1, key2,…key N) :求并集并将并集保存到dstkey的集合
sdiff(key1, key2,…key N) :求差集
sdiffstore(dstkey, key1, key2,…key N) :求差集并将差集保存到dstkey的集合
smembers(key) :返回名称为key的set的所有元素
srandmember(key) :随机返回名称为key的set的一个元素
5.对zset(sorted set)操作的命令
zadd(key, score, member):向名称为key的zset中添加元素member,score用于排序。如果该元素已经存在,则根据score更新该元素的顺序。
zrem(key, member) :删除名称为key的zset中的元素member
zincrby(key, increment, member) :如果在名称为key的zset中已经存在元素member,则该元素的score增加increment;否则向集合中添加该元素,其score的值为increment
zrank(key, member) :返回名称为key的zset(元素已按score从小到大排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”
zrevrank(key, member) :返回名称为key的zset(元素已按score从大到小排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”
zrange(key, start, end):返回名称为key的zset(元素已按score从小到大排序)中的index从start到end的所有元素
zrevrange(key, start, end):返回名称为key的zset(元素已按score从大到小排序)中的index从start到end的所有元素
zrangebyscore(key, min, max):返回名称为key的zset中score >= min且score <= max的所有元素
zcard(key):返回名称为key的zset的基数
zscore(key, element):返回名称为key的zset中元素element的score
zremrangebyrank(key, min, max):删除名称为key的zset中rank >= min且rank <= max的所有元素
zremrangebyscore(key, min, max) :删除名称为key的zset中score >= min且score <= max的所有元素
zunionstore / zinterstore(dstkeyN, key1,…,keyN, WEIGHTS w1,…wN, AGGREGATE SUM|MIN|MAX):对N个zset求并集和交集,并将最后的集合保存在dstkeyN中。对于集合中每一个元素的score,在进行AGGREGATE运算前,都要乘以对于的WEIGHT参数。如果没有提供WEIGHT,默认为1。默认的AGGREGATE是SUM,即结果集合中元素的score是所有集合对应元素进行 SUM运算的值,而MIN和MAX是指,结果集合中元素的score是所有集合对应元素中最小值和最大值。
6.对Hash操作的命令
hset(key, field, value):向名称为key的hash中添加元素field<—>value
hget(key, field):返回名称为key的hash中field对应的value
hmget(key, field1, …,field N):返回名称为key的hash中field i对应的value
hmset(key, field1, value1,…,field N, value N):向名称为key的hash中添加元素field i<—>value i
hincrby(key, field, integer):将名称为key的hash中field的value增加integer
hexists(key, field):名称为key的hash中是否存在键为field的域
hdel(key, field):删除名称为key的hash中键为field的域
hlen(key):返回名称为key的hash中元素个数
hkeys(key):返回名称为key的hash中所有键
hvals(key):返回名称为key的hash中所有键对应的value
hgetall(key):返回名称为key的hash中所有的键(field)及其对应的value
jedis redisCluster模式的访问
- 支持自动根据key分散。
/**
*
* 参考文档:http://blog.csdn.net/liubenlong007/article/details/53766734
* @author andrexu
* @Description
* @email xuhc@kerunyun.com
* @created 下午2:16 2018/3/1
*/
public class RedisClusterUtilsTest {
private static Jedis node1;
private static Jedis node2;
private static Jedis node3;
private static Jedis node4;
private static Jedis nodeSlave2;
JedisCluster jedisCluster = null;
private String nameKey = "andrexu";
@Before
public void before() {
String[] serverArray = "172.16.30.37:7001,172.16.30.37:7002,172.16.30.37:7003,172.16.30.37:7004,172.16.30.37:7005,172.16.30.37:7006".split(",");
Set<HostAndPort> nodes = new HashSet<>();
for (String ipPort : serverArray) {
String[] ipPortPair = ipPort.split(":");
nodes.add(new HostAndPort(ipPortPair[0].trim(), Integer.valueOf(ipPortPair[1].trim())));
}
jedisCluster = new JedisCluster(nodes, 1000, 1000, 1, "123456", new GenericObjectPoolConfig());
}
/**
* 简单字符串读写
*/
@Test
public void setStringData() {
System.out.println(jedisCluster.set(nameKey, "张三"));
System.out.println(jedisCluster.get(nameKey));
}
@Test
public void showNodesTest() {
Map<String, JedisPool> nodes= jedisCluster.getClusterNodes();
System.out.println(nodes);
}
/**
* setnx : 如果key存在,返回0,如果不存在,则设置成功。
* setnx的意思是set if not exist.
*/
@Test
public void setnxTest() {
System.out.println(jedisCluster.setnx(nameKey, "张三"));//key不存在,返回值为1
System.out.println(jedisCluster.get(nameKey));
System.out.println(jedisCluster.setnx(nameKey, "张三"));//已经存在,返回值为0
System.out.println(jedisCluster.get(nameKey));
}
/**
* 批量操作key
* keySlot算法中,如果key包含{},就会使用第一个{}内部的字符串作为hash key,这样就可以保证拥有同样{}内部字符串的key就会拥有相同slot。
* 参考 http://brandnewuser.iteye.com/blog/2314280
* redis.clients.util.JedisClusterCRC16#getSlot(java.lang.String)
*
* 注意:这样的话,本来可以hash到不同的slot中的数据都放到了同一个slot中,所以使用的时候要注意数据不要太多导致一个slot数据量过大,数据分布不均匀!
*
* MSET 是一个原子性(atomic)操作,所有给定 key 都会在同一时间内被设置,某些给定 key 被更新而另一些给定 key 没有改变的情况,不可能发生。
*/
@Test
public void msetTest() throws InterruptedException {
/**
* jedisCluster.mset("sf","d","aadf","as");
* 直接这样写,会报错:redis.clients.jedis.exceptions.JedisClusterException: No way to dispatch this command to Redis Cluster because keys have different slots.
* 这是因为key不在同一个slot中
*/
String prefix="andrexu";
String KEY_SPLIT=":";
String result = jedisCluster.mset("{" + prefix + KEY_SPLIT + "}" + "name", "张三", "{" + prefix + KEY_SPLIT + "}" + "age", "23", "{" + prefix + KEY_SPLIT + "}" + "address", "adfsa", "{" + prefix + KEY_SPLIT + "}" + "score", "100");
System.out.println(result);
String name = jedisCluster.get("{" + prefix + KEY_SPLIT + "}" + "name");
System.out.println(name);
Long del = jedisCluster.del("{" + prefix + KEY_SPLIT + "}" + "age");
System.out.println(del);
List<String> values = jedisCluster.mget("{" + prefix + KEY_SPLIT + "}" + "name", "{" + prefix + KEY_SPLIT + "}" + "age", "{" + prefix + KEY_SPLIT + "}" + "address");
System.out.println(values);
}
@Test
public void shardExpTest() {
JedisCluster jedis = jedisCluster;
for (int i = 0; i < 10; i++) {
jedis.set("test" + i, "33333:" + i);
}
jedis.set("a", "bar");
String ak = jedis.get("testl1");
assertEquals("33333:1", ak);
jedis.set("b", "bar1");
String bk = jedis.get("testl2");
assertEquals("33333:2", bk);
}