date: 2020-10-15 10:58:00

updated: 2020-10-19 18:00:00

Redis的一些问题

Remote Dictionary Server

底层C写的

类似于 mysql,可以把最近的query和对应的结果保存下来 => hashquery 存入到缓存里,如果其他用户的query的hash值是一样的,说明查询是一样的,直接从缓存里读出来结果 => 多级缓存,目的是将最初的query进行过滤、优化,尽可能只让最后的事务操作落到DB中,缓解DB压力

1. redis出现的原因

数据文件过大,查询变慢 -> 因为要走 全量扫描

数据存储的2个位置

  • 磁盘

    影响指标

    • 带宽/吞吐:百兆/s -- G/s
    • 寻址时间:毫秒
  • 内存

    纳秒级别

mysql 查询快的原因是因为数据会分成block/datapage来保存,通过B+树建立索引,避免了全表扫描。

当mysql中的表非常大的时候,查询会很慢吗?

  • 如果是一条sql命中了索引的话,还是很快的
  • 如果是并发量很大的话,并且命中索引后指向的是独立的datapage,那么mysql会把每一个datapage拉到内存中查询,一个datapage是4k大小,如果请求量过大,磁盘带宽跟不上,会导致查询变慢

同样的数据,在内存中占的大小比在磁盘中占的大小要小很多。因为有指针,数据复用的现象。

=> 把热点数据放入到内存中,就是 redis,类似的还有 memcache。java 借助 LinkedHashMap 实现 LRU

2. redis 特征

关于单线程以及其他问题的官方回答

  • 绝大部分请求是纯粹的内存操作
  • worker 单线程串行的
    • 关于线程这个问题,影响的瓶颈在于IO通信。Redis 4.x 之后,对数据的具体计算过程等是单线程的,对数据的获取、返回客户端等是多线程的(IO Threads)
  • value 类型且每个类型有自己的本地方法。比如存入一个数组 [a,b,c],客户端要c的下标,redis有数组的index方法可以直接返回2,不需要返回整个数组,在客户端进行计算。相比之下 memcache 就是单纯的 kv 格式,都是string
  • 客户端的访问是并发的,通过epoll解决

内存操作 + 单线程避免锁、上下文竞争切换 + 非阻塞IO + value 的数据结构专门设计 => 速度快

多线程的弊端:一致性。redis的数据要多线程,一定要加锁,会退化成串行,不如单线程。

单线程的弊端:cpu利用率不高。redis单线程已足够用了,如果想继续压榨cpu,就多开实例,相当于多开redis服务器。

redis 的 worker单线程,但是IO Thread可以是多线程 !!!

客户端C1、C2并发请求,IO Thread1 可以读取C1的数据data1,IO Thread2 可以读取C2的数据data2。然后把数据交给单线程的worker来依次计算data1,data2,保证数据是串行的,不会发生错误。worker计算后的数据可以再交给对应的 IO Thread 分别返回给C1、C2。[Redis 6.x 版本的IO Threads多线程]

但是存在问题,即先计算data2后计算data1,理论上来说C1、C2请求负载均衡到不同服务器,本来就可能是无序的状态。优化的话,比如在负载均衡的时候,根据uri来分组,绑定到一个连接池里,交给一个connection里,就可以按照顺序来处理(不是很懂,但是io thread多线程是存在的)

3. 跟踪redis启动后的线程情况

yum install strace
strace -ff -O ./strace_out/out ./redis-server
在启动redis-server的同时,将对系统的调用情况输出到 ./strace_out/ 目录下的,文件名前缀 out

4. redis 持久化

memcache 不支持持久化

指令 BGSAVE 会在后台执行将数据持久化到磁盘,底层调用的是 copyOnWrite 方法,内核中clone指令(类似于fork指令)

copyOnWrite--当前线程A读取数据提供服务,启动线程B来持久化数据,指向的物理空间数据地址都是同一个。如果此时数据发生变化,A指向新的数据地址,B不变。

cow 具体原理: 在内存里已经有一部分数据了,此时执行持久化指令,不可能复制一份数据,可能存不下,存下也是浪费。所以新起一个线程B,不耽误正在提供服务的线程A,线程指B向的数据的物理地址和线程A是一样的,如果同一个key在持久化的时候被修改了,那么在内存中重新开辟一个地址,线程A指向新的物理地址。

5. CPU中断与内核

内核常驻在内存中。

CPU 里有晶振时间中断,每秒震荡很多次,将一秒分成了很多的时间片,每一个设备占用时间片的时候会拿到中断号(类似于时间片号),内核里有一个回调函数,通过中断号可以获取到数据在内存中的地址,这样CPU就可以在对应的时间片内去获取数据处理数据,从而达到切换程序的效果。

