腾讯云:云数据库 TencentDB for Redis(腾讯云云数据库文档)(为什么需要分布式锁)

为什么需要分布式锁

第一步,自身的业务场景:在常做的项目中,目前涉及了以下这些业务场景:场景一: 比如分配任务场景。在这个场景中,由于是公司的业务后台系统,主要是用于审核人员的审核工作,并发量并不是很高,而且任务的分配规则设计成了通过审核人员每次主动的请求拉取,然后服务端从任务池中随机的选取任务进行分配。这个场景看到这里你会觉得比较单一,但是实际的分配过程中,由于涉及到了按用户聚类的问题,所以要比我描述的复杂,但是这里为了说明问题,大家可以把问题简单化理解。那么在使用过程中,主要是为了避免同一个任务同时被两个审核人员获取到的问题。我最终使用了基于数据库资源表的分布式锁来解决的问题。场景二: 比如支付场景。在这个场景中,我提供给用户三个用于保护用户隐私的手机号码(这些号码是从运营商处获取的,和真实手机号码看起来是一样的),让用户选择其中一个进行购买,用户购买付款后,我需要将用户选择的号码分配给用户使用,同时也要将没有选择的释放掉。在这个过程中,给用户筛选的号码要在一定时间内(用户筛选正常时间范围内)让当前用户对这个产品具有独占性,以便保证付款后是100%可以拿到;同时由于产品资源池的资源有限,还要保持资源的流动性,即不能让资源长时间被某个用户占用着。对于服务的设计目标,一期项目上线的时候至少能够支持峰值qps为300的请求,同时在设计的过程中要考虑到用户体验的问题。我最终使用了memecahed的add()方法和基于数据库资源表的分布式锁来解决的问题。场景三: 我有一个数据服务,每天调用量在3亿,每天按86400秒计算的qps在4000左右,由于服务的白天调用量要明显高于晚上,所以白天下午的峰值qps达到6000的,一共有4台服务器,单台qps要能达到3000以上。我最终使用了redis的setnx()和expire()的分布式锁解决的问题。场景四:场景一和场景二的升级版。在这个场景中,不涉及支付。但是由于资源分配一次过程中,需要保持涉及一致性的地方增加,而且一期的设计目标要达到峰值qps500,所以需要我们对场景进一步的优化。我最终使用了redis的setnx()、expire()和基于数据库表的分布式锁来解决的问题。看到这里,不管你觉得我提出的业务场景qps是否足够大,都希望你能继续看下去,因为无论你身处一个什么样的公司,最开始的工作可能都需要从最简单的做起。不要提阿里和腾讯的业务场景qps如何大,因为在这样的大场景中你未必能亲自参与项目,亲自参与项目未必能是核心的设计者,是核心的设计者未必能独自设计。如果能真能满足以上三条,关闭页面可以不看啦,如果不是的话,建议还是看完,我有说的不足的地方欢迎提出建议,我说的好的地方,也希望给我点个赞或者评论一下,算是对我最大的鼓励哈。  第二步,分布式锁的解决. 首先明确一点,有人可能会问是否可以考虑采用ReentrantLock来实现,但是实际上去实现的时候是有问题的,ReentrantLock的lock和unlock要求必须是在同一线程进行,而分布式应用中,lock和unlock是两次不相关的请求,因此肯定不是同一线程,因此导致无法使用ReentrantLock。2. 基于数据库表做乐观锁,用于分布式锁。3. 使用memcached的add()方法,用于分布式锁。4. 使用memcached的cas()方法,用于分布式锁。(不常用)5. 使用redis的setnx()、expire()方法,用于分布式锁。6. 使用redis的setnx()、get()、getset()方法,用于分布式锁。7. 使用redis的watch、multi、exec命令,用于分布式锁。(不常用)8. 使用zookeeper,用于分布式锁。(不常用)第三步,基于数据库资源表做乐观锁,用于分布式锁:1. 首先说明乐观锁的含义:大多数是基于数据版本(version)的记录机制实现的。何谓数据版本号?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个 “version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。在更新过程中,会对版本号进行比较,如果是一致的,没有发生改变,则会成功执行本次操作;如果版本号不一致,则会更新失败。2. 对乐观锁的含义有了一定的了解后,结合具体的例子,我们来推演下我们应该怎么处理:(1). 假设我们有一张资源表,如下图所示: t_resource , 其中有6个字段id, resoource, state, add_time, update_time, version,分别表示表主键、资源、分配状态(1未分配 2已分配)、资源创建时间、资源更新时间、资源数据版本号。(4). 假设我们现在我们对id=5780这条数据进行分配,那么非分布式场景的情况下,我们一般先查询出来state=1(未分配)的数据,然后从其中选取一条数据可以通过以下语句进行,如果可以更新成功,那么就说明已经占用了这个资源update t_resource set state=2 where state=1 and id=5780。(5). 如果在分布式场景中,由于数据库的update操作是原子是原子的,其实上边这条语句理论上也没有问题,但是这条语句如果在典型的“ABA”情况下,我们是无法感知的。有人可能会问什么是“ABA”问题呢?大家可以网上搜索一下,这里我说简单一点就是,如果在你第一次select和第二次update过程中,由于两次操作是非原子的,所以这过程中,如果有一个线程,先是占用了资源(state=2),然后又释放了资源(state=1),实际上最后你执行update操作的时候,是无法知道这个资源发生过变化的。也许你会说这个在你说的场景中应该也还好吧,但是在实际的使用过程中,比如银行账户存款或者扣款的过程中,这种情况是比较恐怖的。(6). 那么如果使用乐观锁我们如何解决上边的问题呢?a. 先执行select操作查询当前数据的数据版本号,比如当前数据版本号是26:select id, resource, state,version from t_resource where state=1 and id=5780;b. 执行更新操作:update t_resoure set state=2, version=27, update_time=now() where resource=xxxxxx and state=1 and version=26c. 如果上述update语句真正更新影响到了一行数据,那就说明占位成功。如果没有更新影响到一行数据,则说明这个资源已经被别人占位了。3. 通过2中的讲解,相信大家已经对如何基于数据库表做乐观锁有有了一定的了解了,但是这里还是需要说明一下基于数据库表做乐观锁的一些缺点:(1). 这种操作,使原本一次的update操作,必须变为2次操作: select版本号一次;update一次。增加了数据库操作的次数。(2). 如果业务场景中的一次业务流程中,多个资源都需要用保证数据一致性,那么如果全部使用基于数据库资源表的乐观锁,就要让每个资源都有一张资源表,这个在实际使用场景中肯定是无法满足的。而且这些都基于数据库操作,在高并发的要求下,对数据库连接的开销一定是无法忍受的。(3). 乐观锁机制往往基于系统中的数据存储逻辑,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整,如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开。4. 讲了乐观锁的实现,是不是会觉得不敢使用乐观锁了呢???当然不是,在文章开头我自己的业务场景中,场景1和场景2的一部分都使用了基于数据库资源表的乐观锁,已经很好的解决了线上问题。所以大家要根据的具体业务场景选择技术方案,并不是随便找一个足够复杂、足够新潮的技术方案来解决业务问题就是好方案?!比如,如果在我的场景一中,我使用zookeeper做锁,可以这么做,但是真的有必要吗???答案觉得是没有必要的!!!第四步,使用memcached的add()方法,用于分布式锁:对于使用memcached的add()方法做分布式锁,这个在互联网公司是一种比较常见的,而且基本上可以解决自己手头上的大部分应用场景。在使用这个方法之前,只要能搞明白memcached的add()和set()的区别,并且知道为什么能用add()方法做分布式锁就好。如果还不知道add()和set()方法,请直接百度吧,这个需要自己了解一下。我在这里想说明的是另外一个问题,人们在关注分布式锁设计的好坏时,还会重点关注这样一个问题,那就是是否可以避免死锁问题???!!!如果使用memcached的add()命令对资源占位成功了,那么是不是就完事儿了呢?当然不是!我们需要在add()的使用指定当前添加的这个key的有效时间,如果不指定有效时间,正常情况下,你可以在执行完自己的业务后,使用delete方法将这个key删除掉,也就是释放了占用的资源。但是,如果在占位成功后,memecached或者自己的业务服务器发生宕机了,那么这个资源将无法得到释放。所以通过对key设置超时时间,即便发生了宕机的情况,也不会将资源一直占用,可以避免死锁的问题。第五步,使用memcached的cas()方法,用于分布式锁:下篇文章我们再细说!第六步,使用redis的setnx()、expire()方法,用于分布式锁:对于使用redis的setnx()、expire()来实现分布式锁,这个方案相对于memcached()的add()方案,redis占优势的是,其支持的数据类型更多,而memcached只支持String一种数据类型。除此之外,无论是从性能上来说,还是操作方便性来说,其实都没有太多的差异,完全看你的选择,比如公司中用哪个比较多,你就可以用哪个。首先说明一下setnx()命令,setnx的含义就是SET if Not Exists,其主要有两个参数 setnx(key, value)。该方法是原子的,如果key不存在,则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0。但是要注意的是setnx命令不能设置key的超时时间,只能通过expire()来对key设置。具体的使用步骤如下:1. setnx(lockkey, 1) 如果返回0,则说明占位失败;如果返回1,则说明占位成功2. expire()命令对lockkey设置超时时间,为的是避免死锁问题。3. 执行完业务代码后,可以通过delete命令删除key。这个方案其实是可以解决日常工作中的需求的,但从技术方案的探讨上来说,可能还有一些可以完善的地方。比如,如果在第一步setnx执行成功后,在expire()命令执行成功前,发生了宕机的现象,那么就依然会出现死锁的问题,所以如果要对其进行完善的话,可以使用redis的setnx()、get()和getset()方法来实现分布式锁。 第七步,使用redis的setnx()、get()、getset()方法,用于分布式锁:这个方案的背景主要是在setnx()和expire()的方案上针对可能存在的死锁问题,做了一版优化。那么先说明一下这三个命令,对于setnx()和get()这两个命令,相信不用再多说什么。那么getset()命令?这个命令主要有两个参数 getset(key,newValue)。该方法是原子的,对key设置newValue这个值,并且返回key原来的旧值。假设key原来是不存在的,那么多次执行这个命令,会出现下边的效果:1. getset(key, “value1″) 返回nil 此时key的值会被设置为value12. getset(key, “value2″) 返回value1 此时key的值会被设置为value23. 依次类推!介绍完要使用的命令后,具体的使用步骤如下:1. setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向2。2. get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向3。3. 计算newExpireTime=当前时间+过期超时时间,然后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime。4. 判断currentExpireTime与oldExpireTime 是否相等,如果相等,说明当前getset设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。5. 在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行delete释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。第八步,使用redis的watch、multi、exec命令,用于分布式锁:下篇文章我们再细说!第九步,使用zookeeper,用于分布式锁:下篇文章我们再细说!第十步,总结:综上,关于分布式锁的第一篇文章我就写到这儿了,在文章中主要说明了日常项目中会比较常用到四种方案,大家掌握了这四种方案,其实在日常的工作中就可以解决很多业务场景下的分布式锁的问题。从文章开头我自己的实际使用中,也可以看到,这么说完全是有一定的依据。对于另外那三种方案,我会在下一篇关于分布式锁的文章中,和大家再探讨一下。常用的四种方案:1. 基于数据库表做乐观锁,用于分布式锁。2. 使用memcached的add()方法,用于分布式锁。3. 使用redis的setnx()、expire()方法,用于分布式锁。4. 使用redis的setnx()、get()、getset()方法,用于分布式锁。不常用但是可以用于技术方案探讨的:1. 使用memcached的cas()方法,用于分布式锁。2. 使用redis的watch、multi、exec命令,用于分布式锁。3. 使用zookeeper,用于分布式锁。

