狂神--Redis

Redis入门

概述

Redis是什么

Redis:REmote DIctionary Server(远程字典服务器)
是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(Key/Value)分布式内存数据
库,基于内存运行,并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一,也被人们称为数据结构服务器

Redis与其他key-value缓存产品有以下三个特点
Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
Redis不仅仅支持简单的 key-value 类型的数据,同时还提供list、set、zset、hash等数据结构的存储。
Redis支持数据的备份,即master-slave模式的数据备份。

Redis能干嘛

内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
发布、订阅消息系统
地图信息分析
定时器、计数器
......

特性

数据类型、基本操作和配置
持久化和复制,RDB、AOF
事务的控制
.....

常用网站

https://redis.io/ 官网
http://www.redis.cn 中文网

Windows安装

下载地址:https://github.com/dmajkic/redis/downloads ( 素材提供 )

双击 redis-server.exe 启动

通过客户端去访问 redis-cli

# 基本的set设值
127.0.0.1:6379> set key kuangshen
OK
# 取出存储的值
127.0.0.1:6379> get key
"kuangshen"

Linux安装

下载地址 http://download.redis.io/releases/redis-5.0.7.tar.gz

安装步骤

1、下载获得 redis-5.0.7.tar.gz 后将它放到我们Linux的目录下 /opt
2、/opt 目录下,解压命令 : tar -zxvf redis-5.0.7.tar.gz
3、解压完成后出现文件夹:redis-5.0.7
4、进入目录: cd redis-5.0.7
5、在 redis-5.0.7 目录下执行 make 命令
	运行make命令时故意出现的错误解析:
	1. 安装gcc (gcc是linux下的一个编译程序,是c程序的编译工具)
	能上网: yum install gcc-c++
	版本测试: gcc-v
	2. 二次make
	3. Jemalloc/jemalloc.h: 没有那个文件或目录
	运行 make distclean 之后再make
4. Redis Test(可以不用执行)
6、如果make完成后继续执行 make install
7、查看默认安装目录:usr/local/bin
	/usr 这是一个非常重要的目录,类似于windows下的Program Files,存放用户的程序
8、拷贝配置文件(备用)
	cd /usr/local/bin
	ls -l
	# 在redis的解压目录下备份redis.conf
	mkdir myredis
	cp redis.conf myredis # 拷一个备份,养成良好的习惯,我们就修改这个文件
	# 修改配置保证可以后台应用
	vim redis.conf
	  A、redis.conf配置文件中daemonize守护线程,默认是NO。
      B、daemonize是用来指定redis是否要用守护线程的方式启动。
	daemonize 设置yes或者no区别
		daemonize:yes
		redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启
		守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项
		pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。
		daemonize:no
		当daemonize选项设置成no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭
		连接工具(putty,xshell等)都会导致redis进程退出。
9、启动测试一下!
# 【shell】启动redis服务
    [root@192 bin]# cd /usr/local/bin
    [root@192 bin]# redis-server /opt/redis-5.0.7/redis.conf
    # redis客户端连接===> 观察地址的变化,如果连接ok,是直接连上的,redis默认端口号 6379
    [root@192 bin]# redis-cli -p 6379
    127.0.0.1:6379> ping
    PONG
    127.0.0.1:6379> set k1 helloworld
    OK
    127.0.0.1:6379> get k1
    "helloworld"
    # 【shell】ps显示系统当前进程信息
    [root@192 myredis]# ps -ef|grep redis
    root 16005 1 0 04:45 ? 00:00:00 redis-server
    127.0.0.1:6379
    root 16031 15692 0 04:47 pts/0 00:00:00 redis-cli -p 6379
    root 16107 16076 0 04:51 pts/2 00:00:00 grep --color=auto redis
    # 【redis】关闭连接
    127.0.0.1:6379> shutdown
    not connected> exit
    # 【shell】ps显示系统当前进程信息
    [root@192 myredis]# ps -ef|grep redis
    root 16140 16076 0 04:53 pts/2 00:00:00 grep --color=auto redis

基础知识说明

redis压力测试工具-----Redis-benchmark

https://www.runoob.com/redis/redis-benchmarks.html

# 测试一:100个并发连接,100000个请求,检测host为localhost 端口为6379的redis服务器性
能
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
# 测试出来的所有命令只举例一个!
====== SET ======
100000 requests completed in 1.88 seconds # 对集合写入测试
100 parallel clients # 每次请求有100个并发客户端
3 bytes payload # 每次写入3个字节的数据,有效载荷
keep alive: 1 # 保持一个连接,一台服务器来处理这些请求
17.05% <= 1 milliseconds
97.35% <= 2 milliseconds
99.97% <= 3 milliseconds
100.00% <= 3 milliseconds # 所有请求在 3 毫秒内完成
53248.14 requests per second # 每秒处理 53248.14 次请求

基本数据库常识

默认16个数据库,类似数组下标从零开始,初始默认使用零号库

查看 redis.conf ,里面有默认的配置
databases 16
# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
# dbid is a number between 0 and 'databases'-1
databases 16

Select命令切换数据库

127.0.0.1:6379> select 7
OK
127.0.0.1:6379[7]>
# 不同的库可以存不同的数据

Dbsize查看当前数据库的key的数量

127.0.0.1:6379> select 7
OK
127.0.0.1:6379[7]> DBSIZE
(integer) 0
127.0.0.1:6379[7]> select 0
OK
127.0.0.1:6379> DBSIZE
(integer) 5
127.0.0.1:6379> keys * # 查看具体的key
1) "counter:__rand_int__"
2) "mylist"
3) "k1"
4) "myset:__rand_int__"
5) "key:__rand_int__"

Flushdb:清空当前库

Flushall:清空全部的库

127.0.0.1:6379> DBSIZE
(integer) 5
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> DBSIZE
(integer) 0

为什么redis是单线程

我们首先要明白,Redis很快!官方表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis
的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就
顺理成章地采用单线程的方案了!
Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是
可以达到100000+的QPS(每秒内查询次数)。这个数据不比采用单进程多线程的同样基于内存的 KV
数据库 Memcached 差!
Redis为什么这么快?
1)以前一直有个误区,以为:高性能服务器 一定是多线程来实现的
原因很简单因为误区二导致的:多线程 一定比 单线程 效率高,其实不然!
在说这个事前希望大家都能对 CPU 、 内存 、 硬盘的速度都有了解了!
2)redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为
多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切
换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存
的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处
理这个事。在内存的情况下,这个方案就是最佳方案。
因为一次CPU上下文的切换大概在 1500ns 左右。从内存中读取 1MB 的连续数据,耗时大约为 250us,
假设1MB的数据由多个线程读取了1000次,那么就有1000次时间上下文的切换,那么就有1500ns *
1000 = 1500us ,我单线程的读完1MB数据才250us ,你光时间上下文的切换就用了1500us了,我还不
算你每次读一点数据 的时间。

五大数据类型

Redis键key

切换数据库

127.0.0.1:6379[8]> select 0
OK
127.0.0.1:6379>

查看数据库大小

127.0.0.1:6379> dbsize
(integer) 2

查看所有key

127.0.0.1:6379[8]> keys *
1) "name"
2) "name2"

清空当前数据库

127.0.0.1:6379[8]> flushdb
OK

清空所有数据库

flushall

判断一个key是否存在

127.0.0.1:6379[8]> exists name
(integer) 1
127.0.0.1:6379[8]>
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> exists name3
(integer) 0

删除key

127.0.0.1:6379[8]> keys *
1) "jack"
2) "tom"
3) "lisa"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> move jack 9
(integer) 1
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> keys *
1) "tom"
2) "lisa"
127.0.0.1:6379[8]> del tom
(integer) 1
127.0.0.1:6379[8]> keys *
1) "lisa"

设置key的过期时间

127.0.0.1:6379[8]> expire tom 10
(integer) 1
127.0.0.1:6379[8]>
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> ttl tom
(integer) 2
127.0.0.1:6379[8]> ttl tom
(integer) -2
127.0.0.1:6379[8]> keys tom
(empty list or set)
127.0.0.1:6379[8]> keys *
1) "jack"
2) "lisa"
127.0.0.1:6379[8]>

查看当前key的类型

127.0.0.1:6379[8]> type jack
string

重命名key

127.0.0.1:6379[8]> rename lilin  li
OK
127.0.0.1:6379[8]> get lilin
(nil)
127.0.0.1:6379[8]> get li
"woman"

随机返回一个key

127.0.0.1:6379[8]> randomkey
"li"
127.0.0.1:6379[8]> randomkey
"b"

持久化操作

save 阻塞
bgsave 非阻塞

认证

auth  password   xxxx

关闭服务

shutdown

String

设置指定 key 的值

127.0.0.1:6379[8]> set name tom
OK

获取指定 key 的值

127.0.0.1:6379[8]> get name
"tom"

获取所有的key

127.0.0.1:6379[8]> keys *
1) "name"
2) "name2"

判断一个key是否存在

127.0.0.1:6379[8]> exists name
(integer) 1
127.0.0.1:6379[8]>
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> exists name3
(integer) 0

追加字符串

127.0.0.1:6379[8]> get tom
"man"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> append tom likewoman
(integer) 12
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> get tom
"manlikewoman"

获取字符串长度

127.0.0.1:6379[8]> strlen tom
(integer) 12
127.0.0.1:6379[8]>

字符串i++

127.0.0.1:6379[8]> set a 0
OK
127.0.0.1:6379[8]> incr a
(integer) 1
127.0.0.1:6379[8]> incr a
(integer) 2
127.0.0.1:6379[8]> incr a
(integer) 3
127.0.0.1:6379[8]> get a
"3"
127.0.0.1:6379[8]>

