Redis

Redis介绍

redis 是完全开源免费的,遵守BSD协议,是一个高性能的 key-value数据库

这里提到的提到了key-value数据库,这是什么那,其实我们在之前就学习过数据库分为两种,一种是关系型数据库,一种为非关系型的数据库,其实Redis就是一种非关系型数据库,并且可基于内存且可持久化的日志性,key-value数据库,并提供多种语言的API。其实这种数据库有一个名词叫做NoSql.

NoSQL

NoSQL指的是非关系型数据库。NoSQL也称之为Not Only SQL(不仅仅只是SQL)的缩写,是对不同于传统的关系型数据库管理系统的统称。

为什么需要使用NoSL

传统关系型数据库在应付超大规模和高并发网站时就会出现各种问题,甚至是力不从心了,比如:

1.对数据库高并发读写需求:

关系型数据库应对上万次的SQL查询,勉勉强强顶得住,但是一旦面对上万次的写操作,硬盘 IO 就已经无法承受了。

2.海量数据高效存储及访问

比较大型的网站,每天用户产生的数据是非常庞大的,因为现在的应用系统一般都需要有日志的功能来记录用户的行为以及系统信息等,所以有一些大型网站一个月就达到了2.5亿的用户动态,如果我们想在一张如此庞大的数据库中查询某一条数据信息,效率之差是我们无法忍受的。

3.数据库的高拓展和高可用需求

基于 web 框架当中,数据库是最难进行横向拓展的,它不会像 webserver 或者 AppServer 那样进行简单的硬件和服务节点的拓展就能提高性能和负载,对于很多24小时不间断的服务来说,对数据库进行升级和拓展是一件非常痛苦的事情。

针对以上提到的问题,促使产生了NoSQL数据库来解决问题,因为它就是用于解决超大规模数据的存储和多重数据种类所带来的问题,这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。

总结:

今天我们可以通过第三方平台(如:Google Facebook等) 可以很容易的访问和抓取数据。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加,我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了,NoSQL数据库的发展却能很好的处理这些大的数据。

NoSQL的优点/缺点

优点:

  • 高可扩展性

  • 分布式计算

  • 低成本

  • 架构的灵活性,半结构化数据

  • 没有复杂的关系

缺点:

  • 没有标准化

  • 有限的查询功能(到目前为止)

  • 最终一致性

集群

 

NoSQL类别

NoSQL分为以下的类别

类型部分代表特点
列存储Hbase ,Cassandra,Hypertable顾名思义,是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的 IO 优势。
文档存储MongoDB CouchDB文档存储一般用类似 json 的格式存储,存储的内容是文档型的。这样也就有机会对某些字段建立索引,实现关系数据库的某些功能。
key-value存储Tokyo Cabinet Tyrant MemcacheDB Redis可以通过 key 快速查询到其 value.一般来说,存储不管 value 的格式,照单全收。(Redis包含了其他功能)
图存储Neo4j FlockDB图形关系的最佳存储。使用传统关系数据库来解决的话性能低下,而且设计使用不方便。
对象存储db40 Versant通过类似面向对象语言的语法操作数据库,通过对象的方式存取数据
xml数据库Berkeley DB XML BaseX高效的存储XML数据,并支持XML的内部查询语法,比如XQuery,Xpath

MySQL关系型数据库可以理解为是一张表,新建一张表时,他需要有对应的列和行,行代表一行数据,当前的列可以说是你需要定义数据类型,比如id,name,age,

id(int)name(string)age(int)
1Tom18
2

这就是我们常见的关系型数据表。

对应的需要有每一行,每一列的这种结构以及对应的数据类型要求的。你在设计表的时候就需要将他们都设计好。

但是对于我们的NoSQL,非关系型数据库。nosql的这种表的话,我们不用这么费劲,简单来说,我们以Redis为列

redis就是 key-value [k:v] 模式

{id: 1,name: Tom,age: 18}

他不需要单独去设计 id 是什么类型,name 是什么类型,age 是什么类型,都不需要,这就是他简单的地方,说白了,他的这种架构表比较灵活。

MySQL的用户表,会跟订单表,购物表,物流表等像关联,关系型数据表是可以通过主id键将他们关联的,每张表之间会存在关联的,

但是NoSQL是没有关联性的,没有标准的格式化:

K: 一般 用 id

V: 一般用 json 字符串

json字符串里面存储的就是像 name,age等用户数据,只需要用id就可以查询字符串,查出这条用户信息,这样就可以大大简化数据,这就是NoSQL的好处。

Redis入门

Redis是由C语言编写的一个远程内存数据库,他不仅性能强劲,而且有复制特性以及为解决问题而生的独一无二的数据模型。

Redis是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库,并提供多种语言的API。

Redis 与其他key-value缓存产品有以下三个特点:

  • redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加我进行使用

  • redis不仅仅支持简单的key-value类型的数据,同时还提供 list,set,zset,hash等数据结构的存储。

  • redis支持数据的备份,即 master-slave模式的数据备份

为什么要使用Redis

以往,我们系统的只需要从数据库读取数据,展示在页面就可以了,后来随着时代的发展系统越来越复杂了,传统的模式越来越难以支撑,缓存技术也在系统中占据越来越重要的角色,而Redis就是目前市场使用量最多的缓存技术。

Redis特点

  • 性能极高-Redis能读的速度是110000次/s,写的速度是81000次/s.

  • 丰富的数据类型-Redis支持二进制案例的 strings,hashes,sets 及 Ordered Sets 数据类型操作。

  • 原子-Redis的所有操作都是原子性的,同时 Redis 还支持对几个操作前后的原子性执行。

  • 丰富的特性-Redis还支持 publish/subscribe,通知 key 过期等等特性。

  • 高速读写-Redis使用自己实现的分离器,代码量短,没有使用 Lock(锁MySQL),因此效率高。

  • 持久化 Redis 直接将数据存储到内存中,要将数据保存到磁盘上,Redis可以通过两种方式实现持久化。

    • 定时快照(snapshot):每隔一段时间将整个数据库内容写入到磁盘上,因为每次都是全部数据,代价比较高。

    • 基于语句追加(aof): 只追踪变化的数据,但是追加的log可能很大,同时所有的操作均重新执行一次,回复速度慢。

因为Redis所有操作都是原子性(要么都成功,要么都失败),同时还没有锁,所以 Redis 的每个命令都是线程安全的

Redis 优势

1.高性能:
假设下如果所有的数据都从数据库中读取,特别是一些复杂的数据,每次都查询 mysql 性能必定非常差。所以对于一些复杂操作耗时查出来的结果且后面不怎么变化的数据放到缓存,能大幅提高系统响应。

2. 高并发:

mysql单机一般只能支撑到2000Qps,而Redis由于是K/V模式的操作,单机可以支撑并发量可达到几万到几十万。

Redis 缺点

1.数据库容量受到物理内存的限制,不能用作海量数据的高性读写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。

2. Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的 IP 才能恢复。

3. 主机宕机,宕机前有部分数据未能及时同步到从机,切换 IP 后还会引入数据不一致的问题,降低了系统的可用性。

4. Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。

Redis应用场景

根据业内项目的实战经验及企业开发中总结的应用场景:

  • 缓存

  • 秒杀

  • 计数器

  • 排行榜

  • 热点数据(经常查询,但是不经常修改或者删除的数据)

  • 分布式锁

  • 分布式ID

  • 消息系统

  • 等等大部分与系统性能密切相关的场景

Redis安装

首先各位要知道 Redis 本身就没有针对windows系统进行开发,所以大家如果能下载到的win版本的Redis其实都是微软自行研发的,我们正常使用也都是基于 Linux,所以整体安装我们都是针对 Linux 来进行的。所以首先需要准备一台虚拟机系统采用:

CentOS7即可

官网地址: Redis - The Real-time Data Platform

官网下载地址:Downloads - Redis

https://download.redis.io/releases/

我们采用最新版本安装

在这可以直接点击下载 Download 7.0.11, 下载之后启动虚拟机,通过远程工具连接然后将下载好的Redis上传到虚拟机中进行安装即可,这是整体方式,但是要注意首先我们需要先安装GCC依赖

GCC依赖

Redis是基于C语言编写的,需要此依赖对 Redis 源码进行编译

yum install -y gcc tcl
apt install -y gcc tcl make make-guile


apt install -y make 
apt install -y make-guile 
apt install -y gcc tcl

具体步骤

1, 上传 redis-7.0.11.tar.gz到虚拟机opt目录

root@redis:/opt# ls
redis-7.0.11.tar.gz
root@redis:/opt# ll
total 2928
drwxr-xr-x  2 root root    4096 Jun 24 08:55 ./
drwxr-xr-x 20 root root    4096 Jun 24 06:01 ../
-rw-r--r--  1 root root 2988485 Jun 24 07:08 redis-7.0.11.tar.gz
root@redis:/opt#

2, 解压此文件

tar -zxvf redis-7.0.11.tar.gz

root@redis:/opt# tar -zxvf redis-7.0.11.tar.gz
root@redis:/opt# ls
redis-7.0.11  redis-7.0.11.tar.gz
root@redis:/opt# ll
total 2932
drwxr-xr-x  3 root root    4096 Jun 24 09:09 ./
drwxr-xr-x 20 root root    4096 Jun 24 06:01 ../
drwxrwxr-x  8 root root    4096 Apr 17  2023 redis-7.0.11/
-rw-r--r--  1 root root 2988485 Jun 24 07:08 redis-7.0.11.tar.gz
root@redis:/opt#

3.进入到安装目录

root@redis:~# cd /opt
root@redis:/opt# ls
redis-7.0.11  redis-7.0.11.tar.gz
root@redis:/opt# cd redis-7.0.11/
root@redis:/opt/redis-7.0.11# ls
00-RELEASENOTES  CODE_OF_CONDUCT.md  COPYING  INSTALL   MANIFESTO  redis.conf  runtest-cluster    runtest-sentinel  sentinel.conf  tests   utils
BUGS             CONTRIBUTING.md     deps     Makefile  README.md  runtest     runtest-moduleapi  SECURITY.md       src            TLS.md
root@redis:/opt/redis-7.0.11# ll
total 292
drwxrwxr-x  8 root root   4096 Apr 17  2023 ./
drwxr-xr-x  3 root root   4096 Jun 24 09:09 ../
-rw-rw-r--  1 root root  43972 Apr 17  2023 00-RELEASENOTES
-rw-rw-r--  1 root root     51 Apr 17  2023 BUGS
-rw-rw-r--  1 root root   5027 Apr 17  2023 CODE_OF_CONDUCT.md
drwxrwxr-x  2 root root   4096 Apr 17  2023 .codespell/
-rw-rw-r--  1 root root   2634 Apr 17  2023 CONTRIBUTING.md
-rw-rw-r--  1 root root   1487 Apr 17  2023 COPYING
drwxrwxr-x  7 root root   4096 Apr 17  2023 deps/
-rw-rw-r--  1 root root    405 Apr 17  2023 .gitattributes
drwxrwxr-x  4 root root   4096 Apr 17  2023 .github/
-rw-rw-r--  1 root root    535 Apr 17  2023 .gitignore
-rw-rw-r--  1 root root     11 Apr 17  2023 INSTALL
-rw-rw-r--  1 root root    151 Apr 17  2023 Makefile
-rw-rw-r--  1 root root   6888 Apr 17  2023 MANIFESTO
-rw-rw-r--  1 root root  22441 Apr 17  2023 README.md
-rw-rw-r--  1 root root 106545 Apr 17  2023 redis.conf
-rwxrwxr-x  1 root root    279 Apr 17  2023 runtest*
-rwxrwxr-x  1 root root    283 Apr 17  2023 runtest-cluster*
-rwxrwxr-x  1 root root   1613 Apr 17  2023 runtest-moduleapi*
-rwxrwxr-x  1 root root    285 Apr 17  2023 runtest-sentinel*
-rw-rw-r--  1 root root   1695 Apr 17  2023 SECURITY.md
-rw-rw-r--  1 root root  14005 Apr 17  2023 sentinel.conf
drwxrwxr-x  4 root root   4096 Apr 17  2023 src/
drwxrwxr-x 11 root root   4096 Apr 17  2023 tests/
-rw-rw-r--  1 root root   3055 Apr 17  2023 TLS.md
drwxrwxr-x  8 root root   4096 Apr 17  2023 utils/

--------------------------------------

root@redis:/opt/redis-7.0.11# ll
total 300
drwxrwxr-x  8 root root   4096 Apr 17  2023 ./
drwxr-xr-x  3 root root   4096 Jun 24 09:09 ../
-rw-rw-r--  1 root root  43972 Apr 17  2023 00-RELEASENOTES
-rw-rw-r--  1 root root     51 Apr 17  2023 BUGS
-rw-rw-r--  1 root root   5027 Apr 17  2023 CODE_OF_CONDUCT.md
drwxrwxr-x  2 root root   4096 Apr 17  2023 .codespell/
-rw-rw-r--  1 root root   2634 Apr 17  2023 CONTRIBUTING.md
-rw-rw-r--  1 root root   1487 Apr 17  2023 COPYING
drwxrwxr-x  7 root root   4096 Jun 24 09:33 deps/
-rw-rw-r--  1 root root    405 Apr 17  2023 .gitattributes
drwxrwxr-x  4 root root   4096 Apr 17  2023 .github/
-rw-rw-r--  1 root root    535 Apr 17  2023 .gitignore
-rw-rw-r--  1 root root     11 Apr 17  2023 INSTALL
-rw-rw-r--  1 root root    151 Apr 17  2023 Makefile
-rw-rw-r--  1 root root   6888 Apr 17  2023 MANIFESTO
-rw-rw-r--  1 root root  22441 Apr 17  2023 README.md
-rw-rw-r--  1 root root 106545 Apr 17  2023 redis.conf
-rwxrwxr-x  1 root root    279 Apr 17  2023 runtest*
-rwxrwxr-x  1 root root    283 Apr 17  2023 runtest-cluster*
-rwxrwxr-x  1 root root   1613 Apr 17  2023 runtest-moduleapi*
-rwxrwxr-x  1 root root    285 Apr 17  2023 runtest-sentinel*
-rw-rw-r--  1 root root   1695 Apr 17  2023 SECURITY.md
-rw-rw-r--  1 root root  14005 Apr 17  2023 sentinel.conf
drwxrwxr-x  4 root root  12288 Jun 24 09:34 src/
drwxrwxr-x 11 root root   4096 Apr 17  2023 tests/
-rw-rw-r--  1 root root   3055 Apr 17  2023 TLS.md
drwxrwxr-x  8 root root   4096 Apr 17  2023 utils/

4, 编译且安装

make && make install

# 看到如下内容表示 redis 安装成功
Hint: It's a good idea to run 'make test' ;)

    INSTALL redis-server	# redis 服务端启动命令
    INSTALL redis-benchmark	# redis 测试压力命令
    INSTALL redis-cli		# redis 客户端命令
make[1]: Leaving directory '/opt/redis-7.0.11/src'


注意,redis在安装的时候就已经把环境变量设置好了,你可以在任意位置启动redis

5. 默认安装路径存在于:/usr/local/bin

Redis启动

Redis启动方式有多种:
1. 默认启动
2. 指定配置启动
3. 开机自启
首先我们进入到默认的Redis安装目录:/usr/local/bin

root@redis:/opt/redis-7.0.11# ll /usr/local/bin/
total 22084
drwxr-xr-x  2 root root  4096 Jun 24 09:34 ./
drwxr-xr-x 10 root root  4096 Feb 16 18:37 ../
-rwxr-xr-x  1 root root  5509096 Jun 24 09:34 redis-benchmark*
lrwxrwxrwx  1 root root 12 Jun 24 09:34 redis-check-aof -> redis-server*
lrwxrwxrwx  1 root root 12 Jun 24 09:34 redis-check-rdb -> redis-server*
-rwxr-xr-x  1 root root  5392000 Jun 24 09:34 redis-cli*
lrwxrwxrwx  1 root root  12 Jun 24 09:34 redis-sentinel -> redis-server*
-rwxr-xr-x  1 root root 11698248 Jun 24 09:34 redis-server*

此目录包含所有 redis 相关默认的配置,我们可以运行一下命令
redis-cil	# redis 提供的命令行客户端
redis-server	# redis 服务端启动脚本
redis-sentinel	# redis 哨兵启动脚本

默认启动
我们首先启动 redis 服务端,当然启动 redis 可以在任何目录位置都行,因为已经加入环境变量了,不一定非要进入安装目录
redis-server



# 看到如下图所示表示 redis 已经启动成功
root@redis:/opt/redis-7.0.11# redis-server
7377:C 24 Jun 2024 09:51:05.903 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
7377:C 24 Jun 2024 09:51:05.903 # Redis version=7.0.11, bits=64, commit=00000000, modified=0, pid=7377, just started
7377:C 24 Jun 2024 09:51:05.903 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
7377:M 24 Jun 2024 09:51:05.904 * Increased maximum number of open files to 10032 (it was originally set to 1024).
7377:M 24 Jun 2024 09:51:05.904 * monotonic clock: POSIX clock_gettime
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 7.0.11 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 7377
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           https://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

7377:M 24 Jun 2024 09:51:05.905 # Server initialized
7377:M 24 Jun 2024 09:51:05.905 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
7377:M 24 Jun 2024 09:51:05.905 * Ready to accept connections



以上是前台启动,退出 ctrl+c 就可以了
前台启动是它的一种默认启动方式,它会出现一个什么问题,它现在是一种前台启动方式,如果我们想要通过客户端来连接 redis 服务端,我们需要在此时重新开启一个窗口,再连接服务器主机,这就比较麻烦了。
当然此时这种启动方式为:前台启动,也就是说,我们如果此时想去连接 Redis 服务端,需要再开启一个窗口,此窗口状态一致保存,如果此时停止,Redis也就停止了,所以这种方式并不是很友好
指定配置启动
如果我们想要的方式是 redis 直接在这个窗口也可以启动,不影响我们正常去操作,然后再通过一个客户端来进行连接,这样的方式如何完成呢?
如果想要进行后台启动,我们就需要修改 Redis 配置,我们需要修改的文件为: redis.conf
在咱们的 Redis 解压目录中


进入默认的安装目录
root@redis:/opt/redis-7.0.11# cd /usr/local/bin/
root@redis:/usr/local/bin# ls
redis-benchmark  redis-check-aof  redis-check-rdb  redis-cli  redis-sentinel  redis-server
root@redis:/usr/local/bin# ll
total 22084
drwxr-xr-x  2 root root  4096 Jun 24 09:34 ./
drwxr-xr-x 10 root root  4096 Feb 16 18:37 ../
-rwxr-xr-x  1 root root  5509096 Jun 24 09:34 redis-benchmark*
lrwxrwxrwx  1 root root 12 Jun 24 09:34 redis-check-aof -> redis-server*
lrwxrwxrwx  1 root root 12 Jun 24 09:34 redis-check-rdb -> redis-server*
-rwxr-xr-x  1 root root  5392000 Jun 24 09:34 redis-cli*
lrwxrwxrwx  1 root root 12 Jun 24 09:34 redis-sentinel -> redis-server*
-rwxr-xr-x  1 root root 11698248 Jun 24 09:34 redis-server*
root@redis:/usr/local/bin#

注意:如果要修改配置我们一般情况下需要先备份

cp redis.conf redis.conf.bak
执行一下命令修改具体配置
vim redis.conf

root@redis:/opt/redis-7.0.11# cp redis.conf redis.conf.bak
root@redis:/opt/redis-7.0.11# ll
total 412
drwxrwxr-x  8 root root   4096 Jun 24 10:46 ./
drwxr-xr-x  3 root root   4096 Jun 24 09:09 ../
-rw-rw-r--  1 root root  43972 Apr 17  2023 00-RELEASENOTES
-rw-rw-r--  1 root root     51 Apr 17  2023 BUGS
-rw-rw-r--  1 root root   5027 Apr 17  2023 CODE_OF_CONDUCT.md
drwxrwxr-x  2 root root   4096 Apr 17  2023 .codespell/
-rw-rw-r--  1 root root   2634 Apr 17  2023 CONTRIBUTING.md
-rw-rw-r--  1 root root   1487 Apr 17  2023 COPYING
drwxrwxr-x  7 root root   4096 Jun 24 09:33 deps/
-rw-r--r--  1 root root     89 Jun 24 09:56 dump.rdb
-rw-rw-r--  1 root root    405 Apr 17  2023 .gitattributes
drwxrwxr-x  4 root root   4096 Apr 17  2023 .github/
-rw-rw-r--  1 root root    535 Apr 17  2023 .gitignore
-rw-rw-r--  1 root root     11 Apr 17  2023 INSTALL
-rw-rw-r--  1 root root    151 Apr 17  2023 Makefile
-rw-rw-r--  1 root root   6888 Apr 17  2023 MANIFESTO
-rw-rw-r--  1 root root  22441 Apr 17  2023 README.md
-rw-rw-r--  1 root root 106545 Apr 17  2023 redis.conf
-rw-r--r--  1 root root 106545 Jun 24 10:46 redis.conf.bak
-rwxrwxr-x  1 root root    279 Apr 17  2023 runtest*
-rwxrwxr-x  1 root root    283 Apr 17  2023 runtest-cluster*
-rwxrwxr-x  1 root root   1613 Apr 17  2023 runtest-moduleapi*
-rwxrwxr-x  1 root root    285 Apr 17  2023 runtest-sentinel*
-rw-rw-r--  1 root root   1695 Apr 17  2023 SECURITY.md
-rw-rw-r--  1 root root  14005 Apr 17  2023 sentinel.conf
drwxrwxr-x  4 root root  12288 Jun 24 09:34 src/
drwxrwxr-x 11 root root   4096 Apr 17  2023 tests/
-rw-rw-r--  1 root root   3055 Apr 17  2023 TLS.md
drwxrwxr-x  8 root root   4096 Apr 17  2023 utils/

配置具体修改

# 监听地址,默认为127.0.0.1,所以只能本地访问,我们可以修改为 0.0.0.0,表示任意IP访问,但是注意,此设置仅用于测试 bind 0.0.0.0
参数说明
'0.0.0.0' 表示任意 IP 的访问

# 将守护进程,从默认的关闭改为开启,即为后台运行
daemonize yes

# 设置 redis 访问密码,任意访问,但是需要密码
requirepass 753159

# -----------常见配置(按情况自行调整,建议增加日志)----------------
# 监听的端口
port	6379

