从redis setnx 来看看分布式锁
什么是分布式锁
分布式锁(多服务共享锁)在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行访问/操作。
为什么需要分布式锁
在单体应用服务里,不同的客户端操作同一个资源,我们可以通过操作系统提供的互斥(锁/信号量等等)来提供互斥的能力,保证操作资源的只有一个客户端。
在分布式的情况里,就需要第三方组件来保证对统一资源的操作的互斥。
(下单中,两个人下单,一个人下单请求走订单服务A机器,另一个人下单请求走订单服务B,这样用单体的思维处理就可能不是很合适,需要借用第三方组件配合来实现分布式锁)
分布式锁可以用redis, zookeeper , etcd等等来实现,下面我们简单说说....
redis分布式锁
简单例子
set key value ex/px nx 或setnx
以setnx 为例,可以使用 setnx key value 来进行 "加锁" ( setnx 主要 是nx加了语义:如果存在就不操作,不存在就添加),多个客户端确保只有一个加锁成功去操作统一资源
127.0.0.1:6379[1]> setnx lockObj 1 // 加锁
(integer) 1
127.0.0.1:6379[1]> setnx lockObj 1 // 存在了就不能再加锁
(integer) 0
127.0.0.1:6379[1]> get lockObj
"1"
127.0.0.1:6379[1]> del lockObj // 释放锁
(integer) 1
127.0.0.1:6379[1]> get lockObj
(nil)
127.0.0.1:6379[1]> setnx lockObj 1
(integer) 1