字符串 i--

127.0.0.1:6379[8]> get a
"3"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> decr a
(integer) 2
127.0.0.1:6379[8]> decr a
(integer) 1
127.0.0.1:6379[8]> get a
"1"

设置步长

127.0.0.1:6379[8]> get a
"1"
127.0.0.1:6379[8]> incrby a 666
(integer) 667
127.0.0.1:6379[8]> decrby a 111
(integer) 556
127.0.0.1:6379[8]>

获取字符串片段

127.0.0.1:6379[8]> get tom
"manlikewoman"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> getrange tom 0 3
"manl"

字符串替换

27.0.0.1:6379[8]> get tom
"manlikewoman"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> setrange tom 3 dislike
(integer) 12
127.0.0.1:6379[8]> get tom
"mandislikean"
127.0.0.1:6379[8]>

设置key带过期时间

127.0.0.1:6379[8]> setex  time  20  a
OK
127.0.0.1:6379[8]> ttl time
(integer) 2
127.0.0.1:6379[8]> ttl time
(integer) 2
127.0.0.1:6379[8]> ttl time
(integer) 1
127.0.0.1:6379[8]> ttl time
(integer) 1
127.0.0.1:6379[8]> ttl time
(integer) 0
127.0.0.1:6379[8]> ttl time
(integer) -2
127.0.0.1:6379[8]> ttl time
(integer) -2
127.0.0.1:6379[8]> keys time
(empty list or set)

不存在则设置

127.0.0.1:6379[8]> setnx jack man
(integer) 1
127.0.0.1:6379[8]> get jack
"man"
127.0.0.1:6379[8]> setnx jack wonman
(integer) 0
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> get jack
"man"
127.0.0.1:6379[8]>

批量操作

127.0.0.1:6379[8]>
127.0.0.1:6379[8]> mset a 1 b 2 c 3 d 4
OK
127.0.0.1:6379[8]> keys *
1) "c"
2) "b"
3) "d"
4) "tom"
5) "jack"
6) "a"
7) "lisa"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> mget a b c d
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379[8]>

返回原值,在设置值

127.0.0.1:6379[8]> getset  lilin  man
(nil)
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> getset  lilin  woman
"man"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> get lilin
"woman"

List

头插入

127.0.0.1:6379[8]>
127.0.0.1:6379[8]> lpush people jack liming
(integer) 2
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> lpush people tom
(integer) 3
127.0.0.1:6379[8]> keys *
1) "people"

127.0.0.1:6379[8]> lrange pepole  0  22
1) "tom"
2) "liming"
3) "jack"
127.0.0.1:6379[8]>

尾插入

127.0.0.1:6379[8]> rpush people zhangsan
(integer) 4
127.0.0.1:6379[8]>
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> lrange people  0  22
1) "tom"
2) "liming"
3) "jack"
4) "zhangsan"
127.0.0.1:6379[8]>

头删除

127.0.0.1:6379[8]> lrange people 0 22
1) "tom"
2) "liming"
3) "jack"
4) "zhangsan"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> lpop people
"tom"
127.0.0.1:6379[8]> lrange people 0 22
1) "liming"
2) "jack"
3) "zhangsan"
127.0.0.1:6379[8]>

尾删除

127.0.0.1:6379[8]> rpop people
"zhangsan"
127.0.0.1:6379[8]> lrange people 0 22
1) "liming"
2) "jack"
127.0.0.1:6379[8]>

获取位置的值

127.0.0.1:6379[8]> lindex people 0
"liming"
127.0.0.1:6379[8]>

获取列表长度

127.0.0.1:6379[8]> llen people
(integer) 2
127.0.0.1:6379[8]>

按值删除

127.0.0.1:6379[8]> lrem people 1 jack
(integer) 1
127.0.0.1:6379[8]>

按照位置保留

127.0.0.1:6379[8]> ltrim people 0  0
OK
127.0.0.1:6379[8]> lrange people 0 22
1) "liming"
127.0.0.1:6379[8]>

按照位置插入

127.0.0.1:6379[8]> lrange people 0 22
1) "liming"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> linsert people before  liming  tom
(integer) 2
127.0.0.1:6379[8]> lrange people 0 22
1) "tom"
2) "liming"
127.0.0.1:6379[8]> linsert people after liming  jack
(integer) 3
127.0.0.1:6379[8]> lrange people 0 22
1) "tom"
2) "liming"
3) "jack"

删除最后一个元素,并加入新的集合

127.0.0.1:6379[8]> lrange people 0 22
1) "jack"
2) "tom"
3) "liming"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> rpoplpush  people  people1
"liming"
127.0.0.1:6379[8]> keys *
1) "people1"
2) "people"
127.0.0.1:6379[8]> lrange people1 0 22
1) "liming"
127.0.0.1:6379[8]>

Set

设置key的值

127.0.0.1:6379[8]> sadd choose java c++
(integer) 2
127.0.0.1:6379[8]> smembers choose
1) "java"
2) "c++"
127.0.0.1:6379[8]>

查看key的值

127.0.0.1:6379[8]> smembers choose
1) "java"
2) "c++"
127.0.0.1:6379[8]>

判断值是否存在

127.0.0.1:6379[8]> sismember choose java
(integer) 1
127.0.0.1:6379[8]> sismember choose java1
(integer) 0
127.0.0.1:6379[8]>

查看集合长度

127.0.0.1:6379[8]> scard choose
(integer) 2
127.0.0.1:6379[8]>

移除指定值

127.0.0.1:6379[8]> srem choose c++
(integer) 1
127.0.0.1:6379[8]> smembers choose
1) "java"
127.0.0.1:6379[8]>

获取随机值

127.0.0.1:6379[8]> srandmember choose 1
1) "java"
127.0.0.1:6379[8]>

随机移除成员

127.0.0.1:6379[8]> smembers choose
1) "java"
2) "go"
3) "python"
4) "c++"
5) "javascript"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> spop choose 1
1) "c++"
127.0.0.1:6379[8]> smembers choose
1) "java"
2) "python"
3) "javascript"
4) "go"
127.0.0.1:6379[8]>

将值从集合移动到另一集合

127.0.0.1:6379[8]> smove choose  choose2 java
(integer) 1
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> smembers choose2
1) "java"
2) "1"
127.0.0.1:6379[8]>

Hash

设置key 的值

127.0.0.1:6379[8]> hset people  name  zhangsan
(integer) 1

获取key的值

127.0.0.1:6379[8]> hget people name
"zhangsan"
127.0.0.1:6379[8]>

获取所有key的值

127.0.0.1:6379[8]> hgetall people
1) "name"
2) "jack"
3) "name1"
4) "zhangsan"
5) "name3"
6) "liming"
127.0.0.1:6379[8]>

删除map中的值

127.0.0.1:6379[8]> hdel people name
(integer) 1
127.0.0.1:6379[8]> hgetall people
1) "name1"
2) "zhangsan"
3) "name3"
4) "liming"
127.0.0.1:6379[8]>

判断map中是否存在

127.0.0.1:6379[8]> hexists  people  name
(integer) 0
127.0.0.1:6379[8]> hexists  people  name1
(integer) 
127.0.0.1:6379[8]>

获取所map的key或value有值

127.0.0.1:6379[8]> hkeys people
1) "name1"
2) "name3"
127.0.0.1:6379[8]> hvals  people
1) "zhangsan"
2) "liming"
127.0.0.1:6379[8]>

i++

127.0.0.1:6379[8]> hset people i 0
(integer) 1
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> hvals  people
1) "zhangsan"
2) "liming"
3) "0"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> hincrby people i 1
(integer) 1
127.0.0.1:6379[8]> hincrby people i 1
(integer) 2
127.0.0.1:6379[8]> hincrby people i 1
(integer) 3
127.0.0.1:6379[8]> hvals  people
1) "zhangsan"
2) "liming"
3) "3"

不存在就添加

  127.0.0.1:6379[8]> hsetnx people name  jack
(integer) 1
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> hsetnx people name  jack
(integer) 0
127.0.0.1:6379[8]> hsetnx people name  jack
(integer) 0
127.0.0.1:6379[8]>

Zset

设置key的值

127.0.0.1:6379[8]> zadd people 1 jack  6 liming
(integer) 2
127.0.0.1:6379[8]> zrange people 0 22
1) "jack"
2) "liming"
127.0.0.1:6379[8]>

获取key的值

127.0.0.1:6379[8]> zrange people 0 -1
1) "jack"
2) "liming"
127.0.0.1:6379[8]>

删除

127.0.0.1:6379[8]> zrange people 0 -1
1) "jack"
2) "lili"
3) "lilia"
4) "liming"
127.0.0.1:6379[8]> zrem people lili
(integer) 1
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> zrange people 0 -1
1) "jack"
2) "lilia"
3) "liming"

集合长度

127.0.0.1:6379[8]> zcard people
(integer) 3
127.0.0.1:6379[8]>
127.0.0.1:6379[8]>

区间集合长度

127.0.0.1:6379[8]> zcount people 0 2
(integer) 3
127.0.0.1:6379[8]> zcount people 0 0
(integer) 0
127.0.0.1:6379[8]> zcount people 0 3
(integer) 3

三种特殊数据类型

Geospatial

GEO地理位置

简介

Redis 的 GEO 特性在 Redis 3.2 版本中推出, 这个功能可以将用户给定的地理位置信息储存起来, 并对

这些信息进行操作。来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。geo的数据类型为

zset。

GEO 的数据结构总共有六个常用命令:geoadd、geopos、geodist、georadius、

georadiusbymember、gethash