# 工作空间,默认是当前目录,运行redis-server时的命令,日志,持久化等文件会保存在这个目录
dir ./

# 数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15(MySQL可以自己创建 Redis 不需要但是可以指定数量)
databases	16

# 设置 redis 能够使用的最大内存
maxmemory 512MB

# 日志文件,默认不记录日志,可以指定日志文件名来开启记录日志
logfile	"redis.log"

# 将 'protected yes' 修改为 'protected no'


启动测试
此时的启动我们需要带着配置文件进行,我们此时正好在 redis 配置文件所在的路径中,所以可以不写全路径,直接启动带上配置文件即可 ,若在其他目录,需要写配置文件全路径(绝对路径)
redis-server redis.conf

root@redis:/opt/redis-7.0.11# redis-server redis.conf
root@redis:/opt/redis-7.0.11# ps -ef | grep redis
root   7449       1  0 12:20 ?        00:00:00 redis-server 0.0.0.0:6379
root   7455    1292  0 12:20 pts/0    00:00:00 grep --color=auto redis
root@redis:/opt/redis-7.0.11#


此时变成后台启动,所以没有日志,我们可以通过以下命令查看启动情况,当然停止的话直接杀死进程即可
ps -ef | grep redis # 查看 redis 运行的进程号
kill -9 xxxx	# 杀死进程

开启自启动

Redis最简单的启动方式就是开机自启动,就好像 MySQL那样,通过系统服务来进行自启动,Redis 也可以做到。

1. 新建系统服务

vim /etc/systemd/system/redis.service


具体内容如下:

[Unit]
Description=redis-server
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /opt/redis-7.0.11/redis.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target

2. 重载系统服务

systemctl daemon-reload

3. 执行此命令让 redis 服务开启开机自启动

systemctl enable redis

4. 通过一下命令操作 redis 系统服务

启动

systemctl start redis
systemctl restart redis

root@redis:/opt/redis-7.0.11# vim /etc/systemd/system/redis.service
root@redis:/opt/redis-7.0.11# systemctl daemon-reload
root@redis:/opt/redis-7.0.11# systemctl start redis
root@redis:/opt/redis-7.0.11# systemctl enable redis
Created symlink /etc/systemd/system/multi-user.target.wants/redis.service → /etc/systemd/system/redis.service.
root@redis:/opt/redis-7.0.11#

Redis客户端访问

和MySQL一样,Redis自带一个命令行客户端: redis-cli,具体使用方式如下
redis-cli [options]

options 代表具体的一些选项,比如常见的如下:

1 -h 127.0.0.1: 指定要连接的redis节点 ip 地址,默认是本机
2 -p 6379: 指定要连接的redis节点的端口
3 -a 753159: 指定redis访问密码

redis-cli -p 6379 -a 123456
redis-cli -p 6378 -a 123456
root@redis:~# redis-cli -p 6379 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379>


ping命令
ping命令使用客户端向Redis服务器发送一个PING,如果服务器运作正常的话,会返回一个PONG.
通常用于测试与服务器的连接是否仍然生效,或者用于测量延迟值。
我们可以通过这样的命令来进行测试:

127.0.0.1:6379> ping
PONG
127.0.0.1:6379>set name tom		# redis存储数据
ok
127.0.0.1:6379>get name			# redia 取出数据
"tom"
127.0.0.1:6378> dbsize			# redis 查看数据库大小
(integer) 1
127.0.0.1:6379> set name jerry
OK
127.0.0.1:6379> get name		
"jerry"
127.0.0.1:6379> dbsize			# 当key(name)值相同时,后面的key值会覆盖前面的值
(integer) 1
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> get name
"jerry"
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> DBSIZE
127.0.0.1:6378> keys *		# 查看所有的key
1) "age"
2) "name"
(integer) 2
127.0.0.1:6379> flushdb			# 清空数据
OK
127.0.0.1:6379> DBSIZE			# 查看数据大小
(integer) 0
127.0.0.1:6379> select 0		# 切换数据库(select index[0-15])
OK
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> select 5
OK
127.0.0.1:6378> TYPE name		#	返回 key 所存储的 value 的数据结构类型
string
127.0.0.1:6378> DEL [name age]		# 删除指定的一批 keys
(integer) 0
127.0.0.1:6378> set name kerry
OK
127.0.0.1:6378> set age 18
OK
127.0.0.1:6378> exists age		# 判断当前 key 是否存在
(integer) 1
127.0.0.1:6378> exists tom
(integer) 0
127.0.0.1:6378> flushall
OK
127.0.0.1:6378> get name
(nil)
127.0.0.1:6378> get age
(nil)
127.0.0.1:6379> set hot aaa		# 设置热点
OK
127.0.0.1:6379> get hot			# 获取热点
"aaa"
127.0.0.1:6379> expire hot 10	# 设置热点过期时间
(integer) 1
127.0.0.1:6379> ttl hot			# 返回 key 剩余的过期时间
(integer) -2	# 如果 key 不存在或者已过期,返回-2 
127.0.0.1:6379> get hot			# 再次查看热点值已经不存在了
(nil)
127.0.0.1:6379> ttl age	
(integer) -1	# 如果 key 存在并且没有设置过期时间 (永久有效),返回 -1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> MOVE age 1	# 把当前数据库中的 'age' 移动到 编号为1的数据库
(integer) 1
127.0.0.1:6379> keys *		# 查看当前数据库中的key为 'age' 发现已经没有age了
(empty array)
127.0.0.1:6379> select 1	# 	切换到编号为1的数据库
OK
# 查看该数据库中是否有 key 为 age 的数据 有如下三种方式可以查看
127.0.0.1:6379[1]> get age	
"18"
127.0.0.1:6379[1]> keys *
1) "age"
127.0.0.1:6379[1]> exists age	
(integer) 1
127.0.0.1:6379[1]> TYPE age
string		# redis 默认采用 string 来存储数据
127.0.0.1:6379[1]> set phone 123
OK
127.0.0.1:6379[1]> keys *
1) "age"
2) "phone"
127.0.0.1:6379[1]> set key qf
OK
127.0.0.1:6379[1]> keys *
1) "age"
2) "key"
3) "phone"
127.0.0.1:6379[1]> del key		# 删除 key
(integer) 1
127.0.0.1:6379[1]> keys *
1) "age"
2) "phone"
127.0.0.1:6379[1]> del age phone	# 删除多个key,各个key用空格分开就可以了
(integer) 2
127.0.0.1:6379[1]> keys *
(empty array)








注意:Redis默认从16个数据库,默认使用第0个
可以通过Select进行切换,当然我们还可以查看当前库大小
select 0	# 切换数据库(index[0-15]) index最大值为15
dbsize		# 查看数据库大小
flushdb		# 清除当前数据库数据
flushall	# 清除全部数据(flushall)

当然因为它是 key:value 结构的,我们还可以直接通过 set 与 get 来添加数据,当然还有一些基础命令如下:

set name qf		# 存入数据
get qf			# 取出数据
keys * 			# 查看所有的key [keys pattern]
flushall		# 清除全部数据
flushdb			# 清空当前数据

Redis认知补充

Redis快的真正原因
1 内存存储: Redis 将所有数据保存在内存中,内存读写速度非常快,远快于硬盘。这也是 redis 最主要的性能优势。
2 数据结果简单:redis 支持的数据结构比较简单(例如字符串,哈希,列表,集合和有序集合),这也在一定程度是提高了其效率。
3 非阻塞 IO:redis 使用单线程和非阻塞I/O多路复用库处理并发连接。在处理大量并发连接时,这种模型比多线程和阻塞 I/O 的模型要高效
4 持久化策略:redis 提供了多种灵活的数据持久化方式,包括RDB 快照和AOF 日志。用户可以根据需求选择最合适的持久化策略,以平衡性能和数据安全之间的关系。
5 优化的数据结构:redis 为了高效地实现其命令,使用了许多优化的数据结构和算法,例如 快速列表,跳跃列表和压缩列表

总结:因此,redis 可以处理大量的读写操作,特别是在需要高速缓存,会话缓存,队列和发布/订阅等高性能场景中,redis的性能表现非常出色。

Redis 是单线程的吗?

Redis 在处理命令请求时是单线程的,也就是说在任意时刻,redis只会使用一个CPU核心。redis 的作者 Antirez(Salvatore Sanfilippo)设计redis时,就选择了这种单线程模型,因为在很多场景下,单线程能够更好地避免竞态条件和复杂的同步问题,简化了系统的设计和运行机制。
然而,这并不意味着 redis 的所有操作都是单线程的。例如,redis 的某些后台操作,如持久化,数据清理和复制等,是由单独的线程完成的。另外,从redis4.0开始,某些耗时的操作,如删除大key 和 LRU 过期等,也可以由后台线程异步处理。
所以,在理解 redis 是单线程的时候,需要理解这是指 redis 主线程处理网络请求和执行命令是单线程的。这种设计充分利用了CPU 和内存的高速性能,避免了多线程之间的上下文切换和资源竞争,保证了极高的执行效率。

Redis 的数据类型

其实最经典就是五大数据类型,具体如下:

当然因为 redis 是一个 key-value 的数据库,key 一般是String类型
  • String
  • List
  • Set
  • Hash
  • SortedSet(Zset)
当然除了这些以外还有一些特殊的类型,总体凑起来目前应该有10个数据类型,所以我们先从主要的数据类型开始
这里说一下,如果想详细学习每个类型具体的一些命令和使用,我们其实可以参照官网:
https://redis.io/docs/data-types/tutorial/#keys
https://redis.io/docs/latest/#keys
VScode编辑工具官网:
https://code.visualstudio.com/

Rediskey的基本命令

  • keys * # 查看所有的 key

  • Exists key {key...} # 判断当前 key 是否存在

  • ExPIRE key seconds 设置 key 的过期时间,超过时间后,将会自动删除该 key,单位是秒。(热点数据)

  • TTL key 返回 key 剩余的过期时间。这种反射能力允许 Redis 客户端检查指定 key 在数据集里面的有效期。

    • 如果 key 不存在或者已过期,返回-2

    • 如果 key 存在并且没有设置过期时间 (永久有效),返回 -1.

  • Move key db 将当前数据库的 key 移动到给定的数据库 db 当中。如果当前数据库(源数据库)和给定数据库(目标数据库)有相同名字的给定 key,或者 key 不存在于当前数据库,那么 MOVE 没有任何效果

  • TYPE key 返回 key 所存储的 value 的数据结构类型。

  • DEL key [key...] 删除指定的一批 keys,如果删除中的某些 key 不存在,则直接忽略

  • flushall清除全部库数据

  • flushdb 清空当前库数据

Redis数据类型-String

String 类型可以是说Redis最基本也是最常用的数据类型了,一个key对应一个value.

String 类型基本上可以包含任何数据,比如图片或者序列化的对象等。

一个Redis中的字符串 value 最多可以是512M

String的常见命令:

  • SET: 添加或者修改已经存在的一个String类型的键值对(如果key不存在则是新增,如果存在则是修改)

  • GET: 根据key获取String类型的value

  • GETSET: 先获取在设置,如果get时值不存在返回空,然后在进行设置,如果get时值存在,返回对应的值,在进行修改

  • APPEND: Append命令用于为指定的 key追加值,如果key已经存在并且是一个字符串,APPEND命令将value值追加到key原来的值的末尾,如果key不存在,APPEND就简单地将给定key设为value,就像执行SET key value一样

  • MSET:批量添加多个String类型的键值对

  • MGET : 根据多个key获取多个String类型的value

  • MSETNX: 命令用于所有给定key都不存在时,同时设置一个或多个key-value对。(原子性操作)

  • INCR: 命令将key中存储的数字值增一。如果key不存在,那么key的值会先被初始化为0,然后再执行INCR操作。如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。本操作的值限制在64位(bit)有符号数字表示至内。

  • INCRBY: 让一个整型的值自增并指定步长 (例如:num5让num值自增5,其余与INCR一致)

  • DECR: 命令将key中存储的数字值减一(与INCR命令相反,其余一致)

  • DECRBY: 让一个整型的值自增并指定步长(与INCRBY相反,其余一致)。

  • SETNX: 添加一个String类型的键值对,前提是这个key不存在,否则不执行(与MSETNX区别在于,此命令只能设置一组key-value)

  • SETEX: 命令为指定的key设置值及其过期时间。如果key已经存在,SETEX命令将会替换旧的值

注意:mset与msetnx区别

在Redis中,SET和SETNX命令用于设置字符串类型的键值,但他们在处理键已经存在的情况下的行为是不同的。当然MSET与MSETNX与之同理,只是添加MSET与MSETNX可以批量添加数据

1. SET命令: 如果键已经存在,SET会直接覆盖旧的值,无论键是否存在,都将设置新值。例如: SET mykey "hello"

2. SETNX命令: SETNX表示 "SET if Not exist",也就是只有在键不存在的情况下,才会设置新的值。如果键已经存在,SETNX将不做任何操作。例如: SETNX mykey "hello"

SETNX命令常常被用于实现锁和其他需要保证原子性的操作。例如,你可以使用SETNX来实现一个分布式锁,只有第一个请求的客户端可以获取锁(设置成功),后来的客户端因为键已经存在,所以设置失败,从而实现锁的功能。

总的来说。SET和SETNX的主要区别在于他们任何处理键已经存在的情况

常用命令实践

[root@mail ~]# redis-cli -p 6378 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
# getset 先获取在设置,如果get时值不存在返回空,然后在进行设置,如果get时值存在,返回对应的值,在进行修改
127.0.0.1:6378> set age 18
OK
127.0.0.1:6378> get age
"18"
127.0.0.1:6378> getset age 20
"18"
127.0.0.1:6378> get age
"20"
127.0.0.1:6378> getset key1 qf
(nil)
127.0.0.1:6378> get key1
"qf"
# APPEND: Append命令用于为指定的 key追加值,如果key已经存在并且是一个字符串,APPEND命令将value值追加到key原来的值的末尾,如果key不存在,APPEND就简单地将给定key设为value,就像执行SET key value一样
127.0.0.1:6378> APPEND key1 java
(integer) 6
127.0.0.1:6378> get key1
"qfjava"
127.0.0.1:6378> APPEND key2 php
(integer) 3
127.0.0.1:6378> get key2
"php"
127.0.0.1:6378> flushdb			清空当前库中数据
OK
127.0.0.1:6378> keys *
(empty array)
# MSET:批量添加多个String类型的键值对
127.0.0.1:6378> MSET k1 a1 k2 a2 k3 a3 k4 a4
OK
127.0.0.1:6378> mset user:1:name mask user:1:age 18	# 存储对象数据
OK
# MGET :  根据多个key获取多个String类型的value
127.0.0.1:6378> mget k1 k2 k3 k4
1) "a1"
2) "a2"
3) "a3"
4) "a4"
127.0.0.1:6378> keys *
1) "k2"
2) "k3"
3) "k1"
4) "k4"
127.0.0.1:6378> mget user:1:name user:1:age
1) "mask"
2) "18"
# MSETNX: 命令用于所有给定key都不存在时,同时设置一个或多个key-value对。(原子性操作)
127.0.0.1:6378> msetnx key1 k1 key2 k2
(integer) 1
127.0.0.1:6378> mget key1 key2
1) "k1"
2) "k2"
127.0.0.1:6378> keys *
1) "k2"
2) "user:1:name"
3) "k1"
4) "k3"
5) "key1"
6) "user:1:age"
7) "key2"
8) "k4"
127.0.0.1:6378> MSETNX key1 aaa key3 bbb	# 有一个key不存在,整体就都不生效
(integer) 0		# 原子性操作,要么全部成功,要么全部失败
127.0.0.1:6378> keys *
1) "k2"
2) "user:1:name"
3) "k1"
4) "k3"
5) "key1"
6) "user:1:age"
7) "key2"
8) "k4"
127.0.0.1:6378>


# INCR: 命令将key中存储的数字值增一。如果key不存在,那么key的值会先被初始化为0,然后再执行INCR操作。如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。本操作的值限制在64位(bit)有符号数字表示至内。一般使用场景做计算使用
127.0.0.1:6378> set num 1
OK
127.0.0.1:6378> get num
"1"
127.0.0.1:6378> incr num
(integer) 2
127.0.0.1:6378> get num
"2"
127.0.0.1:6378> incr num
(integer) 3
127.0.0.1:6378> get num
"3"
127.0.0.1:6378> incr num1
(integer) 1
127.0.0.1:6378> get num1
"1"
127.0.0.1:6378> incr num1
(integer) 2
127.0.0.1:6378> get num1
"2"


# INCRBY: 让一个整型的值自增并指定步长 (例如:num5让num值自增5,其余与INCR一致)
127.0.0.1:6378> incrby num 3	# 指定增长步长为3
(integer) 6
127.0.0.1:6378> get num
"6"
127.0.0.1:6378> incrby num 3
(integer) 9
127.0.0.1:6378> get num
"9"


# DECR: 命令将key中存储的数字值减一(与INCR命令相反,其余一致)
127.0.0.1:6378> decr num	# 递减1
(integer) 8
127.0.0.1:6378> decr num
(integer) 7
127.0.0.1:6378> decr num
(integer) 6


# DECRBY: 让一个整型的值自增并指定步长(与INCRBY相反,其余一致)。
127.0.0.1:6378> decrby num 2   # 指定递减步长
(integer) 4
127.0.0.1:6378> decrby num 2
(integer) 2
127.0.0.1:6378> get num
"2"
127.0.0.1:6378> decrby num 2
(integer) 0
127.0.0.1:6378> get num
"0"

# SETNX: 添加一个String类型的键值对,前提是这个key不存在,否则不执行(与MSETNX区别在于,此命令只能设置一组key-value)也是原子性操作,每次只能设置一组
127.0.0.1:6378> keys *
 1) "num1"
 2) "num"
 3) "user:1:name"
 4) "k2"
 5) "k3"
 6) "k1"
 7) "key1"
 8) "k4"
 9) "key2"
10) "num2"
11) "user:1:age"
127.0.0.1:6378> SETNX k1 111
(integer) 0
127.0.0.1:6378> get k1
"a1"
127.0.0.1:6378> SETNX k5 wwww
(integer) 1
127.0.0.1:6378> get k5
"wwww"


# SETEX:  命令为指定的key设置值及其过期时间。如果key已经存在,SETEX命令将会替换旧的值	语法:SETEX KEY SECONDS VALUE # 设置过期后的值
127.0.0.1:6378> set k6 a6
OK
127.0.0.1:6378> get k6
"a6"
127.0.0.1:6378> setex k6 20 redis	# 设置20秒后的值为redis
OK
127.0.0.1:6378> get k6
"redis"
127.0.0.1:6378> setex k5 60 java
OK
127.0.0.1:6378> ttl k5
(integer) 53
127.0.0.1:6378> ttl k5
(integer) 46
127.0.0.1:6378> ttl k5
(integer) 38
127.0.0.1:6378> ttl k5
(integer) 24
127.0.0.1:6378> ttl k5
(integer) 13
127.0.0.1:6378> ttl k5
(integer) -2
127.0.0.1:6378> get k5
(nil)
127.0.0.1:6378> setex k9 10 php
OK
127.0.0.1:6378> get k9	# 没有超过10秒内key的值为php
"php"
127.0.0.1:6378> get k9	# 超过10秒key的值为nil
(nil)

除了以上这些 String 还有一些类似于Java字符串操作的命令,比如截取字符串等,具体可以参照官网

其实String的使用场景很多,比如

  • 计数器

  • 统计多单位数量

  • 粉丝数

  • 对象缓存储存

Redis 数据类型 List

  • List 类型是一种有序集合,也是Redis是集成数据结构之一。List类型内部实现为一个双向链表,即每个节点都有两个指针,一个指向前一个节点,一个指向后一个节点。
  • List 功能非常强大,我们即可以把它当成栈(先进后出),队列(先进先出),并且可以存放重复的值。
  • List 甚至可以使用负数下标,以-1 表示列表的最后一个元素,-2表示列表的倒数第二个元素,以此类推。

常用命令

  • LPUSH key value1 value2 将一个或多个值插入到列表头部(左侧)

  • LRANGE key start stop 获取列表指定范围内的元素,根据下标

  • RPUSH key value1 value2 在列表中(右侧)添加一个或多个值

  • LPOP key 移除并获取列表的第一个元素

  • RPOP key 移除并获取列表最后一个元素

  • LINDEX key index 通过索引获取列表中的元素

  • LLEN key 获取列表长度

  • LREM key count value 移除列表元素(可以移除多个重复的值)

  • LTRIM key start stop 对一个列表进行修剪(trim),就是说,通过下标让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

  • RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回

  • EXISTS也可以判断列表中是否有值(exists list)

  • LSET key index value 通过索引设置列表元素的值(需要保证现有列表和下标存在,一般作为更新操作)

  • LINSERT key BEFORE|AFTER pivot value 在列表的元素前或后插入元素

  • BLPOP key1 key2 timeout 移出并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

命令实践

# LPUSH key value1 value2 将一个或多个值插入到列表头部(左侧)
# 语法:LPUSH key element[element...] 列表就是key,[element...]表示可以插入多
# 个元素,L代表 LEFT,LPUSH表示从最左边开始插入 实例如下:
127.0.0.1:6378> LPUSH list one two three
(integer) 3
127.0.0.1:6378> lpush list 1
(integer) 4
127.0.0.1:6378> lpush list 2
(integer) 5
127.0.0.1:6378> LRANGE list 0 -1	
1) "2"
2) "1"
3) "three"
4) "two"
5) "one"




# LRANGE key start stop 获取列表指定范围内的元素,根据下标
# '0'代表start即插入的第一个元素,'-1'代表stop即最后一个查如的元素
# 语法:LRANGE key start stop
127.0.0.1:6378> LRANGE list 0 -1
1) "2"
2) "1"
3) "three"
4) "two"
5) "one"
6) "four"


# RPUSH key value1 value2 在列表中(右侧)添加一个或多个值
# RPUSH key element [element...] 从最后一个元素开始插入
127.0.0.1:6378> RPUSH list four
(integer) 6
127.0.0.1:6378> RPUSH list five
(integer) 7
127.0.0.1:6378> RPUSH list six
(integer) 8
127.0.0.1:6378> LRANGE list 0 -1
1) "2"
2) "1"
3) "three"
4) "two"
5) "one"
6) "four"
7) "five"
8) "six"
127.0.0.1:6378> RPUSH list seven eight nine
(integer) 11
127.0.0.1:6378> LRANGE list 0 -1
 1) "2"
 2) "1"
 3) "three"
 4) "two"
 5) "one"
 6) "four"
 7) "five"
 8) "six"
 9) "seven"
