Redis高级应用

Redis高级应用

事务管理

redis的事务支持:

与其他NoSQL不同Redis是存在事务的,尽管没有传统数据库那么强大,但是还是非常有用,尤其是高并发的情况中,使用redis的事务可以保证数据一致性的同时,大幅度提高数据读写的响应速度

事务特点:

redis的事务是使用watch-multi-exec-discard-unwatch的命令组合,它可以提供两个重要保证:
1.事务是一个被隔离的操作,事务中的方法都会被redis进行序列化并按顺序执行,事务在执行的过程中不会被其他客户端的发出的命令所打断
2.事务是一个原子性操作,它要么全部执行、要么全部不执行

事务操作:

1.multi:开启事务(OK),之后的命令就会进入队列(QUEUED),而不是马上执行
2.exec:执行事务如果被监听的键没有被修改,则采用提交命令,否则就执行回滚命令
3.discard:回滚事务
4.watch key1 key2…:监听某些键,当被监听的键在提交事务前被修改,则事务会回滚(基于乐观锁机制)
5.unwatch key1 key2…:取消监听

注意:

redis不会判断数据类型,如果遇到命令语法格式正确而数据类型不符合的情况,不会事务回滚

Spring操作事务:需要保证是一个连接,所以不能直接使用模板对象
    @Test
    public void redisTransation(){
        //事务必须是同一个连接,此时开启操作都是单挑连接
//        redisTemplate.multi();
//        redisTemplate.opsForValue().set("name","zhangsan");
//        redisTemplate.opsForValue().set("age","19");
//        String name = (String) redisTemplate.opsForValue().get("name");
//        System.out.println(name);
//        redisTemplate.exec();
        //先保证同一个连接
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {

                operations.multi();
                operations.opsForValue().set("name","zhangsan");
                operations.opsForValue().set("age","19");
                String name = (String) redisTemplate.opsForValue().get("name");
                System.out.println(name);
                List list = operations.exec();
                //返回结果
                System.out.println(list);
                return list;
            }
        });
}
流水线(了解)
为什么需要流水线操作?

在实际开发过程中,redis的性能瓶颈不在于自己计算能力,通常限制在于网络传输,为了减少网络传输所带来的redis性能闲置时间,因此就可以使用流水线,类似于批处理操作

注意:

流水线必须是同一个连接才能实现,所以在实际开发过程中,通常是多个人进行redis操作,这种情况下流程线是不适用的,如果一个人需要进行大量的redis操作,这个时候才需要考虑流水线

发布和订阅(了解)
发布与订阅的命令

1.subscribe chat
2.publish chat “message”

redis发布与订阅

redis提供了消息发布和订阅的功能,一方作为订阅者,订阅某个管道,另一方作为发布者,发布消息,订阅方就能及时接受到这个消息

redis的发布订阅和传统MQ的各自特点
redis特点:

轻量级,实时性,数据安全性低(消息发送出,有可能没被订阅者收到)

传统MQ特点:

专业级,能处理的消息量更大,实时性较低,数据安全性有保证

超时时间
概述:

1.给redis的key设置一个过期的时间,时间到之后该键值对就会被redis清理,节省内存
2.在实际开发过程中,因为内存的限制,并不可能保证redis和数据库的数据达到完全相同,因此只能尽可能保证redis中存储的都是热点数据(被频繁访问的数据)
3.对于这种情况,如何保证热点数据被存在redis中,通可以使用超时命令+内存管理(内存回收策略)两个方案:
在实际开发过程中,项目组通常都会要求开发者给所有的缓存数据添加超时时间

相关命令

1.persist key :
持久化key,即得永生(移除key的超时时间),所有的key默认就是永生的
2.[p(毫秒)]expire key seconds:
设置key的超时时间,单位是s,通常设置超时时间为5~10分钟
3.[p(毫秒)]ttl key :
查看key的剩余时间,单位是秒
-1表示永生
-2表示死亡

注意点:

redis在key过期后,不会立刻从内存中移除该键值对
因为如果需要从内存中移除过期的键对应的键值对,redis需要维护一个观察key是否过期的功能,这样肯定会降低redis的性能,这并不是开发者所希望看见的情况

以下三种情况下,redis会从内存中移除键值对:

1.当用户使用get命令获取一个已经超时的key时,redis会立刻从内存中清除该键值对
2.redis内部有一个定时器,默认是每秒清除一次过期的key
3.如果内存满了根据不同的淘汰策略,redis会清除某些键值对(这些key有可能没有过期,甚至是永生的)

Lua脚本语言
为什么需要Lua脚本?

具体的业务逻辑需要用java程序控制,因此redis2.6以后开始支持Lua脚本,Lua脚本端可以在redis实现复杂的业务逻辑
Lua脚本的另一个特点,可以保证整个脚本是原子性执行的,线程是安全的,能够保证并发数据一致性(基于redis的单线程模式)
类似于存储过程(但是存储过程是线程不安全的)

