Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >redis妙用-string类型

redis妙用-string类型

原创
作者头像
并发笔记
修改于 2020-10-22 02:12:50
修改于 2020-10-22 02:12:50
1.6K0
举报
文章被收录于专栏:并发笔记并发笔记

string类型,是我们最常用的。以及一些特性,我们都比较熟悉,这一节一起回顾一下string的应用场景,以及对这些场景延申的一些思考

  • 缓存 如何设计缓存存储,使用spring cache另当别论
  • 分布式锁 锁续期
  • 计数器
  • 分布式全局序列 减少IO交互提高效率
api

针对字符串的操作

命令

说明

SET key value

存储字符串键

MSET key valuekey value ...

批量存储字符串键

SETNX key value

存入一个存在在的字符串,若存在存储不成功

GET key

获取一个键的值

MGET keykey ...

批量获取键的值

针对数字的操作

命令

说明

INCRBY key increment

对数字key进行{increment}的增加

DECRBY key decrement

对数字key进行{decrement}的减少

INCR key

对数字key自增1

DECR key

对数字key自减1

统一的操作

命令

说明

DEL keykey ...

删除一个键

EXPIRE key seconds

设置key的过期时间(秒)

PEXPIRE key milliseconds

设置key的过期时间(毫秒)

应用场景
缓存

    string数据类型,我们常用来做为缓存,一般都是使用spring cache这样的框架来管理缓存。那么考虑一下,在没有使用任何框架情况下,我们使用redis作为缓存,redis中key怎么设计呢?如下表。

redis_user_data.png
redis_user_data.png

    我们第一个会想到使用json、xml来将user的数据序列化之后保存到redis中,但是这样的话,不便于我们做修改操作,对不对。如果我们要修改的话,需要查询出来进行反序列化,才能做修改。

    但是如果进行 这一串操作的话,又会产生第二个问题,就是并发情况下,其他线程会读到修改之前的数据。这里多说一句,我们在实际生产中解决这一问题的方案就是对数据做修改时,直接删除缓存,然后别的线程查询时,再写入缓存。

    那么有没有其他的设计方式来解决这一问题呢?我们把解决思路放在缓存的key上,在设计缓存key时,制定一个约定熟成的规定来存储,比如

代码语言:txt
AI代码解释
复制
-- 约定key生成规则为
user::{id}::name、user::{id}::age
-- 写入缓存
MSET user::1::name ally user::1::age 18
-- 读取缓存
MGET user:1::name user::1::age
分布式锁

    分布式锁,通常会用到SETNX、EXPIRE,SETNX用来获取锁,而EXPIRE设置锁的失效时间,防止死锁。如下

代码语言:txt
AI代码解释
复制
SETNX("couponcode::123456", 1)     //return 1,成功获得锁
SETNX("couponcode::123456", 1)     //return 0,有人持有该锁,获取失败

PEXPIRE("couponcode", 1000)      //设置key的失效时间,防止系统宕机,导致死锁

    那么这个时候其实又会有另一个问题,SETNX和PEXPIRE是分两步执行,那么可能出现SETNX成功了,在执行PEXPIRE时失败了,就会导致死锁。那么redis给我们还提供了一个原子操作。

代码语言:txt
AI代码解释
复制
SET couponcode 1 EX 10 NX    //EX 表示失效时间,NX表示不存在则增加

    以上命令看似完美的解决了分布式锁的问题,既保证了原子性,又解决了死锁的问题。但是我们还忽略了一点,假如我们设置锁的超时时间为10秒,但是我们应用系统处理需要20秒,那么在多余的10秒内将会有其他的线程获得该锁,那么怎么解决呢?

    其实对于处理redis的续期,业界比较正确的姿势是采用redisson这个客户端工具,具体可见同性恋交友网站github。

redisson文档
redisson文档

    redisson官方文档中,有明确的提到看门狗每30秒钟会帮我们检查锁的时间,并帮助我们续期。那么这时就问题来了,那么假如锁的时间为10秒,而看门狗的检查时间为30秒,那么不就可以有多个线程同时持有锁了嘛,虽然可以通过Config.lockWatchdogTimeout来指定,但是抱着对技术敬畏的心态,我们一起来看看看门狗是怎么实现的。那么我们写一个demo,跟这源码看看

代码语言:txt
AI代码解释
复制
public class RedissonLock {
    public static void main(String[] args) {
        RedissonClient redissonClient = Redisson.create();
        RLock lock = redissonClient.getLock("lock");

        lock.lock();  // 推测,要确认续期的时间,是在加锁时触发看门狗的。所以我们从此进入到RedissonLock.lock();
    }
}

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
        Long ttl = tryAcquire(leaseTime, unit, threadId);  // 看名字就知道是我们要找的,尝试获得锁。
        // lock acquired
        if (ttl == null) {
            return;
        }
// ....
}