上面就是简单的加锁的例子,仔细思考下分布式锁使用的使用我们需要考虑哪些问题?
存在的问题
简单的总结了下,我们在使用redis分布式锁的时候需要考虑如下情况:
1- 死锁问题
2- 续锁生命周期
3- 操作原子性
4- 锁的归属权
5- redis 集群,锁的状态一致性
死锁问题
如果我们业务代码出现bug或服务器出现问题,没有及时释放锁,那么其他的客户端就永远获取不到这个加锁的资格。
这个时候我们就可以加上对应的处理逻辑:
golang 加上defer 加锁锁逻辑 , python try-Except-finally 用finally 释放锁。并且在加锁的时候加上过期时间(根据业务进行合适的加)
续锁生命周期
上面我们可以解决锁的释放问题,但是我们的业务处理时间不一定百分百能知道处理的时间,这个时候如果锁过期了但是资源操作没有做完,那么就会出现问题。
在java的 Redisson 有个watch 续命机制, golang 的话可以 借鉴 Rllock ,开启一个守护进程监听,定时续命(一定要提前续命,不要等到到时间再续)
操作原子性
在我们进行加锁 加过期时间的时候,这两个操作不能分两步操作。因为如果setnx加锁成功了,这时候失败了,那么这个锁就永远被占用了。
根据这个问题我们可以使用lua脚本或者使用第三方模块是可以同时进行这两个步骤的
`if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then return redis.call('PEXPIRE',KEYS[1],ARGV[2]) else return 0 end`
锁的归属权问题
现在有几个场景:
1- 客户端1 拿到锁处理业务, 没处理完已经过期了;这时候客户端2 拿到锁在处理,结果客户端1 处理完就释放锁了
2- 一个业务不同画像的人处理的业务不同,这时候我们就需要根据不同画像人进行分配 “锁”
我们在实际开发的时候有时候需要了解业务场景, 有时候需要给锁加一个所属权的令牌。可以在setnx key时,key设定特殊化的数值
redis 集群,锁的状态一致性
在redis采用集群(主从),如果master加锁失败了,这时候服务宕机了,slave还同步这个 key ,那么这个时候就会有客户端加锁成功
redis作者对于这个问题提出了解答:REDLOCK
zookeeper 实现分布式锁
简单例子
[zk: localhost:2181(CONNECTED) 62] create /lock
Created /lock
[zk: localhost:2181(CONNECTED) 63] create -s -e /lock/req // 创建临时节点
Created /lock/req0000000000
[zk: localhost:2181(CONNECTED) 64] create -s -e /lock/req
Created /lock/req0000000001
[zk: localhost:2181(CONNECTED) 65] create -s -e /lock/req
Created /lock/req0000000002
[zk: localhost:2181(CONNECTED) 67] ls /lock // 节点下的临时节点
[req0000000000, req0000000001, req0000000002]
[zk: localhost:2181(CONNECTED) 68] delete /lock/req0000000000 // 释放第一锁
golang的例子
package mainimport ("fmt""github.com/samuel/go-zookeeper/zk""sort""time"
)func sortChildren(children []string) {sort.Slice(children, func(i, j int) bool {return children[i] < children[j]})
}
func main() {go func() {conn, _, err := zk.Connect([]string{"xx.xx.xx.xx:2181"}, time.Second*5)if err != nil {fmt.Println("Connect:", err.Error())return}defer conn.Close()lockPath := "/locksObj"lockName := "lock"// 创建锁的根节点_, err = conn.Create(lockPath, []byte{}, int32(0), zk.WorldACL(zk.PermAll))if err != nil && err != zk.ErrNodeExists {fmt.Println("Create:", err.Error())return}// 获取锁lockNodePath, err := conn.CreateProtectedEphemeralSequential(lockPath+"/"+lockName+"-", []byte{}, zk.WorldACL(zk.PermAll))if err != nil {fmt.Println("CreateProtectedEphemeralSequential:", err.Error())return}// doworkfor {children, _, err := conn.Children(lockPath)if err != nil {fmt.Println("Children:", err.Error())return}// 对子节点按照序列号进行排序sortChildren(children)// 检查自己创建的节点是否是第一个节点if lockNodePath == lockPath+"/"+children[0] {// 获取到了锁fmt.Println("Acquired lock")break}// 监听前一个节点的删除事件exists, _, watch, err := conn.ExistsW(lockPath + "/" + children[0])if err != nil {fmt.Println("ExistsW: ", err.Error())break}if !exists {// 前一个节点已删除,再次检查自己创建的节点是否是第一个节点children, _, err = conn.Children(lockPath)if err != nil {fmt.Println("Children: ", err.Error())break}sortChildren(children)if lockNodePath == lockPath+"/"+children[0] {// 获取到了锁fmt.Println("Acquired lock")break}}// 等待前一个节点的删除事件<-watch}// 执行需要保护的代码fmt.Println("====start-1====")time.Sleep(3 * time.Second)fmt.Println(11111111)// 释放锁,删除自己创建的节点err = conn.Delete(lockNodePath, -1)if err != nil {fmt.Println("Delete: ", err.Error())return}fmt.Println("Released lock")}()go func() {conn, _, err := zk.Connect([]string{"xx.xx.xx.xx:2181"}, time.Second*5)if err != nil {fmt.Println("Connect:", err.Error())return}defer conn.Close()lockPath := "/locksObj"lockName := "lock"// 创建锁的根节点_, err = conn.Create(lockPath, []byte{}, int32(0), zk.WorldACL(zk.PermAll))if err != nil && err != zk.ErrNodeExists {fmt.Println("Create:", err.Error())return}// 获取锁lockNodePath, err := conn.CreateProtectedEphemeralSequential(lockPath+"/"+lockName+"-", []byte{}, zk.WorldACL(zk.PermAll))if err != nil {fmt.Println("CreateProtectedEphemeralSequential:", err.Error())return}for {children, _, err := conn.Children(lockPath)if err != nil {fmt.Println("Children:", err.Error())return}// 对子节点按照序列号进行排序sortChildren(children)// 检查自己创建的节点是否是第一个节点if lockNodePath == lockPath+"/"+children[0] {// 获取到了锁fmt.Println("Acquired lock")break}// 监听前一个节点的删除事件exists, _, watch, err := conn.ExistsW(lockPath + "/" + children[0])if err != nil {fmt.Println("ExistsW: ", err.Error())break}if !exists {// 前一个节点已删除,再次检查自己创建的节点是否是第一个节点children, _, err = conn.Children(lockPath)if err != nil {fmt.Println("Children: ", err.Error())break}sortChildren(children)if lockNodePath == lockPath+"/"+children[0] {// 获取到了锁fmt.Println("Acquired lock")break}}// 等待前一个节点的删除事件<-watch}// 执行需要保护的代码fmt.Println("====start-2====")time.Sleep(5 * time.Second)fmt.Println(22222222)// 释放锁,删除自己创建的节点err = conn.Delete(lockNodePath, -1)if err != nil {fmt.Println("Delete: ", err.Error())return}fmt.Println("Released lock")}()time.Sleep(10 * time.Second)
}
zookeeper 怎么实现 分布式锁的

zookeeper 会建立一个长链接,监听锁对象节点的状态和事件
ETCD实现 分布式锁
简单实现
package mainimport ("context""fmt"clientv3 "go.etcd.io/etcd/client/v3""time"
)func main() {go func() {config := clientv3.Config{Endpoints: []string{"xx.xx.xx.xx:2379"},DialTimeout: 5 * time.Second,}// 获取客户端连接client, err := clientv3.New(config)if err != nil {fmt.Println(err)return}// 上锁// 用于申请租约lease := clientv3.NewLease(client)// 申请一个10s的租约leaseGrantResp, err := lease.Grant(context.TODO(), 10) //10sif err != nil {fmt.Println(err)return}// 拿到租约的idleaseID := leaseGrantResp.IDctx, cancelFunc := context.WithCancel(context.TODO())// 停止defer cancelFunc()// 确保函数退出后,租约会失效defer lease.Revoke(context.TODO(), leaseID)// 自动续租keepRespChan, err := lease.KeepAlive(ctx, leaseID)if err != nil {fmt.Println(err)return}// 处理续租应答的协程go func() {select {case keepResp := <-keepRespChan:if keepRespChan == nil {fmt.Println("lease has expired")break} else {// 每秒会续租一次fmt.Println("收到自动续租应答", keepResp.ID)}}}()// if key 不存在,then设置它,else抢锁失败kv := clientv3.NewKV(client)// 创建事务txn := kv.Txn(context.TODO())// 如果key不存在txn.If(clientv3.Compare(clientv3.CreateRevision("/lockObj/lock/job"), "=", 0)).Then(clientv3.OpPut("/lockObj/lock/job", "", clientv3.WithLease(leaseID))).Else(clientv3.OpGet("/lockObj/lock/job")) //如果key存在// 提交事务txnResp, err := txn.Commit()if err != nil {fmt.Println(err)return}// 判断是否抢到了锁if !txnResp.Succeeded {fmt.Println("锁被占用了:", string(txnResp.Responses[0].GetResponseRange().Kvs[0].Value))return}// 处理业务fmt.Println("======work======")time.Sleep(5 * time.Second)fmt.Println("======END======")}()time.Sleep(20 * time.Second)
}
实现原理
etcd 支持以下功能,正是依赖这些功能来实现分布式锁的:
- Lease机制:即租约机制(TTL,Time To Live),etcd可以为存储的kv对设置租约,当租约到期,kv将失效删除;同时也支持续约,keepalive
- Revision机制:每个key带有一个Revision属性值,etcd每进行一次事务对应的全局Revision值都会+1,因此每个key对应的Revision属性值都是全局唯一的。通过比较Revision的大小就可以知道进行写操作的顺序
- 在实现分布式锁时,多个程序同时抢锁,根据Revision值大小依次获得锁,避免“惊群效应”,实现公平锁
- Prefix机制:也称为目录机制,可以根据前缀获得该目录下所有的key及其对应的属性值
- watch机制:watch支持watch某个固定的key或者一个前缀目录,当watch的key发生变化,客户端将收到通知
执行流程
- 步骤 1: 准备
客户端连接 Etcd,以 /lock/mylock 为前缀创建全局唯一的 key,假设第一个客户端对应的 key="/lock/mylock/UUID1",第二个为 key="/lock/mylock/UUID2";客户端分别为自己的 key 创建租约 - Lease,租约的长度根据业务耗时确定,假设为 15s;
- 步骤 2: 创建定时任务作为租约的“心跳”
当一个客户端持有锁期间,其它客户端只能等待,为了避免等待期间租约失效,客户端需创建一个定时任务作为“心跳”进行续约。此外,如果持有锁期间客户端崩溃,心跳停止,key 将因租约到期而被删除,从而锁释放,避免死锁。
- 步骤 3: 客户端将自己全局唯一的 key 写入 Etcd
进行 put 操作,将步骤 1 中创建的 key 绑定租约写入 Etcd,根据 Etcd 的 Revision 机制,假设两个客户端 put 操作返回的 Revision 分别为 1、2,客户端需记录 Revision 用以接下来判断自己是否获得锁。
- 步骤 4: 客户端判断是否获得锁
客户端以前缀 /lock/mylock 读取 keyValue 列表(keyValue 中带有 key 对应的 Revision),判断自己 key 的 Revision 是否为当前列表中最小的,如果是则认为获得锁;否则监听列表中前一个 Revision 比自己小的 key 的删除事件,一旦监听到删除事件或者因租约失效而删除的事件,则自己获得锁。
- 步骤 5: 执行业务
获得锁后,操作共享资源,执行业务代码。
- 步骤 6: 释放锁
完成业务流程后,删除对应的key释放锁。
扩展
马丁·克莱普曼 对 分布式锁以及对redlock的看法
分布式锁的目的是确保在可能尝试执行同一工作的多个节点中,只有一个节点实际执行该操作(至少一次只有一个)。
主要有两个功能:
1- 效率:使用锁可以避免不必要地重复相同的工作,多执行一次也无妨,只要最终正确就行
2- 正确性:锁定可以防止并发进程互相干扰并扰乱系统状态。如果锁定失败并且两个节点同时处理同一数据,则会导致文件损坏、数据丢失、永久不一致。
马丁认为锁在分布式系统使用会碰到以下三类问题:
1- 网络延迟:您可以保证数据包始终在某个保证的最大延迟内到达
2- GC问题: 导致锁无法续期等等问题
3- 时钟飘移:依赖于时钟的就容易出现问题
马丁认为redlock 强依赖于时钟,节点之间时钟不对,会使锁不可靠:
假设系统有五个 Redis 节点(A、B、C、D 和 E)和两个客户端(1 和 2)。如果其中一个 Redis 节点上的时钟向前跳动,会发生什么情况?
- 客户端 1 获取节点 A、B、C 上的锁。由于网络问题,无法访问 D 和 E。
- 节点C上的时钟向前跳跃,导致锁过期。
- 客户端2获取节点C、D、E上的锁。由于网络问题,无法访问A和B。
- 客户 1 和 2 现在都相信他们持有锁。
如果 C 在将锁持久保存到磁盘之前崩溃并立即重新启动,则可能会发生类似的问题。因此,Redlock 文档建议延迟重新启动崩溃的节点,至少要延迟最长寿命锁的生存时间。但这种重新启动延迟再次依赖于对时间的相当准确的测量,并且如果时钟跳跃就会失败。
马丁提出了 fencing token 方案

客户端 1 获取租约并获得令牌 33,但随后它进入长时间暂停状态并且租约到期。客户端 2 获取租约,获取令牌 34(数字始终增加),然后将其写入发送到存储服务,包括 34 的令牌。稍后,客户端 1 恢复正常并将其写入发送到存储服务,包括其令牌值 33。但是,存储服务器记得它已经处理了具有更高令牌编号 (34) 的写入,因此它拒绝具有令牌 33 的请求。
总结
分布式锁不是百分百安全,我们要根据实际使用情况来考虑锁的使用(解决效率问题还是正确行问题),在使用分布式锁的时候我们需要考虑锁的续期,锁归属,集群数据一致性,操作原子性,GC,时钟飘逸,网络延迟等等的问题。在cap 理论里, redis保证了ap, zk和etcd保证cp ,所以实际使用中根据业务的情况,选择redis/zk/etcd之一来实现分布式锁。
相关文章:
从redis setnx 来看看分布式锁
什么是分布式锁 分布式锁(多服务共享锁)在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行访问/操作。 为什么需要分布式锁 在单体应用服务里,不同的客户端操作同一个资源,我们可以通过操作系统提供…...
校园网网络规划与设计——计算机网络实践报告
W...Y的主页 😊 代码仓库分享💕 目录 一、设计目的 二、软硬件环境 三、理论基础 四、设计方案 五、网络配置步骤 六、设计过程中出现的问题及相应解决办法 八、参考资料 一、设计目的 深入理解网络工程的三层层次设计模型; 掌握网络…...
Qt QScrollArea 不显示滚动条 不滚动
使用QScrollArea时,发现添加的控件超出QScrollArea 并没有显示,且没有滚动条效果 原因是 scrollArea指的是scrollArea控件本身的大小,肉眼能看到的外形尺寸。 scrollAreaWidgetContents指的是scrollArea控件内部的显示区域,里面可…...
【SVN在Linux下的常用指令】
windows下的TortoiseSVN是资源管理器的一个插件,以覆盖图标表示文件状态,几乎所以命令都有图形界面支持,比较好用,这里就不多说。主要说说linux下svn的使用,因为linux下大部分的操作都是通过命令行来进行,所…...
2024 高级前端面试题之 Node 「精选篇」
该内容主要整理关于 Node 模块的相关面试题,其他内容面试题请移步至 「最新最全的前端面试题集锦」 查看。 Node模块精选篇 1. package.json版本号规则2. package.json 与 package-lock.json 的关3. npm 模块安装机制4. 模块化的差异 AMD CMD COMMONJS ESMODUL5. No…...
linux麒麟系统安装mongodb7.0
1.mogedb下载 下载的是他tar包 https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel80-7.0.5.tgz wget -o https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel80-7.0.5.tgz 也可以下载rpm包 2.将包上传至服务器并解压 #进入目录 并解压 cd /opt/ tar…...
Spring声明式事务
1.概念 事务就是用户定义的一系列执行SQL语句的操作, 这些操作要么完全地执行,要么完全地都不执行, 它是一个不可分割的工作执行单元 一个使用Mybatis-Spring的主要原因是它允许Mybatis参与到Spring的事务管理中,而不是给Mybatis创建一个新的…...
PyTorch深度学习实战(34)——Pix2Pix详解与实现
PyTorch深度学习实战(34)——Pix2Pix详解与实现 0. 前言1. 模型与数据集1.1 Pix2Pix 基本原理1.2 数据集分析1.3 模型构建策略 2. 实现 Pix2Pix 生成图像小结系列链接 0. 前言 Pix2Pix 是基于生成对抗网络 (Convolutional Generative Adversarial Netwo…...
第96讲:MySQL高可用集群MHA的核心概念以及集群搭建
文章目录 1.MHA高可用数据库集群的核心概念1.1.主从复制架构的演变1.2.MHA简介以及架构1.3.MHA的软件结构1.4.MHA Manager组件的启动过程1.5.MHA高可用集群的原理 2.搭建MHA高可用数据库集群2.1.环境架构简介2.2.搭建基于GTID的主从复制集群2.2.1.在三台服务器中分别搭建MySQL实…...
外星人入侵(python)
前言 代码来源《python编程从入门到实践》Eric Matthes 署 袁国忠 译 使用软件:PyCharm Community Editor 2022 目的:记录一下按照书上敲的代码 alien_invasion.py 游戏的一些初始化设置,调用已经封装好的函数方法,一个函数的…...
Unity中开发程序打包发布
添加ESC脚本 使用Unity打包发布的过程中,考虑到打开的程序会处于全屏界面,而此时我们又会有退出全屏的需求,因此需要添加ESC脚本,当我们单击ESC脚本的过程中,退出全屏模式。 在Assets/Scenes下,创建esc.cs…...
2024.2.1日总结
web的运行原理: 用户通过浏览器发送HTTP请求到服务器(网页操作)。web服务器接收到用户特定的HTTP请求,由web服务器请求信息移交给在web服务器中部署的javaweb应用程序(Java程序)。启动javaweb应用程序执行…...
LeetCode解法汇总2670. 找出不同元素数目差数组
目录链接: 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目: https://github.com/September26/java-algorithms 原题链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 描述: 给你一个下…...
STM32目录结构
之前一直头疼的32目录,比51复杂,又没有C规律,也不像python脚本文件关联不强,也不像工整的FPGA工程,编的时候到处放,爆出的错千奇百怪。短暂整理了一个,还是没有理得很轻。 startup_stm32f10x_m…...
算法专题:记忆搜索
参考练习习题总集 文章目录 前置知识练习习题87. 扰乱字符串97. 交错字符串375. 猜数字大小II403. 青蛙过河464. 我能赢吗494. 目标和552. 学生出勤记录II576. 出借的路径数 前置知识 没有什么特别知识,只有一些做题经验。要做这类型的题目,首先写出暴…...
【数据分享】1929-2023年全球站点的逐日最低气温数据(Shp\Excel\免费获取)
气象数据是在各项研究中都经常使用的数据,气象指标包括气温、风速、降水、湿度等指标,其中又以气温指标最为常用!说到气温数据,最详细的气温数据是具体到气象监测站点的气温数据! 之前我们分享过1929-2023年全球气象站…...
2024美赛数学建模D题思路+模型+代码+论文(持续更新)
2024美赛数学建模A题B题C题D题E题F题思路模型代码论文:开赛后第一时间更新,获取见文末名片 组队环节: 美赛最多是3个人参赛,一般的队伍都是由三人组成(当然如果你很大佬也可以一个人参赛),队伍…...
dubbo+sentinel最简集成实例
说明 在集成seata后,下面来集成sentinel进行服务链路追踪管理~ 背景 sample-front网关服务已配置好 集成 一、启动sentinel.jar 1、官网下载 选择1:在本地启动 nohup java -Dserver.port8082 -Dcsp.sentinel.dashboard.serverlocalhost:8082 -Dp…...
9.2爬楼梯(LC70-E)
算法: 多举几个例子,找规律: 爬到第一层楼梯有一种方法,爬到二层楼梯有两种方法。 那么第一层楼梯再跨两步就到第三层 ,第二层楼梯再跨一步就到第三层(时序)。 所以到第三层楼梯的状态可以由…...
Asp.net移除Server, X-Powered-By, 和X-AspNet-Version头
移除X-AspNet-Version很简单,只需要在Web.config中增加这个配置节: <httpRuntime enableVersionHeader"false" />移除Server在Global.asax文件总增加: //隐藏IIS版本 protected void Application_PreSendRequestHeaders() {HttpContext.Current.Res…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
基于SpringBoot在线拍卖系统的设计和实现
摘 要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统,主要的模块包括管理员;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...
GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...
