Redis
履霜坚冰至
1. NoSQL概述
- 定义
NoSQL,泛指非关系型的数据库,Not Only Structured Query Language
- 使用原因
- 单机MySQL时代
- Memcached(缓存) + Mysql + 垂直拆分(读写分离)
- 分库分表 + 水平拆分 + Mysql集群
- NoSQL+分库分表 + 水平拆分 + Mysql集群
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长, 这时候我们就需要使用NoSQL数据库的
- 分类
分类 | Examples举例 | 典型应用场景 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
键值(key-value) | Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB | 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。 | Key 指向 Value 的键值对,通常用hash table来实现 | 查找速度快 | 数据无结构化,通常只被当作字符串或者二进制数据 |
列存储数据库 | Cassandra, HBase, Riak | 分布式的文件系统 | 以列簇式存储,将同一列数据存在一起 | 查找速度快,可扩展性强,更容易进行分布式扩展 | 功能相对局限 |
文档型数据库 | CouchDB, MongoDb | Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) | Key-Value对应的键值对,Value为结构化数据 | 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 | 查询性能不高,而且缺乏统一的查询语法。 |
图形(Graph)数据库 | Neo4J, InfoGrid, Infinite Graph | 社交网络,推荐系统等。专注于构建关系图谱 | 图结构 | 利用图结构相关算法。比如最短路径寻址,N度关系查找等 | 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群方案。 |
- 特点
- 易扩展
- 大数据量,高性能(Redis 一秒读取11万,写8万)
- 灵活的数据模型
- 高可用
- 最终一致性CAP定理和BASE理论
- 大数据时代3v:主要是描述问题的
- 海量Velume
- 多样Variety
- 实时Velocity
- 大数据时代的3高 : 主要是对程序的要求
- 高并发
- 高可扩
- 高性能
2. Redis概述
- 定义
Redis(Remote Dictionary Server ),即远程字典服务。是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
- 下载安装
- windows安装
建议在github下载 [redis下载](https://github.com/dmajkic/redis)
- Linux安装
# 解压
tar -zxvf 压缩包名
# 环境安装
yum install gcc-c++
# 然后进入redis目录下编译
make
# 然后执行安装
make install
# 1. redis默认安装路径 /usr/local/bin
# 2. 将redis的配置文件复制到 程序安装目录 /usr/local/bin/kconfig下
mkdir kconfig
cp /opt/redis-5.0.8/redis.config kconfig
# 3. redis默认不是后台启动的,需要修改配置文件!将daemonize no 改为yes
vim redis.config
daemonize yes
# 4. 通过制定的配置文件启动redis服务
redis-server kconfig/redis.config
# 5. 使用redis-cli连接指定的端口号测试,Redis的默认端口6379
redis-cli -p 6379
# 6. 查看redis进程是否开启
ps -ef|grep redis
# 7. 关闭Redis服务
shutdown
exit
# 8.再次查看进程
ps -ef|grep redis
3. 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 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
9 | -P | 通过管道传输 请求 | 1 |
10 | -q | 强制退出 redis。仅显示 query/sec 值 | |
11 | –csv | 以 CSV 格式输出 | |
12 | *-l*(L 的小写字母) | 生成循环,永久执行测试 | |
13 | *-l*(L 的小写字母) | 生成循环,永久执行测试 | |
14 | *-I*(i 的大写字母) | Idle 模式。仅打开 N 个 idle 连接并等待。 |
4.Redis基础配置
- 数据库
databases 16,默认16【0-15】个数据库,使用是第0个,可以使用select 3进行切换
127.0.0.1:6379> flushdb # 清空当前数据库
127.0.0.1:6379> flushall #清空所有数据库的键值对。
127.0.0.1:6379> config get databases # 命令行查看数据库数量databases
1) "databases"
2) "16"
127.0.0.1:6379> select 8 # 切换数据库 DB 8
OK
127.0.0.1:6379[8]> dbsize # 查看数据库大小
(integer) 0
# 不同数据库之间 数据是不能互通的,并且dbsize 是根据库中key的个数。
127.0.0.1:6379> set name sakura
OK
127.0.0.1:6379> SELECT 8
OK
127.0.0.1:6379[8]> get name # db8中并不能获取db0中的键值对。
(nil)
127.0.0.1:6379[8]> DBSIZE
(integer) 0
127.0.0.1:6379[8]> SELECT 0
OK
127.0.0.1:6379> keys * #查看数据库所有key
1) "counter:__rand_int__"
2) "mylist"
3) "name"
4) "key:__rand_int__"
5) "myset:__rand_int__"
127.0.0.1:6379> DBSIZE # size和key个数相关
(integer) 5
127.0.0.1:6379> CONFIG GET * # 获取所有配置项
- 单线程
- 基于内存和网络带宽,cpu不是性能瓶颈,使用单线程操作效率是最高的
- 基于C语言,每秒查询【QPS】达到10W+
序号 | 参数 | 默认 | 功能 |
---|---|---|---|
1 | daemonize | no | Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程 |
2 | pidfile | /var/run/redis.pid | 后台运行或当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定 |
3 | port | 6379 | 指定Redis监听端口,默认端口为6379,6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字 |
4 | bind | 127.0.0.1 | 绑定的主机地址 |
5 | timeout | 300 | 当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能 |
6 | loglevel | verbose | 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning |
7 | logfile | stdout | 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null |
8 | databases | 16 | 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid> 命令在连接上指定数据库id |
9 | save<seconds> <changes> | save 900 1 save 300 10 save 60 10000 | 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合,表示900秒(15分钟)内有1个更改 |
10 | rdbcompression | yes | 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大 |
11 | dbfilename | dump.rdb | 指定本地数据库文件名 |
12 | dir | ./ | 指定本地数据库存放目录 |
13 | slaveof <masterip> <masterport> | 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步 | |
14 | masterauth <mater-password> | 当master服务设置了密码保护时,slav服务连接master的密码 | |
15 | requirepass | foobared | 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password> 命令提供密码,默认关闭 |
16 | maxclients | 128 | 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息 |
17 | maxmemory <bytes> | 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区 | |
18 | appendonly | no | 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。 |
19 | appendfilename | appendonly.aof | 指定更新日志文件名 |
20 | appendfsync | everysec | 指定更新日志条件,共有3个可选值: no:表示等操作系统进行数据缓存同步到磁盘(快) always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) everysec:表示每秒同步一次(折衷,默认值) |
21 | vm-enabled | no | 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中 |
22 | vm-swap-file | /tmp/redis.swap | 虚拟内存文件路径,不可多个Redis实例共享 |
23 | vm-max-memory | 0 | 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘 |
24 | vm-page-size | 32 | edis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值 |
25 | vm-pages | 134217728 | 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。 |
26 | vm-max-threads | 4 | 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟 |
27 | glueoutputbuf | yes | 设置在向客户端应答时,是否把较小的包合并为一个包发送 |
28 | hash-max-zipmap-entries 64 hash-max-zipmap-value 512 | 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法 | |
29 | activerehashing | yes | 指定是否激活重置哈希 |
30 | include | /path/to/local.conf | 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件 |
31 | protected-model | yes | 保护模式 |
5. 数据类型
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
5.1 基本连接命令
命令 | 描述 |
---|---|
Redis Echo 命令 | 打印字符串 |
Redis Select 命令 | 切换到指定的数据库 |
Redis Ping 命令 | 查看服务是否运行 |
Redis Quit 命令 | 关闭当前连接 |
Redis Auth 命令 | 验证密码是否正确 |
127.0.0.1:6379> exists name # 判断是否存在key为name
127.0.0.1:6379> move name 1 # 移动key=name到1号数据库
127.0.0.1:6379> type name # 查看数据类型
5.2 Redis-key
命令 | 描述 |
---|---|
Redis Type 命令 | 返回 key 所储存的值的类型。 |
Redis PEXPIREAT 命令 | 设置 key 的过期时间亿以毫秒计。 |
Redis PEXPIREAT 命令 | 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计 |
Redis Rename 命令 | 修改 key 的名称 |
Redis PERSIST 命令 | 移除 key 的过期时间,key 将持久保持。 |
Redis Move 命令 | 将当前数据库的 key 移动到给定的数据库 db 当中。 |
Redis RANDOMKEY 命令 | 从当前数据库中随机返回一个 key 。 |
Redis Dump 命令 | 序列化给定 key ,并返回被序列化的值。 |
Redis TTL 命令 | 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 |
Redis Expire 命令 | seconds 为给定 key 设置过期时间。 |
Redis DEL 命令 | 该命令用于在 key 存在是删除 key。 |
Redis Pttl 命令 | 以毫秒为单位返回 key 的剩余的过期时间。 |
Redis Renamenx 命令 | 仅当 newkey 不存在时,将 key 改名为 newkey 。 |
Redis EXISTS 命令 | 检查给定 key 是否存在。 |
Redis Expireat 命令 | EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 |
Redis Keys 命令 | 查找所有符合给定模式( pattern)的 key 。 |
127.0.0.1:6379> set name "zz" # 设置key-value或修改值
127.0.0.1:6379> expire name 10 # 设置key过期时间为10s
127.0.0.1:6379> del key # 删除键值对
127.0.0.1:6379> Renamenx a a1 # 修改key
127.0.0.1:6379> set name "zz" # 设置key-value或修改值
127.0.0.1:6379> get name # 获取key为name的值
127.0.0.1:6379> ttl name # 当前key没有设置过期时间,所以会返回-1.当前key有设置过期时间,而且key已经过期,所以会返回-2.当前key有设置过期时间,且key还没有过期,故会返回key的正常剩余时间.
5.3 String
命令 | 描述 |
---|---|
Redis Setnx 命令 | 只有在 key 不存在时设置 key 的值。 |
Redis Getrange 命令 | 返回 key 中字符串值的子字符 |
Redis Mset 命令 | 同时设置一个或多个 key-value 对。 |
Redis Setex 命令 | 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
Redis SET 命令 | 设置指定 key 的值 |
Redis Get 命令 | 获取指定 key 的值。 |
Redis Getbit 命令 | 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 |
Redis Setbit 命令 | 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 |
Redis Decr 命令 | 将 key 中储存的数字值减一。 |
Redis Decrby 命令 | key 所储存的值减去给定的减量值(decrement) 。 |
Redis Strlen 命令 | 返回 key 所储存的字符串值的长度。 |
Redis Msetnx 命令 | 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 |
Redis Incrby 命令 | 将 key 所储存的值加上给定的增量值(increment) 。 |
Redis Incrbyfloat 命令 | 将 key 所储存的值加上给定的浮点增量值(increment) 。 |
Redis Setrange 命令 | 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 |
Redis Psetex 命令 | 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。 |
Redis Append 命令 | 如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。 |
Redis Getset 命令 | 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
Redis Mget 命令 | 获取所有(一个或多个)给定 key 的值。 |
Redis Incr 命令 | 将 key 中储存的数字值增一。 |
127.0.0.1:6379> APPEND name1 "ll" # 追加,不存在key则创建
127.0.0.1:6379> set views 0
127.0.0.1:6379> msetnx k1 v1 k2 v2 # 批量设置key和值
127.0.0.1:6379> msetnx k1 v1 k2 v2 # 批量设置key和值,有原子性
127.0.0.1:6379> incr views # 自增1
127.0.0.1:6379> decr views # 自减1
127.0.0.1:6379> INCRBY age 5 # 加步长5
127.0.0.1:6379> DECRBY age 10 # 减步长10
127.0.0.1:6379> del key # 删除键值对
127.0.0.1:6379> setrange name1 2 ii # 替换字符串(2,替换字符串的长度)
127.0.0.1:6379> setex name 20 "ll" # set 键值对并设置过期时间(set with expire)
127.0.0.1:6379> setnx name1 "kk" #仅当key不存在时进行set(set if not exist),分布式锁中会经常使用
127.0.0.1:6379> substr name1 0 2 # 截取字符串[0,2]不赋值
127.0.0.1:6379> getrange name1 0 2 # 截取字符串数组[0,2]闭区间,[0,-1]代表全部
127.0.0.1:6379> STRLEN name1 # 获取key为name1值的长度
127.0.0.1:6379> mget k1 k2 # 批量获取key的值
127.0.0.1:6379> getset k1 1 # 先get后set key的值
5.4 List
实际上是一个链表,before Node after,left,right都可以插入,可以做消息队列(先进先出 lpush rpop),栈(先进后出 lpush lpop),一个列表最多可以包含2^ 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
命令 | 描述 |
---|---|
Redis Lindex 命令 | 通过索引获取列表中的元素 |
Redis Rpush 命令 | 在列表中添加一个或多个值 |
Redis Lrange 命令 | 获取列表指定范围内的元素 |
Redis Rpoplpush 命令 | 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
Redis Blpop 命令 | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
Redis Brpop 命令 | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
Redis Brpoplpush 命令 | 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
Redis Lrem 命令 | 移除列表元素 |
Redis Llen 命令 | 获取列表长度 |
Redis Ltrim 命令 | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
Redis Lpop 命令 | 移出并获取列表的第一个元素 |
Redis Lpushx 命令 | 将一个或多个值插入到已存在的列表头部 |
Redis Linsert 命令 | 在列表的元素前或者后插入元素 |
Redis Rpop 命令 | 移除并获取列表最后一个元素 |
Redis Lset 命令 | 通过索引设置列表元素的值 |
Redis Lpush 命令 | 将一个或多个值插入到列表头部 |
Redis Rpushx 命令 | 为已存在的列表添加值 |
127.0.0.1:6379> lpush list 1 2 3 4 # 从左边放入list一个或多个值(和栈相似先进后出)
127.0.0.1:6379> Rpush list 0 # 从右边插入(头部)
127.0.0.1:6379> lpop list # 从左边移除第一个元素并返回
127.0.0.1:6379> rpop list # 从右边移除第一个元素并返回
127.0.0.1:6379> lrem list 1 2 # 移除list中值为2,数量1个
127.0.0.1:6379> ltrim list 1 3 # 通过下标截取指定长度并加入list
127.0.0.1:6379> rpoplpush list list1 # list右边最后一个元素放入list1中
127.0.0.1:6379> lrange list1 0 -1 # 改变lsit1中第0个元素,注意序号0不能为空
127.0.0.1:6379> linsert list1 before 3 x # 将x插入list1中第一个值为3的前面
127.0.0.1:6379> linsert list1 after 3 y # 将y插入list1中第一个值为3的后面
127.0.0.1:6379> lrange list 0 3 # 展示数据[0,3]
127.0.0.1:6379> lindex list 1 # 索引获取List列表
127.0.0.1:6379> llen list # 返回list的长度
5.5 set
不重复的集子,Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 2^232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
命令 | 描述 |
---|---|
Redis Sunion 命令 | 返回所有给定集合的并集 |
Redis Scard 命令 | 获取集合的成员数 |
Redis Srandmember 命令 | 返回集合中一个或多个随机数 |
Redis Smembers 命令 | 返回集合中的所有成员 |
Redis Sinter 命令 | 返回给定所有集合的交集 |
Redis Srem 命令 | 移除集合中一个或多个成员 |
Redis Smove 命令 | 将 member 元素从 source 集合移动到 destination 集合 |
Redis Sadd 命令 | 向集合添加一个或多个成员 |
Redis Sismember 命令 | 判断 member 元素是否是集合 key 的成员 |
Redis Sdiffstore 命令 | 返回给定所有集合的差集并存储在 destination 中 |
Redis Sdiff 命令 | 返回给定所有集合的差集 |
Redis Sscan 命令 | 迭代集合中的元素 |
Redis Sinterstore 命令 | 返回给定所有集合的交集并存储在 destination 中 |
Redis Sunionstore 命令 | 所有给定集合的并集存储在 destination 集合中 |
Redis Spop 命令 | 移除并返回集合中的一个随机元素 |
127.0.0.1:6379> sadd myset 1 2 3 4 1 # 增加myset集子,1重复只会有1个
127.0.0.1:6379> srem myset 1 # 移除myset集合值为1的成员
127.0.0.1:6379> spop myset # 随机删除myset集子中的一个成员
127.0.0.1:6379> smove myset myset1 7 # 将myset集子值为7移动到myset1中
127.0.0.1:6379> smembers myset # 查看myset所有值
127.0.0.1:6379> Scard myset # 获取myset中的成员数
127.0.0.1:6379> Sinter myset myset1 # 获取myset集合和myset1的交集
127.0.0.1:6379> Sunion myset myset1 # 返回myset集合和myset1的并集
127.0.0.1:6379> sdiff myset myset1 # myset和myset1差集
127.0.0.1:6379> sismember myset 2 # 判断myset成员是否存在2的值成员
127.0.0.1:6379> srandmember myset 2 # 返回myset集合两个随机成员
5.6 Hash
Hash是一个string类型的field和value的映射表,Map,相当于key -map,Hash更适合于对象的存储,Sring更加适合字符串
命令 | 描述 |
---|---|
Redis Hmset 命令 | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
Redis Hmget 命令 | 获取所有给定字段的值 |
Redis Hset 命令 | 将哈希表 key 中的字段 field 的值设为 value 。 |
Redis Hgetall 命令 | 获取在哈希表中指定 key 的所有字段和值 |
Redis Hget 命令 | 获取存储在哈希表中指定字段的值/td> |
Redis Hexists 命令 | 查看哈希表 key 中,指定的字段是否存在。 |
Redis Hincrby 命令 | 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
Redis Hlen 命令 | 获取哈希表中字段的数量 |
Redis Hdel 命令 | 删除一个或多个哈希表字段 |
Redis Hvals 命令 | 获取哈希表中所有值 |
Redis Hincrbyfloat 命令 | 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
Redis Hkeys 命令 | 获取所有哈希表中的字段 |
Redis Hsetnx 命令 | 只有在字段 field 不存在时,设置哈希表字段的值。 |
127.0.0.1:6379> hset myhash f1 v1 # 设置myhash key-value
127.0.0.1:6379> hset myhash f1 v1 f2 v2 # 批量设置myhash key-value
127.0.0.1:6379> hdel myhash f1 # 删除指定的hash
127.0.0.1:6379> Hincrby myhash f4 2 # 值+2
127.0.0.1:6379> Hincrbyfloat myhash f4 0.1 # 值+0.1
127.0.0.1:6379> hget myhash f1 # 获取键值为f1的值
127.0.0.1:6379> hmget myhash f1 f2 # 获取键值为f1 f2的值
127.0.0.1:6379> hgetall myhash #获取所有的key和value值
127.0.0.1:6379> hexists myhash f1 # 判断myhash是否存在f1键
127.0.0.1:6379> hlen myhash # 获取myhash的长度
127.0.0.1:6379> hvals myhash # 获取hash的所有值
127.0.0.1:6379> hkeys myhash # 获取hash的所有键
127.0.0.1:6379> hsetnx myhash f3 v3 # 只有在字段 field 不存在时,设置哈希表字段的值。
5.7 Zset
在set基础上增加了排序,不同的是每个元素都会关联一个double类型的分数score,score相同:按字典顺序排序,有序集合的成员是唯一的,但分数(score)却可以重复。redis正是通过分数来为集合中的成员进行从小到大的排序。
命令 | 描述 |
---|---|
Redis Zrevrank 命令 | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
Redis Zlexcount 命令 | 在有序集合中计算指定字典区间内成员数量 |
Redis Zunionstore 命令 | 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
Redis Zremrangebyrank 命令 | 移除有序集合中给定的排名区间的所有成员 |
Redis Zcard 命令 | 获取有序集合的成员数 |
Redis Zrem 命令 | 移除有序集合中的一个或多个成员 |
Redis Zinterstore 命令 | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
Redis Zrank 命令 | 返回有序集合中指定成员的索引 |
Redis Zincrby 命令 | 有序集合中对指定成员的分数加上增量 increment |
Redis Zrangebyscore 命令 | 通过分数返回有序集合指定区间内的成员 |
Redis Zrangebylex 命令 | 通过字典区间返回有序集合的成员 |
Redis Zscore 命令 | 返回有序集中,成员的分数值 |
Redis Zremrangebyscore 命令 | 移除有序集合中给定的分数区间的所有成员 |
Redis Zscan 命令 | 迭代有序集合中的元素(包括元素成员和元素分值) |
Redis Zrevrangebyscore 命令 | 返回有序集中指定分数区间内的成员,分数从高到低排序 |
Redis Zremrangebylex 命令 | 移除有序集合中给定的字典区间的所有成员 |
Redis Zrevrange 命令 | 返回有序集中指定区间内的成员,通过索引,分数从高到底 |
Redis Zrange 命令 | 通过索引区间返回有序集合成指定区间内的成员 |
Redis Zcount 命令 | 计算在有序集合中指定区间分数的成员数 |
Redis Zadd 命令 | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
127.0.0.1:6379> zadd myset 1 one 2 two # 增加两个值,前面的数字代表排序分数
127.0.0.1:6379> zrem myset 2 # 移除myset值为2的成员
127.0.0.1:6379> zrange myset 0 -1 # 查询myset集合所有元素
127.0.0.1:6379> zrevrange myset 0 -1 # 从大到小查询
127.0.0.1:6379> zrangebyscore myset -inf +inf withscores # 排序最小到最大并输出分数,-inf这里只能放最小值
127.0.0.1:6379> zcard myset # 获取有序集子的个数
127.0.0.1:6379> zcount myset 1 3 # 获取指定成员的数量
5.8 Geospatial(地理位置)
实现原理是zset,可以使用zset命令操作geo,有效的经度从-180度到180度,有效的纬度从-85.05112878度到85.05112878度。当坐标位置超出上述指定范围时,该命令将会返回一个错误。
命令 | 描述 |
---|---|
Redis GEOHASH 命令 | 返回一个或多个位置元素的 Geohash 表示 |
Redis GEOPOS 命令 | 从key里返回所有给定位置元素的位置(经度和纬度) |
Redis GEODIST 命令 | 返回两个给定位置之间的距离 |
Redis GEORADIUS 命令 | 以给定的经纬度为中心, 找出某一半径内的元素 |
Redis GEOADD 命令 | 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中 |
Redis GEORADIUSBYMEMBER 命令 | 找出位于指定范围内的元素,中心点是由给定的位置元素决定 |
127.0.0.1:6379> GEOADD china:city 16.32 30.33 beijing # 增加城市纬度、经度、名称
127.0.0.1:6379> getpos china:city beijing chongqin # 获取指定城市
127.0.0.1:6379> GEODIST china:city beijing shanghai # 获取指定城市的距离
127.0.0.1:6379> GEORADIUS china:city 110 30 1000km withdist withcoord count 3 #以指定经纬度和1000km为半径找到城市,距离,经纬度,限制多少个
127.0.0.1:6379:> GEORADIUSBYMEMBER china:city shanghai 400 km # 找出位于指定城市周围的城市
127.0.0.1:6379:> geohash china:city shanghai chongqin # 将二维经纬度转换为11个字符Geohash字符串
5.9 Hyperloglog(基数统计)
数据集中不重复的元素的个数,应用场景:网页的访问量(UV),如果允许容错0.81%,那么一定可以使用Hyperloglog
命令 | 描述 |
---|---|
Redis Pfmerge 命令 | 将多个 HyperLogLog 合并为一个 HyperLogLog |
Redis Pfadd 命令 | 添加指定元素到 HyperLogLog 中。 |
Redis Pfcount 命令 | 返回给定 HyperLogLog 的基数估算值。 |
127.0.0.1:6379> pfadd mykey a b c d e f g #增加
127.0.0.1:6379> pfcount mykey # 基数数量
127.0.0.1:6379> pfmerge mykey2 mykey1 mykey # 合并基数文件
5.10 BitMaps(位图)
用位存储,信息状态只有 0 和 1
命令 | 描述 |
---|---|
Redis Setbit 命令 | 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 |
Redis Getbit 命令 | 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 |
127.0.0.1:6379> setbit sign 0 1 # 设置sign的第0位为 1
127.0.0.1:6379> getbit sign 0 # 获取第0位的数值
127.0.0.1:6379> BITCOUNT sign # 统计sign中为1的位数
5.11 服务器命令
5.12 Redis脚本命令
命令 | 描述 |
---|---|
Redis Script kill 命令 | 杀死当前正在运行的 Lua 脚本。 |
Redis Script Load 命令 | 将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。 |
Redis Eval 命令 | 执行 Lua 脚本。 |
Redis Evalsha 命令 | 执行 Lua 脚本。 |
Redis Script Exists 命令 | 查看指定的脚本是否已经被保存在缓存当中。 |
Redis Script Flush 命令 | 从脚本缓存中移除所有脚本。 |
5.13 Redis 发布订阅命令
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
命令 | 描述 |
---|---|
Redis Unsubscribe 命令 | 指退订给定的频道。 |
Redis Subscribe 命令 | 订阅给定的一个或多个频道的信息。 |
Redis Pubsub 命令 | 查看订阅与发布系统状态。 |
Redis Punsubscribe 命令 | 退订所有给定模式的频道。 |
Redis Publish 命令 | 将信息发送到指定的频道。 |
Redis Psubscribe 命令 | 订阅一个或多个符合给定模式的频道。 |
------------订阅端----------------------
# 订阅sakura频道
127.0.0.1:6379> SUBSCRIBE xxy
--------------消息发布端-------------------
# 发布消息到
127.0.0.1:6379> PUBLISH xxy "hello world"
-----------------查看活跃的频道------------
127.0.0.1:6379> PUBSUB channels
1) "sakura"
- 原理
个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。
6. 事务
redis单条命令具有原子性,redis多条命令(事务)不具有原子性,Redis事务没有隔离级别的概念,本质是一组命令被序列化,按照顺序执行
命令 | 描述 |
---|---|
Redis Exec 命令 | 执行所有事务块内的命令。 |
Redis Watch 命令 | 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |
Redis Discard 命令 | 取消事务,放弃执行事务块内的所有命令。 |
Redis Unwatch 命令 | 取消 WATCH 命令对所有 key 的监视。 |
Redis Multi 命令 | 标记一个事务块的开始。- |
- 正常
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set ke v1
QUEUED
127.0.0.1:6379> lpush l1 v2
QUEUED
127.0.0.1:6379> hset h h1 v1
QUEUED
127.0.0.1:6379> sadd s 12 3
QUEUED
127.0.0.1:6379> zadd s1 1 f
QUEUED
127.0.0.1:6379> exec
1) OK
2) (integer) 1
3) (integer) 1
4) (integer) 2
5) (integer) 1
- 丢弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set j 1
QUEUED
127.0.0.1:6379> discard # 丢弃事务
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
127.0.0.1:6379> get j
(nil)
- 编程式异常
代码有问题,事务所有命令不会执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set ke v1
QUEUED
127.0.0.1:6379> lpush l1 v2
QUEUED
127.0.0.1:6379> hset h1 v3
(error) ERR wrong number of arguments for 'hset' command
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
- 运行时异常
语法问题抛出异常,1/0,其他命令可以正常执行
127.0.0.1:6379> set k1 v1
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> get k2
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) "v2"
- 监控watch
每次提交执行exec后都会自动释放锁,不管是否成功
序号 | 锁 | 解释 |
---|---|---|
1 | 悲观锁 | 无论做什么都会加锁 |
2 | 乐观锁 | 认为什么时候都不会出现问题,所以不会上锁!获取version,更新的时候比较version |
- 正常
127.0.0.1:6379> set k1 100
OK
127.0.0.1:6379> set k2 0
OK
127.0.0.1:6379> watch k1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby k1 10
QUEUED
127.0.0.1:6379> incrby k2 10
QUEUED
127.0.0.1:6379> exec
1) (integer) 90
2) (integer) 10
- 多个连接进行修改
解决办法:unwatch解锁获取最新值,然后再加锁进行事务。
127.0.0.1:6379> watch money # money上锁
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY use 20
QUEUED
127.0.0.1:6379> # 此时事务并没有执行
127.0.0.1:6379> INCRBY money 500 # 修改了线程一中监视的money
(integer) 600
127.0.0.1:6379> EXEC # 执行之前,另一个线程修改了我们的值,这个时候就会导致事务执行失败
(nil) # 没有结果,说明事务执行失败
127.0.0.1:6379> get money # 线程2 修改生效
"600"
127.0.0.1:6379> get use # 线程1事务执行失败,数值没有被修改
"0"
7. 实例
7.1 Jedis
SpringBoot 一般已经不使用该技术,因为线程不安全,一般使用lettuce,jedis所有方法和cli操作的命令相同熟悉即可
<!--fastjson1.2.61以下版本有漏洞-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.14</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
@Test
void testJedis() {
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.auth("123456");
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","xxy");
jsonObject.put("name","k");
String s = jsonObject.toJSONString();
Transaction multi = jedis.multi();
try {
multi.set("u1",s);
multi.set("u2",s);
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
} finally {
System.out.println(jedis.get("u1"));
System.out.println(jedis.get("u2"));
jedis.close();
}
}
7.2 SpringBoot和Redis
- 概述
- SpringBoot操作数据: Spring-data jpa jdbc mongdb redis
- springboot 2.x后 ,原来使用的 Jedis 被 lettuce 替换。
//LettuceConnectionFactory
public class LettuceConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory {
}
//JedisConnectionFactory
public class JedisConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory {
jedis:采用的直连,多个线程操作的话,是不安全的。如果要避免不安全,使用jedis pool连接池!更像BIO模式
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式
- Redis在SpringBoot中的自动配置
- 找到org.springframework.boot.autoconfigure包下的spring-boot-autoconfigure-2.5.0.jar!\META-INF\spring.factories,搜索redis
- 存在一个xxxAutoConfiguration和RedisProperties
- 若要修改配置查看RedisProperties
@ConfigurationProperties(
prefix = "spring.redis"
)
spring.redis.port=3967
- 测试
- 导入依赖
<!--操作redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置
spring.redis.host=127.0.0.1
spring.redis.password=root
- 自定义序列化模板
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);
objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(stringRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
- 编写代码
@Test
void testSpringBootRedis() {
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
dog.setDogName("皇子");
dog.setAge(3);
JSONObject jsonObject = new JSONObject();
try {
String s = jsonObject.toJSONString(dog);
redisTemplate.opsForValue().set("js2",s);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(redisTemplate.opsForValue().get("js2"));
}
7.3 封装lettuce命令
package com.xxy.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工具类*/
public class RedisUtil {
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);
}
}
8. Redis.config
8.1 前述
- units单位
不区分大小写
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
- INCLUDES包含
相当于导入其他文件import
# include .\path\to\local.conf
# include c:\path\to\other.conf
- GENERAL通用跳到基础配置表格
[表格]:
- SNAPSHOTTING快照
持久化,redis是内存数据库,数据容易断电就失
# 多少秒进行了多少操作,就持久化到文件.rdb.aof
save 900 1
save 300 10
save 60 10000
# 持久化如果出错,是否继续工作
stop-writes-on-bgsave-error yes
# 是否压缩rdb文件
rdbcompression yes
# 校验rdb文件
rdbchecksum yes
# rdb文件保存目录
dir ./
-
REPLICATION 主从复制
-
SECURITY安全
requirepass root
- LIMITS限制
maxclients 10000
maxmemory <bytes>
# 内存达到上限后的处理策略
maxmemory-policy noeviction
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 模式 aof配置(持久化)
appendonly no #默认是不开启aof模式,默认使用rdb方式
appendfilename "appendonly.aof" #持久化文件名
# appendfsync always #每次修改都会sync同步,消耗性能
appendfsync everysec #每秒执行一次sync,可能会丢失ls的数据
# appendfsync no #不执行sync,操作系统自己同步数据,速度最快
8.2 持久化RDB
- RDB(Redis DataBase)
在指定的时间间隔内将内存中的数据集快照写入磁盘,就是Snapshot快照,它恢复时是将快照文件直接读到内存里,默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件中进行自定义。
- RDB工作原理
-
Redis 调用forks。同时拥有父进程和子进程。在进行
RDB
的时候,redis
的主线程是不会做io
操作的,主线程会fork
一个子线程来完成该操作; -
子进程将数据集写入到一个临时 RDB 文件中。
-
当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
- 触发条件
- save的规则满足的情况下,会自动触发rdb规则,但是会阻塞,不接受其他操作
- 执行flushall命令,也会触发我们的rdb规则
- 退出redis,也会产生dump.rdb文件
- 恢复rdb文件
- 只需要将rdb文件放到我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复数据
# redis启动目录
config get dir
- 优缺点
序号 | 优点 | 缺点 |
---|---|---|
1 | 适合大规模数据恢复 | 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了 |
2 | 对数据的完整性要求不高 | fork进程的时候,会占用一定的内容空间 |
8.3 持久化AOF
- AOF(Append Only File)【仅仅追加命令模式】
记录命令history,以日志的形式来记录每个写的操作,需要恢复时追加命令但不可以改写文件,然后执行,默认文件无限追加,文件超过配置文件里设置大小,会重写
- 开启步骤
- 修改配置文件
appendonly yes
- 重启Redis
# 查看进程
ps -ef|grep redis
- aof文件出现错误
redis-check-aof --fix
- 优缺点
序号 | 优点 | 缺点 |
---|---|---|
1 | 每一次修改都会同步,文件的完整性会更加好 | 如果每秒同步一次,可能会丢失一秒的数据,如果从不同步,效率最高 |
2 | 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢! | |
3 | Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化 |
8.4 RDB和AOF对比
公司一般混合使用RDB和AOF
名称 | RDB | AOF |
---|---|---|
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 丢数据 | 根据策略决定 |
9. Redis主从复制(集群)
- 定义
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。至少一主二从,主机只能写,从机只能读
- 集群解决问题
- 单台服务器难以负载大量的请求
- 单台服务器故障率高,系统崩坏概率大
- 单台服务器内存容量有限
- 集群作用
- 数据冗余
- 故障恢复
- 负载均衡:在主从复制的基础上,配合读写分离
- 高可用基石:主从复制还是哨兵和集群能够实施的基础
- 环境配置
- 查看当前redis信息
# 查看当前库的信息
127.0.0.1:6379> info replication
-
复制配置文件,修改对应信息
1、端口
2、pid名字
3、log文件名字
4、dump.rdb名字
-
配置一主二从,默认每台Redis服务器都是主节点
有密码的去从机的配置文件加上masterauth 密码,永久配置需要到配置文件中配置,命令配置是暂时的,命令配置重启后会变回主机
- 命令
# 配置主机号和端口,把自己作为从机
SLAVEOF 127.0.0.1 6379
# 查看当前库的信息
127.0.0.1:6379> info replication
- 配置文件
# 配置主机号和端口,把自己作为从机
slaveof <masterip> <masterport>
# 主机密码
masterauth <master-password>
-
测试结果分析
-
主机断开连接,从机返回的是空
-
配置后,master将传送整个数据文件到slave ,并完成一次完全同步。
- 全量复制:
slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
- 增量复制:
:Master继续将新的所有收集到的修改命令依次传给slave,完成同步但是只要是重新连接master,一次完全同步(全量复制)将被自动执行!我们的数据一定可以在从机中看到
- 当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
- 两种方式可以产生新的主机:
- 从机手动执行命令
slaveof no one
,这样执行以后从机会独立出来成为一个主机 - 使用哨兵模式(自动选举)
10. 哨兵模式
10.1 定义
当主服务器宕机后自动选举主机,原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例
10.2 作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
10.3 测试
- 配置哨兵配置文件sentinel.conf
# sentinel monitor 被监控的名称 host port 主机没了投票数
sentinel monitor myredis 127.0.0.1 6379 1
- 启动哨兵
[root@localhost bin]# redis-sentinel sentinel.conf
- 断开主机,发现127.0.0.1 6379当了主机了,果主机此时回来了,只能归并到新的主机下,当做从机
10.4 优缺点
序号 | 优点 | 缺点 |
---|---|---|
1 | 哨兵集群,基于主从复制模式,所有主从配置优点,它都有 | Redis不好在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦 |
2 | 主从可以切换,故障可以转移,系统的可用性会更好 | 实现哨兵模式的配置其实是很麻烦的,里面有很多选择! |
3 | 哨兵模式就是主从复制模式的升级,手动到自动,更加健壮! | 主机回来,只能归并到新的主机下,当做从机 |
10.5 配置文件
# 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
11. Redis缓存穿透和雪崩
11.1 缓存穿透(缓存查不到)
- 定义
在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。
- 解决方案
- 布隆过滤器
对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力
- 缓存空对象
一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。缺点:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间,对于保持一致性业务会有影响
11.2 缓存击穿(量大,缓存过期)
- 定义
相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。
- 解决方案
- 设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
- 加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
11.3 缓存雪崩
- 定义
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
- 解决方案
- redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
- 限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
- 数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
12. Redis和数据库一致性
- 先更新数据库,再更新redis(弃)
如果先更新数据库成功,接着更新redis失败,那么会造成数据不一致,所以这种方法舍弃
- 先更新redis,在更新数据库(弃)
如果更新reids成功,更新数据库失败,那么同样会造成数据不一致
- 先更新数据库,再删除redis
这种方案,同样会造成数据不一致的问题,但是相比上两个方案,如果他设置key的过期时间,那么保证了数据的最终一致性。如果在更新数据库后删除redis失败,又未设置redis过期时间。那么会造成数据不一致。
如果线程A更新数据库,正准备更新redis时。线程B在更新线程A更新redis前获取了redis中的数据,那么其他数据拿到的数据还是旧数据,如果删除redis失败也会造成数据不一致
- 解决方案:
-
mysql和redis设置事务,在发生异常时回滚数据
-
redis设置重试机制,在删除失败后进入重试模式
-
先删除redis,再更新数据库()
如果线程A删除了redis,正准备更新数据库。线程B查询了redis没有之后,查询了数据库的旧数据,并且把它写到redis。之后线程A才更新数据成功,会出现数据库和redis的数据不一致
- 延迟双删
线程A在删除redis以及更新数据库后,睡眠一段时间后,再次删除reids中的数据。这个睡眠时间得大于一次查询的时间。
- (延迟双删)先删除 Redis,再写 MySQL,再删除 Redis(优)
- 先写 MySQL,通过 Binlog,异步更新 Redis
这个方案,比如 binlog + kafka会保证 MySQL 和 Redis 的最终一致性,但是如果中途请求 B 需要查询数据,如果缓存无数据,就直接查 DB;如果缓存有数据,查询的数据也会存在不一致的情况。所以这个方案,是实现最终一致性的终极解决方案,但是不能保证实时性。
- 个人建议
-
实时一致性方案:采用“先写 MySQL,再删除 Redis”的策略,这种情况虽然也会存在两者不一致,但是需要满足的条件有点苛刻,所以是满足实时性条件下,能尽量满足一致性的最优解。
-
最终一致性方案:采用“先写 MySQL,通过 Binlog,异步更新 Redis”,可以通过 Binlog,结合消息队列异步更新 Redis,是最终一致性的最优解。
13. 总结
本次学习了Redis,对Redis有了基础的了解,会不断进行学习