10) "eight"
11) "nine"


# LPOP key 移除并获取列表的第一个元素
# 语法:LPOP key [count]
127.0.0.1:6378> LPOP list 1
1) "2"


# RPOP key 移除并获取列表最后一个元素
# 语法:RPOP key [count]
127.0.0.1:6378>RPOP list 1
"four"

# LINDEX key index 通过索引获取列表中的元素
# 语法:LINDEX key index 通过'index'索引下标可以找出列表中的元素
127.0.0.1:6378> LPUSH list one two three four	# 从左侧插入4个元素
(integer) 4
127.0.0.1:6378> LRANGE list 0 -1	# 查看列表中所有元素
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6378> LINDEX list 1	# 查找索引下标为1的元素
"three"



# LLEN key 获取列表长度
127.0.0.1:6378> LLEN list		# 获取列表长度
(integer) 4

# LREM key count value 移除列表元素(可以移除多个重复的值)
# 语法: LREM key count element 
127.0.0.1:6378> LPUSH list two	# 往列表中添加一个元素
(integer) 5
127.0.0.1:6378> lrange list 0 -1	# 再次查看列表中所有元素
1) "two"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6378> LREM list 1 two	# 删除一个 two 元素
(integer) 1
127.0.0.1:6378> lrange list 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6378>




# LTRIM key start stop 对一个列表进行修剪(trim),就是说,通过下标让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
127.0.0.1:6378> lrange list 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6378> LTRIM list 1 3
OK
127.0.0.1:6378> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6378>


# RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回,这个重点掌握,现实工作中有很多用处,比如把购物车中商品移动到支付列表中等
127.0.0.1:6378> RPUSH qlist 1 2 3 4
(integer) 4
127.0.0.1:6378> lrange qlist 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
# 从右边开始移除 'qlist'中的数据,然后再把移动的元素添加到 'flist' 列表中
127.0.0.1:6378> RPOPLPUSH qlist flist  
"4"
127.0.0.1:6378> lrange flist 0 -1
1) "4"
127.0.0.1:6378> lrange qlist 0 -1
1) "1"
2) "2"
3) "3"





# EXISTS也可以判断列表中是否有值(exists list) 
127.0.0.1:6378> exists flist
(integer) 1						# 返回 1 表示有数据
127.0.0.1:6378> exists qlist
(integer) 1
127.0.0.1:6378> lpop flist 1
1) "4"
127.0.0.1:6378> lrange flist 0 -1
(empty array)
127.0.0.1:6378> exists flist	# 返回 0 表示没有数据
(integer) 0
127.0.0.1:6378> flushdb			# 清空当前数据库中的数据
OK




# LSET key index value 通过索引设置列表元素的值(需要保证现有列表和下标存在,一般作为更新操作)
# 语法:LSET key index element
# 列表和下标都不存在,无法做更新操作,所以报错
127.0.0.1:6378> LSET mylist 0 one	
(error) ERR no such key
127.0.0.1:6378> rpush mylist one two three	# 在列表中添加数据
(integer) 3
127.0.0.1:6378> lrange mylist 0 -1
1) "one"
2) "two"
3) "three"
# 因为列表中有数据,所以再次执行 'lset mylist 0 one' 就不报错了
127.0.0.1:6378> lset mylist 0 one  
OK
127.0.0.1:6378> lrange mylist 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6378> lset mylist 2 hello	# 更新元素操作
OK
127.0.0.1:6378> lrange mylist 0 -1
1) "one"
2) "two"
3) "hello"



# LINSERT key BEFORE|AFTER pivot value 在列表的元素前或后插入元素
# 语法: LINSERT key BEFORE | AFTER PIVOT(核心,支撑点) element
127.0.0.1:6378> LINSERT mylist before hello af # 在 hello 之前插入 af
(integer) 4
127.0.0.1:6378> lrange mylist 0 -1  #查看列表信息
1) "one"
2) "two"
3) "af"
4) "hello"
127.0.0.1:6378> LINSERT mylist after two jack # 在two之后插入 'jack'
(integer) 5
127.0.0.1:6378> lrange mylist 0 -1  查看列表信息
1) "one"
2) "two"
3) "jack"
4) "af"
5) "hello"



# BLPOP key1 key2 timeout 移出并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
# 语法:BLPOP key [key...] timeout
127.0.0.1:6378> LRANGE list 0 -1
(empty array)
127.0.0.1:6378> BLPOP list 10
(nil)		# 没有元素发生了阻塞,超过10秒拿到一个 nil 空值
(10.06s)
127.0.0.1:6378> BRPOP list 10
(nil)
(10.02s)
127.0.0.1:6378> lpop list 1
(nil)		# 直接取出元素,这就是不阻塞的效果
127.0.0.1:6378>

List小结

1. 我们可以把list看成一个链表,前后中间都可以插入数据

2. 如果key不存在。则创建新的 list (链表)

3. 如果 key存在,新增或者修改内容

4. 如果某一个list中没有任何数据,表示不存在(空链表)

5. 既然类似于链表,两边数据的插入和改动效率高,中间效率稍低

6. 常用功能:消息队列,消息排队(lpush rpop)栈(lpush lpop)

Redis数据类型-set

Redis的set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据(无序不重复)

常见命令

  • SADD key member1[member2] 向集合添加一个或多个成员

  • SMEMBERS key 返回集合中的所有成员

  • SISMEMBER key member 判断 member 元素是否是集合 key 的成员

  • SCARD key 获取集合的成员数

  • SREM key member1[member2] 移除集合中一个或多个成员

  • SRANDMEMBER key [count] 返回集合中一个或多个随机数

  • spop key 移除并返回集合中的一个随机元素(原子操作,多个客户端并发访问时,每个返回的元素都是唯一的)

  • SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合

  • SDIFF key1 key2返回给定所有集合的差集(两个set中不同的值)

  • SINTER key1 key2返回给定所有集合的交集(两个set中相同的值)

  • SUNION key1 key2返回所有给定集合的并集(两个set中所有的值,重复的只有一个)

    • SDIFF,SINTER,SUNION这些命令就可以完成 共同好友,共同爱好等类似的功能,比如将用户的关注的人关注放入到 set中,即可完成"共同关注"功能

  • SDIFFSTORE destination key1 key2返回给定所有集合的差集并存储在 destination中

  • SINTERSTORE destination key1 key2返回给定所有集合的交集并存储在 destination 中

  • SUNIONSTORE destination key1 key2所有给定集合的并集存储在destination中

由于Set类型的特性,他经常被用来实现标签系统,好友关系,粉丝关系等功能。

常见命令使用

# SADD key member1[member2] 向集合添加一个或多个成员
127.0.0.1:6378> flushall	清空数据
OK
127.0.0.1:6378> SADD myset aaa bbb ccc	# 添加元素
(integer) 3


# SMEMBERS key 返回集合中的所有成员
127.0.0.1:6378> smembers myset	# 查看添加的元素
1) "ccc"
2) "aaa"
3) "bbb"


# SISMEMBER key member 判断 member 元素是否是集合 key 的成员
127.0.0.1:6378> sismember myset ccc
(integer) 1		# 返回'1'代表存在该元素
127.0.0.1:6378> sismember myset ddd
(integer) 0		# 返回 '0'代表不存在该元素


# SCARD key 获取集合的成员数
127.0.0.1:6378> SCARD myset
(integer) 3		# 返回 '3' 表示集合中有3个成员


# SREM key member1[member2] 移除集合中一个或多个成员
# 语法:SREM key member [member...]
127.0.0.1:6378> SREM myset aaa bbb
(integer) 2		# 删除两个元素
127.0.0.1:6378> SMEMBERS myset		# 查看当前集合的元素
1) "ccc"
127.0.0.1:6378> flushall	# 清空所有数据库中的数据
OK
127.0.0.1:6378> SMEMBERS myset		# 查看当前集合的元素
(empty array)


# SRANDMEMBER key [count] 返回集合中一个或多个随机数
127.0.0.1:6378> SADD myset aaa bbb ccc ddd eee	# 添加元素,准备实验数据
(integer) 5
127.0.0.1:6378> SRANDMEMBER myset 2
1) "ccc"
2) "ddd"
127.0.0.1:6378> SRANDMEMBER myset 3
1) "ccc"
2) "eee"
3) "bbb"
127.0.0.1:6378> SRANDMEMBER myset 3
1) "eee"
2) "bbb"
3) "aaa"


# spop key 移除并返回集合中的一个随机元素(原子操作,多个客户端并发访问时,每个返回的元素都是唯一的)使用场景 抽奖
127.0.0.1:6378> spop myset 1	# 移除并返回集合中的一个随机元素
1) "bbb"
127.0.0.1:6378> SMEMBERS myset	# 查看当前集合的元素
1) "ccc"
2) "ddd"
3) "eee"
4) "aaa"
127.0.0.1:6378> SPOP myset 1
1) "ccc"
127.0.0.1:6378> smembers myset
1) "ddd"
2) "eee"
3) "aaa"



# SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合
# 语法:SMOVE source destination member
# 'source'为源 'myset','destination'为目标 's1','member'为源myset中任意成员
127.0.0.1:6378> SMOVE myset s1 aaa
(integer) 1
127.0.0.1:6378> SMEMBERS s1			# 查看 s1 当前集合的元素
1) "aaa"
127.0.0.1:6378> SMEMBERS myset		# 查看 myset 当前集合的元素
1) "ddd"
2) "eee"
127.0.0.1:6378> flushall			# 清空所有数据
OK



# SDIFF key1 key2返回给定所有集合的差集(两个set中不同的值)
# 语法:SDIFF key [key...]
127.0.0.1:6378> flushall	# 清空所有数据
OK
127.0.0.1:6378> SADD s1 aaa bbb ccc		# 准备数据
(integer) 3
127.0.0.1:6378> SADD s2 ccc ddd eee		# 准备数据
(integer) 3
# 这里是以 s1 作为参照物 (即 s1与s2,s1中不同是元素)
127.0.0.1:6378> SDIFF s1 s2 
1) "aaa"
2) "bbb"
# 这里是以 s2 作为参照物 (即 s1与s2,s2中不同是元素)
127.0.0.1:6378> SDIFF s2 s1		# 这里是以 s2 作为参照物
1) "ddd"
2) "eee"




# SINTER key1 key2返回给定所有集合的交集(两个set中相同的值)
# 语法:SINTER key [key...]
127.0.0.1:6378> SINTER s1 s2
1) "ccc"


# SUNION key1 key2返回所有给定集合的并集(两个set中所有的值,重复的只有一个)
# 语法:SUNION key [key...]
127.0.0.1:6378> SUNION s1 s2
1) "bbb"
2) "ccc"
3) "ddd"
4) "eee"
5) "aaa"



 # SDIFF,SINTER,SUNION这些命令就可以完成 共同好友,共同爱好等类似的功能,比如将用户的关注的人关注放入到 set中,即可完成"共同关注"功能

# SDIFFSTORE destination key1 key2返回给定所有集合的差集并存储在destination中
# 语法:SDIFFSTORE destination key [key...]
# 命令描述:这里是以 s1 作为参照物 (即 s1与s2,s1中不同是元素存储到目标集合key1)
127.0.0.1:6378> SDIFFSTORE key1 s1 s2
(integer) 2		# 表示存储了2条数据到key1目标集合
127.0.0.1:6378> SMEMBERS key1	# 查看目标集合key1中存储的元素
1) "aaa"
2) "bbb"




# SINTERSTORE destination key1 key2返回给定所有集合的交集并存储在destinatio中
# 语法:SINTERSTORE destination key [key...]
127.0.0.1:6378> SINTERSTORE key2 s1 s2
(integer) 1
127.0.0.1:6378> SMEMBERS key2
1) "ccc"



# SUNIONSTORE destination key1 key2所有给定集合的并集存储在destination中
# 语法: SUNIONSTORE destination key [key...]
127.0.0.1:6378> SUNIONSTORE key3 s1 s2
(integer) 5
127.0.0.1:6378> smembers key3
1) "bbb"
2) "ccc"
3) "ddd"
4) "eee"
5) "aaa"
127.0.0.1:6378>

Hash 哈希类型详解

Redis hash 是一个 String 类型的 field和value的映射表,hash特别适合用于存储对象。

我们可以把它想象成Map集合key:<key-value>

Redis的Hash数据类型是用来表示键值对映射的一种数据类型,通常用于表示对象,如用户信息,商品信息等。哈希可以存储大量的字段-值对,并且设计得十分节省空间,使其成为表示数据对象的理想选择。你可以用哈希来表示基本对象,也可以用来存储一组计数器等多种数据。

Redis的Hash数据类型使用场景: 做数据存储和数据频繁更改

常见命令

  • HSET key field1 value1 field2 value2 同时将多个 field-value(域-值)对设置到哈希表 key 中

    • 注意:Redis 4.0.0版本以后,HMSET被视为已弃用。请在新代码中使用HSET

  • HGET key field 获取存储在哈希表中指定字段的值

  • HMGET key field1 field2 获取所有给定字段的值

  • HGETALL key 获取在哈希表中指定 key 的所有字段和值

  • HDEL key field1 field2删除一个或多个哈希表字段

  • HLEN key 获取哈希表中字段的数量

  • HKEYS keys 获取所有哈希表中的字段

  • HVALS key 获取哈希表中所有值

  • HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。

  • HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment (如果是负数,就是减量)。

  • HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值(原子性操作)。

hash常见命令使用

# HSET key field1 value1 field2 value2 同时将多个 field-value(域-值)对设置到哈希表 key 中
# 语法: HSET key field value [field value ...]
127.0.0.1:6378> flushall
OK
# 设置哈希表hash1中的域-值对
127.0.0.1:6378> HSET hash1 key1 a1 key2 a2 key3 a3 
(integer) 3
127.0.0.1:6378>


# 注意:Redis 4.0.0版本以后,HMSET被视为已弃用。请在新代码中使用HSET

# HGET key field 获取存储在哈希表中指定字段的值
# 语法:HGET key field	该命令每次只能获取一个域与值对
127.0.0.1:6378> HGET hash1 key1
"a1"
127.0.0.1:6378> HGET hash1 key2
"a2"
127.0.0.1:6378> hget hash1 key3
"a3"



# HMGET key field1 field2 获取所有给定字段的值
# 语法:HMGET key field [field ...] 
127.0.0.1:6378> HMGET hash1 key1 key2 key3
1) "a1"
2) "a2"
3) "a3"


# HGETALL key 获取在哈希表中指定 key 的所有字段和值
# 语法 HGETALL key
127.0.0.1:6378> HGETALL hash1
1) "key1"
2) "a1"
3) "key2"
4) "a2"
5) "key3"
6) "a3"
127.0.0.1:6378> HSET hash1 key1 aaa key2 bbb	# 覆盖的效果
(integer) 0
127.0.0.1:6378> HGETALL hash1
1) "key1"
2) "aaa"
3) "key2"
4) "bbb"
5) "key3"
6) "a3"



# HDEL key field1 field2删除一个或多个哈希表字段
# 语法: HDEL key field [field ...]
127.0.0.1:6378> HDEL hash1 key1 key2
(integer) 2
127.0.0.1:6378> HGETALL hash1	# 获取在哈希表中指定 key 的所有字段和值
1) "key3"
2) "a3"


# HLEN key 获取哈希表中字段的数量
# 语法:HLEN key
127.0.0.1:6378> FLUSHALL	# 清空数据
OK
127.0.0.1:6378> HSET qf k1 v1 k2 v2 k3 v3	# 准备数据
(integer) 3
127.0.0.1:6378> HGETALL qf	# 获取在哈希表qf中指定 key 的所有字段和值
1) "k1"
2) "v1"
3) "k2"
4) "v2"
5) "k3"
6) "v3"
127.0.0.1:6378> HLEN qf		# 获取哈希表中字段的数量
(integer) 3




# HKEYS key 获取所有哈希表中的字段
# 语法:HKEYS key
127.0.0.1:6378> HKEYS qf	# 获取所有哈希表qf中的字段
1) "k1"
2) "k2"
3) "k3"


# HVALS key 获取哈希表中所有值
# 语法: HVALS key
127.0.0.1:6378> HVALS qf	# 获取哈希表中所有值
1) "v1"
2) "v2"
3) "v3"


# HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。
# 语法: HEXISTS key field
127.0.0.1:6378> HEXISTS qf k2
(integer) 1						# 返回 '1' 表示当前哈希表 qf 中包含字段 k2
127.0.0.1:6378> HEXISTS qf k4
(integer) 0						# 返回 '0' 表示当前哈希表 qf 中不包含字段 k4



# HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment (如果是负数,就是减量)。
# 语法:HINCRBY key field increment
127.0.0.1:6378> HSET hash1 k1 1 k2 2 k3 3	# 准备数据
(integer) 3
127.0.0.1:6378> HGETALL hash1
1) "k1"
2) "1"
3) "k2"
4) "2"
5) "k3"
6) "3"
# 为哈希表 hash1 中的指定字段 k1 的整数值加上增量 2
127.0.0.1:6378> HINCRBY hash1 k1 2 
(integer) 3
127.0.0.1:6378> HGET hash1 k1	# 获取存储在哈希表 hash1 中指定字段k1的值
"3"
为哈希表 hash1 中的指定字段 k3 的整数值加上减量 -1
127.0.0.1:6378> HINCRBY hash1 k3 -1
(integer) 2
127.0.0.1:6378> HGET hash1 k3  # 获取存储在哈希表 hash1 中指定字段k3的值
"2"




# HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值(原子性操作)。
# HSETNX key field value
# 原子性操作,不作覆盖操作(存在,执行失败)
127.0.0.1:6378> HSETNX hash1 k1 30
(integer) 0
127.0.0.1:6378> HGET hash1 k1
"3"
# 原子性操作,不作覆盖操作(不存在,执行成功)
127.0.0.1:6378> HSETNX hash1 k4 30
(integer) 1
127.0.0.1:6378> HGET hash1 k4
"30"
127.0.0.1:6378> HGETALL hash1	# 获取在哈希表中指定 hash1 的所有字段和值
1) "k1"
2) "3"
3) "k2"
4) "2"
5) "k3"
6) "2"
7) "k4"
8) "30"

# 使用场景
127.0.0.1:6378> HSET user:1 name zhangsan age 18
(integer) 2
127.0.0.1:6378> HGETALL user:1
1) "name"
2) "zhangsan"
3) "age"
4) "18"
127.0.0.1:6378> HMGET user:1 name age
1) "zhangsan"
2) "18"

Redid数据类型-Zset(Sorted Set)

在Redis中,ZSET(有序集合)是一种复合类型的数据结构,它将Set和Hash两种数据结构进行了结合,类似与 JAVA 的 TreeMap(实际上是SkipList实现)。

在ZSET中,每个元素都是唯一的(就像Set),但是每个元素关联了一个socre(就像Hash的Value).

这个 socre(分数) 用于对元素进行从小到大的排序。

可以简单理解为在 set 的基础上,增加了一个值,

例如:Zset k1 score v1

特点: 可排序,元素不重复,查询速度快

由于这个特点所以 Zset 经常被用于排行榜类型的功能

注意:所有的排序默认都是升序排序,如果需要降序需要在Z后面加上REV

常用命令

  • ZADD key score member [score member ...] 向有序集合添加一个或多个成员,或者更新已存在成员的分数。

  • ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合指定区间内的成员。成员按分数值递增(从小到大)来排序。

  • ZRANGEBYSCORE key min max WITHSCORES 通过分数返回有序集合指定区间内的成员(分数可以采用正负无穷大 -inf +inf).

  • ZREM key member 从有序集合中删除一个成员

  • ZCARD key 返回有序集合的成员数量

  • ZCOUNT key min max 返回有序集合中分数在给定范围内的成员数量

  • ZSCORE key member 返回有序集合中指定成员的分数。

常用命令的使用

# ZADD key score member [score member ...] 向有序集合添加一个或多个成员,或者更新已存在成员的分数。
# 语法:ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]
127.0.0.1:6378> ZADD myset 1 one 2 two	# 向有序集合添加一个或多个成员
(integer) 2
127.0.0.1:6378> ZADD myset 3 three 4 four
(integer) 2



# ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合指定区间内的成员。成员按分数值递增(从小到大)来排序。
# 语法:ZRANGE key start stop [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]
127.0.0.1:6378> ZRANGE myset 0 -1	# 通过索引区间返回有序集合指定区间内的成员
1) "one"
2) "two"
127.0.0.1:6378> ZRANGE myset 0 -1 withscores	# 添加参数查看
1) "one"
2) "1"
3) "two"
4) "2"
127.0.0.1:6378> ZADD myset 3 three 4 four
(integer) 2
127.0.0.1:6378> ZRANGE myset 0 -1	# 升序排序
1) "one"
2) "two"
3) "three"
4) "four"
# 在Z后面加上REV 降序排列
127.0.0.1:6378> ZREVRANGE myset 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6378> ZREVRANGE myset 0 -1 withscores
1) "four"
2) "4"
3) "three"
4) "3"
5) "two"
6) "2"
7) "one"
8) "1"



# ZRANGEBYSCORE key min max [WITHSCORES][LIMIT offset count] 通过分数返回有序集合指定区间内的成员(分数可以采用正负无穷大 -inf +inf).
# 语法:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
# 一般用于统计某个范围内的值
127.0.0.1:6378> ZRANGEBYSCORE myset -inf +inf
1) "one"
2) "two"
3) "three"
4) "four"

127.0.0.1:6378> ZADD myscore 10 aaa 20 bbb 30 ccc 50 ddd 100 eee
(integer) 5
127.0.0.1:6378> ZRANGE myscore 0 -1
1) "aaa"
2) "bbb"
3) "ccc"
4) "ddd"
5) "eee"
# 取某个范围内的数据,排行榜也可以通过它来实现
127.0.0.1:6378> ZRANGEBYSCORE myscore 20 100 
1) "bbb"
2) "ccc"
3) "ddd"
4) "eee"
127.0.0.1:6378> ZRANGEBYSCORE myset -inf +inf withscores
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
7) "four"
8) "4"
# 语法:ZRANGEBYSCORE key min max [withscores] [limit offset count]
# 跳过前面2个存储后面3个
127.0.0.1:6378> ZRANGEBYSCORE myset -inf +inf withscores limit 2 3
1) "three"
2) "3"
3) "four"
4) "4"
127.0.0.1:6378> ZRANGEBYSCORE myset -inf +inf withscores limit 1 3
1) "two"
2) "2"
3) "three"
4) "3"
5) "four"
6) "4"
127.0.0.1:6378> ZADD myset 5 five
(integer) 1
127.0.0.1:6378> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
5) "five"
127.0.0.1:6378> ZRANGEBYSCORE myset -inf +inf withscores
 1) "one"
 2) "1"
 3) "two"
 4) "2"
 5) "three"
 6) "3"
 7) "four"
 8) "4"
 9) "five"
