Redis介绍
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
redis基础
redis默认有16个数据库,默认使用的是第0个。
可以使用select进行切分。
127.0.0.1:6379> select 3 #切换到3号数据库
OK
127.0.0.1:6379[3]> DBSIZE #查看数据库的DB大小
(integer) 0
127.0.0.1:6379[3]>
127.0.0.1:6379[3]> set name shuai #存放键值对数据
OK
127.0.0.1:6379[3]> get name
"shuai"
127.0.0.1:6379[3]> keys * #获取所有的key
1) "name"
127.0.0.1:6379[3]>
清除数据库:flushdb
127.0.0.1:6379[3]> flushdb #清空当前数据库
OK
127.0.0.1:6379[3]> keys *
(empty array)
127.0.0.1:6379[3]> flushall #清除全部数据库
Redis是单线程的。
核心:redis是将所有数据全部放在内存中的,所以使用单线程去操作效率是最高的,多线程的CPU上下文切换是耗时的操作,对于内存系统来说,如果没有上下文切换就是最高的,多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案。
五大数据类型
Redis-Key
127.0.0.1:6379> set name shuai #set key
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> exists name #查看当前key是否存在
(integer) 1
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> get name
"shuai"
127.0.0.1:6379> type name #查看当前key的类型
string
127.0.0.1:6379> expire name 10 #设置key的过期时间
(integer) 1
127.0.0.1:6379> ttl name #查看当前key的剩余时间
(integer) -2
127.0.0.1:6379>
127.0.0.1:6379> get name
String(类型)
append name ",c" #追加字符串
(integer) 8
127.0.0.1:6379> get name
"shuaic,c"
127.0.0.1:6379> set views 0 #初始浏览量为0
OK
127.0.0.1:6379> incr views#浏览量自增1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views#浏览量自减1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incrby views 10
(integer) 11
字符串范围:getrange
127.0.0.1:6379> set key1 "hello,shuai"
OK
127.0.0.1:6379> get key1
"hello,shuai"
127.0.0.1:6379> getrange key1 0 3 #截取字符串
"hell"
127.0.0.1:6379> getrange key1 0 -1#获取全部字符串
"hello,shuai"
字符串替换:setrange
127.0.0.1:6379> setrange key1 0 nihao#替换指定位置开始的字符串
(integer) 11
127.0.0.1:6379> get key1
"nihao,shuai"
设置过期时间:setex
127.0.0.1:6379> setex key1 10 "nihao" #设置key1的值为nihao ,10秒后过期
OK
127.0.0.1:6379> ttl key1
(integer) 3
127.0.0.1:6379> get key1
(nil)
不存在则设置:setnx。常用于分布式锁
127.0.0.1:6379> setnx mykey "redis"
(integer) 1
127.0.0.1:6379> get mykey
"redis"
批量操作数据:mset、mget、msetnx
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379> mget k1 k2 k3 k4#同时获取多个值
1) "v1"
2) "v2"
3) "v3"
4) "v4"
127.0.0.1:6379> msetnx k1 v1 k5 v5 #msetnx是一个原子性的操作,要么同时成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k5
(nil)
组合命令
127.0.0.1:6379> getset db redis #如果不存在值,则返回null
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mogodb#如果存在值,获取原理的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mogodb"
List列表类型
基本的数据类型,列表。相当于一个双端队列
在redis里,可以将list做栈、队列、阻塞队列。
所有的list命令都是用l开头的。
插入数据
127.0.0.1:6379> LPush list one#将一个值或多个值,插入到列表头部
(integer) 1
127.0.0.1:6379> LPush list two
(integer) 2
127.0.0.1:6379> LPush list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1 #通过区间获取具体的值
1) "three"
2) "two"
127.0.0.1:6379> RPUSH list right#将一个值或多个值,插入到列表尾部
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
移除数据
127.0.0.1:6379> Lpop list #移除list第一个元素
"three"
127.0.0.1:6379> rpop list#移除list 最后一个元素
"right"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 1 #通过下标获取list中的某一个值
"one"
127.0.0.1:6379> lindex list 0
"two"
127.0.0.1:6379> lrem list 1 one#移除list集合中指定个数的值,精确匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "two"
截取指定的数据
127.0.0.1:6379> rpush list "a"
(integer) 1
127.0.0.1:6379> rpush list "b"
(integer) 2
127.0.0.1:6379> rpush list "c"
(integer) 3
127.0.0.1:6379> rpush list "d"
(integer) 4
127.0.0.1:6379> ltrim list 1 2#通过下标截取指定的数据
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "b"
2) "c"
组合命令:rpoplpush
127.0.0.1:6379> rpush list "a"
(integer) 1
127.0.0.1:6379> rpush list "b"
(integer) 2
127.0.0.1:6379> rpush list "c"
(integer) 3
127.0.0.1:6379> rpoplpush list mylist#移除最后一个元素,并将其移到新的列表中
"c"
127.0.0.1:6379> LRANGE list 0 -1
1) "a"
2) "b"
将指定下标的值替换为另外的值,相当于更新操作
127.0.0.1:6379> lset list 0 item
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "item"
2) "b"
在指定值的前面加数据linsert before、after
127.0.0.1:6379> rpush list "a"
(integer) 1
127.0.0.1:6379> rpush list "b"
(integer) 2
127.0.0.1:6379> linsert list before "a" "and"
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "and"
2) "a"
小结
它实际上是一个链表,before Node afeter,left,right都可以插入值
如果key不存在,创建新的链表
如果key存在,新增内容
如果移除了所有值,空链表,也代表不存在
在两边插入或者改动值,效率最高
set(集合)
set中的值是不可重复的
127.0.0.1:6379> sadd set "a" #set里面添加值
(integer) 1
127.0.0.1:6379> sadd set "b"
(integer) 1
127.0.0.1:6379> sadd set "c"
(integer) 1
127.0.0.1:6379> SMEMBERS set#查看set的所有值
1) "a"
2) "c"
3) "b"
127.0.0.1:6379> SISMEMBER set a #判断某个值是否在set集合中
(integer) 1
移除元素
127.0.0.1:6379> srem set a#移除set里的指定元素
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "c"
2) "b"
随机抽取元素
127.0.0.1:6379> SRANDMEMBER set 2#随机抽取两个元素
1) "b"
2) "c"
127.0.0.1:6379> spop set #随机删除元素
"b"
数字集合类
127.0.0.1:6379> sadd set1 a
(integer) 1
127.0.0.1:6379> sadd set1 b
(integer) 1
127.0.0.1:6379> sadd set1 c
(integer) 1
127.0.0.1:6379> sadd set1 d
(integer) 1
127.0.0.1:6379> sadd set2 bb
(integer) 1
127.0.0.1:6379> sadd set2 b
(integer) 1
127.0.0.1:6379> sadd set2 c
(integer) 1
127.0.0.1:6379> sdiff set1 set2 #查看set1和set2的差集
1) "a"
2) "d"
127.0.0.1:6379> sinter set1 set2 #交集
1) "c"
2) "b"
127.0.0.1:6379> SUNION set1 set2#并集
1) "d"
2) "c"
3) "a"
4) "b"
5) "bb"
Hash(哈希)
Map集合,key-map(key-value),这个值是map集合
127.0.0.1:6379> hset hash si shuai #set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget hash si#获取一个字段值
"shuai"
127.0.0.1:6379> hmset hash s1 a s2 b#设置多个字段值
OK
127.0.0.1:6379> hmget hash s1 s2
1) "a"
2) "b"
127.0.0.1:6379> hgetall hash#获取全部的数据
1) "s1"
2) "a"
3) "s2"
4) "b"
127.0.0.1:6379> hdel hash s1#删除指定key字段
(integer) 1
127.0.0.1:6379> hgetall hash
1) "s2"
2) "b"
获取所有的key或value
127.0.0.1:6379> HEXISTS hash s2 #判断指定的字段是否存在
(integer) 1
127.0.0.1:6379> hkeys hash#获取所有的key
1) "s2"
127.0.0.1:6379> hvals hash#获取所有的value
1) "b"
127.0.0.1:6379> hlen hash#获取hash表的字段数量
(integer) 1
不存在则设置:hsetnx。常用于分布式锁
127.0.0.1:6379> hsetnx hash s1 hello#如果不存在则设置
(integer) 1
127.0.0.1:6379> hsetnx hash s1 nihao#如果存在则不设置
(integer) 0
hash变更的数据,user name age,经常变动的信息。
Zset(有序集合)
在set的基础上增加了一个值,set k1 v1 ; zset k1 score1 v1
127.0.0.1:6379> zadd myset 1 one # 添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three#添加多个值
(integer) 2
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
排序实现
127.0.0.1:6379> zadd salary 2500 zhangsan#添加三个用户
(integer) 1
127.0.0.1:6379> zadd salary 500 lisi
(integer) 1
127.0.0.1:6379> zadd salary 5000 wangwu
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf#显示全部用户,按从小到大排序
1) "lisi"
2) "zhangsan"
3) "wangwu"
127.0.0.1:6379> ZREVRANGE salary 0 -1#从大到小排序
1) "wangwu"
2) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscoresf#显示全部用户,按从小到大排序并附带值
1) "lisi"
2) "500"
3) "zhangsan"
4) "2500"
5) "wangwu"
6) "5000"
移除元素
127.0.0.1:6379> zrange salary 0 -1
1) "lisi"
2) "zhangsan"
3) "wangwu"
127.0.0.1:6379> zrem salary lisi#移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "wangwu"
127.0.0.1:6379> zcard salary #查看有序集合中的个数
(integer) 2
三种特殊数据类型
geospatial地理位置
Redis的Geo。可以推算地理位置的信息。
六个命令
相关链接:http://www.redis.cn/commands/geoadd.html
geoadd:添加地理位置,一般通过Java程序一次性导入
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
geopos:获取指定的经度与纬度
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
geodist:返回两个给定距离间的距离
127.0.0.1:6379> geodist china:city beijing chongqing
"1464070.8051"
127.0.0.1:6379> geodist china:city beijing chongqing km
"1464.0708"
georadius:以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km
1) "chongqing"
georadiusbymember:根据给定的位置元素找到指定范围内的元素
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 10000 km
1) "chongqing"
2) "beijing"
GEOHASH:返回一个或多个位置元素的 Geohash 表示。
geo底层实现原理其实就是Zset,可以通过Zset命令操作GEO
Hyperloglog基数
A{1,3,5,7,8,8,9}
B{1,3,5,7,8,9}
基数(不重复元素)=6
主要用于计数。
127.0.0.1:6379> PFADD mykey a b c d e f g h i j #创建第一组元素mykey
(integer) 1
127.0.0.1:6379> PFCOUNT mykey
(integer) 10
127.0.0.1:6379> PFADD mykey2 i j z x y c b n m#创建第二组元素mykey2
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2#合并两组元素
OK
127.0.0.1:6379> PFCOUNT mykey3#查看并且的数量
(integer) 15
Bitmaps
位存储
统计用户信息,活跃,不活跃,登录,未登录,打卡,365打卡;都可以使用Bitmaps
Bitmaps位图,数据结构,都是操作二进制位来进行记录,就只有0和1两个状态。
365天=365bit 1字节=8bit
使用bitmap来记录周一到周日的打卡情况。
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
查看某一天是否打卡
127.0.0.1:6379> GETBIT sinn 3
(integer) 0
统计操作,打卡天数
127.0.0.1:6379> BITCOUNT sign
(integer) 5
事务
Redis事务的本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行的过程中,会按顺序执行。
一次性、顺序性、排他性执行一些列的命令。
Redis事务没有隔离级别的概念。
所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行。
Redis单条命令是保证原子性的,但是事务不保证原子性。
redis的事务:
开启事务(Multi)
命令入队(…)
执行事务(exec)
127.0.0.1:6379> multi#开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec#执行事务
1) OK
2) OK
3) OK
放弃事务
127.0.0.1:6379> multi#开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD#取消事务
OK
127.0.0.1:6379> get k4#事务中的命令都不会执行
(nil)
悲观锁
很悲观,什么时候都会出问题,无论做什么都会加锁。
乐观锁
很乐观,认为什么时候都不会出问题,所以不会上锁,更新数据的时候去判断一下,在此期间是否有人修改过这个数据。
获取version
更新的时候比较version
Redis监视测试
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money #监视money对象
OK
127.0.0.1:6379> multi #事务正常结束,数据期间没有发生变动,这个时候正常执行成功
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
测试多线程修改值,使用watch可以当作redis的乐观锁操作!
事务执行之前,另外一个线程,修改了相关的值,就会导致事务执行失败。
解决:unwatch先解锁,再watch加锁获取最新的值。
Jedis
Redis官方推荐的Java连接开发工具,使用Java操作Redis中间件。
1.导入依赖包
2.连接数据库
3.关闭数据库
//1.new jedis对象
Jedis jedis = new Jedis("1.15.3.239",6379);
//2.输入指令
System.out.println(jedis.ping());
jedis.close();
此处指令就是上面的redis指令
SpringBoot整合
1.导入依赖
2.配置连接
#SpringBoot所有的配置类,都有一个自动配置类
#自动配置类会绑定一个properties配置文件 RedisPropertis
spring.redis.host=1.15.3.239
spring.redis.port=6379
3.测试
Redis持久化
很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
快照(snapshotting)持久化(RDB)
在指定时间间隔内将内存中的数据集中快照写入磁盘,也就是Snapshot快照,它恢复时是将快照文件直接读到内存里。
redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
快照持久化是 Redis 默认采用的持久化方式,在 Redis.conf 配置文件中默认有此下配置:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
rdb触发机制:
- save的规则满足的情况下,会自动触发rdb规则
- 执行flushall命令,也会触发rdb规则
- 退出redis,也会产生rdb文件
恢复rdb文件:
只需要将rfb文件放在redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据。
查看需要存在的位置:
config get dir
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进程操作,
- fork进程的时候,会占用一定的内存空间
AOF(append-only file)持久化
将所有命令都记录下来(history),恢复的时候就将这个文件全部执行一遍。
aof默认没有打开,可通过以下命令开启aof
appendonly yes
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入硬盘中的 AOF 文件。AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步
优点:
- 每一次修改都同步,文件的完整性更加好
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高的
缺点:
- 相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
- aof运行效率也要比rdb慢,所以redis默认配置是rdb
Redis发布订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送信息,订阅者(sub)接收消息。
Redis客户端可以订阅任意数量的频道。
Redis主从复制
主从复制:将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点,后者称为从节点;数据的复制是单向的,只能由主节点到从节点。
主从复制,读写分离。80%的情况下都是在进行读操作,减缓服务器的压力,架构中经常使用。最低为一主二从。
复制原理:
Slave启动成功连接到master后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制;增量复制。
哨兵模式
主从切换技术的方法是:当服务器宕机后,需要手动把一台从服务器切换到主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用。这时候就考虑哨兵模式。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵模式能够后台监控主机是否故障,如果故障了根据投票数 自动将从库转换为主库。
优点:
- 哨兵集群,基于主从复制模式
- 主从可以切换,故障可以转移,系统可用性就会更好
- 哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点:
- Redis不好在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦
- 实现哨兵模式的配置比较麻烦
Redis缓存穿透和雪崩
缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。