【Redis系列笔记】分布式锁

本文涉及的产品
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 MongoDB,通用型 2核4GB
简介: 分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行,这就是分布式锁的核心思路

1. 概述

分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。

分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行,这就是分布式锁的核心思路

1.1. 前提条件

需要满足一些条件:

可见性:多个线程都能看到相同的结果(此处可见性区别于并发编程的内存可见性)

互斥:互斥是分布式锁的最基本的条件,使得程序串行执行

高可用:程序不易崩溃,时时刻刻都保证较高的可用性

高性能:由于加锁本身就让性能降低,所有对于分布式锁本身需要他就较高的加锁性能和释放锁性能

安全性:安全也是程序中必不可少的一环

1.2. 分类

常见的分布式锁有三种

Mysql:mysql本身就带有锁机制,但是由于mysql性能本身一般,所以采用分布式锁的情况下,其实使用mysql作为分布式锁比较少见

Redis:redis作为分布式锁是非常常见的一种使用方式,现在企业级开发中基本都使用redis或者zookeeper作为分布式锁,利用setnx这个方法,如果插入key成功,则表示获得到了锁,如果有人插入成功,其他人插入失败则表示无法获得到锁,利用这套逻辑来实现分布式锁

Zookeeper:zookeeper也是企业级开发中较好的一个实现分布式锁的方案,由于本套视频并不讲解zookeeper的原理和分布式锁的实现,所以不过多阐述

技术

MySQL

Redis

Zookeeper

互斥

利用mysql本身的互斥锁机制

利用setnx这样的互斥命令

利用节点的唯一性和有序性实现互斥

高可用

高性能

一般

一般

安全性

断开连接,自动释放锁

利用锁超时时间,到期释放

临时节点,断开连接自动释放

1.3. 核心思路

实现分布式锁时需要实现的两个基本方法:获取锁以及释放锁

//获取锁:
//互斥:确保只能有一个线程获取锁O
//添加锁,利用setnx的互斥特性
SETNX lock thread1
//非阻塞:尝试一次,成功返回true,失败返回false
//添加锁,NX是互斥、EX是设置超时时间
SET lock threadl NX EX 1
//释放锁:
//手动释放
//释放锁,删除即可
DEL key
//超时释放:获取锁时添加一个超时时间
//添加锁过期时间,避免服务宕机引起的死锁
EXPIRE lock 10

我们利用redis 的setNx 方法,当有多个线程进入时,我们就利用该方法,第一个线程进入时,redis 中就有这个key 了,返回了1,如果结果是1,则表示他抢到了锁,那么他去执行业务,然后再删除锁,退出锁逻辑,没有抢到锁的哥们,等待一定时间后重试即可

2. Redis分布式锁

资料链接:https://pan.baidu.com/s/1BnQ08PlhJ0WLoBcMMHp75A

提取码:gv96

2.1. Redis实现分布式锁的初级版本

在order-service服务中导入redis的依赖坐标

<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

在配置文件中添加redis的配置

spring:
redis:
host: 192.168.200.128
port: 6379

在order-service服务中创建一个setNx锁

package cn.itcast.order.lock;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
/**
 * 创建setnx锁
 */
public class RedisSetNxLock {
    //定义锁的名称
    private final String name;
    private final StringRedisTemplate stringRedisTemplate;
    public RedisSetNxLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }
    //定义当前key的前缀
    private static  final String KEY_PREFIX="lock:";
    /**
     * 获取锁
     *
     * @param timeoutsec key的过期时间
     * @return true表示拿到锁,false表示没有拿到锁
     */
    public boolean tryLock(Long timeoutsec) {
        //2.获取当前线程的id作为value值,保证唯一性
        long threadId = Thread.currentThread().getId();
        /**
         * 1.获取锁
         * setIfAbsent(K key, V value, long timeout, TimeUnit unit)
         *
         key –参数1表示redis中的key
         value – 参数2表示redis中存储的值
         timeout – 参数3表示key的过期时间
         unit – 参数4表示时间单位
         */
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId + "", timeoutsec, TimeUnit.MINUTES);
        /**
         * 这个是为了防止类型的拆箱,如果返回值为null的话,boolean类型会报错
         * 意思:如果相等于则返回true,不想等于返回false,如果flag=null的话,也是返回false;
         */
        return Boolean.TRUE.equals(flag);
    }
    /**
     * 释放锁
     *
     */
    public void unLock() {
        //通过手动释放锁
        stringRedisTemplate.delete(KEY_PREFIX+name);
    }
}

改造orderService接口中创建订单的业务代码