内核的指令 clone、select、epoll、read、write、sendfile

6. BIO -> NIO -> epoll(多路复用)

  • BIO: 客户端请求数据,发送一个key,如果这时候数据还没到,没有value,会一直等着,导致用户进程的阻塞。即一个连接一个线程
  • NIO: 客户端每次请求的key都扔到一个线程里,哪个线程能获取到value了,就把数据返回。即一个连接N个线程
    • 缺点: 如果请求量特别多,会导致线程过多(几十万的请求量就相当于几十万的线程),不断循环线程的时间复杂度是 O(N),影响效率。所以通过内核指令 select,在内核里维护一个fds(fileDescription)的数组,数组里是需要监听的socket对象。当其中一个数据到了,就会启动CPU中断,select() 返回,启动一个线程,让socket接收数据。缺点就是需要多次遍历数组,不知道是哪一个socket拿到了数据,另外还得从等待队列中移除被唤醒的进程。
  • epoll(多路复用): 通过内核的epoll指令,redis可以知道哪一个客户端(socket)的数据已经准备好了,然后串行获取数据,计算结果返回给客户端
    • 调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
    • 调用epoll_ctl向epoll对象中添加这100万个连接的socket
    • 调用epoll_wait收集发生的事件的连接(添加到一个双向链表里)

7. 零拷贝

kafka、nginx 零拷贝技术大同小异

客户端请求数据 socket -> 调用内核 sendfile 指令 -> 获取文件数据(文件描述符fd)并缓存到内核中 -> 直接返回网卡到客户端

8. expire 过期删除

redis 维护了一个 expire dict字典(因为不是所有的key都会过期,设置为过期的单独保存到字典中,可以更节省内存)来保存会过期的key,用到数据的时候会先检查key是否过期,过期则删除,然后返回错误。这是惰性删除,如果数据不被查找可能不会删除,造成内存浪费,所以同时有一个定时执行的函数,servercron,会在一定时间内,随机选出来字典里的数据,如果过期删除,否则再随机选择,直到规定的时间结束。这个时间有长有短,即servercron的运行时间有长有短。一般执行短的删除,每隔一段时间执行一次长的删除。

9. value 的五个类型

typedef struct redisObject {
// 类型 一共5个类型
unsigned type:4; // 编码 字符串--raw 数值--int ...
unsigned encoding:4; // 对象最后一次被访问的时间
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ // 引用计数
int refcount; // 指向实际值的指针
void *ptr; } robj;
  1. string

    • 数值可以按string保存,对于已经进行过计算的数值,在encoding里会把这个value设置为int,这样下次再计算的时候直接计算,而不需要判断是否能转换成数值。第一次进行计算的时候是需要进行判断的。

      • 比如对于秒杀场景,初始化值10是string,第一次-1会判断10能不能转换为数字,如果可以-1,并且把encoding设置为intencoding=raw 表示字符串),下一次-1的时候直接-1,省略了判断的过程,提高效率
    • bitmap 位图 8个比特位,首位默认是0,遵循ascii码。但是如果 offset 超过7,那么redis会自动拓展这个位图到设置的那个长度
      setbit k1 1 1 # 偏移量1的位置设置为1
      get k1 # 返回 "@" 01000000 二进制64,对应的是 @
      setbit k1 7 1 # 偏移量7的位置设置为1
      get k1 # 返回 "A" 01000001 二进制65,对应的是 A
      • bitcount 可以统计出value里有多少个7,换算到字节的话,长度/8,可以减少存储空间;
      • bitop 可以进行二进制的与或非操作
      • 适用场景:
        1. 统计用户在一段时间内登陆次数,比如一年365,length就是365,登陆就置为1
        2. 活跃用户的统计
        3. 权限控制 类似于linux的chmod 777
        4. 布隆过滤器 redis自带
  2. list
    • 按放入顺序 有序
    • value 保存是双向链表的结构,同时key保存了头元素和尾元素的指针,访问头尾都是O(1)
    • 插入、弹出有方向区别,为了模拟不同的数据结构。同向:栈;异向:队列。通过index指令还可以模拟数组。
    • 适用场景:数据共享
  3. set
    • 去重 无序,当元素变多之后元素可能顺序和之前也不一样。

      • java hashset 底层实现是hashmap,只不过value设置为null,当元素变多,会触发rehash,导致元素顺序更不一样
    • 适用场景:随机事件;差集可以做推荐好友,交集可以做推荐共同好友
  4. hash
    • 即hashmap,通过 hset key field value 来设置;通过 hgetall key 可以直接返回field和value
    • 适用场景:商品详情页,可以把商品的一整套信息放在一起
  5. zset: 排序的 set

