【Java学习】Redis

121 阅读16分钟

Nosql概述

单机mysql瓶颈

  • 数据量太大,一个机器放不下
  • 数据的索引,一个机器内存也放不下
  • 访问量(读写混合),一个服务器承受不了

Memached(缓存)+ MYSQL + 垂直拆分(读写分离)

网站80%的情况都是在读数据库,为了减轻数据库压力,保证效率,加一层缓存。

发展过程:优化数据结构和索引-->文件缓存(IO)-->Memcached

分库分表 + 水平拆分 + MySQL集群

截屏2023-04-15 17.22.09.png

MyISAM:表锁,十分影响效率,高并发下会出现严重的锁问题

InnoDB:行锁

如今

实时数据要求较高,MySQL等关系型数据库不够用,数据量很多,变化很快。

MySQL存储一些比较大的文件、图片导致数据库表很大,效率很低。希望有一种数据库来专门处理这种数据,减小MySQL的压力。

NoSQL

NoSQL = Not Only SQL(不仅仅是SQL),泛指非关系型数据库。

NoSQL在当今大数据环境下发展十分迅速,其中Redis是发展最快也是必须要掌握的技术。

NoSQL特点

  • 方便扩展(数据之间没有关系、很好扩展)
  • 大数据量高性能(NoSQL的缓存记录级,是一种细粒度的缓存,性能比较高)
  • 数据类型是多样的(不需要事先设计数据库,随取随用)
  • 传统RDBMS和NoSQL
    • 传统RDBMS
      • 结构化组织
      • SQL
      • 数据和关系都存在单独的表中
      • 数据操作、数据定义语言
      • 严格的一致性
      • 基础的事务
    • NoSQL
      • 不仅仅是数据
      • 没有固定的查询语言
      • 键值对存储、列存储、文档存储、图形数据库(社交关系)
      • 最终一致性
      • CAP定理和BASE理论
      • 高性能、高可用、高可扩展

大数据时代的3V:主要是描述问题的

  • 海量Volume
  • 多样Variety
  • 实时Velocity

大数据时代的3高:主要是对程序的要求

  • 高并发
  • 高可扩展性
  • 高性能

一般来说:

  • 商品的基本信息(MySQL)
  • 商品的描述、评论(文档型数据库、MongoDB)
  • 图片(Hadoop HDFS)
  • 商品的关键字(搜索、ElasticSearch)
  • 商品热门的波段信息(内存数据库、Redis)
  • 商品的交易、外部的支付接口

NoSQL的四大分类

  • Key-Value键值对
  • 文档型数据库
    • MongoDB
      • MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
      • MongoDB是一个介于关系型数据库和非关系型数据库中间的产品,最像关系型数据库的非关系型数据库
  • 列存储数据库
  • 图关系数据库
    • 存放的是关系,比如:社交网络、广告推荐

Redis概述

Redis是什么

Redis(Remote Dictionary Server),远程字典服务,可基于内存也可持久化的日志型、Key-Value数据库,并提供多种语言的API。

Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis能干什么

  • 内存存储、持久化,内存中是断电即失,所以说持久化很重要(RDB、AOF)
  • 效率高,可以用于高速缓存
  • 发布、订阅系统(消息队列)
  • 地图信息分析
  • 计数器(浏览量)

Redis的特性

  • 多样的数据类型
  • 持久化
  • 集群
  • 事务

Redis基础知识

Redis默认有16个数据库,默认使用第0个,可以使用select切换数据库。

  • 清除当前数据库 flushdb
  • 清除全部数据库的内容 FLUSH

Redis是基于内存操作,其瓶颈是机器的内存和网络带宽,数据的读写命令是单线程的。

几个误区

  • 高性能的服务器一定是多线程的(❌)
  • 多线程(CPU上下文会切换)一定比单线程效率高(❌)

速度:CPU>内存>硬盘

核心: Redis将所有的数据全部放在内存中,所以使用单线程去操作效率是最高的。对于内存系统来说,如果没有上下文切换效率就是最高的。

Redis-Key

exists key 判断当前key是否存在
move name 1 把name移到1号数据库里面,原本都在0号数据库
expire name 10 设置key的过期时间
keys * 查看所有key
set name xxx 设置key
get name 查看key
type name 查看当前key的类型

五大基本数据类型

String(字符串)

append key "xxx" 追加字符串,若原本key不存在,就相当于set key
strlen key 返回key的长度
incr key 自增1
decr key 自减1
incrby key xx 自增xx,设置步长
decrby key xx 自减xx,设置步长
getrange key startindex endindex 返回字符串范围,截取一部分字符串
getrange key 0 -1 获取全部的字符串,和get key是一样的
setrange key startindex xx 替换指定位置开始的字符串
setex (set with expire) 设置过期时间
setex key 30 "hello" 设置key的值为hello,30秒后过期
setnx (set if not exist) 不存在就设置 (在分布式锁中经常使用)
flushdb 清空当前数据库中的数据
mset 批量设置
msetnx 如果不存在,就设置(原子操作)
mget 批量获取
# 对象
set user:1 {name:zhangsan,age:3} 设置一个user:1对象,值为json字符来保存一个对象
mset user:1:name zhangsan user:1:age 2 和上面用途相同
mget user:1:name user:1:age 批量获取
# 组合命令
getset 先getset
getset db redis 会返回nil或当前值,并且设置更新值

