redis详细笔记

redis详细笔记
xxd
2021/12/03 北京

一、redis入门

redis安装

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

1 /usr 这是一个非常重要的目录,类似于windows下的Program Files,存放用户的程序

8、拷贝配置文件(备用)

cd /usr/local/bin
ls -l
# 在redis的解压目录下备份redis.conf
mkdir myredis
cp redis.conf myredis # 拷一个备份,养成良好的习惯,我们就修改这个文件
# 修改配置保证可以后台应用
vim redis.conf

设置redis为后台守护进程启动

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进程退出。

修改redis中连接的绑定主机地址

网络

在进行redis的安装之后,如果需要外部连接到这里面的内容,那么就需要对应的修改一些配置,虚拟机外面才可以访问的到

bind 127.0.0.1 # 绑定的端口

protected-mode yes #保护模式

port 6379 # 端口

查看redis是否启动

# 查看进程
ps -ef | grep redis

# 简写命令 查看进程
lsof -i  :6379查看redis是否启动

redis性能测试

Redis-benchmark是官方自带的Redis性能测试工具

redis-benchmark [option] [option value]

可选的参数有这些:

序号选项描述默认值
1-h指定服务器主机名127.0.0.1
2-p指定服务器端口6379
3-s指定服务器 socket
4-c指定并发连接数50
5-n指定请求数10000
6-d以字节的形式指定 SET/GET 值的数据大小2
7-k1=keep alive 0=reconnect1
8-rSET/GET/INCR 使用随机 key, SADD 使用随机值
9-P通过管道传输请求1
10-q强制退出 redis。仅显示 query/sec 值
11–csv以 CSV 格式输出
12*-l*(L 的小写字母)生成循环,永久执行测试
13-t仅运行以逗号分隔的测试命令列表。
14*-I*(i 的大写字母)Idle 模式。仅打开 N 个 idle 连接并等待。

redis-性能测试

二、redis数据库操作

redis官网命令详解

DBSIZE  #查看数据库中key的数量
#redis中默认有16个数据库(可以在配置文件中修改) 默认使用的是0号数据库,且数据库的名字默认累加,不能自己定义,如果数据库需要设置密码的话,设置了一个库的密码,所有的都需要密码验证。
flushdb  #清空当前库
flushall #清空所有库

# exists key 的名字,判断某个key是否存在
exists key xiao

#移除当前库中的某个key
move aa 1

# expire key 秒钟:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。 普通未设置时间默认为-1(永不过期)
# ttl key 查看还有多少秒过期,-1 表示永不过期,-2 表示已过期

# type key 查看你的key是什么类型

三、redis中的数据类型

redis作为一款nosql的数据库,核心是单线程操作。可以用作数据库,缓存,消息代理。它支持数据结构,例如字符串,哈希,列表,集合带范围查询的排序集合,位图,超日志,带有半径查询和流的地理空间索引。redis具有内置的复制,lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过redis sentinel 和redis cluster自动分区提供了高可用性。

1、String字符串类型

单值单value

string类型是二进制安全的,意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象。string类型是redis最基本的数据类型,一个redis中字符串value最多可以使512M。

# set 设置值,设置的时候可以使用expire key 秒钟  指定失效时间
# get 获取值
# del  删除值
# append 添加值
127.0.0.1:6379> append key1 "hello" # 对不存在的 key 进行 APPEND ,等同于 SET key1 "hello"
127.0.0.1:6379> APPEND key1 "-2333" # 对已存在的字符串进行 APPEND (integer) 10 长度从 5 个字符增加到 10 个字符

# strlen  获取字符串的长度

# incr、decr 一定要是数字才能进行加减,+1 和 -1。
# incrby、decrby 命令将 key 中储存的数字加上指定的增量值。
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> incr views
(integer) 3
127.0.0.1:6379> decr views
(integer) 2
127.0.0.1:6379> decr views
(integer) 1

127.0.0.1:6379> incrby views 10
(integer) 14
127.0.0.1:6379> decrby views 5
(integer) 9
127.0.0.1:6379> 

# range [范围]
# getrange 获取指定区间范围内的值,类似between...and的关系,从零到负一表示全部(0 -1) 其他范围表示 下标从0开始计算
# setrange 设置指定区间范围内的值,格式是setrange key值 具体值 (从当前下标开始替换后面的值)
127.0.0.1:6379> set key2 abcdef123456
OK
127.0.0.1:6379> strlen key2
(integer) 12
127.0.0.1:6379> getrange key2 0 -1
"abcdef123456"
127.0.0.1:6379> getrange key2 0 4
"abcde"
127.0.0.1:6379> 

