美文网首页
redis整理

redis整理

作者: ant_1024 | 来源:发表于2018-09-04 10:36 被阅读6次

    redis整理

    - redis是啥?

    • redis是一个高性能的key-value数据库(好像很简单的样子)
    - 那么问题来了,为什么性能这么高,也就是为啥快?
    1. redis是以内存作为存储介质。

      • 所以读写效率高。以设置和读写一个256字节的字符串来说,读取速度为11万次/s,写的速度是8.1万次/s
    2. 数据结构是kv

      • 查找复杂度是o(1)
    3. 单线程

      • 避免了频繁的上下文切换
    4. 使用多路复用I/O模型,非阻塞Io

      [图片上传失败...(image-1af1b-1536028551371)]

    • redis-client在操作的时候,会产生具有不同事件类型的socket。
    • 在服务端,有一段I/O多路复用程序,将其置入队列之中
    • 然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。
      具体可以参考
    - 那除了快还有啥特点,memcache也不慢,为啥要用redis?
    1. 支持的类型多,除了支持string,还支持list、set、zset和hash
    2. 支持数据的持久化,可以将内存中的数据存储到硬盘
    其他的呢
    1. 支持主从模式,可以配置集群、数据备份。
    2. 支持原子、事务
    3. 支持publish/subscribe, 通知, key 过期等等特性。

    - 应用场景呢,干啥能用到啊?

    1. 缓存(热数据)变动比较小的数据
      • 像组织架构。查询比较多的数据,像会员详情。
    2. 单线程,可以避免并发
      1. 计数器 incr,统计访问次数。
      2. 全局增量ID生成,(比如插入会员的时候,在进入队列之后就要返回id,所 以不能等数据库生成id之后再返回)
    3. list类型可以做队列(要求高点的的还是用activeMQ或者其他的吧)
    4. 统计日活跃数(位操作)
      • 每天新建一个byteArray,初始化都是0,member_id作为offset,利用setbit设置为1,统计的时候利用bitcount就可以统计了!
    5. 验证重复请求
      • 将前段的requestIP,参数的hash当做key,设置时间,下次请求如果有这个key,则判断为重复请求,防止刷数据。
    6. 秒杀
      • 把库存放到redis中,秒杀的时候先把库存-1,如果大于0,则成功,否则,失败。不能先判断大于0,然后再-1,会导致超卖。
    7. 最新列表
      • 利用list的lpush即可。
    8. 排行榜
      • 利用有序集合

    - redis是放到内存里的,系统重启了不就丢失了吗?

    这里就要说说redis的持久化了
    主要有两种方式

    1. RDB 不定期的通过异步方式保存到磁盘上(这称为“半持久化模式”)
    2. AOF也可以把每一次数据变化都写入到一个append only file(aof)里面(这称为“全持久化模式”)。
      具体可以参考这篇文章redis持久化

    - 现在你搭建了一个redis运行起来了,我想知道redis占用了多少内存

    通过redis-cli登陆redis客户端,然后输入info命令,这里我们查看memory

    [图片上传失败...(image-828cb7-1536028551372)]

    1. used_memory 是redis分配器分配的内存总量,包括使用的虚拟内存。即used_memory=数据内存+虚拟内存
    2. used_memory_rss 是redis进程占据的系统内存,除了包括分配器分配的内存,还有包括**线程本身运行需要的内存,内存碎片****,不包括虚拟内存!!used_memory_rss=数据内存+线程内存+内存碎片
    3. 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中的属性。
        • 参考这篇文章 虚拟内存
    • 如果<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)]

    两个问题

    1. 为什么要定期发送保活命令?

    2. 为什么在发送rdb文件之后,又发送变更命令?

    3. 因为master会不定时发送变更命令,为了防止slave无意义的等待,以此告诉slave,我还活着,不要中断连接。

    4. master保存rdb文件是一个子进程进行的,所以保存期间依然可以处理客户端请求。因此为了保证数据一致性,会再次发送变更命令

    分布式锁

    参考这篇

    事务

    link 不支持回滚 出问题 会继续执行

    一些面试题

    面试题

    转载来源:
    作者:小绵羊你毛不多
    链接:https://www.jianshu.com/p/0000e64bf5f3

    相关文章

      网友评论

          本文标题:redis整理

          本文链接:https://www.haomeiwen.com/subject/msqgwftx.html