java大数据主要学习哪些知识

阶段一 JavaSE基础核心Java基础语法· 分支结构if/switch· 循环结构for/while/do while· 方法声明和调用· 方法重载· 数组的使用· 命令行参数、可变参数IDEA· IDEA常用设置、常用快捷键· 自定义模板· 关联Tomcat· Web项目案例实操面向对象编程· 封装、继承、多态、构造器、包· 异常处理机制· 抽象类、接口、内部类· 常有基础API、集合List/Set/Map· 泛型、线程的创建和启动· 深入集合源码分析、常见数据结构解析· 线程的安全、同步和通信、IO流体系· 反射、类的加载机制、网络编程Java8/9/10/11新特性· Lambda表达式、方法引用· 构造器引用、StreamAPI· jShell(JShell)命令· 接口的私有方法、Optional加强· 局部变量的类型推断· 更简化的编译运行程序等MySQL· DML语言、DDL语言、DCL语言· 分组查询、Join查询、子查询、Union查询、函数· 流程控制语句、事务的特点、事务的隔离级别等JDBC· 使用JDBC完成数据库增删改查操作· 批处理的操作· 数据库连接池的原理及应用· 常见数据库连接池C3P0、DBCP、Druid等阶段二 Hadoop生态体系架构Maven· Maven环境搭建· 本地仓库中央仓库· 创建Web工程· 自动部署Linux· VI/VIM编辑器· 系统管理操作远程登录· 常用命令· 软件包管理企业真题Shell编程· 自定义变量与特殊变量· 运算符· 条件判断· 流程控制· 系统函数自定义函数· 常用工具命令· 面试真题Hadoop· Hadoop生态介绍· Hadoop运行模式· 源码编译· HDFS文件系统底层详解· DNNN工作机制· HDFS的API操作· MapReduce框架原理· 数据压缩· Yarn工作机制· MapReduce案例详解· Hadoop参数调优· HDFS存储多目录· 多磁盘数据均衡· LZO压缩· Hadoop基准测试Zookeeper· Zookeeper数据结果· 内部原理· 选举机制· Stat结构体· 器· 分布式安装部署· API操作· 实战案例· 面试真题· 启动停止脚本HA+新特性· HDFS-HA集群配置Hive· Hive架构原理· 安装部署· 远程连接· 常见命令及基本数据类型· DML数据操作· 查询语句· Join排序· 分桶函数· 压缩存储· 企业级调优· 实战案例· 面试真题Flume· Flume架构· Agent内部原理· 事务· 安装部署· 实战案例· 自定义Source· 自定义Sink· Ganglia监控Kafka· 消息队列· Kafka架构· 集群部署· 命令行操作· 工作流程分析· 分区分配策略· 数据写入流程· 存储策略· 高阶API· 低级API· 拦截器· 监控· 高可靠性存储· 数据可靠性和持久性保证· ISR机制· Kafka压测· 机器数量计算· 分区数计算· 启动停止脚本DataX· 安装· 原理· 数据一致性· 空值处理· LZO压缩处理阶段三 Spark生态体系架构Scala· Scala基础入门· 函数式编程· 数据结构· 面向对象编程· 模式匹配· 高阶函数· 特质· 注解类型参数· 隐式转换· 高级类型· 案例实操Spark Core· 安装部署· RDD概述· 编程模型· 持久化检查点机制· DAG· 算子详解· RDD编程进阶· 累加器广播变量Spark SQL· SparkSQL· DataFrame· DataSet· 自定义UDFUDAF函数Spark Streaming· SparkStreaming· 背压机制原理· Receiver和Direct模式原理· Window原理及案例实操· 7x24 不间断运行性能考量Spark内核优化· 内核源码详解· 优化详解Hbase· Hbase原理及架构· 数据读写流程· API使用· 与Hive和Sqoop集成· 企业级调优Presto· Presto的安装部署· 使用Presto执行数仓项目的即席查询模块Ranger2.0· 权限管理工具Ranger的安装和使用Azkaban3.0· 任务调度工具Azkaban3.0的安装部署· 使用Azkaban进行项目任务调度,实现.0· Kylin的安装部署· Kylin核心思想· 使用Kylin对接数据源构建模型Atlas2.0· 元数据管理工具Atlas的安装部署Zabbix· 集群监控工具Zabbix的安装部署DolphinScheduler· 任务调度工具DolphinScheduler的安装部署· 实现数仓项目任务的自动化调度、配置邮件报警Superset· 使用SuperSet对数仓项目的计算结果进行可视化展示Echarts· 使用Echarts对数仓项目的计算结果进行可视化展示Redis· Redis安装部署· 五大数据类型· 总体配置· 持久化· 事务· 发布订阅· 主从复制Canal· 使用Canal实时监控MySQL数据变化采集至实时项目阶段四 Flink生态体系架构Flink· 运行时架构· 数据源Source· Window API· Water Mark· 状态编程· CEP复杂事件处理Flink SQL· Flink SQL和Table API详细解读Flink 内核· Flink内核源码讲解· 经典面试题讲解GitGitHub· 安装配置· 本地库搭建· 基本操作· 工作流· 集中式ClickHouse· ClickHouse的安装部署· 读写机制· 数据类型· 执行引擎DataV· 使用DataV对实时项目需求计算结果进行可视化展示sugar· 结合Springboot对接百度sugar实现数据可视化大屏展示Maxwell· 使用Maxwell实时监控MySQL数据变化采集至实时项目ElasticSearch· ElasticSearch索引基本操作、案例实操Kibana· 通过Kibana配置可视化分析Springboot· 利用Springboot开发可视化接口程序阶段五 项目实战阶段数据采集平台项目离线数据仓库项目Spark实时分析项目Flink实时数仓项目推荐和机器学习项目用户画像项目在线教育项目阿里云电商项目

