前言

  项目初期的时候,一般会发布到一台主机上,当达到负载极限时,要想提升其性能,要么提升硬件,要么多台主机,然而成本上的花销,后者比前者便宜太多了,虽然便宜,但是却更加复杂。

  大多数编程语言提供的各种锁只会对同一项目的同一主机的代码产生作用,当同一项目发布在多台主机的时候,这些主机中的项目要形成一个整体,因此原先同步访问共享资源的代码将会失去效果。

  由于共享资源多种多样,如:文件、业务的临时状态、数据库数据等,本章的同步锁主要解决的是不依赖于主机环境的共享资源,如:数据库数据;而共享资源依赖于项目环境时,想要同步访问共享资源,则当某主机共享资源变动时,需要将其同步到其他主机,也就是集群服务器了,如果不想要搭建集群服务器,可将相应的功能剥离出来成为单一的项目,也就是分布式结构。

  由于后期必然会演变成分布式架构,而各个结构又是集群,因此如果当前情况下就把项目构架得太多复杂,投入再多的人力也是很难完成的,因此要先简化结构,一步步实现,至于先集群还是先分布,看个人喜好了。

实现

  实现的主要目标就是保证任意时刻,只能有一个线程可以得到操作的权利。

  首先来定义锁的接口,可以提供2个方法:Lock、Unlock,也可以只提供Lock,然后返回Unlock,如果Unlock为null则表示加锁失败。

  既然讲到唯一,如果不依赖其他的额外资源的情况下,很多人应该已经想到了,那就是数据库表的主键,因此实现思路就是加锁的时候向数据库中插入一条记录,那么成功插入的操作就获取到了锁,然后解锁时,删除这条记录即可,初步实现如下:

public delegate void UnlockDelegate();public UnlockDelegate Lock(string key)
{using (var conn = new SqlConnection(this.connectionString)){conn.Open();using (var cmd = new SqlCommand(string.Empty, conn)){try{var createdRows = Create(cmd, key);if (createdRows > 0){return () =>{DeleteById(cmd, key);conn.Close();};}}catch{conn.Close();}return null;}}
}private int Create(SqlCommand cmd, string key)
{cmd.Parameters.Clear();cmd.CommandText = this.insertSql;cmd.Parameters.AddWithValue("@id", key);return cmd.ExecuteNonQuery();
}private int DeleteById(SqlCommand cmd, string key)
{cmd.Parameters.Clear();cmd.CommandText = this.deleteSql;cmd.Parameters.AddWithValue("@id", key);return cmd.ExecuteNonQuery();
}

  项目运行过程当中没有绝对的安全,总有一些内因、外因导致项目出现错误,如果某个主机获取了锁以后,该主机因为某些原因没有释放锁,那么其他的主机将会无法再获取到该锁了。

  那么锁就需要一个过期时间,因此我们需要在表中增加一个表示锁的创建时间,那么在创建锁之前就需要先根据key去获取锁是否存在,如果存在且已经过期,那么删除该记录才能继续创建锁。

  该处的删除跟解锁时的删除是不一样的,因为在多线程、并发环境下,程序并不能保证只有唯一一个线程获取到了已存在的锁数据,有可能多个线程都获取到了锁数据,有的可能已经准备删除,而有的才刚刚获取到,因此此处的删除必须保证返回的影响行数大于0,否则直接返回null,重构后的代码如下:

public UnlockDelegate Lock(string key, int expires = 5)
{using (var conn = new SqlConnection(this.connectionString)){conn.Open();using (var cmd = new SqlCommand(string.Empty, conn)){var createdOn = GetCreatedOnById(cmd, key);if (createdOn > 0){var nowOn = DateTime.Now.ToUnix();if (nowOn - createdOn > expires){var deletedRow = DeleteById(cmd, key);if (deletedRow == 0){conn.Close();return null;}}}try{var createdRows = Create(cmd, key, expires);if (createdRows > 0){return () =>{DeleteById(cmd, key);conn.Close();};}}catch{conn.Close();}return null;}}
}

  由于DateTime并没有直接转换成时间戳的方法,因此该方法需要自己扩展,实现思路就是当前时间-1970年的总毫秒数,这里就不提供代码了,因为长时间都是依赖于orm来开发的,对sql已经很生疏了,因此各位要的是理解以上实现,不要太在意代码。

简化

  使用数据库来实现虽然代码量不多,但需要数据库的支持,连接字符串、表、字段都是可变的,如果不写死的话,就需要提供不少的配置。

  由于项目必然会使用到缓存,如:redis、memcache等高性能的缓存系统,而redis中提供了SetNX、Expires这样的api,如果基于redis实现的话,只要几行代码便可完成。

  相应的库可以去redis官网查询,这里的例子使用的是Sider,代码如下:

private ThreadwisePool pool;public RedisMutex(string host)
{this.pool = new ThreadwisePool(host);
}public UnlockDelegate Lock(string key, int expires = 5)
{var client = this.pool.GetClient();var ok = client.SetNX(key, string.Empty);if (!ok)return null;client.Expire(key, new TimeSpan(0, 0, expires));return () => client.Del(key);
}