package cn.itcast.order.service.impl;
import cn.itcast.feign.client.AccountFeignClient;
import cn.itcast.feign.client.StorageFeginClient;
import cn.itcast.order.entity.Order;
import cn.itcast.order.lock.RedisSetNxLock;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.service.OrderService;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
 *订单业务相关
 */
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Resource
    private AccountFeignClient accountClient;
    @Resource
    private StorageFeginClient storageClient;
    @Resource
    private  OrderMapper orderMapper;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 下单业务
     * @param order
     * @return
     */
    @Override
    @Transactional
    public Long create(Order order) {
        //获取锁
        RedisSetNxLock redisSetNxLock=new RedisSetNxLock("order:"+order.getUserId(),stringRedisTemplate);
        boolean flag = redisSetNxLock.tryLock( 1200L);
        if(!flag){
            log.error("不允许重复下单");
            return 0L;
        }
        // 创建订单
        orderMapper.insert(order);
        try {
            // 扣用户余额
            accountClient.deduct(order.getUserId(), order.getMoney());
            // 扣库存
            storageClient.deduct(order.getCommodityCode(), order.getCount());
            return order.getId();
        } catch (FeignException e) {
            log.error("下单失败,原因:{}", e.contentUTF8(), e);
            throw new RuntimeException(e.contentUTF8(), e);
        }finally {
            //释放锁
            redisSetNxLock.unLock();
        }
    }
}

测试效果

  • 我们起两个order-service服务,并且端口号分别为8881和8801
  • 然后在创建订单的业务方法中的第一行打上断点
  • 启动方式都为debug方式
  • 在postman中设置两个请求

form表单的参数

userId=user202103032042012
commodityCode=100202003032041
count=2
money=200

线程1:

线程2:

测试结果:debug发现,两个线程只有一个线程能够下单成功,另外一个获取锁失败,则下单失败,并在控制台打印不可重复下单异常信息。

2.2. 基于Redis实现分布式锁误删情况

2.2.1. 误删逻辑说明:

持有锁的线程在锁的内部出现了阻塞,导致他的锁自动释放,这时其他线程,线程2来尝试获得锁,就拿到了这把锁,然后线程2在持有锁执行过程中,线程1反应过来,继续执行,而线程1执行过程中,走到了删除锁逻辑,此时就会把本应该属于线程2的锁进行删除,这就是误删别人锁的情况说明

解决方案:解决方案就是在每个线程释放锁的时候,去判断一下当前这把锁是否属于自己,如果属于自己,则不进行锁的删除,假设还是上边的情况,线程1卡顿,锁自动释放,线程2进入到锁的内部执行逻辑,此时线程1反应过来,然后删除锁,但是线程1,一看当前这把锁不是属于自己,于是不进行删除锁逻辑,当线程2走到删除锁逻辑时,如果没有卡过自动释放锁的时间点,则判断当前这把锁是属于自己的,于是删除这把锁。

2.2.2. 具体实现:

需求:修改之前的分布式锁实现,满足:在获取锁时存入线程标示(可以用UUID表示)在释放锁时先获取锁中的线程标示,判断是否与当前线程标示一致

  • 如果一致则释放锁
  • 如果不一致则不释放锁

核心逻辑:在存入锁时,放入自己线程的标识,在删除锁时,判断当前这把锁的标识是不是自己存入的,如果是,则进行删除,如果不是,则不进行删除。

本次,只需要更改RedisSetNxLock类中的获取锁和释放锁中的代码逻辑,无须更改下单业务逻辑

修改后代码

//定义一个uuid作为前缀
private static  final String ID_PREFIX=UUID.randomUUID().toString().replace("-","");
/**
     * 获取锁
     * @param timeoutsec key的过期时间
     * @return true表示拿到锁,false表示没有拿到锁
     */
public boolean tryLock(Long timeoutsec) {
//2.获取当前线程的id作为value值,保证唯一性
String threadId = ID_PREFIX+"-"+Thread.currentThread().getId();
/**
         * 1.获取锁
         * setIfAbsent(K key, V value, long timeout, TimeUnit unit)
         *
         key –参数1表示redis中的key
         value – 参数2表示redis中存储的值
         timeout – 参数3表示key的过期时间
         unit – 参数4表示时间单位
         */
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId , timeoutsec, TimeUnit.MINUTES);
/**
         * 这个是为了防止类型的拆箱,如果返回值为null的话,boolean类型会报错
         * 意思:如果相等于则返回true,不想等于返回false,如果flag=null的话,也是返回false;
         */