计算机专业同学如何避免自己成为一名低级码农

作为一个上世纪八十年代老牌的计算机专业毕业生,我想讲几点:

  • 从码农做起,提高专业水平。
  • 把一件事做到非常完美,提高自己的知名度
  • 选择一个可托付终生的专业方向,不成功绝不言放弃,更不能频繁更换专业方向。
只要能做到以上三点,我想,你最终能成为一个行业内举足轻重的人物。

从码农做起,提高专业水平。

高楼大厦拔地而起,地基很重要。一般你至少要做4~5年的纯码农,才能对基本的例程、代码规范、底层程序库、系统软件的通用架构、操作系统、流行的ID这些能力,主要来自于码农4~5年的基础培养,不可缺失。

一件事一定要做到非常完美

2015年,我们公司高薪招聘了一名985软件开发专业毕业的码农。面试时,他觉得自己基础不错,就把它作为重点培养。工作一年后发现两个问题:学习能力和不细心。

比如非正常断电后,Windows操作系统经常会对硬盘进行扫描检测,有时要反复按键才能正常进入系统。让他分析一下。他刚刚打,听说需要UPS保护。那个 这是他告诉我的。而另一位工程师,通过各方寻找,用一个软件的改动,完美解决了问题。

诸如此类。我长期养成了这种做事不彻底不完美的习惯,领导同事都觉得他水平有问题,不会重用。