结束语

  那么今天分享的文章就到这里了,如果代码有错误或者有问题的话,请留言,谢谢。

转载于:https://www.cnblogs.com/ahl5esoft/p/5103345.html

项目演化系列--分布式锁相关推荐

  1. mysql 分布式锁_【分布式锁的演化】分布式锁居然还能用MySQL?

    前言 之前的文章中通过电商场景中秒杀的例子和大家分享了单体架构中锁的使用方式,但是现在很多应用系统都是相当庞大的,很多应用系统都是微服务的架构体系,那么在这种跨jvm的场景下,我们又该如何去解决并发. ...

  2. 我的物联网项目(二十七) 分布式锁粗心导致大量阻塞

    有天项目中某个业务出现了异常,查询相关日志显示如下: ### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQ ...

  3. spring项目使用redis分布式锁解决重复提交问题

    场景演示 假设有一个录入学生信息的功能,为了便于演示,要求不能有重名的学生,并且数据库对应字段没有做唯一限制. @GetMapping("/student/{name}")publ ...

  4. 实战系列-分布式锁的Redis实现

    导语   本篇博客,博主使用本地的Docker 搭建了一套测试环境,用来手写一个属于自己的基于Redis分布式锁实现方案,通过自己实现来了解分布式锁的原理.并且对整个的构建过程做了分享,希望可以对大家 ...

  5. SpringCloud技术指南系列(十三)分布式锁之Redis实现(redisson)

    SpringCloud技术指南系列(十三)分布式锁之Redis实现(redisson) 一.概述 分布式锁是控制分布式系统之间同步访问共享资源的一种方式.在分布式系统中,常常需要协调他们的动作.如果不 ...

  6. 关于分布式锁的面试题都在这里了

    「我今天班儿都没上,就为了赶紧把这篇文章分布式锁早点写完.我真的不能再贴心了.」 「边喝茶边构思,你们可不要白嫖了!三连来一遍?」 引言 为什么要学习分布式锁? 最简单的理由就是作为一个社招程序员,面 ...

  7. 分布式锁中的王者方案:Redisson

    我们先来看下 Redis 官网对分布式锁的说法: 而 Java 版的 分布式锁的框架就是 Redisson. 本篇实战内容将会基于我的开源项目 PassJava 来整合 Redisson. 我把后端. ...

  8. zookeeper应用之分布式锁

    前一段时间有讨论过用redis来实现分布式锁,讲到setNx不是原子性.redis新的set方法及其误删和守护线程,还为了原子性不得不使用redis的脚本.虽然最终分布式锁的这个效果是实现了,但是,不 ...

  9. 面试精讲之面试考点及大厂真题 - 分布式专栏 23 分布式系统下分布式锁的实现

    23 分布式系统下分布式锁的实现 困难只能吓倒懦夫懒汉,而胜利永远属于敢于等科学高峰的人. --茅以升 引言 锁是开发过程中十分常见的工具,你一定不陌生,悲观锁,乐观锁,排它锁,公平锁,非公平锁等等, ...

最新文章

  1. 使用WinINet和WinHTTP实现Http访问
  2. coco与voc相互转化
  3. [转]云原生到底是什么?
  4. requestPermissions读写手机存储权限_泛圈云盘可为企业建立高效安全的云办公在线协同文档存储?...
  5. libnids校验和引起回放包不能正常捕捉
  6. DbVisualizer 12.0.* Ubuntu
  7. c++多线程——数据共享
  8. 《JavaScript高级程序设计(第3版)》阅读总结记录第一章之JavaScript简介
  9. 数据结构与算法必知基础知识
  10. 比起高性能计算,高端存储才更亟待国产化
  11. 导向滤波与opencv python实现
  12. BeanUtils.populate方法使用
  13. matlab错误的代码,matlab代码纠正错误
  14. 安搭Share为您推荐学理财投资必读的书籍
  15. PVCBOT【27号】机械避役--线控变色龙机器人
  16. 黄金思维圈,看透问题本质的利器,成功者必备工具
  17. 哈工大计算机学院统一复试划线,哈工大计算机专业,复试比例101%,擦线党没戏了...
  18. Python核心编程(一)
  19. Kepware与smart200建立连接
  20. 老马群控使用教程之连接设备【手机支架、集控器、USB线】

热门文章

  1. 技术小故事-Activity的Launch Mode引起的动画“疑案”
  2. oracle11g ora-27154 past/wait 错误解决方法
  3. 夯实基础开新局--全国乡镇党委换届工作进展顺利
  4. Flask成长笔记--在Flask中加密的方式
  5. c语言函数游戏,C语言做游戏常用到一些函数大全 2011.doc
  6. mysql 删除 修改密码_MySQL新建用户,授权,删除用户,修改密码
  7. [Java] 蓝桥杯ALGO-119 算法训练 寂寞的数
  8. [Java] 蓝桥杯ADV-166 算法提高 聪明的美食家
  9. mysql主主复制和mha_MySQL第二章主从复制MHA高可用
  10. mongodb查询的语法(大于,小于,大于或等于,小于或等于等等)