单体架构:一台Web服务器、一台数据库服务器。
回顾,关系型数据库:基于二维表来存储数据的数据库就是关系型数据库。
MySQL跟Redis的区别:
- MySQL是关系型数据库,它是基于二维表来去存储数据的数据库,MySQL数据是写在磁盘的,它是跟磁盘进行交互的;Redis是非关系型数据库,它是把数据存储在内存当中的,是跟内存进行交互的,Redis它更多保证的是数据的性能!既然保证了性能就一定会损失数据的一致性和可靠性!Redis的出现是为了去解决性能的问题!!!
- MySQL基于标准、统一的SQL语言 / 语句去操作的,而Redis是基于命令 / 指令去操作的。
-
存储方式
-
关系型数据库基于磁盘进行存储,会有大量的磁盘IO,对性能有一定影响
-
非关系型数据库,他们的操作更多的是依赖于内存来操作,内存的读写速度会非常快,性能自然会好一些
-
-
扩展性
-
关系型数据库集群模式一般是主从,主从数据一致,起到数据备份的作用,称为垂直扩展。
-
非关系型数据库可以将数据拆分,存储在不同机器上,可以保存海量数据,解决内存大小有限的问题。称为水平扩展。
-
关系型数据库因为表之间存在关联关系,如果做水平扩展会给数据查询带来很多麻烦
-
1.了解NoSql
什么是Nosql?
- NoSQL,即Not-Only-SQL,不仅仅是SQL, 意思就是我们干事情不能只用SQL,泛指非关系型的数据库!
- NoSQL定位:NoSql数据库并不是要取代关系型数据库,而是作为关系型数据库的补充!最终数据还是存储在硬盘/磁盘里面。
-
NoSQL仅仅是一个概念, 泛指非关系型的数据库,区别于关系数据库,非关系型数据库往往不支持事务,它们不保证关系数据的ACID特性,只能实现基本的一致性,而传统的关系型数据库能满足事务ACID的原则。
-
NoSQL不是什么情况下都用的,是有适用前提的:应对基于海量用户(高并发)和海量数据前提下的数据处理问题。
关系型数据库(RDBMS):
-
Mysql
-
Oracle
-
DB2
-
SQLServer
非关系型数据库(NoSql):
-
Redis
-
Mongo DB(文档型数据库):是一个基于分布式文件存储的数据库,由C++语言编写。
-
MemCached
2.Redis介绍
- 高性能的数据备份AOF、高性能的epoll模型
- 高可靠:RDB做持久化
- 高拓展:做集群应用时可以进行负载均衡以及数据的分片
2.1 什么是Redis?
官网:https://redis.io
中文网:https://www.redis.net.cn/
全称: REmote DIctionary Server ( 远程字典服务器 )。 诞生于2009年!
- Redis是一个开源的,基于内存的键值型{Key-Value}的NoSQL(非关系型)数据库!Redis是互联网技术领域使用最为广泛的存储中间件!
- 是完全开源免费的,用C语言编写的, 遵守 BCD协议。
- 是一个高性能的(key-value)分布式内存数据库, 基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器。
- 可用作高速缓存,提高系统的整体访问性能!(why高速?===>内存 => 离CPU最近)
- Redis是一个基于内存去交互的数据库。
- Redis作为分布式缓存,实现高性能数据读取。
- Redis的出现是为了解决性能问题,Redis是为了解决关系型数据库的性能瓶颈而出现的内存数据库!
- Redis更多保证我们数据的性能,既然保证性能,意味着一定会损失数据的一致性、可靠性。
- Redis属于CAP模型中的AP模型。如果发生网络分区了,是保证C呢还是保证A呢?
- Redis主要解决查询问题,而MQ主要解决数据的异构问题!
- Redis它默认有16个库(0~15),也就是说Redis它默认分了16个库,默认使用第一个db0。我们不用去用select指令去切换库,没有什么意义!
- Redis可以理解为它就是一个巨大的HashMap!
什么是CAP模型?
- CAP模型是一种用于描述分布式系统特性的理论模型。
- CAP模型由三个关键概念组成:一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)。
- C - 一致性(Consistency):指的是在分布式系统中,当一个节点对数据进行修改后,其它节点立即能够看到这个修改。官方:指的是在分布式系统中,所有节点对于同一份数据的访问和操作都能保持一致的特性。
- A - 可用性(Availability):指的是系统能够及时响应用户请求,并一直保持可用的状态。即使系统中的某个节点发生故障或者网络出现问题,系统仍然能够继续运行。
- P - 分区容错性(Partition Tolerance):指的是系统在面对网络分区(节点之间的通信中断或丢失)的情况下仍能继续运行。分布式系统通常由多个节点组成,分布在不同的物理位置上,而网络分区可能导致节点之间无法直接通信。
- 根据CAP模型,分布式系统无法同时满足一致性、可用性和分区容错性这三个特性,一个分布式系统最多只能同时满足这三项中的两项。
Redis 与其他 key - value 缓存产品有以下三个特点:
- Redis支持数据的持久化,持久化机制 => 可以将内存中的数据保持在磁盘中,重启的时候可以再次加载到内存进行使用,从而保证数据的安全性
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
- Redis支持数据的备份,即master-slave(主从)模式的数据备份
2.2 Redis优势
- 基于内存存储,读写性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。Redis官方提供的数据显示Redis能够在单机环境下可以达到10w+/s的并发或QPS(每秒内查询次数),而一般MySQL数据库写的并发可以达到600个并发/s,读的2000/s,对于大型互联网项目的百万并发,根本扛不住!
- 丰富的数据类型 – Redis支持二进制案例的 Strings,Lists,Hashes,Sets 及 Ordered Sets 数据类型操作。
- 支持事务:操作都是原子性,所谓原子性就是对数据的更改要么全部执行,要么全部不执行。
- 原子 – Redis的所有操作都是原子性的 => 是线程安全的,同时Redis还支持对几个操作全并后的原子性执行。
- 丰富的特性 – Redis还支持 publish/subscribe订阅,通知,key 过期等等特性
- Redis的工作线程 采用/是 单线程,每个命令具备原子性,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- Redis底层采用NIO中的I/O多路复用机制或模型 epoll,非阻塞IO,能够非常好的支持并发,从而保证线程安全问题;
- 适合存储热点数据(热点商品、咨询、新闻),减少服务器的访问压力!
- 能分布式集群部署,保证高可用:高可用 - Redis的集群方案:主从复制集群、哨兵集群(慢慢被淘汰)、Cluster分片集群(是Redis官方提供的分布式数据存储方案,支持数据分片)
- 完善的内存管理机制,比如可以给key加过期时间,缓存淘汰策略(LFU和LRU等不同的淘汰算法)!
- 支持多种编程语言!
- 支持数据持久化
Redis单线程模型(对于IO、网络处理采用的epoll模型去进行对应的处理)
- Redis单线程,Redis采用了NIO中的IO多路复用原则,也就是底层采用一个线程或单个线程同时处理或维护多个不同的Redis客户端IO操作或TCP连接(操作),从而提高了Redis的并发性能,提高处理并发效率和保证线程安全问题,底层采用Linux操作系统的epoll技术或机制来避免空轮训!
- 但是NIO在不同的操作系统上实现的方式有所不同,在我们Windows操作系统使用select实现轮训(相当于就是个for循环,从头遍历到尾 => 轮训),时间复杂度是为O(n),而且还存在空轮训的情况,效率非常低,其次是默认对我们轮训的数据有一定限制,所以支持上完的TCP链接是非常难的,所以在Linux操作系统采用epoll实现事件驱动回调,不会存在空轮训的情况,只对活跃的Socket连接去实现主动回调,这样在性能上有大大的提升,所以时间复杂度是为O(1)。
- 注意:Windows操作系统是没有epoll的,只有Linux操作系统才有epoll。
- 注意:Redis官方是没有Windows版本的Redis,只有Linux版本的,这是因为Redis底层采用NIO中的多路IO复制的机制,Redis底层采用NIO epoll实现,而在NIO中的epoll只有Linux操作系统独有!Windows操作系统是没有epoll的!
- 包括在Tomcat 9里面也支持了NIO中的IO多路复用原则,这也是为什么Nginx、Redis都能够非常支持高并发,最终原因都是Linux中的IO多路复用原则epoll机制!
Redis的作用
- Redis:使用Redis去做缓存可以减轻数据库DB的访问压力!这个时候我们可能会先查Redis,Redis如果有的情况下,就不会查我们的MySQL数据库,这个时候就会遇到另外一个问题,就是MySQL与Redis数据的一个同步性问题。
既然Redis可以减轻DB数据库的访问压力,那为什么不直接把所有数据放在Redis当中呢?
- Redis里面它的一个内存大小都是有一个阈值的限制的,一旦超出阈值的话,就会有一个淘汰策略!
- 谈到缓存,想到JVM内置缓存框架: ECACH、os cache!性能很差!
- JVM内置缓存框架(数据缓存到我们的JVM里面)它的数据是不共享的,尤其是我们服务器做集群的情况下!
Redis的应用场景有哪些?
- Token令牌的生成
- 短信验证码的code
- 缓存热点数据 - 缓存查询数据:减轻数据库DB的访问压力,Redis的数据类型都可以做缓存,只不过不同的数据类型能更适应不同的业务场景!!!Redis提供了键值过期时间设置,并且也提供了灵活控制最大内存和内存溢出后的淘汰策略。可以这么说,一个合理的缓存设计能够为一个网站的稳定保驾护航。
- 分布式锁:在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用Redis自带的SETNX命令实现分布式锁,除此之外,还可以使用官方提供的RedLock分布式锁实现。
- Redis可以帮助实现网页计数器:因为Redis本身是单线程的,可以保证一个很好的原子性问题,是线程安全的,因此可以利用Redis原子性的自增操作,可以实现计数器的功能,比如统计用户点赞数、用户访问数等!
- 做延迟操作:比如在做秒杀抢购,订单加入购物车后,超时不支付则订单取消 => 实现:对key做一个时间的监听!
- 实现分布式消息中间件
- 虽然Redis支持发布和订阅,但是很少有人用Redis去做发布和订阅
- 限速器,可用于限制某个用户访问某个接口的频率,比如秒杀场景用于防止用户快速点击带来不必要的压力。
- 排行榜,利用zset数据结构的score的不同,可以实现⼀个排行榜的功能。
- 好友关系,利用集合的⼀些命令,比如交集、并集、差集等,实现共同好友、共同爱好之类的功能。
Redis的客户端
- 支持Java语言的Redis的客户端:Redisson、lettuce(是现在SpringBoot自带的一个客户端),它们两个是支持同步以及异步的,并且它做了很多的封装,比如说不同的数据类型,以及不同的集群它都有支持,像其它客户端它就不支持这些,并且只支持同步!
- sync:同步 async:异步
Redis里面支不支持事务操作?
- Redis里面是支持事务操作的!
- Redis的特性 - 快!
Redis为什么快?
- 基于内存去操作,所以它没有实时的磁盘IO,数据是异步刷新到磁盘的。
- 本身就是Key-Value结构,类似于HashMap,一个Key会对应一个Value,通过一个Key的Hash值 取余 数组的长度 得到一个数组的下标,知道这个数据放在哪里,去拿的时候,直接能找到下标,所以查询时,时间复杂度接近于O(1) => 因为只要你 用到了Hash去存储,就一定会有哈希冲突,在Redis里面是通过链表来解决的,并且是头插法,但是注意链表的查询时间复杂度可不为O(1)!
- 高效的数据类型:Redis提供了多种高效的数据结构,如哈希表、有序集合、列表等,这些数据结构都被实现的非常高效,能够在O(1)的时间复杂度内完成数据读写操作,这也是Redis能够快速处理数据请求的重要因素之一!并且Redis的底层(数据结构)支持像跳表、SDS这些用空间换时间的数据结构!
- 单线程模型:Redis的指令执行是单线程的,避免了频繁的上下文切换所带来的系统开销 => Why?为什么不用多线程呢?=> 反向思考:什么时候用多线程呢? => 之所以用多线程是因为一个任务执行很耗时间!我一个任务执行比较耗时,所以才会用多线程!=> 因为Redis的指令执行不用去跟磁盘进行IO交互,只是基于内存去操作,所以执行还是很快的!因此单次执行的时间比你线程切换的时间反而更短!!!
- Redis底层采用NIO中的IO多路复用模型中的epoll模型,是非阻塞IO,实现了单个线程同时处理多个客户端连接的能力,能够非常好的支持并发!
- 多线程的引入:在Redis 6.0中,为了进一步提升IO的性能,引入了多线程的机制,采用多线程,使得网络处理的请求并发进行,大大提升性能,Thread I/O的引入除了可以减少网络I/O等待造成的影响,还可以充分利用CPU的多核优势。
数据一致性问题
- 空间跟时间一定是互斥的!数据一致性一定是跟性能互斥的!
- 数据一致性问题:缓存跟DB的数据可能不一致,数据一致性一定是跟性能互斥的,是由性能或是由并发请求导致的!
假设有一个线程Thread1:
- 它去查数据,先去看Redis当中有没有,如果没有,Redis会从DB拿,从DB拿到数据之后,它还要做第二步操作
- 将其写入到Redis缓存,以供后续的查询使用!
这两个步骤它不是一个原子性的操作,因此在并发场景下可能会有其他的线程在这两个步骤之间对数据进行更改!
key-value结构存储:
常用的缓存技术
目前最流行的分布式缓存技术有Redis和Memcached两种。
分布式缓存技术对比
Redis相比Memcached有哪些优势?
- Memcached所有的值均是简单的字符串,Redis作为其代替者,支持更为丰富的数据类型
- Memcached不支持持久化,Memcached没有考虑数据的容灾问题,重启服务后所有的数据会丢失,而Redis可以通过异步的方式将数据持久化的写入到磁盘
- Redis使用单线程的多路IO复用模型,Memcached使用多线程的非阻塞IO模型。(Redis6.0引入了多线程IO,用来处理网络数据的读写和协议解析,但是命令的执行仍然是单线程)
- value值大小不同:Redis最大可以达到512M;Memcache只有1MB。
Memcached与Redis的区别都有哪些?
- 存储方式不同:Memcached把数据全部存在内存之中,断电重启之后数据会丢失;而Redis支持数据的持久化,可以将内存当中的部分数据存储在磁盘当中,这样能保证数据的持久性
- 数据类型支持不同:Memcached对数据类型支持相对简单,而Redis支持复杂的数据类型
- Redis支持发布订阅模型、Lua脚本、事务等功能,而Memcached不支持
2.3 Redis下载
(1)Http://redis.io/ 英文地址
(2)Http://www.redis.cn/ 中文地址
Redis安装包分为Windows版(第三方大牛改过的)和Linux版(官方):
-
Windows版下载地址:https://github.com/microsoftarchive/redis/releases
-
Linux版下载地址: https://download.redis.io/releases/
在Windows中安装Redis
- Redis的Windows版属于绿色软件,直接解压即可使用,解压后目录结构如下:
1.3 Redis服务启动与停止
以Window版Redis进行演示:
1.3.1 服务启动命令
- redis-server.exe redis.windows.conf
Redis服务默认端口号为 6379 ,通过快捷键Ctrl + C 即可停止Redis服务
当Redis服务启动成功后,可通过客户端进行连接。
1.3.2 客户端连接命令
- redis-cli.exe
- 通过redis-cli.exe命令默认连接的是本地的Redis服务,并且使用默认6379端口。
- 输入exit则退出客户端。
也可以通过指定如下参数连接:
-h IP地址
-p 端口号
-a 密码(如果需要)a代表auth,授权、认证的意思
1.3.3 修改Redis配置文件
- 设置Redis服务密码,修改redis.windows.conf,找到第443行:requirepass 123456
注意:
- 修改密码后需要重启Redis服务才能生效
- Redis配置文件中 # 表示注释
重启Redis后,再次连接Redis时,需加上密码,否则连接失败:
- redis-cli.exe -h localhost -p 6379 -a 123456
- 此时,-h 和 -p 参数可省略不写。
3.2 Redis的环境安装Linux
-
注意:Redis官方是没有Windows版本的Redis,只有Linux版本的,这是因为Redis底层采用NIO中的多路IO复制的机制,Redis底层采用NIO epoll实现,而在NIO中的epoll只有Linux操作系统独有!Windows操作系统是没有epoll的!
Redis的版本号:2.8,3.0,6.2.7
- 第二个版本号为:奇数,非稳定版本(2.7,2.9,3.1);
- 偶数,稳定版本
3.2.1 Redis的编译环境
- Redis是C语言开发的,安装redis需要先去官网下载源码进行编译,编译需要依赖于GCC编译环境,如果CentOS上没有安装gcc编译环境,需要提前安装,所以需要安装Redis的依赖环境gcc,安装命令如下:yum install gcc-c++
上传Redis安装文件到Linux服务器,并且移动到/usr/local/MySoftWare目录中,接着解压:
编译Redis(编译,将.c文件编译为.o文件)
- 进入解压文件夹,执行 make
- 编译成功!如果编译过程中出错,先删除安装文件目录,后解压重新编译。
安装:make PREFIX=/home/admin/myapps/redis install
安装之后的bin目录:
bin文件夹下的命令:
Copy文件:将redis解压的文件夹中的redis.conf文件复制到安装目录
- Redis启动需要一个配置文件,可以修改端口号信息。
3.3 Redis的启动
3.3.1 Redis的前端 / 前台模式启动
直接运行bin/redis-server将使用前端模式启动:
前端模式启动的缺点是启动完成后,不能再进行其他操作,这个界面只能启动,启动后不能进行其他操作,如果要退出操作必须使用Ctrl+C。
3.3.2 Redis的后端 / 后台启动
修改redis.conf配置文件,找到daemonize no,将no改为yes,然后可以使用后端模式启动。
- no表示不允许后端启动!
启动时,需要既指定指令,指定配置文件(这里所在文件夹是redis)
验证Redis是否已经启动成功:Redis默认端口:6379,通过当前服务进行查看
3.3.3 命令行客户端访问连接redis
- 如果想要通过指令来操作redis,可以使用redis的客户端进行操作,在bin文件夹下运行redis-cli
- 如果想要连接指定的ip地址以及端口号,则需要按照: redis-cli -h IP地址(默认是127.0.0.1) -p 端口号(默认是6379) -a 指定Redis的访问密码语法结构连接。
- 该指令默认连接的127.0.0.1 ,端口号是6379 -a指的就是auth 密码...
如果设置了密码,那么使用命令行模式来去访问连接Redis时, 需要先 auth + 密码,否则会报错
3.3.4 向Redis服务器发送命令
- ping,测试客户端与Redis的连接是否正常,如果连接正常,回收到Pong
3.3.5 退出客户端:quit
3.3.6 Redis的停止
(1) 强制结束程序(不推荐)
- 强制终止Redis进程可能会导致Redis持久化数据丢失。
- 语法:kill -9 pid-进程号
- 进程号pid可以通过 ps -aux | grep redis 进行查询。
(2) 正确停止Redis的方式应该是 :向Redis发送SHUTDOWN命令,方法为(关闭默认的端口)
- shutdowna会在关闭之前,会生成RDB备份,也就是会生成持久化文件!(dump.rdb)
- Redis在哪里启动,它的日志文件和它的rdb备份文件就会出现在哪里!
3.3.7 第三方工具(redis-desktop-manager)操作redis
1. 关闭Linux防火墙
2. 进入自己的redis安装目录,vim进入redis.conf修改redis.conf文件中的bind参数
设置Redis的账号密码
- 技巧:进入编辑模式后,输入/+搜索内容按回车键后可以进行搜索!
- 搜索:# requirepass foobared
- 删除掉前面的#注释,并将密码改为123456,接着按ESC退出编辑模式,按:wq保存并退出!
以后,客户端连接:auth 123456
设置Redis允许外部IP访问:
- 注释掉 bind 127.0.0.1,bind指允许访问的IP地址,默认是127.0.0.1,会导致只能在本地进行访问,如果修改为0.0.0.0则可以在任意IP访问!
- protected-mode no ###允许外界访问
接着重新启动Redis:
连接Redis:
4.Redis数据结构
Redis是一种基于内存的数据库,并且提供一定的持久化功能,它是一种键值(key-value)数据库,使用 key 作为索引找到当前缓存的数据,并且返回给程序调用者。
注意:Redis的数据类型它一定说的是Value,因为在Redis里面,这个Key它就必须是String,它的数据类型是由Value来决定的!
Redis五种基本数据类型介绍:
Redis存储的是Key-Value结构的数据,其中Key是字符串类型,Value有5种常用的数据类型:
- String - 字符串
- List - 列表
- Set - 集合
- Hash - 哈希
- ZSet / SortedSet - 有序集合
当前的 Redis 支持 6 种数据类型,它们分别是字符串(String)、列表(List)、集合(set)、哈希结构(hash)、有序集合(zset)和基数(HyperLogLog)
重点掌握前五种数据结构!!!
解释说明:
- 字符串(string):普通字符串,Redis中最简单的数据类型
- 哈希(hash):也叫散列,类似于Java中的HashMap结构 ,存储的就是键值对
- 列表(list):按照插入顺序排序(有序),可以有重复元素,类似于Java中的LinkedList
- 集合(set):无序集合,没有重复元素,类似于Java中的HashSet
- 有序集合(sorted set/zset):集合中每个元素关联一个分数(score),根据分数升序排序,没有重复元素
5.Redis常用指令
命令学习网站:Redis 命令参考 — Redis 命令参考
不能在生产环境用的几个通用指令 / 命令:
通用指令是指所有数据类型,都可以使用的指令!
keys pattern:查找所有符合给定(Pattern)模式的Key
keys * => 查看所有的Key,返回存在于当前数据库中所有的键
如果非要查看Redis当中到底有多少个键-Keys,可以用dbsize命令 - 返回当前数据库中Key-键的总数,它在计算键总数时不会遍历所有键,而是直接获取Redis内置的键总数变量,相当于是一个计时器,因此时间复杂度为O(1),而keys * 时间复杂度为O(n)!
keys a*:查询以a开头的Key - 键
smembers => 用于获取指定集合键中的所有Key
像keys * 以及 smembers这两个单个指令返回的数据比较大的,在生产环境中不建议去使用!因为Redis的指令执行是单线程的,如果请求量过大,单次耗时比较久可能会导致线程阻塞,所有耗时的指令都慎用!!!
flushall => 清空所有数据库(中所有的Key)(慎用啊!!!因为是删库跑路的!!!)
flushdb => 清空当前数据库(中所有的Key)
---------------------------------------------------------------------------
exists key:检查键是否存在
select db_number:切换到指定的数据库,数据库索引号index用数字值指定,默认在0库操作,以0作为起始索引值,比如,select 1:切换到1库
info:获取Redis服务器的各种信息及统计数据
monitor:监控当前Redis服务器执行的指令有哪些,实时打印出Redis服务器接收到的命令(调式用)
Type key:返回Key所存储的值的类型
Persist Key:移除Key的生存时间,转换成一个持久的Key
检查 / 判断Key - 键是否存在:exists key 存在返回1,不存在返回0!
del key1 key2 ... keyN - 可以删除多个Key键,返回删除键的个数,删除不存在的键返回0!
对某个key设置过期时间(单位:秒):语法 - expire key timeout-过期时间。
- 内存非常宝贵,对于一些数据,我们应该给它设置一些过期时间,当过期时间到了之后,它就会自动被删除!
注意:我们默认通过set去设置key它是不过期的,它是没有过期时间的!
- 比如我们通过expire给某个key设置了过期时间,然后后面又通过set去修改了该Key对应的Value,此时该key就会从本来有过期时间的变成了不会过期的,即通过set设置覆盖了key的过期时间!
- 我们在set的时候可以设置key的过期时间,语法:set key value Ex seconds
- 注意:我们去设置过期都是针对的是Key,而不是Value,都是去给Key设置过期时间!
- expire key 10
- expire key 10000
- 设置键在xxx毫秒之后过期:pexpire key
- 设置key在某个时间戳timestamp后过期:expireat key timestamp
查看该Key还有多长时间消失:ttl或pttl key - 查询键的剩余过期时间 / 查看一个Key的剩余有效期,单位是秒,而pttl精度更高可以达到毫秒级别!
- ttl / TTL key 如果返回结果为-2,则表示该Key已经消失不存在了
- 如果返回-1,说明该Key没有设置过期时间!
- 如果get的时候显示(nil),则表示为空的意思!
rename 旧key 新key:给键重命名!
- 如果在rename之前,新键已经存在,那么在重命名键期间不仅会执行del命令删除旧的键,并且新键的Value值将会被旧键的Valeu值覆盖。
为了防止被强行rename(防止旧键-旧key被删除),Redis提供了renamenx命令 - renamenx key newkey,确保只有new-Key(新键)不存在时才被覆盖!
randomkey:随机返回一个Key
Key键名的生产实践
- 设计合理的键名,有助于防止键冲突和项目的可维护性,比较推荐的方式时使用" 业务名-数据库名:对象名-表名:id:[属性] "作为键名。
- 例如MySQL的数据库名为mall,用户表名为order,那么对应的键可以用" mall:order:1"。
- 注意:在能描述键含义的前提下适当减少键的长度,从而减少由于键过长的内存浪费!
5.1 String类型
我们知道Redis它是标准的C写的,而C语言里面恰好就有String类型,它的实现是char[ ]数组,但是注意:Redis并没有直接使用C写的String类型,而是自己去实现了String字符串类型!
- String字符串类型是Redis最基础的数据结构!一个键-Key最大存储是521MB!
- 首先Key - 键都是字符串类型,而且其它几种数据类型都是在字符串基础上构建的!
- 在Redis里面是没有int数据类型的,int数据类型也是属于String类型的!
- SETNX key value 或 set key value nx:键必须不存在,才可以设置成功(返回1),用于添加数据(分布式锁常用,它是Java里面做分布式锁的一个前提)! (NX代表Not Exist,代表不存在)
- SETXX key value 或 set key value xx:与setnx相反,键必须存在,才可以设置成功,用于更新数据!
- SETEX key seconds value:添加一个String类型的键值对,并且指定Key的过期时间 / 有效期(这里的EX指的Expire)
赋值语法:SET key value
SET Key Value ex 过期时间:它是一个原子命令 - 合二为一的,不可分割的命令,设置Key - 键的同时指定过期时间(单位:秒 - S)
- set name Jack ex 100:设置键的同时指定过期时间为100秒
取值语法: GET key
批量设置多个键值语法: MSET key value [key value …]
批量获取多个键值语法: MGET key [key …]
删除语法:DEL key 【key...】
5.1.1 String字符串类型数字的递增与递减
递增数字:
- 当存储的字符串是整数时,Redis提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值。
- 值不是整数,返回错误;
- 值是整数,返回自增后的结果;
- 键不存在,按照值为0自增,返回结果为1。
- 除了incr命令,Redis提供了decr(自减)、 incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数) !
- 递增数字语法: INCR key => 因此String类型可以去做分布式ID
- 递减数值语法: DECR key
- 增加指定的整数语法: INCRBY key incremen(步长:就一次性加多少)
- 减少指定的整数 语法:DECRBY key decrement
5.1.2 String类型的其他命令
append追加指令
- append可以向字符串尾部追加值
strlen - 返回字符串Value的长度
- 返回字符串Value的长度
- 注意:每个中文占3个字节
getset key value:设置并返回键原来的值
- getset 和 set 一样会设置值,但是不同的是,它返回的是键原来的值!
- 如果该key是新的,则说明是第一次为该key赋值,因此返回(nil)。
setrange key index 要设置的字符:设置指定位置的字符,下标从0开始计算:
getrange key startIndex endIndex:截取字符串中的一部分,需要指明开始和结束的偏移量,截取的范围是个闭区间。
5.1.3 命令的时间复杂度
- 字符串这些命令中,除了del 、mset、 mget支持多个键的批量操作,时间复杂度和键的个数相关,为O(n),还有getrange和字符串长度相关,时间复杂度也是O(n),其余的命令基本上都是O(1)的时间复杂度,在速度上还是非常快的。
5.1.4 字符串String类型的使用场景
1. 缓存功能:单值缓存 & 对象缓存
- Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取,由于Redis具体支撑高并发的特性,所以缓存通常能起到加速读写和降低后端DB数据库的访问压力的作用!
- 单值缓存:Value是普通字符串
- 对象缓存:如果Value是一个Java对象,则可以将对象序列化为JSON字符串后存储,Value是JSON格式的字符串(JSON格式数据)
KEY
VALUE
user:1
{"id":1, "name": "zhangsan1", "balance": 1888}
user:2
{"id":2, "name": "zhangsan2", "balance": 16000}
product:1
{"id":1, "name": "小米11", "price": 4999}
2. 计数器
- 使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步的落到其他数据源。
3. 共享Session信息
- 一个分布式的Web服务将用户的Session信息(例如用户的登录信息)保存在各自的服务器当中,出于负载均衡的考虑,这样会造成一个问题,那就是分布式服务会将用户的访问均衡到不同的服务器上,用户刷新一次访问可能会发现需要重新登录,这个问题是用户无法容忍的。
- 为了解决这个问题,可以使用Redis将用户的Session进行集中管理,在这种模式下只要保证Redis是高可用和(高)扩展性的,每次用户更新或者查询登录信息都直接从Redis当中来集中获取即可。
4. 限流
- 比如,很多应用出于安全的考虑,会在每次进行登录的时候,让用户输入手机验证码,从而确定是否是用户本人,但是为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次,一些网站限制一个IP地址不能在一秒钟之内访问超过n次也可以采用类似的思路!
5. 分布式锁
一旦我们向Redis采用这样的方式存储,那么在可视化界面中,Redis会以层级结构来进行存储,形成类似于这样的结构,更加方便Redis获取数据,一个冒号对应一个层级:
5.2 Hash类型,也叫散列(了解)
Redis hash 是一个String类型的 field 和 value 的映射表,注意这里的value是指field对应的值,不是键对应的值。
- hash特别适合用于存储对象!
- hash叫散列类型,它提供了字段和字段值的映射。
- 字段值只能是字符串类型,不支持散列类型、集合类型等其它类型。相当于是对象格式的存储
操作命令
- 基本上,哈希的操作命令和字符串的操作命令很类似,很多命令在字符串类型的命令前面加上了h字母,代表是操作哈希类型,同时还要指明要操作的field的值。
hash特别适合用于存储对象,常用命令:
赋值语法: HSET key/大key/一级key field-属性名-小key/二级key value-属性值
- 设置一个字段值, HSET命令不区分插入和更新操作,当执行插入操作时HSET命令返回1,当执行更新操作时返回0。
取值语法: HGET key field-属性值
设置多个字段语法: HMSET key field value [field value ...]
取多个值语法: HMGET key field [field ...]
获取所有的 Field 和 Value,语法:HGETALL Key
- 在使用 hgetall 时,如果哈希元素个数比较多,会存在阻塞Redis的可能!
- 如果只需要获取部分field,可以使用hmget,如果一定要获取全部field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型
type Key:查看Key所对应的Value的数据类型
删除字段filed语法:HDEL key field [field ...]
HLEN 大Key:计算Field个数或者说查询该Key到底有多少个键值对
hexists 大Key 小Key:判断Field-小Key是否存在,若存在返回1,不存在返回0!
hkeys Key:返回指定哈希键所有的field
hvals Key:获取哈希表中所有的Value
hsetnx Key Filed Value:哈希键必须不存在,才可以设置成功,用于添加数据:
hstrlen Key Field:计算Value的字符串长度
hincrby增加,语法:HINCRBY key field increment
- hincrby和 hincrbyfloat,就像incrby和incrbyfloat命令一样,但是它们的作用域是Filed。
5.2.1 Hash散列的使用场景
从前面的操作可以看出,String和Hash的操作非常类似,那为什么要弄一个hash出来存储?
- 哈希类型比较适宜存放对象类型的数据(缓存对象、做购物车)
我们可以比较下,如果数据库中表记录user为:
id | name | age |
---|---|---|
1 | lijin | 18 |
2 | coding | 20 |
使用hash类型
- hmset user:1 name lijin age 18;
- hmset user:2 name coding age 20;
优点:简单直观,使用合理可减少内存空间消耗;
缺点:要控制内部编码格式,不恰当的格式会消耗更多内存。
---------------------------------------------------------------------------------------
5.3 列表List
- Redis的List是采用来链表来存储,双向链表存储数据,特点:增删快、查询慢(Linkedlist),这个队列是有序的,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)!
在列表List中元素是可以重复的,也就是把没有去重! - 在Redis 中,可以对列表两端插入( push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。
- 列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
List列表类型有两个特点:
- 第一:列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。
- 第二:列表中的元素可以是重复的。
Redis 列表是简单的字符串列表,列表中的字符串称为元素(Element),按照插入顺序排序,常用命令:
命令 | 含义 |
---|---|
lpush key value1 [value2] | 将一个或多个值插入到列表头部 |
rpush key value1 [value2] | 将一个或多个值插入到列表尾部 |
lrange key start stop | 获取列表指定范围内的元素 |
lpop key | 移出并获取列表的第一个元素 |
rpop key | 移除并获取列表最后一个元素 |
BRPOP key1 [key2 ] timeout | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
llen key | 获取列表长度 |
rpoplpush source dest | 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
brpoplpush source dest timeout | 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
向列表左边增加元素: LPUSH key value [value ...]
从列表左边弹出元素: LPOP key(临时存储,弹出后,从队列中清除)
向列表右边增加元素 : RPUSH key value [value ...]
从列表右边弹出元素: RPOP key
获取列表中元素的个数: LLEN key
获取列表指定范围内的元素:LRANGE key start stop
-
将返回start、stop之间的所有元素(包含两端的元素),索引从0开始,可以是负数,如:“-1”代表最后的一个元素,比如 lrange Key 0 -1:就代表取出所有元素!
lrem 对指定元素进行删除,返回值为实际删掉的元素个数!
lrem命令会从列表中找到等于value的元素进行删除,根据count的不同分为三种情况:
- count>0,从左到右,删除最多count个元素。
- count<0,从右到左,删除最多count绝对值个元素。
- count=0,删除所有。
- 一般来说从上往下删除!
ltirm Key startIndex endIndex:按照索引范围修剪列表,例如想保留列表中第0个到第1个元素!
lset Key Index newValue:修改指定索引下标的元素
lindex Key Index:获取列表指定索引下标的元素
llen Key:获取列表长度
List提供了两个阻塞的弹出操作:BLPOP/BRPOP,blpop和brpop,是lpop和rpop的阻塞版本,可以设置阻塞时间或者超时时间,单位为秒,如果阻塞时间为0,表示一直阻塞下去!
- BLPOP:BLPOP key1 timeout - 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表,直到等待超时或发现可弹出元素为止。
注意:brpop后面如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端立即返回。
5.3.1 列表List的使用场景
1. 用户消息时间线:因为List是有序的!
2. 利用List结构模拟实现其他数据结构
- 模拟队列 - Queue:先进先出(First In,First Out):LPUSH + RPOP - 左进右出:入口和出口在不同边
- 模拟栈 - Stack:先进后出:LPUSH + LPOP - 左进左出:入口和出口在同一边
- LPUSH + brpop = Message Queue(消息队列 => 阻塞队列):生产者客户端使用lpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,但我们一般不会用,因为我们有更成熟的技术,MQ!
5.4 Set集合操作命令
Redis Set 是String类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据,即不允许出现重复的元素,并且集合中的元素是无序的,不能通过索引下标获取元素。
一个集合最多可以存储2的32次方-1个元素。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多实际问题。
常用命令:
命令 | 含义 |
---|---|
SADD key member1 [member2] | 向集合添加一个或多个成员 |
SMEMBERS key | 返回集合中的所有成员 |
SCARD key | 获取集合的成员数 |
SINTER key1 [key2] | 返回给定所有集合的交集 |
SUNION key1 [key2] | 返回所有给定集合的并集 |
sdiff key1 [key2] | 返回所有给定集合的差集 |
SREM key member1 [member2] | 移除集合中一个或多个成员 |
添加一个或者多个元素语法:SADD key member1 [member2 ...]
smembers key :获取集合中的所有元素,返回结果是无序的!
srem 删除一个 或 多个元素语法: SREM key member1 [member2 ...] ,返回结果为成功删除的元素个数!
查看元素是否存在 或 判断元素是否在集合中: SISMEMBER key member 返回1表示存在,反之0表示不存在
scard Key:计算元素个数 / 获取所有元素的个数
spop Key [Number]:从集合中随机弹出一个元素,如果不指定个数,则默认为1!(毕竟是随机弹,是不可控的,所以用的比较少)
sinter Set1 Set2:获取两个集合的交集
sinterstore Set03 Set01 Set02:将Set01与Set02取交集并并将结果存储到Set03当中:
suinon Set1 Set2:求多个集合的并集
sdiff Set1 Set2:就获取前一个集合有而后一个集合没有的(所谓的 "差集",不知道哪个弱智想出来的名词,让人晦涩难懂)
5.4.1 Set集合的使用场景
- Set集合类型比较典型的使用场景就是标签-Tag,通过sadd向集合中添加成员得到某用户关注的人或关注的其它东西,有了这些标签之后就可以对不同标签的用户做不同类型的推荐!
- 以及通过sinter取交集之后得到两个人共同关注的人是谁谁谁,还有通过 sdiff 取差集之后得到某某某用户可能认识的人是谁谁谁。
- Set集合还可以通过生成随机数进行抽奖活动。
5.5 ZSet,即Sorted Set - 有序集合操作命令(所有的指令都是Z开头)
- SortedSet又叫Zset,是有序集合,可排序的,但是唯一。 SortedSet和Set的不同之处,是会给Set中的元素添加一个分数,然后通过这个分数进行排序。
- List集合它的Value是允许重复的,而ZSet它的Value是不允许重复的!
- ZSet有序集合的Member成员是唯一的,但分数(Score)却可以重复!
- 当Score相同时,按照Key的ASCII码排序!
Redis Zset有序集合是String类型元素的集合,且不允许有重复成员。每个元素都会关联一个double类型的分数。常用命令: withscores选项代表得到对应的分数!
命令 | 含义 |
---|---|
zadd key score1 member1 [score2 member2] | 向有序集合添加一个或多个成员 |
zrange key start stop [withscores] | 通过索引区间返回有序集合中指定区间内的成员(分数从小到大,升序排序) |
zrevrange key start stop [withscores] | 通过索引区间返回有序集合中指定区间内的成员(分数从大到小,降序排序) |
zincrby key increment member | 有序集合中对指定成员的分数加上增量 increment |
zrem key member [member ...] | 移除有序集合中的一个或多个成员 |
zcard key | 计算集合中元素的数量 |
增加元素:ZADD key score1 member1 [score2 member2 ...]
- 向有序集合中加入一个元素 和 该元素的分数(score),如果该元素已经存在则会用新的分数替换原有的分数。
zadd命令还有四个选项nx、xx、ch、incr 四个选项:
- nx: member必须不存在,才可以设置成功,用于添加。
- xx: member必须存在,才可以设置成功,用于更新。
- ch: 返回此次操作后,有序集合元素和分数发生变化的个数
- incr: 对score做增加,相当于后面介绍的zincrby
获得排名在某个范围的元素列表,并按照元素分数降序返回:
获取元素的分数:ZSCORE key member
删除元素ZREM key member [member ...]
zrange和zrevrange返回指定排名范围的成员
- Zrange升序排序(即从低到高返回),zrevrange反之;
- 获得元素的分数的可以在命令尾部加上WITHSCORES参数,加上 withscores选项,同时会返回成员的分数。
对指定成员的分数加上增量Increment:zincrby key increment member
zcard Key:返回ZSet有序集合中Key中的元素个数
zrank Key Member:计算成员的排名,zrank是从分数从低到高返回排名,zrevrank反之:
5.5.1 ZSet集合的使用场景
- 排序场景:可以通过zrevrange或zrange对元素进行从降序排序或升序排序,比如做排行榜系统:热搜榜、游戏评分排行等等...