redis是单线程模型还是多线程模型?
什么是多线程模型?

eg:tomcat内部维护一个线程池,对于多个请求开启多条线程同时处理

什么是单线程模型?

eg:redis内部维护一个队列,对于多个客户端的多个请求进行排列后,然后单线程的依次处理每个请求

如何选择:

根据CPU的空闲时间判断
1.如果CPU的空闲时间比较多,则应该选择多线程;
2.如果CPU的空闲时间比较少,则应该选择单线程;

eg:

1.tomcat因为要处理很多复杂的业务并且与网络有关联,这些业务可能会导致线程阻塞,线程阻塞时,CPU为了不空闲,切换到其他线程处理请求
2.redis计算能力很高,所以CPU利用很高,在请求量很大的时候,几乎不会出现CPU空闲时间,这个时候如果采用多线程,redis还需要额外的线程切换消耗,得不偿失

Lua脚本语言语法

lua hello.lua

注解:

单行 –
多行–[[ ]]–

数据类型

type(“s”) = string:类型
“abc”=3:长度
“a”…“b”=“ab”:拼接字符串
####### 变量声明:
变量名=变量值: b = 10
####### 变量类型:
1.全局变量:global a = 20
2.局部变量:local b = 10

流程控制:

1.if & if-else:

lua if.lua 19 : 传参
age = tonumber(arg[1]) : 接受参数
 if age > 19 then 	
print("adult") else print("child")
 end

2.if嵌套(没有else if)

逻辑判断:

与或非:and/or/~

循环:
while(i <= 10)
	do
		print(i)
		i = i + 1
	end
	for i=1,10(终止位置),1(步长)
	do
		print(i)
	end
数组:
array={1,2,3,4,5}
for i=1,#array,1 
do
	print(array[i])
end
redis执行Lua脚本
命令格式:

eval lua-script key-num [key1 key2 key3 …] [value1 value2 value3 …]

解释:

eval:代表执行Lua语言的命令
lua-script:代表Lua语言脚本
key-num:整数,代表参数中有多少个key
[key1 key2 key3 …]:key作为参数传递给Lua语言
[value1 value2 value3 …]:作为参数传递给Lua,不是必填

eg:

eval “return ‘content’” 0
eval “redis.call(‘set’, KEYS[1], ARGV[1])” 1 hello world

spring执行Lua脚本

脚本字符串(开发中不会直接使用字符串)

public void luaScript(){
    //准备手动序列化方式,解决模板和原生连接操作相差性行为
    JdkSerializationRedisSerializer jdkSerializationRedisSerializer = 
     new JdkSerializationRedisSerializer();
    //准备脚本
    //1.直接执行脚本字符串
    String luaStr = "return redis.call('set',KEYS[1],ARGV[1])";
    //spring的redisTemplate并没有封装执行lua
    //通过redisTemplate获得连接/2.0之前是jedis/直接使用即可
    RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
    //手动序列化
    byte[] names = jdkSerializationRedisSerializer.serialize("name");
    byte[] xuhengliangs = jdkSerializationRedisSerializer.serialize("xuhengliang");
   //通过原始连接对象操作lua脚本
    byte[] result = connection.eval(luaStr.getBytes(), ReturnType.VALUE, 1,names,xuhengliangs);
    //用原始连接进行set不会序列化
    System.out.println("执行脚本的结果: " + new String(result));

    System.out.println(redisTemplate.opsForValue().get("name"));

}

缓存lua脚本

//缓存lua脚本
String luaStr = "return redis.call('set',KEYS[1],ARGV[1])";
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//开始缓存lua脚本,返回脚本的签名(id唯一标志)
String scriptLoad = connection.scriptLoad(luaStr.getBytes());
System.out.println("缓存脚本的签名:" + scriptLoad);//c686f316aaf1eb01d5a4de1b0b63cd233010e63d
//根据返回的签名,执行缓存脚本
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.evalSha("c686f316aaf1eb01d5a4de1b0b63cd233010e63d",ReturnType.VALUE,1,"money".getBytes(),"10000".getBytes());
执行lua文件(一般是lua文件和缓存一起使用)
    @Test
    public void luaScript() throws IOException {
        //获得lua脚本文件
     File file = new File(this.getClass().getResource("/static/lua/hello.lua").getPath());
        //将文件读到内存中
//        InputStream  ips = new FileInputStream(file);
//        ByteArrayOutputStream    ops = new ByteArrayOutputStream();
//
//        int len;
//        byte[] buffer = new byte[1024];
//        while((len = ips.read(buffer)) != -1){
//            ops.write(buffer,0, len);
//        }
//        //得到脚本的byte数组
//        byte[] bytes = ops.toByteArray();
//直接将文件转成字节数组,通过内存流ByteArrayOutputStream实现
        byte[] bytes = FileUtils.readFileToByteArray(file);
        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        //执行脚本  connection.eval(bytes,ReturnType.VALUE,0,"99".getBytes(),"xuhengliang".getBytes(),"lidiqiu".getBytes());
    }

