redis笔记
文章目录
1、Redis的安装
1.1 win安装
1、官网下载安装包:https://redis.io/
2、解压
3、Redis启动
Redis和MySQL相同也是典型的C/S架构的系统,所以我们需要先启动服务器端然后再使用Redis的客户端登录Redis服务器端。
4、服务端启动
进入到解压后的目录,双击
redis-server.exe
或是在cmd窗口下执行redis-server redis.windows.conf
当然,要是长时间需要使用到redis启动等命令,那么也可以配置一个环境变量
5、客户端启动
Redis默认没有密码,设置密码方式为,将下图用红色框圈出的文件使用NotePad++打开然后将requirepass的注释打开,然后再其后面写上自己项设定的密码即可,我已经密码设置为123456所以上文客户端登录可以直接使用123456作为密码登录。
远程服务上执行命令
如果需要在远程 redis 服务上执行命令,同样我们使用的也是 redis-cli 命令。
语法:
redis-cli –h IP地址 –p 端口 –a 密码
如果是登录远程redis需要使用-h参数,我们本次登录本机redis则不需要使用-h参数。
本地客户端登录用redis-cli 密码登陆(redis-cli -a password)
或者双击
redis-cli.exe
后,执行auth 密码
登录6、Redis关闭
第一种关闭方式:
非正常关闭,比如断电,直接杀死Redis进程等方式。这种方式容易造成数据丢失。
第二种关闭方式
(正常关闭、数据保存)通过客户端使用shutdown命令关闭redis服务。
1.2 Linux下使用Docker安装(推荐)
1、安装Docker即Docker的基础使用
2、执行以下指令
首次使用Docker会下载redis,可能会很慢,可以配置阿里云代理等方式
docker run -p 6379:6379 --name redis --privileged=true -v /app/redis/redis.conf:/etc/redis/redis.conf -v /app/redis/data:/data -d redis redis-server /etc/redis/redis.conf 命令解释: docker run -p 6379:6379 #端口映射 --name redis #设置容器名字 --privileged=true #开启权限 -v /app/redis/redis.conf:/etc/redis/redis.conf #配置容器卷,用于数据持久化和配置读取 -v /app/redis/data:/data -d redis redis-server /etc/redis/redis.conf #读取我们修改好的配置
3、Redis配置文件
随便找个redis配置文件,或者是使用win安装中下载下来的配置文件,复制或是复制文件中内容,放到Linux
/app/redis/redis.conf
即可4、设置Redis自启动(可选)
docker update redis --restart=always
5、客户端连接到redis
在Linux下连接redis
[root@centos100 redis]# docker exec -it redis /bin/bash root@3b6932343d28:/data# redis-cli 127.0.0.1:6379> auth 123456 OK 后续就可以直接执行Redis的指令
在win连接到redis
如果你下载了win安装redis的安装包,那么,进入到解压后的目录执行cmd,执行
redis-cli –h IP地址 –p 端口 –a 密码
命令,也可以连接到Linux下的redis如果没有下载,也可以使用redis的图形用户工具连接,如RedisDesktopManager
Redis集群也会使用Docker安装,当然要是想跟深入了解可以看Docker高级篇中详细记录了集群安装及其思路
1.3 Centos安装
1、win网页下载好tar包后,将文件上传到centos中
2、使用
tar -zxvf redis-x.x.x.tar.gz
进行解压3、可以发现,生成了一个redis文件夹,进入文件夹可以看到,里面存在一个Makefile文件
4、安装gcc
yum install -y gcc
5、进入解压目录后执行以下命令
make MALLOC=libc
6、编译完成后执行以下命令
make install PREFIX=/usr/redis
7、进入
/usr/redis/bin
执行./redis-server,启动服务
8、新增一个窗口
/usr/redis/bin
,进入,执行./redis-cli -h 127.0.0.1 -p 6379
连接到服务
redis细节
1、redis启动服务的细节
注意:使用./redis-server方式启动,使用的是redis-server这个shell脚本的默认配置
2、如何在启动redis时使用自定义的配置
在默认redis安装完成之后,在安装目录没有任何配置文件,需要在源码目录中复制配置文件到安装目录
a.复制配置文件
cp ~/redis-6.2.6/redis.conf .
此时,使用
ls
就可以看到复制过来的配置文件b.进入bin目录加载配置
./redis-server ../redis.conf
使用redis.conf配置文件启动服务
c.修改redis.conf文件中port 7000
使用
./redis-server ../redis.conf
启动服务d.执行
./redis-cli -h 127.0.0.1 -p 7000
连接到服务
3、redis中库的概念
库:用来存放数据的一个基本单元,一个库可以存放ket-value键值对,redis中每一个库都有一个唯一的编号,从0开始
库的个数:默认情况下为16个库,即编号0-15,默认使用0号库
4、redis客户端使用中文时
./redis-cli -p 7000 --raw
2、常用命令
2.1 库操作
Redis默认16个数据库,类似数组下标从0开始,初始默认0号库
命令 | 说明 |
---|---|
select | 切换数据库,例:select 3 |
dbsize | 查看当前数据库的key数量 |
flushdb | 清空当前库 |
flushall | 清空所有库 |
2.2 Redis键(Key)
keys * | 查看当前库的所有数据,查找符合条件的key,如还可以: |
---|---|
keys h?llo 匹配hello,hallo,hbllo | |
keys h*llo 匹配heeeeeello,0-多个字符 | |
keys h[ae]llo 匹配hello,hallo,但是不匹配hbllo | |
exists key | 判断某个可以是否存在,判断多个key时,只要有一个存在就返回1 |
type key | 查看key是什么类型 |
del key | 删除一个或多个key数据 |
unlink key | 根据value选择非阻塞是删除(仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作) |
ttl key | 查看key还有多少秒过期,-1表示永不过期,-2表示已经过期,也代表key不存在 |
expire key 10 | 10秒钟:给定key的过期时间 |
move key db | 将当前数据库的key移动到指定的db中 |
rename key newkey | 修改key为newkey,当key不存在时返回错误 |
2.3 字符串(String)
string类型是二进制安全的,这意味着Redis的string可以包含任何数据,如jpg图片或者反序列化对象。
string是Redis最基本的数据类型,一个Redis中的字符串value最多可以是512M
set 添加键值对
NX:当前数据库中key不存在时,可以将key-value添加到数据库XX:当前数据库key存在时,可以将key-value添加到数据库,与NX互斥
EX:key超时秒数
PX:key的超时毫秒数,与EX互斥
命令 | 说明 |
---|---|
get | 查询对应键值对 |
append | 将给定的value追加到原值的末尾,返回追加后的字符串长度 |
strlen | 获得值的长度 |
setnx | 只有key不存在时,设置key的值 |
incr | 将key中储存的值增1,只能对数字值使用,如果为空,新增值为1,返回增加后的结果 |
decr | 将key中储存的值减1,只能对数字值使用,如果为空,新减值为-1,返回减少后的结果 |
incrby/decrby <步长> | 将key存储的数字增减。自定义步长 |
mset … | 同时设置一个或是多个key-value |
mset … | 同时获取一个或是多个key |
msetnx … | 同时设置一个或是多个key-value,当且仅当所有给定的key都不存在,当一个不成功,都不成功 |
getrange <起始位置> <结束位置> | 获取值的范围,类似java中的sbstring,前包、后包 |
setrange <起始位置> | 用value覆盖key所存储的字符串,从起始位置开始(索引从0开始) |
setex <过期时间> | 设置键值的同时,设置过期时间 |
getset | 以旧值取代新值 |
2.4 列表(List)
Redis列表是简单地字符串列表,按照插入顺序,你可以添加一个元素到头部或是尾部
它的底层实际是一个双向链表,对两端的操作性能跟高,通过索引下标操作中间节点的性能可能会很差
内存模型
lpush/rpush … | 从左边/右边插入一个值 |
---|---|
lpop/rpop | 从左边/右边吐出一个值。值在健在,值无键亡 |
rpoplpuch | 从key1中右边取出一个值插入到key2左边 |
lrange
| 按索引下标获得元素(从左到右) |
lrange mylist 0 -1 #表示获取所有值 | |
lindex | 按照索引位置获取元素(从左到右) |
llen | 获取列表长度 |
linsert before | 在value后面插入newvalue |
lrem | 从左边删除n个value |
lset | 将列表key中index位置的值替换成value |
lpushx/rpushx … | 同lpush,但是要保证这个key存在 |
ltrim | 保留列表中指定区间的元素 |
2.5 集合(set)
Set是Redis的string类型的无序集合,它的底层是value为null的hash表,所以添加、删除、查找的时间的复杂度都接近O(1)
内存模型
命令 | 说明 |
---|---|
sadd … | 将一个或多个member元素加入到集合key中,已经存在的member忽略 |
smembers | 取出给集合的所有值 |
sismember | 判断集合key中是否含有该value值,有则为1,没有为0 |
scard | 返回该集合的元素个数 |
srem … | 删除集合中的某些元素 |
spop | 随机从集合中取出一个值 |
srandmember | 随机从集合中取出n个值,不会从集合中删除 |
smove value | 把集合中一个值从一个集合移动到另一个集合,比武时同种类型 |
sinter | 返回两个集合中交集部分 |
sunion | 返回两个集合中并集部分 |
sdiff | 返回集合中差集部分(key1中的不包含key2中的),去掉第一个集合中,其他集合中存在的元素 |
spop | 随机返回一个元素,并从集合中删除 |
2.6 ZSet
特点:可排序的set集合、无序、不可重复
内存模型
命令 | 说明 |
---|---|
zadd | 添加一个有序集合元素 |
zcard | 返回集合中元素个数 |
zrange 升序/zrevrange 降序 | 返回一个范围内的元素 |
zrangebyscore | 按照分数查找一个范围内的,还支持分页查询 |
zrank | 返回排名 |
zrevrank | 返回倒序排名 |
zscord | 显示某个元素的分数 |
zrem | 删除某个元素 |
zincrby | 给某个特定元素加分 |
2.7 Hash类型
redis key(string) value(map)
Map<String,Map<String,value>>
特点:
value是map结构,key无序
内存模型
命令 | 说明 |
---|---|
hset | 设置一个key-value对 |
hget | 获取一个key对应的value |
hgetall | 获取所有key-value键值对 |
hdel | 删除某个键值对 |
hexists | 判断是否存在这个key |
hkeys | 获取所有key |
hvals | 获取所有value |
hmset | 一次性设置多个key-value |
hmget | 一次性获取多个value |
hsetnx | 设置一个不存在的key的值 |
hincrby | 为value做假发运算 |
hincrbyfloat | 为value加上浮点值 |
3、持久化机制
Redis官方提供了两种持久化机制:
- 快照(Snopshot)
- AOF(Append only file) 只追加日志文件
3.1 快照
1、特点
这种方式可以某一时刻的所有数据都写入磁盘中,当然这也是redis默认的持久化方式,保存的文件是以
.rdb
形式结尾的文件,因此,也被称之为RDB方式
2、快照的生成方式
- 客户端方式:save和bgsave命令
- 服务器配置自动触发
1、客户端方式之bgsave
客户端可以使用bgsave来创建一个快照,当接收到用户bgsave命令时,redis会调用fork创建一个子进程,然后子进程负责进行持久化,而父进程则继续处理请求。
名词解释: fork当一个进程创建子进程的时候,底层的操作系统会创建该进程的一个副本,在类unix系统中创建子进程的操作会进行优化:在刚开始的时候,父子进程共享相同内存,直到父进程或子进程对内存进行了写之后,对被写入的内存的共享才会结束服务。
2、客户端方式之save 客户端还可以使用save命令来创建一个快照,接收到命令的redis服务器在快照创建完毕之前不会再接收客户端的其他命令
3、服务器配置方式之自动配置
如果用户在redis.conf配置文件中设置了save选项,redis会在满足save选项条件之后自动触发一次bgsave命令,如果设置多个save选项,当任意一个save选项条件满足,redis都会自动触发一次bgsave命令
4、服务器接收到客户端shutdown命令
当redis通过shutdown指令接受到关闭服务器请求时,会执行一个save命令,阻塞所有客户端,不再接受客户端发送的任何指令,并且在save执行结束后关闭服务端
3、配置生成快照的名称和位置
# 1、配置修改快照的名称
dbfilename dump.rdb
# 2、配置生成位置
dir ./
3.2 AOF 只追加日志文件
1、特点
这种方式将客户端执行的写命令记录到日志文件中,AOF持久化会将被执行的写命令写到AOF的文件末尾,以此来记录数据发生的变化,因此只要redis从头到尾执行一次AOF文件中的所有写命令,就可以恢复AOF文件记录的数据集
2、开启AOF持久化
在redis的默认配置中没有开启AOF持久化,需要在配置文件中进行配置
appendonly no -> appendonly yes #开启AOF持久化 appendfilename "appendonly.aof" #指定配置文件名称和位置
3、日志生成频率
1、always【谨慎使用】
说明:每个redis写命令都要同步写入硬盘,严重降低redis速度 解释:如果用户使用了always选项,那么每个redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据丢失减到最少;遗憾的是,因为这种同步策略需要对硬盘进行大量的写入操作,所以redis处理命令的速度会受到硬盘性能的限制 注意:转盘式硬盘在这种频率下208左右个命令/s ﹔固态硬盘(SSD)几百万个命令/s 警告∶使用SSD用户请谨慎使用always选项,这种模式不断写入少量数据的做法有可能会引发严重的写入放大问题,导致将固态硬盘的寿命从原来的几年降低为几个月。
2、everysec【推荐】
说明:每秒执行一次同步显式的将多个写命令同步到磁盘 解释:为了兼顾数据安全和写入性能,用户可以考虑使用everysec选项,让redis每秒一次的频率对AOF文件进行同步; redis每秒同步一次AOF文件时性能和不使用任何持久化特性时的性能相差无几,而通过每秒同步一次AOF文件, redis可以保证,即使系统崩溃,用户最多丢失—秒之内产生的数据。
3、no【不推荐】
说明:由操作系统决定何时同步 解释:最后使用no选项,将完全有操作系统决定什么时候同步AOF日志文件,这个选项不会对redis性能带来影响但是系统崩溃时,会丢失不定数量的数据,另外如果用户硬盘处理写入操作不够快的话,当缓冲区被等待写入硬盘数据填满时, redis会处于阻塞状态,并导致redis的处理命令请求的速度变慢。
3.3 AOF文件的重写
1、AOF存在的问题
AOF同时也带了一些问题,持久化文件会越来越大。例如我们调用incr test命令100次,文件必须保存全部的100次命令,,其实有99条都是多余的。因为要恢复数据库的状态其实只需要一条set test 100就够了,为了压缩AOF持久化文件,redis为我们提供了AOF重写机制。
2、AOF重写
用来一定程度上减小AOF文件大小
3、触发重写的方式
a.客户端方式触发重写
执行bgrewritreaof
b.服务器配置方式自动触发
配置redis.conf中的auto-aof-rewrite-percentage选项
如果设置
auto-aof-rewrite-percentage 100
和auto-aof-rewrite-min-size 64mb
,并启用AOF持久化时,那么当AOF文件大于64mb时,并且AOF文件的体积大了至少一倍(100%)时,会自动触发,如果触发过于频繁,用户可以考虑将auto-aof-rewrite-percentage
设置为更大。如:64mb --> 20mb --> 40mb --> 18mb --> 36mb --> 26mb --> 52mb
即
auto-aof-rewrite-min-size
为重写阈值,而auto-aof-rewrite-percentage 100
控制重写频率。
3.4 重写原理
重写AOF文件的操作,并没有读取旧的AOF文件,而是将整个内存中的数据用命令的方式重写了一个新的AOF文件,替换里原有的文件,这点类似快照。
重写流程: 1. redis调用fork ,现在有父子两个进程子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令 2、父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。 3.当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。 4.现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
3.5 持久化总结
两种持久化方案,既可以同时使用(aof),也可以单独使用,在某些情况下也可以都不使用,具体使用哪种方案取决于用户的数据和应用决定。
无论使用AOF还是快照机制进行持久化,将内存中的数据写入到磁盘都是有必要的,处理持久化外,用户还应该对持久化的文件进行备份(最好备份在多个不同地方)。
4、Java操作Redis
1、 引入依赖
<!-- 引入jedis依赖 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.1.1</version>
</dependency>
2、创建Jedis对象
//1、创建Jedis客户端对象
Jedis jedis = new Jedis("192.168.75.100", 7000);//1、关闭防火墙 2、开启远程连接
//2、选择一个库,默认使用0号库
jedis.select(0);
Set<String> keys = jedis.keys("*");
keys.forEach(System.out::println);
//清空所有库
jedis.flushAll();
//清空当前库
jedis.flushDB();
//3、释放资源
jedis.close();
3、操作key
private Jedis jedis;
@Before
public void before(){
this.jedis = new Jedis("192.168.75.100", 7000);
}
@After
public void after(){
this.jedis.close();
}
@Test
public void testKeys(){
//删除一个
jedis.del("name");
//删除多个
//jedis.del("name","age");
//判断key是否存在
boolean name = jedis.exists("name");
System.out.println(name);
//设置一个超时时间
//long age = jedis.expire("age", 100);
//System.out.println("age = " + age);
//查看key超时时间
//long ttl = jedis.ttl("age");
long ttl = jedis.ttl("age");
System.out.println("ttl = " + ttl);
//随机获取一个key
String s = jedis.randomKey();
System.out.println("s = " + s);
//修改名称
jedis.rename("age","newage");
}
4、操作String
//set
jedis.set("name","zhangsan");
//get
jedis.get("name");
//mset
jedis.mset("aa","张三","bb","李四");
//mget
List<String> mget = jedis.mget("aa", "name", "bb");
mget.forEach(v -> System.out.println("v = " + v));
//getset
String set = jedis.getSet("name", "小明");
System.out.println("set = " + set);
//.......
5、操作List
//lpush
jedis.lpush("lists","aaa");
//lrage
List<String> lists = jedis.lrange("lists", 0, -1);
lists.forEach(v-> System.out.println("v = " + v));
//lpop
jedis.lpop("lists");
jedis.linsert("lists", ListPosition.BEFORE,"aaa","ccc");
//.......
5、SpringBoot操作Redis
Spring Boot Dath Redis 中提供了RedisTemplate和StringRedisTemplate,其中StringRedisTemplate是RedisTemplate的子类,两个方法基本一致,不同之处主要体现在操作的数据类型不同,RedisTemplate中的两个泛型都是object,意味着存储的key和value都可以是一个对象,而StringRedisTemplate的两个泛型都是String,意味着stringRedisTemplate的key和value都只能是字符串。
注意:使用RedisTemplate默认是将对象序列化到Redis中,所以放入的对象都要实现对象序列化接口
5.1 环境准备
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、配置文件
#redis
spring.redis.host=192.168.75.100 #要连接的ip地址
spring.redis.port=7000 #端口
spring.redis.database=0 #要使用哪个库
5.2 使用StringRedisTemplate
//启动springboot应用
//指向主启动类
@SpringBootTest(classes = RedisDemo02Application.class)
@RunWith(SpringRunner.class)
public class TestRedisTemplate {
//注入StringRedisTemplate
@Autowired
private StringRedisTemplate stringRedisTemplate;
//操作key相关'
@Test
public void testKey(){
//删除key
//stringRedisTemplate.delete("name");
//判断key是否存在
Boolean hasKey = stringRedisTemplate.hasKey("name");
System.out.println("hasKey = " + hasKey);
//判断一个key对应类型
DataType type = stringRedisTemplate.type("name");
System.out.println("type = " + type);
//获取所有key
Set<String> keys = stringRedisTemplate.keys("*");
keys.forEach(v-> System.out.println("v = " + v));
//获取key超时时间 -1 永不超时 -2 key不存在 >=0 超时时间
Long expire = stringRedisTemplate.getExpire("name");
System.out.println("expire = " + expire);
//修改key名称
//如果不存在会报错
//stringRedisTemplate.rename("name","newName");
stringRedisTemplate.move("newName",3);
}
//操作redis中的字符串
@Test
public void testString(){
//opsForValue 操作的就是String类型
stringRedisTemplate.opsForValue().set("name","张三");
String name = stringRedisTemplate.opsForValue().get("name");
System.out.println("name = " + name);
//设置key超时时间 60s
stringRedisTemplate.opsForValue().set("code","3478",60, TimeUnit.SECONDS);
//追加
stringRedisTemplate.opsForValue().append("name","是一个好人");
}
//操作list类型
@Test
public void testList(){
//创建一个列表,插入一个元素
stringRedisTemplate.opsForList().leftPush("lists","aaa");
//创建一个列表,插入多个元素
stringRedisTemplate.opsForList().leftPushAll("lists","bbb","ccc","ddd");
ArrayList<String> list= new ArrayList<>();
list.add("小张");
list.add("小明");
//创建一个列表,插入list中的值
stringRedisTemplate.opsForList().leftPushAll("lists",list);
//查看列表
List<String> lists = stringRedisTemplate.opsForList().range("lists", 0, -1);
assert lists != null;
lists.forEach(v-> System.out.println("v = " + v));
}
//操作set
@Test
public void testSets(){
stringRedisTemplate.opsForSet().add("sets","zhangsan","张三","xiaoming");
Set<String> sets = stringRedisTemplate.opsForSet().members("sets");
assert sets != null;
sets.forEach(v -> System.out.println("v = " + v));
Long sets1 = stringRedisTemplate.opsForSet().size("sets");
System.out.println("sets1 = " + sets1);
}
//操作zset
@Test
public void testZset(){
//创建并放入元素
stringRedisTemplate.opsForZSet().add("zsets","xiaoming",10);
Set<String> zsets = stringRedisTemplate.opsForZSet().range("zsets", 0, -1);
zsets.forEach(v-> System.out.println("v = " + v));
Set<ZSetOperations.TypedTuple<String>> zsets1 = stringRedisTemplate.opsForZSet().rangeByScoreWithScores("zsets", 0, 100);
zsets1.forEach(v-> System.out.println("v = " + v));
}
//操作Hash类型
@Test
public void testHash(){
stringRedisTemplate.opsForHash().put("hashs","name","zhangsan");
String o = (String) stringRedisTemplate.opsForHash().get("hashs", "name");
List<Object> hashs = stringRedisTemplate.opsForHash().values("hashs");
hashs.forEach(System.out::println);
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("age","19");
hashMap.put("gender","man");
//插入多个值
stringRedisTemplate.opsForHash().putAll("hashs",hashMap);
//获取多个值
List<Object> values = stringRedisTemplate.opsForHash().multiGet("hashs", Arrays.asList("name", "age"));
System.out.println("values = " + values);
}
}
5.3 使用RedisTemplate
创建一个User类
要实现序列化接口
@Data
//可是使用链式的set
@Accessors(chain = true)
public class User implements Serializable {
private String id;
private String name;
private Integer age;
private Date bir;
}
直接使用RedisTemplate创建一个User对象存储到Redis中
@Test
public void testRedisTemplate(){
User value = new User();
value.setId(UUID.randomUUID().toString()).setName("张三").setAge(21).setBir(new Date());
redisTemplate.opsForValue().set("user", value);
}
使用RedisTemplate操作
可以发现,直接使用RedisTemplate,key-value都会使用到jdk的序列化,经过序列化后的结果在终端上难以查看
RedisTemplate对象中key-value都是使用的JdkSerializationRedisSerializer,因为这种序列化方式,key-value都支持Object
因此,常常我们在使用之前都会修改key的序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
User value = new User();
value.setId(UUID.randomUUID().toString()).setName("张三").setAge(21).setBir(new Date());
redisTemplate.opsForValue().set("user", value);
User user = (User) redisTemplate.opsForValue().get("user");
System.out.println("user = " + user);
但是,又会遇到下一个问题:那就是hash类型下,有两个key,那么,刚刚才设置了一个key的序列化,另一个要怎么办呢?
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//设置key的序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
User value = new User();
value.setId(UUID.randomUUID().toString()).setName("张三").setAge(21).setBir(new Date());
redisTemplate.opsForValue().set("user", value);
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("李四");
redisTemplate.opsForList().leftPushAll("lists",list);
redisTemplate.opsForSet().add("sets",user);
redisTemplate.opsForZSet().add("zsets",user,10);
redisTemplate.opsForHash().put("hashs","user",user);
5.4 Bound API
spring data 为了方便我们对于redis更友好的操作,因此有了bound api
当要对某个key多次操作,那么
stringRedisTemplate.opsForValue().
这段代码要重复多次,因此Bound API对这种情况也有了更友好的操作
使用bound api
stringRedisTemplate.opsForValue().set("name","zhangsan");
stringRedisTemplate.opsForValue().append("name","是一个好孩子");
stringRedisTemplate.opsForValue().get("name");
上下代码效果等同
BoundValueOperations<String, String> nameOperations = stringRedisTemplate.boundValueOps("name");
nameOperations.set("lisi");
nameOperations.append("真好看");
nameOperations.get();
5.5 总结
1、针对key-value都是String的情况,使用StringRedisTemplate。
2、针对处理key-value中有对象的情况,使用RedisTemplate,在使用时记得序列化。
3、针对一个key有多次操作的情况,使用boundXxxOps()的api可以简化书写。
6、Redis应用场景
1、利用redis中字符串类型的超时功能,可以用来存储手机验证码。
2、利用redis中字符串类型完成具有时效性的业务功能(如:淘宝/12306订单超时)
3、完成分布式集群系统中的session共享
4、利用zset类型(可排序、分页),实现排行榜
5、分布式缓存
6、存储认证之后的token认证
7、解决分布式集群中分布式锁问题(lua脚本)
7、分布式缓存
7.1 Redis分布式缓存(一)
1、什么是缓存
定义:就是计算机内存中一段数据
2、内存中数据的特点
a.读写快
b.断电立即丢失
3、缓存解决了什么问题
a.提高网站的吞吐量,提高网站运行效率
b.一定程度上减轻原始数据库的访问压力
4、既然缓存可以提高效率,那么项目中所有数据都加入到缓存中岂不是更好?
使用缓存时,一定是数据库中数据极少发生修改,更多用于查询信息这种情况
5、什么是本地缓存?什么是分布式缓存?
本地缓存:存在应用服务器内存中的数据称之为本地缓存(local cache)
分布式缓存:存储在当前应用服务器内存之外的数据称之为分布式缓存(distribute cache)
集群:将同一个服务的多个节点,放在一起,对系统提供服务的过程被称之为集群
分布式:由多个不同的服务集群共同对系统提供服务,这个系统称之为分布式系统(distribute system)
6、利用mybatis自身的本地缓存结合redis实现分布式缓存
a.mybatis中应用缓存(二级缓存) SqlSessionFaction级别缓存,所有会话共享
b.如何开启(二级缓存)
mapper.xml中加入
<catch/>
标签 --> 本地缓存 c.查看cache标签缓存实现,默认使用
PerpetualCache
分析源码可知,我们只需要重写put/get方法即可
结论:
1、Mybatis默认使用
org.apache.ibatis.cache.impl.PerpetualCache
2、默认缓存使用的是HashMap
d.自定义RedisCache实现
1、通过源码可以看到,可以自定义Cache实现类,并对里面的方法进行重写
2、使用RedisCache
自定义RedisCache实现
//自定义cache实现
public class RedisCache implements Cache {
///当前放入缓存的mapper的namespace
private final String id;
//必须存在有id的构造参数
public RedisCache(String id) {
this.id = id;
}
//返回cache的唯一标识
@Override
public String getId() {
return this.id;
}
//缓存中放入值
@Override
public void putObject(Object key, Object value) {
//通过工具类获取RedisTemplate
RedisTemplate redisTemplate = getRedisTemplate();
//使用redis中的hash作为缓存存储模型
redisTemplate.opsForHash().put(id.toString(), key.toString(), value);
}
//获取缓存中的值
@Override
public Object getObject(Object key) {
//通过工具类获取RedisTemplate
RedisTemplate redisTemplate = getRedisTemplate();
//根据key从redis中获取数据
return redisTemplate.opsForHash().get(id.toString(), key.toString());
}
//删除缓存中的值
//注意:这个方法为mybatis的保留方法,后续版本可能实现
@Override
public Object removeObject(Object key) {
return null;
}
//增删改都需要执行这个方法,清空缓存
@Override
public void clear() {
//通过工具类获取RedisTemplate
RedisTemplate redisTemplate = getRedisTemplate();
//清空缓存
redisTemplate.delete(id.toString());
}
//用来计算缓存数量
@Override
public int getSize() {
//通过工具类获取RedisTemplate
RedisTemplate redisTemplate = getRedisTemplate();
//获取hash中key数量
return redisTemplate.opsForHash().size(id.toString()).intValue();
}
//自定义获取热第三Template
private RedisTemplate getRedisTemplate() {
//通过工具类获取RedisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
}
测试:
@SpringBootTest(classes = RedisDemo02Application.class)
@RunWith(SpringRunner.class)
public class TestUserService {
@Autowired
private UserService userService;
@Test
public void testUser(){
List<User> all = userService.findAll();
all.forEach(System.out::println);
userService.findAll();
}
@Test
public void testFindById(){
System.out.println(userService.finaById("1"));
System.out.println(userService.finaById("1"));
}
@Test
public void testDeleteById(){
userService.deleteById("1");
}
@Test
public void testInsert(){
User user = new User();
user.setName("xxc").setAge(21).setBir(new Date());
userService.save(user);
}
@Test
public void testUpdate(){
User user = new User();
user.setName("xxc").setAge(24).setBir(new Date()).setId("011e6608-1de1-4388-b1ce-6e50c9375cdb");
userService.update(user);
}
}
根据测试可以发现,当执行增删改都会执行清空缓存的方法
7.2 Redis分布式缓存(二)
此时,其实还存在一个问题,那么就是:
1、清空缓存时,只能清除自己这个namespace(redis中,以namespace为key)的键值对,会造成以下问题:
a.如果项目中表查询之间没有任何关联查询,那么现在使用的这种缓存方式没有问题
b.现有缓存在有表连接查询过程中一定会有问题
2、如何解决这个问题
:用来将多个具有关联关系查询的缓存放在一起进行处理
例如:
<!--关联关系缓存处理--> <cache-ref namespace="com.xxc.redisdemo02.dao.UserDao"/>
7.3 Redis分布式缓存(三)
1、缓存优化策略
对放入redis的可以key优化:key的长度不能太长:
-1781060725:4117791490:com.xxc.redisdemo02.dao.UserDao.findAll:0:2147483647:select * from t_user:SqlSessionFactoryBean
尽可能将key设计的更简短?
算法:MD5处理(加密)
特点:1、一切文件字符串经过MD5处理之后,都会生成32位16进制的字符串
2、不同内容的文件经过MD5进行加密,加密结果一定不一致(问:a.txt和b.txt怎么判断内容是否相同。答:MD5加密,如果一致,那么内容 相同)
3、相同内容经过MD5生成结果始终一致
推荐:在redis整合MyBatis过程中,进行MD5加密
//自定义cache实现
public class RedisCache implements Cache {
///当前放入缓存的mapper的namespace
private final String id;
//必须存在有id的构造参数
public RedisCache(String id) {
this.id = id;
}
//返回cache的唯一标识
@Override
public String getId() {
return this.id;
}
//缓存中放入值
@Override
public void putObject(Object key, Object value) {
//通过工具类获取RedisTemplate
RedisTemplate redisTemplate = getRedisTemplate();
//使用redis中的hash作为缓存存储模型
redisTemplate.opsForHash().put(id.toString(), getKeyToMd5(key.toString()), value);
}
//获取缓存中的值
@Override
public Object getObject(Object key) {
//通过工具类获取RedisTemplate
RedisTemplate redisTemplate = getRedisTemplate();
//根据key从redis中获取数据
return redisTemplate.opsForHash().get(getKeyToMd5(key.toString()), key.toString());
}
//删除缓存中的值
//注意:这个方法为mybatis的保留方法,后续版本可能实现
@Override
public Object removeObject(Object key) {
return null;
}
//增删改都需要执行这个方法,清空缓存
@Override
public void clear() {
//通过工具类获取RedisTemplate
RedisTemplate redisTemplate = getRedisTemplate();
//清空缓存
redisTemplate.delete(id.toString());
}
//用来计算缓存数量
@Override
public int getSize() {
//通过工具类获取RedisTemplate
RedisTemplate redisTemplate = getRedisTemplate();
//获取hash中key数量
return redisTemplate.opsForHash().size(id.toString()).intValue();
}
//自定义获取热第三Template
private RedisTemplate getRedisTemplate() {
//通过工具类获取RedisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
private String getKeyToMd5(String keyString){
return DigestUtils.md5DigestAsHex(keyString.getBytes());
}
}
2、面试相关概念
1)什么是缓存穿透?
定义:客户端查询了一个数据库中没有的数据记录,导致缓存在这种情况无法利用,称之为缓存穿透
MyBatis中的cache是解决了缓存穿透问题的,将数据库中没有查询到的结果也进行缓存(null)
2)什么是缓存雪崩?
定义:在系统运行的某一时刻,突然系统中缓存全部失效,恰好在这一时刻涌来了大量的用户请求,导致所有模块化缓存无法运用,导致极端情况,数据库阻塞或挂起
缓存存储时:业务系统非常大、模块多、业务数据不同,不同模块在放入缓存时,都会设置一个缓存的超时时间
解决方案:1、缓存永久存储(不推荐) 2、针对不同的业务数据一定要设置不同的超时时间
3)什么是缓存击穿?
定义:如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。
解决方案:1、不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间
2、互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者 默认值。
4)有没有遇到过?如何解决?
8、Redis主从复制
主从复制架构仅仅用来解决数据的冗余备份,从节点仅仅用来同步数据
无法解决:master节点出现故障转移
主从复制架构图
搭建主从复制
# 1.准备三台机器并修改配置
- master:
port 6379
bind 0.0.0.0
- slave1:
port 6380
bind 0.0.0.0
slaveof masterip masterport
- slave2:
port 6381
bind 0.0.0.0
slaveof masterip masterport
# 启动3台机器进行测试
cd /usr/redis/bin
./redis-server /root/master/redis.conf
./redis-server /root/slave1/redis.conf
./redis-server /root/slave2/redis.conf
slave节点只可读数据,不能增删改数据
当主节点挂掉之后,slave节点也会持续给主节点发送心跳包,主节点再次启动了之后,slave节点会再次连接上主节点
只解决了数据的冗余备份,如果主节点宕机了还是存在着问题,因此就存在了哨兵机制
9、Redis中的哨兵机制
9.1 哨兵机制
Sentinel(哨兵)是Redis的高可用性解决方案∶由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。简单的说哨兵就是带有自动故障转移功能的主从架构。
无法解决:1、单节点并发压力 2、单节点内存和物理磁盘上限
9.2 架构原理
9.3 哨兵架构搭建
1、 在主节点上创建哨兵机制
在Master对应的redis.conf同目录下创建sentinel.conf,名字绝对不能错
2、 配置哨兵,在sentinel.conf文件中填入以下内容
sentinel monitor 被监控数据库名字(自己起名字) ip port 1(哨兵数量)
超过半数以上哨兵检测到master宕机了,才会执行主从切换的功能
3、 启动哨兵模式进行测试
redis-sentinel /root/sentinel/sentinel.conf
哨兵默认端口 26379
9.4 使用SpringBoot中操作哨兵
#redis单节点
#spring.redis.host=192.168.75.100
#spring.redis.port=7000
#spring.redis.database=0
#Redis sentinel配置
#master写得是哨兵监听的那个名称(对应配置文件中配置的名称)
spring.redis.sentinel.master=mymaster
#连接的不再是一个具体的主机,而是多个哨兵节点
spring.redis.sentinel.nodes=192.168.75.100:26379
注意,如果连接过程中出现错误,可能是没有开启远程连接权限
因此在sentinel配置文件中添加
bind=0.0.0.0
即可
10、Redis集群
10.1 集群
Redis在3.0后开始支持Cluster(模式)模式,目前redis的集群支持节点的自动发现,支持slave-master选举和容错,支持在线分片(sharding shard )等特性。
10.2 集群架构图
PING --> PONG机制
10.3 集群细节
1、所有Redis节点之间彼此互联(PING-PING机制),内部使用二进制协议优化传输速度和带宽
2、节点的fail是通过集群中超过半数的节点检测失效才生效
3、客户端与redis直连,不需要中间proxy层,客户端不需要连接集群所有节点,连接集群中任意一个可用节点即可
4、redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster负责node<->slot<->value
10.4 集群搭建
判断一个是集群中的节点是否可用,是集群中的所用主节点选举过程,如果半数以上的节点认为当前节点挂掉,那么当前节点就是挂掉了,所以搭建redis集群时建议节点数最好为奇数,搭建集群至少需要三个主节点,三个从节点,至少需要6个节点。
1、准备环境安装ruby以及redis集群依赖
yum install -y ruby rubygems
gem install Redis
2、在机器上创建7个目录
mkdir 7000 7001 7002 7003 7004 7005 7006
3、复制配置文件到每个目录
[root@centos100 ~]# cp redis-6.2.6/redis.conf 7000
[root@centos100 ~]# cp redis-6.2.6/redis.conf 7001
[root@centos100 ~]# cp redis-6.2.6/redis.conf 7002
[root@centos100 ~]# cp redis-6.2.6/redis.conf 7003
[root@centos100 ~]# cp redis-6.2.6/redis.conf 7004
[root@centos100 ~]# cp redis-6.2.6/redis.conf 7005
[root@centos100 ~]# cp redis-6.2.6/redis.conf 7006
4、修改配置文件
port 7000 //修改端口
bind 0.0.0.0 //开启远程连接
cluster-enabled yes //开启集群模式
cluster-config-file nodes-port.conf //集群节点配置文件
cluster-node-timeout 5000 //集群节点超时时间
appendonly yes //开启aof持久化
5、指定不同目录下的文件启动七个节点
[root@centos100 bin]# ./redis-server /root/7000/redis.conf
[root@centos100 bin]# ./redis-server /root/7001/redis.conf
[root@centos100 bin]# ./redis-server /root/7002/redis.conf
[root@centos100 bin]# ./redis-server /root/7003/redis.conf
[root@centos100 bin]# ./redis-server /root/7004/redis.conf
[root@centos100 bin]# ./redis-server /root/7005/redis.conf
[root@centos100 bin]# ./redis-server /root/7006/redis.conf
6、查看进程
1、创建集群
1、复制集群操作脚本到bin目录下
[root@centos100 src]# cp ./redis-trib.rb /usr/redis/bin
2、创建集群
./redis-trib.rb --replicas 1 192.168.75.100:7000 192.168.75.100:7001 192.168.75.100:7002 192.168.75.100:7003 192.168.75.100:7004 192.168.75.100:7005 192.168.75.100:7006
我这边使用上面这条命令报错了:
根据提示修改了命令:
./redis-cli --cluster create 192.168.75.100:7000 192.168.75.100:7001 192.168.75.100:7002 192.168.75.100:7003 192.168.75.100:7004 192.168.75.100:7005 192.168.75.100:7006 --cluster-replicas 1
如此就成功了
输入命令后,在这里停止了,输入yes回车后,创建好了集群;
2、查看集群状态
# 查看集群状态 check [原始集群中任意一个节点]
./redis-cli --cluster check 192.168.75.100:7000
# 集群节点状态说明
主节点:
主节点存在hash slots,且主节点的hash slots没有交叉
主节点不能删除
一个主节点可以有多个从节点
主节点宕机时,多个副本之间自动选举主节点
从节点:
从节点没有hash slots
从节点可以删除
从节点不负责数据的写,只负责数据的同步
3、添加主节点
添加主节点 add-node --slave [新加入节点] [集群中任意节点]
./redis-cli --cluster add-node 192.168.75.100:7006 192.168.75.100:7000
注意:
1、该节点必须以集群模式启动
2、默认情况下该节点是以master节点形式添加
4、添加从节点
1、添加从节点 add-node --slave [新加入加点] [集群中任意节点]
./redis-cli --cluster add-node --slave 192.168.75.100:7007 192.168.75.100:7000
注意:
当添加副本节点时没有指定主节点,redis会随机给副本节点较少的主节点添加当前副本节点
2、为确定的master添加主节点 add-node --slave --master-id master节点id [新加入加点] [集群中任意节点]
./redis-cli --cluster add-node --slave --master-id 41a5da77b40241896c2234a1ff23cf1d679a1e33 192.168.75.100:7007 192.168.75.100:7000
这条命令我试过,没成功,花了些时间找到了以下解法:
使用1提供的方法将主从节点都添加进集群中,然后使用下面这个设置主从关系来解决
设置主从关系
./redis-cli -p 7007 cluster replicate 41a5da77b40241896c2234a1ff23cf1d679a1e33
解析:
7007:从节点ip
41a5da77b40241896c2234a1ff23cf1d679a1e33:主节点
5、删除副本节点
1、删除节点 del-node [集群中任意节点] [删除节点id]
./redis-cli --cluster del-node 192.168.75.100:7000 41a5da77b40241896c2234a1ff23cf1d679a1e33
注意:
被删除的节点一定是从节点或是hash slots没有被分配到的节点
6、集群在分片
1、再分片 reshard [集群中任意节点]
./redis-cli --cluster reshard 192.168.75.100:7000
PS:学习过程中遇到的坑
1、使用win直接连接redis时,连接不成功
1、注释掉配置文件redis.conf中的
默认情况下redis拒绝所有的客户端远程连接,因此需要修改配置
bind 127.0.0.1 #该配置会导致,其他ip登录被拒绝。 改为: bind 0.0.0.0 #允许一切用户端连接
2、修改配置文件redis.conf中的
此配置会使redis目前处于受保护模式,不允许非本地客户端链接
protected-mode no
修改为
protected-mode yes
3、关闭虚拟机的防火墙
bash sudo systemctl stop firewalld.service
2、改了以上配置,任然链接不了,出现以下错误
DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server. 3) If you started the server manually just for testing, restart it with the '--protected-mode no' option. 4) Setup a bind address or an authentication password. NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside.
使用谷歌翻译
拒绝 Redis 在保护模式下运行,因为启用了保护模式,没有指定绑定地址,也没有向客户端请求身份验证密码。在此模式下,仅接受来自环回接口的连接。如果您想从外部计算机连接到 Redis,您可以采用以下解决方案之一:
1) 只需禁用保护模式,通过从服务器的同一主机连接到 Redis 从环回接口发送命令 'CONFIG SET protected-mode no'正在运行,但是如果这样做,请确保 Redis 不能从 Internet 公开访问。使用 CONFIG REWRITE 使此更改永久生效。
2) 或者,您可以通过编辑 Redis 配置文件并将保护模式选项设置为“否”来禁用保护模式,然后重新启动服务器。
3) 如果您手动启动服务器只是为了测试,请使用“--protected-mode no”选项重新启动它。
4) 设置绑定地址或认证密码。注意:您只需执行上述操作之一,服务器即可开始接受来自外部的连接。
按照翻译过来的方式修改即可
3、XShell免费版一次性只能开4的连接,继续开会重新启动一个新窗口,这里推荐FinalShell
这边使用了FinallShell 离线激活步骤,适用MAC/WIN提供的方法
4、ruby安装问题
使用yum install -y ruby rubygems
安装ruby,使用ruby -v
查看ruby版本,可以看到是2.0.0,因此在使用gem install -l ./redis-4.7.1.gem
时,会报错,提示ruby版本大于2.4,因此,还要做以下操作
1、安装基本工具
yum install ruby
2、查看当前版本
ruby -v
效果:
ruby 2.0.0p648 (2015-12-16) [x86_64-linux]
3、安装yum源yum install centos-release-scl-rh
4、安装指定版本的ruby
yum install rh-ruby24 -y
5、加载环境变量
scl enable rh-ruby24 bash #加载环境变量
6、查看当前ruby版本
ruby -v
效果:
ruby 2.4.6p354 (2019-04-01 revision 67394) [x86_64-linux]
7、这里你会看到ruby版本已经是高于2.0.0以上的版本了,但是这里有个坑,当你重启虚拟机的时候,你再执行ruby -v会发现莫名其妙的又变成了2.0.0.
莫慌,执行以下命令:vim /etc/profile.d/rh-ruby23.sh
把下面内容粘贴进去
#!/bin/bash source /opt/rh/rh-ruby24/enable export X_SCLS="`scl enable rh-ruby24 'echo $X_SCLS'`" export PATH=$PATH:/opt/rh/rh-ruby24/root/usr/local/bin
写完别忘了 :wq 保存
8、最后scl enable rh-ruby23 bash #加载环境变量 ruby -v
5、rubygems安装
gem下载地址,下载好后上传到centos上,之后使用gem install -l ./redis-4.7.1.gem
安装即可