Redis 中的原子操作(3)-使用Redis实现分布式锁

并发同时访问_实践篇(09)Redis如何应对并发访问? 我们在使用 Redis 时,不可避免地会遇到并发访问的问题,比如说如果多个用户同时下单,就会对缓存在 Redis 中的商品库存并发更新。一旦有了并发写操作,数据就会被修改,如果我们没有对并发写请求做好控制,就可能导致数据被改错,影响到业务的正常使用(例如库存数据错误,导致下单异常)。为了保证并发访问的正确性,Redis 提供了两种方法,分别是加锁和原子操作。看上去好像是一种很好的方案,但是,其实这... 阅读详情

Redis 中的分布式锁如何使用

分布式锁的使用场景

为了保证我们线上服务的并发性和安全性,目前我们的服务一般抛弃了单体应用,采用的都是扩展性很强的分布式架构。

对于可变共享资源的访问,同一时刻,只能由一个线程或者进程去访问操作。这时候我们就需要做个标识,如果当前有线程或者进程在操作共享变量,我们就做个标记,标识当前资源正在被操作中, 其它的线程或者进程,就不能进行操作了。当前操作完成之后,删除标记,这样其他的线程或者进程,就能来申请共享变量的操作。通过上面的标记来保证同一时刻共享变量只能由一个线程或者进行持有。

  • 对于单体应用:多个线程之间访问可变共享变量,比较容易处理,可简单使用内存来存储标示即可;
  • 分布式应用:这种场景下比较麻烦,因为多个应用,部署的地址可能在不同的机房,一个在北京一个在上海。不能简单的存储标示在内存中了,这时候需要使用公共内存来记录该标示,栗如 Redis,MySQL 。。。

使用 Redis 来实现分布式锁

这里来聊聊如何使用 Redis 实现分布式锁

Redis 中分布式锁一般会用 set key value px milliseconds nx 或者 SETNX+Lua 来实现。

因为 SETNX 命令,需要配合 EXPIRE 设置过期时间,Redis 中单命令的执行是原子性的,组合命令就需要使用 Lua 才能保证原子性了。

看下如何实现

使用 set key value px milliseconds nx 实现

因为这个命令同时能够设置键值和过期时间,同时Redis中的单命令都是原子性的,所以加锁的时候使用这个命令即可

func (r *Redis) TryLock(ctx context.Context, key, value string, expire time.Duration) (isGetLock bool, err error) {
	// 使用 set nx
	res, err := r.Do(ctx, "set", key, value, "px", expire.Milliseconds(), "nx").Result()
	if err != nil {
		return false, err
	}
	if res == "OK" {
		return true, nil
	}
	return false, nil
}

SETNX+Lua 实现

如果使用 SETNX 命令,这个命令不能设置过期时间,需要配合 EXPIRE 命令来使用。

因为是用到了两个命令,这时候两个命令的组合使用是不能保障原子性的,在一些并发比较大的时候,需要配合使用 Lua 脚本来保证命令的原子性。

func tryLockScript() string {
	script := `
		local key = KEYS[1]

		local value = ARGV[1] 
		local expireTime = ARGV[2] 
		local isSuccess = redis.call('SETNX', key, value)

		if isSuccess == 1 then
			redis.call('EXPIRE', key, expireTime)
			return "OK"
		end

		return "unLock"    `
	return script
}

func (r *Redis) TryLock(ctx context.Context, key, value string, expire time.Duration) (isGetLock bool, err error) {
	// 使用 Lua + SETNX
	res, err := r.Eval(ctx, tryLockScript(), []string{key}, value, expire.Seconds()).Result()
	if err != nil {
		return false, err
	}
	if res == "OK" {
		return true, nil
	}
	return false, nil
}

除了上面加锁两个命令的区别之外,在解锁的时候需要注意下不能误删除别的线程持有的锁

为什么会出现这种情况呢,这里来分析下

举个栗子

1、线程1获取了锁,锁的过期时间为1s;

