Redis
为什么要使用redis
01
遇到的问题
当我们的系统引入了传统的缓存框架,比如(ehcache),因为ehcache等框架只是一个内置的缓存框架,所以前端的缓存和后台的(每一个web server)的缓存都是独立存在的,假如一个缓存中的数据发生了更新,其他缓存是不可能知道的,这样对于乐观锁,总会提示失败;
在分布式环境下,缓存不能同步;
解决
让缓存集中处理(大家使用同一个缓存服务),我们需要一个类似于MYSQL这样可以通过服务来提供第三方的缓存工具(缓存服务器);
流行的第三方缓存服务器:memcache/redis
认识redis
02
Remote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统(可以把redis想象成一个巨大的MAP)。
理解
redis可以看成一个Map(key-value)
在redis中,所有的key都可以理解为byte[](String)
VALUE
在memcache中,value也只能是byte[](String);------------>Map<String,String>
在redis中,value的可选类型很多,String,list,set,orderset,hash
Map<String,List> Map<String,Set> Map<String,String> Map<String,Map<>>
优势
性能极高 – Redis能支持超过 10W次每秒的读写频率。
丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行(简单的事务)。
丰富的特性 – Redis还支持 publish/subscribe(发布/订阅), 通知, key 过期等等特性。
理念
redis是一个key-value的内存存储应用(使用redis主要还是把数据存在内存中,这个可以最大的使用redis的性能优势)
redis可以把数据存储在内存中,也可以持久化到磁盘上
redis不是一个适用于任何场景的存储应用
我们之前介绍数据库:关系型数据库,面向对象数据库,**NoSQL(Not only SQL)--->(KEY-VALUE)**内存数据库
redis不光可以作为一个缓存,他还是一个高效的内存数据库;可以在某些情况下,高效的替换到传统的关系型数据库,非常好的处理好高并发,高请求的场景
学redis
redis在线入门 : http://try.redis.io/
redis 中文资料站: http://www.redis.cn/
redis入门
两个最基本的redis命令
SET KEY VALUE:把VALUE保存到redis中KEY对应的值
GET KEY:取出redis中KEY对应的值
关于Redis中的key
在示例中,key的值为server:name:代表server的name;这是redis中key取名的一个规范
我们之前说到redis不光是一个缓存,还可以看成一个数据库,这种数据库和传统的关系型数据库最大的区别就在于,传统的关系型数据库,在保存数据之前,都已经有一个固定的schema;内容是保存在一个schema中不同行的数据;所有的数据都存在在一张指定的表中,所有的内容都对应表里面一个指定的行,每一个行都有固定的数据类型
所以,当我们在使用关系型数据库表现一个对象(数据结构的时候),我们能够事前通过schema规范好这个对象的结构
但是,对于redis来说,整个数据库就是一个Map,没有任何结构可言,所有通过set等方法扔到redis中的数据,可以简单理解为就是所有的数据乱七八糟的放在一个map中
问题,在redis中,怎么存储一个User??(Map能不能通过value查询???不能,换句话说,在redis中,要能够支持查询的属性,都必须反映在key上,换句话说,redis的key的作用:1,反映数据的结构,2反映查询的内容)
redis的基本概念
03
数据库
redis也有数据库的概念,一个数据库中可以保存一组数据
各个数据库之间是相互隔离的,当然也可以在不同数据库之间复制数据
一般一个应用会单独使用一个数据库
每一个数据库都有一个id号,默认的数据库id为0
可以使用select命令选择当前使用的数据库
select 1;redis默认为我们创建16个数据库,这个参数可以在redis配置文件中使用databases修改
Command(命令)
redis中提供了非常大量的命令来方便的操作数据库中的数据,还可以使用redis中的一些特性
redis的命令可以简单理解为mysql的SQL命令
redis命令分为
- 对数据的操作
- 发布/订阅相关操作
- 事务控制
- 脚本命令
- 连接服务器命令
- 数据库服务相关命令
KEY-VALUE
key用来标记一个数据;一般在key中需要尽量标明数据的名字(还可以使用key来表明数据所属类型),比如用于标示一个对象的时候,可以使用user:1000来作为key,代表id为1000的用户对象
value表示一个key对应的值;在redis中,数据可以是任何内容,redis把所有的value都作为byte处理;所以可以用来保存任何内容
redis最突出的特点是提供了5种常用的数据存储类型(value的类型),深刻理解这5中数据结构和各自的使用场景,对redis的使用有很大帮助
存储
redis可以作为内存数据库,也可以把数据持久化到磁盘上;大部分情况下,都是把redis作为内存数据库;
默认情况下
# after 900 sec (15 min) if at least 1 key changed # after 300 sec (5 min) if at least 10 keys changed # after 60 sec if at least 10000 keys changed save 900 1 save 300 10 save 60 10000数据默认存储在安装目录下.rdb文件中(可以在配置文件中dbfilename dump.rdb配置)
redis也可以设置为append模式,每次key的修改都会append到文件中,这种方式有可能丢失60秒的数据
通过配置:appendonly yes开启
appendfilename "appendonly.aof"设置append文件
可以设置append的模式(类似于mysql的事务文件同步机制)
# appendfsync always:每次更新key及时同步到append文件; appendfsync everysec:每一秒同步一次key的更新; # appendfsync no:不管理append文件的更新,根据操作系统去定
redis中的string
04
常见的字符串操作
strlen key:返回key的value的值长度
getrange key X Y:返回key对应value的一个子字符串,位置从X到Y
append key value:给key对应的value追加值,如果key不存在,相当于set一个新的值
如果字符串的内容是数值(integer,在redis中,数值也是string)
incr key:在给定key的value上增加1;(常用于id);redis中的incr是一个原子操作,支持并发;如果key不存在,则相当于设置1
incrby key value:给定key的value上增加value值;相当于key=key.value+value;这也是一个原子操作
decr:在给定key的value上减少1
decrby key value:给定key的value上减少value值
常见的使用场景
存储json类型对象
incr user:id set user:1 {id:1,name:xiaolong} incr user:id set user:2 {id:2,name:stef}作为计数器, incr count;优酷视频点赞
incr video:100:goodcount decr video:100:goodcount
redis中的list
05
redis的LIST结构(想象成java中的LinkedList),是一个双向链表结构,可以用来存储一组数据
从这个列表的前端和后端取数据效率非常高
常用操作
RPUSH:在一个list最后添加一个元素
RPUSH firends "stef"LPUSH:在一个list最前面添加一个元素
LPUSH firends "stea"LTRIM key start stop:剪裁一个列表,剩下的内容从start到stop
LTRIM friends 0,3
//只剩下前4个数据;LRANGE key start stop:获取列表中的一部分数据,两个参数,第一个参数代表第一个获取元素的位置(0)开始,第二个值代表截止的元素位置,如果第二个参数为-1,截止到列表尾部
LRANGE firends 0 -1LLEN key: 返回一个列表当前长度
LLEND friendsLPOP:移除list中第一个元素,并返回这个元素
LPOP friendsRPOP:移除list中最后一个元素,并返回这个元素
RPOP friends使用场景
可以使用redis的list模拟队列,堆栈
朋友圈点赞
/* 规定朋友圈内容的格式 内容: user:x:post:x content来存储 点赞: post:x:good list来存储 */ //创建一条微博内容 set user:1:post:91 'hello redis'; //点赞 lpush post:91:good '{id:1,name:stef,img:xxx.jpg}'; lpush post:91:good '{id:2,name:xl,img:xxx.jpg}'; lpush post:91:good '{id:3,name:xm,img:xxx.jpg}'; //查看有多少人点赞 llen post:91:good; //查看哪些人点赞 lrange post:91:good 0 -1;思考,如果用数据库实现这个功能,SQL会多复杂??
回帖
//创建一个帖子 set user:1:post:90 'wohenshuai'; //创建一个回帖 set postreply:1 'nonono'; //把回帖和帖子关联 lpush post:90:replies 1; //再来一条回帖 set postreply:2 'hehe'; lpush post:90:replies 2; //查询帖子的回帖 lrange post:90:replies 0 -1; get postreply:2; get postreply:1;
redis中的set
SET结构和java中差不多,数据没有顺序,并且每一个值不能重复
常见操作
SADD:给set添加一个元素
sadd language 'java';SREM:从set中移除一个给定元素
srem language 'php';SISMEMBER:判断给定的一个元素是否在set中,如果存在,返回1,如果不存在,返回0
sismember language 'php';SMEMBERS:返回指定set内所有的元素,以一个list形式返回
smembers language;SCARD:返回set的元素个数
scard language;SRANDMEMBER key count:返回指定set中随机的count个元素
srandmember friends 3; //随机推荐3个用户(典型场景,抽奖)SUNION(并集):综合多个set的内容,并返回一个list的列表,包含综合后的所有元素;
sadd language 'php';
sadd pg 'c';
sadd pg 'c++';
sadd pgs 'java';
sadd pgs 'swift';
sunion language pg pgs;SINTER key [key ...] (交集):获取多个key对应的set之间的交集
SINTER friends:user:1000 friends:user:1001 friends:user:1002 =>获取1000,1001,1002三个用户的共同好友列表;
SINTERSTORE destination key [key ...] :获取多个key对应的set之间的交集,并保存为新的key值;目标也是一个set;
SINTER groupfriends friends:user:1000 friends:user:1001 friends:user:1002 =>获取三个用户共同的好友列表并保存为组好友列表;
使用场景
去重
抽奖
准备一个抽奖池 sadd luckdraws 1 2 3 4 5 6 7 8 9 10 11 12 13 抽3个三等奖 srandmember luckdraws 3 srem luckdraws 11 1 10 抽2个二等奖 srandmember luckdraws 2 srem luckdraws 3 7做set运算(好友推荐)
初始化好友圈 sadd user:1:friends 'user:2' 'user:3' 'user:5' sadd user:2:friends 'user:1' 'user:3' 'user:6' sadd user:3:friends 'user:1' 'user:7' 'user:8' 把user:1的好友的好友集合做并集 user:1 user:3 user:6 user:7 user:8 让这个并集和user:1的好友集合做差集(这个并集必须在前面,且结果要存储起来,sdiffstore) user:1 user:6 user:7 user:8 从差集中去掉自己 user:6 user:7 user:8 随机选取推荐好友
sorted set
07
SET是一种非常方便的结构,但是数据无序
redis提供了一个sorted set,每一个添加的值都有一个对应的分数,可以通过这个分数进行排序;sorted set中的排名是按照分组升序排列
常用操作
ZADD:添加一个带分数的元素,也可以同时添加多个
ZADD hackers 1940 "Alan Kay"
ZADD hackers 1906 "Grace Hopper"
ZADD hackers 1969 "Linus Torvalds"
ZADD hackers 1940 "Alan Kay" 1906 "Grace Hopper" 1969 "Linus Torvalds"ZCOUNT key min max :给定范围分数的元素个数
ZCOUNT hackers 1940 1960 =>1940到1960的hacker个数ZRANK key member :查询指定元素的分数在整个列表中的排名(从0开始)
ZRANK hackers "Alan Kay" =>alan kay的年龄在所有hacker中的排名ZRANGE key start stop:按照分数从小到大排
zrange hackers 0 -1 =>
1) "Grace Hopper"
2) "Alan Kay"
3) "Linus Torvalds"ZREVRANGE key start stop:按照分数从大到小排;
使用场景
sorted set算是redis中最有用的一种结构,非常适合用于做海量的数据的排行(比如一个巨型游戏的用户排名);sorted set中所有的方法都建议大家去看一下;sorted set的速度非常快;
天梯排名
添加初始排名和分数
查询fat在当前ladder中的排名
查询ladder中的前3名
jian增加了20ladder score

