一.概括
1.Redis是什么?
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
2.Redis能干什么
- 内存储存、持久化、内存中的数据是断电及失、所以说持久化很重要!(rdb、aof)
- 效率高、可以用于告诉缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器、(数据浏览量)
3.Redis特性
- 持久化
-
多样化数据库
-
集群
-
事务
4.Redis官网
二.安装Redis
1.Windows安装
下载地址:Release 3.2.100 · microsoftarchive/redis (github.com)
下载后直接解压获得
双击 redis-server.exe
启动Redis服务器
双击redis-cli.exe运行redis客户端
这是我们就可以使用但是我们一般在Linu上使用
2.Linux安装
- 我们去官网下载安装包
- 上传到opt目录下
- 输入tar -zxvf 安装包 解压安装包
- 进入解压出来的目录
- 环境安装
yum -y install gcc-c++ 学习Linux时已安装过 gcc -v 查看信息 make 配置全部环境 make install
- redis的默认安装路径在 /usr/local/bin
- 在/usr/local/bin下新建我们自己的目录 mkdir jconfig
- cp /opt/redis-6.0.6/redis.conf jconfig 把redis.config移动我们新建的包下
- redis默认不是后台启动的,需要我们去修改配置文件 vim redis.conf
daemonize原本是no,我们改为yes就行
-
redis-server jconfig/redis.conf 启动redis
-
redis-cli -p 6379 进行连接测试
-
ping 查看是否启动成功,返回pong就是启动成功,就可以使用了
-
shutdown 关闭redis
-
exit 退出redis
三.性能测试
1.*redis-benchmark是一个官方自带的性能测试工具
redis-benchmark测试工具可选参数如下·:
2.测试
# 测试:100个并发连接 100000请求 需要在/usr/local/bin 下执行
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
四.基础知识
1.redis有16个数据库分别是DB0~DB15,默认使用的是DB0,
- select 数字 切换到数字的数据库
- dbsize 可以查看当前数据库的大小(大小取决于有多少个key)
- keys * 查看所有的key
- flushdb 清空当前数据库中的内容。
- flushall 清空所有数据库中的内容。
2.redis为什么是单线程的?
redis是基于内存的操作,CPU不是redis的性能瓶颈而是机器内存和网络带宽,所以单线程可以满足redis的的需求就是用了单线程。
3.redis是单线程为什么还这么快?
误区1:高性能的服务器一定是多线程的?
误区2:多线程(CPU上下文会切换!)一定比单线程效率高!
Redis是将所有的数据放在内存中的,所以说使用单线程去操作效率是最高的,多线程(CPU上下文会切换:耗时的操作!),对于内存系统来说如果没有上下文切换效率是最高的,多次读写都是在一个CPU上在内存存储数据情况下单线程就是最佳的方案。
五.五大基本数据类型
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
Redis-key:在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作。
- exists key 判断键是否存在 返回1表示存在返回0表示不存在
- del key 删除键值对
- move key db 将键值对移动到指定数据库
- pepire key second 设置键值对的过期时间
- ttl key 查看剩余过期时间 如果存在没设置过期时间返回-1,设置过期时间 已过期或者这个数据不存在会返回-2
- type key 查看value的数据类型
1.String (字符串)
命令 | 描述 | 示例 |
---|---|---|
APPEND key value | 向指定的key的value后追加字符串 | 127.0.0.1:6379> set name jiu OK 127.0.0.1:6379> append name qi (integer) 5 127.0.0.1:6379> get name "jiuqi” |
INCR/DECR key | 将指定key的value数值进行+1/-1(仅对于数字) | 127.0.0.1:6379> set age 3 127.0.0.1:6379> incr age "4" 127.0.0.1:6379> decr age "3" |
INCRBY/DECRBY key n | 按指定的步长对数值进行加减 | 127.0.0.1:6379> incrby age 5 "8" 127.0.0.1:6379> decrby age 3 "5" |
INCRBYFLOAT key n | 为数值加上浮点型数值 | 127.0.0.1:6379> incrbyfloat age 3.3 "8.3" |
STRLEN key | 获取key保存值的字符串长度 | 127.0.0.1:6379> get name "jiuqi" 127.0.0.1:6379> strlen name (integer) 5 |
GETRANGE key start end | 按起止位置获取字符串(闭区间,起止位置都取) | 127.0.0.1:6379> get name "jiuqi" 127.0.0.1:6379> getrange name 2 4 |
SETRANGE key offset value | 用指定的value 替换key中 offset开始的值 | 127.0.0.1:6379> setrange name 3 jiu 127.0.0.1:6379> get name "jiujiu" |
GETSET key value | 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 | 127.0.0.1:6379> getset name jiuqi "jiujiu" |
SETNX key value | 仅当key不存在时进行set | 127.0.0.1:6379> setnx name jiuqi (integer) 0 127.0.0.1:6379> setnx name1 jiuqi (integer) 1 |
SETEX key seconds value | set 键值对并设置过期时间 | 127.0.0.1:6379> setex name1 30 name OK 127.0.0.1:6379> ttl name1 (integer) 26 |
MSET key1 value1 [key2 value2..] | 批量set键值对 | 127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 |
MSETNX key1 value1 [key2 value2..] | 批量设置键值对,仅当参数中所有的key都不存在时执行 | 127.0.0.1:6379> msetnx k1 v1 k4 v4 (integer) 0 127.0.0.1:6379> msetnx k4 v4 k5 v5 |
MGET key1 [key2..] | 批量获取多个key保存的值 | 127.0.0.1:6379> mget k1 k2 k4 k5 1) "v1" 2) "v2" 3) "v4" 4) "v5" |
set user:1 {name:baize,age:11} | json字符串设置一个对象 | 127.0.0.1:6379> set user:1 {name:baize,age:11} |
mset 类型:id:key value 类型:id:key value | 设置一个对象· | 127.0.0.1:6379> mset user:1:name baize user:1:age 11 |
String类似的使用场景:
- 计数器
- 统计多单位的数量 uid:123666:follow 0
- 粉丝数
- 对象存储缓存
2.List(列表)
Redis列表是简单的字符串列表,按照插入顺序排序你可以添加一个元素到列表的头部(左边)或者尾部(右边),一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)
redis列表可以经过规则定义将其变为队列、栈、双端队列等
-
list实际上是一个链表,before Node after , left, right 都可以插入值
-
如果key不存在,则创建新的链表
-
如果key存在,新增内容
-
如果移除了所有值,空链表,也代表不存在
-
在两边插入或者改动值,效率最高!修改中间元素,效率相对较低
命令 | 描述 | 示例 |
---|---|---|
lpush/rpush 列表 value1[value2..] | 从左边或者右边向列表中添加值 | 127.0.0.1:6379[1]> lpush list one (integer) 1 127.0.0.1:6379[1]> lpush list two (integer) 2 127.0.0.1:6379[1]> lpush list three (integer) 3 127.0.0.1:6379[1]> lpush list 1 2 3 (integer) 6 |
lrange 列表 start end | 获取list元素 | 127.0.0.1:6379[1]> lrange list 0 -1 1) "3" 2) "2" 3) "1" 4) "three" 5) "two" 6) "one" 127.0.0.1:6379[1]> lrange list 1 2 1) "two" 2) "one" |
lpop/rpop 列表 | 从左边或者右边移除列表中的一个值 | 127.0.0.1:6379[1]> lpop list "3" 127.0.0.1:6379[1]> rpop list "one" |
lindex 列表 index | 通过索引获取列表元素 | 127.0.0.1:6379[1]> lindex list 2 "three" |
llen 列表 | 查看列表长度 | 127.0.0.1:6379[1]> llen list (integer) 4 |
lrem 列表 移除数量 value | 移除具体的元素 | 127.0.0.1:6379[1]> lrem list 1 two (integer) 1 |
ltrim key start end | 通过下标保留指定范围内的元素 | 127.0.0.1:6379[1]> ltrim list 1 2 OK |
rpoplpush source destination | 将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部 | 127.0.0.1:6379[1]> rpoplpush list mylist "three" 127.0.0.1:6379[1]> lrange mylist 0 -1 1) "three" |
lset key index value | 通过索引为元素设值,需要索引有值 | 127.0.0.1:6379[1]> lrange list 0 -1 127.0.0.1:6379[1]> lset list 1 first |
linsert key before|after pivot value | 在指定列表元素的前/后 插入value | 127.0.0.1:6379[1]> linsert list before two other (integer) 4 127.0.0.1:6379[1]> lrange list 0 -1 1) "one" 2) "other" 3) "two" 4) "three" |
blpop/brpop key1[key2] 等待时间 | 移出并获取列表的第一个/最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 | 127.0.0.1:6379[1]> brpop list 5 127.0.0.1:6379[1]> blpop name 1 |
3.set(集合)
命令 | 描述 | 示例 |
---|---|---|
sadd 集合 member1[member2..] | 向集合中无序增加一个/多个成员 | 127.0.0.1:6379[1]> sadd myset key1 |
scard 集合 | 获取集合的成元素数量 | 127.0.0.1:6379[1]> scard myset (integer) 3 |
smembers 集合 | 查询集合的所有成员 | 127.0.0.1:6379[1]> smembers myset 1) "key1" 2) "key2" 3) "key3" |
sismember 集合 元素 | 查询元素是否是集合的成员 | 127.0.0.1:6379[1]> sismember myset key1 (integer) 1 127.0.0.1:6379[1]> sismember myset key5 (integer) 0 |
srandmember 集合 count | 随机返回集合中count个成员,count缺省值为1 | 127.0.0.1:6379[1]> srandmember myset "key1" 127.0.0.1:6379[1]> srandmember myset 2 1) "key2" 2) "key3" |
spop 集合 count | 随机移除并返回集合中count个成员,count缺省值为1 | 127.0.0.1:6379[1]> spop myset "key3" 127.0.0.1:6379[1]> spop myset 2 1) "key1" 2) "key2" |
smove 集合1 集合2 元素 | 将集合1的元素移到集合2 | 127.0.0.1:6379[1]> smove myset1 myset2 key (integer) 1 127.0.0.1:6379[1]> smembers myset2 1) "key" |
srem key member1[member2..] | 移除集合中一个/多个成员 | 127.0.0.1:6379[1]> srem myset2 key (inteVger) 1 |
sdiff 集合1 集合2 | 返回所有集合的差集 | 127.0.0.1:6379[1]> smembers myset1 127.0.0.1:6379[1]> sdiff myset1 myset2 |
sinter 集合1 集合2 | 返回所有集合的交集 | 127.0.0.1:6379[1]> sinter myset1 myset2 1) "key3" |
sunion 集合1 集合2 | 返回所有集合的并集 | 127.0.0.1:6379[1]> sunion myset1 myset2 1) "key2" 2) "key5" 3) "key3" 4) "key1" 5) "key4" |
sinterstore 集合3 集合1 集合2 | 在sinter的基础上,存储结果到集合3中。 | 127.0.0.1:6379[1]> sinterstore myset3 myset1 myset2 (integer) 1 127.0.0.1:6379[1]> smembers myset3 1) "key3" |
4.hash(哈希)
Hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!Hash更适合于对象的存储,Sring更加适合字符串存储!
命令 | 描述 | 示例 |
---|---|---|
hset key field value | 将哈希表 key 中的字段 field 的值设置为 value | 127.0.0.1:6379[1]> hset myhash field1 one (integer) 1 |
hmset key field1 value1 [field2 value2..] | 同时将多个 field-value 添加到哈希表 key 中 | 127.0.0.1:6379[1]> hmset myhash field2 two field3 three OK |
hget key field value | 获取存储在哈希表中指定字段的值 | 127.0.0.1:6379[1]> hget myhash field1 "one" |
hmget key field1 [field2..] | 获取表中所有字段的值 | 127.0.0.1:6379[1]> hmget myhash field1 field2 field3 1) "one" 2) "two" 3) "three" |
hsetnx key field value | 只有在字段 field 不存在时,设置哈希表字段的值。 | 127.0.0.1:6379[1]> hsetnx myhash field1 (error) ERR wrong number of arguments for 'hsetnx' command 127.0.0.1:6379[1]> hsetnx myhash field4 four (integer) 1 |
hexists key field | 查看指定的字段在表中是否存在。 | 127.0.0.1:6379[1]> hexists myhash field4 (integer) 1 |
hgetall key | 获取在哈希表key 的所有字段和值 | 127.0.0.1:6379[1]> hgetall myhash 1) "field1" 2) "one" 3) "field2" 4) "two" 5) "field3" 6) "three" 7) "field4" 8) "four" |
hkeys key | 获取哈希表key中所有的字段 | 127.0.0.1:6379[1]> hkeys myhash 1) "field1" 2) "field2" 3) "field3" 4) "field4" |
hvals key | 获取哈希表中所有值 | 127.0.0.1:6379[1]> hvals myhash 1) "one" 2) "two" 3) "three" 4) "four" |
hlen | 获取哈希表中字段的数量 | 127.0.0.1:6379[1]> hlen myhash (integer) 4 |
hdel key field1 [field2..] | 删除哈希表key中一个/多个field字段 | 127.0.0.1:6379[1]> hdel myhash field4 field3 (integer) 2 |
hincrby key field n | 为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果 一样只适用于整数型字段 | 127.0.0.1:6379[1]> hincrby myhash field4 2 (integer) 9 127.0.0.1:6379[1]> hincrby myhash field4 -2 (integer) 7 |
hincrbyfloat key field n | 为哈希表 key 中的指定字段的浮点数值加上增量 n。 | 127.0.0.1:6379[1]> hincrbyfloat myhash field4 0.9 "7.9" |
hmset 集合:id key1 value1 key2 vaue2 | 设置一个对象 | 127.0.0.1:6379[1]> hmset user:1 name jiuqi age 3 OK |
5.Zset(有序集合)
每个元素都会关联一个double类型的分数(score),redis正是通过分数来为集合中的成员进行从小到大的排序,score相同按字典顺序排序。有序集合的成员是唯一的但分数(score)却可以重复。
- set排序 存储班级成绩表 工资表排序!
- 普通消息,1.重要消息 2.带权重进行判断
- 排行榜应用实现,取Top N测试
命令 | 描述 | 示例 | |
---|---|---|---|
zadd key score member1 [score2 member2] | 向集合添加一个或多个成员 | 127.0.0.1:6379[1]> zadd salary 500 baize (integer) 1 | |
zcard key | 获取有序集合的成员数 | 127.0.0.1:6379[1]> zcard salary (integer) 5 | |
zcount key min max | 计算在集合中score在指定区间的成员数 | 127.0.0.1:6379[1]> zcount salary 0 2000 (integer) 4 | |
zscore key member | 返回集合中成员的分数值 | 127.0.0.1:6379[1]> zscore salary baize "500" | |
zincrby key n member | 集合中对指定成员的score加上n | 127.0.0.1:6379[1]> zincrby salary 5000 jiuqi "8000" | |
zrank key member | 返回有序集合中指定成员的索引 | 127.0.0.1:6379[1]> zrank salary jiuqi (integer) 4 | |
zrange key start end | 通过索引区间返回有序集合成指定区间内的成员,score从低到高 | 127.0.0.1:6379[1]> zrange salary 0 -1 1) "baize" 2) "ningyao" 3) "zuoyou" 4) "yabahu" 5) "jiuqi" | |
zrevrange key start end | 通过索引区间返回有序集合成指定区间内的成员,score从高到低 | ||
zrangebyscore key min max | 通过分数返回集合指定区间内的成员 | 127.0.0.1:6379[1]> zrangebyscore salary 1000 3000 1) "ningyao" 2) "zuoyou" 3) "yabahu" | |
zrem key member1 [member2..] | 移除集合中一个/多个成员 | 127.0.0.1:6379[1]> zrem salary yabahu (integer) 1 | |
zremrangebyrank key start stop | 通过索引区间移除集合中成员 | 127.0.0.1:6379[1]> zremrangebyrank salary 0 3 (integer) 4 | |
zremrangebyscore key min max | 通过score区间移除集合中成员 | 127.0.0.1:6379[1]> zremrangebyscore salary 0 3000 (integer) 2 | |
zrevrangebyscore key max min | 返回集中指定score区间内的成员,从高到低排序 | 127.0.0.1:6379[1]> zrevrangebyscore salary 2000 1000 1) "yabahu" 2) "zuoyou" 3) "ningyao" | |
zinterstore key numkeys key1 [key2 ..] | 计算给定的一个或多个集合的交集并将结果集存储在新的有序集合 key 中,numkeys:表示参与运算的集合数 | 127.0.0.1:6379[1]> zadd salary1 1000 ningyao 1500 zuoyou 2000 yabahu 1000 gucan 127.0.0.1:6379[1]> zadd salary 1000 ningyao 1000 yabahu 2000 zuoyou 10000 qijingchun |
六.三种特殊数据类型
1.Geospatial(地理位置)
经纬度的范围
- 经度从-180度到180度。
- 纬度从-85.05112878度到85.05112878度。
指定单位的参数 unit 必须是以下单位的其中一个:
-
m 表示单位为米。
-
km 表示单位为千米。
-
mi 表示单位为英里。
-
ft 表示单位为英尺。
Geospatial使用Zset保存的数据所有Zset的命令可以使用
命令 | 解释 | 示例 |
---|---|---|
geoadd key 经度 纬度 member [经度 纬度 member1..] | 将具体位置的经纬度存入一个集合 | 127.0.0.1:6379[1]> geoadd china:city 116.40 39.90 beijing (integer) 1 |
geopos key member [member..] | 获取集合中的一个/多个位置坐标 | 127.0.0.1:6379[1]> geopos china:city beijing 1) 1) "116.39999896287918091" 2) "39.90000009167092543" |
geodist key member1 member2 [unit] | 返回两个给定位置之间的距离。默认以米作为单位。 | 127.0.0.1:6379[1]> geodist china:city beijing shanghai "1067378.7564" |
georadius key longitude latitude radius m|km|mi|ft [WITHCOORD][WITHDIST] [WITHHASH] [COUNT count] | 以给定的经纬度为中心, 返回集合包含的位置当与中心的距离不超过给定最大距离的所有位置。 | 127.0.0.1:6379[1]> georadius china:city 110 30 1000 km withcoord 1) 1) "nanchang" 2) 1) "115.88999837636947632" 2) "28.66999910629679249" 2) 1) "hangzhou" 2) 1) "120.15000075101852417" 2) "30.2800007575645509" 3) 1) "zhengzhou" 2) 1) "113.65999907255172729" |
georadiusbymember key member radius... | 以给定的位置为中心, 返回集合包含的位置当与中心的距离不超过给定最大距离的所有位置。 | 127.0.0.1:6379[1]> georadiusbymember china:city beijing 1000 km 1) "zhengzhou" 2) "beijing" |
geohash key member1 [member2..] | 返回一个或多个位置经纬度的一维字符串。 | 127.0.0.1:6379[1]> geohash china:city shanghai hangzhou beijing |
2.Hyperloglog
- Redis HyperLogLog 是用来做基数统计的算法,其底层使用string数据类型。
- HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的并且是很小的,花费 12 KB 内存就可以计算接近 2^64 个不同元素的基数。
- 因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
- HyperLogLog有0.81%的错误率,如果允许容错就可以使用HyperLogLog
什么是基数?
- 数据集中不重复元素的个数
应用场景?
- 网页的访问量(UV):一个用户多次访问,也只能算作一个人。
- 传统实现是比较存储用户的id,当用户变多之后这种方式及其浪费空间。
命令 | 描述 | 示例 |
---|---|---|
pfadd key element1 [elememt2..] | 添加元素 | 127.0.0.1:6379> pfadd array a b c d e f f a c (integer) 1 |
pfcount key [key] | 返回集合基数 | 127.0.0.1:6379> pfcount array (integer) 6 |
pfmerge destkey sourcekey [sourcekey..] | 将多个集合合并为一个新的集合 | 127.0.0.1:6379> pfadd array1 f g h i j k (integer) 1 127.0.0.1:6379> pfmerge array2 array array1 OK 127.0.0.1:6379> pfcount array2 (integer) 11 |
3.BitMaps
BitMaps位图,数据结构都是操作二进制位进行记录的,就只有0和1两个状态
当信息状态只有两种情况时我们就可以使用BitMaps
命令 | 描述 | 示例 |
---|---|---|
setbit key offset value | 为指定key的offset位设置值 | 127.0.0.1:6379> setbit sing 0 1 (integer) 0 |
getbit key offset | 获取offset位的值 | 127.0.0.1:6379> getbit sing 0 (integer) 1 |
bitcount key [start end] | 不写[start end]就是统计offset被设置为1的key的数量,写可以指定统计范围按字节 | 127.0.0.1:6379> bitcount sing (integer) 6 |
七.事务
1.基本知识
Redis事务本质:一组命令的集合。每条命令都会被序列化,按顺序执行不允许其他命令进行干扰
事务的特点:一次性、顺序性、排他性
没有隔离级别,所有的命令在事务中并没有被执行只有发起执行命令时才会执行。
Redis的单条命令是保证原子性的,但是redis事务不能保证原子性,代码语法错误(编译时异常)所有的命令都不执行,代码逻辑错误 (运行时异常) 其他命令可以正常执行
2.redis执行事物的命令:
- 开启事务(multi)
- 命令入队(普通命令)
- 执行事务(exec)
- 取消事务(
discurd
) - 事务错误
开启事务
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> exec
1) OK
2) OK
3) "v2"取消事务
127.0.0.1:6379> set k3 v3
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> getset k2
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
(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> incr k1
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k1
"v1"
3.监控
悲观锁:很悲观,认为什么时候都会出现问题,无论做什么都会加锁
乐观锁:很乐观,认为什么时候都不会出现问题所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据,获取version更新的时候比较version。
Redis中使用watch对数据进行监控,相当于乐观锁。
正常执行
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 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
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20模拟多线程插队
线程一:
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 50
QUEUED
127.0.0.1:6379> get money
QUEUED
线程二:127.0.0.1:6379> incrby money 1000
(integer) 1100线程一:
127.0.0.1:6379> exec
(nil)解除锁定
unwatch
八.Jedis
Jedis是官方推荐的一款java操作redis数据库的工具
1.创建一个空的maven项目,并且更改好项目环境
2.导入依赖,maven仓库官网地址:https://mvnrepository.com/
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.25</version>
</dependency>
3.测试
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
}
}
4.命令和Linux一样
public class TestString {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.set("name","九七");
System.out.println(jedis.get("name"));
}
}
5.事务
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("name","九七");
jsonObject.put("age","3");
String result = jsonObject.toJSONString();
Transaction multi = jedis.multi();
try {
multi.set("user1", result);
multi.set("user2",result);
multi.exec();
}catch (Exception e){
multi.discard();
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
}
九.SpringBoot整合
SpringBoot操作数据库:spring-data jpa jdbc mongodb redis
在SpringBoot2.X之后,jedis被替换成lettuce
- jedis:采用的是直连,多个线程操作是不安全的。为了安全我们还要使用jedis pool连接池。 类似BIO的模式
- lettuce: 采用netty,实例可以在多个线程中共享不存在线程不安全的情况!可以减少线程数 据了。更像NIO模式
1.测试
- 新建一个springboot项目添加对应的依赖
- 配置
spring:
redis:
host: 127.0.0.1
port: 6379
- 测试
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
/*
*获取redis的连接对象
* RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
* connection.flushDb();
* */
//redisTemplate.opsForZSet();
redisTemplate.opsForValue().set("name","九七");
System.out.println(redisTemplate.opsForValue().get("name"));
}
}
2.序列化
第一种方法:json序列化
- 编写我们的实体类
@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class user {
private String name;
private int age;
}
- 测试
@Test
void contextLoads() {
/*
*获取redis的连接对象
* RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
* connection.flushDb();
* */
//redisTemplate.opsForZSet();
redisTemplate.opsForValue().set("name","九七");
System.out.println(redisTemplate.opsForValue().get("name"));
}
public void Test() throws JsonProcessingException {
User user = new User("九七", 20);
String s = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",s);
System.out.println(redisTemplate.opsForValue().get("user"));
}
第二种方式:实体类序列化
- 实体类继承Serializable接口
@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User implements Serializable {
private String name;
private int age;
}
- 测试
@Test
public void Test() throws JsonProcessingException {
User user = new User("九七", 20);
redisTemplate.opsForValue().set("user",user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
第三种方式:自定义RedisTemplate里面解决了序列化和类型转换的问题
- config配置类下新建 RedisConfig
@Configurable
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// RedisTemplate对象,使用<String, Object>更方便
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
//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;
}
}
- 实体类
@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User{
private String name;
private int age;
}
- 测试
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
@Qualifier("redisTemplate") //为避免导入到底层我们需要加上这个导入到自己写地方
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
/*
*获取redis的连接对象
* RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
* connection.flushDb();
* */
//redisTemplate.opsForZSet();
redisTemplate.opsForValue().set("name","九七");
System.out.println(redisTemplate.opsForValue().get("name"));
}
@Test
public void Test() throws JsonProcessingException {
User user = new User("九七", 20);
redisTemplate.opsForValue().set("user",user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
}
3.RedisUtil工具类
一般情况下操作Redis就需要一直调用RedisTemplate.opsxxx的方法,但在工作中不会去这样使用会有一个XxxUtil的工具类。
- 编写工具类
package com.jiu.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
- 测试
@Autowired
private RedisUtil redisUtil;
@Test
public void test1() {
redisUtil.set("name","zhengdan");
System.out.println(redisUtil.get("name"));
}
十.Redis.conf详解
1.unit单位对大小写不敏感
# Redis configuration file example.
#
# Note that in order to read the configuration file, Redis must be
# started with the file path as first argument:
#
# ./redis-server /path/to/redis.conf
# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.
2.INCLUDES:可以有多个配置文件和Spring的import一样
################################## 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
3.NETWORK 网络
bind 127.0.0.1 #绑定的ip
# Protected mode is a layer of security protection, in order to avoid that
# Redis instances left open on the internet are accessed and exploited.
#
# When protected mode is on and if:
#
# 1) The server is not binding explicitly to a set of addresses using the
# "bind" directive.
# 2) No password is configured.
#
# The server only accepts connections from clients connecting from the
# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain
# sockets.
#
# By default protected mode is enabled. You should disable it only if
# you are sure you want clients from other hosts to connect to Redis
# even if no authentication is configured, nor a specific set of interfaces
# are explicitly listed using the "bind" directive.
protected-mode yes #保护模式
# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6379 #端口号设置
4. GENERAL 通用
################################# GENERAL #####################################
# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize yes #以守护线程的方式运行,默认是no
# If you run Redis from upstart or systemd, Redis can interact with your
# supervision tree. Options:
# supervised no - no supervision interaction
# supervised upstart - signal upstart by putting Redis into SIGSTOP mode
# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
# supervised auto - detect upstart or systemd method based on
# UPSTART_JOB or NOTIFY_SOCKET environment variables
# Note: these supervision methods only signal "process is ready."
# They do not enable continuous liveness pings back to your supervisor.
supervised no
# If a pid file is specified, Redis writes it where specified at startup
# and removes it at exit.
#
# When the server runs non daemonized, no pid file is created if none is
# specified in the configuration. When the server is daemonized, the pid file
# is used even if not specified, defaulting to "/var/run/redis.pid".
#
# Creating a pid file is best effort: if Redis is not able to create it
# nothing bad happens, the server will start and run normally.
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)
# notice (moderately verbose, what you want in production probably) 生产环境
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" #输出的日志名
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
# and optionally update the other syslog parameters to suit your needs.
# syslog-enabled no
# Specify the syslog identity.
# syslog-ident redis
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
# syslog-facility local0
# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
# dbid is a number between 0 and 'databases'-1
databases 16 #数据库的数量,默认是16个
# By default Redis shows an ASCII art logo only when started to log to the
# standard output and if the standard output is a TTY. Basically this means
# that normally a logo is displayed only in interactive sessions.
#
# However it is possible to force the pre-4.0 behavior and always show a
# ASCII art logo in startup logs by setting the following option to yes.
#
always-show-logo yes #输出时是否显示logo
5.SNAPSHOTTING 快照
################################ SNAPSHOTTING ################################
#
# Save the DB on disk:
#
# save <seconds> <changes>
#
# Will save the DB if both the given number of seconds and the given
# number of write operations against the DB occurred.
#
# In the example below the behaviour will be to save:
# after 900 sec (15 min) if at least 1 key changed
# after 300 sec (5 min) if at least 10 keys changed
# after 60 sec if at least 10000 keys changed
#
# Note: you can disable saving completely by commenting out all "save" lines.
#
# It is also possible to remove all the previously configured save
# points by adding a save directive with a single empty string argument
# like in the following example:
#
# save ""
#在900秒内,如果至少有一个key进行了修改,我们进行持久化操作
save 900 1
#在300秒内,如果至少有十个key进行了修改,我们进行持久化操作
save 300 10
#在60秒内,如果至少有10000个key进行了修改,我们进行持久化操作
save 60 10000
# By default Redis will stop accepting writes if RDB snapshots are enabled
# (at least one save point) and the latest background save failed.
# This will make the user aware (in a hard way) that data is not persisting
# on disk properly, otherwise chances are that no one will notice and some
# disaster will happen.
#
# If the background saving process will start working again Redis will
# automatically allow writes again.
#
# However if you have setup your proper monitoring of the Redis server
# and persistence, you may want to disable this feature so that Redis will
# continue to work as usual even if there are problems with disk,
# permissions, and so forth.
stop-writes-on-bgsave-error yes #持久化出错了是否继续工作
# Compress string objects using LZF when dump .rdb databases?
# For default that's set to 'yes' as it's almost always a win.
# If you want to save some CPU in the saving child set it to 'no' but
# the dataset will likely be bigger if you have compressible values or keys.
rdbcompression yes #是否压缩rdb文件,需要消耗一些cpu资源
# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
# This makes the format more resistant to corruption but there is a performance
# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
# for maximum performances.
#
# RDB files created with checksum disabled have a checksum of zero that will
# tell the loading code to skip the check.
rdbchecksum yes #是否校验rdb的文件,出错就自动修复
# The filename where to dump the DB
dbfilename dump.rdb #RDB保存的文件
# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir ./ #rdb文件保存的目录
6. SECURITY 安全
# Using an external ACL file
#
# Instead of configuring users here in this file, it is possible to use
# a stand-alone file just listing users. The two methods cannot be mixed:
# if you configure users here and at the same time you activate the exteranl
# ACL file, the server will refuse to start.
#
# The format of the external ACL user file is exactly the same as the
# format that is used inside redis.conf to describe users.
#
# aclfile /etc/redis/users.acl
# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatiblity
# layer on top of the new ACL system. The option effect will be just setting
# the password for the default user. Clients will still authenticate using
# AUTH <password> as usually, or more explicitly with AUTH default <password>
# if they follow the new protocol: both will work.
#
# requirepass foobared #在这里设置密码,默认是没有密码,我们一般在命令设置
7.CLIENTS
# Set the max number of connected clients at the same time. By default
# this limit is set to 10000 clients, however if the Redis server is not
# able to configure the process file limit to allow for the specified limit
# the max number of allowed clients is set to the current file limit
# minus 32 (as Redis reserves a few file descriptors for internal uses).
#
# Once the limit is reached Redis will close all the new connections sending
# an error 'max number of clients reached'.
#
# IMPORTANT: When Redis Cluster is used, the max number of connections is also
# shared with the cluster bus: every node in the cluster will use two
# connections, one incoming and another outgoing. It is important to size the
# limit accordingly in case of very large clusters.
#
# maxclients 10000 设置能连接上redis的最大客户端的数量
# Set a memory usage limit to the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys
# according to the eviction policy selected (see maxmemory-policy).
#
# If Redis can't remove keys according to the policy, or if the policy is
# set to 'noeviction', Redis will start to reply with errors to commands
# that would use more memory, like SET, LPUSH, and so on, and will continue
# to reply to read-only commands like GET.
#
# This option is usually useful when using Redis as an LRU or LFU cache, or to
# set a hard memory limit for an instance (using the 'noeviction' policy).
#
# WARNING: If you have replicas attached to an instance with maxmemory on,
# the size of the output buffers needed to feed the replicas are subtracted
# from the used memory count, so that network problems / resyncs will
# not trigger a loop where keys are evicted, and in turn the output
# buffer of replicas is full with DELs of keys evicted triggering the deletion
# of more keys, and so forth until the database is completely emptied.
#
# In short... if you have replicas attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for replica
# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes> redis的最大内存容量
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select one from the following behaviors:
#
# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key having an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.
#
# LRU means Least Recently Used
# LFU means Least Frequently Used
#
# Both LRU, LFU and volatile-ttl are implemented using approximated
# randomized algorithms.
#
# Note: with any of the above policies, Redis will return an error on write
# operations, when there are no suitable keys for eviction.
#
# At the date of writing these commands are: set setnx setex append
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
# getset mset msetnx exec sort
#
# The default is:
#
# maxmemory-policy noeviction 内存到达上限的处理策略
六种淘汰策略:
- noeviction(默认策略):对于写请求不再提供服务,直接返回错误(DEL请求和部分特殊请求除外)
- allkeys-lru:从所有key中使用LRU算法进行淘汰(LRU算法:即最近最少使用算法)
- volatile-lru:从设置了过期时间的key中使用LRU算法进行淘汰
- allkeys-random:从所有key中随机淘汰数据
- volatile-random:从设置了过期时间的key中随机淘汰
- volatile-ttl:在设置了过期时间的key中,淘汰过期时间剩余最短的
8. APPEND ONLY MODE
# By default Redis asynchronously dumps the dataset on disk. This mode is
# good enough in many applications, but an issue with the Redis process or
# a power outage may result into a few minutes of writes lost (depending on
# the configured save points).
#
# The Append Only File is an alternative persistence mode that provides
# much better durability. For instance using the default data fsync policy
# (see later in the config file) Redis can lose just one second of writes in a
# dramatic event like a server power outage, or a single write if something
# wrong with the Redis process itself happens, but the operating system is
# still running correctly.
#
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check http://redis.io/topics/persistence for more information.
appendonly no #默认是不开启aof模式的,默认使用的是rdb方式持久化,大部分情况完全够用
# The name of the append only file (default: "appendonly.aof")
appendfilename "appendonly.aof" #持久化文件的名字
# appendfsync always 每次修改都会同步,消耗性能
appendfsync everysec #每秒执行一次同步,可能会丢失这一秒的数据
# appendfsync no 不执行同步,操作系统自己同步数据
十一.持久化
1.RDB
RDB全称Redis DataBase。RDB是指在指定的时间间隔内将数据集快照写入磁盘,恢复时将这些快照文件直接读取到内存中 。
Redis会单独创建(fork:复制一个和当前一模一样的进程作为子进程)出一个子进程进行持久化,会将数据写入一个临时文件中,待持久化过程结束,临时文件会替换已经持久化好的文件。在这个过程中主进程不进行任何IO操作,这也就确保了极高的性能。如果在进行大规模数据恢复并且对于数据恢复的完整性不是非常敏感,那么RDB的方式要比AOF更加的高效。RDB的缺点就是持久化之后数据可能丢失。默认的持久化就是RDB,在一般情况下不需要修改这个配置。
RDB保存的文件的名字在配置文件的快照位置,位置在 usr/local/bin,在生产环境我们一般会将保存的文件备份。
RDB的触发机制:
- save的规则满足的情况下,会自动触发rdb原则
- 执行flushall命令,也会触发我们的rdb原则
- 退出redis,也会自动产生rdb文件
如何恢复RDB文件:
- 我们首先找到rdb文件的位置 config get dir
- 只需要将rdb文件放到我们redis的安装目录下就好了,redis启动会自动检查dump.rdb文件,恢复里面的数据
RDB的优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
RDB的缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机最后一次修改的数据就没有了。
- fork进程的时候会占用一定的内存空间。
2.AOF
AOF(Append Only File)是用日志的形式来记录每一个写操作,将Redis执行过的命令进行记录(读操作不记录),只追加文件不改写文件。Redis在启动时会自动加载AOF持久化的文件重新构建数据,也就是Redis重启会把AOF持久化文件中的信息从前到后执行一次以完成数据的恢复
aof默认是关闭的我们需要去配置文件开启
appendonly no #改为yes就是开启了
# The name of the append only file (default: "appendonly.aof")
appendfilename "appendonly.aof" #aof文件存放的位置
当appendonly.aof出现错误时redis会无法启动,我们需要使用redis-check-aof的redis-check-aof --fix appendonly.aof 命令进行修复他们都在/usr/local/bin目录下
总结:
- RDB可以在指定的时间间隔内对数据集进行持久化快照存储
- AOF持久化记录每次客户端发送给Redis服务器的写操作,服务器中重启时会重新执行命令恢复原始数据,AOF持久化的每一次记录都会追加在文件的末尾,并且Redis有重写机制的存在使得AOF的文件被控制在合理的大小
- 如果Redis只做缓存,如果说只希望数据在服务器启动的时候存在,可以不使用任何的持久化方式
- 刚刚上面讲到一个小细节,如果两种持久化同时开启,Redis服务器会默认先找AOF持久化,因为AOF的保存数据集要比RDB要完整,这也就是Redis考虑安全的原因
十二.Redis发布订阅
Redis的发布订阅(publish/subscribe)是一种消息通信模式,发送者(publish)发送消息,订阅者(subscribe)接收消息
命令 | 描述 |
---|---|
psubscribe pattern [pattern..] | 订阅一个或多个符合给定模式的频道。 |
pubsub subcommand [argument[argument]] | 查看订阅与发布系统状态。 |
publish channel message | 发送信息到指定频道 |
punsubscribe pattern [pattern..] | 退订一个或多个符合给定模式的频道。 |
subscribe channel [channel..] | 订阅给定的一个或多个频道的信息 |
unsubscribe channel [channel..] | 退订给定的一个或多个频道的信息 |
订阅端
subscribe jiuqiyou
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "jiuqiyou"
3) (integer) 1
1) "message"
2) "jiuqiyou"
3) "fds"
发送端
127.0.0.1:6379> publish jiuqiyou fds
(integer) 1
应用
- 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
- 多人在线聊天室。
十三.Redis主从复制
1.概念
主从复制是指将一台Redis服务器的数据复制到其他Redis服务器,前者称为主节点(Master/Leader)后者称为从节点(Slave/Follower)。数据的复制是单向的只能由主节点复制到从节点(主节点以写为主、从节点以读为主)
默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点
作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
- 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
- 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
- 高可用基石:主从复制还是哨兵和集群能够实施的基础。
为什么使用集群:
- 单台服务器难以负载大量的请求
- 单台服务器故障率高,系统崩坏概率大
- 单台服务器内存容量有限。
2.环境准备
查看当前库的信息:info replication
127.0.0.1:6379> info replication
# Replication
role:master 角色
connected_slaves:0 从机数量
master_replid:8f1b059b5ad26eedd5d34ab38d8122bad6c20c5d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
既然需要启动多个服务就需要多个配置文件,每个配置文件对应修改以下信息:
- 端口号
- pid文件名
- 日志文件名
- rdb文件名
测试:
1.启动单机多服务集群
[root@jiuqi jconfig]# cp redis.conf redis79.conf
[root@jiuqi jconfig]# cp redis.conf redis80.conf
[root@jiuqi jconfig]# cp redis.conf redis81.conf
[root@jiuqi jconfig]# ls
redis79.conf redis80.conf redis81.conf redis.conf
2.修改新增的配置文件
改成对应的就行例如80
port 6380 # 端口号
logfile "6380.log" # 日志文件名
pidfile /var/run/redis_6380.pid # pid文件名
dbfilename dump6380.rdb # rdb文件名
3.新开多个页面启动不同配置文件
#这里也只以80为例
[root@jiuqi bin]# redis-server jconfig/redis80.conf
[root@jiuqi bin]# redis-cli -p 6380
3.一主二从配置
默认情况下每台Redis服务器都是主节点,我们一般情况下只用配置从机就好了!
使用slaveof host port就可以为从机配置主机了。
1.使用命令搭建
这种方式搭建的是暂时性的 ,真实开发需要去配置文件修改
80端口:
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:0
master_replid:3d4094604ec606ce71b93d973c2fac28b81f0108
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6380> slaveof 127.0.0.1 6379
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:2
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4f7ff1318bc06858fbb81863bf7139b12e2c34fc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
79端口:
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:22f54033364cd1bc2e8845d5f8cff5c3f3f59bc7
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=28,lag=1
master_replid:4f7ff1318bc06858fbb81863bf7139b12e2c34fc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:28
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:28
2.配置文件修改
################################# REPLICATION #################################
# Master-Replica replication. Use replicaof to make a Redis instance a copy of
# another Redis server. A few things to understand ASAP about Redis replication.
#
# +------------------+ +---------------+
# | Master | ---> | Replica |
# | (receive writes) | | (exact copy) |
# +------------------+ +---------------+
#
# 1) Redis replication is asynchronous, but you can configure a master to
# stop accepting writes if it appears to be not connected with at least
# a given number of replicas.
# 2) Redis replicas are able to perform a partial resynchronization with the
# master if the replication link is lost for a relatively small amount of
# time. You may want to configure the replication backlog size (see the next
# sections of this file) with a sensible value depending on your needs.
# 3) Replication is automatic and does not need user intervention. After a
# network partition replicas automatically try to reconnect to masters
# and resynchronize with them.
#
# replicaof <masterip> <masterport>
4.使用规则
- 主机可读可写从机只可读不可写
- 当主机断电宕机后默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后又会连接上从机恢复原状
- 当从机断电宕机后若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机又可以获取到主机的所有数据,这里就要提到一个同步原理。
- 当主机出现故障后可以在从机输入slaveof no one命令当作主机,故障主机回来也不会改变
- 当一个从机的主机c设置是另一台从机B,那么B也只能读不能写,c能读取主机a的信息
十四.哨兵模式
1.概述
当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工 干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题。 后台监控可以发现主机是否故障如果故障了根据投票数自动将从库转换为主库。 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程作为进程它会独立运行,其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
2.配置哨兵
在/usr/local/bin/jconfig下vim sentinel.conf写入sentinel monitor 哨兵的名字 host port 1(代表投票机制)
sentinel monitor myredis 127.0.0.1 6379 1
3.启动哨兵
- redis-sentinel jconfig/sentinel.conf
完整的哨兵模式配置文件 sentinel.conf
4.优缺点
优点:
- 哨兵集群基于主从复制模式,所有主从复制的优点它都有
- 主从可以切换,故障可以转移,系统的可用性更好
- 哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
- Redis不好在线扩容,集群容量一旦达到上限在线扩容就十分麻烦
- 实现哨兵模式的配置其实是很麻烦的里面有很多配置项
5.完整的哨兵模式配置文件 sentinel.conf
# 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
十五.缓存穿透/击穿/雪崩
1.缓存穿透
概念:数据在缓存中未命中会去数据库中进行查找,数量少可能问题不大可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力可能导致数据库崩溃
解决方案:
1.缓存空对象
一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求,但这样做大量的空对象会耗费一定的空间存储效率并不高。解决这个缺陷的方式就是设置较短过期时间但即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
2.布隆过滤器
对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。
2.缓存击穿
概念:一个存在的key在缓存过期的一刻同时有大量的请求,这些请求都会击穿到DB造成瞬时DB请求量大压力骤增。
解决方案:
1.设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
2.加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
3.缓存雪崩
概念:大量的key设置了相同的过期时间,导致大量缓存在同一时刻全部失效,造成瞬时DB请求量大压力骤增引起雪崩。
解决方案:
1.redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
2.限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
3.数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。