127.0.0.1:6379> get key2
"abcdef123456"
127.0.0.1:6379> setrange key2 3 123456789
(integer) 12
127.0.0.1:6379> get key2
"abc123456789"
127.0.0.1:6379> 

127.0.0.1:6379> get key2
"abc123456789"
127.0.0.1:6379> setrange key2 4 xx
(integer) 12
127.0.0.1:6379> get jey2
(nil)
127.0.0.1:6379> get key2
"abc1xx456789"
127.0.0.1:6379> 

# setex(set with expire)键秒值 设置值和过期时间一起
# setnx(set if not exist) 设置值,如果不存在就设置对应的键值,如果存在就不设置值,返回0。

# mset Mset 命令用于同时设置一个或多个 key-value 对。
# mget Mget 命令返回所有(一个或多个)给定 key 的值。 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。
# msetnx 当所有 key 都成功设置,返回 1 。 如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。原子操作

127.0.0.1:6379> mset key3 xiaoxiao  key4 xiaoxiao1
OK
127.0.0.1:6379> keys *
1) "name2"
2) "key2"
3) "views"
4) "name"
5) "key3"
6) "key1"
7) "key4"

127.0.0.1:6379> keys *
1) "name2"
2) "key2"
3) "views"
4) "name"
5) "key1"
127.0.0.1:6379> mget name2 key1 key2 key3
1) "linux"
2) "helloworld"
3) "redis"
4) (nil)

127.0.0.1:6379> keys *
1) "name2"
2) "key2"
3) "views"
4) "name"
5) "key3"
6) "key1"
7) "key4"
127.0.0.1:6379> msetnx key1 xiaoxiao key5 xiaoxioa3
(integer) 0
127.0.0.1:6379> keys *
1) "name2"
2) "key2"
3) "views"
4) "name"
5) "key3"
6) "key1"
7) "key4"
127.0.0.1:6379> get key5
(nil)

# 传统对象缓存
set user:1 value(json数据)
# 可以用来缓存对象
mset user:1:name zhangsan user:1:age 2
mget user:1:name user:1:age
# ===================================================
# getset(先get再set)
# ===================================================
127.0.0.1:6379> getset db mongodb # 没有旧值,返回 nil
(nil)
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379> getset db redis # 返回旧值 mongodb
"mongodb"
127.0.0.1:6379> get db
"redis"

2、列表List

单值多value

# Lpush:将一个或多个值插入到列表头部。(左)
# rpush:将一个或多个值插入到列表尾部。(右)
# lrange:返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

127.0.0.1:6379> lpush list aa bb cc dd
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "dd"
2) "cc"
3) "bb"
4) "aa"
127.0.0.1:6379> lrange list 0 1
1) "dd"
2) "cc"

# lpop 命令用于移除并返回列表的第一个元素。当列表 key 不存在时,返回 nil 。
# rpop 移除列表的最后一个元素,返回值为移除的元素。
127.0.0.1:6379> lpop list
"dd"
127.0.0.1:6379> rpop list
"aa"
127.0.0.1:6379> lrange list 0 -1
1) "cc"
2) "bb"

# lindex 获取元素对应下标的值
127.0.0.1:6379> lrange list 0 -1
1) "cc"
2) "bb"
127.0.0.1:6379> lindex list 0
"cc"
127.0.0.1:6379> lrange list 0 -1
1) "cc"
2) "bb"
127.0.0.1:6379> lindex list 1
"bb"

# llen 返回列表的长度
127.0.0.1:6379> llen list
(integer) 2

# lrem key 根据参数count的值,移除列表中与参数value相等的元素
127.0.0.1:6379> lrange list2 0 -1
1) "4jkl"
2) "3ghi"
3) "2def"
4) "1abc"
127.0.0.1:6379> lrem list2 0 "1abc"
(integer) 1
127.0.0.1:6379> lrem list2 1 "1abc"
(integer) 0
127.0.0.1:6379> lrem list2 1 "2def"
(integer) 1
127.0.0.1:6379> lrange list2 0 -1
1) "4jkl"
2) "3ghi"

# Ltrim key 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
127.0.0.1:6379> rpush list3 value1 value2 value3
(integer) 3
127.0.0.1:6379> lrange list3 0 -1
1) "value1"
2) "value2"
3) "value3"
127.0.0.1:6379> ltrim list3 1 2
OK
127.0.0.1:6379> lrange list3 0 -1
1) "value2"
2) "value3"