LRU淘汰最长时间没使用
LFU淘汰最低使用频率
hashes
08
hashes可以理解为一个map,这个map由一对一对的字段和值组成
所以,可以用hashes来保存一个对象
常见操作
HSET:给一个hashes添加一个field和value
HSET user:1000 name "John Smith"
HSET user:1000 email "john.smith@example.com"
HSET user:1000 password "s3cret"HGET可以得到一个hashes中的某一个属性的值
HGET user:1000 name =>"John Smith"HGETALL:一次性取出一个hashes中所有的field和value,使用list输出,一个field,一个value有序输出
HGETALL user:1000 =>
1) "name"
2) "John Smith"
3) "email"
4) "john.smith@example.com"
5) "password"
6) "s3cret"HMSET:一次性的设置多个值(hashes multiple set)
HMSET user:1001 name "Mary Jones" password "hidden" email "mjones@example.com"HMGET:一次性的得到多个字段值(hashes multiple get),以列表形式返回
HMGET user:1001 name email =>
1)"Mary Jones"
2)"mjones@example.com"HINCRBY:给hashes的一个field的value增加一个值(integer),这个增加操作是原子操作
HSET user:1000 visits 10
HINCRBY user:1000 visits 1 => 11
HINCRBY user:1000 visits 10 => 21HKEYS:得到一个key的所有fields字段,以list返回
HKEYS user:1000 =>
1)"name"
2)"password"
3)"email"HDEL:删除hashes一个指定的filed
HDEL user:1000 visits使用场景
使用hash来保存一个对象更直观;(建议不使用hash来保存)
分组
//set user:id 1 //set dept:id 1 HMSET ids user:id 1 dept:id 1 orderbill:id 1 HINCRBY ids user:id HINCRBY ids dept:id HMSET users user:1 "{id:1,name:xx}" user:2 "{id:2,name:xx}"
jedis
09
Jedis jedis = new Jedis("localhost", 6379);
/*
方法同jedis的命令
*/
jedis.close();mybatis-redis.jar
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-redis -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>User u = new User(1L, "aw");
Jedis jedis = new Jedis("localhost", 6379);
/* 存储对象 */
jedis.set("user:1".getBytes(), SerializeUtil.serialize(u));
/* 取出对象 */
User u2 = SerializeUtil.deserialize(jedis.get("user:1").getBytes());
jedis.close();
System.out.println(u2);redis作为mybatis的缓存
10
导包
mybatis-redis.jar
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-redis -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>配置redis.properties
redisConfig要用,也可以没有
host=localhost
port=6379
connectionTimeout=5000
soTimeout=5000
password=
database=0
clientName=相关mapper.xml中配置
<cache type="org.mybatis.caches.redis.RedisCache" />