redis笔记

279 阅读15分钟

DB-engines

前戏

  • 一个文本文件data.txt如何从里面找出一个xxoo?
grep? awk? 

常识

寻址和带宽两个维度回答
硬盘:寻址(ms) 带宽(G/M)
内存:寻址(ns) 带宽(极大)
内存比硬盘寻址快10W倍。
数据量变大,寻址和带宽都受限 IO瓶颈
============================================================
IO buffer
磁盘 有磁道和扇区(512b)  (索引)成本太高
操作系统 无论读多少最小4K (4K对齐,4K是一个容器) 

mysql

行级存储,建表必须给出schema。字节宽度,每一行的宽度定死了。没有放数据的字段有占位。

问:表很大,性能下降?这句话对不对?
答:(从带宽和寻址两个角度说)
    如果有索引,增 删 改变慢 (因为要维护索引)
    查询?:通过索引查还是很快。但是并发量大的时候,会命中很多data page 受磁盘带宽的影响速度慢!

data page (4k)

mysql 在磁盘上搞出了很多4K大小的data page(小格子)。丝毫不浪费IO
如果只建表不建索引 不会变快还是全量IO。
索引(其实也是数据)也是在data page中。
=>>数据和索引都是在磁盘中的。

b+树

树干是在内存中。找叶子。(减少IO量)

HANA

内存关系型数据库 2亿 太贵了

折中缓存方案

两个基础设施:冯诺依曼体系的硬件、以太网TCP/IP

redis是最终一致性的。
redis 有K 有V (是有类型的)
类型其实不是最重要的,redis server有取数的方法! 计算向数据移动

memcache vs redis
memcache很早之前就有了也是KV的 value没有类型的概念。 俩人最本质的区别
memcache可以存一个json 但是需要通过IO把这个json拿到客户端,在客户端解析。

json可以表示很复杂的东西。世界上有3中数据表示。

IO的发展

redis epoll 以下为IO的发展

传统的阻塞I/O(BIO)

如果数据没有被`kernel`接受到,就阻塞住了。
每次一个连接抛出一个线程。

NIO

一个线程内存的成本是1M,线程多了CPU成本

内核发展了,IO不是阻塞的了,不用每次都开启一个线程了。
用户态拿到fd后判断状态可以不处理接着处理后面的。
这就需要频繁的和内核态有IO,用户态也累。

多路复用 select引出(NIO)

多路复用就是指多个连接`文件描述符`被一个线程进行处理。
有人比喻公交车就是多路复用😊
!!!

但是大量的fd在用户态和内核态之间来回拷贝

redis epoll

epoll有三个调用
epoll create 会产生一个epoll fd 指向epoll instance
epoll会产生一个共享空间mmap
epoll ctl(add del等操作)
epoll wait

mmap 共享空间出来了。fd不用来回拷贝了

redis单线程快吗?

redis单线程单进程(CPU亲密度?)来处理用户请求,IO
单线程每个链接内是有顺序的!
规避了并发下数据库的事务操作

mysql更倾向于BIO,因为数据往磁盘存。

安装

  • 好好阅读源码包中的README.md
wget http://download.redis.io/releases/redis-5.0.5.tar.gz
tar xf redis-5.0.5.tar.gz
make 实际上是当前目录有Makefile 
会缺少gcc --> yum install -y gcc
因为刚才make失败了 清理一下信息 make distclean 然后再make

In order to install Redis binaries into /usr/local/bin just use:
make install

# 成为系统服务
cd utils
./install_server.sh

类型操作

redis是k-v的。value有5种类型
redis-cli 默认链接的是 6379端口上的实例
默认有16个库 select 8 就切到了8号库
flushall flushdb 小心用

string

字符串、数值、bitmap