# rpoplpush 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。
127.0.0.1:6379> lrange list2 0 -1
1) "4jkl"
2) "3ghi"
127.0.0.1:6379> lrange list3 0 -1
1) "value2"
2) "value3"
127.0.0.1:6379> rpoplpush list2 list3
"3ghi"
127.0.0.1:6379> lrange list2 0 -1
1) "4jkl"
127.0.0.1:6379> lrange list3 0 -1
1) "3ghi"
2) "value2"
3) "value3"

# lset key index value 将列表 key 下标为 index 的元素的值设置为 value 。
127.0.0.1:6379> exists list # 对空列表(key 不存在)进行 LSET
(integer) 0
127.0.0.1:6379> lset list 0 item # 报错
(error) ERR no such key
127.0.0.1:6379> lpush list "value1" # 对非空列表进行 LSET
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 "new" # 更新值
OK
127.0.0.1:6379> lrange list 0 0
1) "new"
127.0.0.1:6379> lset list 1 "new" # index 超出范围报错
(error) ERR index out of range

#linsert key before/after pivot value 用于在列表的元素前或者后插入元素。将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。
127.0.0.1:6379> lrange list3 0 -1
1) "value0"
2) "value2"
3) "value3"
127.0.0.1:6379> linsert list3 before value2 value1
(integer) 4
127.0.0.1:6379> lrange list3 0 -1
1) "value0"
2) "value1"
3) "value2"
4) "value3"


性能总结:

list实际上是一个链表,Node before after 在它的左边或者右边都可以插入。

在两边插入,或者改动值效率最高。中间元素相对而言效率会低一点。

消息排队,消息队列:Lpush Rpop 栈: Lpush Lpop

list就是链表,略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消 息排行等功能。List的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在List中,然后工 作线程再用POP操作将任务取出进行执行。Redis还提供了操作List中某一段的api,你可以直接查询,删 除List中某一段的元素。 Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部 添加或者删除元素,这样List即可以作为栈,也可以作为队列。

3、集合set

无序不重复集合

# ===================================================
# sadd 将一个或多个成员元素加入到集合中,不能重复
# smembers 返回集合中的所有的成员。
# sismember 命令判断成员元素是否是集合的成员。
# ===================================================
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 0
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "hello"
127.0.0.1:6379> SISMEMBER myset "hello"
(integer) 1
127.0.0.1:6379> SISMEMBER myset "world"
(integer) 0
# ===================================================
# scard,获取集合里面的元素个数
# ===================================================
127.0.0.1:6379> scard myset
(integer) 2
# ===================================================
# srem key value 用于移除集合中的一个或多个成员元素
# ===================================================
127.0.0.1:6379> srem myset "kuangshen"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
# ===================================================
# srandmember key 命令用于返回集合中的一个随机元素。
# ===================================================
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
3) "hello"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "world"
2) "kuangshen"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "kuangshen"
2) "hello"
# ===================================================
# spop key 用于移除集合中的指定 key 的一个或多个随机元素
# ===================================================
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
3) "hello"
127.0.0.1:6379> spop myset
"world"
127.0.0.1:6379> spop myset
"kuangshen"
127.0.0.1:6379> spop myset
"hello"
# ===================================================
# smove SOURCE DESTINATION MEMBER
# 将指定成员 member 元素从 source 集合移动到 destination 集合。
# ===================================================
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "kuangshen"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "kuangshen"
2) "set2"
# ===================================================
- 数字集合类
- 差集: sdiff
- 交集: sinter
- 并集: sunion
# ===================================================
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> SMEMBERS key1
1) "a"
2) "c"
3) "b"
127.0.0.1:6379> SMEMBERS key2
1) "d"
2) "c"
3) "e"
127.0.0.1:6379> sdiff key1 key2  # 差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER key1 key2 # 交集
1) "c"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "b"
2) "c"
3) "a"
4) "d"
5) "e"

在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为 集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功 能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集 合中。

4、哈希 Hash

K V 模式不变,但是V是一个键值对的形式 map集合,key-value

value是一个map集合