官方文档:https://www.redis.net.cn/order/3685.html
geoadd
# 语法
geoadd key longitude latitude member ...
# 将给定的空间元素(纬度、经度、名字)添加到指定的键里面。
# 这些数据会以有序集he的形式被储存在键里面,从而使得georadius和georadiusbymember这样的
命令可以在之后通过位置查询取得这些元素。
# geoadd命令以标准的x,y格式接受参数,所以用户必须先输入经度,然后再输入纬度。
# geoadd能够记录的坐标是有限的:非常接近两极的区域无法被索引。
# 有效的经度介于-180-180度之间,有效的纬度介于-85.05112878 度至 85.05112878 度之间。,
当用户尝试输入一个超出范围的经度或者纬度时,geoadd命令将返回一个错误。

中文插不进去,不知道什么原因

127.0.0.1:6379[8]> geoadd china:city 116.23 40.22 BJ
(integer) 1
127.0.0.1:6379[8]> geoadd china:city 121.48 31.40 SH 113.88 22.55 SZ 120.21 30.20 HZ
(integer) 3
127.0.0.1:6379[8]> geoadd china:city 106.54 29.40 CQ 108.93 34.23 XA 114.02 30.58 WH
(integer) 3
127.0.0.1:6379[8]> zrange china:city 0 -1
1) "CQ"
2) "XA"
3) "SZ"
4) "WH"
5) "HZ"
6) "SH"
7) "BJ"
geopos
# 语法
geopos key member [member...]
#从key里返回所有给定位置元素的位置(经度和纬度)
127.0.0.1:6379[8]> geopos china:city BJ
1) 1) "116.23000055551528931"
   2) "40.2200010338739844"
127.0.0.1:6379[8]> geopos china:city SH WH
1) 1) "121.48000091314315796"
   2) "31.40000025319353938"
2) 1) "114.01999980211257935"
   2) "30.58000021509926825"
127.0.0.1:6379[8]> geopos china:city SH WH LF
1) 1) "121.48000091314315796"
   2) "31.40000025319353938"
2) 1) "114.01999980211257935"
   2) "30.58000021509926825"
3) (nil)
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> geopos china:city HB
1) (nil)
127.0.0.1:6379[8]>
geodist
# 语法
geodist key member1 member2 [unit]
# 返回两个给定位置之间的距离,如果两个位置之间的其中一个不存在,那么命令返回空值。
# 指定单位的参数unit必须是以下单位的其中一个:
# m表示单位为米
# km表示单位为千米
# mi表示单位为英里
# ft表示单位为英尺
# 如果用户没有显式地指定单位参数,那么geodist默认使用米作为单位。
#geodist命令在计算距离时会假设地球为完美的球形,在极限情况下,这一假设最大会造成0.5%的误
差。
127.0.0.1:6379[8]> geodist china:city BJ WH
"1090672.1686"
127.0.0.1:6379[8]> geodist china:city BJ SH
"1088785.4302"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> geodist china:city BJ WH  km
"1090.6722"
georadius
# 语法
georadius key longitude latitude radius m|km|ft|mi [withcoord][withdist]
[withhash][asc|desc][count count]
# 以给定的经纬度为中心, 找出某一半径内的元素

连接 redis-cli,增加参数 --raw ,可以强制输出中文,不然会乱码

127.0.0.1:6379[8]> geodist china:city BJ WH  km
"1090.6722"
# 在 china:city 中寻找坐标 100 30 半径为 1000km 的城市
127.0.0.1:6379[8]> georadius china:city 100 30 1000 km
1) "CQ"
2) "XA"
127.0.0.1:6379[8]>
# withdist 返回位置名称和中心距离
127.0.0.1:6379[8]> georadius china:city 100 30 1000 km withdist
1) 1) "CQ"
   2) "635.2850"
2) 1) "XA"
   2) "963.3171"
127.0.0.1:6379[8]>
# withcoord 返回位置名称和经纬度
127.0.0.1:6379[8]> georadius china:city 100 30 1000 km withcoord
1) 1) "CQ"
   2) 1) "106.54000014066696167"
      2) "29.39999880018641676"
2) 1) "XA"
   2) 1) "108.92999857664108276"
      2) "34.23000121926852302"
127.0.0.1:6379[8]>
# withdist withcoord 返回位置名称 距离 和经纬度 count 限定寻找个数
127.0.0.1:6379[8]> georadius china:city 100 30 1000 km withcoord withdist count 1
1) 1) "CQ"
   2) "635.2850"
   3) 1) "106.54000014066696167"
      2) "29.39999880018641676"
127.0.0.1:6379[8]> georadius china:city 100 30 1000 km withcoord withdist count 2
1) 1) "CQ"
   2) "635.2850"
   3) 1) "106.54000014066696167"
      2) "29.39999880018641676"
2) 1) "XA"
   2) "963.3171"
   3) 1) "108.92999857664108276"
      2) "34.23000121926852302"

georadiusbymember
georadiusbymember key member radius m|km|ft|mi [withcoord][withdist]
[withhash][asc|desc][count count]
# 找出位于指定范围内的元素,中心点是由给定的位置元素决定
127.0.0.1:6379[8]> georadiusbymember china:city BJ 1000 km
1) "BJ"
2) "XA"
127.0.0.1:6379[8]> georadiusbymember china:city SH 400 km
1) "HZ"
2) "SH"
127.0.0.1:6379
geohash
geohash key member [member...]
# Redis使用geohash将二维经纬度转换为一维字符串,字符串越长表示位置更精确,两个字符串越相似
表示距离越近。
127.0.0.1:6379[8]> geohash china:city BJ CQ
1) "wx4sucu47r0"
2) "wm5z22h53v0"
127.0.0.1:6379[8]> geohash china:city BJ BJ
1) "wx4sucu47r0"
2) "wx4sucu47r0"
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> geohash china:city BJ SH
1) "wx4sucu47r0"
2) "wtw6sk5n300"
127.0.0.1:6379[8]>
zrem
GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位置信息的删除.
127.0.0.1:6379[8]> zrange china:city  0 -1
1) "CQ"
2) "XA"
3) "SZ"
4) "WH"
5) "HZ"
6) "SH"
7) "BJ"
127.0.0.1:6379[8]> zrem china:city CQ
(integer) 1
127.0.0.1:6379[8]> zrange china:city  0 -1
1) "XA"
2) "SZ"
3) "WH"
4) "HZ"
5) "SH"
6) "BJ"
127.0.0.1:6379[8]>

Hyperloglog

简介

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积
非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
HyperLogLog则是一种算法,它提供了不精确的去重计数方案。
举个栗子:假如我要统计网页的UV(浏览用户数量,一天内同一个用户多次访问只能算一次),传统的
解决方案是使用Set来保存用户id,然后统计Set中的元素数量来获取页面UV。但这种方案只能承载少量用户,一旦用户数量大起来就需要消耗大量的空间来存储用户id。我的目的是统计用户数量而不是保存用户,这简直是个吃力不讨好的方案!而使用Redis的HyperLogLog最多需要12k就可以统计大量的用户数,尽管它大概有0.81%的错误率,但对于统计UV这种不需要很精确的数据是可以忽略不计的。

什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。
基数估计就是在误差可接受的范围内,快速计算基数。

基本命令

