最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

环球新消息丨Redis 深度学习

来源:博客园

简介

Redis 是完全开源免费的,是一个高性能的key-value内存数据库。

Redis 有三个主要的特点:


【资料图】

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
  • Redis支持数据的备份,即master-slave模式的数据备份。

优点:

  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 支持丰富的数据类型 - Redis支持最大多数开发人员已经知道如列表,集合,可排序集合,哈希等数据类型。这使得在应用中很容易解决的各种问题,因为我们知道哪些问题处理, 使用哪种数据类型更好解决。
  • 操作都是原子的 - 所有 Redis 的操作都是原子,从而确保当两个客户同时访问 Redis 服务器得到的是更新后的值(最新值)。原子性(atomicity):一个事务是一个不可分割的最小工作单位,事务中包括的诸操作要么都做,要么都不做。Redis所有单个命令的执行都是原子性的,这与它的单线程机制有关;
  • 缓存
  • 消息传递队列中使用(Redis原生支持发布/订阅)
  • 在应用程序中,如:Web应用程序会话,网站页面点击数等任何短暂的数据;

Redis支持八种数据类型:

  • String 字符串
  • Hash 哈希
  • List 列表
  • Set 集合
  • Sorted Set 有序集合

String

string是redis最基本的类型,一个key对应一个value。string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象string类型是Redis最基本的数据类型,一个键最大能存储512MB。

redis 127.0.0.1:6379> SET name "itcast"OKredis 127.0.0.1:6379> GET name"itcast"127.0.0.1:6379> type namestring

删除键值

redis 127.0.0.1:6379> del name

删除这个 key 及对应的 value验证键是否存在

redis 127.0.0.1:6379> exists name(integer) 0

在上面的例子中,SET 和 GET 是 Redis STRING命令,name 和 itcast 是存储在 Redis 的键和字符串值。

Hash 哈希

Redis hash 是一个键值对集合。

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

127.0.0.1:6379> HMSET my_hash_table username itcast age 18 sex maleOK127.0.0.1:6379> HGETALL my_hash_table1) "username"2) "itcast"3) "age"4) "18"5) "sex"6) "male"

在上面的例子中,哈希数据类型用于存储包含用户基本信息的用户对象。

这里 HSET,HGETALL 是 Redis HASHES命令, 同时 my_hash_table 也是一个键。

List 列表

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

redis 127.0.0.1:6379> lpush tutorial_list redis(integer) 1redis 127.0.0.1:6379> lpush tutorial_list mongodb(integer) 2redis 127.0.0.1:6379> lpush tutorial_list rabbitmq(integer) 3redis 127.0.0.1:6379> lrange tutorial_list 0 101) "rabitmq"2) "mongodb"3) "redis"

列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。

Set 集合

Redis Set是string类型的无序集合。

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

在 Redis 可以添加,删除和测试成员存在的时间复杂度为 O(1)。

127.0.0.1:6379> sadd myset redis(integer) 1127.0.0.1:6379> sadd myset mongodb(integer) 1127.0.0.1:6379> sadd myset rabitmq(integer) 1127.0.0.1:6379> sadd myset rabitmq(integer) 0127.0.0.1:6379> smembers myset1) "mongodb"2) "redis"3) "rabitmq"

注:在上面的例子中 rabitmq 被添加两次,但由于它是只集合具有唯一特性。

集合中最大的成员数为 \(2^{32}\) - 1(4294967295, 每个集合可存储40多亿个成员)。

zset(sorted set:有序集合)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数(权重)。redis正是通过分数来为集合中的成员(权重)进行从小到大的排序。

zset的成员是唯一的,但分数(score)却可以重复。

127.0.0.1:6379> zadd mysortset 0 redis(integer) 1127.0.0.1:6379> zadd mysortset 2 mongodb(integer) 1127.0.0.1:6379> zadd mysortset 1 rabitmq(integer) 1127.0.0.1:6379> ZRANGEBYSCORE mysortset 0 10001) "redis"2) "rabitmq"3) "mongodb"

请求/响应协议和RTT

Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:

  • 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
  • 服务端处理命令,并将结果返回给客户端。