help @string
============
set k1 ooxx nx(如果不存在的key 才创建) 分布式锁场景!
set k1 ooxx xx(如果存在 更新之)
setrange 这个命令和理解的可能不一样! Overwrite part of a string at key starting at the specified offset
type 查看value的类型
object encoding 
strlen 字节的个数
============
二进制安全,redis与外界交互的时候都是字节数组,双方都是规定好codec 就不会有问题
socket 都是字节流 | 多语言交互用json是一个道理
======数值操作======
incr 对数值的操作都交给redis来完成。规避并发下对事务的操作。 
抢购 秒杀 详情页 点赞 评论数 (看似不需要特别精准的)
默认只能get出来 ascii的 超过ASCII范围的 就用16进制表示 || redis-cli --raw 会编码
使用redis的时候双方要约定好编解码。因为redis里面都是字节数组
getset 会减少一次IO
============
redis原子性
批量操作 或者叫 多笔操作
msetnx (不存在才创建) 
例如:
    已经存在了 k为k1,v为v1 
    进行 msetnx k1 12 k2 20 执行会失败 因为k1已经存在了。这种叫原子,失败都失败

bitmap

# 将第索引为1的二进制位设为1 即第一个字节应该是01000000
setbit k 1 1 
bitpos: 
    setbit k 1 1 # @    01000000
    setbit k 9 1 # @@   01000000 01000000
    bitpos k 1 1 1 # 表示字节区间从1到1里 第一个二进制位为1的offset 应该返回是9
    bitcount k 0 1 # 表示统计字节区间从0到1中 二进制位是1的 个数
bitop:
    二进制位的 操作
    setbit k 1 1 # @ 01000000
    setbit k 7 1 # A 01000001
    setbit k1 1 1 # @ 01000000
    setbit k1 6 1 # B 01000010
    bitop and result k k1 # 进行and(与)操作,将结果写入result #@ 01000000 
    bitop or result k k1 # 进行或操作,结果就是 C 01000011

=============字符集===============
基础的字符集叫ASCII
其他的一般叫扩展字符集,为什么叫扩展,其他字符集不再对ASCII包含的进行编码
ASCII 一个字节 It is a 7-bit code    
=============字符集===============

bitmap 位图 场景

用户系统,统计用户一年登录天数
    如果用MySQL 每行得存 时间 和 是否登录
    如果用户有上千万 呢?
    用户为key value 为365位的一个值。 
    第几位就是第几天。一个用户一个记录
    setbit zhagnsan 1 1 # 表示张三在一年的第二天登录了
    用bitcount zhangsan start end 可以计算随机端口 效率极高 且省空间。
每个用户365个二进制位 46个字节 1000W个用户 就是460M大小 远小于mysql 且快过mysql    
Linux权限设置也是用了二进制位 rwx
JD 618做活动
当天只要登录就送礼物。假设JD 有2E用户 备多少礼物?
换言之,活跃用户统计
    key     value
    date    用户ID映射到二进制位上
    =============demo===============
    bitset 20190101 0 1 # 张三的ID映射到第0个二进制位 且在20190101 登录了
    bitset 20190101 1 1 # 李四的ID映射到第1个二进制位……………………………………………………
做逻辑或运算 bitop or start end

要么 日期为key 要么用户ID为key 一般场景就这两个 

list

提到list 想到链表 单向 双向
redis 的list类型 中 key中有两个指针 分别指向头尾 head tail
redis命令中有一些是L开头的 
    有一部分是代表list lrange
    有的代表是list的left 例如 lpush 从左边push lpop从左边弹
list可以描述一个stack 同向push,pop
list也可以藐视queue 反向push,pop 例如lpush rpop
lrange 拿list里面的元素 lrange 0 -1
lset 更新值 传入索引
list也可以描述 数组 因为可以通过索引存取 lindex
list是不去重的
lrem 
linsert 在谁(前后)插入一个值。如果有两个一样的值 描述的是第一个出现的
blpop 阻塞的 描述一个阻塞队列(单播队列,第一个阻塞的人拿到)
ltrim 删除两端的

hash

键值对的。
H开头的一些命令
hset/hmset
hget/hmget
hincrbyfloat 对hash value进行计算操作

应用场景:
    商品的详情页 通过hash key拿到hash value
    微博中点赞,等 因为支持数值计算

set

去重的,不维护排序
smembers 得到所有元素 但是避免使用,因为IO
sadd 向set中添加
set 重要的是算 交集差集等
sinter sinterstore(给出一个目标key 结果会存在目标key)作者的细心之处。
sdiff 差集