[PFADD key element [element ...]

添加指定元素到 HyperLogLog 中。

[PFCOUNT key [key ...]

返回给定 HyperLogLog 的基数估算值。

[PFMERGE destkey sourcekey[sourcekey ...]

将多个 HyperLogLog 合并为一个 HyperLogLog,并集计算

127.0.0.1:6379[8]> pfadd hyper a b c d e f g h i j
(integer) 1
127.0.0.1:6379[8]> pfcount hyper
(integer) 10
127.0.0.1:6379[8]> pfadd hyper2 i j z x c v b n m
(integer) 1
127.0.0.1:6379[8]> pfmerge hyper3 hyper hyper2
OK
127.0.0.1:6379[8]> pfcount hyper3
(integer) 15
127.0.0.1:6379[8]>

Bitmaps

简介

在开发中,可能会遇到这种情况:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如
需要记录用户一年的打卡情况,打卡了是1, 没有打卡是0,如果使用普通的 key/value存储,则要记录
365条记录,如果用户量很大,需要的空间也会很大,所以 Redis 提供了 Bitmap 位图这中数据结构,
Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1;如果要记录 365 天的打卡情况,使用 Bitmap
表示的形式大概如下:0101000111000111...........................,这样有什么好处呢?当然就是节约内存
了,365 天相当于 365 bit,又 1 字节 = 8 bit , 所以相当于使用 46 个字节即可。
BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上
底层也是通过对字符串的操作来实现。Redis 从 2.2 版本之后新增了setbit, getbit, bitcount 等几个
bitmap 相关命令。

setbit 设置操作

SETBIT key offffset value : 设置 key 的第 offffset 位为value (1或0)

# 使用 bitmap 来记录上述事例中一周的打卡记录如下所示:
# 周一:1,周二:0,周三:0,周四:1,周五:1,周六:0,周天:0 (1 为打卡,0 为不打卡)

127.0.0.1:6379[8]> setbit sign 0 1
(integer) 0
127.0.0.1:6379[8]> setbit sign 1 1
(integer) 0
127.0.0.1:6379[8]> setbit sign 2 0
(integer) 0
127.0.0.1:6379[8]> setbit sign 3 0
(integer) 0
127.0.0.1:6379[8]> setbit sign 4 1
(integer) 0
127.0.0.1:6379[8]> setbit sign 5 1
(integer) 0
127.0.0.1:6379[8]> setbit sign 6 1
(integer) 0
127.0.0.1:6379[8]>

getbit 获取操作

GETBIT key offffset 获取offffset设置的值,未设置过默认返回0

127.0.0.1:6379[8]> getbit sign 5
(integer) 1
127.0.0.1:6379[8]> getbit sign 3
(integer) 0
127.0.0.1:6379[8]>

bitcount 统计操作

bitcount key [start, end] 统计 key 上位为1的个数

127.0.0.1:6379[8]> bitcount sign
(integer) 5
127.0.0.1:6379[8]>

Redis.conf

熟悉基本配置

Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf

config get * # 获取全部的配置

Units 单位

# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

1、配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit

2、对 大小写 不敏感

INCLUDES 包含

################################## INCLUDES ###################################

# Include one or more other config files here.  This is useful if you
# have a standard template that goes to all Redis servers but also need
# to customize a few per-server settings.  Include files can include
# other files, so use this wisely.
#
# Notice option "include" won't be rewritten by command "CONFIG REWRITE"
# from admin or Redis Sentinel. Since Redis always uses the last processed
# line as value of a configuration directive, you'd better put includes
# at the beginning of this file to avoid overwriting config change at runtime.
#
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf

和Spring配置文件类似,可以通过includes包含,redis.conf 可以作为总文件,可以包含其他文件!

NETWORK 网络配置

bind 127.0.0.1 # 绑定的ip
protected-mode yes # 保护模式
port 6379 # 默认端口

GENERAL 通用

daemonize yes # 默认情况下,Redis不作为守护进程运行。需要开启的话,改为 yes
supervised no # 可通过upstart和systemd管理Redis守护进程
pidfile /var/run/redis_6379.pid # 以后台进程方式运行redis,则需要指定pid 文件
loglevel notice # 日志级别。可选项有:
# debug(记录大量日志信息,适用于开发、测试阶段)
# verbose(较多日志信息)
# notice(适量日志信息,使用于生产环境)
# warning(仅有部分重要、关键信息才会被记录)
logfile "" # 日志文件的位置,当指定为空字符串时,为标准输出
databases 16 # 设置数据库的数目。默认的数据库是DB 0
always-show-logo yes # 是否总是显示log

SNAPSHOPTING 快照

# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化)
save 900 1
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化)
save 300 10
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
save 60 10000
stop-writes-on-bgsave-error yes # 持久化出现错误后,是否依然进行继续进行工作
rdbcompression yes # 使用压缩rdb文件 yes:压缩,但是需要一些cpu的消耗。no:不压
缩,需要更多的磁盘空间
rdbchecksum yes # 是否校验rdb文件,更有利于文件的容错性,但是在保存rdb文件的时
候,会有大概10%的性能损耗
dbfilename dump.rdb # dbfilenamerdb文件名称
dir ./ # dir 数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录

SECURITY安全

访问密码的查看,设置和取消

# 启动redis
# 连接客户端
# 获得和设置密码
config get requirepass
config set requirepass "123456"
#测试ping,发现需要验证
127.0.0.1:6379> ping
NOAUTH Authentication required.
# 验证
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> ping
PONG

限制

maxclients 10000 # 设置能连上redis的最大客户端连接数量
maxmemory <bytes> # redis配置的最大内存容量
maxmemory-policy noeviction # maxmemory-policy 内存达到上限的处理策略
#volatile-lru:利用LRU算法移除设置过过期时间的key。
#volatile-random:随机移除设置过过期时间的key。
#volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL)
#allkeys-lru:利用LRU算法移除任何key。
#allkeys-random:随机移除任何key。
#noeviction:不移除任何key,只是返回一个写错误。

append only模式

appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这种
方式在许多应用中已经足够用了
appendfilename "appendonly.aof" # appendfilename AOF 文件名称
appendfsync everysec # appendfsync aof持久化策略的配置
# no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
# always表示每次写入都执行fsync,以保证数据同步到磁盘。
# everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。

常见配置介绍

1、Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程

daemonize no

2、当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfifile指定

pidfifile /var/run/redis.pid

3、指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认

端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字

port 6379

4、绑定的主机地址

bind 127.0.0.1

5、当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

timeout 300

6、指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose

loglevel verbose

7、日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方

式为标准输出,则日志将会发送给/dev/null

logfifile stdout

8、设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id

databases 16

9、指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

save

Redis默认配置文件中提供了三个条件:

save 900 1

save 300 10

save 60 10000

分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

10、指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时

间,可以关闭该选项,但会导致数据库文件变的巨大

rdbcompression yes

11、指定本地数据库文件名,默认值为dump.rdb

dbfifilename dump.rdb

12、指定本地数据库存放目录

dir ./

13、设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步

slaveof

14、当master服务设置了密码保护时,slav服务连接master的密码

masterauth

15、设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,

默认关闭

requirepass foobared

16、设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可

以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,

Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

maxclients 128

17、指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝

试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,

但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区

maxmemory

18、指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不

开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来

同步的,所以有的数据会在一段时间内只存在于内存中。默认为no

appendonly no

19、指定更新日志文件名,默认为appendonly.aof

appendfifilename appendonly.aof

20、指定更新日志条件,共有3个可选值:

no:表示等操作系统进行数据缓存同步到磁盘(快)

always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)

everysec:表示每秒同步一次(折衷,默认值)

appendfsync everysec

21、指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将

访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔

细分析Redis的VM机制)

vm-enabled no

22、虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享

vm-swap-fifile /tmp/redis.swap

23、将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据

都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有

value都存在于磁盘。默认值为0

vm-max-memory 0

24、Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多

个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page

大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用

默认值

vm-page-size 32

25、设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中

的,,在磁盘上每8个pages将消耗1byte的内存。

vm-pages 134217728

26、设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都

是串行的,可能会造成比较长时间的延迟。默认值为4

vm-max-threads 4

27、设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

glueoutputbuf yes

28、指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法

hash-max-zipmap-entries 64

hash-max-zipmap-value 512

29、指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)

activerehashing yes

30、指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各

个实例又拥有自己的特定配置文件

include /path/to/local.conf

Redis的持久化

Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中

的数据库状态也会消失。所以 Redis 提供了持久化功能!

RDB(Redis DataBase)

什么是RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快
照文件直接读到内存里。Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。
这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那
RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

Rdb 保存的是 dump.rdb 文件

Fork

Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量,环境变量,程序计数器等)
数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。

配置位置及SNAPSHOTTING解析

################################ SNAPSHOTTING  ################################
#
# Save the DB on disk:
#
#   save <seconds> <changes>
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   In the example below the behaviour will be to save:
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
#
#   Note: you can disable saving completely by commenting out all "save" lines.
#
#   It is also possible to remove all the previously configured save
#   points by adding a save directive with a single empty string argument
#   like in the following example:
#
#   save ""

save 900 1
save 300 10
save 60 10000

这里的触发条件机制,我们可以修改测试一下:

save 120 10 # 120秒内修改10次则触发RDB

RDB 是整合内存的压缩过的Snapshot,RDB 的数据结构,可以配置复合的快照触发条件。

默认:

save 120 10 # 120秒内修改10次则触发RDB

  • 11分钟内改了1万次
  • 5分钟内改了10次
  • 15分钟内改了1次

如果想禁用RDB持久化的策略,只要不设置任何save指令,或者给save传入一个空字符串参数也可以。

若要修改完毕需要立马生效,可以手动使用 save 命令!立马生效 !

其余命令解析

Stop-writes-on-bgsave-error:如果配置为no,表示你不在乎数据不一致或者有其他的手段发现和控
制,默认为yes。
rbdcompression:对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用
LZF算法进行压缩,如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。
rdbchecksum:在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约
10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。默认为yes。

如何触发RDB快照

1、配置文件中默认的快照配置,建议多用一台机子作为备份,复制一份 dump.rdb
2、命令save或者是bgsave
save 时只管保存,其他不管,全部阻塞
bgsave,Redis 会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave
命令获取最后一次成功执行快照的时间。
3、执行flushall命令,也会产生 dump.rdb 文件,但里面是空的,无意义 !
4、退出的时候也会产生 dump.rdb 文件!

如何恢复

1、将备份文件(dump.rdb)移动到redis安装目录并启动服务即可
2、CONFIG GET dir 获取目录
    127.0.0.1:6379> config get dir
    dir
    /usr/local/bin

优点和缺点

优点:
1、适合大规模的数据恢复
2、对数据完整性和一致性要求不高
缺点:
1、在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改
2、Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。

小结

RDB是一个非常紧凑的文件

RDB在保存RDB文件时父进程唯一需要的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他io操作,所以RDB持久化方式可以最大化redis的性能。

与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些。

数据丢失风险大。

RDB需要经常fork子进程来保存数据集到硬盘上。当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级不能响应客户端的请求。

AOF(Append Only File)

是什么

以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件
但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件
的内容将写指令从前到后执行一次以完成数据的恢复工作

Aof保存的是 appendonly.aof 文件

配置

############################## APPEND ONLY MODE ###############################

# By default Redis asynchronously dumps the dataset on disk. This mode is
# good enough in many applications, but an issue with the Redis process or
# a power outage may result into a few minutes of writes lost (depending on
# the configured save points).
#
# The Append Only File is an alternative persistence mode that provides
# much better durability. For instance using the default data fsync policy
# (see later in the config file) Redis can lose just one second of writes in a
# dramatic event like a server power outage, or a single write if something
# wrong with the Redis process itself happens, but the operating system is
# still running correctly.
#
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check http://redis.io/topics/persistence for more information.

appendonly no

# The name of the append only file (default: "appendonly.aof")

appendfilename "appendonly.aof"
appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这
种方式在许多应用中已经足够用了

appendfilename "appendonly.aof" # appendfilename AOF 文件名称