10) "5"
# 'limit 2 3' limit表示跳过2条数据,返回后面3条数据
127.0.0.1:6378> ZRANGEBYSCORE myset -inf +inf withscores limit 2 3
1) "three"
2) "3"
3) "four"
4) "4"
5) "five"
6) "5"



# ZREM key member 从有序集合中删除一个成员
# 语法:ZREM key member [member ...]
127.0.0.1:6378> ZREM myset five one	# 删除
(integer) 2
127.0.0.1:6378> ZRANGE myset 0 -1	# 查看
1) "two"
2) "three"
3) "four"



# ZCARD key 返回有序集合的成员数量
# 语法: ZCARD key
127.0.0.1:6378> ZCARD myset		# 查看成员数量
(integer) 3

# ZCOUNT key min max 返回有序集合中分数在给定范围内的成员数量
# 语法:ZCOUNT key min max
127.0.0.1:6378> ZRANGE myscore 0 -1
1) "aaa"
2) "bbb"
3) "ccc"
4) "ddd"
5) "eee"
127.0.0.1:6378> ZRANGE myscore 0 -1 withscores
 1) "aaa"
 2) "10"
 3) "bbb"
 4) "20"
 5) "ccc"
 6) "30"
 7) "ddd"
 8) "50"
 9) "eee"
10) "100"
# 返回20-50的人员数量
127.0.0.1:6378> ZCOUNT myscore 20 50
(integer) 3


# ZSCORE key member 返回有序集合中指定成员的分数。给一个具体的成员,返回他的分数
# 语法:ZSCORE key member
127.0.0.1:6378> ZSCORE myscore ccc
"30"
127.0.0.1:6378> ZSCORE myscore eee
"100"
127.0.0.1:6378> ZSCORE myscore ggg	# 成员不存在就返回空(nil)
(nil)

Redis中文网站

可以上这个网站查看redis的命令

https://redis.net.cn/

Redis 特殊数据类型-Geospatial 地理位置

Redis的地理空间 (Geospatial) 数据结构是从3.2版本开始加入的,主要用于需要地理位置的应用场景。这种数据结构允许用户将指定的地理空间位置(经度,纬度,名称)添加到指定的 key 中,这些数据将会存储到 sorted set 中,这样设计的目的是为了方便使用GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作。

此命令能实现类似地理位置推算,两地之间的距离,附件的人等功能

实现原理

1,将某个地理位置的经度分别转换成二进制

2,再将两个二进制交错合并成一个即可,经度占偶数位,纬度占奇数位,得到最终的二进制

3,将合并的二进制做base32编码,得到最终结果

4,将最终结果通过 geohash处理后成功的将一个二维信息转换成了一维信息,节省存储空间,便于前缀检索

相关命令

  • GEOADD添加一个或多个地理位置元素到key中 语法:geoAdd key lng lat member lng1 lat1 member1

    • 要求:满足经度在-180和180之间,纬度在-85.05112878和85.05112878之间。

  • GEODIST 返回一个 key 中指定两个位置之间的距离 语法:GeoDist key member1 member2

    • m表示单位为米
    • km表示单位为千米
    • mi表示单位为英里
    • ft表示单位为英尺
  • GEOPOS 返回一个或多个位置的经纬度信息 语法: GEOPOS key member1 member2

  • GEOSEARCH 命令允许你从一个给定的地理位置开始,查询在一个给定半径内的元素。此外,你可以指定搜索区域的形状为圆形(BYRADIUS)或矩形(BYBOX).

    • 语法:

    GEOSEARCH key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius m|km|ft|mi] [BYBOX width height m|km|ft|mi] [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key]

    GEOSEARCH key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius m|km|ft|mi] [BYBOX width height m|km|ft|mi] [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key

    GEOSEARCH key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius m|km|ft|mi] [BYBOX width height m|km|ft|mi] [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key]
  • FROMMEMBER member: 从键中的一个元素开始搜索。member 是键中的一个元素。

  • FROMLONLAT longitude latitude: 从指定的经度和纬度开始搜索。

  • BYRADIUS radius m|km|ft|mi: 按半径搜索。radius 是半径,单位可以是米(m),公里(km),英尺(ft),或英里(mi).

  • BYBOX width height m|km|ft|mi:按矩形搜索。width 和 height 分别是矩形的宽度和高度,单位可以是米(m),公里(km),英尺(ft),或英里(mi).

  • WITHCOORD: 返回元素的经度和纬度。

  • WITHDIST:返回元素到中心的距离。

  • WITHHASH: 返回元素的geohash.

  • COUNT count [ANY]:返回的元素数量。如果指定了ANY,那么只要找到了 count 个元素或立即返回。

  • ASC|DESC:按距离排序。ASC是升序,DESC是降序。

  • STORE key: 将返回的元素存储到一个键中。

  • 注意,FROMMEMBER 和 FROMLONLAT必须指定其中之一,BYBRADIUS和BYBOX 也必须指定其中之一。

GEOSEARCHSTORE 命令是GEOSEARCH命令的一个扩展。该命令将GEOSEARCH 命令的返回值存储在一个键中,这可以提高效率,因为你可以在不必重新计算相同的 GEOSEARCH结果的情况下,重复使用这些结果。

  • 语法:

GEOSEARCHSTORE destkey srckey [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius m|km|ft|mi] [BYBOX width height m|km|ft|mi] [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC]
  • 说明:所有的选项都和GEOSEARCH 命令一样,只是前面多了两个参数

  • destkey: 将结果存储到这个键中。

  • srckey: 源键,要从这个键中搜索元素。

  • 使用 GEOSEARCHSTORE 命令时需要注意,如果 destkey已经存在,那么这个命令会覆盖它。所以,如果你要保留 destkey 的原有值,需要提前做好备份。

这里我们为了方便演示,大家可以访问这个网站来查询城市的经纬度:

http://jingweidu.757dy.com/

Geospatial相关命令的使用

1. GEOADD  添加一个或多个地理位置元素到key中 
语法:GEOADD key [NX|XX] [CH] longitude latitude member [longitude latitude ...]
127.0.0.1:6378> geoadd city 104.066282 30.572933 chengdu
(integer) 1
127.0.0.1:6378> geoadd city 121.474001 31.230001 shanghai
(integer) 1
127.0.0.1:6378> geoadd city 117.201559 39.085256 tianjing
(integer) 1
127.0.0.1:6378> geoadd city 116.407004 39.904595 beijing
(integer) 1
127.0.0.1:6378> geoadd city 114.530000 38.037497 hebei
(integer) 1
127.0.0.1:6378> geoadd city 106.552003 29.562696 chongqing 114.305005 30.592798 wuhan
(integer) 2

2. GEOPOS 返回一个或多个位置的经纬度信息
语法:GEOPOS key member [member ...]
127.0.0.1:6378> GEOPOS city beijing tianjing shanghai chengdu chongqing wuhan hebei
1) 1) "116.40700489282608032"
   2) "39.9045955411327995"
2) 1) "117.20156103372573853"
   2) "39.08525706526112486"
3) 1) "121.47400349378585815"
   2) "31.23000157447899738"
4) 1) "104.06628459692001343"
   2) "30.57293341250703378"
5) 1) "106.55200034379959106"
   2) "29.56269494724090663"
6) 1) "114.30500596761703491"
   2) "30.59279802223277756"
   
3. GEODIST 返回一个 key 中指定两个位置之间的距离 
语法: GEODIST key member1 member2 [M|KM|FT|MI]
127.0.0.1:6378> GEODIST city beijing tianjing
"113823.0939"
127.0.0.1:6378> GEODIST city beijing tianjing km
"113.8231"
127.0.0.1:6378> GEODIST city beijing tianjing ft
"373435.3474"
127.0.0.1:6378> GEODIST city beijing tianjing mi
"70.7266"
127.0.0.1:6378> GEODIST city shanghai chengdu km
"1661.2496"
127.0.0.1:6378> geodist city shanghai beijing km
"1067.7180"
127.0.0.1:6378> GEODIST city shanghai wuhan km
"687.6601"
127.0.0.1:6378> geodist city shanghai chongqing km
"1442.4114"
127.0.0.1:6378> GEODIST city beijing hebei km
"263.5615"



4.GEOSEARCH 命令允许你从一个给定的地理位置开始,查询在一个给定半径内的元素。此外,你可以指定搜索区域的形状为圆形(BYRADIUS)或矩形(BYBOX).
# 语法:GEOSEARCH key [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius m|km|ft|mi] [BYBOX width height m|km|ft|mi] [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key] 
127.0.0.1:6378> geosearch city frommember beijing byradius 500 km
1) "tianjing"
2) "beijing"
127.0.0.1:6378> GEOSEARCH city FROMMEMBER beijing BYRADIUS 500 KM
1) "tianjing"
2) "beijing"

127.0.0.1:6378> GEOPOS city beijing tianjing shanghai chengdu chongqing wuhan hebei
1) 1) "116.40700489282608032"
   2) "39.9045955411327995"
2) 1) "117.20156103372573853"
   2) "39.08525706526112486"
3) 1) "121.47400349378585815"
   2) "31.23000157447899738"
4) 1) "104.06628459692001343"
   2) "30.57293341250703378"
5) 1) "106.55200034379959106"
   2) "29.56269494724090663"
6) 1) "114.30500596761703491"
   2) "30.59279802223277756"
7) 1) "114.53000038862228394"
   2) "38.03749712267329386"
127.0.0.1:6378>


127.0.0.1:6378> geosearch city frommember beijing byradius 500 km
1) "hebei"
2) "tianjing"
3) "beijing"
# 搜索北京500公里圆形内城市按升序排列以及经纬度
127.0.0.1:6378> geosearch city frommember beijing byradius 500 km asc withcoord
1) 1) "beijing"
   2) 1) "116.40700489282608032"
      2) "39.9045955411327995"
2) 1) "tianjing"
   2) 1) "117.20156103372573853"
      2) "39.08525706526112486"
3) 1) "hebei"
   2) 1) "114.53000038862228394"
      2) "38.03749712267329386"

# 搜索北京500公里圆形内城市按降序排列以及经纬度
127.0.0.1:6378> geosearch city frommember beijing byradius 500 km desc withcoord
1) 1) "hebei"
   2) 1) "114.53000038862228394"
      2) "38.03749712267329386"
2) 1) "tianjing"
   2) 1) "117.20156103372573853"
      2) "39.08525706526112486"
3) 1) "beijing"
   2) 1) "116.40700489282608032"
      2) "39.9045955411327995"
# desc 倒序 (从远到近)
# withcoord	显示制定半径单位的经纬度
# withdist 显示制定半径单位的距离

# 搜索北京500公里圆形内城市按降序排列以及距离
127.0.0.1:6378> geosearch city frommember beijing byradius 500 km desc withdist
1) 1) "hebei"
   2) "263.5615"
2) 1) "tianjing"
   2) "113.8231"
3) 1) "beijing"
   2) "0.0000"




5. GEOSEARCHSTORE 命令是GEOSEARCH命令的一个扩展。该命令将GEOSEARCH 命令的返回值存储在一个键中,这可以提高效率,因为你可以在不必重新计算相同的 GEOSEARCH结果的情况下,重复使用这些结果
语法:GEOSEARCHSTORE destkey srckey [FROMMEMBER member] [FROMLONLAT longitude latitude] [BYRADIUS radius m|km|ft|mi] [BYBOX width height m|km|ft|mi] [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC]
GEOSEARCHSTORE destination source FROMMEMBER member|FROMLONLAT longitude latitude BYRADIUS radius M|KM|FT|MI|BYBOX width heigh M|KM|FT|MI WITHCOORD WITHDIST WITHHASH COUNT count [ANY] [ASC|DESC]

# 将北京500公里圆形内 city 中的城市存储在 destkey 中
127.0.0.1:6378> GEOSEARCHSTORE destkey city frommember beijing byradius 500 km
(integer) 3
# 查看 destkey 中存储了哪些元素
127.0.0.1:6378> ZRANGE destkey 0 -1
1) "hebei"
2) "tianjing"
3) "beijing"


# 因为本质上 geospatial 类型的底层就是 Zset ,我们可以通过 ZRANGE 来查看数据
# 当然比如说我们要清楚地理位置:完全可以使用Zset中的ZREM命令完成。
注意:因为GEO的底层就是Zset,所以完全可以使用Zset的命令来操作GEO
127.0.0.1:6378> keys *
1) "destkey"
2) "city"
127.0.0.1:6378> zrange destkey 0 -1
1) "hebei"
2) "tianjing"
3) "beijing"
127.0.0.1:6378> zrange city 0 -1
1) "chongqing"
2) "chengdu"
3) "wuhan"
4) "shanghai"
5) "hebei"
6) "tianjing"
7) "beijing"
127.0.0.1:6378> GEOPOS city beijing tianjing shanghai chengdu chongqing wuhan hebei
1) 1) "116.40700489282608032"
   2) "39.9045955411327995"
2) 1) "117.20156103372573853"
   2) "39.08525706526112486"
3) 1) "121.47400349378585815"
   2) "31.23000157447899738"
4) 1) "104.06628459692001343"
   2) "30.57293341250703378"
5) 1) "106.55200034379959106"
   2) "29.56269494724090663"
6) 1) "114.30500596761703491"
   2) "30.59279802223277756"
7) 1) "114.53000038862228394"
   2) "38.03749712267329386"
127.0.0.1:6378>

Redis特殊数据类型-HyperLogLog 基数统计

HyperLogLog 是一种概率数据结构,用于估计集合的基数。

HLL(简称):其实代替了通过Set保存用户ID,统计set中元素数量来计算用户访问量的传统方式,因为我们的目的是为了统计独立用户访问量,而并非记录用户id或者用户表示等数据

计算唯一项目通常需要与要计算的项目数量成比例的内存量,因为需要记住过去已经见过的元素,以避免多次计算它们,所以这就会导致计算基数内存量会占用比较多。

但是HyperLogLog实现最多使用12KB内存,就可以计算进阶2^64个不同的基数。

每个 HyperLogLog只会根据输入元素来计算基数,而本身不会存储元素,所以HLL不能像其他集合那样返回各个元素本身。

什么是基数?

基数: 用于统计一个集合中不重复的元素个数,其本身就是对集合去重后,统计剩余元素的个数

A:{1,2,4,6,3,2,1}
去掉重复的元素
B:{1.2,4,6,3}=5(基数)

注意: HLL官方说明中提出了,会用标准0.81%的误差,但是在大数据统计的时候可以忽略不计

常见的使用场景

统计网站的访问 uv (网站独立用户访问量)

  • 当前页面独立访问次数统计

  • 一首歌曲的独立用户播放人数

  • 一个视频的独立用户观看人数

相关命令

  • PFADD key element [element ...] 添加指定元素到HyperLogLog 中

  • PFCOUNT 返回给定 HyperLogLog 的基数估算值

  • PFMERGE 将多个 HyperLogLog 合并为一个HLL

# PFADD 添加一个HLL
# 语法: PFADD key [element [element ...]]
127.0.0.1:6378> flushall	# 清空数据
OK
127.0.0.1:6378> keys *
(empty array)
127.0.0.1:6378> PFADD hll1 1 3 4 5 6 7
(integer) 1




# PFADD 添加二个HLL
127.0.0.1:6378> PFADD HLL2 2 2 1 8 9 3 7
(integer) 1

# PFCOUNT 计算HLL2的基数
# 语法:PFCOUNT key [key ...]
127.0.0.1:6378> PFCOUNT HLL2
(integer) 6


# PFMERGE 将两个HLL组合在一起
# 语法:PFMERGE destkey sourcekey [sourcekey ...]
127.0.0.1:6378> PFMERGE hll3 hll1 HLL2
OK



# PFCOUNT 两个组合成一个的HLL最终计算的基数为9
127.0.0.1:6378> PFCOUNT hll3
(integer) 9

Redis 特殊数据类型-BitMaps

Redis的Bitmaps是一种紧凑的数据结构,用于存储二进制逻辑和状态。然而,需要注意的是,Bitmaps并不是实际的数据类型,而是定义在String类型上的一组位操作。

简单理解Bitmaps其实就是通过位来保存二进制数据,通过0.1的方式来表示两种状态。

适合的场景

  • 钉钉打卡 0表示未打卡 1表示打卡

  • 登录状态

一个 byte (字节) 为8位

举例:

统计某个员工一轴的打卡情况

0表示未打卡

1表示打卡

按照这个逻辑就算是统计用户一年的打卡情况,内存的使用量也非常小

365 = 365 bit 1 byte=8bit 总量:46byte

Bitmaps相关命令

  • SETBIT key offset val 命令在提供的偏移出(offset) 将设置为0或1

  • GETBIT key offset 此命令返回给定偏移处的位的值

  • BITTOP operation destkey key [key ...] 命令可以在一个或多个字符串上执行 位 运算,包括 AND,OR,XOR和NOT操作

    • AND:这是 按位与 操作。每一位上,如果两个数字都为1,结果就是1,否则是0.例如0101 AND 0011结果为0001.

    • OR: 这是 按位或 操作。在每一位上,如果任何一个数字为1,结果就是1,否则是0.列如,0101 OR 0011结果为0111.

    • XOR: 这是 按位异或 操作。在每一位上,如果两个数字不相同,结果就是1,否则是0.例如,0101 XOR 0011结果为0110.

    • NOT:这是 按位非 操作。这个操作将输入键的每个位反转(0变1,1变0),例如,NOT 0101 结果为1010.

  • BITCOUNT key start end 这个命令用于报告设置为1的位的数量

    • 注意:start 和 end 参数定义了要检查的字节范围(不是位的范围)。

    • 这两个参数都是基于字节的,而不是基于位的。也就是说,如果你有一个包含8位的字符串,start=0和end=0将只检查第一个字节(即前8位)

  • STRLEN key 用于统计占用的字节数

1. SETBIT	命令在提供的偏移出(offset) 将设置为0或1	byte中的第1位
语法: SETBIT key offset value 		# 1byte有8位  'offset' 是下标
127.0.0.1:6378> SETBIT week 0 1		'0' 代表第1位 星期一	'1' 代表打卡
(integer) 0
127.0.0.1:6378> SETBIT week 1 1		'1' 代表第2位 星期二	'1' 代表打卡
(integer) 0
127.0.0.1:6378> SETBIT week 2 0		'2' 代表第3位 星期三	'0' 代表未打卡
(integer) 0
127.0.0.1:6378> SETBIT week 3 1		'3' 代表第4位 星期四	'1' 代表打卡
(integer) 0
127.0.0.1:6378> SETBIT week 4 0		'4' 代表第5位 星期五	'0' 代表未打卡
(integer) 0
127.0.0.1:6378> SETBIT week 5 0		'5' 代表第5位 星期六	'0' 代表未打卡
(integer) 0
127.0.0.1:6378> SETBIT week 6 1		'6' 代表第6位 星期日	'1' 代表打卡
(integer) 0
2 . GETBIT
语法:GETSET key offset   # 'offset' 是下标
127.0.0.1:6378> GETBIT week 0		# 查看周一打卡没有
(integer) 1							# 返回 '1' 代表周一打卡了
127.0.0.1:6378> GETBIT week 1		# 查看周二打卡没有
(integer) 1							# 返回 '1' 代表周二打卡了
127.0.0.1:6378> GETBIT week 2		# 查看周三打卡没有
(integer) 0							# 返回 '0' 代表周三未打卡了
127.0.0.1:6378> GETBIT week 3		# 查看周四打卡没有
(integer) 1							# 返回 '1' 代表周四打卡了
127.0.0.1:6378> getbit week 4		# 查看周五打卡没有
(integer) 0							# 返回 '0' 代表周五未打卡了
127.0.0.1:6378> getbit week 5		# 查看周六打卡没有
(integer) 0							# 返回 '0' 代表周六未打卡了
127.0.0.1:6378> getbit week 6		# 查看周日打卡没有
(integer) 1							# 返回 '1' 代表周日打卡了

3. BITOP 命令可以在一个或多个字符串上执行 位 运算,包括 AND,OR,XOR和NOT操作
语法:BITOP operation destkey key [key ...]
127.0.0.1:6378> BITOP and key user:1 user:2
(integer) 1




4. BITCOUNT	这个命令用于报告设置为1的位的数量
语法:BITCOUNT key [start end [BYTE|BIT]] 'start与end' 代表字节
127.0.0.1:6378> BITCOUNT week
(integer) 4
127.0.0.1:6378> BITCOUNT week 0 0	查看第一周第一个字节
(integer) 4

5. STRLEN
语法:STRLEN key	# 用于统计占用的字节数
127.0.0.1:6378> STRLEN week
(integer) 1		# 返回 '1' 表示一个字节


使用场景:可以做联合统计
需求:比如 想查看或查询连续两天登录的用户有几个,假如这是一个新建网站,网站需要大量用户来访问,要用户量,现在想看一看我网站是否能够吸引人,有没有人连续两天访问,有的话有几个,我们该如何去计算,可以如下写
语法 SETBIT key offset value    # 'offset'为下标
# 记录 user:1 的用户登录信息
127.0.0.1:6378> SETBIT user:1 0 1 #'offset'下标为0,表示第一天 '1'表示登录
(integer) 0
127.0.0.1:6378> SETBIT user:1 1 1 #'offset'下标为1,表示第二天 '1'表示登录
(integer) 0
127.0.0.1:6378> SETBIT user:1 2 1 #'offset'下标为2,表示第三天 '1'表示登录
(integer) 0
127.0.0.1:6378> SETBIT user:1 3 0 #'offset'下标为3,表示第四天 '0'表示未登录
(integer) 0