srandmember key count
    count:有正数、负数、0
    正数 取出count不重复的结果集
    负数 取出可以重复的
    0 不返回结果
随机事件可以做的事情:
    微博抽奖
    公司年会抽奖:一般一个人中过奖之后 就不会在中奖了。spop(随机pop一个)

sorted set

排过序的set
例如有 香蕉、苹果、鸭梨。如果要排序 怎么排序?按什么规则排序?
按照名称?含糖量?个头大小? 排序有多个维度,但是有权重(score)。如果不给出分值 计算机不知道怎么办了

如果分值都为1,就是按照名称 字典序来排的。

redis中 对其操作的命令是 z开头的
ZADD 需要写出元素和分值  物理内存是按照分值是 左小右大的排序
zscore 取出分值
zrank 取出排名

sorted set也支持数值计算
zincrby 增加分值

场景:
    网易云音乐 歌曲排名
    新歌默认热度是0
    播放过 score增加
    通过按分值反向取出 进行显示。就是热歌榜

并集交集:
如果两个集合中都有苹果 但是分值不一样会怎么处理?

如何实现快速排序的

  • skip list跳跃表,有层次。每个元素插入的时候随机造层

进阶使用

管道pipe

每次发送消息等待响应。多条消息如果能一起发送的话呢?
计算机的buffer机制
nc localhost 6379 和redis建立一个socket连接。
echo -e "ping\nset k 99\nincr k\nget k" | nc localhost 6379
  • 管道就是让通讯更加高效

redis冷启动(冷加载)

redis启动时批量加载数据

发布订阅sub pub

回顾list。可以实现阻塞队列
如果在看直播的时候,进去就能看见画面,刷礼物等。
如何实现实时聊天通信?
publish ooxx hello
subscribe ooxx 接收方只能接到后面的信息。不像kafka的from beginning
如果使用QQ的时候,其实网上划的时候可以看到历史的聊天。如何做?
回想sorted_set,把消息的日期作为score。可以当做一个窗口 ZREMRANGEBYRANK删除过期数据
    实时消息:走发布订阅,同时进入一个zset(sorted_set)。
    三天内的数据:从上面弄得zset中获取。
    全量的数据:mysql持久化
看下面的架构图。

redis事务

redis数据快是选他的原因。事务追求速度,不支持回滚!
multi开启事务
exec执行事务
每个client从事务开启的一系列操作都会放在一个队列里等着。谁先exec就执行谁的命令
watch 事务开启前监控某个key,如果key变了 事务就不执行。

缓存穿透

什么是缓存穿透?
数据库里没有的东西,redis里肯定也没有。
如果不做任何措施,用户直接搜索就会穿透redis直接去和数据库打交道。
把数据库有的东西在redis中记录一下。对无效的请求进行过滤。
数据库中的数据肯定很多。如何把海量的数据记录下来呢?bitmap!二进制位来标记

布隆过滤器

github.com/RedisBloom/…就是解决上述问题的。

redis-server --loadmodule 绝对路径

BF.ADD 你有啥就添加啥
BF.EXSIT 查看是否存在标记
请求的数据可能会被误标记,小概率穿透
1.客户端如果穿透过去了,数据库返回空。可以在redis中手动添加一个key value为null。以后再查他就直接返回空了。
2.数据库增加了数据,需要维护布隆过滤器

redis作为缓存和数据库的区别

redis作为缓存

  • 缓存数据其实不重要,缓存不是全量
  • 缓存应该随着数据变化。
  • 缓存是热数据
redis里的数据如何随着数据变化。内存是有限的。
随着访问变化 淘汰机制

配置文件

里面注释非常详细。看注释就好了。

expire过期

set 值得时候可以指定过期的时间 秒或毫秒。
expire 命令也可给key 设置过期时间
expireat 指定过期的时刻

对设置过期时间的key 重新set 时间会失效!

如何淘汰过期的keys的

主动:
过期的数据也不会删除。等到访问到该数据时发现是过期的数据,才删除。
这样会占用内存空间
被动:
redis会轮询看有没有过期的给删除。有一定的压力。性能下降
1.redis是每10S测试随机的20个Key.
2.删除所有的过期数据
3.如果这20个数据中有25%的是过期的那么重复上述步骤