10. 持久化

两种方式:

  1. RDB

    • 快照恢复速度快,但是可能会丢失大量数据
  2. AOF
    • 日志完整,但是恢复速度慢,同时可能存在冗余日志
    • 存在冗余日志 => 4.x 之后版本会自动开启对aof日志的重写,即对同一key的操作日志,只保留有效的最近一次的日志,aof文件超过64MB的时候会自动触发

使用方法:

  • 4.x 之前版本

    • 默认开启 rdb 关闭 aof。如果开启了 aof 那么 rdb 就不生效 => 即不推荐 aof,因为过多的io会造成redis性能下降
  • 4.x 之后版本
    • 混合使用 rdb + aof => 持久化的时候,会生成一个aof文件,这个文件包含两部分,第一部分是此刻 rdb 的数据,第二部分是此刻后追加的日志记录 => 类比于 hdfs,数据保存的形式为 fsimage + edits.log

11. 压力测试

eg: redis-benchmark -c 客户端连接数(默认50) -n 请求数(默认10万) -q(quiet 不输出多余日志) -t(设置测试的命令列表) set(只测试set一个)

影响效率的几点:

  1. 网络IO -> 物理地址ip、局域网 是否走DNS转换等
  2. 磁盘IO -> aof 文件写的频率,默认是 appendfsync=appendfsync,如果改成 appendfsync=always 即每一次操作都落入到日志文件里,会降低redis的处理效率

12. 分布式部署

分布式要解决的两个问题

  1. 单点故障 -> 主从复制 -> 为了保证其中一个挂了,另一个能顶替,那么就需要做到数据同步
  2. 单个结点压力大 -> 分片集群,即将数据分片,将不同片的数据放在不同机器上,缓解访问压力 -> 不需要做到数据同步

redis 的数据分片就是每个节点负责不同的槽位,hash(key)%65536

12.1 主从复制中数据同步的方式

分布式CAP定理 -- Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),最多只能同时三个特性中的两个,三者不可兼得

P 指的是节点之间的网络通信,一般情况来说网络故障很正常,所以只能在CA之间进行取舍

但是对于银行转账这样的涉及金融,C是一定需要的,那么就在AP之间取舍

  1. 强一致性

    • client 发送请求,主节点同步把请求发给子节点,只有两个节点都成功,才返回给client成功 => 如果子节点挂了,主节点会一直等子节点重启、执行命令、返回请求,影响效率,即强一致性破坏可用性
  2. 弱一致性(redis默认使用这种)
    • client 发送请求,主节点执行成功,返回给client,同时把请求异步发给子节点,但并不能保证子节点一定执行成功 => 可能会影响一部分数据的一致性 => 如果redis的使用场景是为了缓存热点数据的话,丢一部分数据没有问题;如果是做分布式锁的话,可能会出现问题
  3. 最终一致性
    • client 发送请求,主节点执行成功,返回给client,同时把请求写入到一个位置(类似于黑盒的一个可靠的集群),子节点会读取这个位置的记录,一条条指令执行,也许会有延迟,但是能保证数据最终的一样,哪怕子节点掉了,也可以重新读记录
    • HDFS 中两个namenode之间共享数据来保证数据同步靠的是 journalnode(这也是一个集群,N个节点) ,思想就是:一条数据在journalnode集群里保证至少 n/2 + 1 个节点记录这个成功即可,这个集群会自动在n个journalnode里找到最新版本的数据来更新数据
    • 类似思想也存在于 zookeeper、哨兵、kafka等,参考Paxos

12.2 AKF 拆分原则

分片集群,每个节点管理一部分数据 -> 存在某个节点挂了,无法访问某一部分数据的情况 -> 是否针对每一个节点做HA?需要针对业务做结合

AKF 拆分原则:通过加机器可以解决容量和可用性问题

  • X轴:水平复制,HA 高可用
  • Y轴:按照业务切分,比如分业务分库
  • Z轴:数据分区(切片)