List(列表)

基本的数据类型。

在redis中,栈、队列和阻塞队列都可以用list实现。

消息队列(rpush、rpop)、栈(lpush、lpop)

list本质上就是链表。

lpush list oneone加入队列(类似于入栈)(可以存在重复值)
lrange list 0 -1 获取list所有值
rpush list xx 将元素放入列表最开头(相当于栈底)
lpop list 从栈顶弹出
rpop list 从栈底弹出
lindex list index 通过下标获取list中某一个值,从栈顶开始数
llen list 返回列表长度
lrem list 1 one 从链表移除一个one
ltrim mylist 1 2 通过下标截取指定的长度,只剩下截取的元素
rpoplpush 移除列表最后一个元素,并将其移动到新的列表中
lset list index xx 将列表第index个元素替换为xx,如果不存在,则会报错
linsert list before xx yy 在xx的前面插入yy
linsert list after xx yy 在xx的后面插入yy

Set(集合)

set是无序不重复集合。

sadd myset xx set集合中添加元素
SMEMBERS myset 查看指定set的所有值
SISMEMBER myset xx 判断某一个值是否在set集合中
scard myset 获取set集合的值
srem myset xx 移除某个值
SRANDMENBER myset (xx) 从myset中随机选取xx个成员
spop myset 随机删除一个元素
smove myset myset2 xx 将指定的值xx移动到另外一个set集合中
SDIFF key1 key2 差集 key1 - key2
SINTER key1 key2 交集,共同好友可以用这个实现
SUNION key1 key2 并集

Hash(散列)

Map集合、key-map集合

hset myhash xx yy set一个键值对
hget myhash xx 获取一个字段值
hmset myhash xx1 yy1 xx2 yy2
hmget myhash xx1 xx2
hgetall myhash 键值对全部输出
hdel myhash xx1 删除指定key字段、对应的value值也就消失了
hlen myhash 输出key的个数
hexists myhash xx 判断myhash中的指定key(字段)是否存在
hkeys myhash 获取所有字段
hvals myhash 获取所有值
hincrby myhash xx 1 指定字段自增1
hdecrby myhash xx 1 指定字段自减1
hsetnx myhash xx yy 如果不存在就可以设置、如果存在则失败

hash可以存储变更数据,如用户信息,存储一个对象等。

hset user:1 name zhangsan age 18

Zset

有序集合。

在set的基础上,增加了一个值。

zadd myset 1 one 添加一个值
zadd myset 1 two 3 three 添加多个值
zrange myset 0 -1
# 排序如何实现
zadd salary 2500 xiaohong
zadd salary 5000 zhangsan
zadd salary 500 kuangshen
ZRANGEBYSCORE salary -inf +inf 根据score排序(升序)(范围)
ZRANGEBYSCORE salary -inf +inf withscores 输出的时候把成绩也带上
ZREVRANGE salary 0 -1 从大到小进行排序
ZREVRANGEBYSCORE 降序
zrem salary xiaohong 移除元素
zcard salary 获取有序集合中元素个数
zcount myset 1 3 获取指定区间的成员数量

zset可以做排行榜应用实现。

三种特殊数据类型

geospatial 地理空间

朋友的定位、附近的人、打车距离计算

geoadd 将指定的地理空间位置(经度、纬度、名称)添加到指定的key中
geopos 从key里返回所有给定位置元素的位置(经度和纬度)
geodist 返回两个给定位置之间的距离
georadius 以给定的经纬度为中心,找出某一半径内的元素
georadiusbymember 找出位于指定范围内的元素,中心点是由给定的位置元素决定
geohash 返回一个或多个位置元素的Geohash表示(将二维的经纬度转换为一维的字符串,如果两个字符串越接近,则说明其距离越近)

GEO底层的实现原理其实就是Zset,我们可以使用Zset命令来操作GEO。

hyperloglog

基数(不重复的元素)

Redis Hyperloglog 是进行基数统计的算法,可以接受一定的误差

bitmap

位存储。

打卡统计等。

Redis事务操作

Redis事务本质:一组命令的集合。一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行。

  • 一次性
  • 顺序性
  • 排他性

Redis单条命令是保存原子性的,但是事务不保存原子性。

所有命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行。

Redis事务没有隔离级别的概念。

Redis的事务:

  • 开启事务(multi)
  • 命令入队
  • 执行事务(exec)
  • (取消/放弃事务(DISCARD))事务队列中的命令都不会被执行!

编译型异常(代码有问题、命令写错),事务中的所有命令都不会被执行!

运行时异常,如果事务队列中存在语法错误,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常。

Redis的监视测试

测试多线程修改值,使用watch当作redis的乐观锁操作,执行之前值已被另一个线程修改,则会导致事务执行失败。

  • 如果发现事务执行失败,就先解锁;
  • 获取最新的值,再次监视;
  • 比对监视的值是否发生了变化,如果没有变化,那么可以执行成功,如果有变化则执行失败。

Jedis

Jedis是Redis官方推荐的java连接开发工具,使用java来操作Redis的中间件。

  • 导入依赖包
  • 连接数据库
  • 操作命令
  • 断开连接

SpringBoot集成Redis操作

在SpringBoot2.x之后,原来的jedis被替换为lettuce。

  • jedis:采用的直连,多个线程操作不安全,需要用jedis pool连接池解决。
  • lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数据。

SpringBoot所有的配置类,都有一个自动配置类。

自动配置类都会绑定一个properties配置文件。

默认的RedisTemplate没有过多的设置,redis对象都需要序列化。可以自定义RedisTemplate。

Redis持久化

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能。

RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshot快照,它恢复时是将快照文件直接读到内存里。

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程不进行任何IO操作,确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是特别敏感,那么RDB方式要比AOF更加高效。

RDB的缺点是最后一次持久化后的数据可能丢失。

触发机制

  • save的规则满足的情况下,会自动触发rdb规则
  • 执行flushdb命令,也会触发rdb规则生成rdb文件
  • 退出redis,也会产生rdb文件

恢复rdb文件

  • 只需要将rdb文件放入redis配置目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据

优缺点:

优点

  • 适合大规模的数据恢复
  • 对数据的完整性要求不高

缺点

  • 需要一定的时间间隔进行操作
  • 如果redis意外宕机,最后一次修改数据就没有了
  • fork进程的使用,会占用一定的内存空间

AOF(Append Only File)

默认是不开启aof模式的,默认是使用rdb方式持久化,在大部分情况下,rdb完全够用。

如果需要开启,就将appendonly设置为yes。

把所有命令都记录下来,history,恢复的时候就把这个文件全部再执行一遍。

优缺点:

优点

  • 每一次修改都同步,文件的完整性会更好
  • 每秒同步一次,可能会丢失一秒的数据
  • 从不同步,效率最高

缺点

  • 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢
  • aof运行效率也比rdb慢

Redis实现订阅发布(消息队列)

Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

Redis客户端可以订阅任意数量的频道。

Pub/Sub从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。

使用场景:

  • 实时消息系统
  • 实时聊天
  • 订阅、关注系统都可以

Redis主从复制

Master以写为主,Slave以读为主。 截屏2023-04-20 17.07.28.png

主从复制的作用:

  • 数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
  • 故障恢复: 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,实际上是一种服务的冗余
  • 负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量
  • 高可用(集群)基石: 主从复制是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础

测试:

  • 主机断开连接,从机依旧连接到主机,但是没有写操作。主机回来后,从机依旧可以直接获取到主机写的信息。
  • 如果使用命令行配置的主从,如果从机重启,则会变成主机。当它再次变成从机时,依旧可以拿到主机写的全部信息(全量复制)。
    • 全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
    • 增量复制:master继续将新的所有收集到的修改命令依次传给slave,完成同步

Redis哨兵模式(自动选举master)

哨兵模式是一种特殊的模式。首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例截屏2023-04-20 17.51.15.png

截屏2023-04-20 17.56.20.png 如果master节点断开了,就会从从机中随机选择一个服务器作为主节点(投票算法)。

如果主机此时回来了,也只能归并到新的主机下,当作从机,这就是哨兵模式的规则。

优点:

  • 哨兵集群,基于主从复制模式,所有的主从配置优点,它都有
  • 主从可以切换,故障可以转移,系统的可用性就会更好
  • 哨兵模式就是主从模式的升级,手动到自动,更加健壮

缺点:

  • Redis不好在线扩容,集群容量一旦到达上限,在线扩容十分麻烦
  • 实现哨兵模式的配置本身就比较麻烦

缓存穿透及解决方案

概念

用户想要查询一个数据,发现redis内存数据库中没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库,这会给持久层数据库造成很大的压力,也就相当于出现了缓存穿透。

解决方案

  • 布隆过滤器: 布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力。
  • 缓存空对象: 当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据库也会从缓存中获取,保护了后端数据源。(存在资源浪费和一致性问题)

缓存击穿及解决方案

概念

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新的数据,并且回写缓存,导致数据库压力过大。

解决方案

  • 设置热点数据永不过期
  • 加互斥锁

缓存雪崩及解决方案

概念

缓存雪崩,是指在某一个时间段,缓存集中过期失效或者Redis宕机。

解决方案

  • redis高可用
  • 限流降级
  • 数据预热