Client: INCR XServer: 1Client: INCR XServer: 2Client: INCR XServer: 3Client: INCR XServer: 4

客户端和服务器通过网络进行连接。这个连接可以很快(loopback接口)或很慢(建立了一个多次跳转的网络连接)。无论网络延如何延时,数据包总是能从客户端到达服务器,并从服务器返回数据回复客户端。

这个时间被称之为 RTT (Round Trip Time - 往返时间). 当客户端需要在一个批处理中执行多次请求时很容易看到这是如何影响性能的(例如添加许多元素到同一个list,或者用很多Keys填充数据库)。例如,如果RTT时间是250毫秒(在一个很慢的连接下),即使服务器每秒能处理100k的请求数,我们每秒最多也只能处理4个请求。

如果采用loopback接口,RTT就短得多(比如我的主机ping 127.0.0.1只需要44毫秒),但它仍然是一笔很多的开销在一次批量写入操作中。

幸运的是有一种方法可以改善这种情况。

Redis 管道(Pipelining)

一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。

这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。

Redis很早就支持管道(pipelining)技术,因此无论你运行的是什么版本,你都可以使用管道(pipelining)操作Redis。下面是一个使用的例子:

$ (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379+PONG+PONG+PONG

这一次我们没有为每个命令都花费了RTT开销,而是只用了一个命令的开销时间。

非常明确的,用管道顺序操作的第一个例子如下:

Client: INCR XClient: INCR XClient: INCR XClient: INCR XServer: 1Server: 2Server: 3Server: 4

重要说明:

使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存。所以,如果你需要发送大量的命令,最好是把他们按照合理数量分批次的处理,例如10K的命令,读回复,然后再发送另一个10k的命令,等等。这样速度几乎是相同的,但是在回复这10k命令队列需要非常大量的内存用来组织返回数据内容。

内存优化

内存压缩

Redis2.2版本及以后,存储集合数据的时候会采用内存压缩技术,以使用更少的内存存储更多的数据。如Hashes,Lists,Sets和Sorted Sets,当这些集合中的所有数都小于一个给定的元素,并且集合中元素数量小于某个值时,存储的数据会被以一种非常节省内存的方式进行编码,使用这种编码理论上至少会节省10倍以上内存(平均节省5倍以上内存)。并且这种编码技术对用户和redis api透明。因为使用这种编码是用CPU换内存,所以我们提供了更改阈值的方法,只需在redis.conf里面进行修改即可.

## 最大键值元素个数hash-max-zipmap-entries 64 (2.6以上使用hash-max-ziplist-entries)## 最大键值长度 单位字节hash-max-zipmap-value 512  (2.6以上使用hash-max-ziplist-value)list-max-ziplist-entries 512list-max-ziplist-value 64zset-max-ziplist-entries 128zset-max-ziplist-value 64set-max-intset-entries 512

(集合中)如果某个值超过了配置文件中设置的最大值,redis将自动把把它(集合)转换为正常的散列表。这种操作对于比较小的数值是非常快的,但是,如果你为了使用这种编码技术而把配置进行了更改,你最好做一下基准测试(和正常的不采用编码做一下对比).

内存分配

为了存储用户数据,当设置了maxmemory后Redis会分配几乎和maxmemory一样大的内存(然而也有可能还会有其他方面的一些内存分配)

  • 当某些缓存被删除后Redis并不是总是立即将内存归还给操作系统。这并不是redis所特有的,而是函数malloc()的特性。例如你缓存了5G的数据,然后删除了2G数据,从操作系统看,redis可能仍然占用了5G的内存(这个内存叫RSS,后面会用到这个概念),即使redis已经明确声明只使用了3G的空间。这是因为redis使用的底层内存分配器不会这么简单的就把内存归还给操作系统,可能是因为已经删除的key和没有删除的key在同一个页面(page),这样就不能把完整的一页归还给操作系统.

  • 上面的一点意味着,你应该基于你可能会用到的 最大内存 来指定redis的最大内存。如果你的程序时不时的需要10G内存,即便在大多数情况是使用5G内存,你也需要指定最大内存为10G.

  • 内存分配器是智能的,可以复用用户已经释放的内存。所以当使用的内存从5G降低到3G时,你可以重新添加更多的key,而不需要再向操作系统申请内存。分配器将复用之前已经释放的2G内存.因为这些,当redis的peak内存非常高于平时的内存使用时,碎片所占可用内存的比例就会波动很大。当前使用的内存除以实际使用的物理内存(RSS)就是fragmentation;因为RSS就是peak memory,所以当大部分key被释放的时候,此时内存的mem_used / RSS就比较高.

如果 maxmemory 没有设置,Redis就会一直向OS申请内存,直到OS的所有内存都被使用完(针对 32 位实例,Redis 就做了限制,它的最大可用内存将限制在 3 GB)。所以通常建议设置上redis的内存限制。或许你也想设置 maxmemory-policy 的值为 noeviction(在redis的某些老版本默认 并 不是这样)

设置了maxmemory后,当redis的内存达到内存限制后,再向redis发送写指令,会返回一个内存耗尽的错误。错误通常会触发一个应用程序错误,但是不会导致整台机器宕掉.

内存碎片

什么是内存碎片?

操作系统为你分配了 32 字节的连续内存空间,而你存储数据实际只需要使用 24 字节内存空间,那这多余出来的 8 字节内存空间如果后续没办法再被分配存储其他数据的话,就可以被称为内存碎片。Redis 内存碎片虽然不会影响 Redis 性能,但是会增加内存消耗。Redis 内存碎片产生比较常见的 2 个原因:

1、Redis 存储存储数据的时候向操作系统申请的内存空间可能会大于数据实际需要的存储空间。

Redis 使用 zmalloc 方法(Redis 自己实现的内存分配方法)进行内存分配的时候,除了要分配 size 大小的内存之外,还会多分配 PREFIX_SIZE 大小的内存。

void *zmalloc(size_t size) {// 分配指定大小的内存void ptr = malloc(size+PREFIX_SIZE);if (!ptr) zmalloc_oom_handler(size);#ifdef HAVE_MALLOC_SIZEupdate_zmalloc_stat_alloc(zmalloc_size(ptr));return ptr;#else((size_t)ptr) = size;update_zmalloc_stat_alloc(size+PREFIX_SIZE);return (char)ptr+PREFIX_SIZE;#endif}

另外,Redis 可以使用多种内存分配器来分配内存( libc、jemalloc、tcmalloc),默认使用 jemalloc,而 jemalloc 按照一系列固定的大小(8 字节、16 字节、32 字节…)来分配内存的。jemalloc 划分的内存单元如下图所示:

当程序申请的内存最接近某个固定值时,jemalloc 会给它分配相应大小的空间,就比如说程序需要申请 17 字节的内存,jemalloc 会直接给它分配 32 字节的内存,这样会导致有 15 字节内存的浪费。不过,jemalloc 专门针对内存碎片问题做了优化,一般不会存在过度碎片化的问题。

2、频繁修改 Redis 中的数据也会产生内存碎片。当 Redis 中的某个数据删除时,Redis 通常不会轻易释放内存给操作系统。

过期

Redis如何淘汰过期的keys

超时后只有对key执行DEL命令或者SET命令或者GETSET时才会清除。 这意味着,从概念上讲所有改变key的值的操作都会使他清除。

Redis keys过期有两种方式:被动和主动方式。当一些客户端尝试访问它时,key会被发现并主动的过期。当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除。

具体就是Redis每秒10次做的事情:

  1. 测试随机的20个keys进行相关过期检测。
  2. 删除所有已经过期的keys。
  3. 如果有多于25%的keys过期,重复步奏1

这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys。

定期删除:指Redis每隔一段时间进行定期扫描(默认10秒),扫描十次,每次扫描20个key,删除其中过期的key,若过期比例超过1/4,则判定过期key较多,再次重复此操作。惰性删除:在定期删除的情况下会存在部分过期key无法删除的情况,惰性删除在读写key时会将过期key直接删除。

事务

集群

主从复制

持久化

安全策略

场景

关键词: