Redis数据库

Redis数据库

非关系型数据库

Nosql概述

为什么要用Nosql

1、单机Mysql

1、数据量如果太大,一个机器放不下

2、数据的索引 300万就一定要建立索引(B+Tree)

3、访问量(读写混合),一个服务器承受不了

只要开始以上三种情况之一,那么就需要更新

2、Memcached(缓存)+Mysql+垂直拆分(读写分离)

网站80%的情况下都是在读数据,每次都要去查询数据的话就十分的麻烦,所以说我们希望减轻数据的压力,可以使用缓存来保证效率

发展过程:优化数据结构和索引–》文件缓存(IO)–》Memcached

3、分库分白哦+水平拆分+mysql集群

技术和业务在发展的同时,对人的要求也越来越高

本质:数据库(读、写)

早些年:MylSAM:表锁,十分影响效率,高并发下就会出现严重的锁问题

转战lnnodb:行锁

慢慢的开始使用分库分表来解决写的压力

为什么要用Nosql

用户的个人信息,社交网络,地里位置。用户自己产生的数据,用户日志等等爆发式增长

这时候就需要使用Nosql数据库,可以很好的解决问题

什么是Nosql

Nosql = Not Only Sql(不仅仅是SQL)

关系型数据库:表格,行,列(POI)

泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代,尤其是超大规模的高并发的社区。暴露出来很多难以克服的问题,Nosql在当今大数据环境下发展的十分迅速,Redis发展最快的,而且必须掌握的一个技术

很多的数据类型用户的个人信息,社交网络,地里位置。这些数据类型的存储不需要一个固定的格式,不需要过多的操作就可以横向扩展 Map<String,Object>使用键值对来控制

Nosql的特点

解耦

1、方便扩展(数据库之间没有关系,很好扩展)

2、大数据量高新能(Redis一秒写8万次,读取11万,Nosql的缓存记录级,是一种细粒度的缓存,性能会比较高)

3、数据类型是多样型的(不需要事先设计数据库,随取随用,如果是数据量十分大的表,很多人无法设计)

4、传统RDBMS和Nosql

传统的RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
 -操作,数据定义语言
 - 严格的一直性
 - 基础的事务
Nosql
- 不仅仅是数据
- 没有固定的查询语句
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE (异地多活)
- 高新能,高可用,高可扩展

了解 3V+3高

大数据时代的3V:主要是描述问题的

1、海量Volume

2、多样Variety

3、实时Velocity

3高:主要是对程序的要求

1、高并发

2、高可扩

3、高新能

真正在公司实践:Nosql+RDBMS(关系型数据库) 一起使用

Nosql的四大分类

KV键值对:
  • 新浪:Redis
  • 美团:Redis+Tair
  • 阿里、百度:Redis+memecache
文档型数据库(bson合适和json一样)
  • MongoDB【一般必须掌握】
    • MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
    • MongoDB是一个介于关系型数据库和非关系型数据中中间的产品!MongoDB是非关系型数据库中功能最丰富的,最关系型数据库
列存储数据库
  • HBase
  • 分布式文件系统
图关系数据库
  • 他不是存图形,放的是关系,比如:朋友圈社交网络,广告推荐
  • Neo4j,InfoGrid

Redis入门

Redis是什么

Redis,即远程字典服务

是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、key-value数据库,并提供多种语言API

能干什么?

1、内存存储、持久化、内存中是断电即失、所以说持久化很重要

2、效率高,可以用于高速缓存

3、发布订阅系统

4、地图信息分析

5、计时器、计数器(浏览量)

特性

1、多样的数据类型

2、持久化

3、集群

4、事务

学习中需要用到的东西

官网:Redis

中文网:CRUG网站 (redis.cn)

Windows安装

1、下载地址:发布 ·microsoftarchive/redis (github.com)

2、解压打开redis-server.exe文件 打开服务端

Port:6379 是默认端口号

3、使用Redis客户端来连接Redis redis-cli.exe是客户端

​ 使用ping 出现PONG 说明是连接成功

4、设置set基本值 key value

set name p 设置一个名字为p的

5、get key 获取值

get name

Windows下使用简单,但推荐使用Linux去开发使用

Linux安装

Linux系统(九):安装Redis(2021最新最详细) - 净重21克 - 博客园 (cnblogs.com)

1、下载放到服务器上

2、进入解压后的文件,可以看到redis的配置文件

3、下载c++环境 yum install gcc-c++

4、gcc -v 查看c++版本

5、make 把所有需要的文件全都配置上

6、make install[可以不去执行]

7、redis的默认安装路径 /usr/local/bin 在这个目录下可以看到redis的配置

8、创建一个自己的配置启动类 在本目录下创建 make myconfig

9、将redis配置文件复制到该目录下 cp ~/redis/redis-6.0.6/redis.conf

10、默认不是后台启动的,修改配置文件中的 daemonize no 改为 daemonize yes 表示后台启动 【以后启动这个】

11、进入/usr/local/bin中启动Redis服务 redis-server myconfig/redis.conf 通过指定的配置文件启动服务

12、redis-cli -p 6379 使用客户端进行连接 ping 进行测试

