分布式锁:不同实现方式实践测评(上)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
云数据库 RDS PostgreSQL,高可用系列 2核4GB
简介: 分布式锁:不同实现方式实践测评(上)

Hello读者朋友们,今天打算分享一篇测评实践类的文章,用优雅的代码与真实的数据来讲述在分布式场景下,不同方式实现的分布式锁,分别探究每一种方式的性能情况与最终的优劣分析。

开门见山,我们先看一张表格,是由Jmeter测试生成的数据结果,现在不必看懂,本篇文章会从业务场景说起,到分布式锁的引入与分析,相信读完全文后大家就可以融会贯通。

网络异常,图片无法展示
|


1 总体描述与业务实现

说到分布式锁,我们就会想到分布式架构设计的场景,想到分布式架构设计,一般就认定他是为了高并发高负载的业务而起,想到高并发高负载的业务,我们首先可以想到的就是电商平台的秒杀商品场景,没错,通过逆向推导,我们本次的主要业务就是一个电商平台秒杀商品的场景,在此场景下,我们使用分布式锁用来防止商品超卖这个问题,当然此场景下还有诸多需要解决的问题,但是其余我们在此不会涉及。

1.1 场景描述

本次场景就是一个超级简易版的电商秒杀后端系统,大量用户同时购买库存有限的商品,主要流程就是:

用户进行商品购买,商品系统首先查询库存量,足够时则去到数据库进行库存扣减,而后生成通知订单系统生成订单,最终返回给用户,而库存不够时则直接返回用户提示购买失败,不会经过订单系统,所以正常情况下库存量的数量和生成的最大订单量需一致。

因此,我们重点关注的问题就是如何在超大请求量的情况下防止出现超卖现象

库存足够:

网络异常,图片无法展示
|


库存不足:

网络异常,图片无法展示
|


1.2 业务代码实现

安装上面的场景描述,我们使用Go语言+MySQL数据库进行简单的实现:

数据访问层代码:

package dao
import (
   "database/sql"
   "fmt"
   _ "github.com/go-sql-driver/mysql"
)
type Book struct {
   Id    int64  `json:"id"`
   Name  string `json:"name"`
   Price int32  `json:"price"`
   Store int64  `json:"store"`
}
type BookDao struct {
   db     *sql.DB
   tbName string
}
func NewBookDao() *BookDao {
   mysqlDB, err := sql.Open("mysql", "root:12345@tcp(127.0.0.1:3306)/test?charset=utf8")
   if err != nil {
      fmt.Errorf("Open mysql connection is err  %s", err)
   }
   return &BookDao{db: mysqlDB, tbName: "book"}
}
func (dao *BookDao) GetBookById(id int64) (Book, error) {
   var book Book
   querySql := fmt.Sprintf("SELECT id,name,price,store FROM %s WHERE id=%d", dao.tbName, id)
   rows, err := dao.db.Query(querySql)
   if err != nil {
      fmt.Errorf("GetBookById Query err %s", err)
      return Book{}, err
   }
   if rows.Next() {
      err = rows.Scan(&book.Id, &book.Name, &book.Price, &book.Store)
      if err != nil {
         fmt.Errorf("GetBookById Scan err %s", err)
         return Book{}, err
      }
   }
   return book, nil
}
func (dao *BookDao) UpdateBookStoreById(book Book) error {
   updateSql := fmt.Sprintf("UPDATE %s SET name=?,price=?,store=? WHERE id=?", dao.tbName)
   stmt, err := dao.db.Prepare(updateSql)
   if err != nil {
      fmt.Errorf("UpdateBookStoreById Prepare err %s", err)
      return err
   }
   _, err = stmt.Exec(book.Name, book.Price, book.Store, book.Id)
   return err
}
复制代码

业务层代码:

package service
import (
   "fmt"
   "lock_demo/dao"
)
var orderCount int32 //记录订单数量
func BuyBook(userName string, buyNum int64) string {
   bookDao := dao.NewBookDao()
   book, err := bookDao.GetBookById(1)
   if err != nil {
      fmt.Errorf("GetBookById err %s", err)
      return userName + "购买失败"
   }
   if book.Store >= buyNum && book.Store > 0 {
      book.Store = book.Store - buyNum
      err := bookDao.UpdateBookStoreById(book)
      if err != nil {
         fmt.Errorf("UpdateBookStoreById err %s", err)
         return userName + "购买失败"
      }
      orderCount += 1
      fmt.Printf("===%s 购买成功!数量:%d,剩余:%d,订单量:%d ===\n", userName, buyNum, book.Store, orderCount)
      return userName + "购买成功"
   }
   fmt.Printf("===%s 购买失败!数量:%d,剩余:%d,数量不足===\n", userName, buyNum, book.Store)
   return userName + "购买失败"
}
复制代码

