主要内容
- Redis简介
- 使用Redis作为缓存工具时流程
- Redis单机版安装
- Redis数据类型
- Redis持久化策略
- Redis主从复制
- 哨兵(Sentinel)
- Redis集群(Cluster)
- Jedis
配套视频教程:Redis精品实战教程
一、 Redis简介
1、NoSQL简介
目前市场主流数据存储都是使用关系型数据库。每次操作关系型数据库时都是I/O操作,I/O操作是主要影响程序执行性能原因之一,连接数据库关闭数据库都是消耗性能的过程。关系型数据库索引数据结构都是树状结构,当数据量特别大时,导致树深度比较深,当深度深时查询性能会大大降低。尽量减少对数据库的操作,能够明显的提升程序运行效率。
针对上面的问题,市场上就出现了各种NoSQL(Not Only SQL,不仅仅可以使用关系型数据库)数据库,它们的宣传口号:不是什么样的场景都必须使用关系型数据库,一些特定的场景使用NoSQL数据库更好。
常见NoSQL数据库:
- memcached:键值对,内存型数据库,所有数据都在内存中。
- Redis:和Memcached类似,还具备持久化能力。
- HBase:以列作为存储。
- MongoDB:以Document做存储。
2 Redis简介
Redis是以Key-Value形式进行存储的NoSQL数据库。
Redis是使用C语言进行编写的。
平时操作的数据都在内存中,效率特高,读的效率110000/s,写81000/s,所以多把Redis当做缓存工具使用(在一些框架中还把Redis当做临时数据存储工具)。缓存工具:把数据库中数据缓存到Redis中,由于Redis读写性能较好,访问Redis中数据,而不是频繁访问数据库中数据。
Redis以solt(槽)作为数据存储单元,每个槽中可以存储N多个键值对。Redis中固定具有16384。理论上可以实现一个槽是一个Redis。每个向Redis存储数据的key都会进行crc16算法得出一个值后对16384取余就是这个key存放的solt位置。
同时通过Redis Sentinel(哨兵)提供高可用,通过Redis Cluster(集群)提供自动分区。
二、 使用Redis作为缓存工具时流程(写代码时思路)(边路缓存思想中一部分)
1、应用程序向Redis查询数据
2、判断Key是否存在
3、是否存在
1) 存在
- 把结果查询出来
- 返回数据给应用程序
2) 不存在
- 向MySQL查询数据
- 把数据返回给应用程序
- 把结果缓存到Redis中
三、 基于Docker安装Redis单机版
1 拉取镜像
docker pull redis:5.0.5 |
1 创建并启动容器
docker run -d --name redis -p 6379:6379 --restart always redis:5.0.5 |
2 客户端测试
docker exec -it redis bash |
在任意目录在输入redis-cli 即可进入redis命令行。
四、 Redis数据类型(面试问题)
Redis中数据是key-value形式。不同类型Value是有不同的命令进行操作。key和value都支持下面类型(在代码中多把key设置为String类型):
- String 字符串
- Hash 哈希表
- List 列表
- Set 集合
- Sorted Set 有序集合
Redis中命令有很多,抽取出部分进行讲解。
1、Key操作
1.1 exists
判断key是否存在。
- 语法:exists key名称
- 返回值:存在返回数字,不存在返回0
1.2 expire
设置key的过期时间,单位秒
- 语法:expire key 秒数
- 返回值:成功返回1,失败返回0
1.3 ttl
查看key的剩余过期时间
- 语法:ttl key
- 返回值:返回剩余时间,如果不过期返回-1
1.4 del
根据key删除键值对。
- 语法:del key
- 返回值:被删除key的数量
1.5 keys
命令: keys *
查看所有存在的key
2、字符串值(String)
2.1 set
设置指定key的值。如果key不存在是新增效果,如果key存在是修改效果。键值对是永久存在的。
- 语法:set key value
- 返回值:成功OK
2.2 get
获取指定key的值
- 语法:get key
- 返回值:key的值。不存在返回nil
2.3 setnx
当且仅当key不存在时才新增。恒新增,无修改功能。
- 语法:setnx key value
- 返回值:不存在时返回1,存在返回0
底层:
setnx具备分布式锁能力。在编写代码时如果调用setnx,时会对代码进行加锁。直到删除该key时会解锁。
setnx();// 加锁
// 代码
del();//解锁。
如果在并发访问时第一个线程setnx()时发现没有指定key会正常向下运行。其他线程在执行setnx()时发现有这个key就会等待,等待第一个线程删除key时才会继续向下执行。
2.3.1 常见的锁
- 锁:在Java中可以通过锁,让多线程执行时某个代码块或方法甚至类是线程安全的。通俗点说:一个线程访问,别的线程需要等待。
- 线程锁:同一个应用。多线程访问时添加的锁。synchronized(自动释放)或Lock(手动释放)
- 进程锁:不同进程(一个进程就是一个应用)需要访问同一个资源时,可以通过添加进程锁进行实现。
- 分布式锁:在分布式项目中不同项目访问同一个资源时,可以通过添加分布式锁保证线程安全。常见的分布式锁有两种:Redis的分布式锁和Zookeeper的分布式锁(通过调用Zookeeper的API给Zookeeper集群添加一个节点。如果节点能添加继续向下执行,执行结束删除该节点。如果其他线程发现该节点已经添加,会阻塞等待该节点删除才继续向下执行。)。
2.4 setex
设置key的存活时间,无论是否存在指定key都能新增,如果存在key覆盖旧值。同时必须指定过期时间。
- 语法:setex key seconds value
- 返回值:OK
3、哈希表(Hash)
Hash类型的值中包含多组field value。
3.1 hset
给key中field设置值。
- 语法:hset key field value
- 返回值:成功1,失败0
3.2 hget
获取key中某个field的值
- 语法:hget key field
- 返回值:返回field的内容
3.3 hmset
给key中多个filed设置值
- 语法:hmset key field value field value
- 返回值:成功OK
3.4 hmget
一次获取key中多个field的值
- 语法:hmget key field field
- 返回值:value列表
3.5 hvals
获取key中所有field的值
- 语法:hvals key
- 返回值:value列表
3.6 hgetall
获取所有field和value
- 语法:hgetall key
- 返回值:field和value交替显示列表
3.7 hdel
删除key中任意个field
- 语法:hdel key field field
- 返回值:成功删除field的数量
4、列表(List)
key value1 value2 value3 value4
4.1 Rpush
向列表末尾中插入一个或多个值
- 语法;rpush key value value
- 返回值:列表长度
4.2 lrange
返回列表中指定区间内的值。可以使用-1代表列表末尾
- 语法:lrange list 0 -1
- 返回值:查询到的值
4.3 lpush
将一个或多个值插入到列表前面
- 语法:lpush key value value
- 返回值:列表长度
4.4 llen
获取列表长度
- 语法:llen key
- 返回值:列表长度
4.5 lrem
删除列表中元素。count为正数表示从左往右删除的数量。负数从右往左删除的数量。
- 语法:lrem key count value
- 返回值:删除数量。
5、集合(Set)
set和java中集合一样。不允许重复值,如果插入重复值,后新增返回结果为0。
5.1 sadd
向集合中添加内容。不允许重复。
- 语法:sadd key value value value
- 返回值:集合长度
5.2 scard
返回集合元素数量
- 语法:scard key
- 返回值:集合长度
5.3 smembers
查看集合中元素内容
- 语法:smembers key
- 返回值:集合中元素
6、有序集合(Sorted Set)
有序集合中每个value都有一个分数(score),根据分数进行排序。
6.1 zadd
向有序集合中添加数据
- 语法:zadd key score value score value
- 返回值:长度
6.2 zrange
返回区间内容,withscores表示带有分数
- 语法:zrange key 区间 [withscores]
- 返回值:值列表
五、 Redis持久化策略(面试问题)
Redis不仅仅是一个内存型数据库,还具备持久化能力。
Redis每次启动时都会从硬盘存储文件中把数据读取到内存中。运行过程中操作的数据都是内存中的数据。
一共包含两种持久化策略:RDB 和 AOF
1、RDB(Redis DataBase)
rdb模式是默认模式,可以在指定的时间间隔内生成数据快照(snapshot),默认保存到dump.rdb文件中。当redis重启后会自动加载dump.rdb文件中内容到内存中。
用户可以使用SAVE(同步)或BGSAVE(异步)手动保存数据。
可以设置服务器配置的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令,可以通过save选项设置多个保存条件,但只要其中任意一个条件被满足,服务器就会执行BGSAVE命令。
例如:
save 900 1
save 300 10
save 60 10000
那么只要满足以下三个条件中的任意一个,BGSAVE命令就会被执行。计时单位是必须要执行的时间,save 900 1 ,每900秒检测一次。在并发量越高的项目中Redis的时间参数设置的值要越小。
服务器在900秒之内,对数据库进行了至少1次修改
服务器在300秒之内,对数据库进行了至少10次修改
服务器在60秒之内,对数据库进行了至少10000次修改。
1.1 优点
rdb文件是一个紧凑文件,直接使用rdb文件就可以还原数据。
数据保存会由一个子进程进行保存,不影响父进程做其他事情。
恢复数据的效率要高于aof
总结:性能要高于AOF
1.2 缺点
每次保存点之间导致redis不可意料的关闭,可能会丢失数据。
由于每次保存数据都需要fork()子进程,在数据量比较大时可能会比较耗费性能。
2、AOF(AppendOnly File)
AOF默认是关闭的 appendonly no,需要在配置文件redis.conf中开启AOF。Redis支持AOF和RDB同时生效,如果同时存在,AOF优先级高于RDB(Redis重新启动时会使用AOF进行数据恢复)
AOF原理:监听执行的命令,如果发现执行了修改数据的操作,同时直接同步到数据库文件中,同时会把命令记录到日志中。即使突然出现问题,由于日志文件中已经记录命令,下一次启动时也可以按照日志进行恢复数据,由于内存数据和硬盘数据实时同步,即使出现意外情况也需要担心。
2.1 优点
相对RDB数据更加安全。
2.2 缺点
相同数据集AOF要大于RDB。
相对RDB可能会慢一些。
2.3 开启办法
修改redis.conf中。
appendonly yes 开启aof
appendfilename 设置aof数据文件,名称随意。
# 默认no appendonly yes # aof文件名 appendfilename "appendonly.aof" |
六、 Redis主从复制
Redis支持集群功能。为了保证单一节点可用性,redis支持主从复制功能。每个节点有N个复制品(replica),其中一个复制品是主(master),另外N-1个复制品是从(Slave),也就是说Redis支持一主多从。
一个主可有多个从,而一个从又可以看成主,它还可以有多个从。
1、主从优点
增加单一节点的健壮性,从而提升整个集群的稳定性。(Redis中当超过1/2节点不可用时,整个集群不可用)
从节点可以对主节点数据备份,提升容灾能力。
读写分离。在redis主从中,主节点一般用作写(具备读的能力),从节点只能读,利用这个特性实现读写分离,写用主,读用从。
2、基于Docker一主多从搭建
1.1 拉取redis镜像
# docker pull redis |
1.2 创建并运行三个Docker容器
先停止单机版Redis。单机版Redis端口6379
三个容器分别占用系统的6379、6380、6381端口
# docker run --name redis1 -p 6379:6379 -v /opt/redis:/data -d redis:5.0.5 # docker run --name redis2 -p 6380:6379 -v /opt/redis:/data -d redis:5.0.5 # docker run --name redis3 -p 6381:6379 -v /opt/redis:/data -d redis:5.0.5 |
1.3 在从中指定主的ip和端口
进入redis2容器内部设置主的ip和端口
# docker exec -it redis2 redis-cli # slaveof 192.168.108.128 6379 # exit |
进入redis2容器内部设置主的ip和端口
# docker exec -it redis3 redis-cli # slaveof 192.168.108.128 6379 # exit |
1.1 测试主从效果
进入redis1容器内部,新增key-value
# docker exec -it redis1 redis-cli # set name "bjsxt" # exit |
分别进入redis2和redis3容器,查看是否有name键
# docker exec -it redis1 redis-cli # get name |
七、哨兵(Sentinel)
在redis主从默认只有主具备写的能力,而从只能读。如果主宕机,整个节点不具备写能力。但是如果这是让一个从变成主,整个节点就可以继续工作。即使之前的主恢复过来也当做这个节点的从即可。
Redis的哨兵就是帮助监控整个节点的,当节点主宕机等情况下,帮助重新选取主。
Redis中哨兵支持单哨兵和多哨兵。单哨兵是只要这个哨兵发现master宕机了,就直接选取另一个master。而多哨兵是根据我们设定,达到一定数量哨兵认为master宕机后才会进行重新选取主。
八、Redis集群(Cluster)
1、集群原理
a) 集群搭建完成后由集群节点平分(不能平分时,前几个节点多一个槽)16384个槽。
b) 客户端可以访问集群中任意节点。所以在写代码时都是需要把集群中所有节点都配置上。
c) 当向集群中新增或查询一个键值对时,会对Key进行Crc16算法得出一个小于16384值,这个值就是放在哪个槽中,在判断槽在哪个节点上,然后就操作哪个节点。
集群:集群中所有节点都安装在不同服务器上。
伪集群:所有节点都安装在一台服务器上,通过不同端口号进行区分不同节点。
当集群中超过或等于1/2节点不可用时,整个集群不可用。为了搭建稳定集群,都采用奇数节点。
Redis每个节点都支持一主多从。会有哨兵监控主的状态。如果出现(配置文件中配置当多少个哨兵认为主失败时)哨兵发现主不可用时会进行投票,投票选举一个从当作主,如果后期主恢复了,主当作从加入节点。在搭建redis集群时,内置哨兵策略。
演示时:创建3个节点,每个节点搭建一主一从。一共需要有6个redis。
2、Redis集群安装步骤
2.1 新建配置模板文件
# cd /usr/local # mkdir redis-cluster # cd redis-cluster # vim redis-cluster.tmpl |
红色IP部分需要修改为自己的IP
2.2 使用Shell脚本创建6个目录
for port in `seq 7000 7005`; do mkdir -p ./${port}/conf && PORT=${port} envsubst < ./redis-cluster.tmpl > ./${port}/conf/redis.conf && mkdir -p ./${port}/data; done |
2.3 创建桥连网络
# docker network create redis-net |
查看网络是否创建成功
# docker network ls |
2.4 创建并启动6个容器
for port in `seq 7000 7005`; do docker run -d -ti -p ${port}:${port} -p 1${port}:1${port} -v /usr/local/redis-cluster/${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf -v /usr/local/redis-cluster/${port}/data:/data --restart always --name redis-${port} --net redis-net --sysctl net.core.somaxconn=1024 redis:5.0.5 redis-server /usr/local/etc/redis/redis.conf; done |
2.5 查看6个容器ip及端口
# docker inspect redis-7000 redis-7001 redis-7002 redis-7003 redis-7004 redis-7005 | grep IPAddress |
2.6 执行集群脚本
进入6个容器中任意一个。示例中以redis-7000举例
# docker exec -it redis-7000 bash |
执行创建脚本命令。 --cluster-relicas 1表示每个主有1个从。
redis-cli --cluster create 172.19.0.2:7000 172.19.0.3:7001 172.19.0.4:7002 172.19.0.5:7003 172.19.0.6:7004 172.19.0.7:7005 --cluster-replicas 1 |
输入后给出集群信息,输入yes后创建集群
2.7 验证集群
在任意Redis容器内部,进入Redis客户端工具。
示例中还是以Redis-7000举例。
# redis-cli -c -p 7000 |
九、Jedis(了解)
Redis给Java语言提供了客户端API,称之为Jedis。
Jedis API和Redis 命令几乎是一样的。
例如:Redis对String值新增是set命令,Jedis中也是set方法。所以本课程中没有重点把所有方法进行演示,重要演示Jedis如何使用。
Jedis API特别简单,基本上都是创建对象调用方法即可。由于Jedis不具备把对象转换为字符串的能力,所以每次都需要借助Json转换工具进行转换,这个功能在Spring Data Redis中已经具备,推荐使用Spring Data Redis。
1、添加依赖
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>2.2.6.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
2、单机版
public void testStandalone(){
Jedis jedis = new Jedis("192.168.32.132",6379);
jedis.set("name","smallming-standalone");
String value = jedis.get("name");
System.out.println(value);
}
3、带有连接池
public void testPool(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(20);
jedisPoolConfig.setMaxIdle(5);
jedisPoolConfig.setMinIdle(3);
JedisPool jedisPool = new JedisPool(jedisPoolConfig,"192.168.32.132",6379);
Jedis jedis = jedisPool.getResource();
jedis.set("name","smallming-pool");
String value = jedis.get("name");
System.out.println(value);
}
4、集群
public void testCluster(){
Set<HostAndPort> set = new HashSet<>();
set.add(new HostAndPort("192.168.32.132",7001));
set.add(new HostAndPort("192.168.32.132",7002));
set.add(new HostAndPort("192.168.32.132",7003));
set.add(new HostAndPort("192.168.32.132",7004));
set.add(new HostAndPort("192.168.32.132",7005));
set.add(new HostAndPort("192.168.32.132",7006));
JedisCluster jedisCluster = new JedisCluster(set);
jedisCluster.set("name","smallming");
String value = jedisCluster.get("name");
System.out.println(value);
}