private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
        return get(tryAcquireAsync(leaseTime, unit, threadId));
}

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
        if (leaseTime != -1) {
            return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        }
		//异步获得锁,返回Future对象,这里不明白的,可以补充一下多线程的知识
        RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
		
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            if (e != null) {
                return;
            }

            // lock acquired
            if (ttlRemaining == null) {
				// 开启调度重新续期
                scheduleExpirationRenewal(threadId);
            }
        });
        return ttlRemainingFuture;
}

// 重新续期
private void renewExpiration() {
        ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (ee == null) {
            return;
        }
        
        Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
                if (ent == null) {
                    return;
                }
                Long threadId = ent.getFirstThreadId();
                if (threadId == null) {
                    return;
                }
                
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                future.onComplete((res, e) -> {
                    if (e != null) {
                        log.error("Can't update lock " + getName() + " expiration", e);
                        return;
                    }
                    
                    if (res) {
                        // reschedule itself
                        renewExpiration();
                    }
                });
            }
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        
        ee.setTimeout(task);
}

    至此,一切明白了,redisson在加锁成功后,开启一个定时任务,也是所谓的看门狗。定时任务每次执行会调用renewExpirationAsync(threadId)检查锁是否释放,没有释放则对锁进行续期 renewExpiration()。而定时任务每次调度时间差为internalLockLeaseTime/3,默认锁时间为30秒,那就是10秒。

    那么也就是说不会出现我们担心的问题,当我们锁的时间为10秒时,看门狗会在该锁还剩7秒的时候对锁进行续期。

计数器

    以下场景,我们要对每一个文章的阅读量进行统计

incr
incr

    如果使用数据库要统计的话,我们将面对两个问题,并发修改和数据库压力。处理并发我们可以用cas,那么面对数据库压力我们毫无办法。这是我们可以使用redis提供的incr命令进行统计

incr
incr
分布式全局序列

    在你的业务系统到达一定的体量,特别是进行了分库分表后,分布式唯一键就显得尤为重要,原先的数据库自增id一定是用不了了。常规的解决办法我们多多少少有了解过,雪花算法,UUID。当然这里主要介绍redis生成全局唯一键,使用incr命令生成。

    那么使用redis的incr就可以实现了啊,为什么还要单独拿出来说呢?其实使用incr命令会存在一个问题,那就是IO交互次数过多,想一想在分布式情况,尽管处于同一内网,还是会存在网络问题,过的IO交互就会影响效率,那么有没有解决办法呢?

    redis官方其实有考虑到这一点,解决IO交互次数过多的办法就是,一次性获取多个唯一键,那就是incrby,他可以一其增加多个值

代码语言:txt
AI代码解释
复制
incrby read::1001 5

    那么这种方案,虽然解决了IO交互次数,那么假如系统宕机了,我们就会丢失已经获取的那一段id值,所以在你的需求要求id连续的情况下,不建议采用这种方式。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Redis 应用与原理(三)
