Redis入门学习
- 一、摘要
- 二、五种数据类型的基本命令操作
- 三、Redis连接池
- 四、普通同步方式
- 五、事务方式(Transactions)
- 六、管道(Pipelining)
- 七、管道中调用事务
- 八、分布式直连同步调用
- 九、分布式直连异步管道调用
- 十、分布式连接池同步调用
- 十一、分布式连接池异步调用
- 十二、需要注意的地方
Redis是一个著名的key-value存储系统,而作为其官方推荐的java版客户端jedis也非常强大和稳定,支持事务、管道及有jedis自身实现的分布式。
1 package sy.test; 2 3 import java.util.HashMap; 4 import java.util.Iterator; 5 import java.util.List; 6 import java.util.Map; 7 8 import org.junit.Before; 9 import org.junit.Test; 10 11 import redis.clients.jedis.Jedis; 12 import sy.util.RedisUtil; 13 14 public class TestJedis { 15 private Jedis jedis; 16 17 @Before 18 public void setUp() throws Exception { 19 //连接redis服务器,192.168.0.100:6379 20 jedis = new Jedis("192.168.0.16", 6379); 21 //登陆权限认证 22 jedis.auth("123456"); 23 } 24 25 /** 26 * redis存储字符串 27 */ 28 @Test 29 public void testString() { 30 jedis.set("name", "itme"); 31 System.out.println(jedis.get("name")); 32 33 jedis.append("name", " is my lover"); //拼接 34 System.out.println(jedis.get("name")); 35 36 //删除某个键 37 jedis.del("name"); 38 System.out.println(jedis.get("name")); 39 40 //添加多个键值对 41 jedis.mset("name","mset","age","25","qq","123456"); 42 System.out.println(jedis.get("name") + "-" + jedis.get("age") + "-" + jedis.get("qq")); 43 44 //自增1操作 45 jedis.incr("age"); 46 System.out.println(jedis.get("name") + "-" + jedis.get("age") + "-" + jedis.get("qq")); 47 48 //关闭jedis连接 49 jedis.disconnect(); 50 51 } 52 53 /** 54 * 操作map 55 */ 56 @Test 57 public void testMap(){ 58 59 //添加数据 60 Map<String, String> map = new HashMap<String,String>(); 61 map.put("name", "xinxin"); 62 map.put("age", "22"); 63 map.put("qq", "123456"); 64 65 jedis.hmset("user", map); 66 67 List<String> list = jedis.hmget("user", "name","age","qq"); 68 System.out.println(list); 69 70 //删除数据 71 jedis.hdel("user", "name"); 72 System.out.println(jedis.hmget("user", "age")); //因为删除了,所以返回的是null 73 System.out.println(jedis.hlen("user")); //返回key为user的键中存放的值的个数2 74 System.out.println(jedis.exists("user"));//是否存在key为user的记录 返回true 75 System.out.println(jedis.hkeys("user"));//返回map对象中的所有key 76 System.out.println(jedis.hvals("user"));//返回map对象中的所有value 77 78 Iterator<String> iter=jedis.hkeys("user").iterator(); 79 while (iter.hasNext()){ 80 String key = iter.next(); 81 System.out.println(key+":"+jedis.hmget("user",key)); 82 } 83 84 //关闭jedis连接 85 jedis.disconnect(); 86 } 87 88 /** 89 * 操作List 90 * 底层是双向链表 91 */ 92 @Test 93 public void testList(){ 94 //开始前,先移除所有的内容 95 jedis.del("java framework"); 96 System.out.println(jedis.lrange("java framework",0,-1)); 97 //先向key java framework中存放三条数据 98 jedis.lpush("java framework","spring"); 99 jedis.lpush("java framework","struts"); 100 jedis.lpush("java framework","hibernate"); 101 102 //再取出所有数据jedis.lrange是按范围取出, 103 // 第一个是key,第二个是起始位置,第三个是结束位置,jedis.llen获取长度 -1表示取得所有 104 System.out.println(jedis.lrange("java framework",0,-1)); 105 106 jedis.del("java framework"); 107 jedis.rpush("java framework","spring"); 108 jedis.rpush("java framework","struts"); 109 jedis.rpush("java framework","hibernate"); 110 System.out.println(jedis.lrange("java framework",0,-1)); 111 112 //关闭jedis连接 113 jedis.disconnect(); 114 } 115 116 /** 117 * jedis操作set 118 */ 119 @Test 120 public void testSet(){ 121 //添加 122 jedis.sadd("user1","liuling"); 123 jedis.sadd("user1","xinxin"); 124 jedis.sadd("user1","ling"); 125 jedis.sadd("user1","zhangxinxin"); 126 jedis.sadd("user1","who"); 127 //移除noname 128 jedis.srem("user1","who"); 129 System.out.println(jedis.smembers("user1"));//获取所有加入的value 130 System.out.println(jedis.sismember("user1", "who"));//判断 who 是否是user集合的元素 131 System.out.println(jedis.srandmember("user1")); 132 System.out.println(jedis.scard("user1"));//返回集合的元素个数 133 134 //关闭jedis连接 135 jedis.disconnect(); 136 } 137 138 139 @Test 140 public void testSortList(){ 141 //jedis 排序 142 //注意,此处的rpush和lpush是List的操作。是一个双向链表(但从表现来看的) 143 jedis.del("a");//先清除数据,再加入数据进行测试 144 jedis.rpush("a", "1"); 145 jedis.lpush("a","6"); 146 jedis.lpush("a","3"); 147 jedis.lpush("a","9"); 148 149 System.out.println(jedis.lrange("a",0,-1));// [9, 3, 6, 1] 150 //输入排序后结果 不影响原来的顺序。 151 System.out.println(jedis.sort("a")); //[1, 3, 6, 9] 152 System.out.println(jedis.lrange("a",0,-1)); 153 154 //关闭jedis 155 jedis.disconnect(); 156 } 157 158 @Test 159 public void testRedisPool(){ 160 RedisUtil.getJedis().set("newname", "中文测试"); 161 System.out.println(RedisUtil.getJedis().get("newname")); 162 } 163 164 }
1 package sy.util; 2 3 import redis.clients.jedis.Jedis; 4 import redis.clients.jedis.JedisPool; 5 import redis.clients.jedis.JedisPoolConfig; 6 7 public final class RedisUtil { 8 9 //Redis服务器IP 10 private static String ADDR = "192.168.0.16"; 11 12 //Redis的端口号 13 private static int PORT = 6379; 14 15 //访问密码 16 private static String AUTH = "123456"; 17 18 //可用连接实例的最大数目,默认值为8; 19 //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。 20 private static int MAX_ACTIVE = 1024; 21 22 //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。 23 private static int MAX_IDLE = 200; 24 25 //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException; 26 private static int MAX_WAIT = 10000; 27 28 private static int TIMEOUT = 10000; 29 30 //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的; 31 private static boolean TEST_ON_BORROW = true; 32 33 private static JedisPool jedisPool = null; 34 35 /** 36 * 初始化Redis连接池 37 */ 38 static { 39 try { 40 JedisPoolConfig config = new JedisPoolConfig(); 41 config.setMaxIdle(MAX_ACTIVE); 42 config.setMaxIdle(MAX_IDLE); 43 config.setMaxWaitMillis(MAX_WAIT); 44 config.setTestOnBorrow(TEST_ON_BORROW); 45 jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH); 46 } catch (Exception e) { 47 e.printStackTrace(); 48 } 49 } 50 51 /** 52 * 获取Jedis实例 53 * @return 54 */ 55 public synchronized static Jedis getJedis() { 56 try { 57 if (jedisPool != null) { 58 Jedis resource = jedisPool.getResource(); 59 return resource; 60 } else { 61 return null; 62 } 63 } catch (Exception e) { 64 e.printStackTrace(); 65 return null; 66 } 67 } 68 69 /** 70 * 释放jedis资源 71 * @param jedis 72 */ 73 public static void returnResource(final Jedis jedis) { 74 if (jedis != null) { 75 jedisPool.returnResource(jedis); 76 } 77 } 78 }
最简单和基础的调用方式:
1 /** 2 * jedis redis普通同步方式 3 */ 4 @Test 5 public void testNormal(){ 6 Jedis jedis = RedisUtil.getJedis(); 7 long start = System.currentTimeMillis(); 8 for(int i=0;i<10000;i++){ 9 jedis.set("n" + i, "n" + i); 10 } 11 long end = System.currentTimeMillis(); 12 System.out.println("Simple set" + ((end-start)/1000.0) + " seconds"); 13 }
redis的事务很简单,他主要目的是保障,一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令。一般情况下 redis 在接受到一个 client 发来的命令后会立即处理并返回处理结果,但是当一个 client在一个连接中发 multi命令,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中。当从此连接受到 exec 命令后,redis会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给 client.然后此连接就结束事务上下文。
1 @Test 2 public void testTransaction1(){ 3 Jedis jedis = RedisUtil.getJedis(); 4 long start = System.currentTimeMillis(); 5 6 jedis.flushDB(); 7 jedis.lpush("mylist", "hello"); 8 9 //开启事务 10 Transaction tx = jedis.multi(); 11 tx.set("testtran1", "testtran1"); 12 13 tx.lset("mylist", 10, "world"); 14 tx.set("testtran1-1", "testtran1-1"); 15 //提交事务 16 List<Object> results = tx.exec(); 17 18 long end = System.currentTimeMillis(); 19 System.out.println(jedis.get("testtran1")); 20 System.out.println(jedis.lrange("mylist", 0, -1)); 21 System.out.println(jedis.get("testtran1-1")); 22 System.out.println("Test Transaction1 " + ((end-start)/1000.0) + " seconds"); 23 jedis.disconnect(); 24 } 25 26 @Test 27 public void testTransaction2(){ 28 Jedis jedis = RedisUtil.getJedis(); 29 long start = System.currentTimeMillis(); 30 31 jedis.flushDB(); 32 33 jedis.set("testtran2", "testtran2"); 34 jedis.set("testtran2-1", "testtran2-1"); 35 36 //开启事务 37 Transaction tx = jedis.multi(); 38 tx.set("testtran2", "testtran2_new"); 39 tx.set("testtran2-1", "testtran2-1_new"); 40 41 //回滚事务 42 tx.discard(); 43 44 long end = System.currentTimeMillis(); 45 System.out.println(jedis.get("testtran2")); 46 System.out.println(jedis.get("testtran2-1")); 47 System.out.println("Test Transaction2 " + ((end-start)/1000.0) + " seconds"); 48 jedis.disconnect(); 49 }
示例1说明,只要提交事务了,不管事务中的命令执行成功与否都会执行,即使有命令执行错误,也不会在java中抛出异常。其中tx.lset("mylist", 10, "world");的处理结果是错误的,但是其后边的命令继续执行。
我们调用jedis.watch(…)方法来监控key,如果调用后key值发生变化,则整个事务会执行失败。另外,事务中某个操作失败,并不会回滚其他操作。这一点需要注意。还有,我们可以使用discard()方法来取消事务。
有时,我们需要采用异步方式,一次发送多个指令,不同步等待其返回结果。批量发送请求,减少网络通信时间,最后将多条命令的执行结果打包返回给客户端。
/** * 管道pipeline */ @Test public void testPipeline(){ Jedis jedis = RedisUtil.getJedis(); jedis.flushDB(); //使用管道的方式 Pipeline pipeline = jedis.pipelined(); long start1 = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("pipeline" + i, "pipeline" + i); } long end1 = System.currentTimeMillis(); System.out.println("使用Pipeline的方式 " + ((end1-start1)/1000.0) + " seconds"); //普通方式 long start2 = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("normal" + i, "normal" + i); } long end2 = System.currentTimeMillis(); System.out.println("普通方式 " + ((end2-start2)/1000.0) + " seconds"); }
@Test public void test4combPipelineTrans() { jedis = new Jedis("192.168.0.16", 6379);long start = System.currentTimeMillis(); Pipeline pipeline = jedis.pipelined(); pipeline.multi(); for (int i = 0; i < 100000; i++) { pipeline.set("" + i, "" + i); } pipeline.exec(); List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined transaction: " + ((end - start)/1000.0) + " seconds"); jedis.disconnect(); }
但是经测试(见本文后续部分),发现其效率和单独使用事务差不多,甚至还略微差点。
1 @Test 2 public void test5shardNormal() { 3 List<JedisShardInfo> shards = Arrays.asList( 4 new JedisShardInfo("localhost",6379), 5 new JedisShardInfo("localhost",6380)); 6 7 ShardedJedis sharding = new ShardedJedis(shards); 8 9 long start = System.currentTimeMillis(); 10 for (int i = 0; i < 100000; i++) { 11 String result = sharding.set("sn" + i, "n" + i); 12 } 13 long end = System.currentTimeMillis(); 14 System.out.println("Simple@Sharing SET: " + ((end - start)/1000.0) + " seconds"); 15 16 sharding.disconnect(); 17 }
这个是分布式直接连接,并且是同步调用,每步执行都返回执行结果。类似地,还有异步管道调用。
1 @Test 2 public void test6shardpipelined() { 3 List<JedisShardInfo> shards = Arrays.asList( 4 new JedisShardInfo("localhost",6379), 5 new JedisShardInfo("localhost",6380)); 6 7 ShardedJedis sharding = new ShardedJedis(shards); 8 9 ShardedJedisPipeline pipeline = sharding.pipelined(); 10 long start = System.currentTimeMillis(); 11 for (int i = 0; i < 100000; i++) { 12 pipeline.set("sp" + i, "p" + i); 13 } 14 List<Object> results = pipeline.syncAndReturnAll(); 15 long end = System.currentTimeMillis(); 16 System.out.println("Pipelined@Sharing SET: " + ((end - start)/1000.0) + " seconds"); 17 18 sharding.disconnect(); 19 }
如果,你的分布式调用代码是运行在线程中,那么上面两个直连调用方式就不合适了,因为直连方式是非线程安全的,这个时候,你就必须选择连接池调用。
1 @Test 2 public void test7shardSimplePool() { 3 List<JedisShardInfo> shards = Arrays.asList( 4 new JedisShardInfo("localhost",6379), 5 new JedisShardInfo("localhost",6380)); 6 7 ShardedJedisPool pool = new ShardedJedisPool(new JedisPoolConfig(), shards); 8 9 ShardedJedis one = pool.getResource(); 10 11 long start = System.currentTimeMillis(); 12 for (int i = 0; i < 100000; i++) { 13 String result = one.set("spn" + i, "n" + i); 14 } 15 long end = System.currentTimeMillis(); 16 pool.returnResource(one); 17 System.out.println("Simple@Pool SET: " + ((end - start)/1000.0) + " seconds"); 18 19 pool.destroy(); 20 }
1 @Test 2 public void test8shardPipelinedPool() { 3 List<JedisShardInfo> shards = Arrays.asList( 4 new JedisShardInfo("localhost",6379), 5 new JedisShardInfo("localhost",6380)); 6 7 ShardedJedisPool pool = new ShardedJedisPool(new JedisPoolConfig(), shards); 8 9 ShardedJedis one = pool.getResource(); 10 11 ShardedJedisPipeline pipeline = one.pipelined(); 12 13 long start = System.currentTimeMillis(); 14 for (int i = 0; i < 100000; i++) { 15 pipeline.set("sppn" + i, "n" + i); 16 } 17 List<Object> results = pipeline.syncAndReturnAll(); 18 long end = System.currentTimeMillis(); 19 pool.returnResource(one); 20 System.out.println("Pipelined@Pool SET: " + ((end - start)/1000.0) + " seconds"); 21 pool.destroy(); 22 }
1.事务和管道都是异步模式。在事务和管道中不能同步查询结果。比如下面两个调用,都是不允许的:
1 Transaction tx = jedis.multi(); 2 for (int i = 0; i < 100000; i++) { 3 tx.set("t" + i, "t" + i); 4 } 5 System.out.println(tx.get("t1000").get()); //不允许 6 7 List<Object> results = tx.exec(); 8 9 … 10 … 11 12 Pipeline pipeline = jedis.pipelined(); 13 long start = System.currentTimeMillis(); 14 for (int i = 0; i < 100000; i++) { 15 pipeline.set("p" + i, "p" + i); 16 } 17 System.out.println(pipeline.get("p1000").get()); //不允许 18 19 List<Object> results = pipeline.syncAndReturnAll();
2. 事务和管道都是异步的,个人感觉,在管道中再进行事务调用,没有必要,不如直接进行事务模式。
3. 分布式中,连接池的性能比直连的性能略好(见后续测试部分)。
4.分布式调用中不支持事务。因为事务是在服务器端实现,而在分布式中,每批次的调用对象都可能访问不同的机器,所以没法进行事务。
参考文章:
1.http://my.oschina.net/sphl520/blog/312514#OSC_h3_3
2.http://www.cnblogs.com/liuling/p/2014-4-19-04.html
3.Redis实战《红丸出品》
4.http://www.2cto.com/kf/201504/395403.html
Not more, but better!