# ===================================================
# hset、hget 命令用于为哈希表中的字段赋值 。
# hmset、hmget 同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段。
# hgetall 用于返回哈希表中,所有的字段和值。
# hdel 用于删除哈希表 key 中的一个或多个指定字段
# ===================================================
127.0.0.1:6379> hset myhash field1 "kuangshen"
(integer) 1
127.0.0.1:6379> hget myhash field1
"kuangshen"
127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
OK
127.0.0.1:6379> HGET myhash field1
"Hello"
127.0.0.1:6379> HGET myhash field2
"World"
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"
127.0.0.1:6379> HDEL myhash field1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "World"
# ===================================================
# hlen 获取哈希表中字段的数量。
# ===================================================
127.0.0.1:6379> hlen myhash
(integer) 1
127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
OK
127.0.0.1:6379> hlen myhash
(integer) 2
# ===================================================
# hexists 查看哈希表的指定字段是否存在。
# ===================================================
127.0.0.1:6379> hexists myhash field1
(integer) 1
127.0.0.1:6379> hexists myhash field3
(integer) 0
# ===================================================
# hkeys 获取哈希表中的所有域(field)。
# hvals 返回哈希表所有域(field)的值。
# ===================================================
127.0.0.1:6379> HKEYS myhash
1) "field2"
2) "field1"
127.0.0.1:6379> HVALS myhash
1) "World"
2) "Hello"
# ===================================================
# hincrby 为哈希表中的字段值加上指定增量值。
# ===================================================
127.0.0.1:6379> hset myhash field 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field 1
(integer) 6
127.0.0.1:6379> HINCRBY myhash field -1
(integer) 5
127.0.0.1:6379> HINCRBY myhash field -10
(integer) -5
# ===================================================
# hsetnx 为哈希表中不存在的的字段赋值 。
# ===================================================
127.0.0.1:6379> HSETNX myhash field1 "hello"
(integer) 1 # 设置成功,返回 1 。
127.0.0.1:6379> HSETNX myhash field1 "world"
(integer) 0 # 如果给定字段已经存在,返回 0 。
127.0.0.1:6379> HGET myhash field1
"hello"

hash的应用:

存一些变更的数据,user 尤其是一些用户信息的保存,一些经常变动的信息。

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 存储部分变更的数据,如用户信息等。

5、有序集合zset

在set的基础上面增加了一个值;

set k1 v1

zset k1 score v1

127.0.0.1:6379> zadd myset 1 one  #向zset中添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three #添加多个值
(integer) 1
127.0.0.1:6379> zrange myset 0 -1 # 获取里面的值
1) "one"
2) "two"
3) "three"

##排序

127.0.0.1:6379> zadd saraly 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd saraly 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd saraly 500 xiaowang
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf  #按照从小到大排序  inf表示无穷
(empty array)
127.0.0.1:6379> ZRANGEBYSCORE saraly -inf +inf
1) "xiaowang"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE saraly 0 -1
(empty array)
127.0.0.1:6379> ZRANGEBYSCORE saraly -inf +inf withscores #按照从小到大排序,然后将对应的分数也输出出来
1) "xiaowang"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
# 移除rem 中的元素

127.0.0.1:6379> zrange saraly 0 -1
1) "xiaowang"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem saraly zhangsan
(integer) 1
127.0.0.1:6379> zrange saraly 0 -1
1) "xiaowang"
2) "xiaohong"

#查看个数
127.0.0.1:6379> zcard saraly
(integer) 2

# ===================================================
# zrank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。
# ===================================================
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES # 显示所有成员及其 score 值
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> zrank salary kuangshen # 显示 kuangshen 的薪水排名,最少
(integer) 0
127.0.0.1:6379> zrank salary xiaohong # 显示 xiaohong 的薪水排名,第三
(integer) 2
# ===================================================
# zrevrank 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。
# ===================================================
127.0.0.1:6379> ZREVRANK salary kuangshen # 狂神第三
(integer) 2
127.0.0.1:6379> ZREVRANK salary xiaohong # 小红第一
(integer) 0

和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如 一个存储全班同学成绩的sorted set,其集合value可以是同学的学号,而score就可以是其考试得分, 这样在数据插入集合的时候,就已经进行了天然的排序。可以用sorted set来做带权重的队列,比如普 通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让 重要的任务优先执行。 排行榜应用,取TOP N操作 !

四、三种特殊数据类型

1、geospatial 地理位置

Redis 的 GEO 特性在 Redis 3.2 版本中推出, 这个功能可以将用户给定的地理位置信息储存起来, 并对 这些信息进行操作。来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。geo的数据类型为 zset。 GEO 的数据结构总共有六个常用命令:geoadd、geopos、geodist、georadius、 georadiusbymember、gethash

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> geoadd china:city 116.23 40.22 北京
(integer) 1
127.0.0.1:6379> geoadd china:city 121.48 31.40 上海 113.88 22.55 深圳 120.21
30.20 杭州
(integer) 3
127.0.0.1:6379> geoadd china:city 106.54 29.40 重庆 108.93 34.23 西安 114.02
30.58 武汉
(integer) 3

