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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: 分布式锁:不同实现方式实践测评(上)

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同时购买呢?结果我们不得而知。


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
16天前
|
分布式计算 负载均衡 并行计算
Python 分布式计算框架 PP (Parallel Python):集群模式下的实践探索
该文介绍了使用Parallel Python (PP) 在两台物理机上构建分布式计算集群的经验。PP是一个轻量级框架,旨在简化Python代码在多处理器系统和集群中的并行执行。文中通过设置子节点的IP、端口和密钥启动PP服务器,并在主节点创建PP实例进行负载均衡。实验使用官方的质数和计算示例,显示PP在集群模式下能有效利用多台机器的多核CPU,实现计算效率的显著提升。未来,作者计划进一步研究PP在更复杂任务和大规模集群中的应用潜力。
|
23天前
|
监控 NoSQL 数据建模
使用Apache Cassandra进行分布式数据库管理的技术实践
【6月更文挑战第5天】本文探讨了使用Apache Cassandra进行分布式数据库管理的技术实践。Cassandra是一款高性能、可扩展的NoSQL数据库,适合大规模、高并发场景。文章介绍了其高可扩展性、高性能、高可用性和灵活数据模型等核心特性,并详细阐述了环境准备、安装配置、数据建模与查询以及性能优化与监控的步骤。通过本文,读者可掌握Cassandra的运用,适应不断增长的数据需求。
|
13天前
|
存储 监控 负载均衡
Zookeeper 详解:分布式协调服务的核心概念与实践
Zookeeper 详解:分布式协调服务的核心概念与实践
14 0
|
1月前
|
Cloud Native 数据管理 关系型数据库
【阿里云云原生专栏】云原生数据管理:阿里云数据库服务的分布式实践
【5月更文挑战第21天】阿里云数据库服务在云原生时代展现优势,应对分布式数据管理挑战。PolarDB等服务保证高可用和弹性,通过多副本机制和分布式事务确保数据一致性和可靠性。示例代码展示了在阿里云数据库上进行分布式事务操作。此外,丰富的监控工具协助用户管理数据库性能,支持企业的数字化转型和业务增长。
196 1
|
1月前
|
分布式计算 并行计算 Java
【分布式计算框架】 MapReduce编程初级实践
【分布式计算框架】 MapReduce编程初级实践
34 2
|
1月前
|
存储 Java 分布式数据库
【分布式计算框架】HBase数据库编程实践
【分布式计算框架】HBase数据库编程实践
35 1
|
1月前
|
分布式计算 数据可视化 Hadoop
【分布式计算框架】HDFS常用操作及编程实践
【分布式计算框架】HDFS常用操作及编程实践
28 1
|
1月前
|
存储 大数据 Apache
深入理解ZooKeeper:分布式协调服务的核心与实践
【5月更文挑战第7天】ZooKeeper是Apache的分布式协调服务,确保大规模分布式系统中的数据一致性与高可用性。其特点包括强一致性、高可用性、可靠性、顺序性和实时性。使用ZooKeeper涉及安装配置、启动服务、客户端连接及执行操作。实际应用中,面临性能瓶颈、不可伸缩性和单点故障等问题,可通过水平扩展、集成其他服务和多集群备份来解决。理解ZooKeeper原理和实践,有助于构建高效分布式系统。
|
1月前
|
缓存 分布式计算 负载均衡
Java分布式系统设计与实践
Java分布式系统设计与实践
64 0
|
1月前
|
分布式计算 并行计算 数据处理
NumPy的并行与分布式计算实践
【4月更文挑战第17天】本文探讨了如何使用NumPy进行并行和分布式计算以提升效率。介绍了利用`numexpr`加速多核CPU计算,设置`NUMPY_NUM_THREADS`环境变量实现多线程,并通过Dask和PySpark进行分布式计算。Dask允许无缝集成NumPy,而PySpark则将NumPy数组转换为RDD进行并行处理。这些方法对处理大规模数据至关重要。

热门文章

最新文章