该操作保证redis性能为王!

内存回收策略

使用多大内存
内存满了怎么办?策略
LRU:次数
LFU:时间
allkeys-lru     对所有的数据都进行lru策略
volatile-lru    对设置有过期属性的数据进行lru策略

持久化

redis作为DB使用的时候要注意内存掉电易失。需要持久化(快照|日志)

RDB

redisDB。就是快照 时点性

redis做快照的时候如何保证服务还是可用的?
====================介绍一个Linux的知识====================
Linux中的管道 ll /etc | more 管道前面的输出做为管道后面的输入
使用管道时 管道前后都是新的子进程
注意下面:
    num=0 #在当前进程中创建了一个变量为num
    ((num++)) # 会对这个num ++
    echo $num # 1
    ((num++))| echo hello # num是多少?  子进程里面有num吗?
    echo ? #打印当前进程的ID号
    echo ? | more # 此时打印和上面一样吗? ?优先于管道
引出父子进程的概念:
    常规思想:父进程数据子进程看不到
    通过export导出 子进程是可以访问到的。
子进程改了export中的变量 父进程不会受影响
创建子进程的速度? fork()
进程中 使用内存是程序中有虚拟内存到真实内存的映射。
fork就是玩指针 copy on write(写时复制)
fork是系统调用 保证 快速  
cow是机制,保证父子进程数据隔离
redis RDB就是用了这种机制。 在某个时刻创建一个子进程出来,子进程负责持久化到磁盘。
redis如何触发RDB
有两种:
    save:手动触发 执行一个同步保存操作。
    bgsave:fork出子进程来进行持久化。不影主进程响服务。
弊端:
数据不全,只有时点的数据。
优点:
相当于java中的对象序列化。恢复的时候速度快

AOF

redis的写操作记录记录到文件中(类比hdfs editslog)
日志会无限变大

重写机制:
    4.0以前:删除和压缩冗余的指令日志
    4.0以后:将老的数据RDB到AOF文件中,并且以增量Append到AOF中
如果开启了AOF,RDB和AOF都进行记录。但只会用AOF做恢复(因为AOF记录全)。
redis4.0之后,AOF中包含RDB 然后增加增量的AOF。!
=================补习计算机常识=================
java写文件到磁盘进行flush操作。这个flush是代表啥呢?
所有程序IO操作都是需要调用内核的。
比如写操作,内核会产生一个文件描述符,并且给了一个buffer。
flush是刷这个buffer

集群

如果是redis用来做缓存,那么用rdb就够用了。

单机、单实例、单进程的问题有哪些?
1、单点故障(这点能很容易想到)
2、容量有限(这点得说出来)
3、压力(计算压力,连接数压力)
那么如何解决单点问题?
AKF:
X轴:做镜像,读写可分离(redis/mysql)
Y轴:按业务划分(例如mysql分库)
Z轴:优先级划分集群。

主从复制

主又成为了单点问题。
一般都是对主再做高可用。
那么出了故障,主需要自动切换到备机。需要有程序来监控主是不是挂了。
但是监控程序不能有单点问题。
比如说有三个监控程序。必须有过半的人说有问题才行。
为什么要过半????脑裂???分区容忍性???
网络分区,外界会得到数据不一致。
过半不会给出模棱两可的答案
一般集群使用奇数台。为什么要用奇数台?
跟除以二没关系
三台的话允许一台出现故障
四台的话也是允许一台出现故障,三台和四台允许的风险是一样的
想解决一个问题,往往引来新的问题
一致性问题:
1、强一致性(同步阻塞)破坏可用性
2、通过异步方式,容忍数据丢失一些。
redis 实现做法:
异步复制,是弱一致性。为了快

操作

旧的命令:slaveof
新的用:replicaof 172.0.0.1 6379
# 这里演示6381作为6379的从。 是不能进行写相关的操作的
127.0.0.1:6381> FLUSHALL
(error) READONLY You can't write against a read only replica.
主知道他有哪些从

缓存相关概念

缓存击穿

缓存雪崩

缓存穿透

缓存一致性(双写)