geopos

# 语法
geopos key member [member...]
#从key里返回所有给定位置元素的位置(经度和纬度)
127.0.0.1:6379> geopos china:city 北京
1) 1) "116.23000055551528931"
2) "40.2200010338739844"
127.0.0.1:6379> geopos china:city 上海 重庆
1) 1) "121.48000091314315796"
2) "31.40000025319353938"
2) 1) "106.54000014066696167"
2) "29.39999880018641676"
127.0.0.1:6379> geopos china:city 新疆
1) (nil)

geodist

# 语法
geodist key member1 member2 [unit]
# 返回两个给定位置之间的距离,如果两个位置之间的其中一个不存在,那么命令返回空值。
# 指定单位的参数unit必须是以下单位的其中一个:
# m表示单位为米
# km表示单位为千米
# mi表示单位为英里
# ft表示单位为英尺
# 如果用户没有显式地指定单位参数,那么geodist默认使用米作为单位。
#geodist命令在计算距离时会假设地球为完美的球形,在极限情况下,这一假设最大会造成0.5%的误
差。
127.0.0.1:6379> geodist china:city 北京 上海
"1088785.4302"
127.0.0.1:6379> geodist china:city 北京 上海 km
"1088.7854"
127.0.0.1:6379> geodist china:city 重庆 北京 km
"1491.6716"

georadius

# 语法
georadius key longitude latitude radius m|km|ft|mi [withcoord][withdist]
[withhash][asc|desc][count count]
# 以给定的经纬度为中心, 找出某一半径内的元素

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

[root@kuangshen bin]# redis-cli --raw -p 6379
# 在 china:city 中寻找坐标 100 30 半径为 1000km 的城市
127.0.0.1:6379> georadius china:city 100 30 1000 km
重庆
西安
# withdist 返回位置名称和中心距离
127.0.0.1:6379> georadius china:city 100 30 1000 km withdist
重庆
635.2850
西安
963.3171
# withcoord 返回位置名称和经纬度
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord
重庆
106.54000014066696167
29.39999880018641676
西安
108.92999857664108276
34.23000121926852302
# withdist withcoord 返回位置名称 距离 和经纬度 count 限定寻找个数
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count
1
重庆
635.2850
106.54000014066696167
29.39999880018641676
127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count
2
重庆
635.2850
106.54000014066696167
29.39999880018641676
西安
963.3171
108.92999857664108276
34.23000121926852302

georadiusbymember

# 语法
georadiusbymember key member radius m|km|ft|mi [withcoord][withdist]
[withhash][asc|desc][count count]
# 找出位于指定范围内的元素,中心点是由给定的位置元素决定
127.0.0.1:6379> GEORADIUSBYMEMBER china:city 北京 1000 km
北京
西安
127.0.0.1:6379> GEORADIUSBYMEMBER china:city 上海 400 km
杭州
上海

geohash

# 语法
geohash key member [member...]
# Redis使用geohash将二维经纬度转换为一维字符串,字符串越长表示位置更精确,两个字符串越相似
表示距离越近。
127.0.0.1:6379> geohash china:city 北京 重庆
wx4sucu47r0
wm5z22h53v0
127.0.0.1:6379> geohash china:city 北京 上海
wx4sucu47r0
wtw6sk5n300

rem

GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位 置信息的删除.

127.0.0.1:6379> geoadd china:city 116.23 40.22 beijin
1
127.0.0.1:6379> zrange china:city 0 -1 # 查看全部的元素
重庆
西安
深圳
武汉
杭州
上海
beijin
北京
127.0.0.1:6379> zrem china:city beijin # 移除元素
1
127.0.0.1:6379> zrem china:city 北京 # 移除元素
1
127.0.0.1:6379> zrange china:city 0 -1
重庆
西安
深圳
武汉
杭州
上海

2、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> PFADD mykey a b c d e f g h i j
1
127.0.0.1:6379> PFCOUNT mykey
10
127.0.0.1:6379> PFADD mykey2 i j z x c v b n m
1
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
OK
127.0.0.1:6379> PFCOUNT mykey3
15

3、Bitmap 位图

在开发中,可能会遇到这种情况:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如 需要记录用户一年的打卡情况,打卡了是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 offset value : 设置 key 的第 offset 位为value (1或0)