# 记录 user:2 的用户登录信息
127.0.0.1:6378> SETBIT user:2 0 0 #'offset'下标为0,表示第一天 '0'表示未登录
(integer) 0
127.0.0.1:6378> SETBIT user:2 1 0 #'offset'下标为1,表示第二天 '0'表示未登录
(integer) 0
127.0.0.1:6378> SETBIT user:2 2 1 #'offset'下标为2,表示第三天 '1'表示登录
(integer) 0
127.0.0.1:6378> SETBIT user:2 3 0 #'offset'下标为3,表示第四天 '0'表示未登录
(integer) 0

# BITOP 命令可以在一个或多个字符串上执行 位 运算,包括 AND,OR,XOR和NOT操作
语法:BITOP operation destkey key [key ...]
127.0.0.1:6378> BITOP and key user:1 user:2		# 联合统计
(integer) 1
# 查看保存的数量
127.0.0.1:6378> BITCOUNT key
(integer) 1


Redis事务

Redis事务一次可以执行多条命令,按照命令的顺序进行串行化执行,执行命令的时候不允许其他命令插入,不许加塞。(保证原子性)

一个 Redis 事务从开始(MULTI)到执行(EXEC)的过程,这段时间内所有的命令都会被序列化,入队,在EXEC命令被调用时一次性,按顺序地执行。

开始事务,命令入队,执行事务

Redis 事务可以通过以下四个命令来实现:

1. MULTI: 它标记了一个事务块的开始。MULTI命令之后的所有命令不会立即执行。而是缓存在服务器的一个事务队列中,然后当 EXEC 命令被调用时一次性,按顺序地执行。

2. EXEC: 它标记事务的提交,它的作用是触发服务器执行所有在 MULTI 之后入队的命令

3. DISCARD: 如果客户端在 MULTI 之后决定取消事务,那么它可以调用 DISCARD 命令来做这件事,调用 DISCARD 之后,服务器会清空事务队列,并将客户端的状态从事务状态调整回非事务状态。

4. WATCH key [key ...]: WATCH 命令用于在执行事务前监控一个或多个键,以此来达到乐观锁的效果。如果在调用EXEC命令执行事务之前,有其他客户端抢先对任何一个被监视的键进行了替换,更新,删除等操作,那么当客户端尝试执行事务时,服务器将返回一个错误,客户端可以在这时选择重试事务或者放弃事务。

注意:Redis 的事务不支持回滚,即如果事务执行过程中出现错误,Redis只是简单地停止执行后续的命令,但是不会回滚已经执行的命令。

Redis 的事务虽然简单,但是由于其原子性,通常足够应对绝大部分需要使用事务的场景。

Redis事务命令的使用

# 演示第一个事务
127.0.0.1:6378> flushdb		# 清空当前数据库数据
OK
127.0.0.1:6378> MULTI		# 开启 Redis 事务
OK
语法:set key value [NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]

127.0.0.1:6378(TX)> set num 10	# 入队,此时并没有执行该命令
QUEUED
127.0.0.1:6378(TX)> get num		# 入队,此时并没有执行该命令
QUEUED
127.0.0.1:6378(TX)> EXEC		# 执行命令,然后把 'num' 的值取出来
1) OK
2) "10"
# 以上就是 Redis 一个简单的事务


# 演示第二个事务
127.0.0.1:6378> MULTI		# 开启事务
OK
127.0.0.1:6378(TX)> set num1 20		# 入队,此时并没有执行该命令
QUEUED
127.0.0.1:6378(TX)> set num2 100	# 入队,此时并没有执行该命令
QUEUED
127.0.0.1:6378(TX)> get num1		# 入队,此时并没有执行该命令
QUEUED
127.0.0.1:6378(TX)> get num2		# 入队,此时并没有执行该命令
QUEUED
127.0.0.1:6378(TX)> discard		# 取消事务 'ok' 代表取消成功
OK
127.0.0.1:6378> keys *		# 查看,可以看出它只执行了一个事务
1) "num"
# 正常的事务提交和事务回滚

Redis事务-失败演示

Redis事务会将命令按照顺序执行串行化操作,但是如果这些命令中有一个命令失败了是否影响到整个事务的执行,也就是事务中断或者取消?

答案:不会,只有错误(非语法错误)的命令不会执行
# 在一个 redis 事务中,如果出现了错误的命令,只有错误的命令不会执行,但不会影响整体事务执行
127.0.0.1:6378> MULTI	# 开启事务
OK
127.0.0.1:6378(TX)> set num 10
QUEUED
127.0.0.1:6378(TX)> set str abc
QUEUED
127.0.0.1:6378(TX)> incr num
QUEUED
127.0.0.1:6378(TX)> incr str
QUEUED
127.0.0.1:6378(TX)> keys *
QUEUED
127.0.0.1:6378(TX)> get num
QUEUED
127.0.0.1:6378(TX)> get str
QUEUED
127.0.0.1:6378(TX)> EXEC
1) OK
2) OK
3) (integer) 11
4) (error) ERR value is not an integer or out of range
5) 1) "num"
   2) "str"
6) "11"
7) "abc"
127.0.0.1:6378>


以上的案例就好像我们去超市买东西,发现有一个物品过期了,那么此物品则不会结账,而是挑出去一样

Redis 事务中,如果我们的命令语法都出现了错误,那么Redis事务还会执行吗?

# 此案例中出现了语法错误
127.0.0.1:6378> MULTI
OK
127.0.0.1:6378(TX)> set num1 20
QUEUED
127.0.0.1:6378(TX)> get num1
QUEUED
127.0.0.1:6378(TX)> incr num1 10	# 出现语法错误,直接报错,不让加入队列
(error) ERR wrong number of arguments for 'incr' command
127.0.0.1:6378(TX)> incr num1
QUEUED
127.0.0.1:6378(TX)> incrby num1 10
QUEUED
127.0.0.1:6378(TX)> EXEC	# 提交的事务根本不执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6378> discard		# 出现语法错误,就退出了事务
(error) ERR DISCARD without MULTI
# 出现语法错误,就退出了事务 此次事务就队列就取消了,所以查看不到 'num1'
127.0.0.1:6378> keys *		
1) "num"
2) "str"


以上相当于超市停电或者收银台都不能收款,所有东西你只能全部退回超市

Redis 事务-watch命令

WATCH 是 Redis 的一个事务命令,它为 Redis 提供了乐观锁(Optimistic Locking) 的功能。这使得你可以在执行事务时添加一种并发控制机制。

秒杀业务就是通过 Redis 的 watch 命令来实现乐观锁实现功能

在 Redis 中,WATCH 命令可以用来监视一个或多个key,如果在事务执行之前这些key的值发生了改变,那么事务将会被打断。

注意: 一旦执行了EXEC或DISCARD命令,所有的 WATCH 监控都会被取消。

工作流程

1. WATCH 一个或多个key.

2. 创建事务,即发送 MULTI 命令。

3. 发送一系列的命令。

4. 执行事务,即发送 EXEC 命令,此时,如果任何被 WATCH 的 key 自你开始 WATCH 以来已被其他客户端更改,那么 EXEC 将失败,事务被打断。

# 此案例演示的是并发时出现的线程安全问题
# 客户端1 操作
# 案列:账户a有100元 b有50元,现在要求将a账户的100元转到b账户,最终结果 a=0 b=150
127.0.0.1:6378> set a 100
OK
127.0.0.1:6378> set b 50
OK
127.0.0.1:6378> MULTI	# 开启事务
OK
127.0.0.1:6378> MULTI
OK
127.0.0.1:6378(TX)> DECRBY a 100
QUEUED
127.0.0.1:6378(TX)> incrby b 100
QUEUED
127.0.0.1:6378(TX)> get a
QUEUED
127.0.0.1:6378(TX)> get b
QUEUED
# 在客户端2操作以后再到客户端1执行提交命令,结果已预期不一样
# redis中没有事务隔离级别,所以并发时就出现了安全问题
127.0.0.1:6378(TX)> exec  
1) (integer) -100
2) (integer) 200
3) "-100"
4) "200"
127.0.0.1:6378> get a
"-100"
127.0.0.1:6378> get b
"200"




# 客户端2 操作  在客户端1已经开启事务还未执行提交的时候执行如下命令
127.0.0.1:6378> get a
"50"
127.0.0.1:6378> get b
"100"
127.0.0.1:6378> DECRBY a 50
(integer) 0
127.0.0.1:6378> get a
"0"
127.0.0.1:6378> get b
"100"
127.0.0.1:6378>

如何解决并发安全问题

# 客户端1 操作
# 案列:账户a有100元 b有50元,现在要求将a账户的100元转到b账户,最终结果 a=0 b=150
WATCH
语法:WATCH key [key ...]
127.0.0.1:6378> set a 100
OK
127.0.0.1:6378> set b 50
OK
127.0.0.1:6378> watch a b	# 可以监控多个 key 值
OK
127.0.0.1:6378> MULTI		# 开启事务
OK
127.0.0.1:6378(TX)> DECRBY a 100
QUEUED
127.0.0.1:6378(TX)> INCRBY b 100
QUEUED
127.0.0.1:6378(TX)> get a
QUEUED
127.0.0.1:6378(TX)> get b
QUEUED
# 客户端2改变了数据,再次在客户端1执行exec命令,此时就显示(nil),表示事务没有执行
127.0.0.1:6378(TX)> exec	
(nil)
127.0.0.1:6378> get a
"50"
127.0.0.1:6378> get b
"50"




# 客户端2 操作  在客户端1已经开启事务还未执行提交的时候执行如下命令
127.0.0.1:6378> keys *
(empty array)
127.0.0.1:6378> get a
"100"
127.0.0.1:6378> get b
"50"
127.0.0.1:6378> decrby a 50
(integer) 50
127.0.0.1:6378> get a
"50"
127.0.0.1:6378> get b
"50"
 
 通过watch 设置监控我们就解决了redis 事务没有隔离的安全解决方案
这样就解决了并发安全问题

Redis-图形化客户端

其实有的时候我们需要Redis像MySQL的DBeaver,Navicat那样对应的图形化客户端,那么Redis官方是没有提供的,但是市场上还是有很多好用的可视化的客户端,比较常见的有:

  • Redis Desktop Manage(RDM): RDM是一个跨平台的桌面 Redis 客户端,它以图形化的方式展示 Redis 数据库中的数据,并且支持所有 Redis 命令,RDM 支持Linux,Windows 和 macOS.

  • FastoRedis(Fastonosql): 这是一个跨平台的数据库管理工具,它支持 Redis 以及其他 NoSQL 数据库如MongoDB,Memcacheed等。FastoRedis 提供了一种直观的方式来管理和查看你的Redis数据。

  • Another Redis Desktop Manager: 这是一款优秀的开源项目,由中国开发者创建。它支持直连/哨兵/集群连接,支持键值的增删改查操作,同时还提供了可视化界面和多语言支持。支持的系统包括Windows,Mac 和 Linux.等等

那么我们这里采用Another Redis Desktop Manager 或者Redis Desktop Manager(RDM)

###

RESP的安装与使用

RESP是github上一款免费的Redis图形化管理软件

下载地址:https://github.com/lework/RedisDesktopManager-Windows/releases

1、直接点击下载和解压安装就可以了

连接不成功:尝试在linux中执行下面的两个命令: 以下命令是用于配置 Linux 防火墙(firewalld)以开放指定的端口 (6379/tcp)。

firewall-cmd --zone=public --add-port=6379/tcp --permanent
​

这个命令添加一个规则到 “public” 区域(zone)的防火墙配置中,允许通过端口 6379 的 TCP 连接。–permanent 参数表示该规则将被永久保存,重新启动后仍然有效。

firewall-cmd --reload
​

这个命令重新加载防火墙规则,使最新的修改生效。当你添加、删除或修改防火墙规则时,都需要执行这个命令才能使更改生效。

注意事项:

以上命令需要以管理员权限(例如 root 用户或者使用 sudo)来执行。 1、在执行前,请确保已经安装了 firewalld,并且正在运行。 2、6379 是 Redis 默认的端口号,如果你在使用其他端口,则需要相应地修改上述命令中的端口号。

RESP最佳连接方式是使用修改Redis配置文件来连接

# 修改 redis 配置文件
将 'projected yes' 修改为 'projected no' 然后后加载重启就可以理解
[root@mail ~]# vim /opt/redis-7.0.11/redis.conf
[root@mail ~]# systemctl daemon-reload
[root@mail ~]# systemctl restart redis

看到如下图表示连接成功

 

Jedis 客户端操作 Redis

Jedis 是一个流行的 java 客户端库,它用于连接和操作 Redis 数据库。以下是 Jedis 的一些关键特性和详细说明:

1. 连接:Jedis 提供简单的方法来连接 Redis.你可以指定主机名和端口来连接本地或远程的 Redis 服务器。也可以使用密码验证功能来连接需要认证的 Redis 服务器。

2. 基本操作:Jedis 支持所有的 Redis 命令。例如GET,SET,DEL等键值对操作,以及 LPUSH,LRANGE等 列表操作,还有 SADD,SMEMBERS 等集合操作,等等

3. 高级功能:Jedis 支持 Redis 的一些高级功能,如事务,管道 和 发布/订阅。事务允许你执行一组命令,然后一次性提交到服务器,管道允许你一次性发送多个命令,然后一次性获取所有结果,这可以显著提高网络利用率和性能,发布/订阅则是一个强大的消息发布和订阅系统。

4. 连接池: jedis 提供了一个连接池,它可以在多线程环境中复用连接,避免频繁地创建和销毁连接,提高了应用程序的性能。

5. 二进制安全: Jedis 是二进制安全的,这意味着你可以在Redis中存储任何类型的数据,包括字符串,整数,浮点数,甚至是JPEG图片或者序列化的对象

6. 集群支持: 从2.9.0版本开始,jedis提供了对Redis 集群的原生支持,这包括分区,故障转移和复制等功能。

从这节课开始我们将通过 Java 来连接 Redis

SpringBoot 整合 Redis

一般使用 Redis 都会配合 SpringBoot 来搭建微服务项目,所以很多时候我们需要 Redis 来高速缓存一些数据,来达到存储一些高频率访问的数据,所以这就需要通过官方提供的 Jedis 来实现 Redis 缓存的木的。

maven 仓库地址

https://repo.maven.apache.org/maven2/

创建SpringBoot项目

项目名称 qfRedis

导入 Jedis 依赖(SpringBoot-2.5.4其实可以帮助管理版本)

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>3.4.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <optional>true</optional>
</dependency>

编写配置 application.yml

spring:
	redis:
		database: 0 	# Redis 数据库 0-15
		host: 192.168.10.129	# ip 地址
		password: 753159	# redis 密码
		port: 6379	# redis 的端口号
		timeout: 5s # 超时时间
		jedis:	# Jedis 连接池配置
			pool:
				max-active: 8	# 连接池中最大连接数(使用负值表示没有限制)
				max-wait: 60	# 连接池最大阻塞等待时间(使用负值表示没有限制)
				max-idle: 8		# 连接池中最大空闲数
				min-idle: 1		# 连接池中最小空闲数
				time-between-eviction-runs:
	# 每ms运行一次空闲连接回收器(独立线程)如果使用jedis或初始化redisPool为空值
	
	spring:
  redis:
    database: 0
    host: 192.168.10.113
    password: 753159
    port: 6378
    timeout: 5s
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 1
        max-wait: 1s
        time-between-eviction-runs: 60s
				  	

注意:先把依赖搞定,然后把对应的配置文件配好,此时已经完成,再开始写代码。创建配置文件夹再下面创建一个读取配置文件的类

解决问题

Cannot resolve plugin org.apache.maven.plugins:maven-clean-plugin:3.1.0

//阿里云的远程私有仓库
<mirrors>
    <mirror>
      <id>alimaven</id>
      <name>aliyun maven</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <mirrorOf>central</mirrorOf>        
    </mirror>
  </mirrors>
<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-compiler-plugin</artifactId>
	<version>3.81</version>
</plugin>
java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'

解决方案:修改Lombok版本,修改后重新构建项目,更新maven,重新启动项目。
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <optional>true</optional>
</dependency>

自动装配类型

封装一个 JedisConfig.class 连接类

package com.qf.redistst.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * Jedis 连接池配置
 * 目标: 获取 Jedis 连接,控制 redis
 * 我们相当于自己封装了一个 JedisConfig 类型
 * 我们可以去测试一下我们自己注入的 JedisPool 好用吗
 */

@Configuration
@ConfigurationProperties(prefix = "spring.redis")
public class JedisConfig {

    // 定义 redis 的一些相关参数比如说 database,host,password,port等
    // redis 相关连接参数
    // 针对以下四个属性需要增加 getter 和 setter 方法
    private int database;
    private String host;
    private int port;
    private String password;

    // Jedis 连接池的相关配置

    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;


    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;


    @Value("${spring.redis.jedis.pool.min-idle}")
    private int minIdle;


    private int maxWait;
    private int timeout;
    private boolean testOnBorrow;
    private boolean testOnReturn;
    private boolean testWhileIdle;
    private int maxEvictableIdleTimeMillis;


    @Bean
    // 将连接池注入进来
    public JedisPool jedisPool() {
        // 连接池配置  需要连接相关的内容  就需要去获取相关的对象,new 一个 对象 (创建一个 JedisPoolConfig 连接池对象)
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        // 读取相关配置 读取最大连接数,...  我们需要将他们引入进来
        poolConfig.setMaxTotal(maxActive);
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMinIdle(minIdle);
        // 创建一个 JedisPool 连接池对象 填写 JedisPool 连接池信息 这个构造方法是注入我们的 JedisPool 所以我们需要返回 JedisPool
        JedisPool jedisPool = new JedisPool(poolConfig, host, port, timeout, password,database);
        return jedisPool;
    }
    public int getDatabase() {
        return database;
    }

