redis基础和原理

存储结构

大家一定对字典类型的数据结构非常熟悉,比如map ,通过key value的方式存储的结构。 redis的全称是remote dictionary server(远程字典服务器),它以字典结构存储数据,并允许其他应用通过TCP协议读写字典中的内容。

 

Redis的安装

redis约定次版本号(第一个小数点后的数字)为偶数版本是稳定版,如2.8、3.0,4.0 奇数版本为非稳定版,生产环

境需要使用稳定版;

安装配置:

    **需安装tcl yum install tcl 、 yum install gcc

  1. 下载redis的安装包
  2. tar -zxvf 解压
  3. cd 到解压后的目录
  4. 执行make 完成编译
  5. make test 测试编译状态
  6.  make install {PREFIX=/path}

Redis包含的可执行文件

Redis-server

Redis服务器

Redis-cli

Redis命令行客户端

Redis-benchmark

Redis性能测试工具

Redis-check-aof

Aof文件修复工具

Redis-check-dump

Rdb文件检查工具

Redis-sentinel

Sentinel服务器(2.8以后)

常用的命令是redis-server和redis-cli

1. 直接启动

redis-server ../redis.conf

服务器启动后默认使用的是6379的端口 ,通过--port可以自定义端口 ; 6379在手机键盘上MERZ对应,MERZ是一个人名

Redis-server --port 6380

以守护进程的方式启动,需要修改redis.conf配置文件中daemonize yes

2. 停止redis

redis-cli SHUTDOWN

考虑到redis有可能正在将内存的数据同步到硬盘中,强行终止redis进程可能会导致数据丢失,正确停止redis的方 式应该是向Redis发送SHUTDOW命令

当redis收到SHUTDOWN命令后,会先断开所有客户端连接,然后根据配置执行持久化,最终完成退出

数据类型:

字符串类型

字符串类型是redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据。你可以用它存储用户的

邮箱、json化的对象甚至是图片。一个字符类型键允许存储的最大容量是512M

内部数据结构

在Redis内部,String类型通过 int、SDS(simple dynamic string)作为结构存储,int用来存放整型数据,sds存放字 节/字符串和浮点型数据。在C的标准字符串结构下进行了封装,用来提升基本操作的性能,同时也充分利用已有的 C的标准库,简化实现逻辑。我们可以在redis的源码中【sds.h】中看到sds的结构:

typedef char *sds;

列表类型

列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素或者获得列表的某一个片段。

list可以从左侧或右侧进行添加 命令大家可以自行百度尝试。

内部数据结构:
redis3.2之前,List类型的value对象内部以linkedlist或者ziplist来实现, 当list的元素个数和单个元素的长度比较小的时候,Redis会采用ziplist(压缩列表)来实现来减少内存占用。否则就会采用linkedlist(双向链表)结构。 redis3.2之后,采用的一种叫quicklist的数据结构来存储list,列表的底层都由quicklist实现。

这两种存储方式都有优缺点,双向链表在链表两端进行push和pop操作,在插入节点上复杂度比较低,但是内存开 销比较大; ziplist存储在一段连续的内存上,所以存储效率很高,但是插入和删除都需要频繁申请和释放内存;

quicklist仍然是一个双向链表,只是列表的每个节点都是一个ziplist,其实就是linkedlist和ziplist的结合,quicklist 中每个节点ziplist都能够存储多个数据元素,在源码中的文件为【quicklist.c】,在源码第一行中有解释为:A doubly linked list of ziplists意思为一个由ziplist组成的双向链表;

列表类型内部使用双向链表实现,所以向列表两端添加元素的时间复杂度为O(1), 获取越接近两端的元素速度就越 快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是很快的

hash类型

内部数据结构:

map提供两种结构来存储,一种是hashtable、另一种是前面讲的ziplist,数据量小的时候用ziplist. 在redis中,哈 希表分为三层,分别是,源码在【dict.h】

集合类型 (set)

集合类型中,每个元素都是不同的,也就是不能有重复数据,同时集合类型中的数据是无序的。一个集合类型键可

以存储至多232-1个 。集合类型和列表类型的最大的区别是有序性和唯一性 集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在。由于集合类型在redis内部是使用的值

为空的散列表(hash table),所以这些操作的时间复杂度都是O(1).

数据结构 Set在的底层数据结构以intset或者hashtable来存储。当set中只包含整数型的元素时,采用intset来存储,否则,

采用hashtable存储,但是对于set来说,该hashtable的value值用于为NULL。通过key来存储元素