# 使用 bitmap 来记录上述事例中一周的打卡记录如下所示:
# 周一:1,周二:0,周三:0,周四:1,周五:1,周六:0,周天:0 (1 为打卡,0 为不打卡)
127.0.0.1:6379> setbit sign 0 1
0
127.0.0.1:6379> setbit sign 1 0
0
127.0.0.1:6379> setbit sign 2 0
0
127.0.0.1:6379> setbit sign 3 1
0
127.0.0.1:6379> setbit sign 4 1
0
127.0.0.1:6379> setbit sign 5 0
0
127.0.0.1:6379> setbit sign 6 0
0

getbit 获取操作

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

127.0.0.1:6379> getbit sign 3 # 查看周四是否打卡
1
127.0.0.1:6379> getbit sign 6 # 查看周七是否打卡
0

bitcount 统计操作

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

# 统计这周打卡的记录,可以看到只有3天是打卡的状态:
127.0.0.1:6379> bitcount sign
3

五、redis.conf 配置文件

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

daemonize no

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

pidfile /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

logfile 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

dbfilename dump.rdb

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

dir ./

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

slaveof

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

masterauth

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

requirepass foobared

config get requirepass  #获取密码 默认为空
config set requirepass "123456"  这是密码为123456

# 登录redis
auth 123456

#后面的操作和其他的一样了就

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 appendfilename 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-file /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

六、springboot结合redis使用

查看Springboot中关于Redis的源码配置

@Configuration  //这是一个配置类
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)  //自动导入这里面的配置
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) //导入这两个组件 这里配置了两个,但是在springboot2.0之后就采用了lettuce的方式了,这样可以减少线程的开销,底层使用了netty的方式,类似于BIO的方式
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate") //这里增加了这个注解,如果我们定义自己打的redisTemplete,那么就会让这个redisTemplete失效。
	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中默认配置的redisTemplete使用的是JDK的序列化,这样的方式会让中文字符串转义。

序列化的配置

写自己的redisTemplete

@Configuration
public class RedisConfig {
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        //修改自己经常使用的 String Object
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        //序列化配置 json序列化 Object转义
        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);
        //String序列化
        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;
    }
}

写自己的redisUtils

可以网络上,很多封装好的,直接拿来使用即可。

选择redis版本

这里的版本问题:

如果你的spring boot的版本号是1.4.0到1.5.0 之间,添加redis的jar包的时候添加成 spring-boot-starter-data-redis和spring-boot-starter-redis是都可以的。。。

但是如果你的spring boot的版本号是1.4.0以前也就是1.3.8 版本以前,添加redis的jar包 就必须是spring-boot-starter-redis 的jar包。。

七、其他

1、redis事务

Redis事务的概念:

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列 化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事 务执行命令序列中。

总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

Redis事务没有隔离级别的概念:

批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行!

Redis不保证原子性:

Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其 余的命令仍会被执行。 Redis事务的三个阶段

redis开启事务 (Multi)

redis入队 redis的命令

redis执行事务 (exec)

Redis事务相关命令:

watch key1 key2 ... #监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则
事务被打断 ( 类似乐观锁 )
multi # 标记一个事务块的开始( queued )
exec # 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
discard # 取消事务,放弃事务块中的所有命令
unwatch # 取消watch对所有key的监控

悲观锁:

悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在 拿数据的时候都会上锁,这样别人想拿到这个数据就会block直到它拿到锁。传统的关系型数据库里面就 用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。

乐观锁

悲观锁: 悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在 拿数据的时候都会上锁,这样别人想拿到这个数据就会block直到它拿到锁。传统的关系型数据库里面就 用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。

使用watch检测balance,事务期间balance数据变动,事务执行失败!

# 窗口一
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI # 执行完毕后,执行窗口二代码测试
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec # 修改失败!
(nil)
# 窗口二
127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> set balance 200
OK
# 窗口一:出现问题后放弃监视,然后重来!
127.0.0.1:6379> UNWATCH # 放弃监视
OK
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec # 成功!
1) (integer) 180
2) (integer) 40

总结

watch指令类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端 更改,则使用EXEC执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事 务执行失败。

2、redis的持久化

RDB (Redis database)

在指定的时间间隔内,将内存中的数据集快照写入磁盘内。

初始化默认的是,1分钟改动了1万次,或是5分钟内改动了10次,或是15分钟内改动了1次。

如果想禁用RDB的存储策略,可以不设置save指令,也可以在save指令传入一个空字符串。

