Redis 从表层逐渐深入底层

Redis 从表层逐渐深入底层,第1张

什么是 Redis?

Redis 代表REmote DIctionary Server。它由 Salvatore Sanfilippo 在 2006 年用 C 语言编写。它是一个 NoSQL 高级键值数据存储。Redis 中的读写 *** 作非常快,因为它将所有数据存储在内存中。数据也可以存储在磁盘上,也可以写回内存。

它通常被称为数据结构服务器,因为键可以包含字符串、散列、列表、集合、排序集合、位图和超日志。

NoSQL 意味着not only SQL。NoSQL 数据库不定义像表这样的数据库结构,也不支持查询,比如 SQL SELECT。数据主要存储为 JSON 对象或键值对。

Redis 有什么作用?

由于 Redis 将其数据存储在内存中,因此它最常用作缓存;

访问数据库的效率比较低,在请求与数据库之间加上 redis 缓存,由于缓存里面的数据在内存中,请求在一定程度上避免了和磁盘直接打交道,提升了数据返回的运行效率;

应用场景:
配合关系型数据库做高速缓存

1、高频次,热门数据的访问,降低数据库的 IO;
2、分布式架构,当做 session 共享;
3、多样化的数据结构支持持久化数据;

web 请求访问缓存数据库主数据库正常的流程

缓存穿透、缓存击透以及缓存雪崩

虽然使用缓存可以大大的提升系统的访问效率,但是同时也伴随着其他的问题需要解决,那就是缓存穿透、缓存击穿以及缓冲雪崩的情况是存在的;

存在的问题就是,为了提升访问效率添加的缓存在某些特定的场景之下,缓存失效了(缓存中有需要访问数据缓存才不会失效,其余的都相当于是缓存失效了,虽然片面但是可以这样理解),使得整个系统的性能遭受到了一定的影响;

缓存穿透:缓存以及数据库中都没有某一个数据,使得每次的 web 请求都是直接访问到数据库的,缓存失去了原本的提升效率的作用;

缓存击穿:缓存中的某个 key 在某一瞬间失效了,但是数据库还存在这个 key ,突然大量的请求前来,直接访问了数据库,导致缓存失去了原本提升效率的作用;

缓存雪崩:缓存中大量的 key 都失效了,导致请求直接访问到了数据库,使得数据库的压力增大了;

所有的所有所有导致的各种缓存问题,根本原因就是,缓存中没有用户要访问的数据或者,缓存中用户需要的数据过期了,使得,请求与底层的数据库直接打交道,缓存失去了作用,导致的问题;

缓存穿透


缓存穿透解决方式:
1、对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟,这样可以防止攻击用户反复用同一个id暴力攻击。

2、设置可访问的名单(白名单):
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。

3、采用布隆过滤器:布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。

4、进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。

缓存击穿

解决办法:
1、预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长。
2、实时调整:现场监控哪些数据热门,实时调整key的过期时长。
3、加互斥锁:流程如下图所示

缓存雪崩

缓存击穿指在某一极小时间段内,对同一过期数据的大量访问;
(大量请求都访问某一个过期的key)

缓存雪崩指在某一极小时间段内,对很多已过期的不同数据的大量访问;
(大量请求都访问某一批过期的key)

与缓存击穿的图示基本一致,只是同一时间访问的 key 失效的数量的多少不一样而已

缓存中大量的 key 都失效了,导致了缓存雪崩;

Redis 常用命令

与 Redis 有关的命令都是与 *** 作 key 与 value 的;

与 key 相关的命令
|命令 |命令的作用  |
|--|--|
| <key> * | 查看当前库的所有 <key> |

| exist <key>  | 判断某个 <key> 是不是存在  |

| type <key>  |  判断 <key> 是什么类型  |

| del <key> | 删除某个指定的 <key>  |

| unlink <key>  | 异步的删除 <key>   |

| expire <key> 10 |  为给定的<key>设置过期时间 |

| ttl <key>  | 查看 <key> 的状态 -1 表示永不过期 ,-2 表示已经过期了|

| select  | 切换数据库  |

| dbsize  | 查看数据库的容量  |

| flushdb | 清空当前库  |

| flushall |  清空所有库 |
Redis - 数据类型 - 底层的数据结构

Redis 基本的数据类型有如下几种,在后序的版本中,可能添加新的疏浚类型,同一个数据类型实现的数据结构也是不一样的,比如 String 类型实现它的数据类型有 embstr、raw、int 三种,Redis 底层根据不同的情况选用不同的数据结构,减少内存空间的浪费;

对于不同的编码方式,也就是不同的编码方式对应的数据结构简单说明表:

Redis - String

String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象;

什么是二进制安全

二进制安全是指,在传输数据时,保证二进制数据的信息安全,也就是不被篡改、破译等,如果被攻击,能够及时检测出来。
二进制安全包含了密码学的一些东西,比如加解密、签名等。

举个例子: 把数据11110000加密成10001000,然后传送,就是一种二进制安全的做法
String 常用的命令
| set <key> <value>   | 给具体的 <key> 设置 <value> |
| get <key> 		  | 查询对应的键值 |
| strlen <key>        | 获取键对应的值的长度 |
| setnx <key> <value> | 当键<key>不存在的时候,设置 <value>  |
| incr <key> 		  | 将 key 中存储的数值加一;只能对数字值 *** 作,如果为空,新增值为 1|
| decr <key> 		  | 将 key 存储的数字减一 ,只能对数字 *** 作,如果为空新增加的数字为 -1|
| incrby <key> <步长>  |将 key 中存储的数字值按照步长进行增加,自定义步长|

| mset  <key1><value1><key2><value2>  |同时设置一个或多个 key-value 对|
| mget  <key1><key2><key3>            | 同时获取多个 key 对应的 value |

| msetnx <k1> <value1> <k2> <value2>  | 同时设置一个或者多个 key - value 对,要进行赋值 |的键值对是不存在的,存在的话不能设置成功;|
| getrange  <key> <起始位置> <结束位置>  | 获取起始结束位置的 key 对应的 value
| setex  <key> <过期时间> <value>      | 设置键值对的过期时间|
| getset <key> <value> | 以新换旧,设置了新值同时获得旧值 |
底层的数据结构 - SDS(simple dynamic string)的实现

简单的动态字符串

底层是 C++ 实现的一种简单动态字符串的数据结构,类似于 Java 的ArrayList ,采用分配冗余空间的形式,减少内存的频繁分配;

capacity 的长度一般长于字符串长度 len ,字符串小于 1 M的时候,扩容加倍现有的空间,超过 1 M,每次扩容只会扩容 1M 的空间;

最大的字符串长度是 512M;

Redis - List

单键多数值;

常用命令
// 在每个命令前面加上 l 表示的是关于 list 的 *** 作

lpush/rpush  <key> <value1> <value2> <value3>      从左边/右边插入一个或多个值。

lpop/rpop  <key>     从左边/右边吐出一个值。值在键在,值光键亡。

rpoplpush  <key1> <key2><key1>      列表右边吐出一个值,插到<key2>列表左边。

lrange <key> <start> <stop>       按照索引下标获得元素(从左到右)

lrange mylist 0 -1         0左边第一个,-1右边第一个,(0-1表示获取所有)

lindex <key> <index>      按照索引下标获得元素(从左到右)

llen <key>      获得列表长度 

linsert <key>  before <value> <newvalue><value>的后面插入<newvalue>插入值

lrem <key> <n> <value>      从左边删除n个value(从左到右)

lset<key> <index> <value>    将列表key下标为index的值替换成value
底层的数据结构 - 双向链表 LinkedList 的实现

双向链表,实现的性能比较高,对于两端之间的数据 *** 作的性能是比较好的,但是中间结点的 *** 作就会慢一些;

每一个 key 对应的是一个双向链表,可以对一个 key 添加多个 value ;

列表元素比较少的时候,使用 ziplist ,压缩了空间,所有的元素是连续放置在一个内存空间中的;

Redis - Set

与 List 类似的功能,但是Set 是可以自动的去重的,就是存储的元素需要是唯一的;在希望存储数据,但是存储的数据必须唯一的时候,使用 Set 这种数据类型是合适的;

常用命令
// 在每一个命令前面都加上一个 s 表示关于 Set 类型元素的 *** 作
sadd <key> <value1> <value2> ...    <key> 中添加多个元素
smembers <key>      取出该集合的所有值。
sismember <key> <value>      判断集合<key>是否为含有该<value>值,有1,没有0
scard<key>      返回该集合的元素个数。
srem <key> <value1> <value2> ....       删除集合中的某个元素。
spop <key>      随机从该集合中吐出一个值。
srandmember <key> <n>      随机从该集合中取出n个值。不会从集合中删除 。
smove <source> <destination> value      把集合中一个值从一个集合移动到另一个集合
sinter <key1> <key2>      返回两个集合的交集元素。
sunion <key1> <key2>      返回两个集合的并集元素。
sdiff <key1> <key2>       返回两个集合的差集元素(key1中的,不包含key2中的)

底层数据结构 - Hash 的实现

Redis -Hash 常用命令
hset <key> <field> <value><key>集合中的  <field>键赋值<value>

hget <key1> <field><key1>集合<field>取出 value 

hmset <key1> <field1> <value1> <field2> <value2>...       批量设置hash的值

hexists<key1> <field>      查看哈希表 key 中,给定域 field 是否存在。 

hkeys <key>      列出该hash集合的所有field

hvals <key>      列出该hash集合的所有value

hincrby <key> <field> <increment>      为哈希表 key 中的域 field 的值加上增量 1   -1

hsetnx <key> <field> <value>      将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 
底层数据结构 - Hash 的实现

key 是普通的数据;
value 是一种 Redis Hash 的这种数据结构类似于 Map

Redis Hash 是一个 String 类型的 field(字段) 和 value(值) 的映射表;

下面是 Hash 的一个应用(在 value 里面的存储是 Hash 结构)

底层数据结构 - ziplist 的实现 Redis - ZSet

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/719733.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存