    public void setDatabase(int database) {
        this.database = database;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

application.properties

spring:
  redis:
    database: 0
    host: 192.168.10.113
    password: 123456
    port: 6378
    timeout: 5s
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 1
        max-wait: 1s
        time-between-eviction-runs:

RedisTstApplicationTests.java测试类

package com.qf.redistst;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@SpringBootTest
class RedisTstApplicationTests {
    // 注入一下 JedisPool 测试我们封装的 JedisConfig 这个类好使用吗
    @Autowired
    private JedisPool jedisPool;

    @Test
    void contextLoads() {
        // 需要去获取连接 jedisPool.getResource() 这里可以得到对应的理解
        // 我们还需要给 Redis 返回一个数据 jedis  因为 jedis 要与我们的 redis 整合
        // redis 的一些方法,比如 get set 等对应的操作方法都在jedis中
        Jedis jedis = jedisPool.getResource();
        System.out.println(jedis.ping());
        jedis.set("qfredis","SpringBoot整合redis");
        String result = jedis.get("qfredis");
        System.out.println(result);
        // System.out.println(jedis.get("qfredis"));
        jedis.close();  // 结束关闭jedis
    }

}

验证

package com.qf.redistst;

import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class RedisTstApplicationTests {

    @Test
    void contextLoads() {
        Jedis jedis = jedisPool.getResource();
        System.out.println(jedis.ping());
        Jedis.set("qfredis","springboot整合redis");
        spring result = jedis.get("qfredis");
        Sysetm.out.println(result);
        jedis.close();  # jedis 使用完毕将jedis关闭
    }
}

7-zip文件

https://www.7-zip.org/download.html

windows .7z 解压和压缩文件工具
下载地址
https://www.7-zip.org/a/7z1900-x64.exe
官网地址
https://www.7-zip.org/

Redis完成手机验证码功能

需求描述:用户在客户端输入手机号,点击发送后随机生成4位数字码。有效期60秒,输入验证码,点击验证,返回成功或者失败,且每个手机号5分钟内只能验证3次,并给相应信息提示。

正常完成业务需要调用短信验证接口(收费),比如说阿里云

整体流程

1. 后台Redis生成一个key用于保存的4位验证码,设置此key的过期时间为60秒

2. 前端用户输入的验证码和后台生成的redis key验证码进行比较

如果不相等:

验证码校验失败

如果相等

恭喜你登录成功

3. 防攻击,当用户每次发送获取验证码请求是都在Redis中生成一个key,具体最好如下:

Redis key: key 最好是 IP 地址,值为发送此时,可以默认为1,当用户每获取一次验证码,进行+1操作,当3分钟内超过了5次访问(expire 5 分钟),将当前用户IP存入lock_ip key中,表示锁定(12小时,24小时,或者封死该用户等),此时再通过此IP地址发送访问,将提示用户,你今天不能在获取验证码。

完成业务

获取验证码

此部分我们先来完成验证码的生成,发送,测试前后端联通

发送验证码代码(后端代码)

PhoneCodeValidateController.java

package com.qf.redistst.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@RestController
@Slf4j
public class PhoneCodeValidateController {
    // 注入 Jedis 连接池
    @Autowired
    private JedisPool jedisPool;
    /**
     * 随机生成4位验证码 (调用手机短信 API 接口)
     */
    public int phoneCode(String phone) {
        int num = (int) (Math.random()*10000);
        return num;
    }

    /**
     * 根据用户的手机号生成 Redis 中的key phone:code:手机号
     * 判断 key 是否存在,如果 key 不存在,key 进行赋值,设置过期时间为 60 s
     * 如果 key 存在,提示用户验证已发送,请查收。
     * @param phoneNum
     * @return
     */
    @GetMapping("getValidateCode")
    public String getValidateCode(@RequestParam("phoneNum") String phoneNum) {
        // 获得 Jedis 连接
        Jedis jedis = jedisPool.getResource();
        // 保存手机验证码 Redis key
        String key = "phone:code" + phoneNum; // key 60 秒后销毁的
        // 判断 key 是否存在
        if (!jedis.exists(key)) {
            // 如果不存在,就生成验证码
            int phoneCode = this.phoneCode(phoneNum);
            // 打印验证码
            log.info("验证码已发送"+phoneCode);
            jedis.set(key, phoneCode + "");
            jedis.expire(key,60);// 设置过期时间60秒
            return "验证码已发送!";
        } else {
            return "验证码已发送,还需等待"+jedis.ttl(key)+"秒";
        }
    }
}

发送验证码代码(前端代码)index.html

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>手机app通用模板蓝色系用户登录页面</title>
		<meta content="width=device-width,initial-scale=1.0,maxmum-scale=1.0,user-scale=0" name="viewport" />
		<meta content="yes" name="apple-web-app-capable" />
		<meta content="black" name="apple-mobile-web-app-status-bar-style" />
		<meta content="telephone=no" name="format-detection" />
		<link href="css/style.css" rel="stylesheet" type="text/css" />
	</head>
	<body>
<section class="aui-flexView">
	<header class="aui-navBar aui-navBar-fixed">
		<a href="javascript:;" class="aui-navBar-item">
			<i class="icon icon-return"></i>
		</a>
		<div class="aui-center">
			<span class="aui-center-title"></span>
		</div>
		<a href="javascript:;" class="aui-navBar-item">
			<i class="icon icon-sys"></i>
		</a>
	</header>
	<section class="aui-scrollView">
		<div class="aui-sign-head">
			<img src="images/0.png alt="">
		</div>
		<div class="aui-sign-form">
			<div class="aui-flex">
                <i class="icon icon-phone"></i>
                <div class="aui-flex-box">
                    <input type="text" id="phone1" autocomplete="off" placeholder="输入手机号码">
                </div>
			</div>
			<div class="aui-flex">
				<i class="icon icon-code"></i>
                <div class="aui-flex-box">
                	<input type="text" id="code1" autocomplete="off" placeholder="输入验证码">
                </div>
                <div class=aui-code>
                	<input id="btnSendCode1" type="button" class="btn btn-default" value="获取验证码" onClick="sendMessage1()" />
                </div>
			</div>
			<button class="aui-sign-login" onClick="binding()">立即登录</button>
            <div class="aui-flex aui-flex-clear">
                <div class="aui-flex-box">
                    <a href="javascript:;">手机注册</a>
                </div>
                <div class="aui-links">
                    <a href="javascript:;">忘记密码</a>
                </div>
            </div>
		</div>
	</section>
</section>
</body>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
	var phoneReg = /(^1[3|4|5|7|8]\d{9})(^09\d{8}$)/;
	var count = 60;
	var InterValObj1;
	var curCount1
	function sendMessage1(){
		curCount1 = count;
		var phone = $.trim($('#phone1').val());
		if (!phoneReg.test(phone)) {
			alert("请输入有效的手机号码");
			return false;
		}
		$.ajax({
			type: "get",
			url: "http://localhost:8080/getValidateCode",
			data: {"phoneNum":phone},
			success: function(msg){
				alert(msg);
			}
		})
		$("#btnSendCode1").attr("disabled","true");
		$("#btnSendCode1").val(+curCount1+"秒再次获取");
		InterValObj1 = window.setInterVal(SetRemainTime1,1000);
	}
	function SetRemainTime() {
        if (curCount1 == 0) {
            window.clearInterval(InterValObj1);
            $("#btnSendCode1").removeAttr("disabled");
            $("#btnSendCode1").val("重新发送");
        } else {
            curCount1--;
            $("#btnSendCode").val( + curCount1 + "秒再获取" );
        }
    }
    // 确定按钮方法
    function binding() {
        var phone = $.trim($('#phone').val);
        $.ajax({
                type: "get",
                url: "http://localhost:8080/validateCode",
                data: {
                    "code": $("#code1").val(),
                    "phoneNum": phone
                },
                success: function(msg) {
                alert(msg);
                }
        })
    }
</script>
</html>

判断验证码是否正确

前端点击确认登录按钮
PhoneCodeValidateController后端代码,增加接口方法,此方法用于前端输入验证码是否正确
/**
* 用户点击确定登录按钮,会将前台输入的手机号,验证码发送到后端
* 后端接受以后会和 Redis 中的 key 进行比较
* 如果相等,验证码比较成功,登录成功
* 如果失败,验证码输入错误
* @param code
* @param phoneNum
* return 
*/
@GetMapping("validateCode")
public String validateCode(@RequestParam("code") Strin code,@RequestParam("phoneNum") String phoneNum) {
	// 得到 redis 连接
	Jedis jedis = jedisPool.getResource();
	// 通过 phoneNum 生成验证的 key
	String key = "phone:code:"+phoneNum;
	// 判断当前的 key 是否存在于 redis 中
	if (code.equals(jedis.get(key))) {
		log.info("验证成功,执行登录逻辑");
		return "登录成功";
	} else {
		return "验证码不正确!";
	}
}
--------------
if (code.equals(jedis.get(key))) {
		log.info("验证成功,执行登录逻辑");
		return "登录成功";
	} 
	return "验证码不正确!";
	

防短信攻击

1. 此处我们需要获取用户的 IP 地址,才能完成后续的业务,获取的时机在于用户发送获取验证码请求时,并且将 ip 地址生成 Redis 中的 key: phonecode: 具体 ip

2. 每次获取验证码时,判断如果 key 不存在 (第一次获取验证码用户) 进行 incr +1 操作,并设置过期时间5分钟,如果存在 (同一用户)对当前生成的 key 进行 incr + 1 操作

3. 判断如果当前用户访问超过3次,生成 Redis key: lockip:具体ip,锁定次用户12小时无法获取验证码

获取验证码方法来获取用户IP

@GetMapping("getValidateCode")
public String getValidateCode(@RequestParam("phoneNum") String phoneNum,HttpServletRequest request){
	/**
	* 防止短信轰炸逻辑
	* 1. 根据用户IP生成 Redis Key: protectCode:具体ip
	* 2.每次获取验证码:判断key是否存在
	* 如果不存在:执行 incr+1操作,并设置过期时间5分钟
	* 如果存在:直接执行 incr+1
	* 3.判断如果当前用户访问超过3次,生成 Redis key: lockIP:具体ip,
	* 锁定次用户12小时无法获取验证码(设置过期时间为12小时)
	*/
	// 获得 Jedis 连接
	Jedis jedis = jedisPool.getResource();
	
	// 优化代码,用户访问进来以后,直接判断redis中是否有对应的lockip,如果有直接返回
	// IP锁定12小时
	String lockIp="lockIp"+this.getProtectIp(request);
    if (jedis.exists(lockIp)) {
        return "限制访问12小时后解锁";
    }
    
    // 保护 key
    String protectkey = "protectCode:"+this.getProtectIp(request);
	// 判断保护 key 是否存在
	if (!jedis.exists(protectkey)) {
        // incr 命令可以直接创建 key
        jedis.incr(protectkey);
        jedis.expire(protectkey,300);
    } else {
        jedis.incr(protectkey);
    }
    if (Integer,parseInt(jedis,get(protectkey))>3) {
        jedis.set(lockIp,this.getProtectIp(request));
        jiedis.expire(protectkey,60*60*12);
        return "限制访问12小时后解锁";
    }
}

通用API

Spring Data Redis 中提供了如下的内容

1. 对不同Redis客户端的整合(Lettuce 和 Jedis)

2. 提供了 RedisTemplate 统一 API 操作 Redis

3. 支持 Redis 订阅发布模型

4. 支持 Redis 哨兵和集群

5. 支持基于 Lettuce 的响应式编程 (底层就是 Netty)

6. 支持 JDK,JSON,字符串,Spring对象的数据序列化反序列化

通用API

在我们使用 Jedis 的时候,如果我们想要完成一些 Redis 的操作,对应的命令其实就是对应的方法,比如 Set在 Jedis 中也就是 set() 方法,虽然很好上手,但是这会导致我们的代码比较臃肿,而既然 SpringData出手整合了,它必然会按照一定的规律做进一步的封装,具体如下:

redisTemplate.ops...方法囊括了几乎所有的 Redis 不同数据类型的命令操作

1. 操作 String 类型数据:redisTemplate.opsForValue()

2. 操作 List 类型数据:redisTemplate.opsForList()

3. 操作 Hast 类型数据:redisTemplate.opsForHash()

4. 操作 Set 类型数据:redisTemplate.opsForSet()

5. 操作 ZSet 类型数据:redisTemplate.opsForZSet()

6. 以上这些方法返回的都是 ...Operations 类型,比如

ValueOperation其中包括的就是String类型的所有操作:Set,Get等 注意:RedisTemplate中还封装着一些通用的或者特殊的操作

具体演示

我们会采用 SpringBoot 项目来进行演示,因为 SpringBoot 已经提供了对 Spring Data Redis 的支持,使用起来也非常简单,只要记住几点:

1.引入依赖

2.简单配置

3.拿到 RedisTemplate 即可

我们可以在新建一个项目,用于演示 RedisTemplate

引入依赖

第一件事,我们需要导入 RedisTemplate 依赖和 commons-pool连接池依赖 (其实在创建 SpringBoot 项目的时候可以直接选择,不需要手动导入)

Jedis 与 RedisTemplate 底层使用的连接池都是 commons-pool2,所以需要导入它

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-pool2</artifactId>
</dependency>

----alibaba----
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
</dependency>

RedisAutoConfiguration.class

//	由 IntelliJ IDEA 从 .class 文件重新创建的源代码
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
// 由 FernFlower 反编译器提供支持

package org.springframework.boot.autoconfigure.data.redis;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

原因:在SpringBoot2.x版本以后,从原来的Jedis替换成了 Lettuce,所以2.x以后开始默认使用Lettuce座位Redis客户端,Lettuce客户端基于Netty的NIO框架实现,只需要维持单一的连接即可高效支持业务并发请求。同时,Lettuce支持的特性更加全面,其性能表现并不逊于,甚至优于Jedis.

简单理解:

1.jedis: 采用的直连,多个线程操作的话,是不安全的,如果想要避免线程安全问题,就需要使用 jedisPool 连接池,但是也会有一些线程过多等其他问题,类似于BIO

  1. Lettuce: 底层采用Netty,实例可以在多线程中进行共享,不存在线程安全问题!类似NIO

这里可以看一下 Lettuce-core 底层引用的包包含 Netty

配置文件

如果我们不知道具体要配置什么内容,其实可以通过源码来进行查看,因为所有的SpringBoot配置类都有一个自动装配类型,自动装配类型都会绑定一个 properties 配置文件

StringRedisTemplate

其实实际开发中,如果为了节省空间,并不会完全使用JSON序列化来处理 value,而是统一采用 String 序列化器,存储 java 对象也是如此,这就意味着我们需要重新编写 RedisTemplate,但是 SpringBoot 其实提供了一个 StringRedisTemplate,通过它我们就可以完成以上的需求

@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
	StringRedisTemplate template = new StringRedisTemplate();
	template.setConnectionFactory(redisConnectionFactory);
	return template;
}

1.自定义RedisTemplate模板,RedisConfig.java

package com.qf.redistemplate.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    // 自定义的RedisTemplate
    @Bean // 注入一下
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 为了研发方便,key 为 String 类型
        RedisTemplate<String, Object> template = new RedisTemplate();
        // 设置连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        // 序列化配置,通过 JSON 解析任意对象
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        // 设置 key 序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置 value 序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

2. 自定义 entity.User.java

package com.qf.redistemplate.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String name;
    private int age;
}

3. 测试文件 RedisTemplateApplicationTests.java

package com.qf.redistemplate;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qf.redistemplate.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

@SpringBootTest
class RedisTemplateApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("qf1","千锋教育");
        System.out.println(redisTemplate.opsForValue().get("qf1"));
    }

    @Test
    void saveUser() {
        redisTemplate.opsForValue().set("user:1",new User("MASK",20));
        System.out.println(redisTemplate.opsForValue().get("user:1"));
    }

    // JSON工具
    private ObjectMapper mapper = new ObjectMapper();


    @Test
    void StringTemplate() throws JsonProcessingException {
        User user = new User("qf",18);
        // 手动序列化
        String json = mapper.writeValueAsString(user);
        // 写入数据
        stringRedisTemplate.opsForValue().set("user:2",json);
        // 读取数据
        String val = stringRedisTemplate.opsForValue().get("user:2");
        // 反序列化
        User u = mapper.readValue(val, User.class);
        System.out.println(u);
    }
}

额外

其实在企业中用的最多的还是RedisTemplate,那么为了解决以上的问题,其实可以再自定义的RedisTemplate 中来进行解决,这里给大家提供一个模板,可以在企业中直接使用

RedisConfig.java

package com.qf.redistest.config

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {
	@Bean
	public RedisTemplate<String,object>redisTemplate(RedisConnectionFactory factory) {
		// 为了研发方便 key 直接为 String 类型
		RedisTemplate<String,Object> template = new RedisTemplate<>();
		template.setConnectFactory(factory);
		// 使用Jackson2JsonRedisSerializer jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
		ObjectMapper mapper = new ObjectMapper();
		mapper.setVisibility(PropertyAccessor,ALL,JsonAutoDetect,Visibility,ANY);mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper,DefaultTyping.NON_FINAL,JsonTypeInfo.As.PROPERTY);
		// 设置 key 序列化
		template.setKeySerializer(RedisSerializer.string());
		template.setHashkeySerializer(RedisSerializer.string());
		// 设置 value 序列化
		template.setValueSerializer(jsonRedisSerializer);
		template.setHashkeySerializer(jsonRedisSerializer);
		template.afterPropertiesSet();
		return template;
	}
}

Redis持久化

Redis是内存数据库,所以如果不把数据保存到磁盘上,服务器一旦进程退出,或者断电,数据就会丢失,所以 Redis 提供了持久化功能

RDB(Redis数据库):RDB持久性以指定的时间间隔执行数据集的时间点快照。

AOF(仅追加文件):AOF持久性记录服务器接收到的每个写操作。然后可以在服务器启动时再次重播这些操作,从而重建原始数据集。命令使用与Redis协议本身相同的格式进行记录

要注意的是 Redis7提供了新的持久化模式:RDB+AOF:可以在同一个实例中组合 AOF 和 RDB.

RDB

1. RDB持久化是通过创建数据的快照实现的。具体来说,Redis会在指定的时间间隔内,将内存中的数据集快照写入磁盘,也就是创建了一个数据及的副本。

2. 这个时间间隔可以通过 "sava" 配置选项进行设置,如 "save 900 1" 表示如果900秒内有至少1个key变化,则创建一个 snapshots 快照。

3. RDB 是 Redis 默认的持久化方式,主要有点是能最大化 Redis 的性能,并且生成的 RDB 文件非常适合于全量复制,数据备份等场景。

4. 但是,如果 Redis 意外宕机,你可能会丢失最后一次快照以后修改的所有数据。

5. RDB 保存文件:dump.rdb 默认保存在当前运行目录

6V7版本差异

实际上 redis6 版本的配置和7版本发生了很多大的变化

6.0.16 以下版本

使用 redis-cli 命令连接到Redis服务器,然后发送 SHUTDOWN命令

127.0.0.1:6378> SHUTDOWN
not connected>
[root@mail ~]# systemctl restart redis	# 重启服务
[root@mail ~]# redis-cli -p 6378 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6378>

RDB触发条件

默认触发在服务停止时

一. 自动触发

在这之前我们需要先了解一下对应的配置
stop-writes-on-bgsave-erfor yes	# 持久化如果出错,是否还需要继续工作!
rdbcompression yes	# 是否压缩 rdb 文件,需要消耗一些 cpu 资源!
rdbchecksum yes		# 保存 rdb 文件的时候,进行错误的检查校验!
dbfilename dump.rdb	# rdb 文件名称
dir ,/	# rdb  文件保存的目录!

我们来演示一下

1. 设置5秒内有2次修改触发RDB

2. 改变 rdb 文件名字为 dump6379.rdb

3. 文件生成为位置在 /root/opt

总结

1. 当我们 redis 一旦出现问题,服务重启,服务关闭以后,再次启动时就会读取备份文件,恢复数据。

2. 为了避免出现备份被删除或者丢失的问题,一般情况下我们会定时把 redis 中的备份数据迁移到别处以防止数据丢失。

3. 还有一点要注意:设置时间不能太短,加入1秒就备份数据,会导致大量的磁盘IO,造成磁盘压力。

二,手动触发

命令:通过save bgsave命令能够手动的备份数据

1. 在redis中使用save命令,此命令有Redis主进程执行,会阻塞所有命令,但是这个方式如果数据量大的时候会导致效率较慢

2. 所以比较推荐执行 bgsave,此命令会开启子进程执行 rdb,避免主进程受影响(生成必须用bgsave 这个命令)

演示:此时我们在5秒内只修改一条数据,此时备份不会生效,但是如果我就向保存这条数据,就可以通过 save 或者 bgsave 命令来保存

AOF

Redis 提供的一种持久化策略,也被称为追加模式或日志模式。与Redis 的另一种持久化策略RDB不同,AOF是通过保存Rrdis服务器所执行的写命令来记录数据库的状态。

简单理解:Redis 处理的每一个写命令都会记录在AOF文件,可以看做时命令日志文件。

其实这种方式就是当 Redis 有要求不能丢失任何写的内容,可以采用 AOF 持久化方式。

由于在使用 AOF 这种持久化方式时, Redis 会将每一个收到的命令都通过 write 函数追加到文件中(默认 appendonly.aof).当 redis 重启时会通过重新执行文件中保存的写命令在内存中重建整个数据库内容。

AOF命令

首先AOF默认是关闭的,需要修改 redis.conf 配置文件来开启
appendonly yes # 是否开启 AOF 默认 no
appendfilename "appendonly.aof" # AOF 文件名
appenddirname "appendonlydir" # Redis7 新增,用于存储所有 AOF 文件的目录名称

AOF命令记录频率可以通过 redis.conf 配置文件控制

appendfsync always  # 表示每执行一次写命令,立即记录到 AOF 文件 性能最差
appendfsync everysec # 写命令执行完先放入AOF缓冲区,然后表示每个1秒钟将缓冲区数据写入AOF文件,默认(最多丢失1秒内的数据)
appendfsync no  # 写命令执行完放入 AOF 缓冲区,由操作系统决定何时将缓冲区内容写入磁盘

由此可以看出 always 安全性最高性能最差,以此类推逐步提高性能减少安全性

从 Redis7.0.0开始,Redis 使用多部分AOF机制(AOFRW).即把原来的单个AOF文件撤分为基础文件(最多一个)和增量文件(可能不止一个)。基础文件表示重写AOF时存在的数据的初始(RDB 或 AOF 格式)快照。增量文件包自上次创建基本 AOF 文件以来的增量更改。所有这些文件都放在单独的目录中,并由清单文件跟踪,这是为了解决随着 Redis 处理的写命令增多,AOF文件也会变得越来越大,命令回放的时间也会增多的问题。

  • BASE: 表示基础的 AOF,它一般由子进程通过重写产生,最多只有一个

  • INCR: 表示增量AOF,它一般会在 AOFRW 开始创建。可能存在多个

  • HISTORY: 表示历史的AOF,它由 BASE 和 INCR AOF 变化而来,每次 AOFRW 成功完成时,本次 AOFRW 之前对应的 BASE 和 INCR 都变成 HISTORY,此类型 AOF 会被 Redis 自动删除

演示

1. 首先注释掉之前的RDB,需要注释掉之前的策略,并且编写 save "" 表示禁用RDB

# save 5 2
save ""

将 '#save ""' 前面的注释去掉,即为 'save ""' 表示将 RDB 关闭了或者说表示禁用RDB

2. 找到 AOF 配置

appendonly yes # 默认 no
将 'appendonly no' 修改为 'appendonly yes' 表示开启 AOF 模式

3. 可以设置保存 AOF 文件的目录名称,文件会保存在 RDB 你设置的目录中下的 appenddirname 目录中

[root@mail appendonlydir]# vim /opt/redis-7.0.11/redis.conf

appenddirname "appendonlydir" # Redis7 新增,用于存储所有 AOF 文件的目录名称

[root@mail bin]# cd /root/opt
[root@mail opt]# ls
appendonlydir  cni  containerd  redis.log
[root@mail opt]# pwd
/root/opt
[root@mail opt]# cd appendonlydir
[root@mail appendonlydir]# ls
appendonly.aof.1.base.rdb  appendonly.aof.1.incr.aof  appendonly.aof.manifest
[root@mail appendonlydir]# ll
total 12
-rw-r--r--. 1 root root 89 Jul  9 08:42 appendonly.aof.1.base.rdb
-rw-r--r--. 1 root root 52 Jul  9 08:44 appendonly.aof.1.incr.aof
-rw-r--r--. 1 root root 88 Jul  9 08:42 appendonly.aof.manifest
[root@mail appendonlydir]#

此时,看到以上3个文件,表示 AOF 已经成功开启了

AOF rewrite (AOFRW) 机制

AOFRW 会移除 AOF 中冗余的写命令,以等效的方式重写,生成一个新的 AOF 文件,来达到减少 AOF 文件大小的木的

机制触发有两种方式

1. 自动

此时为两种条件同时满足,才会触发机制

条件1:根据上次重写后的 AOF 文件大小,判断当前 AOF 文件大小是否增长了1倍

条件2: 重新时需要满足设定的文件大小默认 64 MB

2 手动

执行命令:bgrewriteaof 命令

127.0.0.1:6378> set aa 10
OK
127.0.0.1:6378> set aa 30
OK
127.0.0.1:6378> set aa 50
OK
127.0.0.1:6378> BGREWRITEAOF
Background append only file rewriting started
127.0.0.1:6378> keys *
1) "dd"
2) "aa"
127.0.0.1:6378> get aa
"50"
127.0.0.1:6378>

3 原理

a. 触发重写机制以后,Redis 会创建一个子进程,此进程会读取现有的 AOF 文件,将包含的指令压缩并写入到临时文件中

b. 此时主进程如果收到新的写指令,会把这些新指令累计到缓存区中,同时也会写入到原有的 AOF 文件中,保证重写过程若失败,原有文件依旧可用

c. 当子进程重写完成后,主进程会把重写期间产生的变化写入到新的 AOF 文件中

d. 追加结束之后,新的ADF会覆盖老的 AOF

RDB与AOF混合持久化

RDB+AOF: 你还可以在同一个实例中组合AOF和RDB.

以上是官网给出的建议,要注意的是,如果AOF被开启,那么 Redis 会优先加载 AOF 文件

具体应如何应用

官方给出的解释如下:

一般情况下,如果你想要关系型数据库提供的数据安全程度相当的数据安全性,则应该使用者两种持久方法。

如果你非常关心数据,但任然可以忍受灾难发生时几分钟的数据丢失,那么你可以简单地单独使用 RDB.

有许多用户单独使用 AOF ,但我们不鼓励这样做,因为时不时地用拥有 RDB 快照对于进行数据库备份,更快地重新启动以及在 AOF 引擎出现错误是是一个好主意。

主意: 同时开启 RDB 和 AOF 持久化时,重启时会加载 AOF 文件,不会加载 RDB 文件,除非开始判断发现 AOF 文件不存在,会找 RDB 文件。