选择一个可托付终生的专业方向,。

有基础,追求完美,能 没有好的方向是不行的。比如AI、AioT、通信、教学、公共等很多方向,选择一个自己有浓厚兴趣的方向。建议花两到三个月的时间考虑一下。

最近采访了一位工作了12年的软件开发从业者。曾从事通信物理层开发、互联网公司、AI开发、工业控制等工作。It 这应该是丹尼尔。可惜每份工作基本都是两年多,一直没有进入那个公司的核心开发层。每一项工作都是为了试水。

问其专业知识,都是只知大概。试想一下,那个公司喜欢要这种只知大概的码农?没有丰厚底蕴的码农,是不受欢迎的,只能到处飘着!

总结:计算机专业知识点多,更新又快。一旦选定一个奋斗方向,绝不轻言更换,持续学习、创新、提高,紧跟时代潮流,最终都能在某一行业站稳脚跟。否则,如果频繁更换工作,短时间看,薪资有所增长;长远看,专业水准得不到最大提高,最终使自己永远沦为低级码农。

如何提升网站的打开速度

可以从两个方面考虑::服务器/网站建设者和客户端/用户。

服务端/网站建设方

如果你是网站的建设者,你可以考虑但不限于:。

1.选择有实力的互联网服务提供商,选择高带宽、高可靠性的服务器部署服务器端程序,比如云服务器,具备负载均衡的能力。

2.服务器端脱离静态,采用静态文件生成技术,静态资源通过CDN加速。

3.使用缓存提高加载速度。

客户端/网站用户

1 .选择在网速快,人不多的地方上网。最好使用有线上网。

2、选择处理器具有大内存/高速缓存的强大计算机或移动。

3.及时清理浏览器缓存等文件。电脑和手机运行时间过长,导致垃圾文件较多,处理器和内存耗尽,会导致网页打开缓慢。