目录
一、Redis概述
1. Redis是什么
2.Redis可以干什么
3.特性
二、安装
1.windows安装
2.Linux安装
三、redis-benchmark性能测试
语法
redis 性能测试工具可选参数
测试
四、Redis的基本知识说明
基础命令
Redis为什么使用单线程还是这么快
五、Redis的数据类型
Redis-Key
String(字符串)
List(列表)
Set(集合)
Hash(哈希)
Zset(有序集合)
geospatial (地理位置)
hyperloglog(基数统计)
bitmap(位图)
六、Redis事务
redis基本的事务操作
Redis事务的本质
Redis事务的特性
Redis的事务步骤
正常执行事务
放弃事务
编译异常(命令错误)
运行异常(语法错误)
redis实现乐观锁(watch)
测试在事务执行过程中,另外一个线程去修改
Jedis
什么是Jedis
通过jedis操作redis
通过jedis操作事务
七、SpringBoot集成Redis
八、Redis配置文件详解
include :支持引用多个配置文件
NETWORK:网络
GENERAL:通用
SNAPSHOTTING:快照
SECURITY:安全
CLIENTS:客户端限制
MEMORY MANAGEMENT:内存管理
APPEND ONLY MODE: AOF配置
九、Redis持久化
RDB操作
触发机制
恢复rdb文件
优缺点
AOF操作
开启AOF模式
修复appendonly.aof文件
优缺点
十、Redis订阅发布
命令
测试
十一、Redis主从复制
概念
主从复制的作用
集群环境搭建
主从复制之复制原理
哨兵模式详解
概念
配置哨兵
优缺点
哨兵模式的全部配置
十二、缓存穿透、雪崩
缓存穿透
概念
解决方案
缓存击穿
概念
解决方案
缓存雪崩
概念
解决方案
Redis(Remote Dictionary Server),远程字典服务。是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
1.内存存储,持久化,内存中断点即失,所以说持久化很重要。持久化的方式:RDB、AOF。
2.效率高,可以用于高速缓存
3.发布订阅系统
4.地图信息分析
5.计时器,计数器
6.。。。
1.支持多样的数据类型
2.支持持久化
3.支持集群
4.支持事务
5.。。。
官网:Redis官网
中文网:redis中文官方网站
Redis推荐都是在Linux服务器上搭建的。 我们以下的操作都会在linux服务器上进行操作。
1.下载安装包:https://github.com/tporadowski/redis/releases
2. 解压
3.双击redis-server.exe,启动
4.使用redis客户端连接redis,双击redis-cli.exe。
1.下载安装包:Index of /releases/。这里使用5.0.8。
2. 在home文件夹下创建redis目录
mkdir redis
3. 把redis安装包上传到redis目录下
4.解压安装包
tar -zxvf redis-5.0.8.tar.gz
5.进入解压后的文件,可以看到redis的配置文件
6.基本的环境安装
yum install gcc-c++
7.查看版本
gcc -v
8.执行make命令,这里需要一些时间。
make
执行成功后,再次执行make命令。
9.执行make install
make install
10.redis的默认安装路劲是/usr/local/bin,进入/usr/local/bin目录查看
11.将我们的redis的配置文件复制到当前目录下
#创建redis-conf目录
mkdir redis-conf
# 把redis的配置文件拷贝到redis-conf目录下
cp /home/redis/redis-5.0.8/redis.conf redis-conf/
我们之后就使用这个配置文件进行启动。
12.redis默认不是后台启动的,修改配置文件。
13.启动redis服务
回到bin目录下,启动服务
redis-server redis-conf/redis.conf
14.测试连接
15.查看redis的进程是否开启
ps -ef|grep redis
16.如何关闭redis服务
shutdown
再次查看进程是否存在
注:若需要远程连接,需操作以下步骤:
修改配置文件
bind 0.0.0.0
protected-mode no
测试连接,若连接不上,报以下错误
Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnect
redis默认端口号6379是不允许进行远程连接的,所以在防火墙中设置6379开启远程服务;
/sbin/iptables -I INPUT -p tcp --dport 6379 -j ACCEPT
redis-benchmark是一个压力测试工具,是官方自带的。
redis 性能测试的基本命令如下:
redis-benchmark [option] [option value]
注意:该命令是在 redis 的目录下执行的,而不是 redis 客户端的内部指令。
序号 | 选项 | 描述 | 默认值 |
---|---|---|---|
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 | -t | 仅运行以逗号分隔的测试命令列表。 | |
14 | -I(i 的大写字母) | Idle 模式。仅打开 N 个 idle 连接并等待。 |
场景:100个并发连接,1000000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 1000000
redis默认有16个数据库,查看redis.conf配置文件
默认使用的是第0个数据库。
切换数据库
127.0.0.1:6379> select 3 #使用select进行数据库的切换
OK
查看数据库大小
127.0.0.1:6379[3]> DBSIZE #使用DBSIZE查看数据的大小
(integer) 0
查看数据库中所有的key
127.0.0.1:6379[3]> set name zzz
OK
127.0.0.1:6379[3]> get name
"zzz"
127.0.0.1:6379[3]> keys * #查看数据库中所有的key
1) "name"
清除当前数据库
127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
清除所有数据库中的内容
127.0.0.1:6379[3]> flushall
OK
Redis是单线程,官方表示,Redis是基于内存操作的,CPU不是Redis的性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽决定的,所以就使用了单线程。
redis是将所有的数据全部存放在内存中的,所以使用单线程去操作效率是最高的,因为多线程会来回的上下文切换,上下文切换是一个耗时的操作,对于内存系统来说,如果没有上下文切换效率就是最高的。
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与 范围查询, bitmaps, hyperloglog 和 地理空间(geospatial)索引半径查询。 Redis 内置了复制(replication)、LUA脚本、LRU驱动事件(LRU eviction),事务(transactions)和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动分区(Cluster) 提供高可用性(high availability)。
Redis命令帮助文档:Redis命令帮助文档
设置key
127.0.0.1:6379> set name zzz
OK
判断key是否存在
127.0.0.1:6379> exists name
(integer) 0
127.0.0.1:6379> set name zzz
OK
127.0.0.1:6379> exists name
(integer) 1
移除某个key
127.0.0.1:6379> move name 1
(integer) 1
设置过期时间
127.0.0.1:6379> set name zzz
OK
127.0.0.1:6379> expire name 10 #单位是秒
(integer) 1
查看key剩余过期时间
127.0.0.1:6379> ttl name
(integer) 8
127.0.0.1:6379> ttl name
(integer) 2
查看key的类型
127.0.0.1:6379> type name
string
追加字符:append
127.0.0.1:6379> append name '123'
(integer) 6 #这里返回的是值的长度
127.0.0.1:6379> get name
"zzz123"
获取字符长度:strlen
127.0.0.1:6379> strlen name
(integer) 6
自增+1:incr
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #自增+1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
自减-1:decr
127.0.0.1:6379> decr views
(integer) 1
127.0.0.1:6379>
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> get views
"0"
增加多少:incrby
127.0.0.1:6379> incrby views 10
(integer) 10
127.0.0.1:6379> get views
"10"
减小多少:decrby
127.0.0.1:6379> decrby views 6
(integer) 4
127.0.0.1:6379> get views
"4"
截取字符:getrange
127.0.0.1:6379> set name 'hello,redis'
OK
127.0.0.1:6379> get name
"hello,redis"
127.0.0.1:6379> getrange name 0 2
"hel"
127.0.0.1:6379> getrange name 0 -1 #获取全部字符
"hello,redis"
替换字符:setrange
127.0.0.1:6379> get name
"hello,redis"
127.0.0.1:6379> setrange name 5 -
(integer) 11
127.0.0.1:6379> get name
"hello-redis"
设置过期时间:setex
127.0.0.1:6379> setex name 30 'hello,redis'
OK
127.0.0.1:6379> ttl name
(integer) 26
不存在设置:setnx,在分布式锁中常常使用
127.0.0.1:6379> setnx name 'redis' #如果不存在,则创建
(integer) 1
127.0.0.1:6379> setnx name 'mongdb' #如果存在,创建失败
(integer) 0
批量设置值:mset
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k1"
3) "k2"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #msetnx 是一个原子性操作,要么一起成功,要么一起失败
(integer) 0
批量获取值:mget
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
设置对象
127.0.0.1:6379> mset user:name zzz user:age 1
OK
127.0.0.1:6379> mget user:name user:age
1) "zzz"
2) "1"
getset:先get然后在set
127.0.0.1:6379> getset name redis #如果不存在,则返回nil
(nil)
127.0.0.1:6379> get name
"redis"
127.0.0.1:6379> getset name mongodb #如果存在值,获取原来的值,并设置新的值
"redis"
127.0.0.1:6379> get name
"mongodb"
在Redis里面,我们可以把list玩成,栈、队列、阻塞队列。
所有的list命令都是以L开头的。
将一个值或者多个值插入到列表的头部(左)
127.0.0.1:6379> lpush names one two three #将一个值或者多个值插入到列表的头部(左)
(integer) 3
127.0.0.1:6379> lrange names 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange names 0 1
1) "three"
2) "two"
将一个值或者多个值插入到列表的尾部(右)
127.0.0.1:6379> rpush names four #将一个值或者多个值插入到列表的尾部(右)
(integer) 4
127.0.0.1:6379> lrange names 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
获取list中的值
127.0.0.1:6379> lrange names 0 -1 #获取list中全部的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange names 0 1 #获取list中第0个到第1个的值
1) "three"
2) "two"
移除元素(左)
127.0.0.1:6379> lrange names 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> lpop names
"three"
127.0.0.1:6379> lrange names 0 -1
1) "two"
2) "one"
3) "four"
移除元素(右)
127.0.0.1:6379> lrange names 0 -1
1) "two"
2) "one"
3) "four"
127.0.0.1:6379> rpop names
"four"
127.0.0.1:6379> lrange names 0 -1
1) "two"
2) "one"
获取list中某个下标的元素
127.0.0.1:6379> lrange names 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex names 0
"two"
获取list的长度
127.0.0.1:6379> lrange names 0 -1
1) "two"
2) "one"
127.0.0.1:6379> llen names
(integer) 2
移除list中指定个数的值,精确匹配
127.0.0.1:6379> lrange names 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem names 1 one #移除一个
(integer) 1
127.0.0.1:6379> lrange names 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem names 2 three #移除两个
(integer) 2
127.0.0.1:6379> lrange names 0 -1
1) "two"
截取list中的值
127.0.0.1:6379> lpush names one two three four five
(integer) 5
127.0.0.1:6379> lrange names 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> ltrim names 1 2 #通过下标截取指定的长度
OK
127.0.0.1:6379> lrange names 0 -1
1) "four"
2) "three"
移除列表中最后一个元素,并移动到新的列表中
127.0.0.1:6379> lpush mylist one two three four
(integer) 4
127.0.0.1:6379>
127.0.0.1:6379> lrange mylist 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> rpoplpush mylist myotherlist
"one"
127.0.0.1:6379> lrange myotherlist 0 -1
1) "one"
127.0.0.1:6379> lrange mylist 0 -1
1) "four"
2) "three"
3) "two"
将list列表中指定下标的值替换为另一个值
127.0.0.1:6379> exists names # 判断列表是否存在
(integer) 0
127.0.0.1:6379> lset names 0 two #若列表不存在,则报错
(error) ERR no such key
127.0.0.1:6379> lpush names one
(integer) 1
127.0.0.1:6379> lrange names 0 -1
1) "one"
127.0.0.1:6379> lset names 0 two #将列表中下标为0的值替换为two
OK
127.0.0.1:6379> lrange names 0 -1
1) "two"
127.0.0.1:6379> lset names 1 three #若列表中下标不存在,则报错
(error) ERR index out of range
在列表的某个值前边或者后边插入一个值
127.0.0.1:6379> lpush names onw three
(integer) 2
127.0.0.1:6379> lrange names 0 -1
1) "three"
2) "onw"
127.0.0.1:6379> linsert names before three two #在three这个值的前边插入一个值
(integer) 3
127.0.0.1:6379> lrange names 0 -1
1) "two"
2) "three"
3) "onw"
127.0.0.1:6379> linsert names after three two #在three这个值的后边插入一个值
(integer) 4
127.0.0.1:6379> lrange names 0 -1
1) "two"
2) "three"
3) "two"
4) "onw"
set是无序不重复集合。所有的set命令都是以S开头的。
插入:sadd
127.0.0.1:6379> sadd names one two three
(integer) 3
获取:smembers
127.0.0.1:6379> smembers names
1) "two"
2) "one"
3) "three"
查看元素是否存在:sismember
127.0.0.1:6379> sismember names one
(integer) 1
127.0.0.1:6379> sismember names four
(integer) 0
获取Set集合中的个数:scard
127.0.0.1:6379> scard names
(integer) 3
删除指定元素:srem
127.0.0.1:6379> srem names one
(integer) 1
127.0.0.1:6379> scard names
(integer) 2
127.0.0.1:6379> smembers names
1) "two"
2) "three"
随机获取元素:srandmember
127.0.0.1:6379> srandmember names #随机获取一个元素
"three"
127.0.0.1:6379> srandmember names
"two"
127.0.0.1:6379> srandmember names 2 #随机获取指定个数元素
1) "two"
2) "three"
随机删除元素:spop
127.0.0.1:6379> spop names #随机删除一个元素
"one"
127.0.0.1:6379> spop names 2 #随机删除指定个数元素
1) "four"
2) "three"
127.0.0.1:6379> smembers names
1) "two"
将一个指定的元素移动到另外一个set集合中:smove
127.0.0.1:6379> sadd names one two three
(integer) 3
127.0.0.1:6379> smembers names
1) "two"
2) "one"
3) "three"
127.0.0.1:6379> sadd names2 one
(integer) 1
127.0.0.1:6379> smembers names2
1) "one"
127.0.0.1:6379> smove names names2 two #删除某个指定的元素并移动到另一个set集合中
(integer) 1
127.0.0.1:6379> smembers names
1) "one"
2) "three"
127.0.0.1:6379> smembers names2
1) "two"
2) "one"
查看多个集合之间的差集:sdiff
127.0.0.1:6379> sadd key1 a b c
(integer) 3
127.0.0.1:6379> sadd key2 c d e
(integer) 3
127.0.0.1:6379> sdiff key1 key2
1) "a"
2) "b"
查看多个集合之间的交集:sinter
127.0.0.1:6379> sadd key1 a b c
(integer) 3
127.0.0.1:6379> sadd key2 c d e
(integer) 3
127.0.0.1:6379> sinter key1 key2
1) "c"
查看多个集合之间的并集:sunion
127.0.0.1:6379> sadd key1 a b c
(integer) 3
127.0.0.1:6379> sadd key2 c d e
(integer) 3
127.0.0.1:6379> sunion key1 key2
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
可以想象成一个map,就相当于是key-map的结构。本质和string类型没有太大区别。
所有的hash命令都是以H开头的。
插入键值对:hset
127.0.0.1:6379> hset myhash name zzz
(integer) 1
获取:hget
127.0.0.1:6379> hget myhash name
"zzz"
插入多个键值对:hmset
127.0.0.1:6379> hmset myhash age 1 date 2022-01
OK
获取多个值:hmget
127.0.0.1:6379> hmget myhash age date
1) "1"
2) "2022-01"
获取全部键值对:hgetall
127.0.0.1:6379> hgetall myhash
1) "name"
2) "zzz"
3) "age"
4) "1"
5) "date"
6) "2022-01"
移除指定的键值对:hdel
127.0.0.1:6379> hdel myhash date age
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "name"
2) "zzz"
获取键值对的个数:hlen
127.0.0.1:6379> hlen myhash
(integer) 1
判断键值对是否存在:hexists
127.0.0.1:6379> hexists myhash name
(integer) 1
获取所有的键:hkeys
127.0.0.1:6379> hkeys myhash
1) "name"
获取所有键对应的值:hvals
127.0.0.1:6379> hvals myhash
1) "zzz"
指定增量:hincrby
127.0.0.1:6379> hincrby myhash age 1
(integer) 1
127.0.0.1:6379> hget myhash age
"1"
127.0.0.1:6379> hincrby myhash age 5
(integer) 6
127.0.0.1:6379> hget myhash age
"6"
判断某个键值对是否存在:hsetnx
127.0.0.1:6379> hsetnx myhash date 2022-01 #如果键值对不存在,则插入
(integer) 1
127.0.0.1:6379> hsetnx myhash date 2022-02 #如果键值对存在,则失败
(integer) 0
插入:zadd
127.0.0.1:6379> zadd subject 1 zhangsan
(integer) 1
获取:zrange
127.0.0.1:6379> zrange subject 0 -1
1) "zhangsan"
排序(升序):zrangescore
127.0.0.1:6379> zrangebyscore score -inf +inf
1) "xiaohong"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> zrangebyscore score -inf +inf withscores
1) "xiaohong"
2) "50"
3) "zhangsan"
4) "99"
5) "lisi"
6) "100"
127.0.0.1:6379> zrangebyscore score -inf 99 #获取分数小于99的
1) "xiaohong"
2) "zhangsan"
127.0.0.1:6379> zrangebyscore score -inf 99 withscores
1) "xiaohong"
2) "50"
3) "zhangsan"
4) "99"
移除指定元素:zrem
127.0.0.1:6379> zrem score xiaohong
(integer) 1
127.0.0.1:6379> zrange score 0 -1
1) "zhangsan"
2) "lisi"
排序(降序):zrevrange
127.0.0.1:6379> zrevrange score 0 -1
1) "lisi"
2) "zhangsan"
获取集合中的个数:zcard
127.0.0.1:6379> zcard score
(integer) 2
获取指定区间的成员数量:zcount
127.0.0.1:6379> zcount score 1 100
(integer) 2
使用场景:朋友定位,附近的人,打车距离计算
使用这个功能可以推算地理位置的信息,两地之间的距离,附近的人。
GEO底层的实现原理其实就是Zset !
准备测试数据:经纬度查询工具
相关命令:
GEOADD:添加地理位置
# 规则: 两极(南极,北极)无法直接插入,一般情况下我们会使用java程序直接导入
# 参数: key 值(纬度 经度 名称)
127.0.0.1:6379> geoadd china:city 116.405285 39.904989 beijing 121.472644 31.231706 shanghai 113.280637 23.125178 guangzhou 114.085947 22.547 shenzhen 113.665412 34.757975 zhengzhou 120.153576 30.287459 hangzhou
(integer) 6
GEODIST:获取两个给定位置之间的距离
指定单位的参数 unit 必须是以下单位的其中一个:
如果用户没有显式地指定单位参数, 那么 GEODIST
默认使用米作为单位。
127.0.0.1:6379> geodist china:city shanghai zhengzhou #查看上海到郑州的直线距离
"826835.6078"
127.0.0.1:6379> geodist china:city shanghai zhengzhou km
"826.8356"
GEOHASH:获取一个或多个位置元素的hash字符串
127.0.0.1:6379> geohash china:city beijing
1) "wx4g0b7xrt0"
GEOPOS:获取指定城市的纬度和经度
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.40528291463851929"
2) "39.9049884229125027"
127.0.0.1:6379> geopos china:city beijing shanghai
1) 1) "116.40528291463851929"
2) "39.9049884229125027"
2) 1) "121.47264629602432251"
2) "31.23170490709807012"
GEORADIUS:以给定的经纬度为中心,找出某一半径内的元素
127.0.0.1:6379> georadius china:city 121.5447 31.22249 1000 km #获取以纬度121.5447,经度31.22249为中心,1000km为半径的城市
1) "hangzhou"
2) "shanghai"
3) "zhengzhou"
127.0.0.1:6379> georadius china:city 121.5447 31.22249 1000 km withcoord withdist count 2
1) 1) "shanghai"
2) "6.9294"
3) 1) "121.47264629602432251"
2) "31.23170490709807012"
2) 1) "hangzhou"
2) "168.8068"
3) 1) "120.15357345342636108"
2) "30.28745790721532671"
GEORADIUSBYMEMBER:获取位于指定范围内的元素
127.0.0.1:6379> georadiusbymember china:city shanghai 1000 km withcoord withdist
1) 1) "hangzhou"
2) "164.0867"
3) 1) "120.15357345342636108"
2) "30.28745790721532671"
2) 1) "shanghai"
2) "0.0000"
3) 1) "121.47264629602432251"
2) "31.23170490709807012"
3) 1) "zhengzhou"
2) "826.8356"
3) 1) "113.66541177034378052"
2) "34.75797603259534441"
使用场景:统计网页的访问uv(一个人访问一个网站多次,还是算作一个人)。
添加:pfadd
127.0.0.1:6379> pfadd key1 a b c d e f g
(integer) 1
获取基数数量:pfcount
127.0.0.1:6379> pfcount key1
(integer) 7
获取并集数量:pfmerge
127.0.0.1:6379> pfadd key1 a b c d e f g
(integer) 1
127.0.0.1:6379> pfcount key1
(integer) 7
127.0.0.1:6379> pfadd key2 e f g h
(integer) 1
127.0.0.1:6379> pfcount key2
(integer) 4
127.0.0.1:6379> pfmerge key3 key1 key2 #将key1 key2合并
OK
127.0.0.1:6379> pfcount key3
(integer) 8
使用场景:打卡,统计用户登录状态。。。
以下就以打卡来举例:
周一到周日:
周一:打卡 周二:未打卡 周三:未打卡 周四:打卡 周五:未打卡 周六:打卡 周日:未打卡
插入:setbit
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
获取某一条记录:getbit
127.0.0.1:6379> getbit sign 3
(integer) 1
统计数量:bitcount
127.0.0.1:6379> bitcount sign
(integer) 3
一组命令的集合。一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行。
一次性、顺序性、排他性。
Redis事务不具备隔离性。所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会被执行。
Redis的单条命令是保证原子性的,但是Redis的事务是不保证原子性的!
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379> set k2 v2 #命令入队
QUEUED
127.0.0.1:6379> set k3 v3 #命令入队
QUEUED
127.0.0.1:6379> get k3 #命令入队
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) OK
4) "v3"
每一组事务执行完成以后,事务就结束了,如果想要执行新的事务,需要重新开启事务。
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379> set k2 v2 #命令入队
QUEUED
127.0.0.1:6379> set k4 v4 #命令入队
QUEUED
127.0.0.1:6379> discard #取消事务
OK
127.0.0.1:6379> get 4 #事务队列中的命令都不会被执行
(nil)
事务中所有的命令都不会被执行。
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 #错误命令
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec #执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4 #所有的命令都不会被执行
(nil)
如果事务队列中存在语法行错误,那么执行命令的时候,其它命令是可以正常执行的,错误命令抛出异常。
127.0.0.1:6379> set k1 'v1' #设置一个字符串
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> incr k1 #命令入队,自增+1,命令是没问题的,但是字符串是不能自增的,在执行的时候会失败
QUEUED
127.0.0.1:6379> set k2 v2 #命令入队
QUEUED
127.0.0.1:6379> set k3 v3 #命令入队
QUEUED
127.0.0.1:6379> exec #执行事务
1) (error) ERR value is not an integer or out of range #虽然第一条命令报错了,但是事务中的其它命令依然正常执行成功了
2) OK
3) OK
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
正常执行成功
127.0.0.1:6379> set money 100 #设置金额为100元
OK
127.0.0.1:6379> set out 0 #设置划出金额为0元
OK
127.0.0.1:6379> watch money #监听money
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> decrby money 10 #金额减10
QUEUED
127.0.0.1:6379> incrby out 10 #划出金额加10
QUEUED
127.0.0.1:6379> exec #执行事务,事务执行成功。
1) (integer) 90
2) (integer) 10
事务在执行完成后,如果想要再次监听,需要重新设置。
127.0.0.1:6379> watch money #监听money
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec #执行之前,另外一个线程,修改了money,这个时候,就会导致事务执行失败
(nil)
如果修改失败,解锁,并重新加锁,开启事务,执行事务即可
127.0.0.1:6379> unwatch #先解锁
OK
127.0.0.1:6379> watch money #在重新加速
OK
127.0.0.1:6379> multi #重新开启事务
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec #重新执行事务
1) (integer) 990
2) (integer) 20
Jedis是官方推荐的java连接开发工具,使用Java操作redis的中间件。
1.导入依赖
redis.clients
jedis
4.0.1
2. 连接redis
package com.zjb;
import redis.clients.jedis.Jedis;
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
}
}
3.连接成功
package com.zjb;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.flushDB();
//开启事务
Transaction multi = jedis.multi();
try {
multi.set("k1","v1");
multi.set("k2","v2");
multi.exec(); //执行事务
}catch (Exception e){
multi.discard(); //取消事务
e.printStackTrace();
}finally {
System.out.println(jedis.get("k1"));
System.out.println(jedis.get("k2"));
jedis.close(); //关闭连接
}
}
}
1.引入依赖
org.springframework.boot
spring-boot-starter-data-redis
2.添加redis配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
3.添加自定义配置类(这是一个固定的配置类,可以直接在企业的项目里边去使用)
package com.zjb.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis配置类
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//Jackson序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//字符串序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
4.测试
package com.zjb.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 必须序列化
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable{
private String name;
private int age;
}
package com.zjb;
import com.zjb.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class Redis03SpringbootApplicationTests {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("user",new User("xiaohong",3));
System.out.println(redisTemplate.opsForValue().get("user"));
}
}
bind 0.0.0.0 # 绑定的ip 远程连接时修改
protected-mode yes #保护模式
port 6379 #端口号
daemonize yes #守护进程 默认no
pidfile /var/run/redis_6379.pid #如果以后台的方式运行,我们就需要指定一个pid文件
loglevel notice #日志级别 debug 、verbose 、notice(适用生产环境)、warning
logfile "" #日志的文件位置名
databases 16 #数据库的数量 默认是16个数据库
always-show-logo yes #是否总是显示logo
#如果900s内,至少有一个key进行了修改,就进行持久化操作
save 900 1
#如果300s内,至少有10个key进行了修改,就进行持久化操作
save 300 10
#如果60s内,至少有10000个key进行了修改,就进行持久化操作
save 60 10000
#持久化如果出错redis是否继续工作
stop-writes-on-bgsave-error yes
#是否压缩rdb文件,需要消耗一些cpu资源
rdbcompression yes
#保存rdb文件时,进行错误检查校验
rdbchecksum yes
#rdb文件保存的目录
dir ./
# redis的密码 默认是空的
requirepass
#最大客户端连接数
maxclients 10000
#配置redis最大的内存容量
maxmemory
#内存到达上限之后的处理策略,六种策略如下:
#1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
#2、allkeys-lru : 删除lru算法的key
#3、volatile-random:随机删除即将过期key
#4、allkeys-random:随机删除
#5、volatile-ttl : 删除即将过期的
#6、noeviction : 永不过期,返回错误
maxmemory-policy noeviction
#默认不开启aof模式,默认使用rdb方式进行持久化
appendonly no
#aof持久化文件的名字
appendfilename "appendonly.aof"
# 每次修改都会同步,消耗性能
# appendfsync always
#每秒同步一次,可能会丢失这一秒的数据
appendfsync everysec
#不执行同步
# appendfsync no
Redis是内存数据库,如果不将内存中的数据保存到磁盘中,那么一旦服务器进程退出,服务器中的数据也会消失。
在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时将快照文件直接读取到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件,待持久化过程都结束了,在用这个临时文件替换上次持久化好的文件,整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能,如果需要进行大规模数据恢复,且对于数据恢复的完整性不是非常敏感,那么RDB方式要比AOF方式更加高效。RDB的缺点是最后一次持久化后的数据可能丢失。默认使用RDB方式。
RDB保存的文件是dump.rdb
1.只需将rdb文件放在redis启动目录下就可以,redis启动的时候会自动检查dump.rdb文件恢复其中的数据
2. 查看需要存放的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" 如果在这个目录下存在dump.rdb文件,启动时就会自动恢复文件中的数据
优点:
缺点:
以日志的形式记录每个写操作,将redis执行过的所有指令记录下来,只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复。
AOF保存的是appendonly.aof文件
默认是不开启的,只需要修改配置文件即可
1.修改配置文件
#将no改为yes即可
appendonly no
2.重启服务
如果appendonly.aof被破坏了,在启动redis服务时就会报错,这时我们应该怎样修复appendonly.aof文件呢?
redis给我们提供了redis-check-aof工具,用来修复appendonly.aof文件。
redis-check-aof --fix appendonly.aof
如果文件正常,重启就可以恢复了。
优点:
缺点:
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis客户端可以订阅任意数量的频道。
订阅、发布消息图:
订阅端
#订阅一个频道
127.0.0.1:6379> subscribe channel
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel"
3) (integer) 1
1) "message"
2) "channel"
3) "hello,redis pub/sub"
发布端
#向频道中发送消息
127.0.0.1:6379> publish channel 'hello,redis pub/sub'
(integer) 1
主从复制,是指将一台redis服务器的数据,复制到其他的redis服务器。前者称为主节点(master/leader),后者被称为从节点(slave/follwer);数据的复制都是单向的,只能由主节点到从节点。master以写为主,Slave以读为主。
默认情况下,每台redis服务器都是主节点,且一个节点可以有多个从节点(或者没有从节点),但一个从节点只能有一个主节点。
只配置从节点,不用配置主节点。主机可以写,从机只能读不能写。
查看当前redis信息
127.0.0.1:6379> info replication #当前redis信息
# Replication
role:master #角色名称
connected_slaves:0 #从节点数量
master_replid:dc74c05af5dfd774beb611e085f0e31f6898bf03
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
配置从节点有两种方式
1.命令:这种方式是暂时的
# 语法: slaveof host port
# 参数说明:
# host: 主节点的IP
# port: 主节点的端口号
slaveof 127.0.0.1 6379
2.修改配置文件
打开以下两个配的注释,并编辑主节点ip、端口号。
#配置主节点 ip 端口号
replicaof
# 当主节点设置的有密码时,配置主节点密码
masterauth
服务启动后该节点就是从节点。
Slave启动成功连接到master后会发送一个sync命令。
master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:master继续将新的所有收集到的修改命令依次传给slave,完成同步。
但只要是重新连接master,一次完全同步(全量复制)将会自动执行。
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用。这不是种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题。
能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,他会独立运行。其原理是哨兵通过发送命令,等待redis服务响应,从而监控运行的多个redis实例。
这里的哨兵有两个作用:
然而一个哨兵进程对redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
1.配置哨兵配置文件,sentinel.conf
#语法:sentinel monitor 被监控的主机名称 host port 1
# 后边这个数字1,代表主机挂了,slave投票看让谁接替成为主机,票数最多的,就会成为主机
sentinel monitor master-redis 127.0.0.1 6379 1
哨兵配置文件不止有这一个配置,但这个配置是最基本的。
2.启动哨兵
redis-sentinel redis-conf/sentinel.conf
如果主机挂掉,查看sentinel的日志,发现已经自动选取从节点为新的主节点。
如果主机恢复了,只能归到新的主节点下,当做从机,这就是哨兵模式的规则。
优点
缺点
#Example sentinel.conf
#哨兵sentinel实例运行的端口 默认是26379
port 26379
#哨兵sentinel的工作目录
dir /tmp
#哨兵sentinel监控的redis主节点的ip port
#master-name 可以自己命名主节点的名字 只能由字母A-z,数字0-9、这三个字符".-_"组成
#quorum配置多少个sentinel哨兵统一认为master主节点失联,那么这时客观上认为主节点失联了
#sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 2
#当在redis实例中开启了requiress foobared 授权密码 这样所有连接redis实例的客户端都要提供密码
#设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
#sentinel auth-pass
sentinel auth-pass mymaster MySUPER--secret-0123passwOrd
#指定多少毫秒之后 主节点没有应答哨兵sentinel此时 哨兵主观上认为主节点下线 默认30秒
#sentinel down-after-milliseconds
sentinel down-after-milliseconds mymaster 30000
#这个配置指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行同步
#这个数字越小,完成failover所需的时间就越长
#如果这个数字越大,就意味着越多slave因为replication而不可用
#可以通过将这个值设为1 来保证每次只有一个slave处于不能处理命令请求的状态
#sentinel paraller-syncs
sentinel paraller-syncs mymaster 1
#故障转移的超时时间 failover-timeout 可以用在以下这些方面
#1.同一个sentinel对同一个master两次failover之间的间隔时间
#2.当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按paraller-syncs所配置的规则来了
#默认三分钟
#sentinel failover-timeout
sentinel failover-timeout mymaster 180000
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1 ,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个sigkill信号终止,之后重新执行
#通知脚本
#sentinel notification-script
sentinel notification-script mymaster /var/redis/notify.sh
#客户端重新配置主节点参数脚本
#当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息
#以下参数将会在调用脚本时传给脚本:
#
#目前总是failover
#是leader或者observer中的一个
#参数from-ip from-port to-ip to-port 是用来和旧的master和新的master通信的
#这个脚本应该是通用的,能被多次调用,不是针对性的。
#sentinel client-reconfig-script
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据库方面。但同时,他也带来了一些问题。其中,最要害的问题就是数据的一致性问题 。从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据数据库没有,也就是缓存未命中,于是向持久层数据库查询,发现也没有,于是本次查询失败,当用户很多的时候,缓存都没有命中,于是都去请求持久层数据库,这会给持久层数据库造成很大的压力,这时候相当于出现了缓存穿透。
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层数据库的查询压力。
缓存空对象
当存储层未命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后在访问这个数据会从缓存中获取,保护了后端数据源。
但是这种方法会存在两个问题:
这里需要注意和缓存穿透的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像一个屏障凿开了一个大洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且返回写缓存,会导致数据库瞬间压力过大。
设置热点数据永不过期
从缓存层面看,没有设置过期时间,所以不会出现热点key过期后产生的问题。
加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
缓存雪崩是指在某一个时间段,缓存集中过期失效。或者Redis集群宕机。
产生雪崩的原因之一,比如在写文本的时候,马上到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时,那么到凌晨一点钟的时候,这批商品的缓存都快过期了,而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网,因为自然形成的缓存雪崩 ,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的,无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我就多设几台,这样一台挂掉之后其它的还可以继续工作,其实就是搭建的集群。
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其它线程等待。
数据预热
数据加热的含义就是在正式部署之前,先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。