return Boolean.TRUE.equals(flag);
}
/**
     * 释放锁
     *
     */
public void unLock() {
    //获取线程id
    String threadId = ID_PREFIX+Thread.currentThread().getId();
    //获取redis中的value=线程id
    String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
    //如果两个线程id相等的话,则释放锁
    if(threadId.equals(id)){
        //通过手动释放锁
        stringRedisTemplate.delete(KEY_PREFIX+name);
    }
}

测试结果

  • debug重新启动order-service两个服务,8881和8801
  • 然后在下单业务create方法中的获取锁和释放锁那分别打断点
  • 在unlock释放锁的代码中的第一行打个断点
  • 在postman中启动发起两个服务的请求
  • 第一个线程持有锁后,手动释放锁,第二个线程 此时进入到锁内部,再放行第一个线程,此时第一个线程由于锁的value值并非是自己,所以不能释放锁,也就无法删除别人的锁,此时第二个线程能够正确释放锁,通过这个案例初步说明我们解决了锁误删的问题。

2.3. 基于Redis实现分布式锁的原子性情况

2.3.1. 更为极端的误删逻辑说明:

线程1现在持有锁之后,在执行业务逻辑过程中,他正准备删除锁,而且已经走到了条件判断的过程中,比如他已经拿到了当前这把锁确实是属于他自己的,正准备删除锁,但是此时他的锁到期了,那么此时线程2进来,但是线程1他会接着往后执行,当他卡顿结束后,他直接就会执行删除锁那行代码,相当于条件判断并没有起到作用,这就是删锁时的原子性问题,之所以有这个问题,是因为线程1的拿锁,比锁,删锁,实际上并不是原子性的,我们要防止刚才的情况发生。

2.3.2. 使用Lua语言解决原子性问题


了解Lua语言

Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言,它的基本语法大家可以参考网站:https://www.runoob.com/lua/lua-tutorial.html,这里重点介绍Redis提供的调用函数,我们可以使用lua去操作redis,又能保证他的原子性,这样就可以实现拿锁比锁删锁是一个原子性动作了,作为Java程序员这一块并不作一个简单要求,并不需要大家过于精通,只需要知道他有什么作用即可。

这里重点介绍Redis提供的调用函数,语法如下:

redis.call('命令名称', 'key', '其它参数', ...)

例如,我们要执行set name jack,则脚本是这样:

# 执行 set name jack
redis.call('set', 'name', 'jack')

例如,我们要先执行set name Rose,再执行get name,则脚本如下:

# 先执行 set name jack
redis.call('set', 'name', 'Rose')
# 再执行 get name
local name = redis.call('get', 'name')
# 返回
return name

写好脚本以后,需要用Redis命令来调用脚本,调用脚本的常见命令如下:

例如,我们要执行 redis.call('set', 'name', 'jack') 这个脚本,语法如下:

# 调用脚本
EVAL "return redis.call('set' ,"name", "jack')" 0
后面的0为脚本需要的key类型的参数个数

如果脚本中的key、value不想写死,可以作为参数传递。key类型参数会放入KEYS数组,其它参数会放入ARGV数组,在脚本中可以从KEYS和ARGV数组获取这些参数:

#调用脚本
EVAL "return redis.call('set',KEYS[1],ARGV[1])" 1 name Rose


代码实现原子性

1、在idea中可以下载lua的插件Luanalysis

2、在order-service服务中的resource下创建一个unlock.lua脚本

编写以下内容:

-- 这里的 KEYS[1] 就是锁的key,这里的ARGV[1] 就是当前线程标示
-- 获取锁中的标示,判断是否与当前线程标示一致
if (redis.call('GET', KEYS[1]) == ARGV[1]) then
    -- 一致,则删除锁
    return redis.call('DEL', KEYS[1])
end
-- 不一致,则直接返回
return 0

3、更改释放锁的代码

//定义静态化常量类
private static final DefaultRedisScript<Long> redisScript;
//执行静态快代码
static {
    redisScript=new DefaultRedisScript<>();
    redisScript.setLocation(new ClassPathResource("unlock.lua"));
    redisScript.setResultType(Long.class);
}
/**
     * 释放锁-利用lua脚本实现原子性
     *
     */
public void unLock() {
    //调用lua脚本
    stringRedisTemplate.execute(redisScript,
                                Collections.singletonList(KEY_PREFIX+name),
                                ID_PREFIX+Thread.currentThread().getId());
}

4、解析execute方法

我们的RedisTemplate中,可以利用execute方法去执行lua脚本,关系如下:


