文章目录
- 前言
-
- 问题场景
- [腾讯云 Cloud Studio](#腾讯云 Cloud Studio)
- Redisgo_task
-
- 长短类型分布式场景介绍
- Redisgo_task实现原理
- Redisgo_task唯一外部依赖
- [Redisgo_task Lock结构](#Redisgo_task Lock结构)
- Redisgo_task架构健壮性设计
- Redisgo_task性能指标
- Q&A
- 附:
前言
问题场景
随着云技术的成熟,各大厂商为提升服务质量、降低生产成本、加快产品迭代效率,都提倡服务上云,分享云技术带来的便利。
而作为开发人员,总会被这样的问题时不时烦恼:接手或搭建项目时,由于自身机器的环境变量、sdk 包版本、等因素,导致服务无法正常启动、或者是在本地搭建环境需要依赖多种基础存储、组件,小小的 Mac 几G的存储完全 Hold 不住,尤其涉及算法同学感受极深、或者压根都不想把公司的代码库放到自己的电脑上,弄的公私不分...
其实,不光是研发人员,领导们也很纠结,怕存在代码泄漏、等安全问题。
针对这种特有场景,云提供了一种简单、便捷的解决方案。在解决研发问题的同时,也规避了某中安全问题,甚至提升了功能交付效率。
下面给大家介绍 腾讯云 Cloud Studio ,一种 "在线编程 · AI-辅助开发" 云上工具。
腾讯云 Cloud Studio
在现阶段提供的模版中,存在 30+ 的初始模版,包含 框架模板、云原生模板、建站模板 多种代码库。在功能完全对齐传统的 IDE 模式的基础上,提供 AI 智能组件支持,进一步解放研发的成本。
Redisgo_task
一款基于Goland语言实现的Redis分布式锁产品,支持百万级实例/协程并发,适用于各种常见的分布式场景。
长短类型分布式场景介绍
目前业务中分布式锁场景依据任务对象所需的Occupied Time可分为两种:短任务类型、长任务类型 。
长任务类型
任务A需要在很长的一段时间占有锁,这个时间未知,直至任务A结束,甚至特殊情况下A的周期为业务的全生命周期,才能释放锁,再供任务A/B/C/D/争抢;
短任务类型
任务A在极短的时间内可完成,可在已知的时12:12:10间阈值内,释放锁,再供任务A/B/C/D/争抢;
实质上
两种类型都是对分布式场景下公共资源的一致性保证;
功能上
长任务类型更倾向于实现某单实例的动态切换,如解决实例单点问题等;
短任务类型更倾向于对时刻高并发的限制,如短时间内的流量控制等
Redisgo_task两种任务类型都支持。
Redisgo_task实现原理
基于Redis SetNx()方法进行封装,创建子协程监听任务执行状态,任务执行中频次拉取Luck的Expire,当Expire在配置的阈值范围内,持续增加Expire,从而确保Luck在任务进行中不会过期。
SetNx(value+expire)原子性
lock.Conn.Do("SET", lock.Key, lock.Token, "EX", int(lock.TimeOut), "NX")
子协程Done()时间点
由doneCh <-chan struct{} Channel阻塞控制,在Unlock()中会出发Close()信号
子协程中的Ticker
通过Ticker,持续进行Expire Add操作,可有效避免阻塞及单次Expire Add失败的场景,且有效验证当前锁状态,及时Stop
Redisgo_task唯一外部依赖
dir:config/redisgo_task.toml
[redis]
host = "10.13.40.145:26380"
key = "Redisgo_Task_Consul_Lock_key"
# value = "8292884c-a7a7-0050-9778-e47362a8f578"
# expire = "500ms"
# retries = "10ms"
# cron = "10ms"
Redisgo_task Lock结构
type RedisLock struct {
Host string
Expire time.Duration
Key string
Value string
Conn redis.Conn
Cron time.Duration
Tries time.Duration
DoneExpireChan chan struct{}
}
#expire-锁默认expire「单位s」
#retries重试获取锁间隔
#cron 持续增加expire频次
Redisgo_task架构健壮性设计
考量到产品架构在实用中的健壮性,针对产品的整体架构设计,对实现过程做出了一下方向的调整:
Redisgo_task可扩展性
对功能实现过程依赖的参数,及功能函数进行封装成不同程度的Struct、Interface,方便后期功能扩展
Redisgo_task灵活性
对产品功能涉及到的主要环节阈值拆分,抽象为依赖,支持外部配置化,且作为唯一入口,依据实际场景调整,保证配置的灵活性及功能最细粒度的控制
Redisgo_task可读性
在产品功能实现过程中,添加完整的日志输出,确保逻辑的清晰可读,降低产品的上手难度
Redisgo_task健壮性
在产品功能实现过程中,添加并进行了充分的单元测试
go
package lock
import (
"fmt"
"testing"
)
func TestRetriesTime(t *testing.T) {
var testTry = NewRetry(0,0,false)
fmt.Printf("Test 1 res:%v\n", testTry.RetriesTime())
fmt.Printf("Test 2 res:%v\n", testTry.RetriesTime())
fmt.Printf("Test 3 res:%v\n", testTry.RetriesTime())
fmt.Printf("Test 4 res:%v\n", testTry.RetriesTime())
fmt.Printf("Test 5 res:%v\n", testTry.RetriesTime())
fmt.Printf("Test 6 res:%v\n", testTry.RetriesTime())
}
Redisgo_task性能指标
在数据量、实例数量两个维度验证:在高并发场景下每个实例获得锁的成功率一致;
实验分为三组,分别为样本一、样本二、样本三如下图;
样本一中数据规模每组在10+,较少,两组成功率相差4.2%,无法体现在双实例下,每个实例成功率一致的目标;
样本二中数据规模每组在3w+,尚可,三组成功率均差在0.2%,已经十分接近目标;
样本三中数据规模每组在1w+,尚可,四组成功率均差在0.0025%,可以验证并发场景下,实例强锁成功率均等;
Q&A
1、任务执行时长未知,如何保证任务期间,持续占有锁/?
任务占有锁,会启子协程频次监听锁TTL,在可控粒度下,持续保障锁的Expire延长更新。
2、主任务结束,如何终止ReExpire子协程终止/?
关联Goland中协程通信,项目实现中采用Channel/Close()方案实现。
Of crouse,ctx context/Done方案也可行。
3、当并发场景下,会不会出现任务A占有锁的同时,Expire时间到期,锁被任务B占用/?
较低的概率会出现这种问题。
当默认Expire时间内ReExpire策略无数次失败,才会导致锁到期自动释放,被其他任务占用。
4、如何解决1中,任务A假锁,任务B真锁,A执行结束又将Key删除,破坏任务B的问题/?
在Unlock()实现中,做del操作之前会进行Value的校验,匹配时进行del操作,且通过Lua脚本保证原子性。
5、ReExpire逻辑在短类型场景中,是否没必要存在/?
ReExpire逻辑是为兼容长类型场景设计,在短类型场景中不影响业务逻辑正常进行。
附:
GitHub:https://github.com/weiyanyanyan/redisgo_task