如果要求命令立刻生效,可以只用save命令直接让后台保存。生成dump.rdb文件。

触发保存生成rdb文件的情况

1、save规则满足的情况下,会自动触发rdb规则

执行save命令
save:时只管保存,不管其他,这时候会阻塞。
bgsave:redis会在后台异步进行快照操作。

2、在执行flushall命令,也会触发RDB规则

3、退出redis,也会产生rdb文件

备份就会自动生成一个 dump.rdb文件

恢复rdb文件

将rdb文件放到redis启动目录 ,然后redis启动的时候机会自动加载dump.rdb文件。

优点

1、适合大规模的数据恢复

2、对数据的完整性要求不高

缺点

1、需要一定的时间间隔进程操作,如果redis意外宕机了,最后一次修改的数据就会丢失;

2、fork进程的时候,会占用一定的内存空间。

AOF (append only file)

将我们所有的命令都记录下来,恢复的时候就把这个文件全部都执行一遍。

以日志形式,来记录每一个写操作

# 在redis配置文件中,这个默认是关闭的,需要修改为yes开启 日志记录操作

appendonly no

修改了配置文件之后,重启redis会重新生成一个appendonly.aof文件; 里面就是记录了所有写操作的日志;

如果这个文件被破坏了,里面有错误,这时候redis是启动不起来的。可以使用redis-check-aof来进行修复;

文件正常之后,重启之后就会自动恢复数据;

优点

能最大程度的保证数据的完整性。

在日志的配置级别中可以选择是每次都同步aof日志,还是一秒同步一次。

缺点

aof文件大小远远大于rdb文件。aof文件默认无限追加,文件会越来越大。

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文件,载入较新的那个,微博就是这种架构。

3、redis订阅发布

基本的命令

命令描述
Redis Unsubscribe 命令指退订给定的频道。
Redis Subscribe 命令订阅给定的一个或多个频道的信息。
Redis Pubsub 命令查看订阅与发布系统状态。
Redis Punsubscribe 命令退订所有给定模式的频道。
Redis Publish 命令将信息发送到指定的频道。
Redis Psubscribe 命令订阅一个或多个符合给定模式的频道。

测试

订阅端

redis 127.0.0.1:6379> SUBSCRIBE redisChat
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1

发送端

redis 127.0.0.1:6379> PUBLISH redisChat "Hello,Redis"
(integer) 1
redis 127.0.0.1:6379> PUBLISH redisChat "Hello,Kuangshen"
(integer) 1
# 订阅者的客户端会显示如下消息
1) "message"
2) "redisChat"
3) "Hello,Redis"
1) "message"
2) "redisChat"
3) "Hello,Kuangshen"

原理

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构建的实时聊天系统的例子。

4、redis主从复制

配置三份配置文件,然后启动三份redis-server

redis默认自己是主节点。所以搭建集群的时候,主要是修改从节点的配置。用来完成主从复制。

# 查看当前节点的信息
info replication

1、搭建一主二从节点

修改配置文件内容

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 , 依次类推

2、分表连接三个端口的客户端

redis-cli -p 6379

redis-cli -p 6380

redis-cli -p 6381

3、配置从机(默认都是主机,另外两台去认老大)

执行命令进行暂时配置

# 6380机器上面  认6379未老大(主节点)
SLAVEOF 127.0.0.1 6379


# 6381机器上面  认6379未老大(主节点)
SLAVEOF 127.0.0.1 6379

配置了之后可以查看6379(主)上面的节点信息

redis主机查看节点信息

修改配置文件进行主从复制

配置之后:

主机负责写,从机负责读

主机断开连接了,但是此时从机依旧是连接到主机的,此时就不能进行写操作了。

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

如果是使用命令行配置的从机,重启之后就会变回主机!此时如果再配置回原从机的话,那么还可以再拿到原来主机写的数据。

赋值原理

Slave 启动成功连接到 master 后会发送一个sync命令 Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行 完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。

全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。

增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

5、redis哨兵模式

支持主从复制

主从可以切换,故障转移

哨兵模式,主从手动到自动,更加健壮

扩容麻烦,在线扩容复杂

实现哨兵模式复杂,里面的选择很多

哨兵的作用

这里的哨兵有两个作用 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服 务器,修改配置文件,让它们切换主机。 然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。 各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

redis-哨兵

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

哨兵配置说明

# 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无法正常启动成功。
#通知脚本
# 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

6、redis缓存穿透和雪崩

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

缓冲穿透

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