有序集合类型(sortted-set)

在集合类型的基础上,有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入、删除 和判断元素是否存在等集合类型支持的操作,还能获得分数最高(或最低)的前N个元素、获得指定分数范围内的元 素等与分数有关的操作。虽然集合中每个元素都是不同的,但是他们的分数却可以相同

数据结构:

zset类型的数据结构就比较复杂一点,内部是以ziplist或者skiplist+hashtable来实现,这里面最核心的一个结构就 是skiplist,也就是跳跃表

原理

1 过期时间设置

在Redis中提供了Expire命令设置一个键的过期时间,到期以后Redis会自动删除它。这个在我们实际使用过程中用 得非常多。

EXPIRE命令的使用方法为

EXPIRE key seconds

其中seconds 参数表示键的过期时间,单位为秒。

EXPIRE 返回值为1表示设置成功,0表示设置失败或者键不存在

如果向知道一个键还有多久时间被删除,可以使用TTL命令

TTL key

当键不存在时,TTL命令会返回-2

而对于没有给指定键设置过期时间的,通过TTL命令会返回-1

如果向取消键的过期时间设置(使该键恢复成为永久的),可以使用PERSIST命令,如果该命令执行成功或者成功 清除了过期时间,则返回1 。 否则返回0(键不存在或者本身就是永久的)

EXPIRE命令的seconds命令必须是整数,所以最小单位是1秒,如果向要更精确的控制键的过期时间可以使用 PEXPIRE命令,当然实际过程中用秒的单位就够了。 PEXPIRE命令的单位是毫秒。即PEXPIRE key 1000与EXPIRE key 1相等;对应的PTTL以毫秒单位获取键的剩余有效时间

还有一个针对字符串独有的过期时间设置方式

setex(String key,int seconds,String value)

过期删除的原理

Redis 中的主键失效是如何实现的,即失效的主键是如何删除的?实际上,Redis 删除失效主键的方法主要有两 种:

消极方法(passive way) 在主键被访问时如果发现它已经失效,那么就删除它

积极方法(active way) 周期性地从设置了失效时间的主键中选择一部分失效的主键删除

对于那些从未被查询的key,即便它们已经过期,被动方式也无法清除。因此Redis会周期性地随机测试一些key, 已过期的key将会被删掉。Redis每秒会进行10次操作,具体的流程:

  1.  随机测试 20 个带有timeout信息的key;
  2. 删除其中已经过期的key;
  3.  如果超过25%的key被删除,则重复执行步骤1;

这是一个简单的概率算法(trivial probabilistic algorithm),基于假设我们随机抽取的key代表了全部的key空

间。

 

Redis发布订阅

Redis提供了发布订阅功能,可以用于消息的传输,Redis提供了一组命令可以让开发者实现“发布/订阅”模式 (publish/subscribe) . 该模式同样可以实现进程间的消息传递,它的实现原理是

发布/订阅模式包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或多个频道,而发布者可以向指定的 频道发送消息,所有订阅此频道的订阅者都会收到该消息

发布者发布消息的命令是PUBLISH, 用法是 PUBLISH channel message 比如向channel.1发一条消息:hello PUBLISH channel.1 “hello”

这样就实现了消息的发送,该命令的返回值表示接收到这条消息的订阅者数量。因为在执行这条命令的时候还没有 订阅者订阅该频道,所以返回为0. 另外值得注意的是消息发送出去不会持久化,如果发送之前没有订阅者,那么后 续再有订阅者订阅该频道,之前的消息就收不到了

订阅者订阅消息的命令是

SUBSCRIBE channel [channel ...] 该命令同时可以订阅多个频道,比如订阅channel.1的频道。 SUBSCRIBE channel.1 执行SUBSCRIBE命令后客户端会进入订阅状态

 

Redis的数据持久化

Redis支持两种方式的持久化,一种是RDB方式、另一种是AOF(append-only-file)方式。前者会根据指定的规 则“定时”将内存中的数据存储在硬盘上,而后者在每次执行命令后将命令本身记录下来。两种持久化方式可以单独 使用其中一种,也可以将这两种方式结合使用,当两种持久化存储都打开时默认会优先加载AOF的数据。

RDB方式(快照方式)

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

rdb如果使用save方式会导致备份时 无法被正常使用,如果使用bgsave时需要服务器内存充足,至少是redis设置内存的二倍。