2、线程1完成了业务操作,用时1.5s ,这时候线程1的锁已经被过期时间自动释放了,这把锁已经被别的线程获取了;

3、但是线程1不知道,接着去释放锁,这时候就会将别的线程的锁,错误的释放掉。

面对这种情况,其实也很好处理

1、设置 value 具有唯一性;

2、每次删除锁的时候,先去判断下 value 的值是否能对的上,不相同就表示,锁已经被别的线程获取了;

看下代码实现

var UnLockErr = errors.New("未解锁成功")

func unLockScript() string {
	script := `
		local value = ARGV[1] 
		local key = KEYS[1]

		local keyValue = redis.call('GET', key)
		if tostring(keyValue) == tostring(value) then
			return redis.call('DEL', key)
		else
			return 0
		end
    `
	return script
}

func (r *Redis) Unlock(ctx context.Context, key, value string) (bool, error) {
	res, err := r.Eval(ctx, unLockScript(), []string{key}, value).Result()
	if err != nil {
		return false, err
	}

	return res.(int64) != 0, nil
}

代码可参考 lock

上面的这类锁的最大缺点就是只作用在一个节点上,即使 Redis 通过 sentinel 保证高可用,如果这个 master 节点由于某些原因放生了主从切换,那么就会出现锁丢失的情况:

1、在 Redis 的 master 节点上拿到了锁;

2、但是这个加锁的 key 还没有同步到 slave 节点;

3、master 故障,发生了故障转移,slave 节点升级为 master 节点;

4、导致锁丢失。

针对这种情况如何处理呢,下面来聊聊 Redlock 算法

使用 Redlock 实现分布式锁

在 Redis 的分布式环境中,我们假设有 N 个 Redis master 。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。我们确保将在 N 个实例上使用与在 Redis 单实例下相同方法获取和释放锁。现在我们假设有 5 个 Redis master 节点,同时我们需要在5台服务器上面运行这些 Redis 实例,这样保证他们不会同时都宕掉。

为了取到锁,客户端营该执行以下操作:

1、获取当前Unix时间,以毫秒为单位。

2、依次尝试从5个实例,使用相同的key和具有唯一性的 value(例如UUID)获取锁。当向 Redis 请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在 5-50 毫秒之间。这样可以避免服务器端 Redis 已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试去另外一个 Redis 实例请求获取锁;

3、客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数( N/2+1 ,这里是3个节点)的 Redis 节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功;

4、如果取到了锁,key 的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果);