Redis的一些问题的更多相关文章

  1. 使用redis构建可靠分布式锁

    关于分布式锁的概念,具体实现方式,直接参阅下面两个帖子,这里就不多介绍了. 分布式锁的多种实现方式 分布式锁总结 对于分布式锁的几种实现方式的优劣,这里再列举下 1. 数据库实现方式 优点:易理解 缺 ...

  2. Ignite性能测试以及对redis的对比

    测试方法 为了对Ignite做一个基本了解,做了一个性能测试,测试方法也比较简单主要是针对client模式,因为这种方法和使用redis的方式特别像.测试方法很简单主要是下面几点: 不作参数优化,默认 ...

  3. mac osx 安装redis扩展

    1 php -v查看php版本 2 brew search php|grep redis 搜索对应的redis   ps:如果没有brew 就根据http://brew.sh安装 3 brew ins ...

  4. Redis/HBase/Tair比较

    KV系统对比表 对比维度 Redis Redis Cluster Medis Hbase Tair 访问模式    支持Value大小 理论上不超过1GB(建议不超过1MB) 理论上可配置(默认配置1 ...

  5. Redis数据库

    Redis是k-v型数据库的典范,设计思想及数据结构实现都值得学习. 1.数据类型 value支持五种数据类型:1.字符串(strings)2.字符串列表(lists)3.字符串集合(sets)4.有 ...

  6. redis 学习笔记(2)

    redis-cluster 简介 redis-cluster是一个分布式.容错的redis实现,redis-cluster通过将各个单独的redis实例通过特定的协议连接到一起实现了分布式.集群化的目 ...

  7. redis 学习笔记(1)

    redis持久化 snapshot数据快照(rdb) 这是一种定时将redis内存中的数据写入磁盘文件的一种方案,这样保留这一时刻redis中的数据镜像,用于意外回滚.redis的snapshot的格 ...

  8. python+uwsgi导致redis无法长链接引起性能下降问题记录

    今天在部署python代码到预生产环境时,web站老是出现redis链接未初始化,无法连接到服务的提示,比对了一下开发环境与测试环境代码,完全一致,然后就是查看各种日志,排查了半天也没有查明是什么原因 ...

  9. nginx+iis+redis+Task.MainForm构建分布式架构 之 (redis存储分布式共享的session及共享session运作流程)

    本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,上一篇分享文章制作是在windows上使用的nginx,一般正式发布的时候是在linux来配 ...

  10. windows+nginx+iis+redis+Task.MainForm构建分布式架构 之 (nginx+iis构建服务集群)

    本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,由标题就能看出此内容不是一篇分享文章能说完的,所以我打算分几篇分享文章来讲解,一步一步实现分 ...

随机推荐

  1. 1.7Hadoop-HDFS命令

  2. 5.Topic与Partition

  3. 容器云平台No.2~kubeadm创建高可用集群v1.19.1

    通过kubernetes构建容器云平台第二篇,最近刚好官方发布了V1.19.0,本文就以最新版来介绍通过kubeadm安装高可用的kubernetes集群. 市面上安装k8s的工具很多,但是用于学习的 ...

  4. php反序列化浅谈

    0x01 serialize()和unserialize() 先介绍下几个函数 serialize()是用于将类转换为一个字符串 unserialize()用于将字符串转换回一个类 serialize ...

  5. Java基础一篇过(一)反射

    一.反射是个啥 定义 : 在运行状态中动态获取的类的信息以及动态调用对象的方法,这种功能称为java语言的反射机制. 对于任意一个类,都能够知道这个类的所有属性和方法. 对于任意一个对象,都能够调用它 ...

  6. jQurey轮播插件slides简单使用教程

    动态演示地址: http://www.zqunyan.com/zgproduction/slidesjs/index.html 简介就不多说了,网上有很多,复制粘贴没意义,会想到用这个插件就代表已经了 ...

  7. Python-找字典中公共key-zip reduce lambda

    场景实例: 西班牙足球联赛,每轮球员进球统计: 第一轮:{'1':1,'2':4,'5':2,'7':3} 第一轮:{'2':1,'5':4,'6':2,'3':3} 第一轮:{'1':1,'4':4 ...

  8. Prime Path(POJ - 3126)【BFS+筛素数】

    Prime Path(POJ - 3126) 题目链接 算法 BFS+筛素数打表 1.题目主要就是给定你两个四位数的质数a,b,让你计算从a变到b共最小需要多少步.要求每次只能变1位,并且变1位后仍然 ...

  9. linux centos7使用docker安装elasticsearch,并且用Django连接使用

    一:elasticsearch安装及配置 1:需求分析 当用户在搜索框输入关键字后,我们要为用户提供相关的搜索结果.这种需求依赖数据库的模糊查询like关键字可以实现,但是like关键字的效率极低,而 ...

  10. Windows7 组策略错误:“未能打开这台计算机上的组策略对象。您可能没有合适的权限。”

    在 Windows 7 系统下,打开组策略时,出现 组策略错误 -- "未能打开这台计算机上的组策略对象.您可能没有合适的权限.".如下图所示: 解决方案: 1.进入"计 ...