redis详解
参考 b 站狂神说的视频
https://www.bilibili.com/video/BV1S54y1R7SB?p=36&share_source=copy_web
非关系型数据库:nosql 数据库
非关系型数据库的优点
1。方便扩展(数据之间没有关系,很好扩展)
2。大数据高性能(nosql 的缓存记录数)
3。数据类型是多样型的 (不需要事先设计数据库 随去随用)
高性能 高可用 高可扩
NOSQL的四大分类
K V键值对. ————->缓存 日志
美团:redis tait
阿里 百度:redis memcache
文档型数据库 —————> 分布式文件系统
MongoDB(必须掌握 )
基于分布式文件存储的数据库 C++ 编写,主要用来处理大量的文档
mongoDB 是一个介于关系型数据库和非关系型数据库的中间的产品。MongoDB 是非关系型数据库中功能最丰富 最像非关系型数据库的
ConthDB
列存储数据库—-》web 应用 HBase
图关系数据库——-〉社交网络 Neo4j
2.Redis 是什么
1.redis 远程字典服务 由c 语言编写,支持网络的,基于内存的可持久化 日志型,key value 数据库。支持多种语言的API
免费和开源。 结构化数据库。
作用:
内存存储 持久化 (内存中是断电即失)(持久化 rdb aof)
效率高 可以用于高速缓存
发布订阅系统(简单消息队列)
地图信息分析
计时器 计数器
特性
1. 多样的数据类型
2. 持久化
3.集群
4,事务
测试性能
Redis-benchmark 压力测试工具
测试 100个并发 100000请求
Refis-benchmark -h localhost -p 6379 -c 100 -n 100000
Redis 推荐在 linux上搭建
3.Redis基础知识
Redis默认有 16个数据库 默认使用的第0 个
可以使用select 进行切换数据库
1 2 3 4 5 6 7 | eg:select 3 keys * 查看所有的key flushall 清空所有的数据库 flushdb 清空当前数据库 |
Redis 是单线程的
Redis 是基于内存操作,CPU 并不是redis 的瓶颈,根据机器的内存和网络带宽
为什么单线程还这么快?
CPU>内存 >硬盘
Redis 是将所有的数据都存在内存中,对于内存系统来说,没有上下文切换效率就是最高的。
它可以用作数据库 缓存 消息中间件。
支持多种类型的数据结构 字符串 散列 列表 集合 有序集合与范围查询
redis 的五大数据类型
1 | Redis-Key |
1 2 3 4 5 | 判断是否存在 exists key值 移除指定的key值 Move key值 数据库 设置过期时间,单位是秒 expire name 10 查看key的剩余时间 ttl key值 查看key的类型 type key值 |
string
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | set key值 value 赋值 get key值 获取值 APPEND key值 “hello” 拼接字符串 如果key不存在就新建 STRLEN key值 字符串长度 incr key值 对应的值加 1 decr key值 对应的值减 1 INCRBY key值 步长 可以设置指定增量 eg:INCRBY views 10 对应的值加 10 获取范围 eg:set key1 “hello,zhangsan” GETRANGE key1 0 3 “hell” GETRANGE key1 0 - 1 //查看全部字符串 “hello,zhangsan” |
替换
1 2 3 4 5 6 7 8 | set key2 abcdefg SETRANGE key2 1 xx //替换指定位置开始的字符串 get key2 ->axxdefg setex(set with expire) 设置过期时间 eg:setex key3 30 “hello” setnx(set if not exists) 不存在设置 eg:setnx mikes “redis” |
批量获取和批量设置
1 2 3 4 5 6 7 8 9 10 11 12 13 | 批量设置 mset eg: mset key1 value1 key2 value2 key3 value3 批量获取 mget eg: mget key1 key2 key3 msetnx key1 value1 key4 value4 //msetnx是个原子性操作,一起成功 一起失败 对象 设置一个user对象 user:{id}:{filed} mset user: 1 :name zhangsan user: 1 :age 18 mget user: 1 :name user: 1 :age 1 )”zhangsan” 2 )” 18 ” |
先 get 后 set
1 2 3 4 5 | getset 先get后set getset db redis ->(nil) get db —>redis Getset db mongdb —->redis get db—>mongdb |
string 类型的应用:
计数器
统计数量
统计多单位的数量
对象缓存
List
列表 堆 栈
所有的list命令都是l开头的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 添加 LPUSH list one //每次将数据插入头部 LPUSH list two 获取 LRANGE list 0 - 1 -》“two” -》“one” Rpush list right //每次将数据放到尾部 LRANGE list 0 - 1 -》“two” -》“one” -》“Right |
LPOP 移除尾部的值
RPOP 移除头部的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | eg: Lpop list 通过下标获取值 LRAANGE list 0 - 1 -》two -》one Lindex list 0 —》two 返回列表的长度 Llen list —> 2 移除指定值。list可以重复 Lpush list three Lpush list three Lrem list 1 three //从list中移除一个three List 截断 Rpush mylist “hello” Rpush mylist “hello1” Rpush mylist “hello2” Rpush mylist “hello3” Ltrim mylist 1 2 //通过下标截取指定的长度 Lrange mylist 0 - 1 ->“hello1” ->“Hello2” rpoplpush 移动list中最后一个值 并将其移动到新的列表中 rRush mylist “hello” rRush mylist “hello1” rRush mylist “hello2” rpoplpush mylist myotherlist Lrange mylist 0 - 1 ->“hello” ->“hello1” Lrange myotherlist 0 - 1 ->“Hello2” lset 将列表中指定下标的值替换为另外一个值 更新操作 如果列表不存在 更新会报错 如果下标的值存在更新,不存在 报错 Linset 将某个具体的value 插入到list中某个元素的前面和后面 Rpush mylist “hello” Rpush mylist “world” Insert mylist before world other ->“hello” ->“other” ->“World |
小结
实际上是一个链表。
如果key 不存在 创建新的链表
在两边插入或者改动值 效率最高,中间元素,相对来说效率低一些
应用:消息队列
Set (集合)
不能重复
所有的set命令都是s开头的
添加
1 2 3 4 5 6 7 | sadd myset “hello” sadd myset “kuangshen” 获取所有元素 SMembers SMembers myset ->“hello” ->“kuangshen |
1 2 3 4 5 6 7 8 9 10 | 判断是否包含某个元素 Sismeber myset hello 获取set集合的元素个数 scard myset 移除某一个元素 Srem myset hello <br>随机抽出一个元素 Srandmember myset Srandmember myset 2 随机抽取指定个数的元素 |
随机移除set集合中的元素
1 2 3 4 5 6 7 | eg:SMembers myset ->Kuangshen2 ->Kuangshen ->Hello Spop myset ->kuanghen2 Spop myset —>kuangshen |
Smove 将一个指定的key 移动到另一个set中
1 2 3 4 5 6 7 | Sadd myset “hello” Sadd myset “world” Sadd myset2 “set2” Smove myset myset2 “hello |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | Sadd key1 a Sadd key1 b Sadd key1 c Sadd key2 c Sadd key2 d Sadd key2 e Sdiff key1 key2 //两个set集合的差集 -> “b” -> “a” key1中key2没有的 Sinter key1 key2 //两个set集合的交集 ->“c” SUnion key1 key2 //并集 |
Hash(哈希)
Map 集合 key -map
本质和string 没有什么太大区别
所有的hash命令都是h开头的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | set myhash field1 kuangshen Hget myhash field1 ->“Kuangshen” //值会被覆盖 Hmset myhash field1 hello field2 world Hmget myhash field1 field2 ->“hello” ->”world” Hgetall myhash ->“hello” ->”world” hdel myhash field1 //删除hash指定key值 Hlen myhash //获取hash的长度 Exists myhash field1 //判定hash中指定的key是否存在 Hkeys myhash //获取所有的key值 Hvals myhash //获取所有的value值 Hset myhash field3 5 Hincrby myhash field3 1 //给指定key值增加指定步长 Hsetnx //如果存在不能设置 不存在就能设置 Hash 变更的数据 user name age 更适合对象的存储 Hset user: 1 name “zhangsan” Hset user: 1 age “ 18 ” |
应用:集合之间的操作。交集。并集 差集
Zset 有序集合
在set的基础上,增加了一个值 ,
set k1 v1
Set k1 score1 v1
1 2 3 4 5 6 | eg:Zadd myset 1 one Zadd myset 2 two 3 three Zrange myset 0 - 1 ->”one” “two” “three" |
1 2 3 4 5 6 7 8 9 10 11 12 | 排序 Zadd salary 2500 xiaohong Zadd salary 5000 zhangsan Zadd salary 500 kuangshen ZRANGEBYSCORE salary -inf +inf ->“kuangshen” -> “xiaohong” -> “zhangsan" |
1 2 3 4 5 6 7 | ZRANGEBYSCORE salary -inf +inf with scores ->”kuangshen” ->“ 500 ” ->“xiaohong” ->“ 2500 ” ->“zhangsan” ->“ 5000 |
排序(上限)
ZRANGEBYSCORE salary -inf 2500 ->“kuangshen” ->“xiaohong
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | Zrevrange salary 0 - 1 (从大到小排序) -》“zhangsan” -》“Kuangshen” 移除元素 zrem Zrem salary xiaohong 查看元素个数 Zcard salary Zconut 获取指定区间的成员数量 Zadd myset 1 hello 2 world 3 kuangshen Zconut myset 1 3 -> 3 Zcont myset 1 2 获取指定区间的成员数量 -〉 2 |
小结:set 排序 存储班级成绩表 工资表排序
普通消息 1,重要消息 2.带权重进行判断
应用:排行榜应用实现
三种特殊数据类型
1.Geospatial 地理位置
Redis 的 geo 可以推算地理位置的信息 两地之间的距离 方圆几里的人
Geoadd key 值(纬度 经度 名称)
规则:两极无法直接添加 一般会下载城市数据 直接java程序导入
Geoadd china:city 116.40 39.90 beijing
Geoadd china:city 121.47 31.23 Shanghai
Geoadd china:city 106.50 29.53 Chongqing 114.05 22.52 Shenzhen
有效的经度 从-180 到180
有效的纬度 从-85 到85
获取指定城市的经度和纬度
Geopos china:city beijing ->”116.3999..” ->“39.90000
Geodist 获取指定两个位置的距离(直线距离)
Geodist china:city beijing Shanghai km
我附近的人?(获得所有附近的人的地址 定位)
Georadius 以给定的经纬度为中心 找出某一半径内的元素
1 2 | Georadius China:city 110 30 500 km ->”chongqin” |
Georadius China:city 110 30 500 km withdist 直线距离
Georadius China:city 110 30 500 km withcoord 经纬度
获得指定数量的人
Georadius China:city 110 30 500 km count 1
北京周围的城市
Georadiusbymember china:city beijing 100 km
Geohash 返回一个或者多个位置元素的geohash表示
将返回11个字符串的geohash字符串(将二维的经纬度转换为一维的字符串)
Geo 底层的实现原理就是zset ,可以使用zset 命令来操作
2.Hyperloglog PF开头的命令
什么是基数?不重复的元素
A(1,3,5,7,8,7)
B(1,3,5,7,8)
基数(不重复的元素) 网页的浏览量(一个人访问一个网站多次,但是还是算作一个人)
占用的内存是固定的,2^64 不同的元素,只需要占用128kB内存
0.81%错误率
eg:PFADD mykey a b c d e f g h I j PFCOUNT mykey ->10PFADD mykey2 i j z x c v b n m
PFCOUNT Mykey2
->9PFMEGRE mykey3 mykey mykey2
PFCOUNT mykey3
->15
如果允许有一定错误率,就可以用hyperloglog
3.Bitmaps
位存储 操作二进制来记录
统计用户信息 两个状态的 都可以使用Bitmaps
周一到周六的打卡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | Setbit sign 0 1 Setbit sign 1 0 Setbit sign 2 0 Setbit sign 3 1 Setbit sign 4 1 Setbit sign 5 0 Setbit sign 6 0 Getbit sign 6 -> 0 Getbit sign 3 -> 1 bitcount sign //统计打卡的天数 -> 3 |
Redis基本的事务操作
事务
一组命令的集合 一个事务中的所有命令都会被序列化 会按照顺序执行,
一次性 顺序性 排他性 执行一些列的命令
Redis事务没有隔离级别的概念 单条命令出错不会影响前面的任务 也不会影响后面的命令
Redis 单条命令
开启事务(multi)
命令入队(…)
执行事务(exec)
Multi //开启事务
Set k1 v1
Set k2 v2
Get k2
Set k3 v3
exec //执行事务
->ok
->ok
->”v2”
->ok
每个事务执行完就没有了
放弃事务 discard 在事务执行之前可以放弃事务
编译型异常(代码有问题,命令有错)事务中所有的命令都不会被执行
运行型异常(如果事务队列中存在语法性错误,)执行事务的时候,其他命令会正常执行,错误命令抛出异常。
乐观锁
悲观锁:无论做什么都会加锁
乐观锁:更新数据的时候去判断一下,在此期间,是否有人修改过这个数据,
Mysql 获取version,更新的时候比较version
Redis监视测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | Set money 100 Set out 0 Watch money 监视money对象 Muiti Decrby money 20 Incrby out 20 Exec 执行命令 -> 80 -> 20 |
测试多线程修改值,使用watch可以当作redis的乐观锁操作
1 2 3 4 5 6 7 | Watch money Multi Decrby money 10 Incrby out 10 Exec 执行之前,另外一个线程,修改了我们的值,这个时候,就会导致我们事务执行失败 ->(nil)<br><br> |
如何解决?释放锁,重新写事务
Unwatch
Watch money
Jedis
使用java操作Redis
Redis官方推荐的java连接开发工具,使用java操作redis中间件。
导入包
2.编码测试:
连接数据库
操作命令
断开连接
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
方法和之前的api完全相同
Jedis.close();//关闭连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 事务: public static void main(String[] args) { // Jedis jedis = new Jedis( "127.0.0.1" , 6379 ); System.out.println(jedis.ping()); jedis.flushDB(); JSONObject jsonObject = new JSONObject(); jsonObject.put( "hello" , "world" ); jsonObject.put( "name" , "zhangsan" ); Transaction multi = jedis.multi(); //开启事务 String result = jsonObject.toJSONString(); try { multi.set( "user1" ,result); multi.set( "user2" ,result); multi.exec(); //执行事务 } catch (Exception e){ multi.discard(); //销毁事务 e.printStackTrace(); } finally { System.out.println(jedis.get( "user1" )); System.out.println(jedis.get( "user2" )); jedis.close(); } } |
springboot整合
Springboot2.x之后。原来使用的jedis 被替换成了letture
Jedis:采用的直连。 多线程操作的话,不安全,避免不安全就使用jedis pool连接池
Lettuce:采用netty,异步,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程数量。
导入包
配置连接
测试
传输对象需要序列化
自定义的redisTemplate
Redis.conf详解
1.大小写不敏感
2.可以包含其他配置文件
Network
Bind 127.0.0.1 绑定ip
通用 general
Demonize yes #后台运行 默认是no
Loglevel notice #生产环境
Logfile 日志文件名
Database 16 #数据库的数量
Always-show-logo yes #是否显示logo
快照
持久化,在规定的时间内,执行了多少次操作,则会持久化到文件.rdb .aof
如果900秒至少有一个key 进行了修改,我们
Save 900 1
Save 300 10
Save 60 10000
Stop-writes-on-bgsave-error yes 持久化如果出错,是否继续工作
Rdbcompression yes 是否压缩rdb文件,会消耗一些cpu资源
Rdbchecksum yes 保存rdb文件的时候,进行错误的检查校验
Dir ./ rdb文件保存的目录
Requirepass 123456 #设置密码
Config set require pass 123456
Config get require pass
->noauth authentication required
Auth 123456
->ok
Append only 模式
Appendonly no 默认是不开启,默认是使用rdb方式持久化,大部分所有的情况下,rdb完全够用
Appendfsync everysec 每秒执行一次
Redis持久化
Redis是内存数据库,如果不将内存中的数据库保存到磁盘中,一旦服务器进程退出,数据库状态也会消失,所以redis提供了持久化功能
默认是rdb 缺点就是最后一次持久化的数据可能会丢失
保存的文件是dump.rdb
触发规则
1.save的规则满足的情况下,会自动触发rdb规则
2.Flushall命令,也会触发我们的rdb规则
3.退出redis,也会产生rdb文件
备份就会自动生成一个dump.rdb
如果恢复rdb文件的数据
只需要将rdb文件
放到我们的redis启动目录下,redis启动的时候会自动检查dump.rdb 恢复其中的数据
查看需要的位置
Config get dir
->”dir”
->”usr/local/bin” 如果在该目录下存在dump.rdb文件,启动就会自动回复其中的数据
优点:
适合大规模的数据恢复
如果你对数据的完整性要求不高
缺点:
需要一定时间间隔进程操作,redis意外宕机了,最后一次修改数据就没了
Fork进程的时候,会占用一定的内存空间
AOF
以日志的形式来记录每个写操作,将redis执行过的所有指令记录下来(读操作不记录),只许追加文件不许修改文件,redis启动之初就会读取该文件重新构建,
AOF 保存的是appendonly,aof
默认不开启
重启redis就可以生效了
如果aof文件有错误,redis无法启动
redis修复aof工具 redis-check-aof
Redis-check-aof —-fix appendonly.aof
文件正常 重启就可以恢复
优点:
1.每次修改都同步,文件的完整性会更好
2。每秒同步一次,可能会丢失一秒的数据
3.从不同步,效率最高的
缺点:
1.相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
2.aof运行效率也比rdb慢,所以redis默认配置是rdb
Rewite 重写
发布订阅
消息通信模式 发送者发送消息 订阅者接收消息
消息发送者
频道
消息订阅者
Subscribe 频道名 //订阅频道
Publish 频道名 消息//向某频道发布消息
Subscribe 频道名 …//订阅多个频道
Eg:
订阅端:subscribe bilibili
->”subscribe”
->”kuangshenshuo”
->”message”
->”bilibili”
->”hello,bilibili”
发送端:publish bilibili “hello,bilibili”
Subscribe就是把订阅的人加入到该频道的发布链表中
使用场景:
实时消息系统
实时聊天(聊天室)
订阅 关注系统都是可以的
主从复制 读写分离
Master -slave1
-slave2
-slave3
主机进行读,从机进行写。减缓服务器的压力。
数据的复制是单向的,只能由主节点到从节点。
主从复制的作用:
1。数据冗余
2。故障恢复
3。负载均衡 通过多个从节点分担负载
4,高可用
单台redis的最大使用内存不应该超过20G
默认情况下,每台redis都是主节点
info replication 查看当前库的信息
->role:master 角色
connected_slaves:0 没有从机
复制3个配置文件,修改对应的信息
1。端口
2。pid名字
3。log文件名字
4。dump.rdb名字
启动服务 redis-server kconfig/redis79.conf
连接 redis-cli -p 6379
一主二从
主从复制之复制原理
默认情况下,每台redis都是主节点
info replication //查看当前库的信息
->role:master //角色
在从机中配置
slaveof 127.0.0.1 6379//绑定对应的主机
配置了之后,从机的角色变了 主机中可以看到从机的配置信息
真实的主从配置 修改配置文件
replicaof 主机IP 端口号
主机可以写,从机不能写只能读
主机中的所有信息和数据,都会自动被从机保存
测试:主机断开连接 从机依旧连接到主机的,这个时候,主机如果回来了,从机依旧可以直接获取到主机写的信息
如果使用命令行 配置的主从,重启之后,又会变成主机,只要变成从机,立马就会从主机中获取值
从机启动成功连接到主机后,会发送一条sync同步命令
master接到命令后,启动后台的存盘进同时收集所有接收到的用于修改数据集命令,
在后台命令执行完毕之后,master将传送整个数据文件到slave.完成一次完全同步
全量复制 slave服务在接收到数据库文件数据后,将其存盘加载到内存中
增量复制 master继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要重新连接master ,一次全量复制将会被自动执行。
层层链路
Master -slave1 master -slave2
79 80 81
80为从节点,不能写 ,只能读
slaveof no one
如果主机断开了连接 使用slaveof no one 让自己成为主机
哨兵模式(自动选举老大的模式)sentinel
通过后台监控主机是否故障,如果故障了根据投票数自动将从库转为主库
原理:哨兵通过发送命令 等待redis服务器响应 从而监控运行的多个redis实例
多哨兵模式
主服务器不能用 并且达到一定数量,让各个哨兵爸自己监控的从服务器实现切换主机,为客观下线
配置哨兵配置文件 sentinel.conf
Sentinel montior 被监控的名称 host port 1
Sentinel montior myredis 127.0.0.1 6379 1
后面的数字 代表主机挂了 slave投票看让谁接替成为主机 投票最多的,就会成为主机
启动哨兵
Reidis-sentinel kconfig/sentinel.conf
如果主机节点断开了,就会从从机中随机选择一个服务器
哨兵日志
Failover 发现故障
Sdown 主机转移
如果主机此时回来。只能归并到新的主机下,当作从机
优点
1.哨兵集群,基于主从复值模式,所有的主从配置优点,它都有
2.主从可以切换,故障可以转移,系统的可用性好
3.哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点
Redis不好在线扩容,集群容量一旦到达上限,在线扩容十分麻烦
实现哨兵模式的配置其实是很麻烦的,里面有很多选择。
Redis缓存穿透与雪崩
缓存穿透
用户查询一个数据 redis内存数据库中没有,缓存没有命中,于是在持久层数据库查询
发现也没有,于是本次查询失败,当用户很多,缓存都没有命中(秒杀),于是都去请求了持久层数据库,这就会给持久层数据库造成很大压力,出现了缓存穿透。
布隆过滤器
对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,避免了对底层存储系统的压力
客户端 -》bloomfilter ->缓存层 —-〉存储层
缓存空对象
没命中的空对象缓存起来,同时设置一个过期时间,之后再访问这个数据从缓存中获取
问题:
如果空值能够被缓存起来,这就意味着缓存需要更多空间,因为这当中可能有许多空值的键
即使对空值设置了一个过期时间,还是会存在缓存层和存储层的数据会有一段时间的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿
是指一个key非常热门,在不停的扛着高并发,这个key在失效的瞬间,持续的大并发就会穿透缓存,直接请求数据库。
设置热点数据永不过期
加分布式锁
使用分布式锁,保证每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限
缓存雪崩
在某一个时间段,缓存集中过期失效,redis宕机
停掉一些服务
redis高可用
多增几台redis
限流降级
这个解决方案的思想是,缓存失效时通过加锁或者队列控制数据库写缓存的线程数量
数据预热
数据预热的含义就正式部署之前,先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动触发并加载缓存不同的key,设置不同的缓存过期时间,让缓存失效的时间尽可能均匀