5、如果因为某些原因,获取锁失败(没有在至少 N/2+1 个 Redis 实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些 Redis 实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。

根据官方的推荐,go 版本中 Redsync 实现了这一算法,这里看下具体的实现过程

redsync项目地址

// LockContext locks m. In case it returns an error on failure, you may retry to acquire the lock by calling this method again.
func (m *Mutex) LockContext(ctx context.Context) error {
	if ctx == nil {
		ctx = context.Background()
	}

	value, err := m.genValueFunc()
	if err != nil {
		return err
	}
	
	for i := 0; i < m.tries; i++ {
		if i != 0 {
			select {
			case <-ctx.Done():
				// Exit early if the context is done.
				return ErrFailed
			case <-time.After(m.delayFunc(i)):
				// Fall-through when the delay timer completes.
			}
		}

		start := time.Now()

		// 尝试在所有的节点中加锁
		n, err := func() (int, error) {
			ctx, cancel := context.WithTimeout(ctx, time.Duration(int64(float64(m.expiry)*m.timeoutFactor)))
			defer cancel()
			return m.actOnPoolsAsync(func(pool redis.Pool) (bool, error) {
				// acquire 加锁函数
				return m.acquire(ctx, pool, value)
			})
		}()
		if n == 0 && err != nil {
			return err
		}

		// 如果加锁节点书没有达到的设定的数目
		// 或者键值的过期时间已经到了
		// 在所有的节点中解锁
		now := time.Now()
		until := now.Add(m.expiry - now.Sub(start) - time.Duration(int64(float64(m.expiry)*m.driftFactor)))
		if n >= m.quorum && now.Before(until) {
			m.value = value
			m.until = until
			return nil
		}
		_, err = func() (int, error) {
			ctx, cancel := context.WithTimeout(ctx, time.Duration(int64(float64(m.expiry)*m.timeoutFactor)))
			defer cancel()
			return m.actOnPoolsAsync(func(pool redis.Pool) (bool, error) {
				// 解锁函数
				return m.release(ctx, pool, value)
			})
		}()
		if i == m.tries-1 && err != nil {
			return err
		}
	}

	return ErrFailed
}

// 遍历所有的节点,并且在每个节点中执行传入的函数
func (m *Mutex) actOnPoolsAsync(actFn func(redis.Pool) (bool, error)) (int, error) {
	type result struct {
		Status bool
		Err    error
	}

	ch := make(chan result)
	// 执行传入的函数
	for _, pool := range m.pools {
		go func(pool redis.Pool) {
			r := result{}
			r.Status, r.Err = actFn(pool)
			ch <- r
		}(pool)
	}
	n := 0
	var err error
	// 计算执行成功的节点数目
	for range m.pools {
		r := <-ch
		if r.Status {
			n++
		} else if r.Err != nil {
			err = multierror.Append(err, r.Err)
		}
	}
	return n, err
}

// 手动解锁的lua脚本
var deleteScript = redis.NewScript(1, `
	if redis.call("GET", KEYS[1]) == ARGV[1] then
		return redis.call("DEL", KEYS[1])
	else
		return 0
	end
`)

// 手动解锁
func (m *Mutex) release(ctx context.Context, pool redis.Pool, value string) (bool, error) {
	conn, err := pool.Get(ctx)
	if err != nil {
		return false, err
	}
	defer conn.Close()
	status, err := conn.Eval(deleteScript, m.name, value)
	if err != nil {
		return false, err
	}
	return status != int64(0), nil
}

分析下思路

1、遍历所有的节点,然后尝试在所有的节点中执行加锁的操作;

2、收集加锁成功的节点数,如果没有达到指定的数目,释放刚刚添加的锁;

关于 Redlock 的缺点可参见

How to do distributed locking

锁的续租

Redis 中分布式锁还有一个问题就是锁的续租问题,当锁的过期时间到了,但是业务的执行时间还没有完成,这时候就需要对锁进行续租了

续租的流程

1、当客户端加锁成功后,可以启动一个定时的任务,每隔一段时间,检查业务是否完成,未完成,增加 key 的过期时间;

2、这里判断业务是否完成的依据是:

  • 1、这个 key 是否存在,如果 key 不存在了,就表示业务已经执行完成了,也就不需要进行续租操作了;
  • 2、同时需要校验下 value 值,如果 value 对应的值和之前写入的值不同了,说明当前锁已经被别的线程获取了;

看下 redsync 中续租的实现

// Extend resets the mutex's expiry and returns the status of expiry extension.
func (m *Mutex) Extend() (bool, error) {
	return m.ExtendContext(nil)
}

// ExtendContext resets the mutex's expiry and returns the status of expiry extension.
func (m *Mutex) ExtendContext(ctx context.Context) (bool, error) {
	start := time.Now() 
	// 尝试在所有的节点中加锁
	n, err := m.actOnPoolsAsync(func(pool redis.Pool) (bool, error) {
		return m.touch(ctx, pool, m.value, int(m.expiry/time.Millisecond))
	})
	if n < m.quorum {
		return false, err
	}
	// 判断下锁的过期时间
	now := time.Now()
	until := now.Add(m.expiry - now.Sub(start) - time.Duration(int64(float64(m.expiry)*m.driftFactor)))
	if now.Before(until) {
		m.until = until
		return true, nil
	}
	return false, ErrExtendFailed
}

var touchScript = redis.NewScript(1, `
	// 需要先比较下当前的value值
	if redis.call("GET", KEYS[1]) == ARGV[1] then
		return redis.call("PEXPIRE", KEYS[1], ARGV[2])
	else
		return 0
	end
`)

func (m *Mutex) touch(ctx context.Context, pool redis.Pool, value string, expiry int) (bool, error) {
	conn, err := pool.Get(ctx)
	if err != nil {
		return false, err
	}
	defer conn.Close()
	status, err := conn.Eval(touchScript, m.name, value, expiry)
	if err != nil {
		return false, err
	}
	return status != int64(0), nil
}

1、锁的续租需要客户端去监听和操作,启动一个定时器,固定时间来调用续租函数给锁续租;

2、每次续租操作的时候需要匹配下当前的 value 值,因为锁可能已经被当前的线程释放了,当前的持有者可能是别的线程;

看看 SETEX 的源码

SETEX 能保证只有在 key 不存在时设置 key 的值,那么这里来看看,源码中是如何实现的呢

// https://github.com/redis/redis/blob/7.0/src/t_string.c#L78
// setGenericCommand()函数是以下命令: SET, SETEX, PSETEX, SETNX.的最底层实现
void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
    ...

    found = (lookupKeyWrite(c->db,key) != NULL);
    // 这里是 SETEX 实现的重点
	// 如果nx,并且在数据库中找到了这个值就返回
	// 如果是 xx,并且在数据库中没有找到键值就会返回
	
	// 因为 Redis 中的命令执行都是单线程操作的
	// 所以命令中判断如果存在就返回,能够保证正确性,不会出现并发访问的问题
    if ((flags & OBJ_SET_NX && found) ||
        (flags & OBJ_SET_XX && !found))
    {
        if (!(flags & OBJ_SET_GET)) {
            addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
        }
        return;
    }

    ...
}