开启方式

设置 redis.conf 配置文件中的

vim /opt/redis-7.0.11/redis.conf 
aof-use-rdb-preamble yes # 开启混合模式  默认为 no

总结:

1. RDB 用做全量持久化

2. AOF 做增量持久化

3. 使用 RDB 进行快照存储,使用 AOF 持久化记录所有写操作,当重写策略满足或手动触发时,将最新的数据储存为为 RDB

Redis 订阅发布

Redis 的发布/订阅 (Pub/Sub) 是一种消息通信模式:

1. 发送者 (Pub,即 publisher) 发送消息

2. 订阅者 (Sub,即 subscriber) 接收消息

Redis 客户端可以订阅任何数量的频道 (channel).当有新消息发布到某个频道时,这个消息就会被发送到订阅它的客户端。

其实这个所谓的发布订阅,大家几乎天天都能见到,比如说我现在在这个账号发布了视频,你们为什么能够看到,是因为你们关注了我,所以各位想看最新的课程内容,请记得关注,这样你们就可以收到课程更新的提醒了。其实值就是一种发布订阅机制的体现。

常见命令

SUBSCRIBE channel [channel ...];

订阅给定的一个或多个频道的信息

PUBLISH channel message;

将信息发送到指定的频道

UNSUBSCRIBE [channel [channel ...]];

指退订给定的频道。如果没有指定频道,则退订所有频道。

PSUBSCRIBE pattern [pattern ...];

使用模式匹配订阅频道。

PUNSUBSCRIBE [pattern [pattern ...]]

这个命令用于退订所有给定模式的频道。如果没有参数,那么客户端使用 PUNSUBSCRIBE 退订所有模式

订阅端

127.0.0.1:6378> ping
PONG
127.0.0.1:6378> SUBSCRIBE qf
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "qf"
3) (integer) 1
1) "message"
2) "qf"
3) "redis"
1) "message"
2) "qf"
3) "redisTemplate"
1) "message"
2) "qf"
3) "redis7.0.15"
1) "message"
2) "qf"
3) "MaskRedis"

发布端

127.0.0.1:6378> ping
PONG
127.0.0.1:6378> PUBLISH qf redis
(integer) 1
127.0.0.1:6378> PUBLISH qf redisTemplate
(integer) 1
127.0.0.1:6378> PUBLISH qf redis7.0.15
(integer) 1
127.0.0.1:6378> PUBLISH qf MaskRedis
(integer) 1
127.0.0.1:6378>


使用场景

1. 实时信息系统

2. 实时聊天

3. 订阅,关注功能

Redis 主从复制介绍

Redis 主从复制是 Redis 内置的一种数据冗余和备份方式,同时也是分发读查询负载的一种方法。通过主从复制,可以有多个从服务器 (Slave) 复制一个主服务器 (Master) 的数据。在这个系统中,数据的复制是单向的,只能由主服务器 ( Master) 到从服务器 (Slave).

重点:主节点可读写

从节点: 只能读

主从复制,读写分离!几乎百分之八十情况下都是读的操作,所以多从节点才能减缓服务器压力。

为什么要有主从复制

几乎所有运用了 Redis 的软件中,都不可能只用一台 Redis:

1. 结构上,单个 Redis服务器可能会出现单点故障,并且一台服务器处理所有请求,负载压力较大

2. 容量上,单个 Redis 服务器内存容量有限,就算服务器内存很大也不可能全部用于 Redis,一般来说单台 Redis 服务器内存最大不超过20G

主从复制的主要作用

1. 数据冗余:主要复制实现了数据的热备份,是持久化的一种数据冗余方式

2. 故障恢复:主节点一旦出现问题,可以由从节点提供服务,避免出现程序部可用的情况,实现快速故障恢复。

3. 负载均衡:在主从复制的基础之上,配合读写分离,主节点提供写服务,由从节点提供读服务,分担服务器负载,尤其是在读多写少场景下,可以大大提高 Redis 并发量

4. 高可用 (集群)基石:主从复制是集群和哨兵模式的基础

主从复制搭建

1. 主从复制最基本的也需要1主2从

2. 默认情况下,每台 Redis 服务器都是主节点

3. 一个主节点可以有多个从节点,但是一个从节点只能用一个主节点

环境搭建

只需要配置从库,因为每个 Redis 都是主库

查看库信息命令: info replication

# 147
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:a0ad380769c0fb3bd3251abc953ce1c80e7b1826
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379>

# 148
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:845e5a1c55b95949af7b58e96462f34fc32909df
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379>


# 149 
127.0.0.1:6379> info replication # 查看当前库信息
# Replication
role:master		# 角色
connected_slaves:0	# 从机数为0
master_failover_state:no-failover
master_replid:059146ff07ec981aab0d3f20c513cf2ac84b5a2f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379>

同步文件

root@redis-slave1:~# cd /opt
root@redis-slave1:/opt# ls
redis-7.0.11.tar.gz
root@redis-slave1:/opt# scp redis-7.0.11.tar.gz root@192.168.22.19:/opt/
The authenticity of host '192.168.22.19 (192.168.22.19)' can't be established.
ED25519 key fingerprint is SHA256:8z2EMkn2zaQiC+araUfuMBixAIKr46kAcS/8gIarZos.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.22.19' (ED25519) to the list of known hosts.
root@192.168.22.19's password:
redis-7.0.11.tar.gz                                                                                                              100% 2918KB  28.2MB/s   00:00
root@redis-slave1:/opt#


# 查看同步结果
root@redis-slave2:~# cd /opt
root@redis-slave2:/opt# ls
redis-7.0.11.tar.gz
root@redis-slave2:/opt# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:0c:29:f5:55:cc brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    inet 192.168.22.19/24 metric 100 brd 192.168.22.255 scope global dynamic ens33
       valid_lft 1373sec preferred_lft 1373sec
    inet6 fe81::20c:29ff:fef5:51cc/64 scope link
       valid_lft forever preferred_lft forever
root@redis-slave2:/opt#


# 查看磁盘情况
[root@mail ~]# df -h
Filesystem           Size  Used Avail Use% Mounted on
devtmpfs             3.8G     0  3.8G   0% /dev
tmpfs                3.8G  168K  3.8G   1% /dev/shm
tmpfs                3.8G  419M  3.4G  11% /run
tmpfs                3.8G     0  3.8G   0% /sys/fs/cgroup
/dev/mapper/cl-root   70G   42G   29G  59% /
/dev/sda1           1014M  359M  656M  36% /boot
/dev/mapper/cl-home   76G  7.5G   69G  10% /home
tmpfs                774M   12K  774M   1% /run/user/42
tmpfs                774M     0  774M   0% /run/user/0
[root@mail ~]#

环境搭建

首先我们需要至少3台服务器,我们手里一般之后一台Redis,所以我们需要通过修改配置文件的方式来模拟3台机器,修改配置文件,将端口信息改变,同时配置文件也需要三份:

redis.conf79:6379
redis.conf80:6380
redis.conf81:6381

cp redis.conf redis79/80/81.conf
每个配置文件要修改的配置:
注意:第一台主服务器不需要修改端口

设置监听地址
bind 0.0.0.0

关闭保户模块
protected mode no

port 6380/81 # 修改两台从机端口号
daemonize yes # 允许后台运行
pidfile	/var/run/redis_6380/81.pid	# 进程号修改
logfile	"redis79/80/81.log"	# 日志名称改一下
# save	"" 注释掉
dbfilename dump6379/80/81.rdb	# RDB文件名
dir ./	# 恢复默认
appendonly no # 关闭 AOF 模式

#========两台从机注意,必须设置主机登录密码=========
masterauth 753159
修改之后开启3台 Redis

注意:通过系统服务关闭原有 Redis,然后我们通过配置文件来手动启动3个 Redis
systemctl stop redis # 通过系统服务关闭原始的 Redis

cd /usr/local/bin
redis-server /root/opt/redis-7.0.11/redis79.conf
redis-server /root/opt/redis-7.0.11/redis80.conf
redis-server /root/opt/redis-7.0.11/redis81.conf

redis-server /opt/redis-7.0.11/redis78.conf
redis-server /opt/redis-7.0.11/redis80.conf
redis-server /opt/redis-7.0.11/redis81.conf
ps -ef | grep redis

=======================================================
实践
[root@mail redis-7.0.11]# cp redis.conf redis78.conf
[root@mail redis-7.0.11]# cp redis.conf redis80.conf
[root@mail redis-7.0.11]# cp redis.conf redis81.conf
[root@mail redis-7.0.11]# ls
00-RELEASENOTES     CONTRIBUTING.md  dump.rdb  MANIFESTO     redis80.conf  redis.conf.bak   runtest-moduleapi  sentinel.conf  TLS.md
BUGS                COPYING          INSTALL   README.md     redis81.conf  runtest          runtest-sentinel   src            utils
CODE_OF_CONDUCT.md  deps             Makefile  redis78.conf  redis.conf    runtest-cluster  SECURITY.md        tests
[root@mail redis-7.0.11]# vim redis80.conf
[root@mail redis-7.0.11]# vim redis78.conf
[root@mail redis-7.0.11]# vim redis81.conf
[root@mail redis-7.0.11]# pwd
/opt/redis-7.0.11
[root@mail redis-7.0.11]# cd /usr/local/bin
[root@mail bin]# systemctl stop redis
[root@mail bin]# redis-server /opt/redis-7.0.11/redis78.conf
[root@mail bin]# redis-server /opt/redis-7.0.11/redis80.conf
[root@mail bin]# redis-server /opt/redis-7.0.11/redis81.conf
[root@mail bin]# ps -ef | grep redis
[root@mail ~]# ps -ef | grep redis
root     3335034  1  0 20:46 ?        00:00:00 redis-server 0.0.0.0:6378
root     3335365  1  0 20:46 ?        00:00:00 redis-server 0.0.0.0:6380
root     3335707  1  0 20:46 ?        00:00:00 redis-server 0.0.0.0:6381
root     3347349 3233034  0 20:55 pts/0 00:00:00 grep --color=auto redis
[root@mail ~]#
=====
在Redis核心配置文件中修改公用参数
#通过本机客户端访问redis服务,如果需要其它设备进行访问,可设置为0.0.0.0
bind 127.0.0.1
#如果以后台的方式运行,我们就需要指定一个pid文件
pidfile ""
#RDB文件保存的位置
dir ""
#保护模式
protected-mode no
#后台运行
daemonize yes
#从服务器连接主服务器
masterauth <master-password>
#设置从服务器只读
replica-read-only yes
#Redis服务器连接密码,没有可注释
requirepass foobared

Master重要参数
# #服务运行占用的端口号,从服务器分别设置为6381、6382
port 6380

# Slave1重要参数
#服务运行占用的端口号,从服务器分别设置为6381、6382
port 6381
#设置主从关系,5.0版本前为:slaveof
replicaof 127.0.0.1 6380

# Slave2 重要参数
#服务运行占用的端口号,从服务器分别设置为6381、6382
port 6382
#设置主从关系,5.0版本前为:slaveof
replicaof 127.0.0.1 6380

看到如下图表示已经成功了

关闭 Redis

127.0.0.1:6381> SHUTDOWN
not connected> quit
[root@mail ~]#

主从复制具体搭建

一主二从

1. 配置6379为主机,先来登录一下
redis-cli -p 6379 -a 753159
redis-cli -p 6380 -a 753159
redis-cli -p 6381 -a 753159

2. 配置命令 (只需要配置从机) 命令是临时配置主从复制,重启动就失效了replicaof
语法:SLAVEOF 主机IP  主机端口
语法:slaveof <master ip> <master port>
SLAVEOF 127.0.0.1 6379 # ip地址 端口号  示例

SLAVEOF 192.168.10.113 6378
SLAVEOF 127.0.0.1 6378	# 配置从机命令
127.0.0.1:6378> SLAVEOF 127.0.0.1 6378	执行
OK
SLAVEOF 127.0.0.1 6380	# 配置从机命令
127.0.0.1:6380> SLAVEOF 127.0.0.1 6380	执行
OK

主机状态  info replication # 查看状态
# 6381 主机
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:562265029b94becc35f6540ce8f585ab981461cb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6381>





从机状态
# 6381 从机
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:8b6621bec55beca8d9c726bc731f4afb62e82401
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6381> SLAVEOF 192.168.10.113 6378
OK
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:192.168.10.113
master_port:6378
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_read_repl_offset:0
slave_repl_offset:0
master_link_down_since_seconds:-1
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:8b6621bec55beca8d9c726bc731f4afb62e82401
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6381>



# 6380 从机
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:8cb20fb5c2ea9ae39101339aa01728191cc0656e
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6380> SLAVEOF 192.168.10.113 6378
OK
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:192.168.10.113
master_port:6378
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_read_repl_offset:0
slave_repl_offset:0
master_link_down_since_seconds:-1
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:8cb20fb5c2ea9ae39101339aa01728191cc0656e
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6380>


#角色已经变成了slave,但是状态还是down,这时需要设置下主节点的连接密码才能变成up
从机 'master_link_status:down' 显示 'master_link_status:up'
此时需要在主机点设置连接密码 CONFIG set masterauth 123456
127.0.0.1:6378> CONFIG set masterauth 123456
OK


127.0.0.1:6380> replicaof 192.168.10.113 6378
OK Already connected to specified master
127.0.0.1:6380>

127.0.0.1:6381> replicaof 192.168.10.113 6378
OK Already connected to specified master
127.0.0.1:6381>

redis-cli -p 6378 -a 123456 > config set client-output-buffer-limit slave 1024mb 256mb 0

redis-cli -p 6380 -a 123456 > config set client-output-buffer-limit slave 1024mb 256mb 0

redis-cli -p 6381 -a 123456 > config set client-output-buffer-limit slave 1024mb 256mb 0


防火墙
firewall-cmd --zone=public --add-port=6379/tcp --permanent
firewall-cmd --zone=public --add-port=6380/tcp --permanent
firewall-cmd --zone=public --add-port=6381/tcp --permanent
firewall-cmd --zone=public --add-port=6382/tcp --permanent
[root@mail ~]# firewall-cmd --zone=public --add-port=6380/tcp
success
[root@mail ~]# firewall-cmd --zone=public --add-port=6381/tcp
success
[root@mail ~]# firewall-cmd --zone=public --add-port=6382/tcp
success


firewall-cmd --list-ports

systemctl restart firewalld.service

哨兵模式详解

相关概念

Redis 的哨兵模式 (Sentinel mode) 是一个高可用解决方案,当运行多个 Redis 实例并且需要自动故障转移时,哨兵模式非常有用。

哨兵模式主要有三个目标:

1. 监控: 哨兵模式会不断地检查主服务器和从服务器是否按预期工作。

2. 通知:如果某些Redis实例有故障,哨兵模式可以通过 API 向管理员或其他应用程序发送通知。

3. 自动故障转移:如果主服务器无法正常工作,哨兵模式可以开始一个故障转移过程,由一个从服务器升级为新的主服务器,并让其他服务器改变他们的主服务器为新的主服务器。

在一个典型的哨兵模式下,至少需要3个哨兵实例来避免 "脑裂" (网络分裂导致多个主服务器同时存在)。哨兵们会通过投票来决定主服务器是否已经下线,以及选择哪个从服务器升级为新的主服务器。

哨兵模式的实现是基于发布-订阅模式的,每个哨兵节点都会订阅其它哨兵节点的信息,这样当主服务器出现故障时,哨兵节点可以及时进行广播,实现快速故障转移。

具体来说,Redis 的哨兵模式有以下几个主要特点:

1. 哨兵模式自动转移失败的主服务器到一个从服务器。

2. 哨兵模式持续监控所有 Redis 服务器,以便在需要时报告错误

3. 通过提供一个基于哨兵的 API ,客户端可以自动发现新的主服务器地址。

Sentinel(哨兵)时基于心跳机制检测服务状态的,每隔1秒向每个实例发送一个ping命令。如果某个 sentinel发现某 Redis 实例未在规定时间内响应,则认为该实例主观下线,若超过指定数量 (quorum) 的 sentinel 都认为该实例主观下线,则该实例客观下线。quorum 的值最后超过 Sentinel 实例数的一半

在实际应用中,哨兵模式通常用于提高 Redis 的可用性,对于有高可用需求的系统,哨兵模式是一个很好的解决方案。

重点:实现自动转移失败的主服务器到另一个服务器上

1. 如果此时主服务器宕机,哨兵1检测到了,系统并不会立即进行 failover(故障转移)过程

2. 此时仅仅是哨兵1主管的认为主服务不可用,此现象为主观下线

3. 当后续的哨兵也检测到主服务不可用时,并且数量达到指定数量时

4. 哨兵之间就会进行一次投票,投票结果由1个哨兵发起,进行 failover 操作

5. 切换成功之后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器切换主机,这个过程为 客观下线。

选举过程

一旦发现 master 故障,sentinel 需要在 slave 中选择一个作为新的 master,选择依据是这样的:

1. 首先会判断 slave 节点与 master 节点断开时间长短,如果超过指定值(down-after-millseconds*10)则会排除该 slave 节点

2. 然后判断 slave 节点的 slave-priority 值,越小优先级越高,如果是0则永不参与选举

3. 如果 slave-priority一样,则判断 slave 节点的 offset 值,越大说明数据越新,优先级越高

4. 最后是判断 slave 节点的运行 id 大小,越小优先级越高

故障转移

1. Sentinel 给备选的节点发送 slaveof on one 命令,让该节点成为 Master

2. Sentinel 给其他 slave 发送 "slaveof ip 端口" 命令,开始从 Master 上同步数据

3. 最后 Sentinel 将故障节点标记为 slave (执行 slaveof ip 端口 命令),故障节点恢复以后也会成为新的 Master 的 slave

简单演示

演示之前,我们需要先看一下官方提供的 sentinel.conf 文件里的配置,我们可以通过工具来直接查看,大家会惊奇的发现,很多我们都认识

哨兵模式配置文件sentinel.conf

vim /opt/redis-7.0.11/sentinel.conf

[root@mail redis-7.0.11]# vim sentinel.conf
[root@mail redis-7.0.11]# cat sentinel.conf
# Example sentinel.conf

# By default protected mode is disabled in sentinel mode. Sentinel is reachable
# from interfaces different than localhost. Make sure the sentinel instance is
# protected from the outside world via firewalling or other means.
protected-mode no

# port <sentinel-port>
# The port that this sentinel instance will run on
port 26379

# By default Redis Sentinel does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis-sentinel.pid when
# daemonized.
daemonize no

# When running daemonized, Redis Sentinel writes a pid file in
# /var/run/redis-sentinel.pid by default. You can specify a custom pid file
# location here.
pidfile /var/run/redis-sentinel.pid

# Specify the log file name. Also the empty string can be used to force
# Sentinel to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile ""

# sentinel announce-ip <ip>
# sentinel announce-port <port>
#
# The above two configuration directives are useful in environments where,
# because of NAT, Sentinel is reachable from outside via a non-local address.
#
# When announce-ip is provided, the Sentinel will claim the specified IP address
# in HELLO messages used to gossip its presence, instead of auto-detecting the
# local address as it usually does.
#
# Similarly when announce-port is provided and is valid and non-zero, Sentinel
# will announce the specified TCP port.
#
# The two options don't need to be used together, if only announce-ip is
# provided, the Sentinel will announce the specified IP and the server port
# as specified by the "port" option. If only announce-port is provided, the
# Sentinel will announce the auto-detected local IP and the specified port.
#
# Example:
#
# sentinel announce-ip 1.2.3.4

# dir <working-directory>
# Every long running process should have a well-defined working directory.
# For Redis Sentinel to chdir to /tmp at startup is the simplest thing
# for the process to don't interfere with administrative tasks such as
# unmounting filesystems.
dir /tmp

# sentinel monitor <master-name> <ip> <redis-port> <quorum>
#
# Tells Sentinel to monitor this master, and to consider it in O_DOWN
# (Objectively Down) state only if at least <quorum> sentinels agree.
#
# Note that whatever is the ODOWN quorum, a Sentinel will require to
# be elected by the majority of the known Sentinels in order to
# start a failover, so no failover can be performed in minority.
#
# Replicas are auto-discovered, so you don't need to specify replicas in
# any way. Sentinel itself will rewrite this configuration file adding
# the replicas using additional configuration options.
# Also note that the configuration file is rewritten when a
# replica is promoted to master.
#
# Note: master name should not include special characters or spaces.
# The valid charset is A-z 0-9 and the three characters ".-_".
sentinel monitor mymaster 127.0.0.1 6379 2

# sentinel auth-pass <master-name> <password>
#
# Set the password to use to authenticate with the master and replicas.
# Useful if there is a password set in the Redis instances to monitor.
#
# Note that the master password is also used for replicas, so it is not
# possible to set a different password in masters and replicas instances
# if you want to be able to monitor these instances with Sentinel.
#
# However you can have Redis instances without the authentication enabled
# mixed with Redis instances requiring the authentication (as long as the
# password set is the same for all the instances requiring the password) as
# the AUTH command will have no effect in Redis instances with authentication
# switched off.
#
# Example:
#
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd

# sentinel auth-user <master-name> <username>
#
# This is useful in order to authenticate to instances having ACL capabilities,
# that is, running Redis 6.0 or greater. When just auth-pass is provided the
# Sentinel instance will authenticate to Redis using the old "AUTH <pass>"
# method. When also an username is provided, it will use "AUTH <user> <pass>".
# In the Redis servers side, the ACL to provide just minimal access to
# Sentinel instances, should be configured along the following lines:
#
#     user sentinel-user >somepassword +client +subscribe +publish \
#                        +ping +info +multi +slaveof +config +client +exec on

# sentinel down-after-milliseconds <master-name> <milliseconds>
#
# Number of milliseconds the master (or any attached replica or sentinel) should
# be unreachable (as in, not acceptable reply to PING, continuously, for the
# specified period) in order to consider it in S_DOWN state (Subjectively
# Down).
#
# Default is 30 seconds.
sentinel down-after-milliseconds mymaster 30000

# IMPORTANT NOTE: starting with Redis 6.2 ACL capability is supported for
# Sentinel mode, please refer to the Redis website https://redis.io/topics/acl
# for more details.

# Sentinel's ACL users are defined in the following format:
#
#   user <username> ... acl rules ...
#
# For example:
#
#   user worker +@admin +@connection ~* on >ffa9203c493aa99
#
# For more information about ACL configuration please refer to the Redis
# website at https://redis.io/topics/acl and redis server configuration
# template redis.conf.