因此,在 Redis 3.0 之后,提供了 Cluster 的解决方案,核心原理是对数据做分片:
浪漫主义狗
2024/03/21
2000
Redis 应用与原理(三)
redisson分布式锁实现原理
Redisson是一个使用Java编写的开源库,它提供了对Redis数据库的访问和操作的封装,并在此基础上提供了各种分布式功能,包括分布式锁。
叔牙
2023/09/07
1.1K0
redisson分布式锁实现原理
Springboot基于Redisson实现Redis分布式可重入锁【案例到源码分析】
我们在实现使用Redis实现分布式锁,最开始一般使用SET resource-name anystring NX EX max-lock-time进行加锁,使用Lua脚本保证原子性进行实现释放锁。这样手动实现比较麻烦,对此Redis官网也明确说Java版使用Redisson来实现。小编也是看了官网慢慢的摸索清楚,特写此记录一下。==从官网到整合Springboot到源码解读==,以==单节点为例==,==小编的理解都在注释里==,希望可以帮助到大家!!
掉发的小王
2022/07/11
7810
Springboot基于Redisson实现Redis分布式可重入锁【案例到源码分析】
Redisson 高性能 Redis 分布式锁源码分析
加锁的核心方法是:org.redisson.RedissonLock#tryLockInnerAsync
没有故事的陈师傅
2022/09/15
9460
Redisson 高性能 Redis 分布式锁源码分析
Redisson重入锁是通过setnx命令实现的?别再云了
问过很多面试者,redisson的可重复锁是怎么实现的,很多面试者都会不假思索的回答是通过redis的setnx命令来实现的,那么真的是这样吗?今天我们就一起来看下redisson分布式可重入锁到底是怎么实现的。
Java进阶之路
2022/08/03
1.2K0
Redisson重入锁是通过setnx命令实现的?别再云了
redission 锁机制
前段时间,有小伙伴问我,redission锁的原理,看门狗的作用,和一些实际开发中的场景,当时并没有给他比较完整的解答,后来我查了资料对redission做了一个总结,在这里分享给小伙伴们
六个核弹
2023/04/26
5790
Redisson杂谈
Redisson 是一个基于 Netty 通信框架的高性能 Redis 客户端, 实现了分布式和可扩展的 Java 数据结构,提供很多分布式相关操作服务以及大量便利的工具方法,让开发者可以把精力放在开发业务,避免重复造轮子。
政采云前端团队
2023/10/18
3410
Redisson杂谈
冷饭新炒:理解Redisson中分布式锁的实现
在很早很早之前,写过一篇文章介绍过Redis中的red lock的实现,但是在生产环境中,笔者所负责的项目使用的分布式锁组件一直是Redisson。Redisson是具备多种内存数据网格特性的基于Java编写的Redis客户端框架(Redis Java Client with features of In-Memory Data Grid),基于Redis的基本数据类型扩展出很多种实现的高级数据结构,具体见其官方的简介图:
Throwable
2021/01/18
9870
Redis分布式锁存在的问题
假设有这样一个场景,在一个购票软件上买一张票,但是此时剩余票数只有一张或几张,这个时候有几十个人都在同时使用这个软件购票。在不考虑任何影响下,正常的逻辑是首先判断当前是否还有剩余的票,如果有,那么就进行购买并扣减库存数,否则就会提示票数不足,购买失败。伪代码如下:
闻说社
2022/12/28
4100
Redis分布式锁存在的问题
Redis 分布式锁的正确实现原理演化历程与 Redisson 实战总结
Redis 分布式锁使用 SET 指令就可以实现了么?在分布式领域 CAP 理论一直存在。
码哥字节
2021/12/02
9590
Redis 分布式锁的正确实现原理演化历程与 Redisson 实战总结
Redisson分布式锁的源码解读
之前秒杀项目中就用到了这个 Redisson 分布式锁 👇,这篇就一起来看看源码吧!
Java4ye
2024/07/02
1670
【📕分布式锁通关指南 07】源码剖析redisson利用看门狗机制异步维持客户端锁
看门狗机制是redisson解决锁续期问题而设置的,在前文中我们也有手写过,这里我们看看“正版”的是如何执行的。
别惹CC
2025/03/13
1430
【📕分布式锁通关指南 07】源码剖析redisson利用看门狗机制异步维持客户端锁
源码分析:Redisson 分布式锁过程分析
文章首发:https://mp.weixin.qq.com/s/MdmGqPUyaPckUgvUufXVUQ
程序员架构进阶
2021/03/31
4620
分布式锁用Redis还是Zookeeper?
系统 A 是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用户下单。
Spark学习技巧
2022/04/18
2570
分布式锁用Redis还是Zookeeper?
Redis的分布式锁详解
分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。
全栈程序员站长
2021/04/13
3.2K0
Redis的分布式锁详解
Redis高并发分布式锁详解
  1.为了解决Java共享内存模型带来的线程安全问题,我们可以通过加锁来保证资源访问的单一,如JVM内置锁synchronized,类级别的锁ReentrantLock。
忧愁的chafry
2022/10/30
1.1K0
Redis高并发分布式锁详解
一文详解分布式锁的看门狗机制
我们今天来看看这个 Redis 的看门狗机制,毕竟现在还是有很多是会使用 Redis 来实现分布式锁的,我们现在看看这个 Redis 是怎么实现分布式锁的,然后我们再来分析这个 Redis 的看门狗机制,如果没有这个机制,很多使用 Redis 来做分布式锁的小伙伴们,经常给导致死锁。
Java极客技术
2023/11/29
1.4K0
一文详解分布式锁的看门狗机制
Redis进阶学习03---Redis完成秒杀和Redis分布式锁的应用
数据库自增指的是单独使用数据库中某一张表来专门存放主键,当我们需要的时候,只需要提前从该表中读取出一批主键集合,缓存在内存中即可,但是该方法显然太慢了,因此不推荐使用
大忽悠爱学习
2022/05/09
7840
Redis进阶学习03---Redis完成秒杀和Redis分布式锁的应用
可靠的分布式锁 RedLock 与 redisson 的实现
但就“高可用”来说,似乎仍然有所欠缺,那就是如果他所依赖的 redis 是单点的,如果发生故障,则整个业务的分布式锁都将无法使用,即便是我们将单点的 redis 升级为 redis 主从模式或集群,对于固定的 key 来说,master 节点仍然是独立存在的,由于存在着主从同步的时间间隔,如果在这期间 master 节点发生故障,slaver 节点被选举为 master 节点,那么,master 节点上存储的分布式锁信息可能就会丢失,从而造成竞争条件。
用户3147702
2022/06/27
5.4K0
可靠的分布式锁 RedLock 与 redisson 的实现
带你研究Redis分布式锁,源码走起
前阵子我们讲了分布式锁的实现方式之一:zookeeper,那么这次我们来讲讲同样流行,甚至更胜一筹的Redis。
公众号 IT老哥
2020/11/26
4770
相关推荐
Redis 应用与原理(三)
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文