测试效果

  • debug重新启动order-service两个服务,8881和8801
  • 然后在下单业务create方法中的获取锁和释放锁那分别打断点
  • 在unlock释放锁的代码中的第一行打个断点
  • 在postman中启动发起两个服务的请求

第一个线程进来,得到了锁,手动删除锁,模拟锁超时了,其他线程会执行lua来抢锁,当第一个线程利用lua删除锁时,lua能保证他不能删除他的锁,第二个线程删除锁时,利用lua同样可以保证不会删除别人的锁,同时还能保证原子性。

3. Redisson分布式锁

3.1. SetNx实现分布式锁存在以下问题

基于setnx实现的分布式锁存在下面的问题:

重入问题:重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,他的方法都是使用synchronized修饰的,假如他在一个方法内,调用另一个方法,那么此时如果是不可重入的,不就死锁了吗?所以可重入锁他的主要意义是防止死锁,我们的synchronized和Lock锁都是可重入的。

不可重试:是指目前的分布式只能尝试一次,我们认为合理的情况是:当线程在获得锁失败后,他应该能再次尝试获得锁。

超时释放:我们在加锁时增加了过期时间,这样的我们可以防止死锁,但是如果卡顿的时间超长,虽然我们采用了lua表达式防止删锁的时候,误删别人的锁,但是毕竟没有锁住,有安全隐患

主从一致性: 如果Redis提供了主从集群,当我们向集群写数据时,主机需要异步的将数据同步给从机,而万一在同步过去之前,主机宕机了,就会出现死锁问题。

3.2. Redisson介绍

Redisson底层采用的是Netty 框架。支持Redis 2.8以上版本,支持Java1.6+以上版本。Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) 。

使用Redisson可以非常方便将Java本地内存中的常用数据结构的对象搬到分布式缓存redis中。

也可以将常用的并发编程工具如:AtomicLongCountDownLatchSemaphore等支持分布式。

使用RScheduledExecutorService 实现分布式调度服务。

支持数据分片,将数据分片存储到不同的redis实例中。

支持分布式锁,基于Java的Lock接口实现分布式锁,方便开发。

简单来说:

一个基于Redis实现的分布式工具,有基本分布式对象和高级又抽象的分布式服务,为每个试图再造分布式轮子的程序员带来了大部分分布式问题的解决办法。

提供了使用Redis的最简单和最便捷的方法。促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

3.3. Redisson和Jedis、Lettuce区别

  • Jedis:Redis 官方推出的用于通过 Java 连接 Redis 客户端的一个工具包,它提供了全面的类似于 Redis 原生命令的支持,是目前使用最广的一款 java 客户端。
  • Lettuce:一个可扩展的线程安全的 Redis 客户端,通讯框架基于 Netty 开发,支持高级的 Redis 特性,比如哨兵,集群,管道,自动重新连接等特性。从 Spring Boot 2.x 开始, Lettuce 已取代 Jedis 成为首选 Redis 的客户端。
  • Redisson:一款架设在 Redis 基础上,通讯基于 Netty 的综合的、新型的中间件,是企业级开发中使用 Redis 的最佳范本。

总结下来,Jedis 把 Redis 命令封装的非常全面,Lettuce 则进一步丰富了 Api,支持 Redis 各种高级特性。但是两者并没有进一步深化,只给了你操作 Redis 数据库的工具,而 Redisson 则是基于 Redis、Lua 和 Netty 建立起了一套的分布式解决方案,比如分布式锁的实现,分布式对象的操作等等。

在实际使用过程中,Lettuce + Redisson组合使用的比较多,两者相铺相成。

3.4. 具体实现

导入依赖

<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.13.6</version>
</dependency>

配置文件

@Configuration
public class RedisConfig {
    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.200.196:6379");
        return Redisson.create(config);
    }
}

在下单业务中添加分布式锁

@Resource
private RedissonClient redissonClient;
/**
     * 下单业务
     * @param order
     * @return
     */
