- 磁盘, 寻址ms级别,带宽:G/M
- 内存,寻址ns级别, 是磁盘的十万倍,带宽(双通道DDR400的带宽为6.4GBps)
硬盘的原理: https://www.bilibili.com/video/BV11a4y1x7PC?from=search&seid=15696094628094262938
磁盘的扇区大小通常为512byte,但是操作系统读取磁盘时,无论读取多少磁盘数据,都读取4k数据。
数据存储发展进程
-
早期数据存储到简单的文本文件中,使用grep等命令查找,由于没有固定的格式,所以查找困难
-
发展为数据库,数据库将数据拆分为多个
data page
每个大小为4k,存放到磁盘中,正好为一次i/o。- 关系型数据库在建表时,必须定义好schema,即必须定义好表字段以及字段类型,因为类型确认,所以数据宽度以及行的宽度也确认。新增字段采用行级存储,主动开拓一行数据的宽度,修改数据时,只需将对应占位的数据进行替换即可
- 建立索引,用于索引data page,记录某一字段所对应的data page,索引也是data page
- 建立B+T,保存在内存中,经过B+T查询最终确认叶子结点,从而确认索引的data page,根据索引查出数据记录
当数据库表中的数据越来越多时, 增删改要进行树维护,所以会变慢。当查询请求不多时,其查询速度仍然很快。但是当大量查询请求进来时,磁盘的带宽将会严重影响数据库的查询速度。
-
由于磁盘的寻址时间,以及带宽对关系型数据库的影响,部分公司出现了极端化的情况:使用内存作为存储关系型数据库的介质:比如SAP公司的HANA内存级数据库,但是其造价昂贵
数据存储在内存和磁盘的存储体积不同,2T的数据放入内存,可能也就占用1T多的空间
-
磁盘造价低廉但是性能较差,内存虽然性能高但是其造假昂贵,所以出现了折中方案: 使用磁盘关系型数据库+内存缓存,比如memcached+oracle,redis + mysql
Redis是什么
Remote Dictionary Server,翻译为远程字典服务。Redis 是一个完全开源免费的基于key-value的NoSQL存储系统。他是一个使用ANSI C语言编写、遵守BSD协议,支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets) 五大类型。
Redis作者是 Salvatore Sanfilippo,来自意大利的西西里岛。👇
![](https://img-blog.csdnimg.cn/img_convert/82094c02ab38d2e9a5177fb76623ef01.png)
2008年他在开发一个LLOOGG的网站时,需要一个高性能的队列功能,最开始使用MySQL来实现,无奈性能总是提不上去,所以决定自己实现一个专属于LLOOGG的数据库,他就是Redis的前身。后台 Sanfilippoj将 Redis1.0放到Github上大受欢迎。
BSD协议
Redis基于BSD开源协议,BSD开源协议是一个给于使用者很大自由的协议。可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。当你发布使用了BSD协议的代码,或者以BSD协议代码为基础做二次开发自己的产品时,需要满足三个条件:
- 如果再发布的产品中包含源代码,则在源代码中必须带有原来代码中的BSD协议。
- 如果再发布的只是二进制类库/软件,则需要在类库/软件的文档和版权声明中包含原来代码中的BSD协议。
- 不可以用开源代码的作者/机构名字和原来产品的名字做市场推广。
BSD代码鼓励代码共享,但需要尊重代码作者的著作权。BSD由于允许使用者修改和重新发布代码,也允许使用或在BSD代码上开发商业软件发布和销售,因此是对商业集成很友好的协议。
很多的公司企业在选用开源产品的时候都首选BSD协议,因为可以完全控制这些第三方的代码,在必要的时候可以修改或者 二次开发。
Redis vs Memcache
世界上的数据只有三种表现形式:
k=v
k=[v1, v2,...]
k={x=y, a=[1, 2], b={a,1}}
所以任何数据都可以以json的形式发送或者记录。Memchache是一个没有数据类型的缓存系统。它想要表示数据,通常需要客户端自己将数据转换为json。当我需要修改json的某个字段时,需要获取整个字段值,修改完毕后生成新的json,再发送给memcache服务端:
![](https://img-blog.csdnimg.cn/img_convert/7a078b21b3eff032df3a29d1f4eb4bc9.png)
而拥有数据类型的redis可以通过数据类型提供的方法,直接修改某部分信息(向list k1头部插入数据):
![](https://img-blog.csdnimg.cn/img_convert/a0ffcde1aaac07624da950e99bdf9f0d.png)
Redis的优势:计算向数据移动
- 客户端代码更简洁,不需要做数据序列化反序列化处理
- 网络传输数据较少,客户端和服务端只通讯修改的地方
安装redis
CentOS安装Redis5.0
官网下载源码包,解压,根据redeme中的提示按照步骤安装。注意,make是linux系统自带的c编译器,记得安装gcc:
yum install gcc
安装步骤:
# 编译,make命令会去当前路径寻找Makefile文件,执行编译过程
make
make install
# 指定位置安装
make install PREFIX=/opt/redis/
CentOS7安装Redis6.0
# 安装依赖
yum -y install gcc centos-release-scl cmake wget tcl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
# 解决gcc版本低的问题-临时
scl enable devtoolset-9 bash
# 解决gcc版本低的问题-永久
echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
# 编译
make
# 安装, 默认安装到/usr/local/bin 目录下
make install
# 指定位置安装
make install PREFIX=/root/redis6
./utils/install_server.sh安装redis服务
在源码包下,有一个快速安装redis服务的脚本:./utils/install_server.sh
,执行此脚本,会提示输入相应的参数去安装redis服务,或者指定相应的参数去直接安装redis服务:
sudo REDIS_PORT=6379 \
REDIS_CONFIG_FILE=/etc/redis/6379.conf \
REDIS_LOG_FILE=/var/log/redis_6379.log \
REDIS_DATA_DIR=/var/lib/redis/6379 \
REDIS_EXECUTABLE=`command -v redis-server` ./utils/install_server.sh
在redis6版本中,可能会出现如下错误:
This systems seems to use systemd.
Please take a look at the provided example service unit files in this directory, and adapt and install them. Sorry!
解决方案:修改脚本vi ./install_server.sh
,将下面代码注释代码
#bail if this system is managed by systemd
#_pid_1_exe="$(readlink -f /proc/1/exe)"
#if [ "${_pid_1_exe##*/}" = systemd ]
#then
# echo "This systems seems to use systemd."
# echo "Please take a look at the provided example service unit files in this directory, and adapt and install them. Sorry!"
# exit 1
#fi
连接redis
redis-cli 是一个redis的命令行客户端工具,默认集成在redis中,可以使用redis-cli --help
查看使用帮助,默认链接本地的6379的redis-server服务。
- 使用交互方式连接redis:
redis-cli -h {host} -p {port}
- 使用命令行方式连接redis并执行命令:
redis-cli -h 127.0.0.1 -p 6379 get hello
可以使用redis-cli --help
来查看帮助。
应用场景
string 数值
- redis是并发安全的,并且速度极快,可以使用
incr
,decr
等命令用于做秒杀、抢购、点赞等 - 规避并发下,对数据库的事务操作,完全由redis内存操作代替(计算向数据移动)
string 位图
- 数据类型string 位图,使用365位存储365天内的登录状态,然后使用bitcount统计用户在某一随机时间段内的登录次数,使用bitlen统计总共登录的次数(运算速度快、节省空间)
- 数据类型string 位图,统计某一天的热活量,以天为单位,将用户映射到bitmap中,使用bitlen统计当天的热活。 如果统计1月1日、1月2日都登陆的用户,就可以使用逻辑与
bitop and rehuo k0101 k0102
所得的keyrehuo
就是1月1日与1月2日都登陆的用户id
help 命令
在redis客户端中,使用help命令,可以查看redis的各种命令帮助:
127.0.0.1:6379> help
redis-cli 6.0.10
To get help about Redis commands type:
# 列出组下的命令
"help @<group>" to get a list of commands in <group>
# 查看某个命令的帮助
"help <command>" for help on <command>
# 使用tab智能补全
"help <tab>" to get a list of possible help topics
# 使用quit退出redis客户端
"quit" to exit
To set redis-cli preferences:
":set hints" enable online hints
":set nohints" disable online hints
Set your preferences in ~/.redisclirc
目前在redis6中的group包含:
- generic 全局通用命令
- string 数据类型string
- list 数据类型list
- set 数据类型set
- sorted_set 数据类型sorted_set
- hash 数据类型hash
- pubsub
- transactions
- connection
- server
- scripting
- hyperloglog
- cluster
- geo
- stream
每个命令详情中,包含这个命令的相关信息, 命令形式、命令解释、起始版本、命令所属组, 下面这个命令属于generic组,自1.0.0版本就出现了:
KEYS pattern
summary: Find all keys matching the given pattern
since: 1.0.0
group: generic
数据类型
数据类型
127.0.0.1:6379> set key1 "Hello World"
OK
127.0.0.1:6379> set key2 100
OK
使用type查看数据类型:
127.0.0.1:6379> type key1
string
127.0.0.1:6379> type key2
string
数据类型与使用的命令的组对应。这里set 和 get 命令所属的组为 string。
内部编码
type命令返回对应value的数据结构类型,但是实际上每个数据结构在redis内部都有自己的多种编码实现,Redis会在适合的场景选择合适的内部编码,比如要进行数据计算,string的内部编码就是int:
![image-20210221153606191](https://img-blog.csdnimg.cn/img_convert/1936c1f5d703c43e1705b4e570335451.png)
可以使用 object encoding命令查看内部编码:
127.0.0.1:6379> object encoding key1
"embstr"
127.0.0.1:6379> object encoding key2
"int"
内部编码优势
- 可以自由改进内部编码,从而不影响外部数据结构以及命令
- 多种内部编码可以在不同的场景下发挥各自的优势
string
help @string
字符串类型,其他数据结构都是基于字符串类型的基础上构建的,字符串类型可以是字符串、数字、二进制,其值最大不可以超过512M。
内部编码
- int:8字节长整型
- embstr:小于等于39字节的字符串
- raw:大于39字节的字符串
127.0.0.1:6379> set key1 100
OK
127.0.0.1:6379> set key2 hello
OK
127.0.0.1:6379> set key3 a12345678901234567890123456789012345678901234567890
127.0.0.1:6379> object encoding key1
"int"
127.0.0.1:6379> object encoding key2
"embstr"
127.0.0.1:6379> object encoding key3
"raw"
二进制安全
在redis中,所有数据都是二进制安全的。redis只会保存原始的二进制流数据,不会对他们做处理。即使value的内部编码不同,也不会影响数据数据在redis内部的存储方式,所谓的内部编码只不过在对数据处理时,将其转换为其他编码以方便处理。
所以,redis存储的数据的格式、文本编码等信息,需要客户端自己在存入和读取时保持一致。
127.0.0.1:6379> strlen key1 // int 100
(integer) 3
127.0.0.1:6379> append key1 abc
(integer) 6
127.0.0.1:6379> get key1
"100abc"
127.0.0.1:6379> object encoding key1
"raw"
常用命令
常用:
- SET 设置值
- GET 获取值
- MGET 获取多个值
- APPEND 追加字符串
- GETRANGE 取字符串指定的范围的值
- SETRANGE 设置字符串指定范围的值
- STRLEN 字符串长度
字符串操作
SET,GET
set key value [EX seconds|PX milliseconds|KEEPTTL] [NX|XX]
EX: expire time, 过期时间秒
PX: 过期时间,毫秒
NX: not exist, 不存在才设置,通常用于分布式锁
XX: 存在才修改,不存在返回nil
# 永不失效
set key1 value1
# 一秒钟后失效
set key1 value1 EX 1
# 如果存在不设置
127.0.0.1:6379> set key2 value1 NX
OK
127.0.0.1:6379> set key2 value1 NX
(nil)
# 如果存在才设置(修改)
127.0.0.1:6379> set key3 value3 XX
(nil)
SETNX
与SET的 NX参数功能一致,如果存在则不设置:
127.0.0.1:6379> setnx k1 v1
(integer) 1
127.0.0.1:6379> setnx k1 v2
(integer) 0
SETEX
设置一个key的value,并且设置指定的有效期(单位秒):
# 设置k1=v1 有效期10s
setex k1 10 v1
SETRANGE GETRANGE
127.0.0.1:6379> set key1 "hello world"
OK
127.0.0.1:6379> get key1
"hello world"
# 正向索引
127.0.0.1:6379> GETRANGE key1 0 4
"hello"
# 反向索引,-7代表倒数第七个字符
127.0.0.1:6379> GETRANGE key1 0 -7
"hello"
127.0.0.1:6379> SETRANGE key1 6 zhangsan
(integer) 14
127.0.0.1:6379> get key1
"hello zhangsan"
MSET,MGET,MSETNX
m代表more, 一次设置多个值,一次获取多个值,原子操作
127.0.0.1:6379> mset a 1 b 2 c 3
OK
127.0.0.1:6379> mget a b c
1) "1"
2) "2"
3) "3"
msetnx,m代表more,一次设置多个值,如果存在则不设置,该操作为原子操作,如果有任意一个值没有设置成功,所有都不成功:
127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> msetnx a 1 b 2 c 3
(integer) 0
127.0.0.1:6379> mget a b c
1) "1"
2) (nil)
3) (nil)
GETSET
注意,此命令不能保证原子性,因为是先读后写。此方法仅仅是为了减少一次IO操作(与先GET 后 SET 比较):
127.0.0.1:6379> getset key1 abc
"100abc"
127.0.0.1:6379> get key1
"abc"
STRLEN
获取字符串长度,实际上是流数据的字节数
127.0.0.1:6379> set a 张三
OK
127.0.0.1:6379> strlen a
(integer) 6
127.0.0.1:6379> set b 123
OK
127.0.0.1:6379> strlen b
(integer) 3
APPEND
追加字符串
127.0.0.1:6379> set a hello
OK
127.0.0.1:6379> append a " world"
(integer) 11
127.0.0.1:6379> get a
"hello world"
数值操作
INCR | INCRBY | DECR | DECRBY | INCRBYFLOAT |
---|---|---|---|---|
递增1 | 递增指定整数 | 递减1 | 递减指定整数 | 递增指定的浮点值 |
127.0.0.1:6379> set a 100
OK
127.0.0.1:6379> incr a
(integer) 101
127.0.0.1:6379> decr a
(integer) 100
127.0.0.1:6379> incrby a 10
(integer) 110
127.0.0.1:6379> decrby b 10
(integer) 113
127.0.0.1:6379> decrby a 10
(integer) 100
127.0.0.1:6379> incrbyfloat a 0.5
"100.5"
127.0.0.1:6379> incrbyfloat a -0.5
"100"
127.0.0.1:6379> incrby a -10
(integer) 90
127.0.0.1:6379> decrby a -10
(integer) 100
位操作
SETBIT | GETBIT | BITCOUNT | BITPOS | BITOP |
---|---|---|---|---|
设置位 | 获取位 | 计算位数量 | 寻找位的offset | 位操作 |
127.0.0.1:6379> setbit bitkey 7 1 --> 设置位图第7位的值为1 0000 0001
(integer) 0
127.0.0.1:6379> getbit bitkey 7 --> 获取位图第7位的值
(integer) 1
127.0.0.1:6379> getbit bitkey 6
(integer) 0
--------> 因为位图最高位为第7位,八位一个字节,所以只占用一个字节
127.0.0.1:6379> strlen bitkey
(integer) 1
--------> 第8位超过了一个字节,所以使用两个字节存储
127.0.0.1:6379> setbit bitkey 8 1
(integer) 0
127.0.0.1:6379> strlen bitkey
(integer) 2
127.0.0.1:6379> bitcount bitkey 0 15 --> 计算第0位到第15位中,所有1的数量
(integer) 2
127.0.0.1:6379> BITPOS bitkey 0 0 15 --> 在第0位到第15位,寻找0位第一次出现的offset
(integer) 0
127.0.0.1:6379> BITPOS bitkey 1 0 15 --> 在第0位到第15位,寻找1位第一次出现的offset
(integer) 7
---------> 位操作
127.0.0.1:6379> setbit a 0 1 --> 10000 0000
(integer) 0
127.0.0.1:6379> setbit a 1 1 --> 11000 0000
(integer) 0
127.0.0.1:6379> setbit b 0 1 --> 10000 0000
(integer) 0
127.0.0.1:6379> setbit b 2 1 --> 10100 0000
(integer) 0
127.0.0.1:6379> bitop and andkey a b --> 按位与 1000 0000
(integer) 1
127.0.0.1:6379> bitop or orkey a b --> 按位或 1110 0000
(integer) 1
127.0.0.1:6379> bitop xor xorkey a b --> 异或 0110 0000
(integer) 1
127.0.0.1:6379> bitop not notkey a --> 非 0011 1111
(integer) 1
list
![image-20210221180850973](https://img-blog.csdnimg.cn/img_convert/d6549309fa9b56fc7847e76f4578cbfc.png)