13、**退出 shutdown 关闭 无连接后`exit

测试性能

redis-benchmark 是一个压力测试工具

官方自带的性能测试工具

redis-benchmark 命令参数

参数选项说明
-h指定服务器主机名。
-p指定服务器端口。
-s指定服务器 socket。
-c指定并发连接数。
-n指定请求的具体数量。
-d以字节的形式指定 SET/GET 值的数据大小。
-k1 表示 keep alive;0 表示 reconnect,默认为 1。
-rSET/GET/INCR 使用随机 key, SADD 使用随机值。
-PPipeline 请求
-q强制退出 Redis,仅显示 query/sec 值。
–csv以 CSV 格式输出。
-l生成循环,永久执行测试。
-t仅运行以逗号分隔的测试命令列表。
-I(大写i)空闲模式,打开 N 个空闲连接并等待连接

简单的测试

测试100个并发  100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

测试结果之一
====== SET ======
  100000 requests completed in 1.49 seconds   对我们的10万个请求进行写入测试
  100 parallel clients   100个并发客户端
  3 bytes payload        每次写入3个字节
  keep alive: 1			只有一台服务器来处理这些请求,单机性能
  host configuration "save": 900 1 300 10 60 10000 
  host configuration "appendonly": no
  multi-thread: no

45.26% <= 1 milliseconds
99.77% <= 2 milliseconds
100.00% <= 2 milliseconds         所有请求在3毫秒处理完成
67159.17 requests per second      每秒处理67159次请求

Redis基础知识

redis默认有16个数据库,默认使用的是第0个

databases 16 在配置文件中

可以使用select进行切换数据库

[root@PccYH bin]# redis-cli -p 6379   # 连接redis
127.0.0.1:6379> select 3  # 切换到第三个数据库
OK
127.0.0.1:6379[3]> DBSIZE   #查看DB大小
(integer) 0              # 显示为0
127.0.0.1:6379[3]> set name p   #设置一个值
OK
127.0.0.1:6379[3]> DBSIZE
(integer) 1              #显示为1 
127.0.0.1:6379[3]> get name
"p"
127.0.0.1:6379[3]> keys *  # 查看数据库所有的key
1) "name"
# 清空全部
flushall
#清空当前数据库
flushdb

Redis是单线程的

所以Redis的性能瓶颈不是CPU,而是机器内存和网络带宽。

Redis是C语言写的

Redis为什么单线程还这么快

1、误区1:高性能的服务器一定是多线程

2、误区2:多线程(CPU上下文会切换) 一定比单线程效率高

CPU>内存>硬盘的速度要有所了解

核心:redis是将所有的数据全部放在内存中,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换,耗时的操作),对内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案。

五大数据类型

Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队MQ列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。

Redis-Key的基本命令

set name p  # 设置name的值为p
set age 1  # 设置age的值为1 
exists name # 判断当前的key是否存在
keys * # 查看所有的key 值
move key db   # move name 1  移动一个key到另一个库中
expire name 10  # 设置name属性 10s后消失   设置过期时间
ttl name # 查看name属性消失剩余时间  查看当前key的剩余时间
type name  #查看key对应的value类型的  默认为String类型

String(字符串)

set key1 v1  # 设置值
exists key1  # 判断key是否存在
append key1 hello  # 在当前的key后追加字符串,如果不存在,就相当于 set key   
get key1   #  获得值   v1hello
strlen key1  # 获取当前key的长度  7

案例:文章的浏览量在redis中增加,不再存入到数据库中

# 在redis中实现 i++
set views 0 #设置一个浏览量初始为0
incr views #自增当前的key值   增加浏览量   自增1 
decr views # 减少当前的key值   减少浏览量  自减1 

incrby views 10  #设置增长量
decrby views 5  #设置减少量

########################################################################
# 字符串范围
set key1 hello  # 设置key value值
getrange key1 0 3 #获取从0开始到3 的字符  结果:hell  [0,3] 包括0到3的值
getrange key1 0 -1  # 查看所有的字符串  结果:hello

########################################################################

#替换
set key2 abcdefg
setrange key2 1 xx  #替换指定位置开始的字符串
get key2  # axxdefg   将 bc 替换成 xx
########################################################################
# setex(set with expire) # 设置过期时间
# setnx(set if not exist) 不存在再设置(分布式锁中常常使用)

setex key3 30 hello  # key3   30秒后过期   设置key3的值为 hello,30秒后过期
setnx mykey redis # 设置了一个mykey  如果mykey不存在,创建mykey
setnx mykey MonGoDB  # 如果当前key存在,创建失败

########################################################################

# 批量设置值和获取值
mset k1 v1 k2 v2 k3 v3  # 批量设置key-value
mget k1 k2 k3  # 批量获取k1 k2 k3
msetnx k1 v1 k4 v4 # msetnx是一个原子性的操作,要么一起成功,要么一起失败

########################################################################

#对象
set user:1 {name:zhangsan,age:3} # 设置一个user:1对象值为json字符来保存一个对象  
	#user:{id} {值}

#这里的key是一个巧妙的设计  user:{id}:{fild},在redis中OK
mset user:1:name zhangsan user:1:age 2  # 设置了key为user:1:name值为 zhangsan  
#相当于 user:{用户id}:name  zhangsan

mget user:1:name  user:1:age 
#结果: 1)"张三"
#	   2)"2"

########################################################################
案例:更新操作
getset # 先get再set
 
127.0.0.1:6379[3]> getset db redis  # 如果不存在值,则返回null
(nil)
127.0.0.1:6379[3]> get db
"redis"
127.0.0.1:6379[3]> getset db mongodb # 如果存在值,获取原先的值,并设置新的值
"redis"
127.0.0.1:6379[3]> get db
"mongodb"

String的使用场景:value除了字符串还可以是数字

  • 计数器
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存存储

List

基本数据类型,列表

在redis里面,可以将list完成,栈、队列、阻塞队列

所有的list命令都是 lL 开头的

Lpush是从左侧往里面放值

Rpush是从右侧往里面放值

127.0.0.1:6379[3]> LPUSH list one  # 将一个值或多个值,插入到列表的头部
(integer) 1
127.0.0.1:6379[3]> LPUSH list two
(integer) 2
127.0.0.1:6379[3]> LPUSH list three
(integer) 3
127.0.0.1:6379[3]> LRANGE list 0 -1  # 查看所有的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379[3]> LRANGE list 0 1  # 查看从0到1 的值  通过区间获取具体的值
1) "three"
2) "two"
27.0.0.1:6379[3]> RPUSH list right  # 在右侧插入数据
(integer) 4
127.0.0.1:6379[3]> LRANGE list 0 -1  # 值插尾部(右边)
1) "three"
2) "two"
3) "one"
4) "right"


########################################################################

# 移除一个元素
Lpop # 左边移除
Rpop # 右边移除

127.0.0.1:6379[3]> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379[3]> LPOP list  #移除左边的第一个元素 (移除list的第一个元素)
"three" 
127.0.0.1:6379[3]> RPOP list  #移除右边的第一个元素 (移除list最后一个元素)
"right"
127.0.0.1:6379[3]> LRANGE list 0 -1
1) "two"
2) "one"

########################################################################
lindex 

lindex list 1 # 获取第 1 个下标的值  通过下标获得list中的某一个值
lindex list 0 # 获取第 0 位下标的值

########################################################################
Llen

Llen list  # 返回列表的长度

########################################################################
# 移除指定的值
Lrem

127.0.0.1:6379[3]> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379[3]> lrem list 1 one   # 移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379[3]> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379[3]> lrem list 1 three
(integer) 1
127.0.0.1:6379[3]> LRANGE list 0 -1
1) "three"
2) "two"
127.0.0.1:6379[3]> Lpush list three
(integer) 3
127.0.0.1:6379[3]> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379[3]> lrem list 2 three
(integer) 2
127.0.0.1:6379[3]> LRANGE list 0 -1
1) "two"

########################################################################
#trim 修剪  java中是将两边的空格去掉

127.0.0.1:6379[3]> RPUSH list hello
(integer) 1
127.0.0.1:6379[3]> RPUSH list hello1
(integer) 2
127.0.0.1:6379[3]> RPUSH list hello2
(integer) 3
127.0.0.1:6379[3]> RPUSH list hello3
(integer) 4
127.0.0.1:6379[3]> LTRIM list 1 2  # 通过下标截取指定的长度,这个list已经被改变了,截取了只剩下的元素
OK
127.0.0.1:6379[3]> LRANGE list 0 -1
1) "hello1"
2) "hello2"

########################################################################
rpoplpush # 移除列表的最后一个元素,将它移动到新的列表中

3) "hello3"
127.0.0.1:6379[3]> clear
127.0.0.1:6379[3]> LRANGE list 0 -1
1) "hello1"
2) "hello2"
3) "hello3"
127.0.0.1:6379[3]> RPOPLPUSH list otherlist
"hello3"
127.0.0.1:6379[3]> LRANGE list 0 -1  # 查看原来的列表
1) "hello1"
2) "hello2"
127.0.0.1:6379[3]> LRANGE  otherlist 0 -1  # 查看新的目标列表中,确实存在
1) "hello3"

########################################################################
lset # 将列表中指定下标的值替换为另一个值   (更新操作)

127.0.0.1:6379[3]> EXISTS list  # 判断是否存在key
(integer) 0
127.0.0.1:6379[3]> lset list 0 item  # 如果key 不存在替换会报错
(error) ERR no such key
127.0.0.1:6379[3]> lpush list value1  # 从左边插入值
(integer) 1
127.0.0.1:6379[3]> lset list 0 item  # 将下标为0 的值为替换为 item
OK
127.0.0.1:6379[3]> LRANGE list 0 0  # 查看
1) "item"
127.0.0.1:6379[3]> lset list 1 other # 如果不存在下标为1 ,则报错
(error) ERR index out of range

########################################################################
linsert # 将某个具体的value插入到列表中某个元素的前面或后面

127.0.0.1:6379[3]> LRANGE list 0 -1
1) "world"
2) "item"
127.0.0.1:6379[3]> linsert list before world hello  # 在world前插入一个hello
(integer) 3
127.0.0.1:6379[3]> LRANGE list 0 -1
1) "hello"
2) "world"
3) "item"
127.0.0.1:6379[3]> linsert list after world new   # 在world后插入一个new
(integer) 4
127.0.0.1:6379[3]> LRANGE list 0 -1
1) "hello"
2) "world"
3) "new"
4) "item"

小结

  • 实际上是一个链表, before after、left、right都可以插入值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在
  • 在两边插入或改动值,效率最高,中间元素,相对来说效率会低一点

消息队列,消息队列(LpushRpop 拿) 栈(LpushLpop拿)

set(集合)

set中的值是不能重复的

127.0.0.1:6379[3]> sadd myset hello  # set集合中添加元素
(integer) 1
127.0.0.1:6379[3]> sadd myset p
(integer) 1
127.0.0.1:6379[3]> sadd myset world
(integer) 1
127.0.0.1:6379[3]> SMEMBERS myset  # 查看指定set的所有值
1) "world"
2) "hello"
3) "p"
127.0.0.1:6379[3]> SISMEMBER myset hello  # 判断某一个值是不是在set集合中
(integer) 1
127.0.0.1:6379[3]> SISMEMBER myset world
(integer) 1
127.0.0.1:6379[3]> SISMEMBER myset world1
(integer) 0

########################################################################
127.0.0.1:6379[3]> scard myset  # 获取set集合中的内容元素的个数
(integer) 3

########################################################################
srem  # 移除set中的元素

127.0.0.1:6379[3]> srem myset p  # 移除set中的元素
(integer) 1
127.0.0.1:6379[3]> scard myset   # 查看元素个数
(integer) 2
127.0.0.1:6379[3]> smembers myset # 查看元素
1) "world"
2) "hello"

########################################################################
set 无序不重复集合,抽随机

127.0.0.1:6379[3]> SRANDMEMBER myset  # 随机抽选出一个元素
"world"
127.0.0.1:6379[3]> SRANDMEMBER myset
"hello"

127.0.0.1:6379[3]> SRANDMEMBER myset 2 # 随机抽选出指定个数的元素
1) "world"
2) "hello"

########################################################################
随机删除key

127.0.0.1:6379[3]> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379[3]> spop myset  # 随机删除一些set集合中的元素
"world"
127.0.0.1:6379[3]> SMEMBERS myset
1) "hello"

########################################################################
smove  将一个指定的值,移动到另外一个set集合

127.0.0.1:6379[3]> sadd myset hello
(integer) 1
127.0.0.1:6379[3]> sadd myset world
(integer) 1
127.0.0.1:6379[3]> sadd myset redis
(integer) 1
127.0.0.1:6379[3]> sadd myset2 set2
(integer) 1
127.0.0.1:6379[3]> smove myset myset2 redis  #  将一个指定的值,移动到另外一个set集合
(integer) 1
127.0.0.1:6379[3]> SMEMBERS myset2
1) "redis"
2) "set2"

########################################################################
微博,B站 共同关注(交集)
数字集合类
- 差集
- 交集
- 并集

127.0.0.1:6379[3]> sadd key1 a
(integer) 1
127.0.0.1:6379[3]> sadd key1 b
(integer) 1
127.0.0.1:6379[3]> sadd key1 c
(integer) 1
127.0.0.1:6379[3]> sadd key2 c
(integer) 1
127.0.0.1:6379[3]> sadd key2 d
(integer) 1
127.0.0.1:6379[3]> sadd key2 e
(integer) 1

key1 :a b c   key2: c d e
# 差集
127.0.0.1:6379[3]> SDIFF key1 key2  # 查询key1 和 key2 的差集
1) "b"
2) "a"

#交集
127.0.0.1:6379[3]> SINTER key1 key2  # 查询key1 和 key2 的交集   共同好友 这样实现
1) "c"

# 并集
127.0.0.1:6379[3]> SUNION key1 key2  # 查询key1 和 key2 的并集
1) "e"
2) "a"
3) "c"
4) "b"

微博,A用户将所有关注的人放在一个set集合中,粉丝也放在一个集合中

共同关注,共同爱好,二度好友,推荐好友(六度分割理论)

Hash(哈希)

map集合,key-map集合 这时候这个值是一个map集合 本质和String类型没有太大的区别,还是一个简单的key-value

127.0.0.1:6379[3]> hset myhash field1 p  # set一个具体的key-value
(integer) 1
127.0.0.1:6379[3]> hget myhash field1   #获取一个字段值
"p"
127.0.0.1:6379[3]> hmset myhash field1 hello field2 world  # set多个具体的key-valu
OK
127.0.0.1:6379[3]> hmget myhash field1 field2  # 获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379[3]> hgetall myhash   # 获取全部的数据
1) "field1"
2) "hello"
3) "field2"
4) "world"

########################################################################
hdle  #删除指定的key字段

127.0.0.1:6379[3]> hdel myhash field1  #删除指定的key字段,对应的value也消失了
(integer) 1
127.0.0.1:6379[3]> hgetall myhash
1) "field2"
2) "world"

########################################################################
hlen

127.0.0.1:6379[3]> hlen myhash  # 获取哈希表的字段数量
(integer) 1

########################################################################
hexists # 判断某个key是否存在
127.0.0.1:6379[3]> HEXISTS myhash field2
(integer) 1
127.0.0.1:6379[3]> HEXISTS myhash field1
(integer) 0

########################################################################
hkeys myhash # 只获取所有的 key  (field)
hvals myhash # 只获取所有的 value

########################################################################
hincrby  # 自增
127.0.0.1:6379[3]> hset myhash field3 5
(integer) 1
127.0.0.1:6379[3]> HINCRBY myhash field3 1   # 自增
(integer) 6
127.0.0.1:6379[3]> HINCRBY myhash field3 -1  # 通过自增的-1 来实现减少
(integer) 5

########################################################################
hsetnx  # 自增
127.0.0.1:6379[3]> hsetnx myhash field4 hello  # 不存在,则可以设置
(integer) 1
127.0.0.1:6379[3]> hsetnx myhash field4 world  # 存在,则不可以设置  (更新)
(integer) 0

hash变更的数据 user的数据 比如:name,age,尤其是用户信息之类的,经常变动的信息

hash更适合于对象的存储,String更适合字符串存储

hset user:1 name zhangsan
hget user:1 name

Zset(有序集合)

在set的基础上,增加了一个值, set k1 v1 zset k1 score1 v1

127.0.0.1:6379[3]> zadd myset 1 one  # 添加一个值
(integer) 1
127.0.0.1:6379[3]> zadd myset 2 two 3 three  # 添加多个值
(integer) 2 
127.0.0.1:6379[3]> ZRANGE myset 0 -1  # 查看所有的值
1) "one"
2) "two"
3) "three"


########################################################################
ZRANGEBYSCORE  # 通过成绩排序

127.0.0.1:6379[3]> zadd salary 2500 xiaohong
(integer) 1
127.0.0.1:6379[3]> zadd salary 5000 zhangsna
(integer) 1
127.0.0.1:6379[3]> zadd salary 2000 lisi
(integer) 1
127.0.0.1:6379[3]> ZRANGEBYSCORE salary -inf +inf #从小到大(升序)   
								#(+inf正无穷和-inf负无穷) 
1) "lisi"
2) "xiaohong"
3) "zhangsna"

127.0.0.1:6379[3]> ZREVRANGE salary 0 -1  # 从大到小进行排序
1) "zhangsna"
2) "lisi"


127.0.0.1:6379[3]> ZRANGEBYSCORE salary -inf +inf withscores  # 从小到大排序,并附带成绩value
1) "lisi"
2) "2000"
3) "xiaohong"
4) "2500"
5) "zhangsna"
6) "5000"

127.0.0.1:6379[3]> ZRANGEBYSCORE salary -inf 2500 withscores # 升序,只查到2500的并附带成绩
1) "lisi"
2) "2000"
3) "xiaohong"
4) "2500"

########################################################################
zrem

127.0.0.1:6379[3]> ZRANGE salary 0 -1
1) "lisi"
2) "xiaohong"
3) "zhangsna"
127.0.0.1:6379[3]> ZREM salary xiaohong  # 移除有序集合中指定的元素
(integer) 1
127.0.0.1:6379[3]> ZRANGE salary 0 -1
1) "lisi"
2) "zhangsna"

########################################################################
zcard # 获取有序集合中的个数
127.0.0.1:6379[3]> zcard salary
(integer) 2

########################################################################
zcount # 获取指定区间的成员数量
127.0.0.1:6379[3]> zadd myset 1 hello
(integer) 1
127.0.0.1:6379[3]> zadd myset 2 world 3 p
(integer) 2
127.0.0.1:6379[3]> zcount myset 1 3  # 获取指定区间的成员数量
(integer) 3
127.0.0.1:6379[3]

案例思路:

set 排序 存储班级成绩表,工资表排序

普通消息 设置成 1,重要消息设置成 2 带权重进行判断

排行榜应用实现,取TOP N测试

三种特殊数据类型

geospatial地里位置

朋友的定位,附近的人,打车距离

这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人

只有六个命令

Geoadd

# geoadd 添加地理位置
#规则:两级无法直接添加,一般会下载城市数据,直接通过java程序一次性导入
#参数:geoadd key 经度 维度 地点
127.0.0.1:6379[3]> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379[3]> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379[3]> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen
(integer) 2
127.0.0.1:6379[3]> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2

GEOPOS(geopos)

127.0.0.1:6379[3]> geopos china:city beijing chongqing # 获取指定城市的经度和纬度
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "106.49999767541885376"
   2) "29.52999957900659211"

Geodist

两人之间的距离

单位:

  • m 米
  • km 千米
  • mi 英里
  • ft 英尺
127.0.0.1:6379[3]> GEODIST china:city beijing shanghai km  # 查看上海到北京的直线距离 km
"1067.3788"

GeoRadius

以给定的经纬度为中心,找出某一半径内的元素

我附近的人?(获取所有附近的人的定位,定位)通过半径来查询

获取指定数量的人

# 以100 30这个经纬度为中心,寻找方圆1000km内的城市
127.0.0.1:6379[3]> GEORADIUS china:city 110 30 1000 km  
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379[3]> GEORADIUS china:city 110 30 500 km
1) "chongqing"
2) "xian"
127.0.0.1:6379[3]> GEORADIUS china:city 110 30 500 km withdist # 显示到中心距离的位置
  #寻找半径为 500km的城市 并附带直线距离
1) 1) "chongqing"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"

 #寻找半径为 500km的城市 并附带经纬度
127.0.0.1:6379[3]> GEORADIUS china:city 110 30 500 km withcoord # 显示他人的定位信息
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"

#筛选出指定的结果
# 查出该经纬度附近500km的经纬度 直线距离和 指定查询数量
127.0.0.1:6379[3]> GEORADIUS china:city 110 30 500 km withdist withcoord count 1
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379[3]> GEORADIUS china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) "483.8340"
   3) 1) "108.96000176668167114"
      2) "34.25999964418929977"

GEORADIUSBYMEMBER

找出位于指定元素周围的元素

127.0.0.1:6379[3]> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"

Geohash

该命令将返回11个字符的Geohash字符串

# 将二维的经纬度转化为一维的字符串,如果两个字符串越来越近,那么距离越近
127.0.0.1:6379[3]> GEOHASH china:city beijing chongqing
1) "wx4fbxxfke0"           
2) "wm5xzrybty0"

GEO

GEO的底层实现原理其实就是Zset! 可以使用Zset命令来操作geo

127.0.0.1:6379[3]> ZRANGE china:city 0 -1  #查看地图中的全部元素
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379[3]> zrem china:city beijing  # 移除某个元素
(integer) 1
127.0.0.1:6379[3]> ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"

Hyperloglog

什么是基数

A{1,3,5,7,8,7} B{1,3,5,7,8}

基数(不重复的元素)=5,可以接受误差

简介

Redis Hyperloglog基数统计的算法

优点:占用的内存是固定的,2^64不同的元素的技术,只需要占12KB内存,如果要从内存角度来比较的话Hyperloglog首选

网页的UV(一个人访问一个网站多次,但是还是算作一个人)

传统的方式,set保存用户的id,然后就可以统计set中元素数量作为标准判断—这个方式如果保存大量的用户id,就会比较麻烦! 目的是为了计数,而不是保存用户id

127.0.0.1:6379> PFadd mykey a b c d e f g h i j  # 创建第一组元素 mykey
(integer) 1
127.0.0.1:6379> pfcount mykey  # 统计mykey 元素的基数数量
(integer) 10
127.0.0.1:6379> 
127.0.0.1:6379> pfadd mykey2 i j k z x c v b n m  # 创建第二组元素 mykey2
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 10
127.0.0.1:6379> PFMERGE myke3 mykey mykey2  # 合并两组 mykey+mykey2 => myke3 并集
OK
127.0.0.1:6379> PFCOUNT myke3  # 查看myke3(并集)数量
(integer) 16

如果允许容错,那么一定可以使用Hyperloglog

如果不允许容错,就使用set 或者自己的数据类型即可

Bitmaps

位存储

统计疫情感染人数(比如10个人) 为感染的是 0 感染的人 1 将信息用 0 1 存储

0 0 0 0 1 0 1 0 1 0

统计用户信息,活跃,不活跃;登录、未登录;打卡,未打卡 两个状态,都可以使用Bitmaps

Bitmaps位图,数据结构,都是操作二进制位来进行记录, 就只有0 和1 两个状态

365天=365bit 1字节=8bit 46个字节

测试

模拟打卡

星期一星期二
0123456
1(打卡)0(未打卡)10101

一打卡,周二未打卡

周一:1 周二: 0

127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> 

查看某一天是否打卡【getbit】

127.0.0.1:6379> getbit sign 3   # 周四打开
(integer) 1
127.0.0.1:6379> getbit sign 1   # 周二未打卡
(integer) 0

统计操作,统计打卡的天数【bitcount】

127.0.0.1:6379> bitcount sign   # 统计打卡的天数
(integer) 4   #四天

事务

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

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

---  # 一个一个的执行
set
set
set
---- 
队列  set set  set 执行  # 一块执行,不能被打断

Mysql的ACID 要么同时成功,要么同时失败,原子性

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

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

Redis单条命令保存原子性,但是事务不保证没有原子性

Redis的事务

  • 开启事务(Multi)
  • 命令入队(…)其他命令
  • 执行事务(exec)

锁:Redis可以实现乐观锁

正常执行事务

127.0.0.1:6379> MULTI     # 开启事务
OK
# 命令入队
127.0.0.1:6379> set k1 v1   
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec   # 执行事务,将返回结果打印
1) OK
2) OK
3) "v2"
4) OK

放弃事务

127.0.0.1:6379> multi   # 开启事务
OK
# 命令入队
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD   # 取消事务
OK
127.0.0.1:6379> get k4   # 事务队列中命令都不会被执行
(nil)

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

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3   # 错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec   # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4   # 所有的命令都不会执行
(nil)

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

127.0.0.1:6379> set k1 v1   # 先设置key
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> incr k1   # 错误的命令
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
# 虽然第一条命令报错了,其他依旧可以执行
1) (error) ERR value is not an integer or out of range  
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"

监控 Watch(面试常问)

悲观锁:

  • 很悲观,认为什么时候都会出问题,无论做什么都会加锁

乐观锁:

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

Redis测监视测试

127.0.0.1:6379> set money 100   # 设置钱数为100
OK 
127.0.0.1:6379> set out 0       # 设置出钱为0
OK
127.0.0.1:6379> watch money       # 监控钱数
OK
127.0.0.1:6379> MULTI        #开启事务
OK
127.0.0.1:6379> DECRBY money 20      # 减少20
QUEUED
127.0.0.1:6379> INCRBY out 20        # 增加20
QUEUED
127.0.0.1:6379> exec        # 执行
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch 可以当做redis的乐观锁操作

127.0.0.1:6379> set money 100   # 设置钱数为100
OK 
127.0.0.1:6379> set out 0       # 设置出钱为0
OK
127.0.0.1:6379> watch money  # 监视 money对象
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec  # 执行之前,另外一个线程,修改了 money值,会导致事务执行失败
(nil)

另一个线程

127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000  # 在队列没有执行的时候,修改 money的值
OK

如果修改失败,获取最新的值就好

事务执行失败如何解决

127.0.0.1:6379> unwatch   # 1、如果发现事务执行失败,就先解锁
OK
127.0.0.1:6379> watch money  # 2、获取最新的值,再次监视  select version
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY money 10
QUEUED
127.0.0.1:6379> exec 
#3、比对监视的值是否发生了变化,如果没有变化,那么可以执行陈宫,如果变量就执行失败
1) (integer) 990
2) (integer) 1000

Jedis

使用java来操作Redis

Jedis是Redis官方推荐的java连接开发工具,使用java操作Redis中间件,

依赖

 		<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>

测试

  • 连接数据库
 public static void main(String[] args) {
        //1 、new jedis
        Jedis jedis = new Jedis("127.0.0.1",6379);
        //jedis所有的命令就是之前学过的所有指令
        System.out.println(jedis.ping());
    }
// 结果 输出 PONG
  • 操作命令
  • 断开连接

常用API

String

List

Set

Hash

Zset

public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);

        System.out.println("清空数据"+jedis.flushDB());
        System.out.println("判断某个键是否存在"+jedis.exists("username"));
        System.out.println("新增<'username','hello'>"+jedis.set("username", "hello"));
        System.out.println("新增<'password','password'>"+jedis.set("password", "password"));
        System.out.println("系统中所有的键如下");
        Set<String> keys = jedis.keys("*");
        System.out.println(keys);
        System.out.println("删除键password"+jedis.del("passwrod"));
        System.out.println("判断passwrod键是否存在"+jedis.exists("passwrod"));
        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("删除当前数据库中内容"+jedis.flushDB());
        System.out.println("返回当前数据库中key的数目"+jedis.dbSize());
        System.out.println("清除数据库中所有的key"+jedis.flushAll());
		
    	jedis.close  //关闭连接
    }

事务

public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","world");
        jsonObject.put("name","p");
        //开启事务
        Transaction multi = jedis.multi();
        String s = jsonObject.toJSONString();
        try {   //怕出错,用try包裹
            multi.set("user1",s);
            multi.set("user2",s);
            
            //int i/0; //代码异常就放弃事务
            
            multi.exec();  //执行事务
        }catch (Exception e){
            multi.discard();  //失败,放弃事务
        }

        System.out.println(jedis.get("user1"));
        System.out.println(jedis.get("user2"));
        jedis.close();  //关闭连接

    }

SpringBoot整合Redis

依赖

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

springboot 2.x后 ,原来使用的 Jedis 被 lettuce 替换。

Jedis:采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool 连接池 BIO模式

lettuce :采用netty,实例可以再多个线程进行共享,不存在线程不安全的情况,可以减少线程数据了 NIO模式

在application.properties中配置

# SpringBoot 所有配置类,都有一个自动配置类
# 自动配置类都会绑定一个 properties 配置文件
spring.redis.host=127.0.0.1
spring.redis.port=6379

测试

 @Test
    void contextLoads(){
        //redisTemplate 操作不同的数据类型, api和之前学的是一样的
        /*
        redisTemplate.opsForValue();
        opsForValue   操作字符串,类似String
        opsForList    操作List  类似list
        opsForSet
        opsForHash
        opsForZSet
        opsForGeo
        opsForHyperLogLog
        除了基本的操作,常用的方法都可以直接通过redisTemplate操作,比如事务
        和基本的CRUD
         */

        //获取连接
      /*  RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        connection.flushAll();
        connection.flushDb();*/

      redisTemplate.opsForValue().set("mykey","hello");
        redisTemplate.opsForValue().set("mykey","中文");// 在java中可以显示,在redis中无法正常显示,需要序列化
        System.out.println(redisTemplate.opsForValue().get("mykey"));
    }

自定义redisTemplate

1、通过json对象进行存值

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User2 {
    private String name;
    private int age;
}
 	@Autowired
    private RedisTemplate redisTemplate;

	@Test
    void mtest() throws JsonProcessingException {
        //真实开发一般都使用json来传递对象
        User2 user = new User2("张三", 3);
        String jsonuser = new ObjectMapper().writeValueAsString(user);
        redisTemplate.opsForValue().set("user",jsonuser);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }

【在控制台中可以正常显示,在Redis中会显示乱码】

在redis-cli中的结果:[\xac\xed\x00\x05t\x00\x05mykey]

2、通过对象来存值

【需要将对象进行序列化】

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
//在企业中,所有的pojo类都会序列化 
public class User2 implements Serializable {
    private String name;
    private int age;
}
 	@Autowired
    private RedisTemplate redisTemplate;

	@Test
    void mtest() throws JsonProcessingException {
        //真实开发一般都使用json来传递对象
        User2 user = new User2("张三", 3);
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }

【在控制台中可以正常显示,在Redis中会显示乱码】

在redis-cli中的结果:[\xac\xed\x00\x05t\x00\x05mykey]

解决默认的jdk序列化

redis默认的序列化是jdk,这时候可以自己配置序列化方式

package com.p.HelloSpringBoot.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.net.UnknownHostException;

@Configuration
public class RedisConfig {
    //这是固定模板,企业中直接使用
    //自己定义了一个RedisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        //我们为了自己开发方便,一般直接使用<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        //                           连接工厂, 默认的
        template.setConnectionFactory(redisConnectionFactory);
        //Json序列化配置,
        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;
    }
}
	@Autowired
	//使用自定义的Template模板
    @Qualifier("redisTemplate")//找到类上带bean的 找到类名为redisTemplate 的类  
    private RedisTemplate redisTemplate;

	@Test
    void mtest() throws JsonProcessingException {
        //真实开发一般都使用json来传递对象
        User2 user = new User2("张三", 3);
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }

在redis-cli中的结果就变成了:“user”

工具类

在企业中,80% 都不会使用这个原生的方式去编写代码,这时候就需要写工具类

在真实的开发中,一般都可以看到一个公司自己封装RedisUtils

RedisUtils

package com.citydo.utils;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具类*/
@Component
public class RedisUtil {
    //可以用自定的模板序列化
    @Autowired
    private StringRedisTemplate redisTemplate;

    public void setRedisTemplate(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public StringRedisTemplate getRedisTemplate() {
        return this.redisTemplate;
    }

    /** -------------------key相关操作--------------------- */

    /**
     * 删除key
     * 
     * @param key
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 批量删除key
     * 
     * @param keys
     */
    public void delete(Collection<String> keys) {
        redisTemplate.delete(keys);
    }

    /**
     * 序列化key
     * 
     * @param key
     * @return
     */
    public byte[] dump(String key) {
        return redisTemplate.dump(key);
    }

    /**
     * 是否存在key
     * 
     * @param key
     * @return
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 设置过期时间
     * 
     * @param key
     * @param timeout
     * @param unit
     * @return
     */
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 设置过期时间
     * 
     * @param key
     * @param date
     * @return
     */
    public Boolean expireAt(String key, Date date) {
        return redisTemplate.expireAt(key, date);
    }

    /**
     * 查找匹配的key
     * 
     * @param pattern
     * @return
     */
    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 将当前数据库的 key 移动到给定的数据库 db 当中
     * 
     * @param key
     * @param dbIndex
     * @return
     */
    public Boolean move(String key, int dbIndex) {
        return redisTemplate.move(key, dbIndex);
    }

    /**
     * 移除 key 的过期时间,key 将持久保持
     * 
     * @param key
     * @return
     */
    public Boolean persist(String key) {
        return redisTemplate.persist(key);
    }

    /**
     * 返回 key 的剩余的过期时间
     * 
     * @param key
     * @param unit
     * @return
     */
    public Long getExpire(String key, TimeUnit unit) {
        return redisTemplate.getExpire(key, unit);
    }

    /**
     * 返回 key 的剩余的过期时间
     * 
     * @param key
     * @return
     */
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 从当前数据库中随机返回一个 key
     * 
     * @return
     */
    public String randomKey() {
        return redisTemplate.randomKey();
    }

    /**
     * 修改 key 的名称
     * 
     * @param oldKey
     * @param newKey
     */
    public void rename(String oldKey, String newKey) {
        redisTemplate.rename(oldKey, newKey);
    }

    /**
     * 仅当 newkey 不存在时,将 oldKey 改名为 newkey
     * 
     * @param oldKey
     * @param newKey
     * @return
     */
    public Boolean renameIfAbsent(String oldKey, String newKey) {
        return redisTemplate.renameIfAbsent(oldKey, newKey);
    }

    /**
     * 返回 key 所储存的值的类型
     * 
     * @param key
     * @return
     */
    public DataType type(String key) {
        return redisTemplate.type(key);
    }

    /** -------------------string相关操作--------------------- */

    /**
     * 设置指定 key 的值
     * @param key
     * @param value
     */
    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 获取指定 key 的值
     * @param key
     * @return
     */
    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 返回 key 中字符串值的子字符
     * @param key
     * @param start
     * @param end
     * @return
     */
    public String getRange(String key, long start, long end) {
        return redisTemplate.opsForValue().get(key, start, end);
    }

    /**
     * 将给定 key 的值设为 value ,并返回 key 的旧值(old value)
     * 
     * @param key
     * @param value
     * @return
     */
    public String getAndSet(String key, String value) {
        return redisTemplate.opsForValue().getAndSet(key, value);
    }

    /**
     * 对 key 所储存的字符串值,获取指定偏移量上的位(bit)
     * 
     * @param key
     * @param offset
     * @return
     */
    public Boolean getBit(String key, long offset) {
        return redisTemplate.opsForValue().getBit(key, offset);
    }

    /**
     * 批量获取
     * 
     * @param keys
     * @return
     */
    public List<String> multiGet(Collection<String> keys) {
        return redisTemplate.opsForValue().multiGet(keys);
    }

    /**
     * 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value
     * 
     * @param key 位置
     * @param value
     *            值,true为1, false为0
     * @return
     */
    public boolean setBit(String key, long offset, boolean value) {
        return redisTemplate.opsForValue().setBit(key, offset, value);
    }

    /**
     * 将值 value 关联到 key ,并将 key 的过期时间设为 timeout
     * 
     * @param key
     * @param value
     * @param timeout
     *            过期时间
     * @param unit
     *            时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES
     *            秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
     */
    public void setEx(String key, String value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    /**
     * 只有在 key 不存在时设置 key 的值
     * 
     * @param key
     * @param value
     * @return 之前已经存在返回false,不存在返回true
     */
    public boolean setIfAbsent(String key, String value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
     * 
     * @param key
     * @param value
     * @param offset
     *            从指定位置开始覆写
     */
    public void setRange(String key, String value, long offset) {
        redisTemplate.opsForValue().set(key, value, offset);
    }

    /**
     * 获取字符串的长度
     * 
     * @param key
     * @return
     */
    public Long size(String key) {
        return redisTemplate.opsForValue().size(key);
    }

    /**
     * 批量添加
     * 
     * @param maps
     */
    public void multiSet(Map<String, String> maps) {
        redisTemplate.opsForValue().multiSet(maps);
    }

    /**
     * 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
     * 
     * @param maps
     * @return 之前已经存在返回false,不存在返回true
     */
    public boolean multiSetIfAbsent(Map<String, String> maps) {
        return redisTemplate.opsForValue().multiSetIfAbsent(maps);
    }

    /**
     * 增加(自增长), 负数则为自减
     * 
     * @param key
     * @return
     */
    public Long incrBy(String key, long increment) {
        return redisTemplate.opsForValue().increment(key, increment);
    }

    /**
     * 
     * @param key
     * @return
     */
    public Double incrByFloat(String key, double increment) {
        return redisTemplate.opsForValue().increment(key, increment);
    }

    /**
     * 追加到末尾
     * 
     * @param key
     * @param value
     * @return
     */
    public Integer append(String key, String value) {
        return redisTemplate.opsForValue().append(key, value);
    }

    /** -------------------hash相关操作------------------------- */

    /**
     * 获取存储在哈希表中指定字段的值
     * 
     * @param key
     * @param field
     * @return
     */
    public Object hGet(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 获取所有给定字段的值
     * 
     * @param key
     * @return
     */
    public Map<Object, Object> hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 获取所有给定字段的值
     * 
     * @param key
     * @param fields
     * @return
     */
    public List<Object> hMultiGet(String key, Collection<Object> fields) {
        return redisTemplate.opsForHash().multiGet(key, fields);
    }

    public void hPut(String key, String hashKey, String value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    public void hPutAll(String key, Map<String, String> maps) {
        redisTemplate.opsForHash().putAll(key, maps);
    }

    /**
     * 仅当hashKey不存在时才设置
     * 
     * @param key
     * @param hashKey
     * @param value
     * @return
     */
    public Boolean hPutIfAbsent(String key, String hashKey, String value) {
        return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
    }

    /**
     * 删除一个或多个哈希表字段
     * 
     * @param key
     * @param fields
     * @return
     */
    public Long hDelete(String key, Object... fields) {
        return redisTemplate.opsForHash().delete(key, fields);
    }

    /**
     * 查看哈希表 key 中,指定的字段是否存在
     * 
     * @param key
     * @param field
     * @return
     */
    public boolean hExists(String key, String field) {
        return redisTemplate.opsForHash().hasKey(key, field);
    }

    /**
     * 为哈希表 key 中的指定字段的整数值加上增量 increment
     * 
     * @param key
     * @param field
     * @param increment
     * @return
     */
    public Long hIncrBy(String key, Object field, long increment) {
        return redisTemplate.opsForHash().increment(key, field, increment);
    }

    /**
     * 为哈希表 key 中的指定字段的整数值加上增量 increment
     * 
     * @param key
     * @param field
     * @param delta
     * @return
     */
    public Double hIncrByFloat(String key, Object field, double delta) {
        return redisTemplate.opsForHash().increment(key, field, delta);
    }

    /**
     * 获取所有哈希表中的字段
     * 
     * @param key
     * @return
     */
    public Set<Object> hKeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }

    /**
     * 获取哈希表中字段的数量
     * 
     * @param key
     * @return
     */
    public Long hSize(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    /**
     * 获取哈希表中所有值
     * 
     * @param key
     * @return
     */
    public List<Object> hValues(String key) {
        return redisTemplate.opsForHash().values(key);
    }

    /**
     * 迭代哈希表中的键值对
     * 
     * @param key
     * @param options
     * @return
     */
    public Cursor<Entry<Object, Object>> hScan(String key, ScanOptions options) {
        return redisTemplate.opsForHash().scan(key, options);
    }

    /** ------------------------list相关操作---------------------------- */

    /**
     * 通过索引获取列表中的元素
     * 
     * @param key
     * @param index
     * @return
     */
    public String lIndex(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }

    /**
     * 获取列表指定范围内的元素
     * 
     * @param key
     * @param start
     *            开始位置, 0是开始位置
     * @param end
     *            结束位置, -1返回所有
     * @return
     */
    public List<String> lRange(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 存储在list头部
     * 
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPush(String key, String value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPushAll(String key, String... value) {
        return redisTemplate.opsForList().leftPushAll(key, value);
    }

    /**
     * 
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPushAll(String key, Collection<String> value) {
        return redisTemplate.opsForList().leftPushAll(key, value);
    }

    /**
     * 当list存在的时候才加入
     * 
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPushIfPresent(String key, String value) {
        return redisTemplate.opsForList().leftPushIfPresent(key, value);
    }

    /**
     * 如果pivot存在,再pivot前面添加
     * 
     * @param key
     * @param pivot
     * @param value
     * @return
     */
    public Long lLeftPush(String key, String pivot, String value) {
        return redisTemplate.opsForList().leftPush(key, pivot, value);
    }

    /**
     * 
     * @param key
     * @param value
     * @return
     */
    public Long lRightPush(String key, String value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 
     * @param key
     * @param value
     * @return
     */
    public Long lRightPushAll(String key, String... value) {
        return redisTemplate.opsForList().rightPushAll(key, value);
    }

    /**
     * 
     * @param key
     * @param value
     * @return
     */
    public Long lRightPushAll(String key, Collection<String> value) {
        return redisTemplate.opsForList().rightPushAll(key, value);
    }

    /**
     * 为已存在的列表添加值
     * 
     * @param key
     * @param value
     * @return
     */
    public Long lRightPushIfPresent(String key, String value) {
        return redisTemplate.opsForList().rightPushIfPresent(key, value);
    }

    /**
     * 在pivot元素的右边添加值
     * 
     * @param key
     * @param pivot
     * @param value
     * @return
     */
    public Long lRightPush(String key, String pivot, String value) {
        return redisTemplate.opsForList().rightPush(key, pivot, value);
    }

    /**
     * 通过索引设置列表元素的值
     * 
     * @param key
     * @param index
     *            位置
     * @param value
     */
    public void lSet(String key, long index, String value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    /**
     * 移出并获取列表的第一个元素
     * 
     * @param key
     * @return 删除的元素
     */
    public String lLeftPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
     * 
     * @param key
     * @param timeout
     *            等待时间
     * @param unit
     *            时间单位
     * @return
     */
    public String lBLeftPop(String key, long timeout, TimeUnit unit) {
        return redisTemplate.opsForList().leftPop(key, timeout, unit);
    }

    /**
     * 移除并获取列表最后一个元素
     * 
     * @param key
     * @return 删除的元素
     */
    public String lRightPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }

    /**
     * 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
     * 
     * @param key
     * @param timeout
     *            等待时间
     * @param unit
     *            时间单位
     * @return
     */
    public String lBRightPop(String key, long timeout, TimeUnit unit) {
        return redisTemplate.opsForList().rightPop(key, timeout, unit);
    }

    /**
     * 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
     * 
     * @param sourceKey
     * @param destinationKey
     * @return
     */
    public String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
        return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
                destinationKey);
    }

    /**
     * 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
     * 
     * @param sourceKey
     * @param destinationKey
     * @param timeout
     * @param unit
     * @return
     */
    public String lBRightPopAndLeftPush(String sourceKey, String destinationKey,
            long timeout, TimeUnit unit) {
        return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
                destinationKey, timeout, unit);
    }

    /**
     * 删除集合中值等于value得元素
     * 
     * @param key
     * @param index
     *            index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素;
     *            index<0, 从尾部开始删除第一个值等于value的元素;
     * @param value
     * @return
     */
    public Long lRemove(String key, long index, String value) {
        return redisTemplate.opsForList().remove(key, index, value);
    }

    /**
     * 裁剪list
     * 
     * @param key
     * @param start
     * @param end
     */
    public void lTrim(String key, long start, long end) {
        redisTemplate.opsForList().trim(key, start, end);
    }

    /**
     * 获取列表长度
     * 
     * @param key
     * @return
     */
    public Long lLen(String key) {
        return redisTemplate.opsForList().size(key);
    }

    /** --------------------set相关操作-------------------------- */

    /**
     * set添加元素
     * 
     * @param key
     * @param values
     * @return
     */
    public Long sAdd(String key, String... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    /**
     * set移除元素
     * 
     * @param key
     * @param values
     * @return
     */
    public Long sRemove(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }

    /**
     * 移除并返回集合的一个随机元素
     * 
     * @param key
     * @return
     */
    public String sPop(String key) {
        return redisTemplate.opsForSet().pop(key);
    }

    /**
     * 将元素value从一个集合移到另一个集合
     * 
     * @param key
     * @param value
     * @param destKey
     * @return
     */
    public Boolean sMove(String key, String value, String destKey) {
        return redisTemplate.opsForSet().move(key, value, destKey);
    }

    /**
     * 获取集合的大小
     * 
     * @param key
     * @return
     */
    public Long sSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 判断集合是否包含value
     * 
     * @param key
     * @param value
     * @return
     */
    public Boolean sIsMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * 获取两个集合的交集
     * 
     * @param key
     * @param otherKey
     * @return
     */
    public Set<String> sIntersect(String key, String otherKey) {
        return redisTemplate.opsForSet().intersect(key, otherKey);
    }

    /**
     * 获取key集合与多个集合的交集
     * 
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<String> sIntersect(String key, Collection<String> otherKeys) {
        return redisTemplate.opsForSet().intersect(key, otherKeys);
    }

    /**
     * key集合与otherKey集合的交集存储到destKey集合中
     * 
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sIntersectAndStore(String key, String otherKey, String destKey) {
        return redisTemplate.opsForSet().intersectAndStore(key, otherKey,
                destKey);
    }

    /**
     * key集合与多个集合的交集存储到destKey集合中
     * 
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sIntersectAndStore(String key, Collection<String> otherKeys,
            String destKey) {
        return redisTemplate.opsForSet().intersectAndStore(key, otherKeys,
                destKey);
    }

    /**
     * 获取两个集合的并集
     * 
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<String> sUnion(String key, String otherKeys) {
        return redisTemplate.opsForSet().union(key, otherKeys);
    }

    /**
     * 获取key集合与多个集合的并集
     * 
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<String> sUnion(String key, Collection<String> otherKeys) {
        return redisTemplate.opsForSet().union(key, otherKeys);
    }

    /**
     * key集合与otherKey集合的并集存储到destKey中
     * 
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sUnionAndStore(String key, String otherKey, String destKey) {
        return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
    }

    /**
     * key集合与多个集合的并集存储到destKey中
     * 
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sUnionAndStore(String key, Collection<String> otherKeys,
            String destKey) {
        return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
    }

    /**
     * 获取两个集合的差集
     * 
     * @param key
     * @param otherKey
     * @return
     */
    public Set<String> sDifference(String key, String otherKey) {
        return redisTemplate.opsForSet().difference(key, otherKey);
    }

    /**
     * 获取key集合与多个集合的差集
     * 
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<String> sDifference(String key, Collection<String> otherKeys) {
        return redisTemplate.opsForSet().difference(key, otherKeys);
    }

    /**
     * key集合与otherKey集合的差集存储到destKey中
     * 
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sDifference(String key, String otherKey, String destKey) {
        return redisTemplate.opsForSet().differenceAndStore(key, otherKey,
                destKey);
    }

    /**
     * key集合与多个集合的差集存储到destKey中
     * 
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sDifference(String key, Collection<String> otherKeys,
            String destKey) {
        return redisTemplate.opsForSet().differenceAndStore(key, otherKeys,
                destKey);
    }

    /**
     * 获取集合所有元素
     * 
     * @param key
     * @return
     */
    public Set<String> setMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 随机获取集合中的一个元素
     * 
     * @param key
     * @return
     */
    public String sRandomMember(String key) {
        return redisTemplate.opsForSet().randomMember(key);
    }

    /**
     * 随机获取集合中count个元素
     * 
     * @param key
     * @param count
     * @return
     */
    public List<String> sRandomMembers(String key, long count) {
        return redisTemplate.opsForSet().randomMembers(key, count);
    }

    /**
     * 随机获取集合中count个元素并且去除重复的
     * 
     * @param key
     * @param count
     * @return
     */
    public Set<String> sDistinctRandomMembers(String key, long count) {
        return redisTemplate.opsForSet().distinctRandomMembers(key, count);
    }

    /**
     * 
     * @param key
     * @param options
     * @return
     */
    public Cursor<String> sScan(String key, ScanOptions options) {
        return redisTemplate.opsForSet().scan(key, options);
    }

    /**------------------zSet相关操作--------------------------------*/
    
    /**
     * 添加元素,有序集合是按照元素的score值由小到大排列
     * 
     * @param key
     * @param value
     * @param score
     * @return
     */
    public Boolean zAdd(String key, String value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * 
     * @param key
     * @param values
     * @return
     */
    public Long zAdd(String key, Set<TypedTuple<String>> values) {
        return redisTemplate.opsForZSet().add(key, values);
    }

    /**
     * 
     * @param key
     * @param values
     * @return
     */
    public Long zRemove(String key, Object... values) {
        return redisTemplate.opsForZSet().remove(key, values);
    }

    /**
     * 增加元素的score值,并返回增加后的值
     * 
     * @param key
     * @param value
     * @param delta
     * @return
     */
    public Double zIncrementScore(String key, String value, double delta) {
        return redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }

    /**
     * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
     * 
     * @param key
     * @param value
     * @return 0表示第一位
     */
    public Long zRank(String key, Object value) {
        return redisTemplate.opsForZSet().rank(key, value);
    }

    /**
     * 返回元素在集合的排名,按元素的score值由大到小排列
     * 
     * @param key
     * @param value
     * @return
     */
    public Long zReverseRank(String key, Object value) {
        return redisTemplate.opsForZSet().reverseRank(key, value);
    }

    /**
     * 获取集合的元素, 从小到大排序
     * 
     * @param key
     * @param start
     *            开始位置
     * @param end
     *            结束位置, -1查询所有
     * @return
     */
    public Set<String> zRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 获取集合元素, 并且把score值也获取
     * 
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<TypedTuple<String>> zRangeWithScores(String key, long start,
            long end) {
        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    /**
     * 根据Score值查询集合元素
     * 
     * @param key
     * @param min
     *            最小值
     * @param max
     *            最大值
     * @return
     */
    public Set<String> zRangeByScore(String key, double min, double max) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }

    /**
     * 根据Score值查询集合元素, 从小到大排序
     * 
     * @param key
     * @param min
     *            最小值
     * @param max
     *            最大值
     * @return
     */
    public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
            double min, double max) {
        return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
    }

    /**
     * 
     * @param key
     * @param min
     * @param max
     * @param start
     * @param end
     * @return
     */
    public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
            double min, double max, long start, long end) {
        return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,
                start, end);
    }

    /**
     * 获取集合的元素, 从大到小排序
     * 
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<String> zReverseRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }

    /**
     * 获取集合的元素, 从大到小排序, 并返回score值
     * 
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<TypedTuple<String>> zReverseRangeWithScores(String key,
            long start, long end) {
        return redisTemplate.opsForZSet().reverseRangeWithScores(key, start,
                end);
    }

    /**
     * 根据Score值查询集合元素, 从大到小排序
     * 
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<String> zReverseRangeByScore(String key, double min,
            double max) {
        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
    }

    /**
     * 根据Score值查询集合元素, 从大到小排序
     * 
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<TypedTuple<String>> zReverseRangeByScoreWithScores(
            String key, double min, double max) {
        return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
                min, max);
    }

    /**
     * 
     * @param key
     * @param min
     * @param max
     * @param start
     * @param end
     * @return
     */
    public Set<String> zReverseRangeByScore(String key, double min,
            double max, long start, long end) {
        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max,
                start, end);
    }

    /**
     * 根据score值获取集合元素数量
     * 
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Long zCount(String key, double min, double max) {
        return redisTemplate.opsForZSet().count(key, min, max);
    }

    /**
     * 获取集合大小
     * 
     * @param key
     * @return
     */
    public Long zSize(String key) {
        return redisTemplate.opsForZSet().size(key);
    }

    /**
     * 获取集合大小
     * 
     * @param key
     * @return
     */
    public Long zZCard(String key) {
        return redisTemplate.opsForZSet().zCard(key);
    }

    /**
     * 获取集合中value元素的score值
     * 
     * @param key
     * @param value
     * @return
     */
    public Double zScore(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }

    /**
     * 移除指定索引位置的成员
     * 
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Long zRemoveRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().removeRange(key, start, end);
    }

    /**
     * 根据指定的score值的范围来移除成员
     * 
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Long zRemoveRangeByScore(String key, double min, double max) {
        return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
    }

    /**
     * 获取key和otherKey的并集并存储在destKey中
     * 
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long zUnionAndStore(String key, String otherKey, String destKey) {
        return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
    }

    /**
     * 
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long zUnionAndStore(String key, Collection<String> otherKeys,
            String destKey) {
        return redisTemplate.opsForZSet()
                .unionAndStore(key, otherKeys, destKey);
    }

    /**
     * 交集
     * 
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long zIntersectAndStore(String key, String otherKey,
            String destKey) {
        return redisTemplate.opsForZSet().intersectAndStore(key, otherKey,
                destKey);
    }

    /**
     * 交集
     * 
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long zIntersectAndStore(String key, Collection<String> otherKeys,
            String destKey) {
        return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys,
                destKey);
    }

    /**
     * 
     * @param key
     * @param options
     * @return
     */
    public Cursor<TypedTuple<String>> zScan(String key, ScanOptions options) {
        return redisTemplate.opsForZSet().scan(key, options);
    }
}

测试

    @Autowired
    @Qualifier("redisTemplate")//找到类上带bean的 找到类名为redisTemplate 的类
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisUtil redisUtil;

	@Test
    public void test2(){
        redisUtil.set("name","hello");
        System.out.println(redisUtil.get("name"));
    }

Redis.con详解

启动的时候,就通过配置文件来启动

单位

# 设置单位
# 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. #设置1GB、1gB、1Gb都可以用

【配置文件unit单位对大小写不敏感】

包含 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中的import标签,可以将其他文件包含进来

网络

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

通用配置

daemonize yes  #  以守护进程的方式运行, 默认是no 需要自己开启yes   开启后台
pidfile /var/run/redis_6379.pid  # 如果以后台方式运行,就需要指定一个pid文件

#日志
# Specify the server verbosity level.   日志级别
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level) debug级别
# notice (moderately verbose, what you want in production probably)  生产环境
# warning (only very important / critical messages are logged) 
loglevel notice

logfile ""  # 日志的文件位置名,为空就默认为输出
databases 16 # 数据库的数量,默认为16个

always-show-logo yes  # 是否显示redis服务器启动时的log

快照

持久化:在规定的时间内,执行了多少次操作,则会持久化到文件 .rdb .aof

redis是内存数据库,如果没有持久化,那么数据断电及失

save 900 1  # 如果900s内,如果至少一个 key进行了修改,我们进行持久化操作
save 300 10  # 如果300s内,如果至少10个 key进行了修改,我们进行持久化操作
save 60 10000 # 如果60s内,如果至少10000个 key进行了修改,我们进行持久化操作
#之后学习持久化,会自己定义这个测试

stop-writes-on-bgsave-error yes   # 持久化如果出错,是否还需要继续工作
rdbcompression yes  # 是否压缩rdb 文件(持久化的文件),需要消耗cpu资源

rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验
dir ./            # rdb文件保存的目录

REPLICATION复制,主从复制

replicaof <masterip> <masterport>  # 主机的ip和端口
masterauth <master-password> # 主机的密码

SECURITY 安全

redis默认是没有密码的,可以自己设置密码

在redis-cli中设置密码

127.0.0.1:6379> ping   # 未设置密码前
PONG  
127.0.0.1:6379> config get requirepass   # 获取redis密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass 123456  # 设置redis密码
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> auth 123456   # 使用密码登录
OK

CLIENTS 限制

maxclients 10000  # 设置能连接上redis的最大客户端的数量
maxmemory <bytes> # redis配置最大的内存容量
maxmemory-policy noeviction  # 内存达到上限之后的处理策略
	# 移除一些过期的key
	1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
	2、allkeys-lru : 删除lru算法的key   
	3、volatile-random:随机删除即将过期key   
	4、allkeys-random:随机删除   
	5、volatile-ttl : 删除即将过期的   
	6、noeviction : 永不过期,返回错误

APPEND ONLY MODE 模式 aop配置

appendonly no  # 默认是不开启aop模式,默认是使用rdb方式持久化的,在大部分的情况下,rdb完全够用
appendfilename "appendonly.aof"  # 持久化的文件名字

# appendfsync always  # 每次修改都会 sync,消耗性能  不推荐
appendfsync everysec  # 每秒执行一次sync ,可能会丢失这1s的数据
# appendfsync no      # 不执行 sync,这个时候操作系统自己同步数据,速度最快   不推荐

Redis持久化

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以redis提供了持久化功能

RDB

是什么RDB

在主从复制中,rdb就是备用,从机上用

在指定时间间隔内将内存中的数据集写入磁盘,也就是Snapshot快照,它恢复时是将文件直接读到内存里

Redis会单独创建(fork)一个子进程来进行持久化,会先将书局写入到一个临时文件中,待持久化过程都结束了。再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF更加高效,RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置。

rdb保存的文件是 dump.rdb

dbfilename dump.rdb

自定义rdb 保存机制
# save 900 1
# save 300 10
# save 60 10000
save 60 5  # 自定义的 只要60s内修改5次key,就会触发rdb操作

触发机制

  1. save规则满足的情况下,会自动触发rdb规则
  2. 执行flushall命令也会触发rdb规则
  3. 退出redis,也会产生rdb

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

如如何恢复rdb文件

  1. 只需要将rdb文件放在redis的启动目录下就可以了,redis启动的时候会自动检查dump.rdb文件恢复
  2. 查看需要存在的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/redis-6.0.6/bin"	#如果在这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据

几乎他自己的默认配置就够用了,但是我们还是学习

有时候在生产环境,我们会将这个文件进行备份

优点

  1. 适合大规模的数据恢复!dump.rdb
  2. 如果对数据完整性不高!

缺点

  1. 需要一定的时间间隔进行操作,如果redis意外宕机了,那么最后一次修改的数据就没有了
  2. fork进程的时候会占用一定的内存空间

AOF(Append Only File)

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

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

AOF保存的是appendonly.aof文件

默认是不开启的,我们需要手动进行配置,我们只需要将appendonly改为yes即可

append only   yes   #默认是不开启的  默认是没有appendonly.aof该文件

重启redis就可以生效了

#appendonly.aof
如果在该文件中进行破坏,或追加无效内容

如果这个aof文件有错误,这时候redis是启动不起来的,我们需要修复这个aof文件

redis给我们提供了redis-check-aof --fix appendonly.aof 命令来进行appendonly.aof的修复

如果文件正常,重启就可以直接恢复了

优点和缺点

优点:

  1. 每一次修改都同步,文件完整性更加好
  2. 每同步一次,可能会丢失一秒的数据
  3. 从不同步,效率最高的

缺点:

  1. 相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢
  2. AOF运行效率也要比rdb慢,redis默认的配置就是rdb持久化

重写规则

aof默认就是文件的无限追加,文件会越来越大

#配置
auto-aop-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

如果aof文件大于64MB,太大了,!fork一个新的进程来将我们文件进行重写

扩展

Redis 提供了不同级别的持久化方式:

RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始:
RDB的优点
RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.

RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.

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

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

RDB的缺点
如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.

RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.

AOF 优点
使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.

AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.

Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。

AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

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

根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

Redis发布订阅

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

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

第一个:消息发送者,第二个:频道,第三个:消息订阅者

命令

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

命令描述
PSUBSCRIBE pattern [pattern…]订阅一个或多个符合给定模式的频道。
PUNSUBSCRIBE pattern [pattern…]退订一个或多个符合给定模式的频道。
PUBSUB subcommand [argument[argument]]查看订阅与发布系统状态。
PUBLISH channel message向指定频道发布消息
SUBSCRIBE channel [channel…]订阅给定的一个或多个频道。
SUBSCRIBE channel [channel…]退订一个或多个频道

测试

------------订阅端----------------------
127.0.0.1:6379> SUBSCRIBE sakura # 订阅sakura频道
Reading messages... (press Ctrl-C to quit) # 等待接收消息
1) "subscribe" # 订阅成功的消息
2) "sakura"
3) (integer) 1
1) "message" # 接收到来自sakura频道的消息 "hello world"
2) "sakura"
3) "hello world"
1) "message" # 接收到来自sakura频道的消息 "hello i am sakura"
2) "sakura"
3) "hello i am sakura"

--------------消息发布端-------------------
127.0.0.1:6379> PUBLISH sakura "hello world" # 发布消息到sakura频道
(integer) 1
127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 发布消息
(integer) 1

-----------------查看活跃的频道------------
127.0.0.1:6379> PUBSUB channels
1) "sakura"

客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。

缺点

如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。

应用

消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
多人在线聊天室。
稍微复杂的场景,我们就会使用消息中间件MQ处理。

Redis主从复制

概念:

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

默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。

主从复制,读写分离! 80%的情况下都是在进行读操作,减缓服务器的压力,架构中经常使用!一主二从

作用(面试):

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
  • 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
  • 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
  • 高可用(集群)基石:主从复制还是哨兵和集群能够实施的基础。

为什么使用集群:

  • 单台服务器难以负载大量的请求
  • 单台服务器故障率高,系统崩坏概率大
  • 单台服务器内存容量有限。

主机-从机 主机-从机-从机

​ -从机

环境配置

只配置从库,不用配置主库

在Redis中查看当前库的信息:info replication

127.0.0.1:6379> info replication
# Replication  信息
role:master  # 角色
connected_slaves:0   #从机数量  0 为没有从机
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

进入Redis配置文件对启动服务文件进行配置

cp redis.conf redis79.conf 根据端口进行起名

复制3个配置文件,修改对应的信息

1、端口

2、pid名字

3、log文件名字

4、dump.rdb名字

端口为79的配置修改

port 6379  # 修改端口
daemonize yes  # 查看后台运行是否开启
pidfile /var/run/redis_6379.pid  #  查看此处的pid是否跟端口一致
logfile "6379.log"  # 修改不同的日志文件,避免日志文件冲突
dbfilename dump6379.rdb  #  修改不同名的dump文件,避免冲突

端口为80的配置修改

port 6380  # 修改端口
daemonize yes  # 查看后台运行是否开启
pidfile /var/run/redis_6380.pid  #  查看此处的pid是否跟端口一致
logfile "6380.log"  # 修改不同的日志文件,避免日志文件冲突
dbfilename dump6380.rdb  #  修改不同名的dump文件,避免冲突

依次启动不同端口的配置文件

redis-server myconfig/redis79.conf
redis-server myconfig/redis80.conf
redis-server myconfig/redis81.conf

查看Redis服务是否启动

ps -ef|grep redis

一主二从

默认情况下,每台Redis服务器都是主节点一般情况只用配置从机即可

简单来说就是认老大 一主(79) 二从(80,81)

从机命令认79为老大

127.0.0.1:6380> SLAVEOF 127.0.0.1 6379  SLAVEOF IP  HOST
OK
# 此时变为从机
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_repl_offset:70
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:f5b7573d15d81ea52304037d59790c88994ef292
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:70
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:70

#############主机信息
127.0.0.1:6379> info replication
# Replication
role:master  # 当前角色为主机
connected_slaves:1  # 增加一个从机数量
slave0:ip=127.0.0.1,port=6380,state=online,offset=266,lag=1  # 从机信息
master_replid:f5b7573d15d81ea52304037d59790c88994ef292
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:266
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:266
# 如果两个从机都配置了,就有两个从机数量

真实的从主配置应该在配置文件中配置,这样的话是永久的,使用命令是暂时的,当从机服务关闭后就消失了

配置文件中配置

replicaof <masterip> <masterport>  # 主机的ip和端口
masterauth <master-password> # 主机的密码

细节

主机可以写,从机不能写只能读,主机中的所有信息和数据,都会自动被从机保存

如果主机宕机,从机依旧连接到主机的,但是没有写操作。

如果主机恢复了,从机依旧可以获取到从机写的信息。

如果使用命令行来配置主从,如果从机重启了,从机会变成主机,只要变回从机,立马会从主机中获取值

复制原理

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

宕机后手动配置主机

79为80主节点,80为81的主节点

主机—》从机—》从机

如果没有老大了,这个时候手动选择一个老大出来

谋权篡位

如果主机断开了连接,我们可以使用slaveof no one 让自己变成主机,其他的节点就可以手动连接到最新的这个主节点(手动)

如果主机回来了,就需要重新连接

哨兵模式

(自动选举老大模式)

概述

主从切换的方法是:当主服务器宕机后,需要手动把一台服务器切换为主服务器,需要人工干预,费时费力,还会造成一段时间内服务器不可用。更多的时候我们考虑的是哨兵模式,redis从2.8开始正式提供了sentinel架构来解决这个问题。
谋朝篡位的自动版,能够后台监控主机是否故障,如果故障根据投票数自动将从库转换为主库
哨兵模式是一种特殊的模式,首先redis提供了哨兵命令,哨兵是一个独立的进程,作为进程,他会独立运行,其原理是哨兵通过发送命令,等待redis服务器响应,从而监控运行的多个redis实例

哨兵的作用

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

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

配置环境

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

# sentinel monitor 被监控的名称(随便起) host port 1  数字1 ,代表主机挂了,slave投票看让谁接替成为主机,票数最多的,就会成为主机。
sentinel monitor myredis 127.0.0.1 6379 1

2、启动哨兵

redis-sentinel myconfig/sentinel.conf

显示启动完成

00000, modified=0, pid=27114, just started
27114:X 01 Sep 2022 17:29:54.969 # Configuration loaded
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.0.6 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 27114
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

27114:X 01 Sep 2022 17:29:54.970 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
27114:X 01 Sep 2022 17:29:54.972 # Sentinel ID is 65d1cad6817403602f84bb4eb37417568fa5940a
27114:X 01 Sep 2022 17:29:54.972 # +monitor master myredis 127.0.0.1 6379 quorum 1  # 监控的主机为6379

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

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

优点:

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 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
 
# 当在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

缓存穿透与雪崩

概念

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

解决方案

布隆过滤器

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

缓存空对象

当存储层不命中后,即使返回空对象也将其缓存起来,同时会设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端的数据源
但是这个方法存在两个问题
1.如果空值能被缓存下来,这就意味着缓存需要更多的空间存储更多的键,因为这个当中可能会有很多的空值和键
2.即使对空值设置了过期时间,还会存在缓存层和存储层的数据会有一段时间窗口不一致,这对于需要保持一致性的业务会有影响

缓存击穿(查的多,过期)

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

缓存雪崩

是指在某个时间段,缓存集中过期失效。redis宕机
缓存雪崩的原因之一,比如写文本时,马上就要到双十二零点,很快就迎来一波抢购,这波商品比较集中的放入了缓存假设缓存缓存一个小时。到凌晨一点钟,这批商品的缓存就都过期了。而对于这批商品的查询访问都落到数据库上,数据库产生周期性的压力波值。于是所有的请求都会达到存储层,存储层的调用量就会爆增,造成存储器也会挂掉的情况。
其实集中过期不是最致命的,比较致命的缓存雪崩是缓存服务器某个节点宕机或者断网。因为自然原因形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候数据库也会顶住压力,无非是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
双十一:停到一些服务。如退款(保证主要服务可用)
解决方案
1.redis高可用
2.限流降级(springcloud讲过)
3.数据预热
优点他都有
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 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
 
# 当在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

缓存穿透与雪崩

概念

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

解决方案

布隆过滤器

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

缓存空对象

当存储层不命中后,即使返回空对象也将其缓存起来,同时会设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端的数据源
但是这个方法存在两个问题
1.如果空值能被缓存下来,这就意味着缓存需要更多的空间存储更多的键,因为这个当中可能会有很多的空值和键
2.即使对空值设置了过期时间,还会存在缓存层和存储层的数据会有一段时间窗口不一致,这对于需要保持一致性的业务会有影响

缓存击穿(查的多,过期)

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

缓存雪崩

是指在某个时间段,缓存集中过期失效。redis宕机
缓存雪崩的原因之一,比如写文本时,马上就要到双十二零点,很快就迎来一波抢购,这波商品比较集中的放入了缓存假设缓存缓存一个小时。到凌晨一点钟,这批商品的缓存就都过期了。而对于这批商品的查询访问都落到数据库上,数据库产生周期性的压力波值。于是所有的请求都会达到存储层,存储层的调用量就会爆增,造成存储器也会挂掉的情况。
其实集中过期不是最致命的,比较致命的缓存雪崩是缓存服务器某个节点宕机或者断网。因为自然原因形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候数据库也会顶住压力,无非是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
双十一:停到一些服务。如退款(保证主要服务可用)
解决方案
1.redis高可用
2.限流降级(springcloud讲过)
3.数据预热

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值