redis的持久化

持久化方式
快照RDB(默认行为):

快照(snapshotting)它是备份当前瞬间redis在内存中的数据结构,当重启redis之后,从快照中直接恢复数据到内存中即可
shutdown [save|nosave] : 默认关闭redis时会拍一次快照

相关命令:

1.save:
使用当前线程拍摄快照,快照的时候会拒绝客户端的所有写命令
2.bgsave:
开启一个新的线程拍摄快照,当前线程仍然会就收客户端写命令

相关配置:

1.save seconds times :
控制快照的频率
默认:save 900 1 / save 300 10 /save 60 10000
save “” : 配置关闭快照功能
2.stop-writes-on-bgsave-error yes :
bgsave一旦出错,前台写操作会被拒绝,错误反馈作用
3.dbfilename dump.rdb :
快照文件的文件名(默认即可)
4.dir ./ :
快照文件的存放路径 ./ 相对redis.conf

只追加文件AOF:

每次redis执行写命令时,会将该命令记录到一个文件中,当redis重启时,会从文件里面依次执行写命令进,而达到恢复数据的结果

相关配置:

1.appendonly no :
是否启动append only mode,默认关闭
2.appendfilename “appendonly.aof” : 文件名

appendfsnyc : 记录写操作的频率

always : 每次写都记录
everysec : 每秒钟记录一次(默认)
no : 不主动记录,让开发者通过命令被动的记录
3.避免aof文件过大的配置:
auto-aof-rewrite-percentage 100
一旦文件到达上一个文件的100就开启一个新的文件
auto-aof-rewrite-min-size 64mb
一旦文件到64mb就开启一个新的文件
aof-load-truncated yes :
aof文件恢复,如果最后一条命令出现问题是否忽略,默认是忽略

选择持久化模式

特点:
RDB:
恢复速度快,快照数据的安全性会比AOF低
AOF:
文件体积有可能很大,而且恢复速度有可能会很慢,
但数据的安全性比RDB高,一般最多丢失一秒以内的数据
如果希望数据的绝对安全,最好两个同时开启
当redis恢复数据时,会先恢复AOF文件再恢复快照
如果redis仅仅只是作为缓存服务器,两个都可以关闭
最大提示redis的性能,数据在数据库中有
一般情况下,推荐使用RDB就可以
RDB的性能损耗没有AOF那么大

redis内存回收策略

当内存存满的时候,redis该怎么办,取决于内存回收策略的配置
maxmemory-policy
用于配置redis的内存回收策略,当内存达到最大值时采取的内存处理方式
maxmemory-policy可选值:
noeviction (默认):不淘汰任何键值对,当内存已经满时,进入只读模式
volatile-lru:从所有设置了超时时间的key中,选出最近最少被使用
allkeys-lru:从所有key中,选出最近最少被使用
volatile-random:从所有设置了超时时间的key中随机选出某些key淘汰
allkeys-random:从所有key中随机选出某些key淘汰
volatile-ttl:从设置了超时时间的key,选出剩余时间最少的key
规律:
lru:最近最少使用
random:随机
ttl:剩余时间最少
volatile:从设置了超时时间的key中选择
allkeys:从所有的key中选择

注意:

LRU和TTl算法在redis中都不是精准计算,而是一个近似算法,抽样选择
redis默认有一个探测数量的配置maxmemory-samples默认为3,也就是抽样选择3个样本
随机抽取样本,在样本中选择符合淘汰策略key淘汰

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
黑马Redis高级篇是一个关于Redis高级应用的教程。在这个教程中,涉及到了创建文件和目录的操作,以及配置Redis实例的过程。在创建文件和目录的步骤中,可以使用以下命令: 1. 创建文件和目录: - 创建redis目录:cd /home && mkdir redis - 在redis目录下创建myredis1、myredis2和myredis3目录:mkdir /home/redis/myredis1, mkdir /home/redis/myredis2, mkdir /home/redis/myredis3 - 在myredis1、myredis2和myredis3目录分别创建myredis.conf配置文件和data目录:touch /home/redis/myredis1/myredis.conf, mkdir /home/redis/myredis1/data, touch /home/redis/myredis2/myredis.conf, mkdir /home/redis/myredis2/data, touch /home/redis/myredis3/myredis.conf, mkdir /home/redis/myredis3/data - 创建mysentinel1、mysentinel2和mysentinel3目录:mkdir /home/redis/mysentinel1, mkdir /home/redis/mysentinel2, mkdir /home/redis/mysentinel3 2. Redis实例信息如下: - IPPORT角色 - 10.13.164.55:7001 master - 10.13.164.55:7002 master - 10.13.164.55:7003 master - 10.13.164.55:7004 slave - 10.13.164.55:7005 slave - 10.13.164.55:7006 slave 根据这些信息,你可以按照上述步骤创建文件和目录,并配置Redis实例。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [黑马Redis6高级篇](https://blog.csdn.net/D_boj/article/details/131712945)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流放Oo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值