缓存击穿

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

缓存雪崩

缓存雪崩,是指在某一个时间段,缓存集中过期失效。 产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商 品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都 过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波 峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然 形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就 是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知 的,很有可能瞬间就把数据库压垮。

解决

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

7、服务端redis自启动脚本

可以将启动执行的脚本放到对应的,linux启动之后就会自动加载的文件中。ubuntu中是在 /etc/rc.local文件中

# redis自启动脚本
[root@ser02 redis]# vim redserver.sh

#!/bin/bash
stop(){
/data/redis/bin/redis-cli -a redis shutdown
}
start(){
/data/redis/bin/redis-server /data/redis/conf/redis.conf &
}

case $1 in
   start)
   start
   ;;
   stop)
   stop
   ;;
   restart)
   stop
   start
   ;;
   *)
   echo "Usage:$0 (start|stop|restart)"
esac

[root@ser02 redis]# chmod +x redserver.sh 
[root@ser02 redis]# vim /etc/profile
export PATH=/data/redis/:$PATH
[root@ser02 redis]# source /etc/profile

八、redis面试

Redis(或其他缓存系统)

redis工作模型、redis持久化、redis过期淘汰机制、redis分布式集群的常见形式、分布式锁、缓存击穿、缓存雪崩、缓存一致性问题

常见问题

redis性能为什么高?

纯内存操作 单线程 高效的数据结构 合理的数据编码 其他方面的优化

单线程的redis如何利用多核cpu机器?

考虑性能问题的前提是得了解性能的瓶颈在哪。redis 目标就是高性能内存kv存储。 异步io,纯内存操作(持久化逻辑fork), 全非阻塞(非耗时)操作,并且不包含复杂计算。 靠开多线程反而会把开销浪费在加锁,miss cache,很多数据结构也不能实现无锁,而且异步服务的状态和设计比较下更复杂。 相比较多进程,单线程的模式在性能和代码设计带来的优势更明显 局限一般在于网络或者内存,所以要利用的话可以国开几个redis的core 利用端口号进行区分

redis的缓存淘汰策略?

voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 no-enviction(驱逐):禁止驱逐数据
详细文章

redis如何持久化数据?

RDB:在指定的时间间隔能对你的数据进行快照存储。 AOF:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。
详细文章

redis有哪几种数据结构?

String:缓存、计数器、分布式锁等。 List:链表、队列、微博关注人时间轴列表等。 Hash:用户信息、Hash 表等。 Set:去重、赞、踩、共同好友等。 Zset:访问量排行榜、点击量排行榜等。

redis集群有哪几种形式?

redis有三种集群方式:主从复制,哨兵模式和集群。
详细文章推荐

  • 有海量key和value都比较小的数据,在redis中如何存储才更省内存?

  • 如何保证redis和DB中的数据一致性?

1.第一种方案:采用延时双删策略

2、第二种方案:异步更新缓存(基于订阅binlog的同步机制)
详细文章

如何解决缓存穿透和缓存雪崩?

缓存穿透:一般来说,缓存系统会通过key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。 这个时候如果一些恶意的请求到来,就会故意查询不存在的key,当某一时刻的请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。 如何避免缓存穿透:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。对一定不存在的key进行过滤。 可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。 缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统(DB)带来很大压力。导致系统崩溃。 如何避免缓存雪崩:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。 https://zhuanlan.zhihu.com/p/75588064

  • 如何用redis实现分布式锁?

1、使用 set key value [EX seconds][PX milliseconds][NX|XX] 命令 (正确做法)

2、Redlock算法 与 Redisson 实现 详细文章

参考:
1、狂神说 秦疆老师的课程 ,欢迎大家学习,老师讲的很棒,种草推荐
哔哩哔哩-狂神说redis视频
2、尚硅谷 周阳老师的redis课程 阳哥的视频,不多说,yyds
哔哩哔哩-周阳老师redis

推荐:
钱文品老师的 《redis深度历险 — 核心原理和应用实践》
老师讲的真心好,很详细,有能力建议大家京东购入,细细品读。
京东-链接

redis深度历险-电子版:(阿里云盘)仅供学习使用
「redis深度历险.pdf」https://www.aliyundrive.com/s/yUckU6c6tff
点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。

本文详细typora文件,有需要可以下载看
「redis-typora导出.pdf」https://www.aliyundrive.com/s/gKspq6gELhT
点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。

所有内容仅供学习使用,如果文章有什么欠妥或者问题的,欢迎大家指出,抱拳…

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值