Redis入门使用
1 Redis
1.1 简介
1.1.1 NoSQL 简介
目前市场主流数据存储都是使用关系型数据库。每次操作关系型数据库时都是I/O操作,I/O操作是主要影响程序执行性能原因之一,连接数据库关闭数据库都是消耗性能的过程。尽量减少对数据库的操作,能够明显的提升程序运行效率。
针对上面的问题,市场上就出现了各种 NoSQL(Not Only SQL,不仅仅可以使用关系型数据库)数据库,它们的宣传口号:不是什么样的场景都必须使用关系型数据库,一些特定的场景使用 NoSQL 数据库更好。
常见 NoSQL 数据库:
memcached :键值对,内存型数据库,所有数据都在内存中。
Redis:和Memcached类似,还具备持久化能力。
HBase:以列作为存储。
MongoDB:以Document做存储。
1.1.2 Redis简介
Redis 是以 Key-Value 形式进行存储的 NoSQL 数据库。
Redis 是使用 C 语言进行编写的。
平时操作的数据都在内存中,效率特高,读的效率 110000/s,写 81000/s,所以多把 Redis 当做缓存工具使用。
Redis以 solt(槽)作为数据存储单元,每个槽中可以存储 N 多个键值对。Redis中固定具有16384。理论上可以实现一个槽是一个Redis。每个向Redis存储数据的 key 都会进行 crc16 算法得出一个值后对 16384 取余就是这个 key 存放的 solt 位置。
同时通过 Redis Sentinel 提供高可用,通过 Redis Cluster 提供自动分区。
1.2 Redis 单机版安装
1 安装 C 语言依赖
# yum install -y gcc-c++ automake autoconf libtool make tcl
进行编译
# make
2 解压 redis 安装包
# tar -xzvf redis-5.0.5.tar.gz
到解压目录下进行编译
# make
安装
# make install PREFIX=/usr/local/redis
3 修改配置
拷贝 解压包下的 redis.conf 到 redis 的安装目录下
# cp reids.conf /usr/local/redis
编辑 redis.conf
# cd /usr/local/redis/bin
# vim redis-conf
修改 daemonize 的值由 yes 开启守护进程
注释 bind 127.0.0.1
修改 protected-mode 的值为 no
4 启动测试
# ./redis-server redis.conf
启动客户端命令
# ./redis-cli
1.3 Redis 常用命令
redis 以 key-value 的方式存储数据
key 为 String 类型
value 有以下五种类型
String 字符串类型
Hash 哈希表
List 列表
Set 集合
Sorted Set 有序集合
1.3.1 key 操作
判断 key 是否存在 存在返回数字,不存在返回 0
# exists [key]
exprie :设置 key 的过期时间 成功则返回 1 不成功则返回 0
ttl:查看 key 剩余生存时间 time to live
# expire [key] [seconds]
删除 key
# del [key]
查看当前 redis 中 key 数据
# keys [pattern]
# keys *
查看 key 对应的 value 类型
# type [key]
1.3.2 字符串值操作
set : 设置指定 key 的值
get : 查询指定 key 的值
# set [key] [value]
# get [key]
setnx : 当且仅当 key 不存在的时候新增 SET IF NOT EXISTS
# setnx [key] [value]
setex : 是一个原子性操作,为指定的 key 设置值和过期时间
# setex [key] [seconds] [value]
incr : 用于 value 值的递增,默认步长为 1
decr : 用于 value 值的递减,默认步长为 1
redis 是单线程的,不会存在并发(业务:评论数点赞数)
# incr [key]
# decr [key]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YbBEc0EX-1624767035609)(E:\Java\img\image-20201212191813316.png)]
1.3.3 hash 值操作
设置 value 值
# hset [key] [field] [value]
# hset [key] [field] [value] [field] [value] [field] [value] ...
获取 value 值
# hget [key] [value]
# hvals [key]
获取 value 中的 field 字段名以及对应的值
# hgetall [key]
删除 value 中的字段以及其对应的值
# hdel [key] [field] [field]
1.3.4 list 值操作
在指定 key 的值列表末尾添加值
# rpush [key] [value] [value] ...
从左到右遍历指定 key 的 value 值
// -1 表示到末尾
# lrange [key] [start] [end]
在列表的前面添加值
# lpush [key] [value] [value]
获取列表长度
# llen [key]
**从左到右删除指定的 value ,count 表示个数 **
正整数表示从左到右删除的个数 负整数表示从右到左
# lrem [key] [count] [value]
1.3.5 集合(set)操作
sadd
向集合中添加内容。不允许重复。
语法:sadd key value value value
返回值:本次命令新增数据个数
scard
返回集合元素数量
scard key
返回值:集合长度
smembers
查看集合中元素内容
语法:smembers key
返回值:集合中元素
srem
删除集合中的元素
语法: srem key member [member...]
返回值:删除元素个数
sinter
获取多个集合的交集
语法: sinter key [key ...]
返回:多个集合的交集元素
1.3.6 有序集合(Sorted Set)操作
127.0.0.1:6379> select 2
OK
127.0.0.1:6379[2]> zadd names 1.0 cl 2.0 ky 2.1 cc
(integer) 3
127.0.0.1:6379[2]> ZRANGE names 0 2
1) "cl"
2) "ky"
3) "cc"
127.0.0.1:6379[2]> ZRANGE names 0 2 withscores
1) "cl"
2) "1"
3) "ky"
4) "2"
5) "cc"
6) "2.1000000000000001"
127.0.0.1:6379[2]> zrem names cl
(integer) 1
127.0.0.1:6379[2]> keys *
1) "names"
127.0.0.1:6379[2]> ZRANGE names 0 -1
1) "ky"
2) "cc"
127.0.0.1:6379[2]>
1.4 Redis 持久化策略
1.4.1 RDB
RDB redis database 一种基于内存快照的持久化策略
RDB 模式是默认模式,可以在指定的时间间隔内生成数据快照(snapshot),
默认保存到dump.rdb文件中,
当redis重启后会自动加载dump.rdb文件中内容到内存中。
用户可以使用SAVE(同步)或BGSAVE(异步)手动保存数据。
可以设置服务器配置的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令。
优点
RDB 文件是一个紧凑文件,直接使用 RDB 文件就可以还原数据。
数据保存会一个子进程进行保存,不影响父进程。
恢复数据的效率要高于 AOF
缺点
每次保存点之间,因 redis 不可意料的关闭,可能会导致丢失数据。
由于每次保存数据都需要 fork() 子进程,在数据量比较大时可能会比较耗费性能(可忽略)。
默认配置文件:
查看 dump.rdb:
[root@localhost ~]# cd /usr/local/redis/bin/
[root@localhost bin]# ls -a
. dump.rdb redis-check-aof redis-cli redis-sentinel
.. redis-benchmark redis-check-rdb redis.conf redis-server
[root@localhost bin]# vim dump.rdb
[root@localhost bin]#
1.4.2 AOF
AOF append only file 一种基于操作日志的持久化策略
AOF默认是关闭的,需要在配置文件中开启AOF。Redis支持AOF和RDB同时生效,如果同时存在,
AOF优先级高于RDB(Redis重新启动时会使用AOF进行数据恢复)
监听执行的命令,如果发现执行了修改数据的操作,同时直接同步到数据库文件中。
AOP类似日志型的持久化,数据库key发生改变马上同步磁盘
优点
相对RDB数据更加安全。
不会造成数据丢失
缺点
相同数据集AOF要大于RDB。
相对RDB可能会慢一些。
1.5 Redis 主从复制
Redis 主从复制功能保证了单一节点可用性,每一个节点都有 N 个复制品(replica)
其中一个是主节点(master) 其他的节点都是从节点,实现一主多从。
(每一个从节点可以看成又一个主节点)
1.5.1 主从优点
- 增加单一节点的健壮性,从而提升整个集群的稳定性。(Redis中当超过1/2节点不可用时,整个集群不可用)
- 从节点可以对主节点数据备份,提升容灾能力。
- 读写分离。在redis主从中,主节点一般用作写(具备读的能力),从节点只能读,利用这个特性实现读写分离,写用主,读用从。
1.5.2 搭建主从复制
1.5.2.1 创建目录
[root@localhost ~]# mkdir /usr/local/replica
[root@localhost ~]# cd /usr/local/replica/
[root@localhost replica]# pwd
/usr/local/replica
1.5.2.2 从 redis 单机中复制 bin 目录下的配置文件
[root@localhost redis]# cp -r bin /usr/local/replica/master
[root@localhost redis]# cp -r bin /usr/local/replica/slave1
[root@localhost redis]# cp -r bin /usr/local/replica/slave2
1.5.2.3 修改各个从节点的配置文件
[root@localhost redis]# vim /usr/local/replica/slave1/redis.conf
[root@localhost redis]# vim /usr/local/replica/slave2/redis.conf
在从节点中指定自己的端口以及主节点的 ip 和端口
port 6380
replicaof 192.168.72.12 6379
1.5.2.4 启动三个 redis 实例
添加 .sh 脚本启动实例
cd /usr/local/replica/master
./redis-server redis.conf
cd /usr/local/replica/slave1
./redis-server redis.conf
cd /usr/local/replica/slave2
./redis-server redis.conf
[root@localhost replica]# vim startup.sh
# 修改 startup.sh 权限
[root@localhost replica]# chmod u+x startup.sh
# 执行 startup.sh
[root@localhost replica]# ./startup.sh
1.5.3 测试
1.5.3.1 查看 redis 启动进程
# 查看 redis 启动进程
[root@localhost master]# ps -aux | grep redis
root 1523 0.1 0.8 273296 15872 ? Ssl 22:04 0:00 ./redis-server *:6379
root 1535 0.1 0.5 269200 9588 ? Ssl 22:07 0:00 ./redis-server *:6380
root 1540 0.1 0.9 273296 17880 ? Ssl 22:07 0:00 ./redis-server *:6381
root 1577 0.0 0.0 221904 1084 pts/0 S+ 22:16 0:00 grep --color=auto redis
1.5.3.2 数据演示
# 只有主节点能进行读写
# 从节点只读
[root@localhost master]# ./redis-cli -p 6380
127.0.0.1:6380> keys *
1) "age"
2) "password"
3) "username"
127.0.0.1:6380> set phone 12345678910
OK
127.0.0.1:6380>
[root@localhost master]# ./redis-cli -p 6379
127.0.0.1:6379> set id 416240304
(error) READONLY You can't write against a read only replica.
127.0.0.1:6379>
[root@localhost master]# ./redis-cli -p 6381
127.0.0.1:6381> set id 416240304
(error) READONLY You can't write against a read only replica.
127.0.0.1:6381>
1.5.4 主从复制总结
1、主节点可以进行读写操作,单数据变化时自动将数据同步到从节点
2、从节点是只读的
3、一个主节点可以拥有多个从节点,但一个从节点只能对应一个主节点
4、从节点宕机不影响其他从节点以及主节点的读写
5、主节点宕机,不影响从节点的读,但是整个节点不具备写功能
1.6 哨兵
redis 中默认只有主节点具备写的能力,假设主节点宕机,那么整个节点不具备写的能力。
Redis的哨兵可以帮助监控整个节点,当主节点宕机等情况下,帮助重新选取主节点。
Redis中哨兵支持单哨兵和多哨兵:
单哨兵:只要哨兵发现主节点宕机了,就直接选取另一个 从节点作为主节点。
多哨兵:根据我们设定,达到一定数量哨兵认为主节点宕机后才会进行重新选取主节点
1.6.1 搭建多哨兵
# 新建哨兵目录
[root@localhost ~]# mkdir /usr/local/sentinel
# 复制 redis 配置文件 到 sentinel 目录下
[root@localhost sentinel]# cp -r /usr/local/redis/bin/* /usr/local/sentine
# 复制 sentinel.conf 到 sentinel 目录下
[root@localhost sentinel]# cd /usr/local/tmp/redis-5.0.5/
[root@localhost redis-5.0.5]# cp sentinel.conf /usr/local/sentinel/
[root@localhost redis-5.0.5]# cd /usr/local/sentinel/
[root@localhost sentinel]# mv sentinel.conf sentinel-6379.conf
# 修改 sentinel-6379.conf 文件 配置其中的port(6379)、 daemonize(yes)、 sentinel monitor mymaster 192.168.72.12 6379 2
[root@localhost sentinel]# vim sentinel-6379.conf
[root@localhost sentinel]# cp sentinel-6379.conf sentinel-6380.conf
[root@localhost sentinel]# vim sentinel-6380.conf
[root@localhost sentinel]# cp sentinel-6379.conf sentinel-6381.conf
[root@localhost sentinel]# vim sentinel-6381.conf
# 编写脚本启动命令
[root@localhost sentinel]# vim startup.sh
# 设置 startup.sh 权限
[root@localhost sentinel]# chmod u+x startup.sh
# 启动所有哨兵
[root@localhost sentinel]# ./startup.sh
1700:X 26 Dec 2020 15:20:15.730 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1700:X 26 Dec 2020 15:20:15.730 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=1700, just started
1700:X 26 Dec 2020 15:20:15.730 # Configuration loaded
1702:X 26 Dec 2020 15:20:15.733 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1702:X 26 Dec 2020 15:20:15.733 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=1702, just started
1702:X 26 Dec 2020 15:20:15.733 # Configuration loaded
1707:X 26 Dec 2020 15:20:15.738 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1707:X 26 Dec 2020 15:20:15.738 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=1707, just started
1707:X 26 Dec 2020 15:20:15.738 # Configuration loaded
[root@localhost sentinel]#
1.6.2 启动主从
[root@localhost master]# ./redis-cli
127.0.0.1:6379> keys *
1) "username"
127.0.0.1:6379> set password 12345
OK
127.0.0.1:6379> keys *
1) "username"
2) "password"
127.0.0.1:6379>
[root@localhost master]# ./redis-cli -p 6380
127.0.0.1:6380> set age 12
(error) READONLY You can't write against a read only replica.
127.0.0.1:6380>
[root@localhost master]# ./redis-cli -p 6381
127.0.0.1:6381> set age 12
(error) READONLY You can't write against a read only replica.
127.0.0.1:6381>
1.6.3 测试哨兵
# 关闭端口为 6839 的主节点
[root@localhost master]# ./redis-cli shutdown
# 查看 redis 实例启动状态
[root@localhost master]# ps -aux | grep redis
[root@localhost master]# ./redis-cli -p 6380
127.0.0.1:6380> set age 12
OK
127.0.0.1:6380> keys *
1) "age"
2) "username"
3) "password"
127.0.0.1:6380>
[root@localhost master]# ./redis-cli -p 6381
127.0.0.1:6381> set phone 12345678919
(error) READONLY You can't write against a read only replica.
127.0.0.1:6381>
# 重新启动端口为 6379 的节点
[root@localhost master]# ./redis-server redis.conf
1783:C 26 Dec 2020 15:47:19.758 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1783:C 26 Dec 2020 15:47:19.758 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=1783, just started
1783:C 26 Dec 2020 15:47:19.758 # Configuration loaded
[root@localhost master]# ./redis-cli -p 6379
# 节点角色变为从节点
127.0.0.1:6379> role
1) "slave"
2) "192.168.72.12"
3) (integer) 6380
4) "connect"
5) (integer) -1
127.0.0.1:6379>
[root@localhost master]# ./redis-cli -p 6380
127.0.0.1:6380> role
1) "master"
2) (integer) 340199
3) 1) 1) "192.168.72.12"
2) "6381"
3) "340058"
2) 1) "192.168.72.12"
2) "6379"
3) "340199"
127.0.0.1:6380>
[root@localhost master]# ./redis-cli -p 6381
127.0.0.1:6381> role
1) "slave"
2) "192.168.72.12"
3) (integer) 6380
4) "connected"
5) (integer) 341905
127.0.0.1:6381>
1.7 集群(cluster)
1.7.1 复制 redis 单机版中的配置文件
[root@localhost bin]# cp /usr/local/redis/bin/redis.conf /usr/local/redis/bin/redis-7001.conf
1.7.2 修改 redis-7001.conf
port 7001
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 15000
appendonly yes
daemonize yes
protected-mode no
pidfile /var/run/redis_7001.pid
1.7.3 复制多份配置文件并修改内容
1.7.4 编辑脚本命令启动6个 redis
1.7.5 建立集群
./redis-cli --cluster create 192.168.72.12:7001 192.168.72.12:7002 192.168.72.12:7003 192.168.72.12:7004 192.168.72.12:7005 192.168.72.12:7006
--cluster-replicas 1
1.7.6 集群测试
# 启动端口为 7001 的客户端
[root@localhost bin]# ./redis-cli -p 7001 -c
127.0.0.1:7001> keys *
(empty list or set)
# 分配的槽位 14315 在 端口为 7003 的 redis 的服务器上 自动跳转到对应服务器
127.0.0.1:7001> set username cl
-> Redirected to slot [14315] located at 192.168.72.12:7003
OK
192.168.72.12:7003> keys *
1) "username"
192.168.72.12:7003> get username
"cl"
192.168.72.12:7003> set password 1234
-> Redirected to slot [9540] located at 192.168.72.12:7002
OK
192.168.72.12:7002> keys *
1) "password"
192.168.72.12:7002> set age 12
-> Redirected to slot [741] located at 192.168.72.12:7001
OK
192.168.72.12:7001> keys *
1) "age"
192.168.72.12:7001> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.72.12,port=7005,state=online,offset=1663,lag=0
master_replid:9ce824bf758c4496086a700ad5c9c9f62f7db393
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1663
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1663
192.168.72.12:7001> info cluster
# Cluster
cluster_enabled:1
1.8 SpringBoot 整合 Redis
1.8.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1.8.2 在 classpath 目录下编写配置文件
# 配置单机 redis
# spring:
# redis:
# host: 192.168.72.12
# 端口默认 6379
# port: 6379
# 配置集群环境
spring:
redis:
cluster:
nodes: 192.168.72.12:7001,192.168.72.12:7002,192.168.72.12:7003,192.168.72.12:7004,192.168.72.12:7005,192.168.72.12:7006
1.8.3 编写配置类
package com.cl.redis.config;
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;
/**
* @Author chenlan
* @Description TODO
* @Date 2020/12/27 11:56
* @Version 1.0
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
return redisTemplate;
}
}
1.8.4 编写代码
package com.cl.redis;
import com.cl.redis.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import java.util.List;
/**
* @Author chenlan
* @Description TODO
* @Date 2020/12/27 12:10
* @Version 1.0
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringData4RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testObject(){
ValueOperations vos = redisTemplate.opsForValue();
// 编写值序列化器
// 不指定时会有异常 java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.cl.redis.pojo.User
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<User>(User.class));
vos.set("cl", new User("cl","12"));
User cl = (User) vos.get("cl");
System.out.println(cl);
}
// 测试 list 类型
@Test
public void testList(){
ValueOperations vos = redisTemplate.opsForValue();
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<List>(List.class));
List<User> users = new ArrayList<>();
users.add(new User("cl","12"));
users.add(new User("ky","12"));
vos.set("users",users);
List<User> list = (List<User>) vos.get("users");
System.out.println(list);
}
}
1.9 使用 Redis
- 先判断缓存中是否存在。如果存在直接从缓存中取出数据。不执行2,3步骤
- 如果不存在,从mysql中获取数据
- 获取数据后,把数据缓存到redis中