Redis
1.NoSql
# NoSql(Not Only Sql),不仅仅是sql,泛指非关系型数据库
2.NoSql的诞生
随着互联网web2.0网站的兴起,传统的关系型数据库在高并发和特大规模的纯动态网站已经显得力不从心,暴露了很多难以克服的问题,如图片、音频、视频的存储等,传统数据库只能存储结构化的数据,而对于非结构的数据支持不够完善,NoSql这门技术,更好的解决了这些问题,它告诉世界不仅仅是sql.
3.NoSql数据库的四大分类
# 键值对(key-value)存储数据库
这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据.
优点:
key/value模型对于IT系统来说,优势在于简单、易部署.
缺点:
如果DBA(database admin)只对部分值进行查询或更新的时候,key/value就显得效率低下.
常见数据库:
Redis
# 列存储数据库
这类数据库通常是应对分布式存储的海量数据.
特点:
键依然存在,但是它是指向了多个列.这些列是由列家族来安排的
常见数据库:
HBase
# 文档型数据库
文档型数据库的灵感来源于Lotus Notes办公软件,它的存储同键值存储相类似
Bson--->Json {"id":"1","name":"554"}
优点:
该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如Json.文档型数据库可以看做是键值数据库的升级版,允许之间嵌套键值.而且文档型数据库比键值数据库的查询效率更高
缺点:
相较而言,事务支持不友好
常见数据库:
MongDb
# 图形(Graph)数据库---图片 音频 视频 (一般会放到文件服务器)
图形结构的数据库同其他行列以及刚性结构的SQL数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上
** NoSQL数据库没有标准的查询语言(SQL),因此进行数据库查询需要制定数据模型.许多NoSQL数据库都有REST式的数据接口或者查询API.如:Neo4J、InfoGrid、Infinite Graph.
4.NoSql适用场景
1.数据库类型比较简单
2.需要灵活性更强的IT系统
3.对数据性能要求较高(读)
4.不需要高度的数据一致性 NoSql弱化事务/没有事务
5.对于给定key,比较容易映射复杂值的环境(redis)
5.什么是Redis
# redis是一个开源(bsd许可)的内存数据结构存储,用作数据库、缓存和消息代理.
6.Redis的特点
1.Redis是一个高性能key/value内存型数据库
2.Redis支持丰富的数据类型(String,list,set,zset,hash)
3.Redis支持数据持久化---将内存中的数据持久化到磁盘
4.Redis单线程,单进程且效率高(不支持并发操作)
7.Redis安装
1.下载redis
2.准备一台机器安装redis(redis的底层由c编写)
3.将redis上传到linux系统中并解压4.编译安装redis
a)进入redis,解压安装包
b)安装gcc (yum install -y gcc)
c)执行make命令
如果出现 zmalloc.h:50:31:致命错误:jemalloc/jemalloc.h:没有那个文件或目录
make MALLOC=libc
d)安装 make install 会直接安装到当前的redis的源码包,日后找起来不方便
make install PREFIX=/usr/redis
e)进入redis的安装目录
bin 用来存放redis中可执行二进制文件 启动 关闭 等文件
启动redis服务脚本: redis-server
./redis-server
连接redis客户端脚本: redis-cli
./redis-cli -p 端口号 -u 主机ip
注意:redis 默认端口号为 6379
8.Redis中key相关指令
redis存储的数据是以 key value 形式存储 (key都为string)
1.删除指定的key
del key1 key2 ... 删除一到多个key
2.查看key
keys * 查看所有的key
keys h?llo ? 代表匹配任意一个字符
keys h*llo * 代表匹配零到多个字符
keys h[ae]llo [] 匹配一个[]中包含的字符
3.判断某个key是否存在
exists key ... 判断一个key或多个key是否存在
4.给已经存在的key设置过期时间
expire key(单位 秒) 如验证码的过期时间
5.切换库(redis只有16个库,默认使用的是0库)
select 库的编号(可用编号 0-15)
6.移动key到指定的DB
move key 库的编号
7.给已经存在的key设置过期时间
pexpire key(单位 毫秒)
8.查看key的过期时间
ttl key 返回单位 秒
注意:
返回 -1 代表key永久存在
返回 -2 代表key不存在
返回 >=0 代表key过期时间
9.查看key过期时间
pttl key 返回单位 毫秒
返回 -1 代表key永久存在
返回 -2 代表key不存在
返回 >=0 代表key过期时间
10.随即返回一个key
randomkey
11.修改key的名字
rename key newkey
12.查看key对应值的类型
type key
值的类型:
string
list
set
zset
hash
9.Redis中常用的数据库相关的指令
说明 :
使用redis的默认配置器动redis服务后,默认会存在16个库,下标从0-15
可以使用select 库的编号 来选择一个redis的库
1. 清空当前的库 flushdb
2. 清空全部的库 flushall
3. ./redis-cli -p 端口号 -u 主机ip --raw 展示中文
10.Redis的 String 类型的操作 key value
1.命令
命令 | 说明 |
---|---|
set | 设置一个key/value |
get | 根据key获得对应的value |
mset | 一次设置多个key value |
mget | 一次获得多个key的value |
getset | 返回原始key的值,同时设置新值 |
strlen | 获得对应key存储value的长度 |
append | 为对应key的value追加内容 返回值是追加内容后字符串的长度 |
getrange | 截取value的内容(下标0 开始) |
setex | 设置一个key存活的有效期(秒)新添加key的同时添加过期时间 |
psetex | 设置一个key存活的有效期(豪秒)新添加key的同时添加过期时间 |
setnx | 存在不做任何操作,不存在添加 |
msetnx | 可以同时设置多个key,原子操作(只要有一个key存在就不做任何操作) |
decr | 进行数值类型的-1操作 |
decrby | 根据提供的数据进行减法操作 |
Incr | 进行数值类型的+1操作 |
incrby | 根据提供的数据进行加法操作 |
incrbyfloat | 根据提供的数据加入浮点数 |
11.Rdeis的List类型的操作
1.图示
2.命令
命令 | 说明 |
---|---|
lpush | 将某个值加入到一个key列表头部 |
lpushx | 同lpush,但是必须要保证这个key存在 |
rpush | 将某个值加入到一个key列表末尾 |
rpushx | 同rpush,但是必须要保证这个key存在 |
lpop | 返回和移除列表的第一个元素 |
rpop | 返回和移除列表的最后一个元素 |
lrange | 获取某一个下标区间内的元素(遍历所有 0 -1) |
llen | 获取列表元素个数 |
lset | 设置某一个指定索引的值(索引必须存在) |
lindex | 获取某一个指定索引位置的元素 |
lrem | 删除重复元素 lrem key 数字 值 数字:删除的个数 |
ltrim | 保留列表中特定区间(index的区间)内的元素 |
linsert | 在某一个元素之前或之后插入新元素 |
12.Redis的Set类型的操作
1.图示
2.命令
命令 | 说明 |
---|---|
sadd | 为集合添加元素 |
smembers | 显示集合中所有元素 无序 |
scard | 返回集合中元素的个数 |
spop | 随即删除n个元素,并返回对应删除的n个元素 n—>数字 |
smove | 从一个集合中向另一个集合移动元素 smove source(原始集合) destination(目标集合) member(元素) |
srem | 从集合中删除指定的member(1-n个) |
sismember | 判断一个集合中是否含有这个元素 |
srandmember | 随机返回元素 默认返回一个,可以手动指定返回元素的个数 |
sdiff | 去掉第一个集合中其它集合含有的相同元素 |
sinter | 求交集 |
sunion | 求和集 |
13.Redis的ZSet类型的操作
1.图示 ZSet ===> ScoreSet(分数set) ===> SortSet(排序set)
2.命令
命令 | 说明 |
---|---|
zadd | 添加一个有序集合元素 |
zcard | 返回集合的元素个数 |
zrange | 返回一个范围内的元素(zrange zset 0 -1 withscores 遍历所有并展示分数) |
zrangebyscore | 按照分数查找一个范围内的元素 |
zrank | 返回排名(返回index) |
zrevrank | 倒序排名(返回index) |
zscore | 显示某一个元素的分数 |
zrem | 移除某一个元素 |
zincrby | 给某个特定元素加分 |
14.Redis的Hash类型的操作
1.图示
2.命令
命令 | 说明 |
---|---|
hset | 设置一个key/value对 |
hget | 获得一个key对应的value |
hgetall | 获得所有的key/value对 |
hdel | 删除某一个key/value对 |
hexists | 判断一个key是否存在 |
hkeys | 获得所有的key |
hvals | 获得所有的value |
hmset | 设置多个key/value |
hmget | 获得多个key的value |
hsetnx | 设置一个不存在的key的值 |
hincrby | 为value进行加法运算 |
hincrbyfloat | 为value加入浮点值 |
15.Redis中的小细节
1.redis中的端口号默认为 6379
1.如何修改redis默认端口:
a)将源码包中的redis.conf 移动到 redis的安装目录/bin 下
b)vim redis.conf 修改 port 自定义端口号
注意:
想要配置的端口号生效,必须启动redis的同时加载配置文件
./redis-server redis.conf
./redis-cli -p 自定义端口号
2.redis中默认存在16个库
1.修改库的数量
vi redis.conf
databases 自定义数量(小于等于16)
3.redis以 后台进程启动
vi redis.conf
daemonize(守护进程) yes (后台启动)
4.redis可视化操作工具
redis-desk-manager 客户端工具
#注意:
#默认redis服务器只能被本地连接访问
#想要远程访问,必须开启远程访问权限
#vim redis.conf
#修改 bind 0.0.0.0 所有客户端都可以访问
16.Redis中的持久化机制
1.什么是Redis的持久化
#将redis中的数据存储到硬盘里面
#在redis中的操作的是内存中的数据
2.Redis两种持久化方式
#快照(snapshotting)也称RDB 快照持久化(默认开启)
#AOF(append only file) 只追加文件
3.两大持久化机制
-
快照持久化
定义: 这种方式可以将某一时刻的所有数据都写入硬盘中,当然这也是redis的默认持久化方式,保存的文件是以.rdb形式结尾的文件因此这种方式也称之为RDB方式
a) 快照持久化也是redis中的默认开启的持久化方案, 根据redis.conf中的配置,快照将被写入dbfilename指定的文件里面(默认是dump.rdb文件中) b) 根据redis.conf中的配置,快照将保存在dir选项指定的路径上 c) 创建快照的几种方式
1.客户端可以使用BGSAVE命令来创建一个快照,当接收到客户端的BGSAVE命令时,redis会调用fork¹来创建一个子进程,然后子进程负责将快照写入磁盘中,而父进程则继续处理命令请求 #名词解释 : fork当一个进程创建子进程的时候,底层的操作系统会创建该进程的一个副本,在类unix系统中创建子进程的操作会进行优化:在刚开始的时候,父子进程共享相同内存,直到父进程或子进程对内存进行了写之后,对被写入的内存的共享才会结束服务(不会阻塞redis服务)
2.客户端还可以使用SAVE命令来创建一个快照,接收到SAVE命令的redis服务器在快照创建完毕之前将不再响应任何其他的命令 #注意 : SAVE命令并不常用,使用SAVE命令在快照创建完毕之前,redis处于阻塞状态,无法对外服务
3.当redis通过shutdown指令接收到关闭服务器的请求时,会执行一个save命令,阻塞所有的客户端,不再执行客户端执行发送的任何命令,并且在save命令执行完毕之后关闭服务器
4.如果用户在redis.conf中设置了save配置选项,redis会在save选项条件满足之后自动触发一次BGSAVE命令,如果设置多个save配置选项,当任意一个save配置选项条件满足,redis也会触发一次BGSAVE命令 save 时间(秒) key变化次数 save 900 1 save 300 10 save 1 10000
-
AOF持久化(默认不开启)
定义: 这种方式可以将客户端执行的所有写命令记录到日志文件中
1.开启AOF vim redis.conf appendonly yes 开启 appendfilename "appendonly.aof" 日志名字为appendonly.aof 日志文件生成的位置与快照的路径一致 2.日志的同步频率 # appendfsync always appendfsync everysec(默认使用) # appendfsync no
选项 同步频率 always 每个redis写命令都要同步写入硬盘,严重降低redis速度 everysec 每秒执行一次同步显式的将多个写命令同步到磁盘 no 由操作系统决定何时同步 三种日志记录频率的详细分析 :
1.如果用户使用了always选项,那么每个redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据丢失减到最少;遗憾的是,因为这种同步策略需要对硬盘进行大量的写入操作,所以redis处理命令的速度会受到硬盘性能的限制; 注意 : 转盘式硬盘在这种频率下200左右个命令/s ; 固态硬盘(SSD) 几百万个命令/s; 警告 : 使用SSD用户请谨慎使用always选项,这种模式不断写入少量数据的做法有可能会引发严重的写入放大问题,导致将固态硬盘的寿命从原来的几年降低为几个月
2.为了兼顾数据安全和写入性能,用户可以考虑使用everysec选项,让redis每秒一次的频率对AOF文件进行同步;redis每秒同步一次AOF文件时性能和不使用任何持久化特性时的性能相差无几,而通过每秒同步一次AOF文件,redis可以保证,即使系统崩溃,用户最多丢失一秒之内产生的数据(推荐使用这种方式)
3.最后使用no选项,将完全有操作系统决定什么时候同步AOF日志文件,这个选项不会对redis性能带来影响但是系统崩溃时,会丢失不定数量的数据,另外如果用户硬盘处理写入操作不够快的话,当缓冲区被等待写入硬盘数据填满时,redis会处于阻塞状态,并导致redis的处理命令请求的速度变慢(不推荐使用)
3.AOF重写
aof 的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件Redis提供了AOF重写机制
1.重写AOF文件两种方式
a. 执行BGREWRITEAOF命令 b. 配置redis.conf中的auto-aof-rewrite-percentage选项
a. 执行BGREWRITEAOF命令
1. redis调用fork ,现在有父子两个进程 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令 2. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。 3. 当子进程把快照内容已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。 4. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。 #注意 : 重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换原有的文件这点和快照有点类似。(AOF重写过程完成后会删除旧的AOF文件,删除一个体积达几十GB大的旧的AOF文件可能会导致系统随时挂起 )
b. 配置redis.conf中的auto-aof-rewrite-percentage选项
1. AOF重写也可以使用auto-aof-rewrite-percentage 100 和auto-aof-rewrite-min-size 64mb来自动执行BGREWRITEAOF. #说明: 如果设置auto-aof-rewrite-percentage值为100和auto-aof-rewrite-min-size 64mb,并且启用的AOF持久化时,那么当AOF文件体积大于64M,并且AOF文件的体积比上一次重写之后体积大了至少一倍(100%)时,会自动触发,如果重写过于频繁,用户可以考虑将auto-aof-rewrite-percentage设置为更大
4.两种持久化方案的总结
AOF持久化既可以将丢失的数据的时间降低到1秒(甚至不丢失任何数据),那么我们还有什么理由不是用AOF呢?
~~~javascript
#注意 :
这个问题实际上并没有这么简单,因为redis会不断将执行的写命令记录到AOF文件中,所以随着redis运行,AOF文件的体积会不断增大,在极端情况下甚至会用完整个硬盘,还有redis重启重新执行AOF文件记录的所有写命令的来还原数据集,AOF文件体积非常大,会导致redis执行恢复时间过长
两种持久化方案既可以同时使用,又可以单独使用,在某种情况下也可以都不使用,具体使用那种持久化方案取决于用户的数据和应用决定
无论使用AOF还是快照机制持久化,将数据持久化到硬盘都是有必要的,除了持久化外,用户还应该对持久化的文件进行备份(最好备份在多个不同地方)
17.SpringData操作Redis
-
使用spring-data操作redis
Spring-Data-Redis项目(简称SDR)对Redis的Key-Value数据存储操作提供了更高层次的抽象,类似于Spring Framework对JDBC支持一样。
Spring Data Redis使得在Spring应用中读写Redis数据库更加容易
-
连接Redis服务(单机)
在Spring Data Redis中通过org.springframework.data.redis.connection包中的RedisConnection和RedisConnectionFactory类来获取Redis连接。
-
连接redis服务集群
-
RedisTemplate的支持
熟悉Spring的JdbcTemplate对象的话,应该大概能猜出来RedisTemplate的作用了,RedisTemplate对象对RedisConnection进行了封装,它提供了连接管理,序列化等功能,它对Redis的交互进行了更高层次的抽象。
key类型操作
ValueOperations Redis String/Value 操作 ListOperations Redis List 操作 SetOperations Redis Set 操作 ZSetOperations Redis Sort Set 操作 HashOperations Redis Hash 操作 value约束操作
BoundValueOperations Redis String/Value key 约束 BoundListOperations Redis List key 约束 BoundSetOperations Redis Set key 约束 BoundZSetOperations Redis Sort Set key 约束 BoundHashOperations Redis Hash key 约束 -
创建Springboot项目
-
引入相关依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
Springboot相关配置
server: port: 8989 spring: redis: database: 0 #连接redis的哪个库 host: 192.168.132.16 #主机ip port: 7000 #端口号
-
自动注入
@Autowired private RedisTemplate redisTemplate; @Autowired private StringRedisTemplate stringRedisTemplate; #对String类型良好的支持[推荐使用]
-
测试redisTemplate
4.1 简单的String操作
4.2 简单的List操作
4.3 简单的Set操作
4.4 简单的ZSet操作
4.5 简单的Hash操作
-
注意
RedisTemplate和StringRedisTemplate,不同之处在于StringRedisTemplate的Key-Value序列化使用的是StringRedisSerializer, RedisTemplate对象是默认使用JdkSerializationRedisSerializer实现
使用StringRedisTemplate操作Redis之后的结果是友好的
18.Redis实现mybatis的缓存
# 什么是缓存
缓存是计算机内存里的一段数据
# 缓存数据的特点
由于缓存数据在内存中 所以读写缓存的速度非常的快
# 为什么项目中要使用缓存
1.在项目中添加缓存可以在一定程度上减轻数据库的压力
2.可以提高现有网站中的查询效率 加快网站的响应速度
# 项目中是否所有的数据都要使用缓存
否 加入缓存的数据: 查询比较多 增删改比较少
# 在现有的项目中如何应用缓存
a.借助于mybatis自身提供的缓存技术
开启缓存: 在指定模块的mapper.xml中加入如下配置即可:
<!-- 在mapper文件中开启缓存 -->
<cache/>
#注意:
放入缓存中的对象必须实现对象序列化接口
# mybatis自身缓存的缺点
a.自身实现缓存,缓存的数据会占用一定应用服务器内存 导致应用服务处理请求的速度可能变慢
这种缓存也成为本地缓存
b.本地缓存在集群架构下不能实现缓存共享
# 解决本地缓存存在的问题
可以实现分布式缓存服务器 redis
1.什么样的数据适合添加缓存
2.Redis实现mybatis缓存机制步骤
1.导入jar
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.现有项目中如何使用redis作为分布式缓存实现
a.在当前项目实现mybatis提供cache接口 public class 实现类 implements Cache(接口)
b.使用自定义cache
<cache type="自定义cache全限定名"/>
3.创建工具类,通过自定义的工厂工具类获取StringRedisTemplate / RedisTemplate
4.引用序列化工具类,用于SpringRedisTemplate转化获取值的类型
5.要想使用RedisTemplate实现缓存更友好展示,需要改变本身的序列化方式为SpringRedisTemplate的序列化方式
3.自定义工厂
package com.xkdgx.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* ApplicationContextUtils class
*
* @author L-JiaHui
* @date 2020/3/13
*/
@Component /**当前类交由spring工厂管理*/
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**获取当前的spring工厂*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**根据id获取对应的bean*/
public static Object getBean(String id){
Object bean = applicationContext.getBean(id);
return bean;
}
/**根据类型获取对应的bean*/
public static Object getBean(Class clazz){
Object bean = applicationContext.getBean(clazz);
return bean;
}
/**根据id+类型获取对应的bean*/
public static Object getBean(String id,Class clazz){
Object bean = applicationContext.getBean(id, clazz);
return bean;
}
}
4.序列化工具类(直接拿来用)
package com.xkdgx.util;
import java.io.*;
public class SerializeUtils {
//序列化对象为字符串 其它类型--->转化为String
public static String serialize(Object obj) {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream;
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
String string = byteArrayOutputStream.toString("ISO-8859-1");
objectOutputStream.close();
byteArrayOutputStream.close();
return string;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//反序列化为对象 String--->其它类型
public static Object serializeToObject(String str){
try {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(str.getBytes("ISO-8859-1"));
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object object = objectInputStream.readObject();
objectInputStream.close();
byteArrayInputStream.close();
return object;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
5.自定义实现类(使用SpringRedisTemplate—缓存)需要引用序列化工具类
package com.xkdgx.controller;
import com.xkdgx.util.ApplicationContextUtils;
import com.xkdgx.util.SerializeUtils;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.locks.ReadWriteLock;
/**
* RedisCache1 class
*
* @author L-JiaHuui
* @date 2020/3/13
* */
public class RedisCache1 implements Cache {
/**
* 注意:
* 1.必须提供有参构造方法且有一个String id 的参数
* 2. id:当前mapper.xml的namespace
* 3.getId 返回的就是该id
* */
private String id;
/**有参构造*/
public RedisCache1(String id) {
this.id = id;
}
/**返回当前的namespace*/
@Override
public String getId() {
return id;
}
/**
* 添加缓存:
* 参数1:key 参数2:value
* 添加缓存需要注入StringRedisTemplate,由于当前类是交由mybatis实例化,
* 所以需要自定义工具类从当前的工厂中获取到StringRedisTemplate
* */
@Override
public void putObject(Object key, Object value) {
/**获取StringRedisTemplate*/
StringRedisTemplate stringRedisTemplate = (StringRedisTemplate) ApplicationContextUtils.getBean(StringRedisTemplate.class);
/**StringRedisTemplate操作hash*/
HashOperations<String, Object, Object> hash = stringRedisTemplate.opsForHash();
/**
* StringRedisTemplate:
* key-value 必须都为String
* value 可能为---> 对象、集合、地址
* 将查询到的值转化为字符串(使用序列化工具类将其序列化)
* */
String val = SerializeUtils.serialize(value);
/**插入值*/
hash.put(id,key.toString(),val);
}
/**取出缓存*/
@Override
public Object getObject(Object key) {
/**获取StringRedisTemplate*/
StringRedisTemplate stringRedisTemplate = (StringRedisTemplate) ApplicationContextUtils.getBean(StringRedisTemplate.class);
/**StringRedisTemplate操作hash*/
HashOperations<String, Object, Object> hash = stringRedisTemplate.opsForHash();
/**
* 在进行取值之前先进性判断
* 查询缓存:
* 有缓存:返回缓存中的值
* 无缓存:进入添加缓存,调用添加缓存的方法,去数据库查数据,添加缓存 return null
* */
/**判断大键(id)和map集合中的小键(key.toString)是否存在*/
if (hash.hasKey(id,key.toString())){
/**根据大键和小键拿到值(拿到的值是String类型的 在添加时进行了序列化)*/
String val = (String) hash.get(id, key.toString());
/**将拿到的值进行反序列化,转化为原有的类型,进行返回*/
Object o = SerializeUtils.serializeToObject(val);
return o;
}
return null;
}
@Override
public Object removeObject(Object key) {
return null;
}
/**清空缓存*/
@Override
public void clear() {
/**获取StringRedisTemplate*/
StringRedisTemplate stringRedisTemplate = (StringRedisTemplate) ApplicationContextUtils.getBean(StringRedisTemplate.class);
/**根据大键(id)进行删除,实现清空缓存*/
stringRedisTemplate.delete(id);
}
@Override
public int getSize() {
return 0;
}
//Redis单线程不需要
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
}
6.自定义实现类(使用RedisTemplate—缓存)
package com.xkdgx.controller;
import com.xkdgx.util.ApplicationContextUtils;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.concurrent.locks.ReadWriteLock;
/**
* RedisCache2 class
*
* @author L-JiaHuui
* @date 2020/3/13
* */
public class RedisCache2 implements Cache {
/**
* 注意:
* 1.必须提供有参构造方法且有一个String id 的参数
* 2. id:当前mapper.xml的namespace
* 3.getId 返回的就是该id
* */
private String id;
/**有参构造*/
public RedisCache2(String id) {
this.id = id;
}
/**返回当前的namespace*/
@Override
public String getId() {
return id;
}
/**
* 添加缓存:
* 参数1:key 参数2:value
* 添加缓存需要注入RedisTemplate,由于当前类是交由mybatis实例化,
* */
@Override
public void putObject(Object key, Object value) {
/**获取RedisTemplate*/
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
/**将RedisTemplate的序列化方式JdkSerializationRedisSerializer改为
* StringRedisTemplate的Key-Value序列化,是展示更加友好
* */
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
/**RedisTemplate操作hash*/
HashOperations hash = redisTemplate.opsForHash();
hash.put(id,key.toString(),value);
}
/**取出缓存*/
@Override
public Object getObject(Object key) {
/**获取RedisTemplate*/
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
/**改变RedisTemplate的序列化方式*/
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
/**RedisTemplate操作hash*/
HashOperations hash = redisTemplate.opsForHash();
Object o = hash.get(id, key.toString());
return o;
}
@Override
public Object removeObject(Object key) {
return null;
}
/**清空缓存*/
@Override
public void clear() {
/**获取RedisTemplate*/
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
/**改变RedisTemplate的序列化方式*/
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
/**删除大键(id)*/
redisTemplate.delete(id);
}
@Override
public int getSize() {
return 0;
}
//Redis单线程不需要
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
}
19.缓存存在的问题
# 缓存击穿
缓存穿透,是指查询一个数据库一定不存在的数据.正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存.如果数据库查询对象为空,则不放进缓存.
解决方案:
a.查到的数据即使为空,也添加缓存(并设置较短的过期时间)
b.使用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层数据库的查询压力.
# 布隆过滤器(Bloom Filter)
本质上布隆过滤器是一种数据结构,比较巧妙的概率性数据结构
特点是:
高效的插入和查询,可以用来告诉你"某样东西一定不存在或者可能存在"
# 缓存雪崩
缓存雪崩,是指在某一个时间段,缓存集中过期失效.
解决方案:
a.缓存永不过期
b.设置不同的过期时间,让缓存失效的时间点尽量均匀
c.缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量.比如对某个key只允许一个线程查询数据和写缓存,其他线程等待.
20.Redis集群
Redis在3.0后开始支持Cluster(模式)模式,目前redis的集群支持节点的自动发现,
支持slave-master选举和容错,支持在线分片(sharding shard)等特性
选举和容错: 主节点挂掉之后,选举一个从节点代替主节点
分片: 把数据拆分成n多份,放在每一个机器上
- Redis集群架构图
- Redis的集群细节
1.所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
2.节点的fail是通过集群中超过半数的节点检测失效时才生效. (一般搭建的集群是奇数个)
3.客户端与redis节点直连,不需要中间proxy(代理)层.客户端不需要连接集群所有节点,连接集群中任意一个可用节点即可
4.redis cluster把所有的物理节点映射到slot[0-16383]
cluster 管理 node 管理 solt 管理 数据 (解决单点压力,分向不同的node)
solt: 槽
Redis 集群没有并使用传统的一致性哈希来分配数据,而是采用另外一种叫做哈希槽 (hash slot)的方式来分配的。redis cluster 默认分配了 16384 个slot,当我们set一个key 时,会用CRC16算法来取模得到所属的slot,然后将这个key 分到哈希槽区间的节点上,具体算法就是:CRC16(key) % 16384。
(为什么是16384,选取了16384是因为crc16会输出16bit的结果,可以看作是一个分布在0-2^16-1之间的数,redis的作者测试发现这个数对2^14求模的会将key在0-2^14-1之间分布得很均匀,因此选了这个值。)
在构建redis cluster集群时,master必须大于等于3,否则会创建失败。并且,当集群中存活的master节点数小于总节点数的一半的话,集群就无法提供服务了。
例:我们有三个master节点A、B、C,采用哈希槽 (hash slot)的方式来分配16384个slot 的话,它们三个节点分别承担的slot 区间是:
节点A:0 ~ 5460
节点B:5461 ~ 10922
节点C:10923 ~ 16383
节点在收到读写请求时,会根据CRC16(key) % 16384算出的槽号去查是否指向自己,如果是则进行处理,如果不是,则返回moved错误,moved错误携带正确的节点IP和端口号返回客户端并指引其转向执行,而后客户端每次关于该key都会去moved返回的节点执行。
当节点的key正在迁移的时候,收到关于该key的请求,那么节点会返回ask错误,并但会正确的节点ip和端口号给客户端去执行。但是这个转向只对本次请求有效,后面关于该key的请求还是会发送到目前正在处理key迁移的节点,直到key迁移完毕并发送广播通知。
当有新节点D加入时,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到D上。会变成下面这样:
节点A:1364 ~ 5460
节点B:6826 ~ 10922
节点C:12287 ~ 16383
节点D:0 ~ 1364, 5461 ~ 6826, 10923 ~ 12287
删除节点也是类似,数据会均匀的迁移到剩余节点上,迁移完成后就可以删除这个节点了。
-
redis容错架构图
-
容错的细节
1.如果半数以上master节点与master节点通信超时(cluster-node-timeout),认为当前master节点挂掉 2.收到((error) CLUSTERDOWN The cluster is down)错误,整个集群不可用(cluster_state:fail),所有对集群的操作做都不可用 #注意: 如果集群任意master挂掉,且当前master没有slave.集群进入fail状态 也可以理解成进群的slot映射[0-16383]不完成时进入fail状态.
-
redis集群搭建
#注意 : 1. 判断一个是集群中的节点是否可用,是集群中的所用主节点选举过程,如果半数以上的节点认为当前节点挂掉,那么当前节点就是挂掉了,所以搭建redis集群时建议节点数最好为奇数. 2. 搭建集群至少需要三个主节点,三个从节点,至少需要6个节点
1.准备至少六台机器
port 7000 - 7005
2.修改六台机器的配置文件
cluster-enabled yes //开启集群模式
cluster-config-file nodes-.conf //集群节点配置文件
cluster-node-timeout 15000 //集群节点超时时间(默认 15秒)
appendonly yes //开启AOF持久化
3.构建集群
./redis-server /root/7000/redis.conf
./redis-server /root/7001/redis.conf
./redis-server /root/7002/redis.conf
./redis-server /root/7003/redis.conf
./redis-server /root/7004/redis.conf
./redis-server /root/7005/redis.conf
./redis-cli --cluster create 192.168.244.15:7000 192.168.244.15:7001 192.168.244.15:7002 192.168.244.15:7003 192.168.244.15:7004 192.168.244.15:7005 --cluster-replicas 1
注意:
修改成自己定义的端口号
4. 输入yes后集群确定搭建,输入其它命令不创建集群
5.客户端操作集群
a)集群中的节点都是平等的,连接集群中的任意节点即可
./redis-cli -p 任意port -c
-c: 集群形式
6.查看接群节点的状态
a)使用./redis-cli --cluster check ip:port 查看集群中节点的详细
./redis-cli --cluster check 192.168.64.10:任意可用port
7.节点状态说明
#主节点 :
√ 主节点存在hash slots,且主节点的hash slots 没有交叉
√ 主节点不能删除
√ 一个主节点可以有多个从节点
√ 主节点宕机时多个副本之间自动选举主节点
#从节点 :
√ 从节点没有hash slots
√ 从节点可以删除
√ 从节点不负责数据的写,只负责数据的同步
√ 从节点对应的主节点宕机,该从节点会变为主节点
-
集群节点的操作
1.向集群中添加一个主节点 ./redis-cli --cluster add-node 192.168.132.16:7006 192.168.132.16:7005
参数说明
i. 第一个参数是新节点的地址,
ii. 第二个参数是任意一个已经存在的节点的IP和端口
#注意 :
1.该节点必须以集群模式启动
2.默认情况下该节点就是以master节点形式添加,但是该节点没有hashslots
2.向集群中添加副本节点
./redis-cli --cluster add-node 192.168.132.16:7007 192.168.132.16:7006 --cluster-slave
参数说明 :
add-node 添加从节点
参数一:新的从节点
参数二:集群中的任意节点
#注意:
当添加副本节点时没有指定主节点,redis会随机给副本节点较少的主节点添加当前副本节点
---
~~~txt
3.为某个指定主节点添加从节点
./redis-cli --cluster add-node 192.168.244.15:7007 192.168.244.15:7000 --cluster-slave --cluster-master-id 747e1871ea74103e4a6dd0137dd5a222270462b0
参数说明 :
参数一: 新添加的从节点
参数二: 集群中的任意节点
--cluster-master-id 主节点id
4.从集群中删除副本节点
./redis-cli --cluster del-node 192.168.244.15:7005 17a31e662ff7b1854da2b3c8e62088814b4a884c
参数说明:
参数一是任意一个节点的地址
参数二是想要删除的节点id
#注意 :
被删除的节点必须是从节点或没有被分配hash slots的节点
5.节点的从新分片
a)集群节点(hash slots)的从新分配(reshard)
使用 ./redis-cli --cluster reshard 192.168.132.16:7006
#说明 :
需要指定集群中其中一个节点的地址就会自动找到集群中的其他主节点。
b)为哪个主节点分配hash slots
21.后台连接Redis集群
- Jedis操作Redis
注意:
避免redis某个节点宕机的问题,一般会配置多个连接
集群模式下不能再切换库,只能用0号库
flushall, flushdb 都不能用
- SpringData操作Redis