redis

1.redis介绍
2.redis常用数据类型及应用场景
3.Java中操作redis
4.redis集群
5.redis事务

1.redis介绍
redis是一个key-value存储系统,它支持存储的value类型相对更多,包括string(字符串)、hash(哈希类型)、list(链表)、set(集合)、SortedSet(有序集合)。
这些数据类型都支持push/pop、add/remove及取交集并集和差集的操作,而且这些操作都是原子性的。
在此基础上,redis支持各种不同方式的排序。为了保证效率,数据都是缓存在内存中。
Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器

2.redis常用数据类型及应用场景
(1)Key
1)keys 查找所有符合条件的key
2)del 删除
3)exists 是否存在
4)expire 设置生存时间(以秒为单位)
5)persist 将生存时间移除
5)ttl 查看剩余生存时间(以秒为单位)
6)pexpire 设置生存时间(以毫秒为单位)
7)pttl 查看剩余生存时间(以毫秒为单位)
8)randomkey 从数据库随机返回一个key
9)sort 返回键值从小到大排序 desc 从大到小排序
10)type 所存储的值的类型

(2)String
1)get 返回一个
2)mget 返回一个或多个
3)set 设置一个
4)mset 设置一个或多个
5)setex key seconds value 将值value关联到key,并将key的生存时间设为seconds(以秒为单位)
6)setnx 设置一个key值,当且仅当key不存在
7)append 追加值
8)decr 减1
9)decrby key decrement 值减去减量decrement
10)incr 加1
11)incrby key incrment 值加上增量increment
12)getrange key start end 字符串截取0开始
13)strlen 字符串长度
应用场景:
String是最常用的一种数据类型,普通的key/value存储都可以归为此类

(3)Hash
1)hset 将哈希表key中的域field的值设为value (旧值将被覆盖)
2)hmset 同时将多个field-value(域-值)对设置到哈希表key中 (旧值将被覆盖)
3)hsetnx 将哈希表key中的域field的值设置为value,当且仅当域field不存在
4)hget 返回给定域field的值
5)hmget 返回一个或多个给定域的值
6)hgetall 返回所有的域和值
7)hdel 删除一个或多个指定域
8)hexists 查看给定域field是否存在
9)hincrby key field increment 值加上增量increment(增量也可以为负数,相当于对给定域进行减法操作)
10)hkeys 返回所有域
11)hvals 返回所有域的值
12)hlen 返回域的数量
应用场景:
比如我们要存储一个用户信息对象数据,包含以下信息:
用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有以下2种存储方式:


第一种方式将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回。

 

第二种方法是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,会造成内存浪费。
Redis提供的Hash很好的解决了这个问题,Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口:


Key仍然是用户ID, value是一个Map,这个Map的key是成员的属性名,value是属性值,这样对数据的修改和存取都可以直接通过其内部Map的Key, 也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化的问题。

(4)List
1)lset 将下标为index的元素的值设置为value
2)lpush 将一个或多个值插入到列表的表头
3)lpushx 将值插入到列表的表头,当且仅当key存在并且是一个列表
4)lpop 移除并返回列表的头元素
5)rpush 将一个或多个值插入到列表的表尾(最右边)
6)rpushx 将值插入到列表的表尾,当且仅当key存在并且是一个列表
7)rpop 移除并返回列表的尾元素
8)linsert key BEFORE|AFTER pivot value 将值value插入到列表key当中,位于值pivot之前或之后
9)lindex 返回列表中,下标为index的元素
10)lrange 返回列表中指定区间内的元素,区间以偏移量start和stop指定 -1表示最后一个元素
11)llen 返回列表的长度
应用场景:
list的应用场景非常多,也是Redis最重要的数据结构之一,比较适用于列表式存储且顺序相对比较固定,例如:省份、城市列表。

(5)Set
1)sadd 将一个或多个元素加入到集合中
2)scard 集合中元素的数量
3)sismember 元素是否集合的成员
4)smembers 返回集合中的所有成员
5)spop 将随机元素从集合中移除并返回
6)srandmember 返回随机元素
7)srem 移除集合中的一个或多个元素
应用场景:
set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,可以使用set,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。

