Redis
Nosql
现在是大数据时代,一般的关系型数据库无法快速处理那么大的访问量。
为什么使用Nosql
单机Mysql时代
那时候更多使用静态网页Html,访问量不会很大,单个数据库已经足够。
这样的模式存在什么问题?
- 数据量如果太大,一台服务器放不下
- 数据的索引(B+Tree),一台服务器也放不下
- 读写混合,一台服务器无法承受
Memcached(缓存) + MySQL + 垂直拆分
实际场景80%都是进行查询的操作,每次都去查数据库效率很低。所以我们希望使用缓存减轻数据库压力。
发展历程:优化数据结构和索引——》文件缓存(IO)——》Memcached
分库分表 + 水平拆分 + MySQL集群
以前使用MylSAM : 表锁 查一行数据锁住了整张表 高并发下有严重的问题
Innodb使用行锁解决
MySQL在那个时代推出了表分区,但是使用的不多。
集群在这时已经能满足大部分需求。
大数据时代
2010-2020发生了翻天覆地的变化,各种实时变化的数据。
MySQL等关系型数据库的弊端就暴露了,无法应付这种场景。
为什么要用NoSQL
用户的每天生成的个人信息,社交网络,地理位置等等以PB计,呈爆发式增长。
NoSQL非常适合应对这种场景。
什么是NoSQL
NoSQL = Not Only SQL 不仅仅是SQL 泛指非关系型数据库,随着互联网2.0时代的诞生,传统的数据库很难处理,尤其是超大规模高并发的社区,暴露很多无法解决的问题。
很多的数据类型不需要一个固定格式(行 列)
NoSQL的特点
- 方便扩展(数据之间没有关系)
- 大数据量高性能 Redis一秒可以写八万次 读取十一万次 细粒度的NoSQL缓存记录级,性能高
- 数据类型是多样的 不需要设计数据库表(表总是设计的很烂)
关系型与非关系型
传统的RDBMS(关系型)
- 结构化组织
- SQL
- 数据和表的关系都在单独的表中
- 数据定义语言
- 严格的一致性
- 基础的事务
- …
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库
- 最终一致性
- CAP定理 和 BASE
- 高性能 高可用 高可扩
真正在实际环境用的肯定是NoSQL+RDBMS 技术没有之分 只看如何使用
淘宝网站的数据存储
商品的基本信息
商品名称 价格 商品信息
- 使用关系型数据库
商品的描述、评论
- 文档型数据库 MongoDB
图片
-
分布式文件系统 FastDFS
-
淘宝 TFS
-
GOOLE GFS
-
Hadoop HDFS
-
阿里云 oss
关键字
- 搜索引擎 solr
- elasticsearcher
- 淘宝 ISearch
商品的波段信息
秒杀之类的
- 内存数据库
- Redis Tair Memache
商品的交易 外部支付接口
- 第三方应用接口
NoSQL四大分类
KV键值对:
- 新浪:Redis
- 美团:Redis + Tair
- 阿里:Redis + memcache
Redis入门
Redis Remove Dictionary Service 远程字典服务
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、KeyValue数据库,并提供多种语言的API
免费且开源,是当下最热门的NoSQL技术之一,也被人们称为结构化数据库
Redis基本功能
- 内存存储,持久化
- 效率高,用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(点赞量之类的)
Redis特性
- 多样的数据类型
- 持久化
- 集群
- 事务
Redis指令
redis-server 配置文件 #启动redis
keys * #查看数据库
flushdb #清除当前数据库
flushall #清除全部数据库
expire 变量名 时间(秒数) #让变量xx秒后过期
setex key名 过期时间 key值 #set with expire 新建key 并让keyxx秒后过期
ttl 变量名 #获得变量过期的时间
move 变量名 数据库序号 #移除当前的key
exists 变量名 #移除当前的key
incr key名 #key自增一 后面可加想要的步长
decr key名 #自减一 后面可加想要的步长
setnx #set if not exist 不存在才设置 常用于分布式锁
mset key名1 values值1 key名2 values值2 .... #一次设置多个key value
mset key名1 values值1 key名2 values值2 .... #一次设置多个key value 如果其中有已存在的key 则本次所有key设置失败
mset user:1:name zhangsan user:1:age 2 #设置对象键值对
mget user:1:name #获取对象键值对
getset key名 值 #获取当前值 替换成新值 若不存在则新建
Redis使用单线程的原因
使用单线程的好处就是不会受到CPU的太大局限,而仅仅是机器的内存和网络带宽的局限。
- Redis采用纯内存操作,避免大量访问数据库,减少直接读取磁盘数据,redis将数据存储在内存里面,读写数据的时候不会受到硬盘I/O速度的限制。
- 单线程操作避免了不必要的上下文切换和资源竞争,也不需要上锁解锁,没有因为出现死锁而导致的性能损耗。
- 采用了非阻塞I/O多路复用机制
String类型
string更适合字符串存储 hash存储对象更直观 更适合存储对象 hash可以只更新一个字段而string不行
append key名 "字符串" #记得要加双引号 拼接key的字符串 如果当前key不存在 则新建
getrange key名 起始index(从0开始) 终止index #截取字符串
setrange key名 起始index 终止index #替换字符串
setnx #set if not exist 不存在才设置 常用于分布式锁
mset key名1 values值1 key名2 values值2 .... #一次设置多个key value
mset key名1 values值1 key名2 values值2 .... #一次设置多个key value 如果其中有已存在的key 则本次所有key设置失败
mset user:1:name zhangsan user:1:age 2 #设置对象键值对
mget user:1:name #获取对象键值对
getset key名 值 #获取当前值 替换成新值 若不存在则新建
String类型的使用场景
- 计数器
- 构建多键值对对象
- 粉丝数
- 对象缓存存储
List类型
lpush list名 值 #从左边插入
rpush list名 值 #从右边插入
lrange list名 起始index 终止index 值 #从左边开始读取list
rrange list名 起始index 终止index 值 #从右边开始读取list
lpop list名 #把左边第一个值移除
rpop list名 #把右边第一个值移除
llen list名 #读取list的长度
lrem list名 数额 值 #从左边开始 移除指定个该值的key
ltrim list名 起始index 终止index #截取list
rpoplpush list名 另外一个list名 # 移除list的最后一个值到另外一个list中去
lset list名 index 值 #指定下标放值 但该下标必须存在值
exists list名 #判断list是否存在
linsert list名 before/after 值名 插入的值
Set类型
set的值 无序不能重复
sadd set名 值 #往set添加元素
smembers set名 #查看set的所有元素
sismember set名 值 #相当于contains
scard set名 #获取set中元素数量
sremove 值 #移除元素
srandmember set名 个数 #随机获取元素
spop set名 #随机弹出元素并输出
sdiff set1名 set2名 #输出set1中set2没有的元素
sinter set1名 set2名 #输出两个set的交集
sunion set1名 set2名 #并集
Hash(哈希)
hash存储对象更直观 更适合存储对象 string更适合字符串存储 hash可以只更新一个字段
hset key名 field名 值 #存值 field和值类似键值对
hget key名 field名 #获取值
hmset key名 field1名 值 field2名 值 #存多个值 若存在则覆盖
hgetall key名 #获取所有值
hdel key名 field #删除键值对
hlen key名 #获取当前hash中键值对的个数
hexists key名 field #检查field是否存在
hkeys key名 #获得所有的key
hvals key名 #获取所有value
hincrby key名 field 步长 #设置自增 可为负数 也可以用decrby
hsetnx key名 field名 值 #不存在则设置
hset user:1 name rush #存储user信息
hget user:1 name #获取user信息
Zset(有序集合)
zadd set名 score值 值 #添加元素
zadd set名 score值1 值1 score值2 值2 #添加多个元素
zrangebyscore set名 -inf +inf #从小到大排序
zrangebyscore set名 -inf +inf withscore #显示值 从小到大排序
zrange set名 起始index 终止index # 显示set
zrem set名 值 #移除对应的键值对
zcard set名 #获取有序集合的个数
zrevrange set名 起始index 终止index #从大到小排序
zcount set名 score1值 score2值 #获取其中有多少个元素
应用场景
- 班级成绩表
- 工资表
- 排行榜
Geospatial
两极无法直接添加,一般下载城市数据,通过java一键添加。
geoadd 国家名:city 经度 纬度 Geo名 # 添加地理位置
geopos 国家名:city Geo名 # 获取地理位置
geodist 国家名:city Geo名1 Geo名2 # 获取两个地理位置的距离
geodist 国家名:city Geo名1 Geo名2 km # 获取两个地理位置的距离 以千米表示
georadius 国家名:city 经度 纬度 半径 单位 #找到Geo中在这个范围中的所有元素
georadius 国家名:city 经度 纬度 半径 单位 withdist withcoord count 数量
#找到Geo中在这个范围中的所有元素 附带经纬度 限定数量
georadiusbymember 国家名:city 元素名 半径 单位 #查找元素周围的元素
zrange 国家名:city index index #查看geo元素
zrem 国家名:city geo名 #删除geo元素
Hyperloglog
Hyperloglog数据结构,基数统计的算法(元素不重复)。
计数如果使用set记录用户id,再获取set中的元素个数,这样就会保存大量不必要知道的用户id信息,这样做效率很低。
Hyperloglog使用的内存是固定的,只需要12k内存
PFadd key名 元素 元素 ... #添加元素
PFcount key名 #获取元素个数
pfmerge key1名 key2名 key3名 #合并key2和key3生成key1
Bitmaps
位存储
存储是否已经打卡,登录状态等信息
setbit sign名 变量 值 #变量只能为数字 值只能为0或1
getbit sign名 变量 #获取值
bitcount sign名 #获取值为1的个数
事务
- 开启事务(multi)
- 命令入队(…)
- 执行事务(exec)
入队的时候命令还没有执行,直到执行事务(exec)才真正执行了命令
Redis单条命令保证原子性,但Redis事务不保证原子性。事务中的所有命令都会被顺序化,命令会按照顺序执行。命令执行有以下特点。
- 一次性
- 顺序性
- 排他性
Redis事务没有隔离级别的概念,所以不存在幻读、脏读、不可重复读的情况
multi #开启事务
exec #执行事务
discard #放弃事务
异常
编译型异常
- 事务中的所有命令都不会执行
执行时异常
- 除了异常命令,其他命令都可以正常执行
上锁
-
乐观锁
认为什么时候都不会出问题,不会上锁,更新数据的时候判断一下在此期间是否修改过这个数据。会获取并比对该数据的version,version不一致则不进行操作
-
悲观锁
认为什么时候都会出问题,都会上锁。
watch相当于乐观锁
multi #开启事务
watch 变量名 #给变量上乐观锁
unwatch #解锁
exec #执行事务
exec和discard会自动解锁
Jedis
使用Java操作redis,是Redis官方推荐的java连接开发工具。
常见api
SpringBoot整合Redis
在SpringBoot2.x.x版本中,底层使用的是Jedis。在SpringBoot3之后替换成了Lettuce
- Jedis采用直连的方式,线程不安全。如果想要避免这个问题,需要使用Jedis Pool,采用阻塞的方式,有性能损耗。类似Bio模式
- Lettuce是用netty做的,实例可以在多个线程共享,不存在线程不安全的情况。类似Nio模式
持久化
RDB
在指定的时间间隔内对你的数据进行快照存储
AOF(Append Only File)
配置
appendonly no # 默认的 不开启aofm模式的,默认使用rbd方式持久化
appendfilename "appendonly.aof" #持久化文件的名字
# appendfsync always 每次修改都会sync 消耗性能
appendfsync everysec #每秒sync一次 可能会丢失这一秒的数据
# appendsync no 不执行sync 性能最佳
每当进行了操作,就往AOF文件添加记录,这样就不会丢失数据。重启后,Redis会自动加载AOF回载数据。
AOF和RBD的对比
-
AOF可以做到每一次操作都会保存,数据不会丢失
-
AOF文件的大小远远大于RDB文件,修复的速度也比rdb慢
Redis发布订阅
subscribe 频道名 #订阅频道
publish 频道名 消息 #发送消息
主从复制
80%的情况都是进行读取的操作,所以将读写分离,可以有效地提高我们的效率。
主从复制的作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复。
- 负载均衡:在主从复制的基础上,配合读写分离,可以有主节点提供写服务,由主节点提供写服务,由从节点提供读服务,分担服务器负载;尤其在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用(集群)基石:除了上述作用之外,主从复制是哨兵和集群能够实施的基础,因此主从复制是Redis高可用的基础。
不能只用一台Redis的原因
- 从结构上说,单个Redis服务器会发生单点故障,并且一台Redis如果发生了宕机那就完蛋了。
- 从容量上说,单个Redis服务器内存容量有限,一般来说,单台Redis最大使用内存不应该超过20G
必须使用集群
配置
info replication #查看当前库的信息
slaveof ip 端口 #把本机作为某个机器的从机
上面这样配置只能起到暂时的作用,如果想要永久生效则需要配置配置文件,修改主机的replicaof信息
复制三个配置文件,然后修改对应信息。
- 改端口
- pid名字
- log文件名字
- dump.rdb名字
一主二从
默认情况下,每一台机器都是主机。
主机可以写,从机不能写。
如果使用命令行配置,重启后就会变回主机
成为从机会立即复制主机的数据
- 全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
- 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
6379作为主机,6380作为6379的从机,作为6381的主机,6379宕机后6380可以正常写入,若无宕机则不能写入。
slaveof no one #让自己变成主机
哨兵模式
新建配置文件 sentinel.conf
sentinel monitor redis名 ip 端口 1 # 1代表主机挂了
启动哨兵
redis-sentinel sentinel.conf
优缺点
优点
- 基于主从复制,保留了主从复制的优点
- 系统的稳定性、健壮性更佳
- 自动选出新主机,实时性更好
缺点
- 在线扩容并不方便
- 配置很麻烦
Redis缓存穿透
用户查的数据在redis中不存在,即缓存没有命中,于是向持久层数据库查询,当很多用户的查询,缓存都没有命中,就会给持久层数据库很大的压力,黑客故意查询redis中不存在的数据,导致持久层宕机,这就是缓存穿透。
缓存击穿
和穿透不同的是,击穿特指缓存的某个点被大量访问而被击破,并且数据是查得到的。
缓存雪崩
到了某个时间,缓存集体失效。导致持久层数据库宕机
解决方案
- redis高可用:搭建集群
- 限流降级:缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
- 数据预热:把可能要使用的数据先访问缓存一遍,对缓存设置不同的过期时间。
使用redis缓存的场景
- 更改频率不高的数据
- 秒杀这种访问量极大,更新很频繁,数据不能丢失的场景
不使用redis缓存的场景
- 钱、密钥、业务核心关键数据