Redis基础
常识:
磁盘:
1.寻址 ms
2.带宽 (单位时间传多少个字节流) GB/MB
内存:
1寻址 ns
2.带宽 速度一定比磁盘高的多,因为是直接跟cpu打交道的
磁盘在寻址上比内存慢10w倍
I/O buffer :成本问题
磁盘有
磁道和扇区:
一扇区512Byte 带来一个成本问题–》索引变大,成本变大
假如一个硬盘1T,那得有多少个512b,把这个硬盘当成容器,扇区为一个个小格子,那么要快速找到这个格子
操作系统就得给一个区间用来索引(索引就是下标,利于查找),格子一旦小,索引就会变大,那么可想而知,成本就太高了,
那么操作系统的解决方式:无论读多少数据,都是以最少4k来从磁盘拿,(用户即使只要1k,操作系统也会读4k,如果要5k,那么操作系统就会读8k,这样的好处就是大幅度减少索引)
——–
以前 ——->数据存在文件里data.txt
随着文件变大,速度变慢,
硬件决定了磁盘瓶颈,也就是I/O成为了数据读取的瓶颈
——》于是出现了数据库,数据存到数据库
那么数据库分了很多的 datapage(数据页)[每页4k,4k是为了不浪费I/O,因为上面说了,操作系统的一次I/O最少是4K,如果datapage不是4k的整数倍的话,其实就是浪费I/O],每页都会有id号
现在如果去查数据还是全量IO,因为想要的数据不知道在哪,
那么数据库就来了一个索引页的同样也是4K,索引放的就是datapage的 一条数据的id号(就是唯一键,注意唯一键不是主键,而是每一条数据唯一的一个标志信息)和对应的datapage的id号
关系型数据库建表:必须给出schema(有多少列,每一列的数据类型,其实就是该表的结构是什么样)
列的类型:对应了字节宽度(约束该列只能存有多少字节)
给完列的类型后,他的存储是占行的形式(行级存储)
就是一行只放对应的一条数据(这样的好处就是,我的修改,不需要移动位置,只需对那一行操作就行)
索引和datapage都放在磁盘,那么在数据库这个软件在内存放了B+树用于数据查找
B+树
b+树的树叶就是索引,而树干(记录偏移量)是放在内存的
那么数据库的数据变大,表很大
如果表有索引—>对于增删改操作会变慢,因为要去维护索引
对查询来说—>如果是一个或少量查询,直接命中索引的那种,依然很快
——->但是对于并发大的时候会受硬盘的带宽的影响,速度会变慢,因为每次取4k走一次I/O进内存,下一条请求又是走一次I/O进内存,这里慢不是查询慢,而是I/O的速度慢,返回结果自然慢
既然这种主要存储在磁盘的数据库 表变大,性能会变慢
那么另一个极端就是全部存到内存,(SAP HANA关系型数据库)
速度很快,但是成本太高,买不起
数据在内存和在磁盘的体积不一样
因为磁盘是没有指针概念的,不能很快的查到,所以需要索引,
但是进内存后就不需要索引了,那么自然体积会变小
2个基础设施
1.冯诺依曼体系的硬件
2以太网,tcp/ip的网络
限制了在硬件上的瓶颈
——-》引出了一种折中的方案,缓存的概念(memcached,redis)
https://db-engines.com/en/ 对市场数据引擎的评估
架构师:技术选型,产品选择
Redis
基于kv键值对内存存储的缓存数据库
支持秒级 10多万操作
redis.io 英文官方网站
redis.cn 中文官方网站
- Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
- 它支持多种类型的数据结构,如 字符串, 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。
- 支持多种数据结构说的是value
- Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
memcache也是K–V键值 内存中的数据结构存储系统
memcache的V是没有类型的:
为什么redis会取代memcache?
世界上有3中数据表示:
k=a, k=1
k=[1,2,3],k=[a,b,c]
k=[x=y], k=[{} ,{} ,{} ]
json是可以表示很复杂的数据结构的。
那么memcache只要key–value,value存json来表示各种数据结构
为什么还火了一个redis,
redis的Value有类型的意义是什么:
计算向数据移动(就是计算不需要把数据拿回给客户端,而是直接在存数据的地方进行计算)
实操
redis 的类型描述在 Key里面,value还是以字节数组存(物理层次没有类型概念,逻辑层次才有),
基础环境,设施(需要哪些东西):
centos 6.X,redis安装包
yum install wget -y
网上下载安装包
wget http://download.redis.io/releases/redis-5.0.4.tar.gz
tar xf redis…tar.gz //加不加-xf 无所谓,加v显示细节需要IO
yum install gcc -y
部署配置:
源码安装套路:
首先看readme
make distclean
make
make完之后,/root/soft/redis-5.0.4/src 里面就会有可执行程序
./redis-server 就会启动redis
如果要装成操作系统服务
1 make install PREFIX=/opt/bigdata/redis5
2 vi /etc/profile
export REDIS_HOME=/opt/bigdata/redis5
export PATH=$PATH:$REDIS_HOME/bin
. /etc/profile
echo $PATH
cd /root/soft/redis5.0.5/utils
./install_server.sh
会出现下面这句话,为该实例选一个端口号,那么对于同一台机器多个redis来说他们的区分就是靠端口号来区分的
不填的话默认端口号是6379
Please select the redis port for this instance: [6379]
选择完端口号之后,它默认会去/etc/redis/ 目录 创建 6379.conf 文件,也就是说一台机器的多个redis可以通过不同的配置文件启动
Please select the redis config file name [/etc/redis/6379.conf]
定义号配置文件路径,下面就是日志文件路径,默认在/var/log/redis_6379.log
Please select the redis log file name [/var/log/redis_6379.log]
选择一个数据目录给当前的redis ,redis是一个内存数据库,为啥需要磁盘呢?
一般用内存存储的东西,都会有持久化的一个操作,工作虽然完全内存,那么要持久化就必须要磁盘
Please select the data directory for this instance [/var/lib/redis/6379]
给出redis可执行程序的路径,由于。/etc/profile文件里配了,所以自动识别到,。/etc/profile文件里如果没有配置,这里需要手敲路径
Please select the redis executable path [/opt/bigdata/redis5/bin/redis-server]
下面回车
把配置文件拷到了init.d目录,加了开机启动
Copied /tmp/6379.conf => /etc/init.d/redis_6379
Installing service…
Successfully added to chkconfig!
Successfully added to runlevels 345!
Starting Redis server…
然后就可以
service redis_6379 start|status|stop|restart
装第二个redis实例
cd /root/soft/redis5.0.5/utils/
./install_server.sh
Please select the redis port for this instance: [6379] 6380
Please select the redis config file name [/etc/redis/6380.conf]
Please select the redis log file name [/var/log/redis_6380.log]
Please select the data directory for this instance [/var/lib/redis/6380]
Please select the redis executable path [/opt/bigdata/redis5/bin/redis-server]
ps -ef |grep redis
一个物理机中可以有多个redis实例(进程),通过port区分
可执行程序就一份在目录,但是在内存中 未来的多个实例 需要各自的配置文件,持久化目录等资源
初始化运行
service redis_6379 start
概念
redis是单进程,单线程,单实例的
那么官网解释说可以10w多的请求并发,怎么做的?
客户端的请求 一定发到 操作系统内核
而redis 与操作系统kernel之间通信是epoll形式
采用I/O多路复用
—-BIO
于是内核kernel发生了变化 socket可以是nonbloking非阻塞—-》就是fd可以nonbloking
man 2 read Q退出
只要进程才会拥有文件描述符,文件本身不具备,(就是说,一次请求连接也是一个文件,那么进程要管理这个文件就必须打开一个文件描述符–去管理请求连接的那个文件)
0标准输入,1标准输出,2错误输出 必备,对于网络IO会有255 广播
这种模型带来的问题
线程多了调度成本问题,cpu浪费
线程多,1000个线程都消耗1g内存
—-NIO
—–>带来了成本问题,就是用户端轮询fd,就会不断调用kernel拿fd出来看有没有数据
fd管理的那一个请求既然是一个文件,那就一定在磁盘,
而进程可以通过fd找到该文件,但是读不了,文件在磁盘,
只能内核来读,那就需要把fd传给内核,
内核有一个系统调用read可以通过fd找到文件并读数据
—于是内核又往前发展
增加了一个操作系统的调用select,
文件描述符是进程的东西(用户态|用户空间的东西),那么由进程把文件描述符传给kernel 的 select(nfds)
由内核去做轮询这些文件描述符
进程隔一段时间 去访问 内核select
内核会返回有数据的fds
有fds之后,然后进程调用read(fds)取出数据进行处理
—此时还是同步非阻塞的,只不过轮询给到了内核,少了一些进程调用内核的操作,
多路复用:a—-b有很多条路,但是有一条是或者有一段是可以复用的,复用的这一条路就是用户到内核读fd,怎么知道fd有没有数据的这条互不相同路交给kernel
用户态(空间),内核态(空间)
—引发了一个问题 我的很多个fd需要传给内核,那就需要拷贝
此时拷贝的是内存的fd,
(fd不是具体请求数据,而是请求连接数据放在对应文件的描述)
对于程序来说速度要快,就尽力避免IO,也就是0拷贝,怎么样才能不需要把fd拷贝给内核
并且select模型一个进程的最大能打开的文件描述符是有限制的
———-》内核又改进,又增加了一个系统调用MMAP,epoll
这里用户进程在调用epoll时 不会像调用select一样,把fd全部轮询一遍,即便是非阻塞也空轮询了,epoll会在每个fd上注册一个事件,有数据的fd就会回调,并写进mmap的链表,本身是多路复用型,一个伪的AIO
epoll对于一个进程文件描述符打开的限制大大的增加了
共享空间—-内核腾出一部分空间,对这部分空间做映射,也就是说用户那边也有一个这么大小的空间,用户空间地址为7映射到内核空间就是地址为8,
做这么一层映射,这样进程只需告诉内核地址空间fd的位置,内核就能找到
MMAP是用户态和内核态共享的一个空间,用户的连接fd用红黑树结构存储,操作系统内核操作链表结构存储有数据的fd,
—–》此时的文件描述符fd不需要拷贝,最终形态的多路复用NIO
还是同步非阻塞的
进程从链表里拿数据处理还是一条条来的
其实nginx也是这样 —–nginx会根据cpu个数来定义worker进程数—->每个worker进程用的都是epoll()形式来管理连接—》同步非阻塞,多路复用。
是模拟的AIO,伪的,异步的地方在于内核对fd的判断方式,对于用户来说,还是同步非阻塞,
对于epoll模型,同样是IO的多路复用,但是只是对fd轮询做了修改,让活跃的fd回调,并入mmap的链表,进程拿到fd,还是要通过read(fd)来读取fd对应文件的数据,内核并不会先读好,而是进程主动调用IO 的read去读
进程主动去掉read,和内核已经读取完回调进程,直接copy,是区分NIO与AIO的一个关键点
共享空间的红黑树有内核维护
同步的意思是需要进程调用 wait()阻塞了,有活跃才会返回,主动去共享空间链表拿fd,在去调用内核read方法拿到数据
异步的意思,就是不需要等待,有内核完成所有操作(包括读取fd对应文件的数据操作)给进程一个通知就行,很显然epoll不是异步的,epoll只是对fd的精准定位和快速返回,
对于进程IO来说,还需调用read获取数据
其实无论是 select epoll 都是需要客户端轮询去访问内核
select 是进程拿所有的fd 一次性压到内核,让内核返回有数据的fd
这个地方,用户是需要不断的去调用select,如果select返回了fd,就拿着fd去调用系统read(fd)获取数据
epoll 是每次来了一个fd就先注册进epollfd,让epollfd监听,此时虽然不需要每次传fd,但是需要进程轮询wait()阻塞; 访问有没有活跃的fd,有活跃的fd然后调用read(fd)获取数据
最后两种方式,客户端轮询,客户端阻塞等待结果,其实就是同步
使用
客户端连接
命令行:
redis-cli //默认连6379redis实例
exit //客户端退出
redis-cli -h //看帮助
redis-cli -p 6380 //连接指定端口的redis实例
set k380:1 hello //设置键值对
get k380:1 //获取key对应得value
//默认是0号数据库
select 8 //进入redis 8号数据库
不同数据库之间得键值对不共享,是隔离的
也就是说我在执行get k380:1 是空nil
redis-cli -p 6380 -n 8//连接指定端口redis,并进入指定库
FLUSHDB//清库 –生产不要用
help @… //按tab键,显示不同的帮助文档
set xx xxx //key没有类型区分
String
1 |
|
1 |
|
key就是相当于对象,有type,encoding,有type可以避免错误类型计算,有encoding可以快速转换类型计算,不需要看是不是能转换
value只是字节数组
redis有一个二进制安全概念:就是把数据序列化成字节流,客户端以什么编码取,就以什么编码取出来,数据对不对要看与key的编码是否对上
(简单来说redis收到的是字节流,客户端以什么编码变成字节流发过来,redis就怎么存,要取得话也得跟原来编码一样,才能取出正确得数据,因为被序列化成字节了)
key上有encoding的信息就是对客户端传过来得数据类型进行一次预判断,(但是具体什么类型,跟客户端传过来有关【重点:redis只会收到客户端发过来得数据字节流,长度也是按字节来算,具体什么编码,redis不知道】)
域判断什么类型数据,主要方便计算int,或者string,这样就少几次 取出字节数组看能不能转换这个过程,
例如:预判断为int的k,取出value直接转换成int ,不需要每次都去判断能不能转成int,然后再++等数值操作
这就是key里面要做类型预判断的好处
1 | SETBIT offset value//offset是二进制位的offset,不是字节的 |
常识:字符集 ascii
其他的都叫做扩展字符集
扩展字符集不会对ascii重编码
ascii 都是0xxx xxxx 看到0开头,直接ascii 解析
111x xxxx 这种,往后再读两个字节,因为是三字节编码
1 | BITPOS k bit start end //start和end是字节索引,不是二进制位的 |
bitmap用矩阵形式来表述一些业务逻辑
key bitmap
xx 0 0 0 1 1 1
oo 0 1 0 1 1 0
zz 1 1 1 0 0 0
list
双向链表
1 | LPUSH k v [v..] //从左边加 多个元素,每次从最左边加 |
hash
map(k-v)
1 | HSET k field value//放一个键值对进去 |
Set
去除重复,无序
1 |
|
sorted_set
有序—>这个序是排序的意思
排序规则:既要给元素,又要给出score分值,先按分值排序,分值一样字典排序
有正负向索引,就可以对有序的元素进行正序,倒序
元素唯一
sort_set
有一个反向列表
和一个正向列表,
都有正反向索引
但是物理内存中是左小右大,不随命令改变
sort_Set 有元素,分值,排名
具备集合操作
排序的成本是什么
1 |
|
典型的空间换时间的一种数据结构 skip list
元素量比较大时,速度平均值(增删改查)相对是最优的
- 本文作者: 忘忧症
- 本文链接: https://NepenthesZGW.github.io/2020/09/10/redis/redis基础/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!