1、命令的实现里面加入了键值是否存在的判断,来保证 NX 只有在 key 不存在时设置 key 的值;

2、因为 Redis 中总是一个线程处理命令的执行,单命令是能够保证原子性,不会出现并发的问题。

为什么 Redis 可以用来做分布式锁

分布式锁需要满足的特性

  • 互斥性:在任意时刻,对于同一个锁,只有一个客户端能持有,从而保证一个共享资源同一时间只能被一个客户端操作;
  • 安全性:即不会形成死锁,当一个客户端在持有锁的期间崩溃而没有主动解锁的情况下,其持有的锁也能够被正确释放,并保证后续其它客户端能加锁;
  • 可用性:当提供锁服务的节点发生宕机等不可恢复性故障时,“热备” 节点能够接替故障的节点继续提供服务,并保证自身持有的数据与故障节点一致。
  • 对称性:对于任意一个锁,其加锁和解锁必须是同一个客户端,即客户端 A 不能把客户端 B 加的锁给解了。

那么 Redis 对上面的特性是如何支持的呢?

1、Redis 中命令的执行都是单线程的,虽然在 Redis6.0 的版本中,引入了多线程来处理 IO 任务,但是命令的执行依旧是单线程处理的;

2、单线程的特点,能够保证命令的执行的是不存在并发的问题,同时命令执行的原子性也能得到保证;

3、Redis 中提供了针对 SETNX 这样的命令,能够保证同一时刻是只会有一个请求执行成功,提供互斥性的保障;

4、Redis 中也提供了 EXPIRE 超时释放的命令,可以实现锁的超时释放,避免死锁的出现;

5、高可用,针对如果发生主从切换,数据丢失的情况,Redis 引入了 RedLock 算法,保证了 Redis 中主要大部分节点正常运行,锁就可以正常运行;

6、Redis 中本身没有对锁提供续期的操作,不过一些第三方的实现中实现了 Redis 中锁的续期,类似 使用 java 实现的 Redisson,使用 go 实现的 redsync,当然自己实现也不是很难,实现过程可参见上文。

总体来说,Redis 中对分布式锁的一些特性都提供了支持,使用 Redis 实现分布式锁,是一个不错的选择。

分布式锁如何选择

