一. Redis安装
1.1安装Redis
version: '3.1'
services:
redis:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis
environment:
- TZ=Asia/Shanghai
ports:
- 6379:6379
二. Redis常用的命令
2.1 Redis存储数据的结构
常用的5种数据结构:
- key-string:一个key对应一个值。一般用于存储一个值。
- key-hash:一个key对应一个Map。存储一个对象数据。
- key-list:一个key对应一个列表。使用list结构实现栈结构和队列结构。
- key-set:一个key对应一个集合。交集,差集和并集的操作。
- key-zset:一个key对应一个有序的集合。排行榜或积分存储等操作。
另外3种数据结构:
- HyperLogLog:计算近似值。
- GEO:存储地理位置。
- BIT:一般存储的也是一个字符串,存储的是一个byte[]。
2.2 string结构的常用命令
# 1.添加值(如果key已经持有其他值,SET就覆写旧值,无视类型)
set key value
# 2.取值
get key
# 3.批量操作
mset key1 value1 key2 value2 .. keyN valueN
mget key1 key2 .. keyN
# 4.自增命令(+1)
incr key
# 5.自减命令(-1)
decr key
# 6.自增或自减指定数量
incrby key num
decrby key num
# 7.设置值的同时,指定生存时间
setex key second value
# 8.如果key存在,什么都不做,不存在的话,和set命令一样
setnx key value
# 9.在key对应的value后,追加内容
append key value
# 10.查看value字符串的长度
strlen key
2.3 hash常用命令
# 1.存储数据
hset key field value
# 2.获取数据
hget key field
# 3.批量操作
hmset key field1 value1 field2 value2 .. fieldN valueN
hmget key field1 field2 .. fieldN
2.4 key的常用命令
# 1.查看Redis中的全部的key(pattern: *,xxx*,*xxx)
keys pattern
# 2.查看某一个key是否存在(1->存在,0->不存在)
exists key
# 3.删除key
del key1 key2 .. keyN
# 4.设置key的生存时间,单位为秒,或毫秒,设置能活多久
expire key second
pexpire key millisrcond
# 5.设置key的生存时间,单位为秒,或毫秒,设置活到哪一个时间点
expireat key timestamp
pexpireat key millisrcond
# 6.查看key的剩余生存时间,单位为秒,或毫秒(-2 -> 当前key不存在,-1 -> 当前key没有设置生存时间,具体的剩余生存时间)
ttl key
pttl key
# 7.移除key的生存时间(1 - 移除成功,0 - key不存在生存时间,key不存在)
persist key
# 8.移动key到另一个库中
move key db
2.5 库的操作命令
# 1.选择操作库
select 0~15
# 2.清空当前库
flushdb
# 3.清空所有库
flushall
# 4.查询当前库有多少个key
dbsize
# 5.最后一次操作的时间
lastsave
# 6.实时监控Redis服务接受到的命令
monitor
三. Java连接Redis
3.1 Jedis连接Redis
创建maven项目
导入需要的依赖
<dependencies>
<!--Jedis依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!--Junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
</dependencies>
测试
//1.连接Redis
Jedis jedis = new Jedis("xxx.xxx.xxx.xxx",6379);
//2.操作Redis
jedis.set("name","李四");
//3.释放资源
jedis.close();
3.2 Jedis如何存储|获取一个对象,以byte[]的形式
准备一个实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private Integer id;
private String name;
private Date birthday;
}
导入spring-context依赖,以方便对象在字节数组之间转换
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
以byte[]形式存储对象
//存储对象 - 以byte[]形式存储在Redis中
//1。连接Redis
Jedis jedis = new Jedis("xxx.xxx.xxx.xxx",6379);
//2.1 准备key(String)-value(User)
String key = "user";
User value = new User(1,"张三",new Date());
//2.2 将key和value转换为byte[]
byte[] byteKey = SerializationUtils.serialize(key);
byte[] byteValue = SerializationUtils.serialize(value);
//2.3 将key和value存储到Redis中
jedis.set(byteKey,byteValue);
//3. 释放资源
jedis.close();
以byte[]形式获取对象
//获取对象 - 以byte[]形式在Redis中获取
//1。连接Redis
Jedis jedis = new Jedis("xxx.xxx.xxx.xxx",6379);
//2.1 准备key
String key = "user";
//2.2 将key转换为byte[]
byte[] byteKey = SerializationUtils.serialize(key);
//2.3 jedis去Redis中获取value
byte[] byteValue = jedis.get(byteKey);
//2.4 将value反序列化为User对象
User user = (User)SerializationUtils.deserialize(byteValue);
//2.5 输出
System.out.println(user);
//3. 释放资源
jedis.close();
3.3 Jedis如何存储|获取一个对象,以String的形式
为了把对象转换成String形式,要导入fastjson依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
以String形式存储对象
//以String形式存储对象
//1.连接Redis
Jedis jedis = new Jedis("xxx.xxx.xxx.xxx",6379);
//2.1准备key(String)-value(User)
String stringKey = "stringUser";
User value = new User(2,"李四",new Date());
//2.2使用fastJSON将value转化为json字符串
String stringValue = JSON.toJSONString(value);
//2.3存储到Redis中
jedis.set(stringKey,stringValue);
//3.释放资源
jedis.close();
以String形式获取对象
//以String形式获取对象
//1.连接Redis
Jedis jedis = new Jedis("xxx.xxx.xxx.xxx",6379);
//2.1准备key
String stringKey = "stringUser";
//2.2jedis去Redis中获取value
String value = jedis.get(stringKey);
//2.3将value反序列化为User对象
User user = JSON.parseObject(value, User.class);
//2.4输出
System.out.println(user);
//3.释放资源
jedis.close();
3.4 Jedis连接池的操作
上面我们每操作一次都要创建连接,最后释放连接,如果还要操作的话还得做同样的操作,这会对性能有很大的影响。所以用连接池的话,对性能影响不大。
//1. 创建连接池
JedisPool pool = new JedisPool("xxx.xxx.xxx.xxx",6379);
//2. 通过连接池获取jedis对象
Jedis jedis = pool.getResource();
//3. 操作
String user = jedis.get("xxx"); //xxx为key
System.out.println(user);
//4. 释放资源,把jedis对象还给连接池
jedis.close();
还可以设置连接池的基本参数
//1. 创建连接池的配置信息
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(100); //设置连接池的活跃数
poolConfig.setMaxIdle(10); //最大空闲数
poolConfig.setMinIdle(5); //最小空闲数
poolConfig.setMaxWaitMillis(3000); //当连接池空了之后,多久没获取到Jedis对象,就超时,设置timeout时间
//2. 创建连接池
JedisPool pool = new JedisPool(poolConfig,"xxx.xxx.xxx.xxx",6379);
//3. 通过连接池获取jedis对象
Jedis jedis = pool.getResource();
//4. 操作
String user = jedis.get("xxx"); //xxx为key
System.out.println(user);
//5. 释放资源,把jedis对象还给连接池
jedis.close();
四. Redis的管道操作
假如客户端要给Redis服务器发送了三个命令,它会如下图一样分三批次执行,但是执行命令并反馈时网络延迟等等问题消耗的时间很大,所以Redis的管道操作就是解决这个问题的。管道操作就是一次性把所有要执行的命令捆在一起,给Redis服务器发过去。
管道操作
普通操作
//1. 创建连接池
JedisPool pool = new JedisPool("192.168.0.16",6379);
long l = System.currentTimeMillis();
//2. 获取连接对象
Jedis jedis = pool.getResource();
//3. 执行incr - 100000次
for (int i = 0; i < 100000; i++) {
jedis.incr("pp");
}
//4. 释放资源
jedis.close();
System.out.println(System.currentTimeMillis()-l);
管道操作
//*************管道操作************
//1. 获取连接对象
long l = System.currentTimeMillis();
Jedis jedis = pool.getResource();
//2. 创建管道
Pipeline pipeline = jedis.pipelined();
//3. 执行incr - 100000次
for (int i = 0; i < 100000; i++) {
pipeline.incr("qq");
}
//4. 执行命令
pipeline.syncAndReturnAll();
//5. 释放资源
jedis.close();
System.out.println(System.currentTimeMillis()-l);
五. Redis其他配置及集群
修改yml文件,以方便后期修改Redis配置信息
version: '3.1'
services:
redis:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis
environment:
- TZ=Asia/Shanghai
ports:
- 6379:6379
volumes:
- ./conf/redis.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
5.1 Redis的AUTH
我们之前都是直接连接Redis服务的,这样会不安全,这个AUTH相当于密码
方式一:在redis.conf文件中添加以下配置信息就可以了啦
# 后面为密码
requirepass admin
之后在Java连接Redis时也需要这个,只需添加如下代码
jedis.auth("admin")
如果用连接池的话可以运用以下构造函数
public JedisPool(GenericObjectPoolConfig poolConfig, String host, int port, int timeout, String password)
public JedisPool(GenericObjectPoolConfig poolConfig, String host, int port, int timeout, String password, int database)
方式二:在不修改redis.conf文件的前提下,在第一次连接Redis时,输入命令:config set requirepass 密码,后续再次操作Redis时,需要先auth做一下校验
5.2Redis的持久化机制
5.2.1 RDB持久化机制
RDB是Redis默认的持久化机制,在指定的时间间隔内将内存中的数据集快照写入磁盘。RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
RDB持久化机制特点:
RDB持久化文件,速度比较快,而且存储的是二进制的文件,传输起来很方便。
RDB持久化的时机:
save 900 1 # 900秒之内,有一个key改变了,就执行RDB持久化,以下都一个意思
- save 300 10
save 60 10000
RDB无法保证数据的绝对安全。
#RDB持久化机制配置文件
# 代表RDB执行的时机
save 900 1 # 900秒之内,有一个key改变了,就执行RDB持久化,以下都一个意思
save 300 10
save 60 10000
# 开启RDB持久化的压缩
rdbcompression yes
# RDB持久化文件的名称
dbfilename dump.rdb
5.2.2 AOF持久化机制
另一种持久化方式叫AOF,默认是关闭的,原理是将Reids的操作日志以追加的方式写入文件。AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
# AOF配置文件
appendonly yes # 开启AOF持久化
appendfilename "redis.aof" # AOF文件的名称
# 以下配置只能三选一
appendfsync always #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no #从不同步。高效但是数据不会被持久化。
5.2.3二者优缺点
RDB存在哪些优势呢?
- 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
- 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
- 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
- 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。
RDB又存在哪些劣势呢?
- 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
- 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
AOF的优势有哪些呢?
- 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。
- 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。
- 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。
- AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。
AOF的劣势有哪些呢?
- 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
- 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。
- 二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。不过生产环境其实更多都是二者结合使用的。
如果同时开启了AOF和RDB持久化,那么在Redis宕机重启之后,需要加载一个持久化文件,优先选择AOF文件。
如果先开启了RDB,再次开启AOF,如果RDB执行了持久化,那么RDB文件中的内容会被AOF覆盖掉。
5.3 Redis的事务
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会放在一个队列中。在事务执行过程,会按照顺序串行化执行队列中的命令,如果取消了事务,一个队列中的命令全部作废。其他客户端提交的命令请求不会插入到事务执行命令序列中。
# 开启事务命令
multi
# 执行事务命令
exec
# 取消事务命令
discard
如上图,开启事务后,执行了两个命令,返回QUEUED,表示把命令存到了队列之中,之后执行exec命令执行事务。
Redis的事务想发挥功能,需要配合watch监听机制。
在开启事务之前,先通过watch命令去监听一个或多个key,在开启事务之后,如果有其他客户端修改了监听的key,事务会自动取消。
如果执行了事务,或者取消了事务,watch监听会自动消除,一般不需要手动执行unwatch命令。
5.4 Redis的主从架构
单机版Redis存在读写瓶颈的问题,搭建主从架构会能解决这个问题。
version: '3.1'
services:
redis1:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis1
environment:
- TZ=Asia/Shanghai
ports:
- 7001:6379
volumes:
- ./conf/redis1.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
redis2:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis2
environment:
- TZ=Asia/Shanghai
ports:
- 7002:6379
volumes:
- ./conf/redis2.conf:/usr/local/redis/redis.conf
links:
- redis1:master
command: ["redis-server","/usr/local/redis/redis.conf"]
redis3:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis3
environment:
- TZ=Asia/Shanghai
ports:
- 7003:6379
volumes:
- ./conf/redis3.conf:/usr/local/redis/redis.conf
links:
- redis1:master
command: ["redis-server","/usr/local/redis/redis.conf"]
从节点的配置
replicaof master 6379 # master是links后面的别名
5.5 Redis的哨兵
如果在Redis集群中master节点挂了,Redis的主从架构就崩了。哨兵就可以解决这个问题。master节点挂了,哨兵会从剩下的从节点中选出一个节点,让它当master节点。
修改了docker-compose.yml,为了可以在容器内部使用哨兵的配置。
version: '3.1'
services:
redis1:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis1
environment:
- TZ=Asia/Shanghai
ports:
- 7001:6379
volumes:
- ./conf/redis1.conf:/usr/local/redis/redis.conf
- ./conf/sentinel1.conf:/data/sentinel.conf # 修改处
command: ["redis-server","/usr/local/redis/redis.conf"]
redis2:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis2
environment:
- TZ=Asia/Shanghai
ports:
- 7002:6379
volumes:
- ./conf/redis2.conf:/usr/local/redis/redis.conf
- ./conf/sentinel2.conf:/data/sentinel.conf # 修改处
links:
- redis1:master
command: ["redis-server","/usr/local/redis/redis.conf"]
redis3:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis3
environment:
- TZ=Asia/Shanghai
ports:
- 7003:6379
volumes:
- ./conf/redis3.conf:/usr/local/redis/redis.conf
- ./conf/sentinel3.conf:/data/sentinel.conf # 修改处
links:
- redis1:master
command: ["redis-server","/usr/local/redis/redis.conf"]
准备哨兵的配置文件,并且在容器内部手动开启哨兵即可。
# 哨兵需要后台启动
daemonize yes
# 指定Master节点的ip和端口(主)
sentinel monitor master localhost 6379 2
# 指定Master节点的ip和端口(从)
sentinel monitor master master 6379 2
# 哨兵每个多久监听一次redis架构
sentinel down-after-milliseconds mymaster 10000
5.6 Redis的集群
Redis集群在保证主从加哨兵的基本功能之外,还能够提升Redis存储数据的能力。
这里从节点和上面的主从架构的从节点是不同的概念。
这个从节点只管做备份的操作,不做读操作,如果他的配分数据的redis服务挂了,这个从节点就顶替他的位置
准备一个docker-compose.yml文件
version: "3.1"
services:
redis1:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis1
environment:
- TZ=Asia/Shanghai
ports:
- 7001:7001
- 17001:17001
volumes:
- ./conf/redis1.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
redis2:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis2
environment:
- TZ=Asia/Shanghai
ports:
- 7002:7002
- 17002:17002
volumes:
- ./conf/redis2.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
redis3:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis3
environment:
- TZ=Asia/Shanghai
ports:
- 7003:7003
- 17003:17003
volumes:
- ./conf/redis3.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
redis4:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis4
environment:
- TZ=Asia/Shanghai
ports:
- 7004:7004
- 17004:17004
volumes:
- ./conf/redis4.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
redis5:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis5
environment:
- TZ=Asia/Shanghai
ports:
- 7005:7005
- 17005:17005
volumes:
- ./conf/redis5.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
redis6:
image: daocloud.io/library/redis:5.0.7
restart: always
container_name: redis6
environment:
- TZ=Asia/Shanghai
ports:
- 7006:7006
- 1700:17006
volumes:
- ./conf/redis6.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
redis.conf配置文件,下面是redis1的配置文件,其它的修改一下就可以啦。
# 指定redis的端口号
port 7001
# 开启集群
cluster-enabled yes
# 集群信息的文件
cluster-config-file nodes-7001.conf
# 集群的对外ip地址
# Linux主机的ip地址
cluster-announce-ip 192.168.0.17
# Redis的端口
cluster-announce-port 7001
# 集群的总线端口
cluster-announce-bus-port 17001
启动了6个Redis的节点。
随便跳转到一个容器内部,使用redis-cli管理集群。
redis-cli --cluster create 192.168.0.17:7001 192.168.0.17:7002 192.168.0.17:7003 192.168.0.17:7004 192.168.0.17:7005 192.168.0.17:7006 --cluster-replicas 1
5.7 Java连接Redis集群
使用JedisCluster对象连接Redis集群
// 创建Set<HostAndPort> nodes
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
nodes.add(new HostAndPort("192.168.0.17",7001));
nodes.add(new HostAndPort("192.168.0.17",7002));
nodes.add(new HostAndPort("192.168.0.17",7003));
nodes.add(new HostAndPort("192.168.0.17",7004));
nodes.add(new HostAndPort("192.168.0.17",7005));
nodes.add(new HostAndPort("192.168.0.17",7006));
// 创建JedisCluster对象
JedisCluster jedisCluster = new JedisCluster(nodes);
// 操作
String value = jedisCluster.get("b");
System.out.println(value);
六. Redis的常见问题
6.1 key的生存时间到了,Redis会立即删除么?
不会立即删除。
定期删除:
Redis每隔一段时间就会去查看Redis设置了过期时间的key,会在100ms的间隔中默认查看3个key。
惰性删除:
如果当你查询(get)一个已经过了生存时间的key时,Redis会先查看当前key的生存时间,是否已经到达了,直接删除当前key,并且给用户返回一个空值。
6.2 Redis的淘汰机制
在Redis内存已经满的时候,添加了一个新的数据,就会执行淘汰机制。
- volatile-lru:在内存不足时,Redis会在设置了生存时间的key中干掉一个最近最少使用的key。
- allkeys-lru:在内存不足时,Redis会在全部的key中干掉一个最近最少使用的key。
- volatile-lfu:在内存不足时,Redis会在设置了生存时间的key中干掉一个最近最少频次使用的key。
- allkeys-lfu:在内存不足时,Redis会在全部的key中干掉一个最近最少频次使用的key。
- volatile-random:在内存不足时,Redis会在设置了生存时间的key中随机干掉一个key。
- allkeys-random:在内存不足时,Redis会在全部的key中随机干掉一个key。
- volatile-ttl:在内存不足时,Redis会在设置了生存时间的key中干掉一个剩余时间最少的key。
- noeviction:在内存不足时,直接报错。默认的配置
指定淘汰机制的方式:maxmemory-policy 具体机制
指定Redis的最大内存:maxmemory