一、认识分布式
1.认识Redis
什么是Redis,我们先看官方给的定义:
The open source,in-memory data store used by millions of developers as a database,cache,streaming engine,and message broker.
1.什么叫做in-memory data store?
在内存中存储数据,那我们之前在学习语言的时候,我们定义一个变量,那不也是在内存中存储数据嘛?是这样的,如果只是单机程序的话,直接通过变量存储数据的方式确实是比使用Redis更优的选择,我们的主角Redis主要是用在分布式系统当中,在之前的学习中,我们知道,进程与进程之间是具有隔离性的,两个不相干的进程是无法访问他们相互之间的数据的,Redis就算基于网络,可以把自己内存中的变量给别的进程,甚至别的主机的进程进行使用,即:Redis通过网络来完成两个进程之间的通信过程。
2.什么叫做database,cache?
我们对比MySQL,MySQL最大的问题就在于,访问速度比较慢,在很多互联网产品中对于性能要求是很高的。
而Redis也可以作为数据库使用,他的最大优点就算快!因为访问内存的速度是越高与访问硬盘速度的。所以在定性的角度,我们可以知道Redis要快很多,但是很难定量衡量。
但和MySQL相比,Redis最大的劣势就在于,他的存储空间是有限的!因为互联网对于性能要求高的产品笔记占少数,大部分对于性能要求还是没有那么高的。
那有没有存储空间又大,速度又快的方案呢?有的,典型的方案就是可以把Redis和MySQL结合起来使用,根据“二八原则”,一般情况下20%热点数据就能够满足80%的访问需求。所以我们可以只把热点数据存放到Redis当中,把所有的数据存放到MySQL中,将Redis当中一种“cache”来使用。但这样的话系统的复杂程度就大大提升了,而且,如果数据发生了修改,还要涉及到Redis和MySQL之间的数据同步的问题。
3.什么是streaming和message broker呢?
这个Redis的初心,最初就是用来作为一个“消息中间件”的(消息队列),作为一种分布式系统下的生产者消费者模型。但是当前很少人会直接使用Redis作为消息中间件了(因为业界有更多更专业的消息中间件使用)
2.浅谈单机架构
关于分布式系统,大家千万不要把所谓的“分布式”想的太复杂,太高大上了
所谓的单机架构,就是只有一台服务器,这个服务器负责所有的工作。我们可以看下图:假定这是一个电商网站
上图应用服务指的是我们写的服务器程序:
例如:C++的cpp-httplib ,java的Spring,还有MySQL的客户端程序
上图的数据库服务指的是:
MySQL的本体,即MySQL服务器(存储和组织数据的部分),因为MySQL是一个客户端服务器结构的程序!
Q:在单机程序中,能不能把数据库服务器也去掉,光一个应用服务器既负责业务,又负责数据存储呢?
也不是不可以,但是就是会比较麻烦
大家千万不要瞧不上这个单机架构,绝大部分的公司的产品,都是这种单机架构!因为现在的计算机硬件,发展速度非常之快,哪怕只有一台主机,这一台主机的性能也是很高的。但如果业务进一步增长,用户量和数据量都水涨船高,一台主机难以应付的时候,这时候就需要引入更多的主机,引入更多的硬件资源。
3.浅谈分布式是什么
我们知道,一台主机的硬件资源是有上限的!!!,包括不限于以下几种:1.cpu 2.内存 3.硬盘 4.网络 5….
服务器每次收到一个请求,都是需要消耗上述的一些资源的,如果同一时刻,处理的请求多了,都可能会导致服务器处理请求的时间会变长,甚至于处理出错。
如果我们真的遇到了这样的服务器不够用的场景,怎么处理呢?
- 开源:简单粗暴,添加更多的硬件资源,但一个主机上面能增加的硬件资源也是有限的,这取决于主板的扩展能力,当一台主机扩展到极限了,但是还不够,就只能引入多台主机了!不是说新的机器买来就直接可以解决问题了,也需要在软件上做出对应的调整和适配,一旦引入多台主机了,咱们的系统就可以称之为“分布式系统”。
- 节流:在软件上优化,(这就各凭本事了,需要通过性能测试,找到是哪个环节出现了瓶颈,再去对症下药)
注意:引入分布式,这是万不得已的无奈之举,系统的复杂度会大大提高,出现bug的概率也越高,加班的概率 & 丢失年终奖的概率也随之提高。
4.数据库分离和负载均衡
引入多台服务器后,我们自然可以想到将应用服务和数据库服务分离。如下图:
对于应用服务器来说:里面可能会包含很多的业务逻辑,这可能会吃比较多的CPU和内容。
对于数据库服务器,需要更大的硬盘空间,更快的数据访问速度,可以配置更大硬盘的服务器,甚至还可以上SSD硬盘。
通过调整他们的配置,从而达到更高的性价比。一般来说我们会引入更多的应用服务器节点,因为应用服务器吃CPU和内存,如果CPU或者内存吃没了,此时应用服务器就顶不住了。
什么是负载均衡:
用户的请求,先达到负载均衡器/网关服务器(这是一个单独的服务器),假设有1w个用户请求,有2个应用服务器,此时按照负载均衡的方式,就可以让每个应用服务器承担5k的访问量。这个事情就和之前讲过的“多线程”有点像。
对于负载均衡器来说,也有很多的负载均衡具体的算法。例如:如果一个用户总数重复提出同一个请求,那么负载均衡器便会解析他的ip,将他的请求分配到同一个服务器上,以提高服务器的响应速度。
5.理解负载均衡
还是上面那个1w请求的例子,虽然两个应用服务器各自承担5k的任务,但是这个负载均衡器,看起来不是承担了所有的请求嘛,这个东西能顶得住嘛???
负载均衡器,对于请求量的承担能力,要远超过应用服务器的。就好比负载均衡器是领导,分配工作即可,而应用服务器是组员,需要执行任务。执行任务的压力和耗时肯定是远高于分配人物的。
那是否会出现请求量达到连负载均衡器也扛不住了呢??也是有可能的!!!这时候可以考虑引入更多的负载均衡器(引入多个机房)
举个例子:学校是服务同学的,每个学校有他们的教务团队,负责日常作业的跟进,跟进一个同学,势必要花一定的时间,一个助教老师,就是一个应用服务器。根进一个同学,就相当于处理一个请求。当同学数量很多时,就需要引入更多的助教老师。
而引入负载均衡器,就承担了校长的角色,校长给每个助教老师分配任务,随着同学更多了,一个校长分配任务也分配不过来了,就可以划分出多个校区,每个校区都有各自的校长,每个校区里又有一批助教
但是!当人变多了,管理成本就会提高,出现问题的概率也会提高,当机器也是如此。
6.数据库读写分离
如上面讨论,增加应用服务器,确实能够处理更高的请求量,但是随之存储服务器要承担的请求量也就更多了!
咋办呢?可以开源(引入更多的机器,简单粗暴)+节流(门槛高,更复杂),且看下图:
我们知道,在实际的应用场景中,数据库的读频率是要比写频率更高的!故数据库服务器可以采用读写分离的方式,主服务器一般是一个用于数据的更新修改操作,从服务器可以有多个(一主多从)负载数据的读取操作,主服务器实时或者定期的将数据同步到从服务器上,同时从服务器通过负载均衡的方式,让应用服务器进行访问。
7.引入缓存
数据库天然有个问题,响应速度是更慢的!我们将数据区分“冷热”,热点数据放到缓存中,缓存的访问速度往往比数据库要快多了!
如上图,我们的主从服务器,存储的仍然是完整的全量数据,而我们引入的缓存服务器,只是放一小部分的热点数据(会频繁被访问到的数据),根据二八原则:20%的数据能够支持80%的访问量【出自经济学:20%的人持有80%的股份】,甚至更极端情况可以达到一九,实际场景不同会略有差异。这里引入的缓存服务器通常就是Redis,此时,缓存服务器就帮助数据库服务器负重前行!
那么代价是?既然引入缓存,必然不会让其与主数据库直接进行同步数据,因为主数据库的效率会拉低缓存数据库的效率,那么缓存数据库中的数据如何保证其与主数据库一致呢?这是一个很大的问题。
8.数据库分库分表
我们引入分布式系统,不光要能够去应对更高的请求量(并发量),同时也要能应对更大的数据量。那么是否会出现,一台服务器已经存不下数据了呢???当然会存在!!!
虽然一个服务器,存储的数据量可以达到几十个TB,即使如此也可能会存不下(例如短视频文件),一台主机存不下,那么就需要多台主机来进行存储。我们见下图的结构:
我们针对数据库进一步的拆分,即分库分表操作。本来应该数据库服务器,这个数据库服务器上有多个数据库(这里数据库指的是逻辑上的数据集合,即create database创建的那个东西)
现在就可以引入多个数据库服务器,每个数据库服务器存储一个或者一部分数据库。
如果某个表特别大,大到一台主机存不下,也可以针对表进行拆分,具体分库分表如何实践?还是需要结合实际的业务场景来展开,业务是互联网公司最重要的东西,技术只是给业务提供支持的,业务决定了技术!
9.引入微服务
我们来看一下什么是微服务架构
在之前的应用服务器中,一个服务器程序里面做了很多的业务(比如电商,一台服务器上既做用户和商品,又做电商功能),这就可以会导致这一个服务器的代码变的越来越复杂。
为了更方便代码的维护,就可以把这样的一个负载的服务器,拆分成更多的,功能更单一,但是更小的服务器。这样的服务器叫做微服务,这种服务器的种类和数据就增加了。
我们想想,引入微服务到底是在解决什么问题,其实本质上是在解决“人的问题”,啥时候会涉及到“人”的问题??大厂!!如果是小公司,就两三个开发,此时搞微服务就没有太大必要了。
当应用服务器复杂了,势必就需要更多的人来维护了,当人多了,就需要配套的管理,把这些人组织好,通过划分组织结构,分成多个组(就需要进行分工),每个组分别配备领导进行管理。
按照功能,拆分为多组微服务,就可以有利于上述人员的组织结构的分配了。
那么引入微服务,解决了人的问题,付出的代价?
- 系统的性能下降(要想保证性能不下降太多,只能进入更多的机器,更多的硬件资源 => 充钱),因为拆出来更多的服务,多个功能之间更需要依赖网络通信,网络通信的速度很可能是比硬盘还慢的!!!(不过幸运的是,硬件技术的发展,网卡现在有万兆网卡,读写速度已经能够超过硬盘读写了,但也贵!)
- 系统复杂程度提高,可用性受到影响,服务器更多了,出现问题的概率就更大了,这就需要一系列的手段来保证系统的可用性,(比如更丰富的监控报警,以及配套的运维人员)
最后总结下微服务的优势:
- 解决了人的问题
- 使用微服务,可以更方便与功能的复用
- 可以给不同的服务进行不同的部署
10.补充概念
-
应用(Application)/系统(System)
一个应用,就是一个/组 服务器程序
-
模块(Module)/组件(Component)
一个应用,里面有很多个功能,每个独立的功能,就可以称为是一个模块/组件
-
分布式(Distriibuted)
引入多个主机/服务器,协同配合完成一系列的工作,(这里的多个指的是物理上的多个主机)
-
集群(Cluster)
引入多个主机/服务器,协同配合完成一系列的工作,(这里是逻辑上的多个主机)
-
主(Master)/从(Slave)
分布式系统中一种比较典型的结构,多个服务器节点,其中一个是主,另外的是从,从节点的数据要从主节点这里同步过来
-
中间件(Middleware)
和业务无关的服务(功能更通用的服务)
- 数据库
- 缓存
- 消息队列
- …
-
可用性(Availablity)
系统整体可用的时间/总的时间(这是一个系统的第一要务)
-
响应时长(Response Time RT)
衡量服务器的性能,这个指标越小越好,通常是和具体服务器要做的业务是密切相关的。
-
吞吐(Throughput) vs 并发(Concurrent)
衡量系统的处理请求的能力,衡量性能的一种方式
11.分布式总结
-
单机架构(应用程序+数据库服务器)
-
数据库和应用分离
应用程序和数据库服务器,分别放到了不同主机上部署了
-
引入负载均衡优化应用服务器 => 集群
通过负载均衡器,把请求比较均匀的分发给集群中的每个应用服务器,并且当集群中的某个主机挂了,其他主机仍然可以承担服务,提高了整个系统的可用性
-
引入读写分离,数据库主从结构
一个数据库节点作为主节点,其他N个数据库节点作为从节点,主节点负责写数据,从节点负责读数据,主节点需要把修改过的数据同步给从节点。
-
引入缓存,冷热数据分离
进一步提升了服务器针对请求的处理能力,依据的是二八原则。Redis在一个分布式系统中,通常就扮演着缓存这样的角色。但引入的问题是:数据库和缓存的数据一致性问题。
-
引入分库分表,数据库能够进一步扩展存储空间
-
引入微服务,从业务上进一步拆分应用服务器
从业务功能的角度,把应用服务器,拆分成更多的功能更单一,更简单,更小的服务器
上述的这样的几个演化的步骤,只是一个粗略的过程。实际上一个商业项目,真实的演化过程,都是和他的业务发展密切相关的,业务是更重要的,技术只是给业务提供支持的。所谓的分布式系统,其实就算想办法引入更多的硬件资源而已!
二、Redis背景知识
1.Redis特性介绍
Redis是一个在内存中存储数据的中间件,用于作为数据库,用于作为数据缓存,在分布式系统中能够大展拳脚,接下来我将讲解一些Redis的一些特性(优点):
-
In-memory data structures
在内存中存储数据,官方原话:support for strings,hashes,lists,sets,sorted sets,streams,and more.在Redis中,数据是按照一种kv的结构存储的,key都是string,而value则可以是上述的这些数据结构。
这时我们可以对比MySQL:MySQL主要是通过“表”的方式来存储数据的,我们一般称之为“关系型数据库”
而Redis主要是通过“键值对”的方式来存储组织数据的,我们称之为“非关系型数据库”
-
Programmability
官网:Server-side scripting with Lua and server-side storted procedures with Redis Functions 这里的Lua也是一个编程语言。针对Redis的操作,可以直接通过简单的交互式命令进行操作,也可以通过一些脚本的方式,批量执行一些操作(可以带有一些逻辑)
-
Extensibility
A module API for building custom extensions to Redis in C,C++,and Rust 即:可以在Redis原有的功能基础上再进行扩展,Redis提供了一组API,可以通过上述的几个语言编写Redis扩展(本质上就是一个动态链接库,好比Windows上的dll,可以让exe去调用里面包含的很多代码,Linux上的动态库是.so 虽然和dll格式不同,但本质是一样的)。我们可以自己去扩展Redis的功能,比如:Redis自身已经提供了很多的数据结构和命令,通过扩展,让Redis支持更多的数据结构以及支持更多的命令。
-
Persistence
Keep the dataset in memory for fast access but can also persist all writes to permanent storage to survive reboosts and system failures. 持久化,Redis是把数据存储在内存上的,但是进行的退出或者系统重启会导致内存上的数据“丢失”,所以Redis也会把数据存储在硬盘上一份,内存为主,硬盘为辅。(硬盘相当于对内存的数据备份了一下,如果Redis重启了,就会在重启时加载硬盘中的备份数据,使Redis的内存恢复到重启前的状态)
-
Clustering
Horizonta scalability with hash-based sharding,scaling to millions of nodes with automatic repartitioning when growing the cluster. Redis作为一个分布式系统中的中间件,能够支持集群是很关键的,这个水平扩展,类似于是“分库分表”,一个Redis能存储的数据是有限的(内存空间有限),引入多个主机,部署多个Redis节点,每个Redis存储数据的一部分。
-
High availability
Replication with automatic failover for both standalone and clustered deployments 高可用 => 冗余/备份 Redis自身也是支持“主从”结构的,从节点就相当于主节点的备份了。
一个字总结Redis,那就是快!那为啥Redis更快呢?
- Redis数据在内存中,就比访问硬盘的数据库(点名MySQL)要快很多。
- Redis核心功能都是比较简单的逻辑,核心功能都是比较简单的操作内存的数据结构
- 从网络角度上,Redis使用了IO多路复用的方式(epoll,使用一个线程来管理很多个socket)
- Redis使用的是单线程模型(虽然更高版本的Redis引入了多线程),这样的单线程模型,减少了不必要的线程之间的竞争开销。因为多进程提高效率的前提是:CPU密集型的任务,使用多个线程可用充分的利用CPU多核资源。但是Redis的核心任务,主要就是操作内存的数据结构,不会吃很多的CPU。而使用多进程势必要考虑线程安全,锁等问题。
- [个人不认同,网上说的很多]Redis是使用C语言开发的,所以就快。但我觉得MySQL也是C语言开发的,为啥没那么快呢?
2.Redis的应用场景
Redis的Use cases主要分为下面三种情况:
-
Real-time data store
这种使用场景下把redis当做了数据库,在大多数情况下,考虑到数据存储,优先考虑的是“大”,但是仍然有一些场景,考虑的是“快”,就比如做一个搜索引擎 -> 广告搜索(商业搜索),对于性能要求是非常高的,搜索系统中没有用到MySQL这样的数据库。把所有需要检索的数据都存储在内存中,就使用的是类似Redis这样的内存数据库来完成的。当然,使用这样的内存数据库,存储大量的数据,需要不少的硬件资源的(需充值)。在这种使用场景下Redis存的是全量数据,这里的数据是不能随便丢的。
-
Caching & session storage
使用MySQL存数据,大,慢,根据二八原则,把热点数据拎出来,存储在redis中。在这种使用场景下,Redis存的是部分数据,全量数据都是以mysql为主的,哪怕Redis的数据都没了,还可以从mysql这边再加载回来,这便是Redis作为Cache的应用场景。当Redis作为会话存储时候,我们回想起学习http时,cookie => 实现用户身份信息的保存(只是在浏览器这边存储了一个用户的身份标识,一般称为sessionid),是需要session配合的(服务器这里真正的存储了用户数据),之前我们的session都是存储在应用服务器上的,那么我们的分布式服务器如何存储的呢?
我们可能会遇到这样一个问题:用户第一次登录时,负载均衡器把会话放到了应用服务器1中,那么下一次会话,负载均衡器分配了应用服务器2来完成任务,那用户岂不是又要重新登录啦?如何解决这个问题那?
- 想办法让负载均衡器,把同一个用户的请求始终打到同一个机器上(不能轮询了,而是要通过userid之类的方式来分配机器)
- 把会话数据单独拎出来,放到一组独立的机器上存储(Redis),这样的话,当应用程序重启了,会话也不会丢失,这才是我们理想中的情况。
-
Streaming & messaging
Redis作为消息队列(这里是一种服务器,此处咱们说到的消息队列,不是Linux进程间通信的那个消息队列)。基于这个可以实现一个网络版本的生产者消费者模型。对于分布式系统来说,服务器和服务器之间,有时候也需要使用到生产者消费者模型的,优势在于:1.解耦合 2.削峰填谷。业界也有很多知名的消息队列,比如RabbitMQ,Kafka,RocketMQ等,Redis也是提供了消息队列的功能的。如果在当前场景中,对于消息队列的功能依赖不是很多,并且又不想引入额外的依赖了,Redis可以作为一个选择。
3.Redis背景知识小结
我们刚刚说了Redis的这么多优点,那有没有Redis他不能做的事情呢?那就是存储大规模数据。根据之前所学内容,对于分布式系统,我们已经有了一个初步的认识,要想进一步了解,一定要去公司!(中厂,大厂),因为在不同的业务场景下,分布式系统的具体实践方式是差异很大的。
我们一句话来总结Redis就是,他是一个使用内存存储数据的中间件,一般被用作 内存数据库/缓存/消息队列 来使用。
三、Redis环境搭建
1.版本选择说明
我这里选择的Redis 5系列,在Linux中进行安装。因为Redis官方是不支持Windows版本的,如果想使用Windows版本的Redis,可以考虑看看微软维护的Windows版本的Redis分支。
我这里开始学习的时候,先在本机上安装(Centos 和 Ubuntu),后面学习到Redis的集群相关的功能的时候,再使用Docker。
2.在centos上安装Redis
在 Centos上安装Redis 5(2018年底发布,用的多),如果是Centos8,yum 仓库中默认的redis版本就是5,直接yum install 即可 ~
1.换源安装Redis 5
如果是Centos7 ,yum 仓库中默认的redis 版本是3系列,比较老~
此处我们需要安装额外的软件源。我们这里选择的是scl源,所以我们首先安装scl源,再去安装redis,(记得安装要以root身份,或者普通用户su root/sudo)
yum install centos-release-scl-rh
此时,我们再根据特定的命令,才能去安装我们的redis 5
yum install rh-redis5-redis
一路 y 下去就安装好啦。
2.创建符号链接
我们发现,Redis 5 默认安装的目录为 /opt/rh/rh-redis5/root/usr/bin/
, 藏的太深了,不方便我们使用,我们可以通过符号链接, 把需要用到的关键内容设置到方便使用的目录中。符号链接,就是“快捷方式”,在linux下通过ln -s 指令。操作步骤为下面几个步骤:
cd /usr/bin
ln -s /opt/rh/rh-redis5/root/usr/bin/redis-server ./redis-server
ln -s /opt/rh/rh-redis5/root/usr/bin/redis-sentinel ./redis-sentinel
ln -s /opt/rh/rh-redis5/root/usr/bin/redis-cli ./redis-cli
执行完发现,这几个符号链接都已经创建成功了。针对配置文件,我们也需要创建符号链接,步骤如下:
cd /etc/
ln -s /etc/opt/rh/rh-redis5/ ./redis
3.修改配置文件
首先我们要进入到我们刚刚创建好的符号链接的redis这个目录,然后打开这个目录下的.conf配置文件。
cd redis
vim redis.conf
接下来主要修改下面几个部分:
-
设置 ip 地址
bind 0.0.0.0
-
关闭保护模式
protected-mode no
-
启动守护进程
daemonize yes
什么是守护进程呢?我们的服务器程序,一般都会以“后台进程”(守护进程)的方式运行,Linux中的进程,分为“前台”和“后台”进程,前台进程会随着终端的关闭而随之被杀死,后台进程不会随着终端的关闭而关闭。
-
设置工作目录
我们先创建一个工作目录(这个目录是用于保存redis运行是生成的文件的)
mkdir -p /var/lib/redis
再在配置文件中,设置工作目录
dir /var/lib/redis
-
设置日志目录
和上面操作差不多,我就不演示了,先创建日志目录
mkdir -p /var/log/redis/
再在配置文件中,设置日志目录
logfile /var/log/redis/redis-server.log
4.启动Redis
终于配置好了,接下来我们可以直接通过命令来启动Redis服务器啦!
redis-server /etc/redis/redis.conf
我们可以通过 netstat
指令,来看一下redis服务器的启动情况。
netstat -anp | grep redis
5.停止Redis
直接通过进程id ,使用kill指令来停止redis服务器的运行
我们上面时候需要停止(重启)Redis呢?当我们每次修改redis的配置文件时,都需要重启才能使得新的配置生效!
最后,我们来看一看redis生成的日志吧
我们再去看一下redis的工作目录吧:
以上就是我们在centos 7上安装Redis服务器的全部过程啦!如果你是centos 8,那么安装会方便很多,不用这么麻烦滴。因为博主没有ubantu的云服务器环境,这里就不再演示ubantu的安装了,都是大同小异的,后续我们会通过使用Docker进行安装,就不必再考虑这些平台问题啦。
3.Redis客户端介绍
我们的redis和MySQL一样,他也是一个客户端-服务器 结构的程序!redis 客户端和服务器可以在同一个主机上,也可以在不同的主机上(当前阶段,大家一般只有一台机器,此时客户端和服务器就是在同一台机器上的)
而Redis的客户端也有很多种形态,例如:
-
自带了命令行客户端【当前学习时主要使用这个客户端】
redis-cli:有两种连接方式
redis-cli redis-cli -h 127.0.0.1 -p 6379
-
图形化界面的客户端(桌面程序,web程序)
像这样的图形化程序,依赖windows系统,而未来在实际工作中,你用来办公的windows系统,连接到服务器可能会有诸多限制(中间可能会经历很多的跳板机,堡垒机,权限校验等),你的windows上的图形化界面客户端能不能连上你们的服务器里的redis,这是一个未知数!(这也和MySQL同理) -
基于Redis的api自行开发客户端【工作中最主要的形态】
非常类似于MySQL的C语言API和JDBC
咱们谈到的redis的快,是相对于MySQL这样的关系型数据库的,但是如果是直接和内存中的操作变量相比,就没有优势了,甚至更慢了。我们举个例子:在一个单机系统中,应用程序要存储一些数据,比如存储一下用户点赞数,视频id,点赞个数,按照键值对格式来存储。那么,我们是用一个redis来存储,还是直接在内存中搞一个hash map来存储呢??
我们知道,使用hash map是直接操作内存,而使用redis是先通过网络!再操作内存
在上述场景中,是否要使用redis?要结合实际的需求来确定!引入redis的缺点,会变慢,但是有了redis之后,就可以把数据单独存储,后续应用服务器重启,不会影响到数据内容,在未来要扩展成分布式系统,使用redis是更佳的
四、Redis通用命令
1.使用官网文档
接下来我们来学习Redis的实战操作,通过redis-cli 客户端和redis服务器交互,涉及到很多的redis命令,redis的命令非常多,需要我们:
- 掌握常用命令(多操作多练习)
- 学会使用redis文档
2.get和set指令
Redis 中最核心的两个命令,get 是根据 key 来取 value,set 是把key 和 value 存储进去。在redis中,是按照键值对的方式来存储数据的。
我们必须先进入redis-cli 客户端程序,才能输入 redis 命令。
对于上述这里的 key value,不需要加上引号,就是表示字符串的类型。当然,如果要是给key 和 value 加上引号,也是可以的(单引号或者双引号都行),并且在redis中,命令是不区分大小写的。
而 get 命令直接输入key,就能得到value。如果当前key不存在,就会返回nil,和null/NULL是一个意思。
3.全局命令keys
Redis支持很多种数据结构,整体来说,redis 是键值对结构,key 固定就是字符串,value实际上会有很多种类型(字符串,哈希表,列表,集合,有序集合),操作不同的数据结构就会有不同的命令。而全局命令,就是能够搭配任意一个数据结构来使用的命令。
关于keys指令:用来查询当前服务器上匹配的key,通过一些特殊符号(通配符)来描述key的模样,匹配上述模样的key,就能被查询出来。常用命令:|
KEYS pattern
其中pattern:是包含特殊符号的字符串,有的地方翻译成“样式”或者“模式”,存在的意义就是去描述另外的字符串长啥样的。那pattern具体是咋写的呢,我们来举一些例子,首先我们向数据库中插入一些数据。
-
?:匹配任意一个字符
-
*:匹配0个或者多个任意字符
-
[abcde] 只能匹配到 a b c d e,别的不行,相当于给出固定的选项了
-
[^e] :排除e,只有 e匹配不了,其他的都能匹配
-
[a-b]:匹配a-b这个范围的字符,包括两侧边界
上述的匹配规则,大家都不要刻意的去背;用的时候差一下就好啦
注意事项:
keys命令的时间复杂度是O(N),所以,在生产环境上,一般都会禁止使用keys命令,尤其是大杀器keys *(查询redis中所有的keys!!!)
因为:生产环境上的key可能会非常多!而redis是一个单线程的服务器,执行keys * 的时间非常长,就使redis服务器被阻塞了,无法给其他客户提供服务!(这样的后果可能是灾难性的),因为redis经常会被用于缓存,挡在mysql的前面,替mysql负重前行的人,万一redis被一个keys * 阻塞住了,此时其他的查询redis 的操作就超时了,此时这些请求就会直接查数据库,突然一大波请求过来,mysql措手不及,就容易挂了。整个系统就基本瘫痪了。如果你要是没能及时发现,及时恢复的话,那就麻烦啦!
4.生产环境的概念
未来在工作中会涉及到几个环境:
-
办公环境(入职公司之前,公司给你发个电脑)
笔记本(windows,mac)/台式机,现在办公电脑,一般8核16G内存512G磁盘
-
开发环境,有的时候,开发环境和办公环境是一个,一般是做前端/做客户端的。有的时候,开发环境是单独的服务器(28核128G内存,4T硬盘这样的)一般是后端程序,为什么呢?
- 编译一次时间特别久(C++) => C++ 23才引入 module,这个问题 #include 要接锅,所以考虑使用高性能服务器进行编译。
- 有的程序一启动要消耗很多的cpu和内存资源,办公电脑难以支撑。一般的商业搜索项目,启动起来要吃100G的内存
- 有的程序比较依赖linux,在windows环境搭不起来。
-
测试环境(测试工程师使用的,一般也是28核128G4T配置)
-
线上环境/生产环境
(办公环境,开发环境,测试环境,也统称为线下环境),外界用户无法访问到的。线上环境则是外界用户能够访问到的。一旦生产环境上出问题,一定会对用户的使用产生影响。未来咱们去操作线上环境的任何一个设备/程序都要怀着12分的谨慎!
5.exists指令
exists 指令是判定 key 是否存在的。语法格式如下:
EXISTS key [key ... ]
返回值:key 存在的个数。键值对存储的体系中(类似哈希表),key 得是唯一的呀,但是针对多个key来说,这是非常有用的。
时间复杂度:O(1),因为redis组织这些key就是按照哈希表的方式来组织的。
redis支持很多数据结构 => 指的是一个value 可以是一些复杂的数据结构,redis自身的这些键值对,是通过哈希表的方式来组织的,redis具体的某个值,又可以是一些数据结构。
我们要注意:redis是一个客户端-服务器 结构的程序,客户端和服务器之间通过网络来进行通信!所以我们考虑下下面两条指令有什么不同之处:
区别在于:分开的写法,会产生更多轮此时的网络通信。网络通信和直接操作内存相比,效率比较低,成本比较高。因为网络通信时需要封装和分用。
进行网络通信的时候,发送方发送一个数据,这个数据从应用层,到物理层,层层封装(每一层协议都要加上报头或者报尾)=> 发一个快递,要包装一下,要包装好几层。
接收方收到一个数据,这个数据要从物理层,到应用层层层分用(把每一层协议的报头或者尾给拆掉) => 收到一个快递,要拆快递,要拆很多层。而且网卡是 IO 设备,更何况你的客户端和服务器都不一定在一个主机上,中间可能隔着很远。
redis自身也非常清楚上述问题,所以redis的很多命令都是支持一次就能操作多个key 的/多种操作。
6.del指令
del(delete) 删除指定的 key,可以一次删除一个或者多个
DEL key [key ... ]
时间复杂度:O(1)
返回值:删除掉的key的个数
值得注意的是:之前学mysql的时候,当时强调,删除类的操作,比如 drop database,drop table,delete from …,都是非常危险的操作,一旦删除了之后,数据就没有了。
但是redis主要的应用场景,就是作为缓存。此时redis里存的只有一个热点数据,全局数据是在mysql数据库中,此时,如果把redis中的key删除了几个,一把来说,问题不大。
但是,当然如果把所以数据或者一大半数据一下子都干没了,这种影响就会很大,(本来redis是帮MySQL负重前行,redis没数据了,大部分的请求就直接打给mysql,然后就容易把mysql 搞挂掉)。相比之下,如果是mysql这样的数据,哪怕误删了一个数据,都可能是影响很大的。
如果是把redis作为数据库,此时误删数据的影响就大了!
如果是把redis作为消息队列(mq),这种情况误删数据的影响大不大,就要具体问题具体分析了。
7.expire和ttl指令
expire作用是给指定的key设置过期时间,key存活时间超出这个指定的值,就会被自动删除。设置的时间单位是秒。
为什么会有这个指令呢?因为很多业务场景,是有时间限制的,比如我们的手机验证码,通常都是5分钟有效。点外卖,优惠券,也都是指定时间之内有效。
包括后面基于redis实现分布式锁,为了避免出现不能正确解锁的情况,通常都会在加锁的时候设置一下过期时间(所谓的使用redis 作为分布式锁,就是给redis 里写一个特殊的key value)
expire指令格式:
EXPIRE key seconds
PEXPIRE key 毫秒级别,对于计算机来说,秒是一个非常长的时间
注意:此处的设定过期时间,必须是针对已经存在的key设置。设置成功返回1,设置失败返回0
时间复杂度:也是O(1)
对于ttl(time to live)指令,作用是查看当前key的过期时间还剩多少(以秒为单位),与此对应的还有pttl(以毫秒为单位)。我们之前学习网络原理,IP协议的时候,IP协议的报头中,就有一个字段,TTL,不过IP中的TTL不是用时间来衡量过期的,而是通过次数。
8.Redis的过期策略【经典面试题】
redis的key的过期策略是怎么实现的?【经典面试题】
一个redis中可能同时存在很多很多key,这些key中可能有很大一部分都有过期时间,此时,redis服务器咋知道是哪些key已经过期要被删除,哪些key还没过期??
如果直接遍历所有的key,显然是行不通的,效率非常低。所以redis整体的策略就是:
-
定期删除
每次抽取一部分,进行验证过期时间,保证这次抽取检查的过程足够块,为啥这里对于定期删除的时间,有明确的要求呢?因为redis是单线程的程序,主要的任务(处理每个命令的任务,扫描过期key …),如果扫描过期key消耗的时间太多了,就可能导致正常处理请求命令就被阻塞了(产生了类似于执行keys * 这样的效果)
-
惰性删除
假设这个key已经到过期时间了,但是暂时还没删他,key还存在。紧接着,后面又一次访问,正好用到了这个key,于是这次访问就会让redis服务器触发删除key的操作,同时再返回一个nil
-
内存淘汰策略
虽然有了上述两种策略结合,但整体的效果一般,仍然可能会有很多过期的key被残留了,没有及时删除掉。redis为了对上述进行补充,还提供了一些列的内存淘汰策略。这个暂时不介绍。
-
定时删除【网上的错误说法】
含义:在设置Key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除。【×】
- redis中并没有采取定时器的方式来实现过期key删除
- 如果有多个key过期,也可以通过一个定时器