1、如果业务规模不大,qps 很小,使用 Redis,etcd,ZooKeeper 去实现分布式锁都不会有问题,就看公司了基础架构了,如果有现成的 Redis,etcd,ZooKeeper 直接用就可以了;

2、Redis 中分布式锁有一定的安全隐患,如果业务中对安全性要求很高,那么 Redis 可能就不适合了,etcd 或者 ZooKeeper 就比较合适了;

3、如果系统 qps 很大,但是可以容忍一些错误,那么 Redis 可能就更合适了,毕竟 etcd或者ZooKeeper 背面往往都是较低的吞吐量和较高的延迟。

总结

1、在分布式的场景下,使用分布式锁是我们经常遇到的一种场景;

2、使用 Redis 实现锁是个不错的选择,Redis 的单命令的执行是原子性的同时借助于 Lua 也可以很容易的实现组合命令的原子性;

3、针对分布式场景下主从切换,数据同步不及时的情况,redis 中引入了 redLock 来处理分布式锁;

4、根据 martin 的描述,redLock 是繁重的,且存在安全性,不过我们可以根据自己的业务场景做出判断;

5、需要注意的是在设置分布式锁的时候需要设置 value 的唯一性,并且每次主动删除锁的时候需要匹配下 value 的正确性,避免误删除其他线程的锁;

参考

【Redis核心技术与实战】
https://time.geekbang.org/column/intro/100056701

【Redis设计与实现】
https://book.douban.com/subject/25900156/

【Redis 的学习笔记】
https://github.com/boilingfrog/Go-POINT/tree/master/redis

【Redis 分布式锁】
https://redis.io/docs/reference/patterns/distributed-locks/

【How to do distributed locking】
https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

【etcd 实现分布式锁】
https://www.cnblogs.com/ricklz/p/15033193.html#%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81

  • Previous
  • Redis 中的原子操作(2)-redis 中使用Lua来保证命令的原子性

 

分布式锁特点、以及用python3实现redis分布式锁 以下是 Python3 实现 Redis 分布式锁的详细教程,包含代码示例、原理说明及最佳实践。 阅读详情

相关推荐

Multisim新手必看:从零搭建数字时钟仿真电路(附完整原理图)

本文详细介绍了如何使用Multisim从零开始搭建数字时钟仿真电路,涵盖晶振电路设计、计数电路实现、校时功能配置等关键步骤。通过完整的原理图和实用技巧,帮助新手快速掌握Multisim仿真设计,实现精准的数字时钟功能。

ios99的博客 328

Redis 分布式锁

Redis 分布式锁 Redisson实现分布式锁

杨宇昌的博客 896

audio框架学习-Audio_Flinger分析

文章目录一、代码位置二、audio服务启动2.1 main服务2.2 audioflinger添加到系统服务中2.3 audioflinger的对外接口 之前记录过audioTrack的笔记,这次继续参考audioFlinger相关的代码并做笔记; 参考文档:https://www.cnblogs.com/innost/archive/2011/01/15/1936425.html和https://blog.youkuaiyun.com/zyuanyun/article/details/60890534?spm=100

永不秃头的程序员的博客 2136

redis分布式锁的原子保证

redis分布式锁的原子保证,是什么为什么怎么样

qq_46118239的博客 3095

FOC观测器之龙伯格观测器

状态观测器: 理论依据:根据控制理论,如果一个系统能够完全通过其检测到的输出值来重构其系统状态,则认为该系统是可观测的。其作用于无传感器转子的位置和速度的检测反馈,再作用到PARK逆变换和转矩磁链控制上。 Luenberger观察器原理图: Luenberger观测器的离散化: (其观测器的闭环参数为经验验证值) 引入估测电流( iα,iβ )的反馈值并离散化(T 为采样时间),可得...

ShawnChan的博客 2万+

深入解析JESD204B Subclass 1中的SYSREF与LMFC同步机制

本文深入解析了JESD204B Subclass 1协议中的核心同步机制,重点阐述了SYSREF信号如何作为系统级参考,对齐所有设备内部的本地多帧时钟(LMFC),从而实现高速多通道数据采集系统中的确定性延迟。文章结合实战经验,详细说明了硬件设计、时序要点及软件配置流程,是解决多ADC芯片同步对齐问题的关键指南。

