Redis

浪子不回头ぞ 提交于 2020-09-27 13:59:00

Redis

Nosql

​ 现在是大数据时代,一般的关系型数据库无法快速处理那么大的访问量。

为什么使用Nosql

单机Mysql时代

​ 那时候更多使用静态网页Html,访问量不会很大,单个数据库已经足够。

这样的模式存在什么问题?

  • 数据量如果太大,一台服务器放不下
  • 数据的索引(B+Tree),一台服务器也放不下
  • 读写混合,一台服务器无法承受

image-20200817084057828

Memcached(缓存) + MySQL + 垂直拆分

​ 实际场景80%都是进行查询的操作,每次都去查数据库效率很低。所以我们希望使用缓存减轻数据库压力。

​ 发展历程:优化数据结构和索引——》文件缓存(IO)——》Memcached

image-20200817084457668

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

​ 以前使用MylSAM : 表锁 查一行数据锁住了整张表 高并发下有严重的问题

Innodb使用行锁解决

​ MySQL在那个时代推出了表分区,但是使用的不多。

​ 集群在这时已经能满足大部分需求。

image-20200817085058225

大数据时代

​ 2010-2020发生了翻天覆地的变化,各种实时变化的数据。

​ MySQL等关系型数据库的弊端就暴露了,无法应付这种场景。

image-20200817090436934

为什么要用NoSQL

​ 用户的每天生成的个人信息,社交网络,地理位置等等以PB计,呈爆发式增长。

​ NoSQL非常适合应对这种场景。

什么是NoSQL

​ NoSQL = Not Only SQL 不仅仅是SQL 泛指非关系型数据库,随着互联网2.0时代的诞生,传统的数据库很难处理,尤其是超大规模高并发的社区,暴露很多无法解决的问题。

​ 很多的数据类型不需要一个固定格式(行 列)

NoSQL的特点

  • 方便扩展(数据之间没有关系)
  • 大数据量高性能 Redis一秒可以写八万次 读取十一万次 细粒度的NoSQL缓存记录级,性能高
  • 数据类型是多样的 不需要设计数据库表(表总是设计的很烂)

关系型与非关系型

传统的RDBMS(关系型)

  • 结构化组织
  • SQL
  • 数据和表的关系都在单独的表中
  • 数据定义语言
  • 严格的一致性
  • 基础的事务

NoSQL

  • 不仅仅是数据
  • 没有固定的查询语言
  • 键值对存储,列存储,文档存储,图形数据库
  • 最终一致性
  • CAP定理 和 BASE
  • 高性能 高可用 高可扩

真正在实际环境用的肯定是NoSQL+RDBMS 技术没有之分 只看如何使用

image-20200817093701517

淘宝网站的数据存储

商品的基本信息

商品名称 价格 商品信息

  • 使用关系型数据库

商品的描述、评论

  • 文档型数据库 MongoDB

图片

  • 分布式文件系统 FastDFS

  • 淘宝 TFS

  • GOOLE GFS

  • Hadoop HDFS

  • 阿里云 oss

关键字

  • 搜索引擎 solr
  • elasticsearcher
  • 淘宝 ISearch

商品的波段信息

​ 秒杀之类的

  • 内存数据库
  • Redis Tair Memache

商品的交易 外部支付接口

  • 第三方应用接口

NoSQL四大分类

KV键值对:

  • 新浪:Redis
  • 美团:Redis + Tair
  • 阿里:Redis + memcache

image-20200817100738835

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

image-20200902140818831

image-20200902140838081

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%的情况都是进行读取的操作,所以将读写分离,可以有效地提高我们的效率。

image-20200903105056468

主从复制的作用

  • ​ 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  • ​ 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复。
  • ​ 负载均衡:在主从复制的基础上,配合读写分离,可以有主节点提供写服务,由主节点提供写服务,由从节点提供读服务,分担服务器负载;尤其在写少读多的场景下,通过多个从节点分担读负载,可以大大提高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

优缺点

优点

  • 基于主从复制,保留了主从复制的优点
  • 系统的稳定性、健壮性更佳
  • 自动选出新主机,实时性更好

缺点

  • 在线扩容并不方便
  • 配置很麻烦

image-20200903132957528

image-20200903133119305

Redis缓存穿透

​ 用户查的数据在redis中不存在,即缓存没有命中,于是向持久层数据库查询,当很多用户的查询,缓存都没有命中,就会给持久层数据库很大的压力,黑客故意查询redis中不存在的数据,导致持久层宕机,这就是缓存穿透。

缓存击穿

​ 和穿透不同的是,击穿特指缓存的某个点被大量访问而被击破,并且数据是查得到的。

缓存雪崩

​ 到了某个时间,缓存集体失效。导致持久层数据库宕机

解决方案

  • redis高可用:搭建集群
  • 限流降级:缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  • 数据预热:把可能要使用的数据先访问缓存一遍,对缓存设置不同的过期时间。

使用redis缓存的场景

  • 更改频率不高的数据
  • 秒杀这种访问量极大,更新很频繁,数据不能丢失的场景

不使用redis缓存的场景

  • 钱、密钥、业务核心关键数据
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!