@Override
@Transactional
public Long create(Order order) {
//redisson
RLock lock = redissonClient.getLock("lock:" + order.getUserId());
boolean flag = false;
try {
    flag = lock.tryLock(10, 60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
    e.printStackTrace();
}
if(!flag){//获取锁失败
    log.error("获取锁失败,不允许执行业务");
    return 0L;
}
// 创建订单
orderMapper.insert(order);
try {
    // 扣用户余额
    accountClient.deduct(order.getUserId(), order.getMoney());
    // 扣库存
    storageClient.deduct(order.getCommodityCode(), order.getCount());
} catch (FeignException e) {
    log.error("下单失败,原因:{}", e.contentUTF8(), e);
    throw new RuntimeException(e.contentUTF8(), e);
}finally {
    //释放锁
    lock.unlock();
}
return order.getId();
}


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
5天前
|
NoSQL 算法 Java
探讨redis分布式锁
探讨redis分布式锁
11 1
|
12天前
|
缓存 NoSQL 安全
玩转Redis!非常强大的Redisson分布式集合,少写60%代码
Redisson是Java的Redis客户端,提供实时数据平台服务,简化了分布式环境下的数据管理。它包含RList、RSet、RMap等分布式集合,支持ConcurrentMap和Set接口,确保线程安全和数据一致性。例如,RMap实现了本地缓存和监听器功能,允许数据监听和本地加速读取。此外,还提供了RSet的排序和去重功能,以及RQueue和RBlockingQueue等队列实现,支持阻塞操作。通过Redisson,开发者能轻松处理分布式系统的数据同步和操作。
|
14天前
|
存储 缓存 NoSQL
了解Redis,第一弹,什么是RedisRedis主要适用于分布式系统,用来用缓存,存储数据,在内存中存储那么为什么说是分布式呢?什么叫分布式什么是单机架构微服务架构微服务的本质
了解Redis,第一弹,什么是RedisRedis主要适用于分布式系统,用来用缓存,存储数据,在内存中存储那么为什么说是分布式呢?什么叫分布式什么是单机架构微服务架构微服务的本质
|
16天前
|
缓存 NoSQL 关系型数据库
【Redis】 浅谈分布式架构
【Redis】 浅谈分布式架构
|
21天前
|
存储 消息中间件 缓存
Redis - 笔记
Redis是开源的内存数据结构存储系统,兼备数据库、缓存和消息中间件功能。它支持字符串、哈希、列表、集合、有序集合等数据结构,以及地理空间、基数统计和位图等特殊类型。Redis具备复制、LUA脚本、LRU事件、事务、持久化、哨兵和集群等高级特性,以实现高可用性。应用场景包括计数器、存储用户信息、消息队列、共同关注等。字符串最大容量为512M。
|
22天前
|
存储 NoSQL 算法
Redis (分布式锁)
Redis (分布式锁)
199 0
|
1月前
|
监控 NoSQL 算法
探秘Redis分布式锁:实战与注意事项
本文介绍了Redis分区容错中的分布式锁概念,包括利用Watch实现乐观锁和使用setnx防止库存超卖。乐观锁通过Watch命令监控键值变化,在事务中执行修改,若键值被改变则事务失败。Java代码示例展示了具体实现。setnx命令用于库存操作,确保无超卖,通过设置锁并检查库存来更新。文章还讨论了分布式锁存在的问题,如客户端阻塞、时钟漂移和单点故障,并提出了RedLock算法来提高可靠性。Redisson作为生产环境的分布式锁实现,提供了可重入锁、读写锁等高级功能。最后,文章对比了Redis、Zookeeper和etcd的分布式锁特性。
232 16
探秘Redis分布式锁:实战与注意事项
|
1月前
|
存储 NoSQL Redis
【Redis系列笔记】Redis总结
Redis是一个基于内存的 key-value 结构数据库。 Redis 是互联网技术领域使用最为广泛的存储中间件。 Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。 它存储的value类型比较丰富,也被称为结构化的NoSql数据库。
101 0
|
1月前
|
缓存 NoSQL Java
【Redis系列笔记】Redis入门
本文介绍了Redis常用命令,以及SpringBoot集成Spring Data Redis和Spring Cache。Spring Data Redis 提供了对 Redis 的操作方法,而 Spring Cache 则提供了基于注解的缓存功能,可以方便地将方法的返回值缓存到 Redis 中,以提高性能和减少对数据源的访问次数。这样的集成可以帮助开发者更便捷地利用 Redis 来管理应用程序的数据和缓存。
129 4
|
1月前
|
NoSQL Java 大数据
介绍redis分布式锁
分布式锁是解决多进程在分布式环境中争夺资源的问题,与本地锁相似但适用于不同进程。以Redis为例,通过`setIfAbsent`实现占锁,加锁同时设置过期时间避免死锁。然而,获取锁与设置过期时间非原子性可能导致并发问题,解决方案是使用`setIfAbsent`的超时参数。此外,释放锁前需验证归属,防止误删他人锁,可借助Lua脚本确保原子性。实际应用中还有锁续期、重试机制等复杂问题,现成解决方案如RedisLockRegistry和Redisson。

相关产品

  • 云数据库 Redis 版