redis整理
redis整理
- redis是啥?
- redis是一个高性能的key-value数据库(好像很简单的样子)
- 那么问题来了,为什么性能这么高,也就是为啥快?
-
redis是以内存作为存储介质。
- 所以读写效率高。以设置和读写一个256字节的字符串来说,读取速度为11万次/s,写的速度是8.1万次/s
-
数据结构是kv
- 查找复杂度是o(1)
-
单线程
- 避免了频繁的上下文切换
-
使用多路复用I/O模型,非阻塞Io
[图片上传失败...(image-1af1b-1536028551371)]
- redis-client在操作的时候,会产生具有不同事件类型的socket。
- 在服务端,有一段I/O多路复用程序,将其置入队列之中
- 然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。
具体可以参考
- 那除了快还有啥特点,memcache也不慢,为啥要用redis?
- 支持的类型多,除了支持string,还支持list、set、zset和hash
- 支持数据的持久化,可以将内存中的数据存储到硬盘
其他的呢
- 支持主从模式,可以配置集群、数据备份。
- 支持原子、事务
- 支持publish/subscribe, 通知, key 过期等等特性。
- 应用场景呢,干啥能用到啊?
- 缓存(热数据)变动比较小的数据
- 像组织架构。查询比较多的数据,像会员详情。
- 单线程,可以避免并发
- 计数器 incr,统计访问次数。
- 全局增量ID生成,(比如插入会员的时候,在进入队列之后就要返回id,所 以不能等数据库生成id之后再返回)
- list类型可以做队列(要求高点的的还是用activeMQ或者其他的吧)
- 统计日活跃数(位操作)
- 每天新建一个byteArray,初始化都是0,member_id作为offset,利用setbit设置为1,统计的时候利用bitcount就可以统计了!
- 验证重复请求
- 将前段的requestIP,参数的hash当做key,设置时间,下次请求如果有这个key,则判断为重复请求,防止刷数据。
- 秒杀
- 把库存放到redis中,秒杀的时候先把库存-1,如果大于0,则成功,否则,失败。不能先判断大于0,然后再-1,会导致超卖。
- 最新列表
- 利用list的lpush即可。
- 排行榜
- 利用有序集合
- redis是放到内存里的,系统重启了不就丢失了吗?
这里就要说说redis的持久化了
主要有两种方式
- RDB 不定期的通过异步方式保存到磁盘上(这称为“半持久化模式”)
- AOF也可以把每一次数据变化都写入到一个append only file(aof)里面(这称为“全持久化模式”)。
具体可以参考这篇文章redis持久化
- 现在你搭建了一个redis运行起来了,我想知道redis占用了多少内存
通过redis-cli登陆redis客户端,然后输入info命令,这里我们查看memory
[图片上传失败...(image-828cb7-1536028551372)]
- used_memory 是redis分配器分配的内存总量,包括使用的虚拟内存。即used_memory=数据内存+虚拟内存
- used_memory_rss 是redis进程占据的系统内存,除了包括分配器分配的内存,还有包括**线程本身运行需要的内存,内存碎片****,不包括虚拟内存!!used_memory_rss=数据内存+线程内存+内存碎片
- mem_fragmentation_ratio 内存碎片比率 used_memory_rss/used_memory
name | 转化一下 |
---|---|
used_memory_rss | 数据内存+线程内存+内存碎片 |
used_memory | 数据内存+虚拟内存 |
- 对于jemalloc来说,比值在1.03左右比较健康。
- 所以该值越大,代表内存碎片越大! (上图数据这么大,是因为里面数据太少);
啥叫内存碎片:比如对数据修改频繁,且数据大小相差较大,导致redis释放的内存在物理内存中并没有被释放,但redis又无法合理利用,这就会导致内存碎片。
啥叫虚拟内存:就是暂时把不经常访问的数据从内存交换到磁盘中,从而腾出宝贵的 内存空间用于其他需要访问的数据。
- redis没有使用os提供的虚拟内存机制而是自己在用户态实现了自己的虚拟内存机制 理由有两个:
- os 的虚拟内存是已4k页面为最小单位进行交换的。而redis的大多数对象都远小于4k,所以一个os页面上可能有多个redis对象。
- ②redis可以将被交换到磁盘的对象进行压缩,保存到磁盘的对象可以去除指针和对象元数据信息。一般压缩后的对象会比内存中的对象小10倍
- 具体可以看这个文章,虚拟内存
- 虚拟内存使用场景呢?
- 数据库中只是包含少量的keys,而每一个key所关联的value却非常大,那么这种场景对于使用虚存就再合适不过了。
- 如果你的数据库中有大量的keys,其中每个key仅仅关联很小的value,那么这种场景就不是非常适合使用虚拟内存
- key-value变成 key-field-value,原来的key变成了field!为了能让虚存更为充分的发挥作用以帮助我们提高系统的运行效率,我们可以将带有很多较小值的Keys合并为带有少量较大值的Keys。其中最主要的方法就是将原有的Key/Value模式改为基于Hash的模式,这样可以让很多原来的Keys成为Hash中的属性。
- 参考这篇文章 虚拟内存
- redis没有使用os提供的虚拟内存机制而是自己在用户态实现了自己的虚拟内存机制 理由有两个:
- 如果<1,代表虚拟内存开启,并且占用比较大。虚拟内存的媒介是硬盘,速度很慢,所以遇到应及时处理,增加redis节点(集群后面讲)或者增大redis内存、优化应用
redis是如何存储数据的呢!
比如 set hello world (key-->hello value-->world)
-
[图片上传失败...(image-7bc572-1536028551371)]
-
key(hello)并不是已字符串储存的,而是用sds(simple dynamic string简单动态字符串)。
-
val是指针,指向value存储位置,准确的说是值的包装
-
其中reidsObject不是真正的值,而是值的包装,其中type表名value的类型,ptr指向值的存储位置。
-
这些对象默认都是jemalloc来分配内存的,已dictEntry为例,64位计算机,3个字节占用24个字节,jemalloc会给他分配32个字节。(为什么呢?)
看下图,了解一下jemalloc的内存分配就懂了!
-
-
jemalloc划分的内存单元
[图片上传失败...(image-c0bd46-1536028551371)]
-
redisObject
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
int refcount;
void *ptr;
} robj;
-
- type是类型,主要是string list等
- encoding 表示对象的内部编码,对于redis支持的每种类型,都有两种以上的内部编码,对于string,有int 、embstr、raw三种编码。redis会根据不同的场景来对对象进行不同的编码,大大提高了redis的灵活性和效率。
- int 8个字节的长整型
- embstr <=39个字节的字符串
- raw >39个字节的字符串
embstr与raw比较?
embstr使用时只分配一次内存空间(redisObject与sdc是连续的),raw分配两次(分别为redisObject和sds分配),好处:embstr使用时少分配一次,embstr删除时少释放一次,以及对象所有数据连在一起,查找方便。坏处:很明显,如果字符串的长度增加,则需要重新分配空间,因此,embstr实现方式为只读。*编码转换: 当int数据不再是整数,或大小超过了long的范围时,自动转化为raw。在对embstr进行修改时,会先转换成raw,所以修改后的数据,无论大小,都是raw
-
lru
- 记录最后一次被命令访问时间,和回收有关
-
refcount
- 记录的该对象被引用的次数,类型为整型。初始化为1,decrRefCount incrRefCount,为0的时候,回收内存。
- Redis的共享对象目前只支持整数值的字符串对象,初始化为0~9999
-
SDS
struct sdshdr {
int len;
int free;
char buf[];
};
- buf表示字节数组,用来存储字符串;len表示buf已使用的长度,free表示buf未使用的长度
- sds与c字符串比较
- 获取字符串长度 sds是o(1) c是o(n)
- 修改字符串时内存的重分配
具体细节,看这篇文章,或者看《redis设计与实现》
-然后你发现一个节点不够,怎么搭建集群呢?事务?
-
分片 是分群的基础
- 分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。
-
分群
- 几种实现方式
-
客户端分片
[图片上传失败...(image-b14344-1536028551364)]
-
基于代理的集群
- 开源方案 Twemproxy codis
-
路由查询
[图片上传失败...(image-f1bab4-1536028551364)]
-
- 几种实现方式
-
Redis-cluster
[图片上传失败...(image-18e9f4-1536028551371)]
特点:
-
无中心架构。
-
所有节点彼此互联(PING-PONG机制),6379(默认)用于服务客户端查询,16379(默认服务端口 + 10000)用于集群内部通信。
-
节点之间通过gossip协议交换状态信息
-
建议一主多从的机制,这样主机故障之后,会通过投票机制使slave到master
-
没有用到一致性hash,而是哈希槽
- 对于每个进入Redis的键值对,根据key进行散列,分配到这16384个slot中的某一个中。
- 使用的hash算法也比较简单,就是CRC16后16384取模
- 对于这种是不能批量读取数据的,因为每个key不一定在哪个节点上。两种方法:①放的时候, “a{123}”和”b{123}”是在同一个slot上 ② 读取的时候,先对key进行取模,判断在哪个slot上,还能知道每个节点分管的slot段,然后分开取,最后汇总到一起。
请求路由方式
借助客户端进行一次转发。
[图片上传失败...(image-ac9152-1536028551371)]
- codis
框架
[图片上传失败...(image-a14c33-1536028551371)]
- 引入group分组,其指定一个master和多个slave,实现高可用
- master挂掉之后,不会自动升级slave,涉及数据一致性,需手动调整codis-ha
- client可以直接访问proxy
- 采用预分片的形式。启动时创建1024个slot 1个slot只能放在一个group中,一个group可以存放1-1024个
模块简介
- Server 增加额外数据结构,支持slot有关的操作和数据迁移
- Dashboard 支持server、proxy的添加删除以及数据迁移等。
- proxy 客户端链接redis代理服务
另一个角度架构
[图片上传失败...(image-3c5e81-1536028551371)]
- 内部主要有三个模块,redis、router和model
- Router负责将前端请求发送给redis
- Model负责和ZK交互保持数据一致性。数据:group\proxy\slot的配置
- Redis 负责和redis交互
可以参考这个文章写的很不错:redis集群
还需要再研究一下~~~~
主从复制 原理
[图片上传失败...(image-f0c8d3-1536028551371)]
- 当启动一个slave进程,它会向Master发送一个SYNC command,请求同步链接
- 无论是第一次连接,还是重新连接,都会做两件事
- Master都会启动一个后台进程,将数据快照保存到数据文件中。
- 同时Master会记录所有修改数据的命令并缓存到数据文件中
- 缓存操作完成后,Master就发送数据文件给Slave
- Slave将数据文件保存到硬盘上,然后将其加载到内存中
- 接着Master就会将所有修改数据的操作,发给slave
若slave宕机
- 恢复正常后会重新连接,Master收到Slave的连接后,将其完整的数据文件发送给slave。
- 如果同时收到多个slave发来的同步请求,Master也只会在后台启动一个进程保存数据文件,然后将其发送给所有的slave
2.8开始支持增量复制(PSYNC命令)
- Master端为复制流维护一个内存缓冲区,记录最近发送的复制流命令。
- 同时 Master和Slave之间都维护一个复制偏移量,和当前Master服务器的id,这样slave重连时
- 如果Master相同,增量复制
- 否则,依然需要全量复制
来看看master的具体工作
[图片上传失败...(image-d5ee6b-1536028551371)]
两个问题
-
为什么要定期发送保活命令?
-
为什么在发送rdb文件之后,又发送变更命令?
-
因为master会不定时发送变更命令,为了防止slave无意义的等待,以此告诉slave,我还活着,不要中断连接。
-
master保存rdb文件是一个子进程进行的,所以保存期间依然可以处理客户端请求。因此为了保证数据一致性,会再次发送变更命令
分布式锁
事务
link 不支持回滚 出问题 会继续执行
一些面试题
转载来源:
作者:小绵羊你毛不多
链接:https://www.jianshu.com/p/0000e64bf5f3