Redis

1.什么是Redis(官方解释) Introduction to Redis | Redis

    Redis 是一个开源(BSD 许可)的[内存](https://so.csdn.net/so/search?q=%E5%86%85%E5%AD%98&spm=1001.2101.3001.7020)**数据结构存储**,用作数据库、缓存、消息代理和流引擎。Redis 提供[数据结构](https://so.csdn.net/so/search?q=%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84&spm=1001.2101.3001.7020),例如 [字符串](https://redis.io/topics/data-types-intro#strings "字符串")、[散列](https://redis.io/topics/data-types-intro#hashes "散列")、[列表](https://redis.io/topics/data-types-intro#lists "列表")、[集合](https://redis.io/topics/data-types-intro#sets "集合")、带范围查询的[排序集合、](https://redis.io/topics/data-types-intro#sorted-sets "排序集合、")[位图](https://redis.io/topics/data-types-intro#bitmaps "位图")、[超日志](https://redis.io/topics/data-types-intro#hyperloglogs "超日志")、[地理空间索引](https://redis.io/commands/geoadd "地理空间索引")和[流](https://redis.io/topics/streams-intro "流")。Redis 内置了[复制](https://redis.io/topics/replication "复制")、[Lua 脚本](https://redis.io/commands/eval "Lua 脚本")、[LRU 驱逐](https://redis.io/topics/lru-cache "LRU 驱逐")、[事务](https://redis.io/topics/transactions "事务")和不同级别的[磁盘持久性](https://redis.io/topics/persistence "磁盘持久性"),并通过以下方式提供高可用性[Redis Sentinel](https://redis.io/topics/sentinel "Redis Sentinel")和[Redis Cluster](https://redis.io/topics/cluster-tutorial "Redis Cluster")的自动分区。

2.为什么使用Redis

  • 读写性能优异

      Redis能读的速度是110000次/s,写的速度是81000次/s
    
  • 数据类型丰富

      Redis支持二进制案例的String,Lists,Hashes,Sets及Ordered Sets数据类型操作
    
  • 原子性

      Redis的所有操作都是原子性的
    
  • 丰富的特性

      Redis支持 publish/subscribe, 通知, key 过期等特性
    
  • 持久化

      Redis支持两种持久化的方式,一种是RDB方式,另一种是AOF方式
    
  • 发布订阅

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

      三种模式:主从模式,哨兵模式,集群模式
    

3.Redis的使用场景

  • 数据缓存

      一般有两种方式保存数据:
    
              读取前,先去读Redis,如果没有数据,获取数据库,将数据拉入Redis。
    
              插入数据时间,同时写入Redis。
    
      **方案一:**实施起来简单,但是有两个需要注意的地方:避免缓存击穿(Key对应的数据存在,但在Redis中过期,此时若有大量并发请求过来,这写请求发现缓存过去一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮),数据的实时性相对差一些。
    
      **方案二:**数据实时性强,但是开发时不便于统一处理。
    
  • 临时业务运用

      Redis中可以使用expire命令设置一个键的生存时间,到时间后Redis会删除它,利用这一特性可以运用到显示的优惠活动信息,短信,手机验证码等业务场景。
    
  • 分布式锁

      可以利用redis的setnx命令进行,setnx:"set if not exists"就是如果不存在则成功设置缓存同时返回1,否则返回0,即先用setnx来抢锁,如果抢到之后,再用expire给锁设置一个过期时间,防止忘记释放锁
    
      主要用在秒杀活动中。
    
  • 延时操作

      比如在订单生产后我们占用了库存,10分钟后去检验用户是否真正购买,如果没有购买将单据设置无效,同时还原库存
    
      解决方案:我们在订单生产时,设置一个key,同时设置10分钟后过期,我们在后台实现一个监听器,监听key的实效,监听到key失效时将后续逻辑加上。
    
  • 简单队列

      由于Redis有list push和list pop这样的命令,所以能够很方便的执行队列操作。
    

4.Redis的5种基本数据类型

  • Redis数据结构简介

数据结构

结构存储的值

结构的读写能力

String字符串

可以是字符串,整数,浮点数

对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作;

List列表

一个链表,链表上的每一个节点都包含一个字符串

对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素;

Set集合

包含字符串的无序集合

字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等

Hash散列

包含键值对的无序散列表

包含方法有添加、获取、删除单个元素

Zset有序集合

和散列一样用于存储键值对

字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定;包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素

  • 基础数据结构

String字符串

    String是Redis中最基本的数据类型,一个key对应一个value。
          String类型是二进制安全的,意思是redis的string可以包含任何数据。如数字,[字符串](https://so.csdn.net/so/search?q=%E5%AD%97%E7%AC%A6%E4%B8%B2&spm=1001.2101.3001.7020),jpg图片或者序列化的对象。

           **命令使用**

命令

描述

使用

GET

获取存储在给定键中的值

GET name

SET

设置存储在给定键中的值

SET name value

DEL

删除存储在给定键中的值

DEL name

INCR

将键存储的值加1

INCR key

DECR

将键存储的值减1

DECR key

INCRBY

将键存储的值加上整数

INCRBY key amount

DECRBY

将键存储的值减去整数

DECRBY key amount

 **命令执行**

Redis_第1张图片

            **实战场景**

**缓存:**把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql作为持久化层,降低mysql的读写压力

                    **计数器:**redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他数据源

                    **session:**常见方案spring session + redis实现session共享

List列表

Redis中的list其实就是链表(redis用双向链表去实现list)

命令使用

命令

简述

使用

RPUSH

将给定值推入到列表右端

RPUSH key value

LPUSH

将给定值推入到列表左端

LPUSH key value

RPOP

从列表的右端弹出一个值,并返回被弹出的值

RPOP key

LPOP

从列表的左端弹出一个值,并返回被弹出的值

LPOP key

LRANGE

获取列表在给定范围上的所有值

LRANGE key 0 -1

LINDEX

通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推

LINEX key index

命令执行

Redis_第2张图片

Set集合

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

            Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

    **命令使用**

命令

简述

使用

SADD

向集合添加一个或多个成员

SADD key value

SCARD

获取集合的成员数

SCARD key

SMEMBERS

返回集合中的所有成员

SMEMBERS key member

SISMEMBER

判断 member 元素是否是集合 key 的成员

SISMEMBER key member

命令执行

Redis_第3张图片

Hash散列

Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。

**    命令使用**

命令

简述

使用

HEST

添加键值对

HSET hash-key sub-key1 value1

HGET

获取指定散列键的值

HGET hash-key key1

HGETALL

获取散列中包含的所有键值对

HGETALL hash-key

HDEL

如果给定键存在于散列中,那么就移除这个键

HDEL hash-key sub-key1

    **命令执行**

Redis_第4张图片

Zset有序集合

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。

    **命令使用**

命令

简述

使用

ZADD

将一个带有给定分值的成员添加到有序集合里面

ZADD zset-key 178 member1

ZRANGE

根据元素在有序集合中所处的位置,从有序集合中获取多个元素

ZRANGE zset-key 0-1 withccores

ZREM

如果给定元素成员存在于有序集合中,那么就移除这个元素

ZREM zset-key member1

命令执行

Redis_第5张图片

5.Redis中的3种特殊类型

HyperLogLogs(基数统计)

    Redis 2.8.9 版本更新了 Hyperloglog 数据结构!
  • 什么是基数?

      举个例子,A={1,2,3,4,5},B={3,5,6,7,9},那么基数(不重复的元素)=1,2,,4,6,7,9(可以接受一定误差)
    
  • HyperLogLogs基数统计用来解决什么问题?

      这个结构可以非常省内存的去统计各种计数,比如注册IP数,每日访问IP数、在线用户数等。
    
  • 相关命令使用

      ![](https://img-blog.csdnimg.cn/18358a164071421fa828bd7a53881158.png)
    

Bitmap (位存储)

Bitmap 即位图数据结构,都是操作二进制位来进行记录,只有0 和 1 两个状态。

  • 解决什么问题

      比如:统计用户信息,活跃,不活跃,登录,未登录,打卡,未打卡   两个状态的都可以使用Bitmap。
    
      存储一年的打卡状态的内存消耗:365天=365bit  1字节=8bit
    
  • 相关命令使用

使用Bitmap来记录周一到周日的打卡,周一:1 周二:0 周三:0 …

Redis_第6张图片

查看某一天是否有打卡

Redis_第7张图片

统计操作,统计打卡的天数

geospatial (地理位置)

Redis 的 Geo 在 Redis 3.2 版本就推出了! 这个功能可以推算地理位置的信息: 两地之间的距离, 方圆几里的人

geoadd

添加地理位置

Redis_第8张图片

两级无法直接添加,我们一般会下载城市数据

  • 有效的经纬度从-180度到180度

  • 有效的纬度从-85.05112878度到85.05112878度

      ![](https://img-blog.csdnimg.cn/a3d04f0d0ebb4503946358b8589b8685.png)
    

Redis进阶篇—持久化

持久化的方式有两种:RDB和AOF,redis默认采用的是RDB的方式

RDB持久化

RDB就是Redis database的缩写,RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值

触发方式

触发RDB持久化的方式有两种:手动触发和自动触发

手动触发

手动触发分别对应save和bgsave命令

  • **save命令:**阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用
  • **bgsave命令:**Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短

bgsave流程图

Redis_第9张图片

具体流程:

  • redis客户端执行bgsave命令或者自动触发bgsave命令;
  • 主进程判断当前是否已经存在正在执行的子进程,如果存在,那么主进程直接返回;
  • 如果不存在正在执行的子进程,那么就fork一个新的子进程进行持久化数据,fork过程是阻塞的,fork操作完成后主进程即可执行其他操作;
  • 子进程先将数据写入到临时的rdb文件中,待快照数据写入完成后再原子替换旧的rdb文件;
  • 同时发送信号给主进程,通知主进程rdb持久化完成,主进程更新相关的统计信息;

自动触发

在4中情况下会自动触发:

  • redis.conf中配置save m n,即在m秒内有n次修改时,自动触发bgsave生成rdb文件;

  • 主从复制时,从节点要从主节点进行全量复制时也会触发bgsave操作,生成当时的快照发送到从节点;

  • 执行debug reload命令重新加载redis时也会触发bgsave操作;

  • 默认情况下执行shutdown命令时,如果没有开启aof持久化,那么也会触发bgsave操作;

RDB优缺点

优点:

  • RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景;
  • Redis加载RDB文件恢复数据要远远快于AOF方式;

缺点:

  • RDB方式实时性不够,无法做到秒级的持久化;
  • 每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高;
  • RDB文件是二进制的,没有可读性,AOF文件在了解其结构的情况下可以手动修改或者补全;
  • 版本兼容RDB文件问题;

AOF持久化

Redis是“写后”日志,Redis先执行命令,把数据写入内存,然后才记录日志。日志里记录的是Redis收到的每一条命令,这些命令是以文本形式保存。PS: 大多数的数据库采用的是写前日志(WAL),例如MySQL,通过写前日志和两阶段提交,实现数据和逻辑的一致性。

AOF日志采用写后日志,即先写内存,再写日志

如何实现AOF

AOF日志记录Redis的每个写命令,步骤分为:命令追加(append)、文件写入(write)和文件同步(sync)

  • **命令追加:**当AOF持久化功能打开了,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器的 aof_buf 缓冲区
  • **文件写入和同步:**关于何时将 aof_buf 缓冲区的内容写入AOF文件中,Redis提供了三种写回策略

配置项

写回时机

优点

缺点

Always

同步写回

可靠性高,数据基本不会丢失

每个写命令都要落盘,性能影响较大

Everysec

每秒写回

性能适中

宕机时丢失1秒内的数据

No

操作系统控制的写回

性能好

宕机时丢失数据较多

Always 同步写回:每个写命令执行完,立马同步地将日志写回磁盘;

Everysec 每秒写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;

No 操作系统控制的写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘;

Redis进阶—缓存问题:一致性,击穿,穿透,雪崩,污染等

在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问Mysql等数据库。这样可以大大缓解数据库的压力

缓存穿透

**原因:**缓存和数据库中都没有数据,而用户不断发送请求

解决方案:

  • 接口层增加校验,如用户鉴权校验
  • 从缓存没有取到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒,这样可以防止攻击用户反复用同一个id暴力攻击
  • 布隆过滤器

缓存击穿

**原因:**缓存中没有但是数据库中有数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

解决方案:

  • 设置热点数据永不过时
  • 接口限流,熔断和降级
  • 加锁互斥

缓存雪崩

**原因:**缓存中数据大批量过期,而查询数据量过大,引起数据库压力过大甚至宕机,和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库

解决方案:

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
  • 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中
  • 设置热点数据永远不过期

缓存污染(或满了)

缓存污染指的是缓存中一些只会被访问一次或者几次的的数据,被访问完后,再也不会被访问到,但这部分数据依然留存在缓存中,消耗缓存空

缓存污染会随着数据的持续增加而逐渐显露,随着服务的不断运行,缓存中会存在大量的永远不会再次被访问的数据。缓存空间是有限的,如果缓存空间满了,再往缓存里写数据时就会有额外开销,影响Redis性能。这部分额外开销主要是指写的时候判断淘汰策略,根据淘汰策略去选择要淘汰的数据,然后进行删除操作

缓存淘汰策略

Redis共支持八种淘汰策略,分别是noeviction、volatile-random、volatile-ttl、volatile-lru、volatile-lfu、allkeys-lru、allkeys-random 和 allkeys-lfu 策略

主要分三类看:

  • 不淘汰

      noeviction(v4.0后默认的)
    
  • 对设置了过期时间的数据中进行淘汰

      随机: volatile-random
    
      ttl: volatile-ttl
    
      lru: volatile-lru
    
      lfu: volatile-lfu
    
  • 全部数据进行淘汰

      随机:allkeys-random
    
      lru:allkeys-lru
    
      lfu:allkeys-lfu
    

8种策略具体知识(www.baidu.com)

数据库和缓存一致性

原因:

使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库:

Redis_第10张图片

读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存和数据库间的数据一致性问题。

不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况

举例:

  1. 如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。
  2. 如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。

因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题

解决方案:

方案一:队列 + 重试机制

流程如下:​​​​

  • 跟新数据库数据
  • 缓存因为种种问题删除失败
  • 将需要删除的key发送至消息队列
  • 自己消费消息,获得需要删除的key
  • 继续重试删除操作,直到成功

然而该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。

方案二:异步更新缓存(基于订阅binlog的同步机制)

流程如下:

  • MySQL binlog增量订阅消费+消息队列+增量数据更新到redis

      1、读Redis:热数据基本都在redis
    
      2、写MySQL:增删改都是操作MySQL
    
      3、更新Redis数据:MySQL的数据操作binlog,来更新到redis
    
  • Redis更新

      1、数据操作主要分为两大操作:
    
      1)一个是全量(将全部数据一次性写到Redis)
    
      2)一个是增量(mysql的update、insert、delete)
    
      2、读取binlog后分析,利用[消息队列](https://so.csdn.net/so/search?q=%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97&spm=1001.2101.3001.7020),推送更新各个平台的redis数据缓存
    
      这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。
    

你可能感兴趣的:(redis,数据库,缓存)