文章目录
- 前言
- 关于分布式系统
- Redis特性
- Redis应用场景
- Redis5安装
- redis命令
- 最核心的两个命令:get和set
- keys
- exits
- del
- expire
- ttl
- redis中key的过期策略
- type
- redis数据类型的内部实现方式
- redis的单线程
前言
redis最重要的概念:在内存中存储数据
为什么要设计一个在内存中存储数据的数据库,定义的变量天然就存储在内存中,为什么不直接定义变量?
另一个重要的概念:分布式,在单机系统中,使用redis显得多此一举,只有在分布式系统中使用redis才能发挥它真正的性能
可以这样理解:redis基于网络,将内存中的变量,分享给其他进程甚至其他主机的进程使用
为什么不直接使用MySQL?
MySQL最大的问题在于慢,因为它使用硬盘进行存储。而某些应用对于速度相应的要求极高,此时只能使用redis
而redis最大的劣势在于内存有限,能存储的数据量小。将redis和MySQL结合,将20%的热点数据用redis存储,而80%的非热点数据用MySQL存储,就能使结合后的数据库的又大又快。此时redis相当于MySQL的cache,这也是冷热分离架构的关键,但是这样做的代价就是大大提升了系统的复杂程度
(redis最开始的目的是研发一个消息中间件streaming engine,即分布式系统下的生产消费模型。但随着慢慢地发展,大家发现将redis作为数据库来使用是更好的选择。即使redis也支持消息中间件的功能,但市面中存在更多专业的中间件供我们使用,因此对于redis消息队列相关的知识不作为重点学习)
以上文字中,“分布式”被频繁提及,事实也是如此:要谈Redis就离不开分布式
关于分布式系统
要谈分布式就离不开互联网产品架构的演进过程
早期的单机架构:应用服务和数据服务部署在一台主机上,而一台主机的资源有限,包括但不限于:CPU、内存、磁盘、网络…随着用户的请求增多,主机的资源不够使用时,如何处理?
两个方面:1. 开源:简单粗暴,增加更多的硬件资源 2. 节流:从软件上优化,找到性能凭借并解决,节省软件的资源消耗(难)
对于开源,虽然增加硬件资源更加容易,但是一台主机硬件资源存在上限,这个上限由主板的扩展能力决定
既然垂直扩展不行,那就水平扩展,水平扩展就是引入更多的主机,而主机一多,就需要在软件上进行相应的调整和适配
围绕着软件的调整和适配,互联网应用的架构就开始了演进,分布式系统架构就属于演进中的关键一环(不太准确的说:就是使用多台主机/节点存储数据)。至于具体的演进,这里不再赘述,有兴趣可以看我写的上一篇文章
Redis特性
根据官网给出的资料:
- 使用内存存储数据结构(In-memory data structures):Redis作为非关系型数据库,用数据结构存储数据。数据结构一般都是键值对,以string作为key,string, hash, list, set, zset…作为value。相比于关系型数据库以“表”的方式存储数据,非关系型数据库的存储方式显然更简单
- 可编程(Programmability):对于Redis,可以使用简单的命令进行交互,也可以使用脚本语言Lua执行一些带有简单逻辑的操作
- 可扩展(Extensibility):Redis提供了一组API,可以使用C,C++,Rust语言调用这些API。因此我们能自主地扩展Redis的功能(本质上是使用API编写动态链接库)
- 持久化(Persistence):Redis主要使用内存存储数据,并且以硬盘作为辅助。Redis会将内存中的数据持久化到硬盘中,若系统发生问题导致了重启,那么Redis会从硬盘读取备份数据
- 集群化(Clustering):类似于分库分表,可以部署多个Redis节点用来存储数据,每个节点存储整体数据的一部分
- 高可用(High availability):即数据的备份。Redis支持主从结构,主节点崩溃,从节点马上顶上代替主节点,这个过程用时极短,用户甚至感知不到
最重要的一个特性是:快,为什么?
- Redis存储在内存中
- Redis作为非关系型数据库,核心功能的逻辑简单,执行效率高
- 从网络上看,Redis使用IO多路复用(epoll)
- Redis为单线程模型,减少了不必要的线程竞争开销(为什么Redis中,单线程比多线程快?多线程更快的场景为:CPU密集型任务,此时多线程能充分地利用CPU多核资源。而Redis的任务只是简单的操作数据结构,不是CPU密集型任务,使用多线程反而会变慢)
Redis应用场景
- 实时数据存储(Real-time data store):将Redis作为数据库使用。对于大多数应用,它们的数据存储需要优先考虑“大”,但是对于一部分应用,它们的数据存储需要优先考虑“快”。如搜索引擎对于性能的要求非常高,需要将所有的数据存储在内存中,此时可用考虑Redis
- 缓存与会话存储(Caching & session storage):在冷热分离架构中,Reids作为MySQL的辅助,存储热点数据以提高访问热点数据的速度。此时Redis只是一个辅助,若Redis崩溃了,数据还能从MySQL中恢复。会话存储:访问web应用时,本地浏览器用cookie保存了用户的身份信息,而远端服务器用session存储用户信息,只有两者的信息匹配,才能成功验证用户信息。但在应用服务集群中,每次访问的应用服务器可能都不相同,此时将出现一台服务器保存了session,而其他服务器没有保存session。为了减少用户的验证次数,采用Redis保存用户session,服务器从Redis中更新用户session
- 消息队列(Streaming & messaging):基于Redis的消息队列可以实现一个基于网络的生产消费模型。若当前应用中已经使用了Redis,又不想引入其他的中间件时,可以考虑Redis的消息队列
Redis不能做的事:存储大规模数据
Redis5安装
安装
apt install -y redis
验证
redis-server --version
netstat -nltp | grep redis
发现redis只开放了本地环回的端口,意味着不能从外网连接redis服务,这里修改配置文件,使外网IP也能访问redis服务
cd /etc/redis
其中的redis.conf
就是redis的配置文件(通过配置文件开启/关闭/设定软件的某些功能)
编辑该文件
vim redis.conf
找到bind这句话
将127.0.0.1修改成0.0.0.0即可
再找到protected-mode,将yes修改为no
最后重启服务器使配置生效
service redis-server restart # 重启redis服务
service redis-server status # 查看redis运行状态
active(running)表示正常运行,此时修改配置文件成功
使用redis客户端连接服务器,输入以下语句即可
redis-cli
输入ping,返回PONG说明连接成功
ctrl+d退出redis客户端
redis命令
同mysql一样,redis也是客户端-服务器程序
redis的快体现在:与关系型数据库相比(如MySQL),redis快很多。但是与直接在内存中定义数据结构存储数据相比,redis就慢得多了。因为redis是客户端服务器程序,相比于直接操作内存,redis还需要先进行网络传输以获取操作命令
通过官网Redis中的搜索框,输入命令就能查看命令的相关文档
最核心的两个命令:get和set
哈希表怎么用,redis就怎么用
set:将键值对存入数据库
set key value
对于key和value,没有必要加引号
get:根据key取value
如果kv不存在,将返回nil
keys
key固定为字符串,但value有多种类型,常见的有五种:字符串、哈希表、列表、集合、有序集合,操作不用的数据结构,就有不同的命令。全局命令则能操作任意数据结构,keys就是一个全局命令
keys:通过一些特殊符号(通配符)描述key,匹配描述的key将被返回
keys pattern
pattern用来描述key需要符合的条件
- ?匹配一个字符
- *匹配一个/多个字符
- [aef]只能匹配a或者e或者f(给出固定的选项)
- [^e]除了e都能匹配(排除e)
- [a-e]a到e之间的字符都可选,包含a和e
keys的时间复杂度为
O
(
n
)
O(n)
O(n),生产环境中进行使用keys,特别是keys *
因为redis是单线程程序,keys将导致redis被阻塞,使得MySQL的负载升高,可能导致redis和MySQL都一起挂掉,甚至系统崩溃
exits
返回key存在的个数(针对n个key,只会返回0~n之间的数)
exits key [key ...]
时间复杂度
O
(
n
)
O(n)
O(n),n为输入的key的个数
redis的很多命令都支持多个key/完成多个操作,目的是为了减少网络IO次数
del
一次删除一个/多个key,返回删除key的个数
del key [key, ...]
时间复杂度
O
(
n
)
O(n)
O(n),n为输入的key的个数
若将redis作为缓存,删除个别数据对整体的业务影响不大,因为redis可以从mysql中重新读取数据。但是依然建议,对于删除操作要小心谨慎
expire
给key设置过期时间(单位:秒),存活时间超过这个值就删除key(应用场景如验证码,基于redis实现分布式锁)
expire key seconds
pexpire key 毫秒
时间复杂度
O
(
1
)
O(1)
O(1)
返回值为1表示成功,0表示失败
注意:对key设置expire之前,key必须存在,否则将设置失败
ttl
time to live,查看当前key的过期时间,单位:秒
pttl,单位:毫秒
ttl key
时间复杂度
O
(
1
)
O(1)
O(1)
返回值:剩余过期时间,-1表示没有关联过期时间(无穷大),-2表示key不存在
redis中key的过期策略
两个策略相结合:
- 定期删除:每次抽取一部分key,并保证将这些key全部遍历一遍的时间足够快(单线程)
- 惰性删除:时间到了但不删除这个key,用户下次服务这个key时触发删除同时返回nil
然而redis并没有采用定时删除的策略,可能因为引入了定时删除就需要引入多线程
(定时删除策略的实现方式有两种:1. 时间轮 2. 优先级队列)
type
返回key对应value的类型(none, string, set, zset, list, hash)
type key
redis数据类型的内部实现方式
redis会根据value的具体数值,采用不同的内部类型进行存储。如string有多种内部类型,但是这些内部类型暴露给外部的接口都是相同的
- string
- raw:redis实现的最基本字符串
- int:当value就是整数类型时,redis直接使用int进行存储。int通常也用来实现一些特定功能(如计数)
- embstr:针对短字符串进行的特殊优化
- hash
- hashtable:redis实现的最基本哈希表
- ziplist:哈希表中,元素比较少时,使用ziplist(压缩列表)节省空间
- list
- linkedlist:redis实现的最基本list
- ziplist
- 从redis3.2开始,引入quicklist代替linkedlist和ziplist,兼顾两者的优点
- set
- hashtable
- intset:集合中都是整数
- zset
- skiplist:跳表,查询效率为 O ( l o g n ) O(logn) O(logn)
- ziplist
使用
object encoding key
查看key对应value的内部类型
redis的单线程
redis的单线程并不是说redis内部真的只有一个线程,只是说redis只用一个线程处理所有的命令请求。redis中也有多线程,如网络IO时采用了多线程
为什么redis能够使用单线程?核心业务简单,不消耗cpu资源
若两个客户端“同时”发起一个命令,两个命令相同,都是对某个变量进行自增。虽然两个客户端几乎同时发起命令,但是它们的到达时间存在先后。对于单线程,先到达的先执行,显然不存在线程安全问题
为什么redis比其他数据库效率高?速度快?(和关系型数据库MySQL,Oracle对比)
- redis访问内存,其他数据库访问磁盘
- redis的核心功能比其他数据库简单。关系型数据库支持了更复杂的功能,比如约束。为了维护这些功能,每次的操作都会导致额外的开销
- redis的单线程模型,避免了不必要的线程竞争开销,由于redis的核心功能简单,不消耗cpu资源,采用单线程足以满足需求
- redis处理网络IO时,使用epoll这样的IO多路复用机制(在TCP连接中,使用一个线程管理多个socket。因为同一时刻只有少数socket是活跃的,所以可以充分利用等待socket进行IO的时间)