weixin_29271263的博客 399

Redis操作有哪些是原子性的?

这些操作在 Redis 中是原子性的,即这些操作要么完全执行成功,要么完全不执行,保证了数据的一致性和可靠性。4. LPUSH/RPUSH:将一个或多个值插入到列表的头部或尾部。3. INCR/DECR:对指定 key 的值进行增加或减少操作。10. ZADD:将一个或多个成员元素及其分数值添加到有序集合中。5. LPOP/RPOP:从列表的头部或尾部移除并返回一个元素。11. ZREM:从有序集合中移除一个或多个成员。7. SREM:从集合中移除一个或多个元素。1. SET:设置指定 key 的值。

qq_43795369的博客 4093

2024年淘宝平板电脑销售数据分析

本项目的目标是从淘宝电商平台上自动化爬取商品信息,并通过数据分析帮助了解价格、销量等相关指标,为商业决策提供支持。使用Selenium实现数据爬取,结合Python的数据处理与可视化工具,进一步分析和展示结果。

2301_76728517的博客 1474

Redis的原子操作(SET NX PX,Lua,单、多实例分布式锁 )

摘要: 1. 单原子性指令 2. Lua脚本原子性执行多指令 3. Redis实现分布式锁 3.1 单Redis实例保证锁的可靠性和高效性 3.1.1 加锁: A) SET NX指令作为加锁的单原子性指令 B) 设置锁自动释放的有效时间,看门狗 C) 加锁释放锁操作对应唯一的客户端 3.1.2 释放锁: A) 使用Lua脚本判断是否是加锁的同一个客户端 3.2 Redis master集群保证锁的可靠性+可用性 3.2.1 Redlock算法:N/2 +1 条件以及重计算锁有效时间机制 ps: 所有并..

skye_fly的博客 7517

WOFOST作物模型(7):6.0.6版本的PCSE使用(多层水分平衡和SNOMIN碳氮模型的集成)

wofost81文件结构如上,其中所需的参数可从github上获取然后input下有两个需要调用的功能函数py文件,需要拖到自己的运行py文件同级目录下在使用土壤模块SNOMIN进行作物生长模型模拟时,需要为土壤剖面的不同层次提供多种输入变量。这些变量通常不随年份变化,因此会统一在土壤文件中定义。本节将说明如何将这些输入变量整合到一个字典中,进而生成SNOMIN能够读取的土壤文件。

所有专家都曾始于新手 638

Windows2008使用-解决漏洞-CVE-2010-2730

随着硬件的升级,操作系统也逐渐进行了版本更新。目前阶段,Windows2016Server、Windows2022Server逐渐成为了以Windows系列的服务器平台系统。对于有些应用而言,由于其业务的固定,可能仍只能运行在Windows2008Server或者Windows2012Server上。有时候我们为安全需要,需要将相关的操作系统涉及的漏洞做修复。最近碰到在一个Windows2008R2 的操作系统漏洞CVE-2010-2730,一顿操作,终于找到了合适的方案,这里分享给大家。

mystonelxj的博客 2316

使用 Redis 实现分布式锁,解决分布式锁原子性、死锁、误删、可重入、自动续期等问题(使用SpringBoot环境实现)

使用 Redis 实现分布式锁,解决分布式锁原子性、死锁、误删、可重入、自动续期等问题(使用SpringBoot环境实现)

kerwin的博客 1万+

【实战篇导读】四足机器人运动控制导读

认知有限,望大家多多包涵,有什么问题也希望能够与大家多交流,共同成长!本文先对四足机器人运动运动控制系统及相关算法做个简单的介绍,具体内容后续再更,其他模块可以参考去我其他文章主要介绍了波士顿spot机器人的发展历史、四足机器人的硬件系统及电机执行器组成包括硬件系统、关节电机执行器,机载传感器机处理器及电气连接等。概述了四足机器人规划控制、感知、定位及局部地图的相关算法方向。