bgsave方式备份会后台自动备份,但是这个过程中如果数据量大会导致备份过程中 产生的数据变化会丢失。

AOF方式(日志记录方式)

当使用Redis存储非临时数据时,一般需要打开AOF持久化来降低进程终止导致的数据丢失。AOF可以将Redis执行 的每一条写命令追加到硬盘文件中,这一过程会降低Redis的性能,但大部分情况下这个影响是能够接受的,另外 使用较快的硬盘可以提高AOF的性能

开启AOF

默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数启用,在redis.conf 中找到 appendonly yes

开启AOF持久化后每执行一条会更改Redis中的数据的命令后,Redis就会将该命令写入硬盘中的AOF文件。AOF文 件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是apendonly.aof. 可以在redis.conf 中的属性 appendfilename appendonlyh.aof修改

AOF的重写原理

Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。

重写的流程是这样,主进程会fork一个子进程出来进行AOF重写,这个重写过程并不是基于原有的aof文件来做 的,而是有点类似于快照的方式,全量遍历内存中的数据,然后逐个序列到aof文件中。在fork子进程这个过程 中,服务端仍然可以对外提供服务,那这个时候重写的aof文件的数据和redis内存数据不一致了怎么办?不用担 心,这个过程中,主进程的数据更新操作,会缓存到aof_rewrite_buf中,也就是单独开辟一块缓存来存储重写期间 收到的命令,当子进程重写完以后再把缓存中的数据追加到新的aof文件。

当所有的数据全部追加到新的aof文件中后,把新的aof文件重命名为,此后所有的操作都会被写入新的aof文件。

如果在rewrite过程中出现故障,不会影响原来aof文件的正常工作,只有当rewrite完成后才会切换文件。因此这个 rewrite过程是比较可靠的

Redis内存回收策略

Redis中提供了多种内存回收策略,当内存容量不足时,为了保证程序的运行,这时就不得不淘汰内存中的一些对

象,释放这些对象占用的空间,那么选择淘汰哪些对象呢? 其中,默认的策略为noeviction策略,当内存使用达到阈值的时候,所有引起申请内存的命令会报错

  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:随机移除某个key。
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

实际上Redis实现的LRU并不是可靠的LRU,也就是名义上我们使用LRU算法淘汰内存数据,但是实际上被淘汰的键 并不一定是真正的最少使用的数据,这里涉及到一个权衡的问题,如果需要在所有的数据中搜索最符合条件的数 据,那么一定会增加系统的开销,Redis是单线程的,所以耗时的操作会谨慎一些。为了在一定成本内实现相对的 LRU,早期的Redis版本是基于采样的LRU,也就是放弃了从所有数据中搜索解改为采样空间搜索最优解。Redis3.0 版本之后,Redis作者对于基于采样的LRU进行了一些优化,目的是在一定的成本内让结果更靠近真实的LRU。

Redis是单进程单线程,性能为什么这么快

Redis采用了一种非常简单的做法,单线程来处理来自所有客户端的并发请求,Redis把任务封闭在一个线程中从而

避免了线程安全问题;redis为什么是单线程?

官方的解释是,CPU并不是Redis的瓶颈所在,Redis的瓶颈主要在机器的内存和网络的带宽。那么Redis能不能处 理高并发请求呢?当然是可以的,至于怎么实现的,我们来具体了解一下。 【注意并发不等于并行,并发性I/O 流,意味着能够让一个计算单元来处理来自多个客户端的流请求。并行性,意味着服务器能够同时执行几个事情, 具有多个计算单元】

多路复用

Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞 的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提 供服务,而 I/O 多路复用就是为了解决这个问题而出现的。

了解多路复用之前,先简单了解下几种I/O模型

  1. 同步阻塞IO(Blocking IO):即传统的IO模型。
  2. 同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。
  3. IO多路复用(IO Multiplexing):即经典的Reactor设计模式,也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。
  4. 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。

同步和异步、阻塞和非阻塞,到底是什么意思,感觉原理都差不多,我来简单解释一下 同步和异步,指的是用户线程和内核的交互方式 阻塞和非阻塞,指用户线程调用内核IO操作的方式是阻塞还是非阻塞

就像在Java中使用多线程做异步处理的概念,通过多线程去执行一个流程,主线程可以不用等待。而阻塞和非阻塞 我们可以理解为假如在同步流程或者异步流程中做IO操作,如果缓冲区数据还没准备好,IO的这个过程会阻塞,这 个在之前讲TCP协议的时候有讲过.

so就先说到这里~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值