(6)SortedSet
1)zadd 将一个或多个元素加入到有序集中
2)zcard 集合中元素的数量
3)zincrby key increment member 为有序集的成员值加上增量increment
4)zrem 移除有序集中的一个或多个成员
5)zscore 返回有序集中,成员的值
6)zrevrange 返回有序集中,指定区间内的成员
应用场景:
sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构。

3.Java中操作redis
Java中使用Jedis操作Redis,具体实现代码部分讲解。

  1 package redis;
  2 
  3 import org.junit.Before;
  4 import org.junit.Test;
  5 import redis.clients.jedis.BinaryClient;
  6 import redis.clients.jedis.Jedis;
  7 import redis.clients.jedis.Transaction;
  8 
  9 import java.util.HashMap;
 10 import java.util.Map;
 11 
 12 
 13 public class TestRedis {
 14     private Jedis jedis;
 15 
 16     @Before
 17     public void setup() {
 18         jedis = new Jedis("127.0.0.1", 6379);
 19     }
 20 
 21     /**
 22      * Key
 23      * 1)keys 查找所有符合条件的key
 24      * 2)del  删除
 25      * 3)exists 是否存在
 26      * 4)expire 设置生存时间(以秒为单位)
 27      * 5)persist 将生存时间移除
 28      * 5)ttl  查看剩余生存时间(以秒为单位)
 29      * 6)pexpire 设置生存时间(以毫秒为单位)
 30      * 7)pttl 查看剩余生存时间(以毫秒为单位)
 31      * 8)randomkey 从数据库随机返回一个key
 32      * 9)sort  返回键值从小到大排序  desc 从大到小排序
 33      * 10)type 所存储的值的类型
 34      */
 35     @Test
 36     public void keyTest() {
 37         jedis.mset("one", "1", "two", "2", "three", "3");
 38         System.out.println("所有值:" + jedis.keys("*"));
 39         System.out.println("删除:" + jedis.del("one"));
 40         System.out.println("存在:" + jedis.exists("one"));
 41         System.out.println("存在:" + jedis.exists("two"));
 42         System.out.println("有效时间:" + jedis.expire("two", 3600));//生存时间 秒
 43         System.out.println("剩余有效时间:" + jedis.ttl("two"));//剩余生存时间
 44         System.out.println("随机返回:" + jedis.randomKey());
 45         System.out.println("值类型:" + jedis.type("three"));
 46         System.out.println("key的数量:" + jedis.dbSize());
 47         System.out.println("删除全部key:" + jedis.flushDB());
 48     }
 49 
 50 
 51     /**
 52      * redis存储字符串
 53      * 1)get 返回一个
 54      * 2)mget 返回一个或多个
 55      * 3)set  设置一个
 56      * 4)mset 设置一个或多个
 57      * 5)setex key seconds value  将值value关联到key,并将key的生存时间设为seconds(以秒为单位)
 58      * 6)setnx 设置一个key值,当且仅当key不存在
 59      * 7)append 追加值
 60      * 8)decr 减1
 61      * 9)decrby key decrement 值减去减量decrement
 62      * 10)incr 加1
 63      * 11)incrby key incrment  值加上增量increment
 64      * 12)getrange key start end 字符串截取0开始
 65      * 13)strlen 字符串长度
 66      */
 67     @Test
 68     public void testString() {
 69         jedis.set("one", "1");
 70         System.out.println("单个值:" + jedis.get("one"));
 71 
 72         jedis.mset("two", "2", "three", "3", "four", "4", "five", "5");
 73         System.out.println("多个值:" + jedis.mget("two", "three", "four", "five"));
 74 
 75         jedis.setex("six", 3600, "6");
 76         System.out.println(jedis.get("six") + ",有效时间:" + jedis.ttl("six"));
 77 
 78         System.out.println(jedis.setnx("six", "6"));
 79         System.out.println(jedis.setnx("seven", "7"));
 80 
 81         System.out.println("six长度:" + jedis.append("six", "66") + ",six新值:" + jedis.get("six"));
 82 
 83         System.out.println("six:" + jedis.decr("six"));
 84         System.out.println("six:" + jedis.decrBy("six", 659));
 85 
 86         System.out.println("six:" + jedis.incr("six"));
 87         System.out.println("six:" + jedis.incrBy("six", 659));
 88 
 89         jedis.set("java", "hello word");
 90         System.out.println(jedis.getrange("java", 0, 4));//字符串截取0开始
 91         System.out.println(jedis.getrange("java", -4, -1));
 92         System.out.println(jedis.strlen("java"));
 93 
 94         System.out.println("key的数量:" + jedis.dbSize());
 95         System.out.println("删除全部key:" + jedis.flushDB());
 96     }
 97 
 98     /**
 99      * redis操作Map
100      * 1)hset    将哈希表key中的域field的值设为value (旧值将被覆盖)
101      * 2)hmset   同时将多个field-value(域-值)对设置到哈希表key中 (旧值将被覆盖)
102      * 3)hsetnx  将哈希表key中的域field的值设置为value,当且仅当域field不存在
103      * 4)hget    返回给定域field的值
104      * 5)hmget   返回一个或多个给定域的值
105      * 6)hgetall 返回所有的域和值
106      * 7)hdel    删除一个或多个指定域
107      * 8)hexists 查看给定域field是否存在
108      * 9)hincrby key field increment 值加上增量increment(增量也可以为负数,相当于对给定域进行减法操作)
109      * 10)hkeys  返回所有域
110      * 11)hvals  返回所有域的值
111      * 12)hlen   返回域的数量
112      */
113     @Test
114     public void testMap() {
115         jedis.hset("user", "name", "cheng");
116 
117         Map<String, String> map = new HashMap<String, String>();
118         map.put("age", "10");
119         map.put("qq", "123456");
120 
121         jedis.hmset("user", map);
122 
123         jedis.hsetnx("user", "name", "chengwei");
124 
125         System.out.println("name值:" + jedis.hget("user", "name"));
126 
127         System.out.println(jedis.hmget("user", "name", "age", "qqq"));
128 
129         System.out.println("所有的域:" + jedis.hkeys("user"));
130         System.out.println("所有的域的值:" + jedis.hvals("user"));
131         System.out.println("所有的域和值:" + jedis.hgetAll("user"));
132         System.out.println("域的数量:" + jedis.hlen("user"));
133 
134         jedis.hdel("user", "name");
135         System.out.println("name值:" + jedis.hmget("user", "name"));
136 
137         System.out.println(jedis.exists("user"));
138 
139         jedis.hincrBy("user", "age", 10L);
140         System.out.println("age值加后:" + jedis.hget("user", "age"));
141         jedis.hincrBy("user", "age", -10L);
142         System.out.println("age值减后:" + jedis.hget("user", "age"));
143 
144         System.out.println("删除全部key:" + jedis.flushDB());
145     }
146 
147     /**
148      * jedis操作List
149      * 1)lset    将下标为index的元素的值设置为value
150      * 2)lpush   将一个或多个值插入到列表的表头
151      * 3)lpushx  将值插入到列表的表头,当且仅当key存在并且是一个列表
152      * 4)lpop    移除并返回列表的头元素
153      * 5)rpush   将一个或多个值插入到列表的表尾(最右边)
154      * 6)rpushx  将值插入到列表的表尾,当且仅当key存在并且是一个列表
155      * 7)rpop    移除并返回列表的尾元素
156      * 8)linsert key BEFORE|AFTER pivot value 将值value插入到列表key当中,位于值pivot之前或之后
157      * 9)lindex  返回列表中,下标为index的元素
158      * 10)lrange 返回列表中指定区间内的元素,区间以偏移量start和stop指定  -1表示最后一个元素
159      * 11)llen   返回列表的长度
160      */
161     @Test
162     public void testList() {
163         jedis.lpush("java", "struts");
164         jedis.lpush("java", "mybatis");
165 
166         jedis.lset("java", 0L, "spring");
167 
168         System.out.println("1." + jedis.lrange("java", 0, -1));
169 
170         System.out.println("2." + jedis.lpop("java"));
171         System.out.println("2.1." + jedis.lrange("java", 0, -1));
172 
173         jedis.del("java");
174         jedis.rpush("java", "spring");
175         jedis.rpush("java", "struts");
176         jedis.rpush("java", "mybatis");
177 
178         System.out.println("3." + jedis.lrange("java", 0, -1));
179 
180         System.out.println("4." + jedis.rpop("java"));
181         System.out.println("4.1." + jedis.lrange("java", 0, -1));
182 
183         jedis.linsert("java", BinaryClient.LIST_POSITION.AFTER, "spring", "springMVC");
184         System.out.println("5." + jedis.lrange("java", 0, -1));
185 
186         System.out.println("6." + jedis.lindex("java", 0L));
187 
188         System.out.println("7." + jedis.llen("java"));
189 
190         System.out.println("删除全部key:" + jedis.flushDB());
191     }
192 
193     /**
194      * jedis操作Set
195      * 1)sadd         将一个或多个元素加入到集合中
196      * 2)scard        集合中元素的数量
197      * 3)sismember    元素是否集合的成员
198      * 4)smembers     返回集合中的所有成员
199      * 5)spop         将随机元素从集合中移除并返回
200      * 6)srandmember  返回随机元素
201      * 7)srem         移除集合中的一个或多个元素
202      */
203     @Test
204     public void testSet() {
205         //添加
206         jedis.sadd("java", "spring", "struts", "mybatis", "struts2");
207 
208         System.out.println("集合中元素的数量:" + jedis.scard("java"));
209 
210         System.out.println("所有成员:" + jedis.smembers("java"));
211 
212         jedis.srem("java", "spring");
213         System.out.println("移除spring后所有成员:" + jedis.smembers("java"));
214 
215         System.out.println("元素是否集合的成员:" + jedis.sismember("java", "struts2"));
216 
217         System.out.println("将随机元素从集合中移除并返回:" + jedis.spop("java"));
218 
219         System.out.println("所有成员:" + jedis.smembers("java"));
220 
221         System.out.println("返回随机元素:" + jedis.srandmember("java"));
222 
223         System.out.println("所有成员:" + jedis.smembers("java"));
224 
225         System.out.println("删除全部key:" + jedis.flushDB());
226     }
227 
228     /**
229      * jedis操作SortedSet
230      * 1)zadd         将一个或多个元素加入到有序集中
231      * 2)zcard        集合中元素的数量
232      * 3)zincrby key increment member    为有序集的成员值加上增量increment
233      * 4)zrem         移除有序集中的一个或多个成员
234      * 5)zscore       返回有序集中,成员的值
235      * 6)zrevrange    返回有序集中,指定区间内的成员
236      */
237     @Test
238     public void testSortedSet() {
239         jedis.zadd("java", 3.1, "spring");
240         System.out.println("1." + jedis.zcard("java"));
241 
242         Map map = new HashMap();
243         map.put(2.0, "struts");
244         map.put(3.0, "mybatis");
245         jedis.zadd("java", map);
246         System.out.println("2." + jedis.zcard("java"));//集合中元素的数量
247 
248         System.out.println("3." + jedis.zrevrange("java", 0L, -1));//返回有序集中,指定区间内的成员
249 
250         jedis.zincrby("java", 1, "mybatis");
251 
252         System.out.println("4." + jedis.zrevrange("java", 0L, -1));
253 
254         jedis.zrem("java", "spring");//移除有序集中的一个或多个成员
255 
256         System.out.println("5." + jedis.zrevrange("java", 0L, -1));
257 
258         System.out.println("6." + jedis.zscore("java", "mybatis"));//返回有序集中,成员的值
259 
260         System.out.println("删除全部key:" + jedis.flushDB());
261     }
262 
263     
264 }

 

4.redis集群
1)Redis集群的分配数据是采用另外一种叫做哈希槽 (hash slot)的方式来分配的。redis集群默认分配了16384个哈希槽,当我们set一个key时,会用CRC16算法来取模得到所属的槽,然后将这个key分到哈希槽区间的节点上,具体算法就是:CRC16(key)%16384。

节点:一个端口的redis服务便是一个节点
槽(集群将整个系统分为16384个hash槽):这16384个槽位要全部分布在集群中的主节点上。
重新分片:若某个主节点故障了,将该主节点的槽位分配到其他可以用的主节点上。
上线/下线状态:是否全部的槽位都分布在节点上。

2)集群中的每个节点负责处理一部分哈希槽。举个例子,一个集群可以有三个哈希槽,其中:
节点A负责处理0号至5500号哈希槽。
节点B负责处理5501号至11000号哈希槽。
节点C负责处理11001号至16384号哈希槽。
这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。 比如说:
如果用户将新节点D添加到集群中, 那么集群只需要将节点A 、B 、 C中的某些槽移动到节点D就可以了。
与此类似,如果用户要从集群中移除节点A ,那么集群只需要将节点A中的所有哈希槽移动到节点B和节点C ,然后再移除空白(不包含任何哈希槽)的节点A就可以了。

5.事务
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

MULTI 命令用于开启一个事务
EXEC 执行所有事务块内的命令
DISCARD 取消事务
WATCH 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断

 

 1 package redis;
 2 
 3 import org.junit.Test;
 4 import redis.clients.jedis.Jedis;
 5 import redis.clients.jedis.Transaction;
 6 
 7 import java.util.List;
 8 
 9 /**
10  * 模拟刷一次信用卡的交易
11  */
12 public class TestTransaction {
13     @Test
14     public void test(){
15         boolean retValue = false;
16         boolean Interrupted = false;
17 
18         try {
19             retValue = transMethod(100);
20         } catch (Exception e) {
21             Interrupted = true;
22             System.out.println("事务被打断,请重新执行!");
23         } finally {
24             if (retValue) {
25                 System.out.println("使用信用卡消费成功!");
26             } else {
27                 if (!Interrupted) {
28                     System.out.println("使用信用卡消费失败!余额不足!");
29                 }
30             }
31         }
32     }
33 
34 
35     /**
36      * 通俗点讲,watch命令就是标记一个键,如果标记了一个键,
37      * 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中
38      * 重新再尝试一次。
39      * <p>
40      * 首先标记了balance,然后检查余额是否足够,不足就取消标记,并不做扣减;
41      * 足够的话,就启动事务进行更新操作。
42      * 如果在此期间键balance被其他人修改,拿在提交事务(执行exec)时就会报错,
43      * 程序中通常可以捕获这类错误再重新执行一次,直到成功。
44      */
45     private boolean transMethod(int amount) throws Exception {
46 
47         System.out.println("您使用信用卡预付款" + amount + "元");
48 
49         Jedis jedis = new Jedis("127.0.0.1", 6379);
50 
51         int balance = 1000;//可用余额
52         int debt;//欠额
53         int amtToSubtract = amount;//实刷额度
54 
55         jedis.set("balance", String.valueOf(balance));
56         jedis.watch("balance");
57 //        jedis.set("balance", "1100");//为了模拟其他程序已经修改了该条目
58         balance = Integer.parseInt(jedis.get("balance"));
59         if (balance < amtToSubtract) {//可用余额小于实刷金额,拒绝交易
60             jedis.unwatch();
61             System.out.println("可用余额不足!");
62             return false;
63         } else {//可用余额够用的时候再去执行扣费操作
64             System.out.println("扣费transaction事务开始执行...");
65             Transaction transaction = jedis.multi();
66             transaction.decrBy("balance", amtToSubtract);//余额减去amtToSubtract的钱数
67             transaction.incrBy("debt", amtToSubtract);//信用卡欠款增加amtToSubtract的钱数
68             List<Object> result = transaction.exec();//执行事务
69 
70             if (result == null) {//事务提交失败,说明在执行期间数据被修改过
71 
72                 System.out.println("扣费transaction事务执行中断...");
73                 throw new Exception();
74 
75             } else {//事务提交成功
76                 balance = Integer.parseInt(jedis.get("balance"));
77                 debt = Integer.parseInt(jedis.get("debt"));
78                 System.out.println("扣费transaction事务执行结束...");
79 
80                 System.out.println("您的可用余额:" + balance);
81                 System.out.println("您目前欠款:" + debt);
82 
83                 return true;
84             }
85         }
86     }
87 }

 

redis的事务存在的问题。redis只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令

以下是这种做法的优点:
1.Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
2.因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

参考资料:http://doc.redisfans.com/index.html

posted on 2017-05-18 15:23  一路有你一起同行  阅读(128)  评论(0编辑  收藏  举报

导航