接口层代码:

package main
import (
   "fmt"
   "github.com/spf13/cast"
   "lock_demo/service"
   "net/http"
)
func RunServer() {
   http.HandleFunc("/buyBook", func(w http.ResponseWriter, r *http.Request) {
      username := r.URL.Query().Get("name")
      buyNum := r.URL.Query().Get("num")
      resp := service.BuyBook(username, cast.ToInt64(buyNum))
      _, err := w.Write([]byte(resp))
      if err != nil {
         fmt.Errorf("write err %s", err)
      }
   })
   err := http.ListenAndServe(":8081", nil)
   if err != nil {
      fmt.Errorf("Http run err %s ", err)
   }
}
func main() {
   RunServer()
}
复制代码

项目需要添加的依赖(包括将要使用的分布式锁的依赖):

github.com/go-redis/redis/v8 v8.11.4
   github.com/go-redsync/redsync/v4 v4.6.0
   github.com/go-sql-driver/mysql v1.6.0
   github.com/go-zookeeper/zk v1.0.3
   github.com/spf13/cast v1.5.0
   go.etcd.io/etcd/client/v3 v3.5.5
复制代码

数据库:

CREATE TABLE `book`(
   `id` int(11) NOT NULL,
   `name` varchar(255) NOT NULL,
   `price` double NOT NULL,
   `store` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into book value(1,'Go学习笔记',299,100);
复制代码

1.3 问题的产生

为了更直观的看到问题所在,我们先不加锁,启动项目,访问地址:

GET http://localhost:8081/buyBook
复制代码

加上必要的请求参数

GET http://localhost:8081/buyBook?name=BarryYan&num=1
复制代码

下面我们打开Jmeter工具,新建测试计划,在测试计划中新建线程组和Http请求,我们的参数为20线程循环100次,由上面的SQL语句我们知道,商品的库存只有1000,但是20*100次的请求会有2000的请求量,去抢购1000个商品,正常来讲一定是有1000个用户因为库存不足买不到商品:

网络异常,图片无法展示
|


接下来我们启动Jmeter,查看运行项目的控制台,如下图:

网络异常,图片无法展示
|

等Jmeter执行完成后我们看一下MySQL中还有没有库存:

网络异常,图片无法展示
|


大家可以看到,20*100个请求过后,我们的库存还剩余712,并且订单量也是不正确的,这个问题就是典型的高并发下线程不安全导致的超卖问题。

1.4 分析问题的原因

简述问题的原因,就是在多线程情况下资源被每个线程都获取一份相同的实例,而后在更新库存时每个线程都更新自身操作后的数据,进而每个线程执行过后产生了相同的数据结果,导致了操作多次后数量未发生改变,举个例子:

(1)当前库存900

(2)用户A在D1时间访问数据库看到库存900,用户B在D1时间访问数据库看到库存900

(3)用户A购买1个,库存剩余900-1=899,修改数据库库存为899,用户B也购买1个,库存剩余900-1=899,修改数据库库存为899

(4)此时,数据库剩余库存为899

但是用户A和用户B都获得了订单,库存却只减了1,那么如果D1时间点还有用户C、D、E同时购买呢?结果我们不得而知。


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
相关文章
|
5月前
|
人工智能 安全 应用服务中间件
阿里巴巴 MCP 分布式落地实践:快速转换 HSF 到 MCP server
本文分享了阿里巴巴内部将大规模HSF服务快速转换为MCP Server的实践经验,通过Higress网关实现MCP协议卸载,无需修改代码即可接入MCP生态。文章分析了MCP生态面临的挑战,如协议快速迭代和SDK不稳定性,并详细介绍了操作步骤及组件功能。强调MCP虽非终极解决方案,但作为AI业务工程化的起点具有重要意义。最后总结指出,MCP只是AI原生应用发展的第一步,未来还有更多可能性值得探索。
1047 48
|
18天前
|
消息中间件 缓存 监控
中间件架构设计与实践:构建高性能分布式系统的核心基石
摘要 本文系统探讨了中间件技术及其在分布式系统中的核心价值。作者首先定义了中间件作为连接系统组件的"神经网络",强调其在数据传输、系统稳定性和扩展性中的关键作用。随后详细分类了中间件体系,包括通信中间件(如RabbitMQ/Kafka)、数据中间件(如Redis/MyCAT)等类型。文章重点剖析了消息中间件的实现机制,通过Spring Boot代码示例展示了消息生产者的完整实现,涵盖消息ID生成、持久化、批量发送及重试机制等关键技术点。最后,作者指出中间件架构设计对系统性能的决定性影响,
|
5月前
|
监控 Linux 应用服务中间件
Linux多节点多硬盘部署MinIO:分布式MinIO集群部署指南搭建高可用架构实践
通过以上步骤,已成功基于已有的 MinIO 服务,扩展为一个 MinIO 集群。该集群具有高可用性和容错性,适合生产环境使用。如果有任何问题,请检查日志或参考MinIO 官方文档。作者联系方式vx:2743642415。
1423 57
|
5月前
|
安全 JavaScript 前端开发
HarmonyOS NEXT~HarmonyOS 语言仓颉:下一代分布式开发语言的技术解析与应用实践
HarmonyOS语言仓颉是华为专为HarmonyOS生态系统设计的新型编程语言,旨在解决分布式环境下的开发挑战。它以“编码创造”为理念,具备分布式原生、高性能与高效率、安全可靠三大核心特性。仓颉语言通过内置分布式能力简化跨设备开发,提供统一的编程模型和开发体验。文章从语言基础、关键特性、开发实践及未来展望四个方面剖析其技术优势,助力开发者掌握这一新兴工具,构建全场景分布式应用。
496 35
|
6月前
|
存储 负载均衡 测试技术
ACK Gateway with Inference Extension:优化多机分布式大模型推理服务实践
本文介绍了如何利用阿里云容器服务ACK推出的ACK Gateway with Inference Extension组件,在Kubernetes环境中为多机分布式部署的LLM推理服务提供智能路由和负载均衡能力。文章以部署和优化QwQ-32B模型为例,详细展示了从环境准备到性能测试的完整实践过程。
|
7月前
|
并行计算 PyTorch 算法框架/工具
融合AMD与NVIDIA GPU集群的MLOps:异构计算环境中的分布式训练架构实践
本文探讨了如何通过技术手段混合使用AMD与NVIDIA GPU集群以支持PyTorch分布式训练。面对CUDA与ROCm框架互操作性不足的问题,文章提出利用UCC和UCX等统一通信框架实现高效数据传输,并在异构Kubernetes集群中部署任务。通过解决轻度与强度异构环境下的挑战,如计算能力不平衡、内存容量差异及通信性能优化,文章展示了如何无需重构代码即可充分利用异构硬件资源。尽管存在RDMA验证不足、通信性能次优等局限性,但该方案为最大化GPU资源利用率、降低供应商锁定提供了可行路径。源代码已公开,供读者参考实践。
494 3
融合AMD与NVIDIA GPU集群的MLOps:异构计算环境中的分布式训练架构实践
|
7月前
|
人工智能 运维 监控
领先AI企业经验谈:探究AI分布式推理网络架构实践
当前,AI行业正处于快速发展的关键时期。继DeepSeek大放异彩之后,又一款备受瞩目的AI智能体产品Manus横空出世。Manus具备独立思考、规划和执行复杂任务的能力,其多智能体架构能够自主调用工具。在GAIA基准测试中,Manus的性能超越了OpenAI同层次的大模型,展现出卓越的技术实力。
|
9月前
|
分布式计算 DataWorks 大数据
分布式Python计算服务MaxFrame测评
一文带你入门分布式Python计算服务MaxFrame
167 23
分布式Python计算服务MaxFrame测评
|
9月前
|
数据采集 人工智能 分布式计算
MaxFrame:链接大数据与AI的高效分布式计算框架深度评测与实践!
阿里云推出的MaxFrame是链接大数据与AI的分布式Python计算框架,提供类似Pandas的操作接口和分布式处理能力。本文从部署、功能验证到实际场景全面评测MaxFrame,涵盖分布式Pandas操作、大语言模型数据预处理及企业级应用。结果显示,MaxFrame在处理大规模数据时性能显著提升,代码兼容性强,适合从数据清洗到训练数据生成的全链路场景...
385 5
MaxFrame:链接大数据与AI的高效分布式计算框架深度评测与实践!
|
9月前
|
存储 运维 安全
盘古分布式存储系统的稳定性实践
本文介绍了阿里云飞天盘古分布式存储系统的稳定性实践。盘古作为阿里云的核心组件,支撑了阿里巴巴集团的众多业务,确保数据高可靠性、系统高可用性和安全生产运维是其关键目标。文章详细探讨了数据不丢不错、系统高可用性的实现方法,以及通过故障演练、自动化发布和健康检查等手段保障生产安全。总结指出,稳定性是一项系统工程,需要持续迭代演进,盘古经过十年以上的线上锤炼,积累了丰富的实践经验。
644 7

热门文章

最新文章