# ACL LOG
#
# The ACL Log tracks failed commands and authentication events associated
# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked
# by ACLs. The ACL Log is stored in memory. You can reclaim memory with
# ACL LOG RESET. Define the maximum entry length of the ACL Log below.
acllog-max-len 128

# Using an external ACL file
#
# Instead of configuring users here in this file, it is possible to use
# a stand-alone file just listing users. The two methods cannot be mixed:
# if you configure users here and at the same time you activate the external
# ACL file, the server will refuse to start.
#
# The format of the external ACL user file is exactly the same as the
# format that is used inside redis.conf to describe users.
#
# aclfile /etc/redis/sentinel-users.acl

# requirepass <password>
#
# You can configure Sentinel itself to require a password, however when doing
# so Sentinel will try to authenticate with the same password to all the
# other Sentinels. So you need to configure all your Sentinels in a given
# group with the same "requirepass" password. Check the following documentation
# for more info: https://redis.io/topics/sentinel
#
# IMPORTANT NOTE: starting with Redis 6.2 "requirepass" is a compatibility
# layer on top of the ACL system. The option effect will be just setting
# the password for the default user. Clients will still authenticate using
# AUTH <password> as usually, or more explicitly with AUTH default <password>
# if they follow the new protocol: both will work.
#
# New config files are advised to use separate authentication control for
# incoming connections (via ACL), and for outgoing connections (via
# sentinel-user and sentinel-pass)
#
# The requirepass is not compatible with aclfile option and the ACL LOAD
# command, these will cause requirepass to be ignored.

# sentinel sentinel-user <username>
#
# You can configure Sentinel to authenticate with other Sentinels with specific
# user name.

# sentinel sentinel-pass <password>
#
# The password for Sentinel to authenticate with other Sentinels. If sentinel-user
# is not configured, Sentinel will use 'default' user with sentinel-pass to authenticate.

# sentinel parallel-syncs <master-name> <numreplicas>
#
# How many replicas we can reconfigure to point to the new replica simultaneously
# during the failover. Use a low number if you use the replicas to serve query
# to avoid that all the replicas will be unreachable at about the same
# time while performing the synchronization with the master.
sentinel parallel-syncs mymaster 1

# sentinel failover-timeout <master-name> <milliseconds>
#
# Specifies the failover timeout in milliseconds. It is used in many ways:
#
# - The time needed to re-start a failover after a previous failover was
#   already tried against the same master by a given Sentinel, is two
#   times the failover timeout.
#
# - The time needed for a replica replicating to a wrong master according
#   to a Sentinel current configuration, to be forced to replicate
#   with the right master, is exactly the failover timeout (counting since
#   the moment a Sentinel detected the misconfiguration).
#
# - The time needed to cancel a failover that is already in progress but
#   did not produced any configuration change (SLAVEOF NO ONE yet not
#   acknowledged by the promoted replica).
#
# - The maximum time a failover in progress waits for all the replicas to be
#   reconfigured as replicas of the new master. However even after this time
#   the replicas will be reconfigured by the Sentinels anyway, but not with
#   the exact parallel-syncs progression as specified.
#
# Default is 3 minutes.
sentinel failover-timeout mymaster 180000

# SCRIPTS EXECUTION
#
# sentinel notification-script and sentinel reconfig-script are used in order
# to configure scripts that are called to notify the system administrator
# or to reconfigure clients after a failover. The scripts are executed
# with the following rules for error handling:
#
# If script exits with "1" the execution is retried later (up to a maximum
# number of times currently set to 10).
#
# If script exits with "2" (or an higher value) the script execution is
# not retried.
#
# If script terminates because it receives a signal the behavior is the same
# as exit code 1.
#
# A script has a maximum running time of 60 seconds. After this limit is
# reached the script is terminated with a SIGKILL and the execution retried.

# NOTIFICATION SCRIPT
#
# sentinel notification-script <master-name> <script-path>
#
# Call the specified notification script for any sentinel event that is
# generated in the WARNING level (for instance -sdown, -odown, and so forth).
# This script should notify the system administrator via email, SMS, or any
# other messaging system, that there is something wrong with the monitored
# Redis systems.
#
# The script is called with just two arguments: the first is the event type
# and the second the event description.
#
# The script must exist and be executable in order for sentinel to start if
# this option is provided.
#
# Example:
#
# sentinel notification-script mymaster /var/redis/notify.sh

# CLIENTS RECONFIGURATION SCRIPT
#
# sentinel client-reconfig-script <master-name> <script-path>
#
# When the master changed because of a failover a script can be called in
# order to perform application-specific tasks to notify the clients that the
# configuration has changed and the master is at a different address.
#
# The following arguments are passed to the script:
#
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
#
# <state> is currently always "start"
# <role> is either "leader" or "observer"
#
# The arguments from-ip, from-port, to-ip, to-port are used to communicate
# the old address of the master and the new address of the elected replica
# (now a master).
#
# This script should be resistant to multiple invocations.
#
# Example:
#
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

# SECURITY
#
# By default SENTINEL SET will not be able to change the notification-script
# and client-reconfig-script at runtime. This avoids a trivial security issue
# where clients can set the script to anything and trigger a failover in order
# to get the program executed.

sentinel deny-scripts-reconfig yes

# REDIS COMMANDS RENAMING (DEPRECATED)
#
# WARNING: avoid using this option if possible, instead use ACLs.
#
# Sometimes the Redis server has certain commands, that are needed for Sentinel
# to work correctly, renamed to unguessable strings. This is often the case
# of CONFIG and SLAVEOF in the context of providers that provide Redis as
# a service, and don't want the customers to reconfigure the instances outside
# of the administration console.
#
# In such case it is possible to tell Sentinel to use different command names
# instead of the normal ones. For example if the master "mymaster", and the
# associated replicas, have "CONFIG" all renamed to "GUESSME", I could use:
#
# SENTINEL rename-command mymaster CONFIG GUESSME
#
# After such configuration is set, every time Sentinel would use CONFIG it will
# use GUESSME instead. Note that there is no actual need to respect the command
# case, so writing "config guessme" is the same in the example above.
#
# SENTINEL SET can also be used in order to perform this configuration at runtime.
#
# In order to set a command back to its original name (undo the renaming), it
# is possible to just rename a command to itself:
#
# SENTINEL rename-command mymaster CONFIG CONFIG

# HOSTNAMES SUPPORT
#
# Normally Sentinel uses only IP addresses and requires SENTINEL MONITOR
# to specify an IP address. Also, it requires the Redis replica-announce-ip
# keyword to specify only IP addresses.
#
# You may enable hostnames support by enabling resolve-hostnames. Note
# that you must make sure your DNS is configured properly and that DNS
# resolution does not introduce very long delays.
#
SENTINEL resolve-hostnames no

# When resolve-hostnames is enabled, Sentinel still uses IP addresses
# when exposing instances to users, configuration files, etc. If you want
# to retain the hostnames when announced, enable announce-hostnames below.
#
SENTINEL announce-hostnames no

# When master_reboot_down_after_period is set to 0, Sentinel does not fail over
# when receiving a -LOADING response from a master. This was the only supported
# behavior before version 7.0.
#
# Otherwise, Sentinel will use this value as the time (in ms) it is willing to
# accept a -LOADING response after a master has been rebooted, before failing
# over.

SENTINEL master-reboot-down-after-period mymaster 0





[root@mail redis-7.0.11]# pwd
/opt/redis-7.0.11
[root@mail redis-7.0.11]# ls
00-RELEASENOTES     COPYING     dump81.rdb  MANIFESTO         redis78.log       redis81.conf      redis.conf.bak     runtest-sentinel   src
BUGS                deps        dump.rdb    README.md         redis80.conf      redis81.conf.bak  runtest            SECURITY.md        tests
CODE_OF_CONDUCT.md  dump78.rdb  INSTALL     redis78.conf      redis80.conf.bak  redis81.log       runtest-cluster    sentinel.conf      TLS.md
CONTRIBUTING.md     dump80.rdb  Makefile    redis78.conf.bak  redis80.log       redis.conf        runtest-moduleapi  sentinel.conf.bak  utils
[root@mail redis-7.0.11]#

其中比较关键的,我们把他列出来看看

# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
	# master-name: 自定义
	# ip  : Redis ip
	# quorum: 表示最少有几个哨兵认可进行故障迁移
	
# 主服务器进入密码
# sentinel auth-pass mymaster 753159
#=============其他命令===================	

sentinel down-after-milliseconds <master-name> <milliseconds>
# 指定多少毫秒之后,主节点没有应答哨兵,此时哨兵主观上认为主节点下线
sentinel parallel-syncs <master-name> <nums>
# 表示允许并行同步的 slave 个数,当 Master 挂了后,哨兵会选出新的 Master,此时,剩余的 slave 会向新的 master 发起同步数据
sentinel failover-timeout <master-name> <milliseconds>
# 故障转移的超时时间,进行故障转移时,如果超过设置的毫秒,表示故障转移失败
sentinel notification-script <master-name> <script-path>
# 配置当某一事件发生时所需要执行的脚本
sentinel client-reconfig-script <master-name> <script-path>
# 客户端重新配置主节点参数脚本

案例演示

首先让我们的集群变为一主二从,然后配置哨兵配置文件,这里最好建立一个 mysentinel 目录,把配置放入到其中,依旧是建立3个文件,分别修改端口

注意:redis79.conf 也需要加上 masterauth 753159

在 /root/opt/redis-7.0.11此目录下建立一个mysentinel目录,然后直接编写三个文件

vim sentinel26379/80/81.conf
sentinel26379.conf: 26379
sentinel26380.conf: 26380
sentinel26381.conf: 26381

具体配置

port 26379
daemonize yes
sentinel montitor mymaster 127.0.0.1 6381 2
sentinel auth-pass mymaster 753159
sentinel down-after-milliseconds mymaster 3000
logfile "sentinel26379.log"
dir "/root/opt/mylog"
pidfile "/var/run/redis-sentinel-26379.pid"

启动哨兵,这里注意要通过 sentinel 配置来启动

redis-sentinel mysentinel/sentinel26379.conf
redis-sentinel mysentinel/sentinel26380.conf
redis-sentinel mysentinel/sentinel26381.conf
配置成功之后,让主节点6379下线,通过命令查看另外两个节点哪个变成了主节点,我这里是6381
即便6379再次上线,也只会变成从节点了

实战

# 创建两个个哨兵模式文件夹,用来存放哨兵模式配置文件目录和存放哨兵日志目录
[root@mail redis-7.0.11]# mkdir mysentinel
[root@mail opt]# mkdir mylog
[root@mail opt]# ls
0224_day07           chatgpt-web-frontend  containerd  harbor-offline-installer-v2.11.0-rc3.tgz  libiconv-1.15  redis-7.0.11.tar.gz
chaoge.txt           cmatrix-1.2a          game        latest-unix.tar.gz                        mylog          software
chatgpt-web-backend  compressed_file       gitlab      libiconv                                  redis-7.0.11
[root@mail opt]# cd mylog
[root@mail mylog]# pwd
/opt/mylog



# 直接进入该目录
[root@mail redis-7.0.11]# cd mysentinel
[root@mail mysentinel]# pwd
/opt/redis-7.0.11/mysentinel

[root@mail mysentinel]# vim sentinel26378.conf
[root@mail mysentinel]# cat sentinel26378.conf
port 26378
daemonize yes
sentinel monitor mymaster 192.168.10.113 6378 2
sentinel auth-pass mymaster 123456
sentinel down-after-milliseconds mymaster 3000
logfile "sentinel26378.log"
dir "/opt/mylog"
pidfile "/var/run/redis-sentinel-26378.pid"


[root@mail mysentinel]# vim sentinel26380.conf
[root@mail mysentinel]# cat sentinel26380.conf
port 26380
daemonize yes
sentinel monitor mymaster 192.168.10.113 6378 2
sentinel auth-pass mymaster 123456
sentinel down-after-milliseconds mymaster 3000
logfile "sentinel26380.log"
dir "/opt/mylog"
pidfile "/var/run/redis-sentinel-26380.pid"


[root@mail mysentinel]# vim sentinel26381.conf
[root@mail mysentinel]# cat sentinel26381.conf
port 26381
daemonize yes
sentinel monitor mymaster 192.168.10.113 6378 2
sentinel auth-pass mymaster 123456
sentinel down-after-milliseconds mymaster 3000
logfile "sentinel26381.log"
dir "/opt/mylog"
pidfile "/var/run/redis-sentinel-26381.pid"


[root@mail mysentinel]# vim sentinel26382.conf
[root@mail mysentinel]# cat sentinel26382.conf
port 26382
daemonize yes
sentinel monitor mymaster 192.168.10.113 6378 2
sentinel auth-pass mymaster 123456
sentinel down-after-milliseconds mymaster 3000
logfile "sentinel26382.log"
dir "/opt/mylog"
pidfile "/var/run/redis-sentinel-26382.pid"
[root@mail mysentinel]#



哨兵模式配置

# 哨兵 sentinel 实例运行的端口 默认 26379
port 26379

# 哨兵 sentinel 的工作目录
dir /tmp

# 哨兵 sentinel 监控的 redis 主节点的 ip port
# master-name 可以自己命名的主节点名字:只能由字母 A-z、数字 0-9、".-_"这三个字符组成。
# quorum 配置多少个 sentinel 哨兵统一认为 master 主节点失联那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>    
sentinel monitor mymaster 127.0.0.1 6379 2

# 当在 Redis 实例中开启了 requirepass foobared 授权密码 这样所有连接 Redis 实例的客户端都要提供密码
# 设置哨兵 sentinel 连接主从的密码,注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd

# 指定多少毫秒之后,主节点没有应答哨兵 sentinel,此时,哨兵主观上认为主节点下线,默认 30 秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000

# 这个配置项指定了在发生 failover 主备切换时最多可以有多少个 slave 同时对新的 master 进行同步
# 这个数字越小,完成 failover 所需的时间就越长
# 但是如果这个数字越大,就意味着越多的 slave 因为 replication 而不可用
# 可以通过将这个值设为 1 来保证每次只有一个 slave 处于不能处理命令请求的状态
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1

# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
# 1. 同一个 sentinel 对同一个 master 两次 failover 之间的间隔时间
# 2. 当一个 slave 从一个错误的 master 那里同步数据开始计算时间。直到 slave 被纠正为向正确的 master 那里同步数据时。
# 3. 当想要取消一个正在进行的 failover 所需要的时间。
# 4. 当进行 failover 时,配置所有 slaves 指向新的 master 所需的最大时间。
#    不过,即使过了这个超时,slaves 依然会被正确配置为指向 master,但是就不按 parallel-syncs 所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000

# SCRIPTS EXECUTION

# 配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
# 对于脚本的运行结果有以下规则:
# 若脚本执行后返回 1,那么该脚本稍后将会被再次执行,重复次数目前默认为 10
# 若脚本执行后返回 2,或者比 2 更高的一个返回值,脚本将不会重复执行。
# 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为 1 时的行为相同。
# 一个脚本的最大执行时间为 60s,如果超过这个时间,脚本将会被一个 SIGKILL 信号终止,之后重新执行。

# 通知型脚本:当 sentinel 有任何警告级别的事件发生时(比如说 redis 实例的主观失效和客观失效等),将会去调用这个脚本
# 这时这个脚本应该通过邮件,SMS 等方式去通知系统管理员关于系统不正常运行的信息。
# 调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。
# 如果 sentinel.conf 配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则 sentinel 无法正常启动成功。
# 通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh

# 客户端重新配置主节点参数脚本
# 当一个 master 由于 failover 而发生改变时,这个脚本将会被调用,通知相关的客户端关于 master 地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前 <state> 总是 “failover”,<role> 是 “leader” 或者 “observer” 中的一个。
# 参数 from-ip,from-port,to-ip,to-port是用来和旧的 master 和新的 master (即旧的 slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

启动哨兵模式

[root@mail redis-7.0.11]# redis-sentinel mysentinel/sentinel26378.conf
[root@mail redis-7.0.11]# redis-sentinel mysentinel/sentinel26380.conf
[root@mail redis-7.0.11]# redis-sentinel mysentinel/sentinel26381.conf
[root@mail redis-7.0.11]# redis-sentinel mysentinel/sentinel26382.conf
[root@mail redis-7.0.11]# pwd
/opt/redis-7.0.11
# 查看启动结果
[root@mail redis-7.0.11]# ps -ef | grep sentinel
root  1046383  1  0 06:00 ?  00:00:01 redis-sentinel *:26378 [sentinel]
root  1046648  1  0 06:00 ?  00:00:01 redis-sentinel *:26380 [sentinel]
root  1046867  1  0 06:00 ?  00:00:01 redis-sentinel *:26381 [sentinel]
root  1047053  1  0 06:00 ?  00:00:01 redis-sentinel *:26382 [sentinel]
root  1058403 1018110 0 06:10 pts/0 00:00:00 grep --color=auto sentinel
[root@mail redis-7.0.11]# pwd
/opt/redis-7.0.11

看到以上就表示 sentinel 启动成功

Redis缓存穿透和雪崩

Redis 缓存的应用,极大提升了应用程序的性能和效率,特别是数据查询方面。但同时,他也有一些列的问题,数据一致性就是其中比较重点的问题。那么这个问题其实目前的解决方案都是最终一致性,当然也是要看具体需求而定。
这其中还有一些缓存穿透,雪崩也是比较经典的问题

缓存穿透

缓存穿透是指查询一个在缓存和数据库中毒不存在的数据。由于缓存没有这个数据,所以每次查询都会"穿透"缓存直接查询数据库,如果有大量此类查询,会给数据库带来极大压力。
这种情况如果一旦访问量过大,很容易造成 MySQL服务器宕机。

解决办法

1. 布隆过滤器 (Bloom Filter): 这是一种空间效率极高的概率型数据结构,它可以用来判断一个元素是否在一个集合中。将所有可能会被请求的数据的 key 先存入布隆过滤器,当有请求过来时,先使用布隆过滤器判断数据是否存在,如果布隆过滤器判断数据不存在,那就直接返回,不再查询数据库。
2. 空值缓存:即使一个 key 在数据库中没有对应的值,也依然将这个结果进行缓存,但是可以设置一个较短的过期时间。这样,即使攻击者使用大量不存在的 key 进行攻击,也只能触及到缓存层,无法到达数据库层。
3. 限流: 对单个用户的访问频率进行限制,防止恶意用户或者爬虫通过高并发的方式进行攻击。

缓存击穿 (缓存过期的瞬间)

缓存击穿是指一个存在的 key 在缓存中过期,此时有大量的并发请求这个 key,都查不到结果,于是都去数据库中查询并回填缓存,造成数据库短时间内压力过大。简单来说,缓存击穿是指请求的数据在缓存中不存在 (可能是过期,或者根本没有缓存过),导致所有的请求都去数据库,可能会导致数据库瞬间压力过大。
为了防止缓存击穿。有一些常见的解决办法:
1. 设置热点数据永不过期:如果某些数据特别热门,访问的频率远远超过其他数据,那么可以将这部分数据设置为永不过期,这样就可以避免大量请求突然落到数据库上。
2. 加锁排队: 对于查询数据库的操作进行加锁,即在缓存失效的时候,不是直接去查询数据库,而是先进行加锁操作。第一个请求获取到锁,然后进行数据库查询并回填缓存,后续的请求如果获取不到锁,可以选择等待或者返回未获取到数据。
3. 使用互斥锁:可以在缓存失效的时候使用互斥锁,即第一个请求去数据库查询数据,其他的请求就等待,当第一个请求处理完毕后,其他请求再从缓存中获取数据。

这些方法可以根据实际的业务情况进行选择,理想的情况是可以将数据库访问量降到最低,从而避免数据库因为过大的压力而崩溃。

缓存雪崩

缓存雪崩是指在缓存系统中,由于大量的数据同时失效,而引发大量的请求同时去请求数据库,从而可能会导致数据库的压力过大甚至导致数据库崩溃的情况。比如,如果一个系统的缓存策略设置的某一刻所有的缓冲同时失效,那么在这一刻,所有的请求都将会去请求数据库,这就可能会造成缓存雪崩。
其实最致命的雪崩就是服务器某个节点突然宕机或者断网,因为此时对数据库服务器的压力是不可预估的,很有肯呢哪个瞬间把数据库压垮!
为了防止缓存雪崩,有一些常见的解决策略:
1. 缓存数据的过期时间设置为随机:避免所有数据同时过期,不会出现瞬间大量的数据库请求。
2. 使用多级存储策略:例如,使用一级缓存和二级缓存,一级缓存的失效时间短于二级缓存,一级缓存失效后,请求访问二级缓存,而不是直接落到数据库。
3. 缓存预热:在业务低峰期,对将要过期的缓存进行预热,预热的方式就是提前去更新数据
4. 使用高可用的缓冲集群:即使某些节点挂掉,仍然可以保证系统的可用性。
这些策略可以根据实际情况进行选择和使用,以提高系统的稳定性和可用性,减少因为大量请求落到数据库而引发的问题。

如何删除redis

1.把 redis 进程杀死
2.把默认安装在 /usr/local/bin/ 目录下所有文件删除
3.把解压后生成的安装包删除




Hint: It's a good idea to run 'make test' ;)

    INSTALL redis-server
    INSTALL redis-benchmark
    INSTALL redis-cli
make[1]: Leaving directory '/opt/redis-7.0.11/src'

# 看到如下 redis 表示安装成功
[root@mail redis-7.0.11]# redis-server
1898943:C 25 Jun 2024 09:55:00.659 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1898943:C 25 Jun 2024 09:55:00.659 # Redis version=7.0.11, bits=64, commit=00000000, modified=0, pid=1898943, just started
1898943:C 25 Jun 2024 09:55:00.659 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1898943:M 25 Jun 2024 09:55:00.660 * monotonic clock: POSIX clock_gettime
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 7.0.11 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 1898943
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           https://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

1898943:M 25 Jun 2024 09:55:00.662 # Server initialized
1898943:M 25 Jun 2024 09:55:00.662 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
1898943:M 25 Jun 2024 09:55:00.663 * Ready to accept connections

ctrl+N 查找类文件

 

找到自己要查找的类文件回车即可

可以看到自己需要的内容

选中要查看的类名

然后CTRL+N,找到我们需要的文件, 再回车

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值