qq_35635374的博客 679

Redis实现分布式锁

在多线程并发的情况下,我们可以使用锁来保证一个代码块在同一时间内只能由一个线程访问。比如Java的synchronized关键字和Reentrantlock类等等。 这样子可以保证在同一个JVM进程内的多个线程同步执行。 如果在分布式的集群环境中,如何保证不同节点的线程同步执行呢? 怎么才能在分布式系统中,实现不同线程对代码和资源的同步访问? 对于单进程的并发场景,我们可以使用语言...

forever-and-ever的博客 4万+

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

redis实现分布式锁的原理

redis实现分布式锁,从原理的实现,干货满满~~~

LYQ20010417的博客 1万+

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

【SpringBoot篇】基于Redis分布式锁的 误删问题 和 原子性问题

如果属于自己,则不进行锁的删除,假设还是上边的情况,线程1卡顿,锁自动释放,线程2进入到锁的内部执行逻辑,此时线程1反应过来,然后删除锁,但是线程1,一看当前这把锁不是属于自己,于是不进行删除锁逻辑,当线程2走到删除锁逻辑时,如果没有卡过自动释放锁的时间点,则判断当前这把锁是属于自己的,于是删除这把锁。这个时候线程2开始执行,在线程2执行过程中,线程1阻塞结束了,会执行删除锁的操作,相当于判断锁标识并没有起到作用(因为之前一句判断过了),于是就把线程2的锁给删除掉了,又一次发生了误删操作。

小吉妙妙屋 2785

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

SpringBoot中使用Redis实现分布式锁

SpringBoot中使用Redis实现分布式锁

爱吃鸡腿的明仔 5361

Redis实现分布式锁的几种方式

​众所周知,项目开发中难免会遇到高并发场景,而保证数据安全则成为其中的重中之重.。

weixin_45936627的博客 2556

基于 Redis + Lua 脚本实现分布式锁,确保操作的原子性

Lua脚本是高并发、高性能的必备脚本语言,大部分的开源框架(如:redission)中的分布式锁组件,都是用纯lua脚本实现的。

Java Punk 1万+

Redis的分布式锁

Redis分布式锁通过Redis原子操作实现互斥访问,核心是SETNX命令设置带过期时间的键值对,配合Lua脚本确保安全释放。Java实现可通过Jedis手动处理锁获取、唯一标识验证和释放逻辑,或使用Redisson简化开发,其提供自动续期、可重入锁等功能。Redlock算法支持多节点部署提高可靠性。注意事项包括合理设置锁超时、确保原子操作、处理时钟漂移等。适用于秒杀、任务调度等需要互斥访问的场景,Jedis适合简单需求,Redisson更适合复杂场景,能显著降低开发复杂度。

Hzx895554135的博客 2714

Redis 实现分布式锁

本文主要介绍了Redis的分布式锁。涉及Reids的原子操作,分布式锁,Redis的事务,以及用Go语言实现通过Lua脚本来扣减库存。

tatasix的博客 1837

Redis中使用Lua脚本实现原子操作

Redis允许用户在服务器上上传并执行 Lua 脚本,也就是说Redis支持我们使用Lua编写一些简单的逻辑,当做一个自定义的命令,在单次操作中来执行,这在很多场景中都很有用,比如redisson分布式锁,滑动窗口限流等。现在就以简单例子上手来看看java编程怎么使用lua脚本实现redis原子操作。

咖啡煮码 3525

Redis系列:使用Redis实现分布式锁及相关问题

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。 本篇内容包括:关于 Redis 与 分布式锁,Redis 分布式锁的问题及解决方式,Redis 中的 Lua 脚本 以及 Redis 中的 RedLock 算法!

栗筝i的博客 6359
上一篇: 腾讯正式开源 Spring Cloud Tencent,打造一站式微服务解决方案
下一篇: 微服务之服务注册和服务发现篇
程序员职业指南
博客等级 码龄4年 2138粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值