appendfsync everysec # appendfsync aof持久化策略的配置
# no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
# always表示每次写入都执行fsync,以保证数据同步到磁盘。
# everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
No-appendfsync-on-rewrite #重写时是否可以运用Appendfsync,用默认no即可,保证数据安
全性
Auto-aof-rewrite-min-size # 设置重写的基准值
Auto-aof-rewrite-percentage #设置重写的基准值

AOF 启动/修复/恢复

正常恢复:
    启动:设置Yes,修改默认的appendonly no,改为yes
    将有数据的aof文件复制一份保存到对应目录(config get dir)
    恢复:重启redis然后重新加载
异常恢复:
    启动:设置Yes
    故意破坏 appendonly.aof 文件!
    修复: redis-check-aof --fix appendonly.aof 进行修复
    恢复:重启 redis 然后重新加载

Rewrite

AOF 采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小
超过所设定的阈值时,Redis 就会启动AOF 文件的内容压缩,只保留可以恢复数据的最小指令集,可以
使用命令 bgrewriteaof !

重写原理

AOF 文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再
rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧
的aof文件,这点和快照有点类似!

触发机制

Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的已被且文件大
于64M的触发。

优点和缺点

优点:
    1、每修改同步:appendfsync always 同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差
    但数据完整性比较好
    2、每秒同步: appendfsync everysec 异步操作,每秒记录 ,如果一秒内宕机,有数据丢失
    3、不同步: appendfsync no 从不同步
缺点:
    1、相同数据集的数据而言,aof 文件要远大于 rdb文件,恢复速度慢于 rdb。
    2、Aof 运行效率要慢于 rdb,每秒同步策略效率较好,不同步效率和rdb相同。

小总结

AOF文件是一个只进行追加日志的文件

Redis可以在AOF文件体积变得过大时,自动地在后台对AOF进行重写

AOF文件有序的保存了对数据库执行的所有写入操作,这些写入操作以Redis协议的格式保存,因此AOF文件的内容非常容易被人读懂,对文件进行分析也很轻松。

对相同的数据集来说,AOF文件的体积通常要大于RDB文件的体积。

根据所有使用的fsync策略,AOF的速度可能会慢于RDB

总结

1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始
的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重
写,使得AOF文件的体积不至于过大。
3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
4、同时开启两种持久化方式
在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF
文件保存的数据集要比RDB文件保存的数据集要完整。
RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者
建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有
AOF可能潜在的Bug,留着作为一个万一的手段。
5、性能建议
因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够
了,只保留 save 900 1 这条规则。
如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自
己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产
生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite
的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重
写可以改到适当的数值。
如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也
减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,
启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。

Redis事务

Redis 事务本质:一组命令的集合! 一个事务中的所有命令都会被序列化,在事务执行过程的中,会按

照顺序执行!

一次性、顺序性、排他性!执行一些列的命令!

------ 队列 set set set 执行------

所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!Exec

Redis单条命令式保存原子性的,但是事务不保证原子性!

redis的事务:

  • 开启事务(multi)
  • 命令入队(......)
  • 执行事务(exec)

正常执行事务!

127.0.0.1:6379[8]> multi
OK
127.0.0.1:6379[8]> set k1 v1
QUEUED
127.0.0.1:6379[8]> set k2 v2
QUEUED
127.0.0.1:6379[8]> get k2
QUEUED
127.0.0.1:6379[8]> set k3 v3
QUEUED
127.0.0.1:6379[8]> exec
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379[8]>

放弃事务!

127.0.0.1:6379[8]> multi
OK
127.0.0.1:6379[8]> set k1 v1
QUEUED
127.0.0.1:6379[8]> set k4 v4
QUEUED
127.0.0.1:6379[8]> discard
OK
127.0.0.1:6379[8]> get k4
(nil)
127.0.0.1:6379[8]>

编译型异常(代码有问题! 命令有错!) ,事务中所有的命令都不会被执行!

127.0.0.1:6379[8]> multi
OK
127.0.0.1:6379[8]> set k1 v1
QUEUED
127.0.0.1:6379[8]> set k2 v2
QUEUED
127.0.0.1:6379[8]> set k3 v3
QUEUED
127.0.0.1:6379[8]> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> set k4 v4
QUEUED
127.0.0.1:6379[8]> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379[8]> get k4
(nil)
127.0.0.1:6379[8]>

运行时异常(1/0), 如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!

127.0.0.1:6379[8]> multi
OK
127.0.0.1:6379[8]> incr k1
QUEUED
127.0.0.1:6379[8]> set k2 v2
QUEUED
127.0.0.1:6379[8]> set k3 v3
QUEUED
127.0.0.1:6379[8]> get k3
QUEUED
127.0.0.1:6379[8]> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
127.0.0.1:6379[8]>

监控! Watch

悲观锁:
    很悲观,认为什么时候都会出问题,无论做什么都会加锁!
乐观锁:
    很乐观,认为什么时候都不会出问题,所以不会上锁! 
    更新数据的时候去判断一下,在此期间是否有人修改过这个数据,
    获取version更新的时候比较 version

Redis测监视测试

127.0.0.1:6379[8]> set money 100
OK
127.0.0.1:6379[8]> set out 0
OK
127.0.0.1:6379[8]> watch money
OK
127.0.0.1:6379[8]> multi
OK
127.0.0.1:6379[8]> decrby money 20
QUEUED
127.0.0.1:6379[8]> incrby out 20
QUEUED
127.0.0.1:6379[8]> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379[8]> watch money
OK
127.0.0.1:6379[8]> decrby money 10
(integer) 70
127.0.0.1:6379[8]> multi
OK
127.0.0.1:6379[8]> decrby money 10
QUEUED
127.0.0.1:6379[8]> incrby out 10
QUEUED
127.0.0.1:6379[8]> exec  # 执行之前,另外一个线程,修改了我们的值,这个时候,就会导致事务执行失败!
(nil)
127.0.0.1:6379[8]>
127.0.0.1:6379[8]> unwatch   #解锁
OK
127.0.0.1:6379[8]> watch money
OK
127.0.0.1:6379[8]> multi
OK
127.0.0.1:6379[8]> decrby money 10
QUEUED
127.0.0.1:6379[8]> exec
1) (integer) 40

Redis发布订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统!

Redis 客户端可以订阅任意数量的频道。

订阅/发布消息图:

第一个:消息发送者,

第二个:频道 第三个:消息订阅者!

下图展示了频道 channel1 ,以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

当有新消息通过 PUBLISH 命令发送给频道 channel1 时,这个消息就会被发送给订阅它的三个客户端:

命令

这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等

测试

以下实例演示了发布订阅是如何工作的。在我们实例中我们创建了订阅频道名为 redisChat
127.0.0.1:6379[1]> subscribe redisChat
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1
现在,我们先重新开启个 redis 客户端,然后在同一个频道 redisChat 发布两次消息,订阅者就能接收
到消息。

127.0.0.1:6379[1]> publish redisChat "hello world"
(integer) 1
127.0.0.1:6379[1]>
127.0.0.1:6379[1]> publish redisChat "hello redis"
(integer) 1
127.0.0.1:6379[1]>
# 订阅者的客户端会显示如下消息

1) "message"
2) "redisChat"
3) "hello world"
1) "message"
2) "redisChat"
3) "hello redis"

原理

  Redis是使用C实现的,通过分析 Redis 源码里的 pubsub.c 文件,了解发布和订阅机制的底层实现,籍
此加深对 Redis 的理解。
Redis 通过 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能。
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个 channel
,而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关
键,就是将客户端添加到给定 channel 的订阅链表中。

  通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel
字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个
key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应
的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。

使用场景
Pub/Sub构建实时消息系统
Redis的Pub/Sub系统可以构建实时的消息系统
比如很多用Pub/Sub构建的实时聊天系统的例子。

Redis主从复制

概念

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点
(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。
Master以写为主,Slave 以读为主。

默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从
节点只能有一个主节点。

主从复制的作用主要包括:
1、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2、故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务
的冗余。
3、负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务
(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写
少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
4、高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是
Redis高可用的基础。
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:
1、从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较
大;
2、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有
内存用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G。

电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。

环境配置

基本配置

配从库不配主库,从库配置:

slaveof 主库ip 主库端口 # 配置主从
Info replication # 查看信息

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:8c195dee5ad4570e0859000f5d5ba4f4e62feae1
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

每次与 master 断开之后,都需要重新连接,除非你配置进 redis.conf 文件!

修改配置文件!

准备工作:我们配置主从复制,至少需要三个,一主二从!配置三个客户端!

1、拷贝多个redis.conf 文件
2、指定端口 6379,依次类推
3、开启daemonize yes
4、Pid文件名字 pidfile /var/run/redis_6379.pid , 依次类推
5、Log文件名字 logfile "6379.log" , 依次类推
6、Dump.rdb 名字 dbfilename dump6379.rdb , 依次类推

一主二从

1、环境初始化
2、默认三个都是Master 主节点
3、配置为一个Master 两个Slave
	slaveof 127.0.0.1 6379
	slaveof 127.0.0.1 6379

masterauth 主节点有密码需要在从节点加上这个配置

主节点

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=490,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=490,lag=0
master_failover_state:no-failover
master_replid:76ddabf7ea2c24abfee9e4f347784216c969dfdf
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:490
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:490

从节点
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_read_repl_offset:14
slave_repl_offset:14
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:76ddabf7ea2c24abfee9e4f347784216c969dfdf
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14

从节点
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_read_repl_offset:448
slave_repl_offset:448
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:76ddabf7ea2c24abfee9e4f347784216c969dfdf
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:448
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:435
repl_backlog_histlen:14

在主机设置值,在从机都可以取到!从机不能写值!

127.0.0.1:6379[1]> set name tom
OK
127.0.0.1:6379[1]> get name
"tom"
127.0.0.1:6379[1]>
127.0.0.1:6380[1]> get name
"tom"
127.0.0.1:6380[1]> set name1 jack
(error) READONLY You can't write against a read only replica.
127.0.0.1:6380[1]>
127.0.0.1:6381[1]> get name
"tom"
127.0.0.1:6381[1]>
127.0.0.1:6381[1]> set name1 jack
(error) READONLY You can't write against a read only replica.
127.0.0.1:6381[1]>

层层链路

上一个Slave 可以是下一个slave 和 Master,Slave 同样可以接收其他 slaves 的连接和同步请求,那么
该 slave 作为了链条中下一个的master,可以有效减轻 master 的写压力!
127.0.0.1:6379[1]> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=4213,lag=1
master_failover_state:no-failover
master_replid:76ddabf7ea2c24abfee9e4f347784216c969dfdf
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:4213
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:4213

127.0.0.1:6380[1]> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_read_repl_offset:4199
slave_repl_offset:4199
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=4199,lag=0
master_failover_state:no-failover
master_replid:76ddabf7ea2c24abfee9e4f347784216c969dfdf
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:4199
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:4102
repl_backlog_histlen:98

127.0.0.1:6381[1]> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_read_repl_offset:4199
slave_repl_offset:4199
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:76ddabf7ea2c24abfee9e4f347784216c969dfdf
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:4199
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:4186
repl_backlog_histlen:14

6379 设置值以后 6380 和 6381 都可以获取到!

127.0.0.1:6379[1]> set a 1
OK
127.0.0.1:6379[1]>
127.0.0.1:6379[1]> get a
"1"
127.0.0.1:6379[1]>
127.0.0.1:6380[1]> get a
"1"
127.0.0.1:6380[1]>
127.0.0.1:6381[1]> get a
"1"
127.0.0.1:6381[1]>

谋朝篡位

一主二从的情况下,如果主机断了,从机可以使用命令 SLAVEOF NO ONE 将自己改为主机!这个时
候其余的从机链接到这个节点。对一个从属服务器执行命令 SLAVEOF NO ONE 将使得这个从属服务器
关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。
主机再回来,也只是一个光杆司令了,从机为了正常使用跑到了新的主机上.

复制原理

Slave 启动成功连接到 master 后会发送一个sync命令
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行
完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步.但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

哨兵模式

概述

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工
干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑
哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题。

谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独
立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里的哨兵有两个作用
  通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。
各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认
为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一
定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。
切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为
客观下线。

配置测试

1、配置哨兵配置文件 sentinel.conf

# sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1

后面的这个数字1,代表主机挂了,slave投票看让谁接替成为主机,票数最多的,就会成为主机!

2、启动哨兵!

1:X 14 Mar 2023 03:32:16.785 # Could not rename tmp config file (Device or resource busy)
1:X 14 Mar 2023 03:32:16.785 # WARNING: Sentinel was not able to save the new configuration on disk!!!: Device or resource busy
1:X 14 Mar 2023 03:32:16.785 # Sentinel ID is c421c75cebaab7997c7b917b5479d328096ffb93
1:X 14 Mar 2023 03:32:16.785 # +monitor master mymaster 127.0.0.1 6379 quorum 1
1:X 14 Mar 2023 03:32:16.785 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:32:16.787 # Could not rename tmp config file (Device or resource busy)
1:X 14 Mar 2023 03:32:16.787 # WARNING: Sentinel was not able to save the new configuration on disk!!!: Device or resource busy
1:X 14 Mar 2023 03:32:16.787 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:32:16.789 # Could not rename tmp config file (Device or resource busy)
1:X 14 Mar 2023 03:32:16.789 # WARNING: Sentinel was not able to save the new configuration on disk!!!: Device or resource busy

如果Master 节点断开了,这个时候就会从从机中随机选择一个服务器!(这里面有一个投票算法!)

1:X 14 Mar 2023 03:37:56.905 # +sdown master mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:37:56.905 # +odown master mymaster 127.0.0.1 6379 #quorum 1/1
1:X 14 Mar 2023 03:37:56.905 # +new-epoch 1
1:X 14 Mar 2023 03:37:56.905 # +try-failover master mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:37:56.908 # Could not rename tmp config file (Device or resource busy)
1:X 14 Mar 2023 03:37:56.908 # WARNING: Sentinel was not able to save the new configuration on disk!!!: Device or resource busy
1:X 14 Mar 2023 03:37:56.908 # +vote-for-leader c421c75cebaab7997c7b917b5479d328096ffb93 1
1:X 14 Mar 2023 03:37:56.908 # +elected-leader master mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:37:56.908 # +failover-state-select-slave master mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:37:56.991 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:37:56.991 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:37:57.054 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:37:58.004 # Could not rename tmp config file (Device or resource busy)
1:X 14 Mar 2023 03:37:58.004 # WARNING: Sentinel was not able to save the new configuration on disk!!!: Device or resource busy
1:X 14 Mar 2023 03:37:58.004 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:37:58.004 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:37:58.085 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:37:58.144 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:37:58.144 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
1:X 14 Mar 2023 03:37:58.196 # +failover-end master mymaster 127.0.0.1 6379

1:X 14 Mar 2023 03:37:58.196 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6381
1:X 14 Mar 2023 03:37:58.196 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
1:X 14 Mar 2023 03:37:58.196 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
1:X 14 Mar 2023 03:37:58.198 # Could not rename tmp config file (Device or resource busy)

1:X 14 Mar 2023 03:37:58.198 # WARNING: Sentinel was not able to save the new configuration on disk!!!: Device or resource busy
1:X 14 Mar 2023 03:38:28.211 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=166202,lag=0
master_failover_state:no-failover
master_replid:d333babcdcc45e8569282c0e5ecb0dc4c350fe17
master_replid2:76ddabf7ea2c24abfee9e4f347784216c969dfdf
master_repl_offset:166202
second_repl_offset:129649
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:108233
repl_backlog_histlen:57970
127.0.0.1:6381>
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6381
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_read_repl_offset:148153
slave_repl_offset:148153
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:d333babcdcc45e8569282c0e5ecb0dc4c350fe17
master_replid2:76ddabf7ea2c24abfee9e4f347784216c969dfdf
master_repl_offset:148153
second_repl_offset:129649
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:4102
repl_backlog_histlen:144052

如果主机此时回来了,只能归并到新的主机下,当做从机,这就是哨兵模式的规则!

1:X 14 Mar 2023 03:48:38.159 # -sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
1:X 14 Mar 2023 03:48:48.164 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
1:X 14 Mar 2023 03:48:51.360 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
1:X 14 Mar 2023 03:48:51.362 # Could not rename tmp config file (Device or resource busy)
1:X 14 Mar 2023 03:48:51.362 # WARNING: Sentinel was not able to save the new configuration on disk!!!: Device or resource busy
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6381
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_read_repl_offset:179181
slave_repl_offset:179181
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:d333babcdcc45e8569282c0e5ecb0dc4c350fe17
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:179181
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:173782
repl_backlog_histlen:5400

127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=175466,lag=0
slave1:ip=127.0.0.1,port=6379,state=online,offset=175466,lag=0
master_failover_state:no-failover
master_replid:d333babcdcc45e8569282c0e5ecb0dc4c350fe17
master_replid2:76ddabf7ea2c24abfee9e4f347784216c969dfdf
master_repl_offset:175601
second_repl_offset:129649
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:108233
repl_backlog_histlen:67369
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6381
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_read_repl_offset:176290
slave_repl_offset:176290
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:d333babcdcc45e8569282c0e5ecb0dc4c350fe17
master_replid2:76ddabf7ea2c24abfee9e4f347784216c969dfdf
master_repl_offset:176290
second_repl_offset:129649
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:4102
repl_backlog_histlen:172189

哨兵模式

优点:
1、哨兵集群,基于主从复制模式,所有的主从配置优点,它全有
2、 主从可以切换,故障可以转移,系统的可用性就会更好
3、哨兵模式就是主从模式的升级,手动到自动,更加健壮!
缺点:
1、Redis 不好啊在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦!
2、实现哨兵模式的配置其实是很麻烦的,里面有很多选择!

哨兵模式的配置文件

# Example sentinel.conf

# 哨兵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无
法正常启动成功。
#通知脚本
# shell编程
# 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 # 一般都是由运维来配
置!

缓存穿透和雪崩

服务的高可用问题!

在这里我们不会详细的区分析解决方案的底层!
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。
另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。

缓存穿透(查不到)

概念

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

解决方案

布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;

缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;

但是这种方法会存在两个问题:
1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

缓存击穿(量太大,缓存过期!)

概述

这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。

解决方案

设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。

加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

缓存雪崩

概念

缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。

解决方案

redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)
限流降级(在SpringCloud讲解过!)
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

Jedis

什么是Jedis 是 Redis 官方推荐的 java连接开发工具! 使用Java 操作Redis 中间件!如果你要使用java操作redis,那么一定要对Jedis 十分的熟悉!

测试

1、导入对应的依赖

<!--导入jedis的包-->
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.2.0</version>
    </dependency>
    <!--fastjson-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </dependency>
</dependencies>

2、编码测试:

import redis.clients.jedis.Jedis;
public class TestPing {
	public static void main(String[] args) {
		Jedis jedis = new Jedis("",6379);
		jedis.auth("");
		System.out.println("连接成功");
//查看服务是否运行
		System.out.println("服务正在运行: "+jedis.ping());
	}
}

4、启动redis服务

5、启动测试,结果

连接成功
服务正在运行: PONG

常用API

基本操作

public static void main(String[] args) {
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    //验证密码,如果没有设置密码这段代码省略
    // jedis.auth("password");
    jedis.connect(); //连接
    jedis.disconnect(); //断开连接
    jedis.flushAll(); //清空所有的key
}

对key操作的命令

public static class TestKey {
		public static void main(String[] args) {
    		Jedis jedis = new Jedis("127.0.0.1", 6379);
    		//验证密码,如果没有设置密码这段代码省略
    		// jedis.auth("password");
			System.out.println("清空数据:"+jedis.flushDB());
			System.out.println("判断某个键是否存在:"+jedis.exists("username"));
			System.out.println("新增<'username','tom'>的键值 对:"+jedis.set("username", "tom"));
			System.out.println("新增<'password','password'>的键值 对:"+jedis.set("password", "password"));
			System.out.print("系统中所有的键如下:");
			Set<String> keys = jedis.keys("*");
			System.out.println(keys);
			System.out.println("删除键password:"+jedis.del("password"));
			System.out.println("判断键password是否存 在:"+jedis.exists("password"));
			System.out.println("查看键username所存储的值的类 型:"+jedis.type("username"));
			System.out.println("随机返回key空间的一个:"+jedis.randomKey());
			System.out.println("重命名key:"+jedis.rename("username","name"));
			System.out.println("取出改后的name:"+jedis.get("name"));
			System.out.println("按索引查询:"+jedis.select(0));
			System.out.println("删除当前选择数据库中的所有key:"+jedis.flushDB());
			System.out.println("返回当前数据库中key的数目:"+jedis.dbSize());
			System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());
		}
	}
清空数据:OK
判断某个键是否存在:false
新增<'username','tom'>的键值 对:OK
新增<'password','password'>的键值 对:OK
系统中所有的键如下:[password, username]
删除键password:1
判断键password是否存 在:false
查看键username所存储的值的类 型:string
随机返回key空间的一个:username
重命名key:OK
取出改后的name:tom
按索引查询:OK
删除当前选择数据库中的所有key:OK
返回当前数据库中key的数目:0
删除所有数据库中的所有key:OK

对String操作的命令

	public static class TestString {
		public static void main(String[] args) {
    		Jedis jedis = new Jedis("127.0.0.1", 6379);
    		//验证密码,如果没有设置密码这段代码省略
    		// jedis.auth("password");
			jedis.flushDB();
			System.out.println("===========增加数据===========");
			System.out.println(jedis.set("key1","value1"));
			System.out.println(jedis.set("key2","value2"));
			System.out.println(jedis.set("key3", "value3"));
			System.out.println("删除键key2:"+jedis.del("key2"));
			System.out.println("获取键key2:"+jedis.get("key2"));
			System.out.println("修改key1:"+jedis.set("key1", "value1Changed"));
			System.out.println("获取key1的值:"+jedis.get("key1"));
			System.out.println("在key3后面加入值:"+jedis.append("key3", "End"));
			System.out.println("key3的值:"+jedis.get("key3"));
			System.out.println("增加多个键值 对:"+jedis.mset("key01","value01","key02","value02","key03","value03"));
			System.out.println("获取多个键值 对:"+jedis.mget("key01","key02","key03"));
			System.out.println("获取多个键值 对:"+jedis.mget("key01","key02","key03","key04"));
			System.out.println("删除多个键值对:"+jedis.del("key01","key02"));
			System.out.println("获取多个键值 对:"+jedis.mget("key01","key02","key03"));
			jedis.flushDB();
			System.out.println("===========新增键值对防止覆盖原先值==============");
			System.out.println(jedis.setnx("key1", "value1"));
			System.out.println(jedis.setnx("key2", "value2"));
			System.out.println(jedis.setnx("key2", "value2-new"));
			System.out.println(jedis.get("key1"));
			System.out.println(jedis.get("key2"));
			System.out.println("===========新增键值对并设置有效时间=============");
			System.out.println(jedis.setex("key3", 2, "value3"));
			System.out.println(jedis.get("key3"));
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(jedis.get("key3"));
			System.out.println("===========获取原值,更新为新值==========");
			System.out.println(jedis.getSet("key2", "key2GetSet"));
			System.out.println(jedis.get("key2"));
			System.out.println("获得key2的值的字串:"+jedis.getrange("key2", 2,
					4));
		}
	}
===========增加数据===========
OK
OK
OK
删除键key2:1
获取键key2:null
修改key1:OK
获取key1的值:value1Changed
在key3后面加入值:9
key3的值:value3End
增加多个键值 对:OK
获取多个键值 对:[value01, value02, value03]
获取多个键值 对:[value01, value02, value03, null]
删除多个键值对:2
获取多个键值 对:[null, null, value03]
===========新增键值对防止覆盖原先值==============
1
1
0
value1
value2
===========新增键值对并设置有效时间=============
OK
value3
null
===========获取原值,更新为新值==========
value2
key2GetSet
获得key2的值的字串:y2G

对List操作命令

public static class TestList {
		public static void main(String[] args) {
    		Jedis jedis = new Jedis("127.0.0.1", 6379);
    		//验证密码,如果没有设置密码这段代码省略
    		// jedis.auth("password");
			jedis.flushDB();
			System.out.println("===========添加一个list===========");
			jedis.lpush("collections", "ArrayList", "Vector", "Stack",
					"HashMap", "WeakHashMap", "LinkedHashMap");
			jedis.lpush("collections", "HashSet");
			jedis.lpush("collections", "TreeSet");
			jedis.lpush("collections", "TreeMap");
			System.out.println("collections的内容:" + jedis.lrange("collections",
					0, -1));//-1代表倒数第一个元素,-2代表倒数第二个元素,end为-1表示查询全部
			System.out.println("collections区间0-3的元 素:"+jedis.lrange(" collections",0,3));
			System.out.println("===============================");// 删除列表指定的值 ,第二个参数为删除的个数(有重复时),后add进去的值先被删,类似于出栈
			System.out.println("删除指定元素个数:" + jedis.lrem("collections", 2, "HashMap"));
			System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
			System.out.println("删除下表0-3区间之外的元 素:"+jedis.ltrim(" collections", 0, 3));
			System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
			System.out.println("collections列表出栈(左 端):"+jedis.lpop(" collections"));
			System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
			System.out.println("collections添加元素,从列表右端,与lpush相对 应:"+jedis.rpush(" collections", "EnumMap"));
			System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
			System.out.println("collections列表出栈(右 端):"+jedis.rpop("collections"));
			System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
			System.out.println("修改collections指定下标1的内 容:"+jedis.lset("collections", 1, "LinkedArrayList"));
			System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
			System.out.println("===============================");
			System.out.println("collections的长度:" + jedis.llen("collections"));
			System.out.println("获取collections下标为2的元 素:"+jedis.lindex(" collections", 2));
			System.out.println("===============================");
			jedis.lpush("sortedList", "3", "6", "2", "0", "7", "4");
			System.out.println("sortedList排序前:" + jedis.lrange("sortedList", 0, -1));
			System.out.println(jedis.sort("sortedList"));
			System.out.println("sortedList排序后:" + jedis.lrange("sortedList", 0, -1));
		}
	}
===========添加一个list===========
collections的内容:[TreeMap, TreeSet, HashSet, LinkedHashMap, WeakHashMap, HashMap, Stack, Vector, ArrayList]
collections区间0-3的元 素:[]
===============================
删除指定元素个数:1
collections的内容:[TreeMap, TreeSet, HashSet, LinkedHashMap, WeakHashMap, Stack, Vector, ArrayList]
删除下表0-3区间之外的元 素:OK
collections的内容:[TreeMap, TreeSet, HashSet, LinkedHashMap, WeakHashMap, Stack, Vector, ArrayList]
collections列表出栈(左 端):null
collections的内容:[TreeMap, TreeSet, HashSet, LinkedHashMap, WeakHashMap, Stack, Vector, ArrayList]
collections添加元素,从列表右端,与lpush相对 应:1
collections的内容:[TreeMap, TreeSet, HashSet, LinkedHashMap, WeakHashMap, Stack, Vector, ArrayList]
collections列表出栈(右 端):ArrayList
collections的内容:[TreeMap, TreeSet, HashSet, LinkedHashMap, WeakHashMap, Stack, Vector]
修改collections指定下标1的内 容:OK
collections的内容:[TreeMap, LinkedArrayList, HashSet, LinkedHashMap, WeakHashMap, Stack, Vector]
===============================
collections的长度:7
获取collections下标为2的元 素:null
===============================
sortedList排序前:[4, 7, 0, 2, 6, 3]
[0, 2, 3, 4, 6, 7]
sortedList排序后:[4, 7, 0, 2, 6, 3]

对Set的操作命令

public static class TestSet {
		public static void main(String[] args) {
    		Jedis jedis = new Jedis("127.0.0.1", 6379);
    		//验证密码,如果没有设置密码这段代码省略
    		// jedis.auth("password");
			jedis.flushDB();
			System.out.println("============向集合中添加元素(不重复)============");
			System.out.println(jedis.sadd("eleSet", "e1","e2","e4","e3","e0","e8","e7","e5"));
			System.out.println(jedis.sadd("eleSet", "e6"));
			System.out.println(jedis.sadd("eleSet", "e6"));
			System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
			System.out.println("删除一个元素e0:"+jedis.srem("eleSet", "e0"));
			System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
			System.out.println("删除两个元素e7和e6:"+jedis.srem("eleSet", "e7","e6"));
			System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
			System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));
			System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));
			System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
			System.out.println("eleSet中包含元素的个数:"+jedis.scard("eleSet"));
			System.out.println("e3是否在eleSet中:"+jedis.sismember("eleSet", "e3"));
			System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet", "e1"));
			System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet", "e5"));
			System.out.println("=================================");
			System.out.println(jedis.sadd("eleSet1", "e1","e2","e4","e3","e0","e8","e7","e5"));
			System.out.println(jedis.sadd("eleSet2", "e1","e2","e4","e3","e0","e8"));
			System.out.println("将eleSet1中删除e1并存入eleSet3 中:"+jedis.smove("eleSet1", "eleSet3", "e1"));//移到集合元素
			System.out.println("将eleSet1中删除e2并存入eleSet3 中:"+jedis.smove("eleSet1", "eleSet3", "e2"));
			System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));
			System.out.println("eleSet3中的元素:"+jedis.smembers("eleSet3"));
			System.out.println("============集合运算=================");
			System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));
			System.out.println("eleSet2中的元素:"+jedis.smembers("eleSet2"));
			System.out.println("eleSet1和eleSet2的交 集:"+jedis.sinter("eleSet1","eleSet2"));
			System.out.println("eleSet1和eleSet2的并 集:"+jedis.sunion("eleSet1","eleSet2"));
			System.out.println("eleSet1和eleSet2的差 集:"+jedis.sdiff("eleSet1","eleSet2"));//eleSet1中有,eleSet2中没有
			jedis.sinterstore("eleSet4","eleSet1","eleSet2");//求交集并将交集保存到dstkey的集合
			System.out.println("eleSet4中的元素:"+jedis.smembers("eleSet4"));
		}
	}
============向集合中添加元素(不重复)============
8
1
0
eleSet的所有元素为:[e7, e3, e5, e1, e0, e4, e8, e2, e6]
删除一个元素e0:1
eleSet的所有元素为:[e7, e3, e5, e1, e4, e8, e2, e6]
删除两个元素e7和e6:2
eleSet的所有元素为:[e3, e5, e1, e4, e8, e2]
随机的移除集合中的一个元素:e5
随机的移除集合中的一个元素:e4
eleSet的所有元素为:[e3, e1, e8, e2]
eleSet中包含元素的个数:4
e3是否在eleSet中:true
e1是否在eleSet中:true
e1是否在eleSet中:false
=================================
8
6
将eleSet1中删除e1并存入eleSet3 中:1
将eleSet1中删除e2并存入eleSet3 中:1
eleSet1中的元素:[e7, e8, e3, e4, e5, e0]
eleSet3中的元素:[e2, e1]
============集合运算=================
eleSet1中的元素:[e7, e8, e3, e4, e5, e0]
eleSet2中的元素:[e2, e8, e3, e4, e0, e1]
eleSet1和eleSet2的交 集:[e8, e3, e4, e0]
eleSet1和eleSet2的并 集:[e2, e4, e3, e8, e7, e1, e0, e5]
eleSet1和eleSet2的差 集:[e7, e5]
eleSet4中的元素:[e4, e3, e8, e0]

对Hash的操作命令

public static class TestHash {
		public static void main(String[] args) {
    		Jedis jedis = new Jedis("127.0.0.1", 6379);
    		//验证密码,如果没有设置密码这段代码省略
    		// jedis.auth("password");
			Map<String,String> map = new HashMap<>();
			map.put("key1","value1");
			map.put("key2","value2");
			map.put("key3","value3");
			map.put("key4","value4");
			//添加名称为hash(key)的hash元素
			jedis.hmset("hash",map);
			//向名称为hash的hash中添加key为key5,value为value5元素
			jedis.hset("hash", "key5", "value5");
			System.out.println("散列hash的所有键值对 为:"+jedis.hgetAll("hash"));//return Map<String,String>
			System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));//returnSet<String>
			System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));//returnList<String>
			System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加 key6:"+jedis.hincrBy("hash", "key6", 6));
			System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
			System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加 key6:"+jedis.hincrBy("hash", "key6", 3));
			System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
			System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash", "key2"));
			System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
			System.out.println("散列hash中键值对的个数:"+jedis.hlen("hash"));
			System.out.println("判断hash中是否存在 key2:"+jedis.hexists("hash","key2"));
			System.out.println("判断hash中是否存在 key3:"+jedis.hexists("hash","key3"));
			System.out.println("获取hash中的值:"+jedis.hmget("hash","key3"));
			System.out.println("获取hash中的 值:"+jedis.hmget("hash","key3","key4"));
		}
	}
散列hash的所有键值对 为:{key1=value1, key2=value2, key5=value5, key3=value3, key4=value4}
散列hash的所有键为:[key1, key2, key5, key3, key4]
散列hash的所有值为:[value2, value3, value4, value1, value5]
将key6保存的值加上一个整数,如果key6不存在则添加 key6:6
散列hash的所有键值对为:{key1=value1, key2=value2, key5=value5, key6=6, key3=value3, key4=value4}
将key6保存的值加上一个整数,如果key6不存在则添加 key6:9
散列hash的所有键值对为:{key1=value1, key2=value2, key5=value5, key6=9, key3=value3, key4=value4}
删除一个或者多个键值对:1
散列hash的所有键值对为:{key1=value1, key5=value5, key6=9, key3=value3, key4=value4}
散列hash中键值对的个数:5
判断hash中是否存在 key2:false
判断hash中是否存在 key3:true
获取hash中的值:[value3]
获取hash中的 值:[value3, value4]

事务

基本操作

public static class TestMulti {
		public static void main(String[] args) {
//创建客户端连接服务端,redis服务端需要被开启
    		Jedis jedis = new Jedis("127.0.0.1", 6379);
    		//验证密码,如果没有设置密码这段代码省略
    		// jedis.auth("password");
			jedis.flushDB();
			JSONObject jsonObject = new JSONObject();
			jsonObject.put("hello", "world");
			jsonObject.put("name", "java");
//开启事务
			Transaction multi = jedis.multi();
			String result = jsonObject.toJSONString();
			try{
//向redis存入一条数据
				multi.set("json", result);
//再存入一条数据
				multi.set("json2", result);
//这里引发了异常,用0作为被除数
				int i = 100/0;
//如果没有引发异常,执行进入队列的命令
				multi.exec();
			}catch(Exception e){
				e.printStackTrace();
//如果出现异常,回滚
				multi.discard();
			}finally{
				System.out.println(jedis.get("json"));
				System.out.println(jedis.get("json2"));
//最终关闭客户端
				jedis.close();
			}
		}
	}
java.lang.ArithmeticException: / by zero
	at com.neuqsoft.rd.rediscluster.kuangshen.JedisTest$TestMulti.main(JedisTest.java:237)
null
null

SpringBoot整合

概述

在SpringBoot中一般使用RedisTemplate提供的方法来操作Redis。那么使用SpringBoot整合Redis需要
那些步骤呢。
1、 JedisPoolConfig (这个是配置连接池)
2、 RedisConnectionFactory 这个是配置连接信息,这里的RedisConnectionFactory是一个接口,我们需要使用它的实现类,在SpringD Data Redis方案中提供了以下四种工厂模型:
	JredisConnectionFactory
	JedisConnectionFactory
	LettuceConnectionFactory
	SrpConnectionFactory
3、 RedisTemplate 基本操作

导入依赖

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

yaml配置

spring:
    redis:
        host: 127.0.0.1
        port: 6379
        password: 123456
        jedis:
            pool:
                max-active: 8
                max-wait: -1ms
                max-idle: 500
                min-idle: 0
        lettuce:
            shutdown-timeout: 0ms

测试

@SpringBootTest
class SpringbootRedisApplicationTests {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;
    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("myKey","myValue");
        System.out.println(redisTemplate.opsForValue().get("myKey"));
    }
}

封装工具类

1、新建一个SpringBoot项目

2、导入redis的启动器

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

3、配置redis,可以查看 RedisProperties 分析

# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379

4、分析 RedisAutoConfifiguration 自动配置类

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

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

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}
通过源码可以看出,SpringBoot自动帮我们在容器中生成了一个RedisTemplate和一个StringRedisTemplate。
但是,这个RedisTemplate的泛型是<Object,Object>,写代码不方便,需要写好多类型转换的代码;我们需要一个泛型为<String,Object>形式的RedisTemplate。
并且,这个RedisTemplate没有设置数据存在Redis时,key及value的序列化方式。看到这个@ConditionalOnMissingBean注解后,就知道如果Spring容器中有了RedisTemplate对象了,这个自动配置的RedisTemplate不会实例化。因此我们可以直接自己写个配置类,配置RedisTemplate。

5、既然自动配置不好用,就重新配置一个RedisTemplate

@Configuration
public class RedisConfig {
    @Bean
    @SuppressWarnings("all")
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactoryfactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<String,Object>();
            template.setConnectionFactory(factory);
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
            Jackson2JsonRedisSerializer(Object.class);
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(om);
            StringRedisSerializer stringRedisSerializer = new
            StringRedisSerializer();
            // key采用String的序列化方式
            template.setKeySerializer(stringRedisSerializer);
            // hash的key也采用String的序列化方式
            template.setHashKeySerializer(stringRedisSerializer);
            // value序列化方式采用jackson
            template.setValueSerializer(jackson2JsonRedisSerializer);
            // hash的value序列化方式采用jackson
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
            return template;
    }
}

6、写一个Redis工具类(直接用RedisTemplate操作Redis,需要很多行代码,因此直接封装好一个RedisUtils,这样写代码更方便点。这个RedisUtils交给Spring容器实例化,使用时直接注解注入。)

posted @ 2023-03-15 21:07  When?  阅读(36)  评论(0编辑  收藏  举报