目录
Redis 特点:
由于访问量上升,使用SQL架构的网站在数据库上都出现了性能问题,而redis属于NoSql,它可以作为数据库,缓存和消息中间件。缓存的作用可以提高速度,例如有的数据经常的被访问但是不用频繁的被修改就可以放到缓存里,就可以直接读取而不需要再进行计算。一般来说都是固定不变的数据。缓存不适合持久储存海量数据。
Redis支持多种数据结构(只有这五种),如字符串String, 散列(hashes), 列表(list),集合(set), 有序集合(sorted set)。
一、五种数据类型
1. select
选择储存的数据库
select 1
选择第几个数据库,这里是第二个
2. 字符串(String)
1)添加String
set [key] [value]
ex:
set name longwang
2)获取String
get [key] [value]
3)批量设置
m (multi)
mset [key] [value] [key] [value]...
ex:
mset age 20 addr shanghai
4)批量获取
mget [key]...
ex:
mget age addr
2. 散列(hashes)
因为哈希也是key value所以格式是
1)hash设置
hset [key] [field(也就是哈西的value)] [value]
ex:
hset user name chuangw
2)hash获取(一样的)
hget user name
3)hash多条设置
hmset [key] [field] [value] [field] [value]
ex:
hmset user age 20 addr shanghai
4)hash多条获取
hmget user age addr
5)获取key下的所有值
hgetall [key]
ex:
hgetall user
6)hash删除
hdel key field field...
ex:
hdel user age addr
3. 列表(list)
1)从两边加
lpush/rpush key value value
ex:
lpush students longwang chuanwang haoge
rpush students bingsong haoyong
2)显示值
lrange key start stop
stop 写-1话就是显示全部, 如果大于上限也是显示全部.(-2会显示少一个,以此类推)
ex:
lrange student 0 4
添加步骤逻辑:
左添加最后一个永远在最左边,右添加永远在最右边
a.
b.
c.
d-e.
3)显示list length
llen key
4)删除(remove)
lrem key count value
count:删除几个这个值,因为可以有重复值。删除顺序是从左到右。如果超过数量则全删,不会报错
lrem students 1 haoyong
4. 集合(Set)
特点:无序(其实有内部排序),不可重复(重复的会被删掉不会报错)
1)添加
sadd key member member
ex:
sadd letters aaa bbb ccc ddd aaa
发现只加了4条,因为aaa重复了而set不能重复
2)获取set
smembers key
发现顺序是乱的(其实是内部排序了)
3)查询Set大小
scard key
4)删除set数据
srem key member member
5. 有序集合sorted set
1)添加
zadd key score member [score memeber]
score: 按照分数给member 排序, 分数从小到大排序
2)查看条数
zcard key
3)查看set成员
zrange key start stop
4)删除
zrem key member member
6. 通用指令
1)通用删除
del key key
之前的String 是没有删除的可以“del”来删除。del可以删除不同类型的数据
这是是删了String 的name 和 sorted set
2)查询所有的keys
keys pattern
* 星号就是所有
二、层级目录
可以更方便的查看不同用户购物车里的物品
1.层级目录设置
可以在RMD中看到
2.层级目录获取
三、失效时间
例子:手机验证码10分钟有效时间,超出时间需要重新申请。
设置失效时间有三种方式。1.在设置值的时候直接设置时间。2,给已有的值设置时间。
1.设置值时设置失效时间
在key value后+ ex设置秒/px设置毫秒
a. 查看剩余时间
ttl key //秒
pttl key //毫秒
还剩14秒
----------------------------------------------------------------------------------------------------------------
-2 代表已经完全失效
DRM里也可以查看TTL, 时间到了就会消失
----------------------------------------------------------------------------------------------------------------
设置一个不带时间的,显示-1。代表永远有效。
2. 给已有的值设置时间。
expire key seconds //秒
pexpire key ms //毫秒
3. NX/XX
NX:表示这个key不存在,才能设置成功
XX:表示这个key存在,才能设置成功
这个可以用来做锁,来占位置。
scenario analysis:
去取钱,第一个先取,然后锁住花(22秒)取完了才能到下一个人。
但是这么写不完善,(例如万一花了30秒才完成的话,就会把后一个人的锁取消掉)具体需要学习 "redlock" 和 "lua脚本"。
监控(watch):
悲观锁:认为什么都会出问题,无论什么都会加上锁。(效率很低)
乐观锁:认为什么都不会出问题,所以不会上锁。只有更新数据的时候回去判断一下,在此期间数据是否被修改了。
1.获取version
2.更新的时候比较version
例:模拟多线程,开两个客户端
客户端1:
这里监视money,但是注意这里不执行。
客户端2:
直接进行修改Money.
回到客户端1,执行:
返回nil说明执行失败。
只要执行了修改,不管值是否一样,都会触发Watch.
四、事务操作
事务就是一组命令的集合,一个事务中所有命令都会被集合化,执行中所有命令都会按顺序执行。会一次性,顺序性,排他性地执行这些命令。
Redis事务没有隔离级别地概念,命令在事务中不会被执行。只有在命令发起是在会被执行。
1. 创建/执行事务
步骤:
1.开启事务
multi
2.命令入列
3.执行事务。
exec
ex:
2. 删除/放弃事务 (discard)
放弃事务之后,所有命令都不会被执行。
3. 异常处理
编译异常: 命令错误,代码语法不对。那么事务中所有命令都不会被执行。
注:getset key value。这里没有value。就算剩下的命令没有问题也不会执行。
运行时异常: 计算时的异常(例如:1除0),那么这条会抛异常,剩下的命令正常执行。
Incr 命令将 key 中储存的数字值增一。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
五、IDEA中的Redis
1.Jedis连接Redis
先创建新的project
勾选这两个。
由于2.0版本后redis默认配置lettuce客户端,所以先要移除依赖
在这里修改
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
操作:
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
jedis.set("name","yao");
String name = jedis.get("name");
System.out.println("here is "+name);
if (null != jedis){
jedis.close();
}
输出:
2. 操作五大变量
1)String
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
//设值,单条
jedis.set("name","yaoshuige");
//取值,单条
String name = jedis.get("name");
System.out.println("here is "+name);
//设值,多条
jedis.mset("age","30","addr","shamghai1");
//取值,多条
List<String> list = jedis.mget("age", "addr");
list.forEach(System.out::println);
//删除
jedis.del("addr");
// String addr = jedis.get("addr");
list = jedis.mget("age", "addr");
System.out.println("删除后");
list.forEach(System.out::println);
if (null != jedis){
jedis.close();
}
输出:
2)hashed
//设值,单条
jedis.hset("rapper","name","agiao");
取值,单条
String name = jedis.hget("rapper", "name");
System.out.println(name);
//设值,多条
Map<String,String> map = new HashMap<>();
map.put("say","Hi!");
map.put("words","Hello!");
jedis.hmset("rapper",map);
List<String> list = jedis.hmget("rapper", "words","say");
//取值,多条
list.forEach(System.out::println);
//删除
jedis.hdel("rapper","words");
System.out.println("删除后:");
list.forEach(System.out::println);
输出:
3)List
//list
//push
jedis.lpush("students","s1","s2","s3");
jedis.rpush("students","s4","s5");
//取值
List<String> students = jedis.lrange("students", 0, -1);
students.forEach(System.out::println);
//获取长度
Long size = jedis.llen("students");
System.out.println("the size is: "+size);
//删除
jedis.lrem("students",1,"s2");
students = jedis.lrange("students", 0, -1);
students.forEach(System.out::println);
4)Set
//set
//赋值
jedis.sadd("letters","a","b","c");
//取值
Set<String> letters = jedis.smembers("letters");
letters.forEach(System.out::println);
//查看大小
Long size = jedis.scard("letters");
System.out.println("size is "+ size);
//删除
jedis.srem("letters","a");
letters = jedis.smembers("letters");
letters.forEach(System.out::println);
输出:
5)Sorted Set
//sorted set
//添加数据
Map<String, Double> map = new HashMap<>();
map.put("张三",5D);
map.put("张二",7D);
map.put("张一",4D);
jedis.zadd("score",map);
//获取数据
Set<String> set = jedis.zrange("score", 0, -1);
set.forEach(System.out::println);
//获取长度
Long size = jedis.zcard("score");
System.out.println(size);
//删除
jedis.zrem("score","张三");
set = jedis.zrange("score", 0, -1);
set.forEach(System.out::println);
输出:
3.通用命令
1)层级目录
//层级目录
jedis.set("use:01:item","iphone");
String iphone = jedis.get("use:01:item");
System.out.println(iphone);
//查询所有Key
Set<String> set = jedis.keys("*");
set.forEach(System.out::println);
//获取数据库大小
System.out.println(jedis.dbSize());
输出:
2)设置锁(NX/XX)
//失效时间
//设值失效时间
jedis.setex("code",10,"test");
//给已存在的key设置失效时间
jedis.expire("score",20);
//查询失效时间
Long ttl = jedis.ttl("code");
System.out.println(ttl);
//NX:不存在时设置成功
//XX:存在时设置成功
SetParams params = new SetParams().xx().ex(30);
jedis.set("code","test",params);
3)操作事务
一般很少用的,因为redis的事务能力非常弱。会用到redis的watch事件,类似于乐观锁
//操作事务
//开启事务
Transaction tx = jedis.multi();
tx.set("code","test");
//执行事务
tx.exec();
//回滚事务
tx.discard();
六、磁盘持久化方案
redis是在内存中操作数据,所以有数据丢失的风险,例如在redis宕机时。
为了应对这个问题,redis有两种解决方案:快照(rdb)和aof。
1.快照(rdb)
1)使用“bgsave”命令
有点:方便快捷,一行命令就可以做到。
缺点:因为不确定什么时候会宕机,所以为了以防万一每一行都要打,很麻烦。
2)设置自动储存
打开redis.comf配置文件,可以看到
当达到条件时,就会启动后台自动保存,存到dump.rdb里。
这里有3条分别是:
900秒内,如果有1个key发生变化时储存
300秒内,如果有10个key发生变化时储存
60秒内,如果有10,000个key发生变化时储存
这个条件可以自行编辑。
快照原理:保存到了本地的rdb文件中了
打开redis.comf配置文件,可以看到快照的储存名和保存路径
打开rdb文件
发现里面保存的是key value。当redis出故障重启时,会将key value从rdb中读取,重新写入redis。
优点:不需要一直bgsave
缺点:如果达不到要求的数据还是会丢失,仍然有数据丢失的风险。
2.aof
开启方法,找到appendonly,把no改成yes
默认情况下,如果启动了appendonly,则rdb默认失效
appendonly是实时记录,所有数据都会被保留。
打开appendonly.aof
可以看到里面保存的是命令而不是key value。输入的每条命令都会被保存进来,当redis重启时,所有的命令会被重新输入进redis。
优点:实时保存。
缺点:数据量更大,rdb直接存key value,aof存完整的命令。所以aof文件占用大小会比rdb更大。当文件大到一定程度时,重启时的速度也会变慢。
但是redis 4.0之后,rdb和aof可以混合开启。可以拥有两方面的优点,但是执行效率也会降低。
七、主从关系
概念:
当redis只有一个时(单节点),读取的压力会很大(因为redis一般作为缓存,里面数据不更改所以不考虑写入的压力)。当有多个时,能分流很多主节点的读取压力,提高可用性。
在集群环境下,大于一半不可用、 整个集群不可用。
哨兵(sentinel)
当主节点下线(down)后,哨兵会开始选取新的从节点变成主节点,而且同级的从节点会被划分进新的主节点下。当主节点回复时,也会变成新的主节点的从节点。
哨兵判断主节点下线的方式为ping,当哨兵超过30秒没收到主节点返回的pong时,将会被判断为掉线。当超过一般的哨兵判断主节点掉线时,便会看是选取新的主节点。(为什么要多个哨兵?因为有可能是网络波动造成的)
当选举过程超时时,会重新开始选举,不会一直卡主。
默认超时时间为3分钟。