分布式系统(distributed system)是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件。内聚性是指每一个数据库分布节点高度自治,有本地的数据库管理系统。透明性是指每一个数据库分布节点对用户的应用来说都是透明的,看不出是本地还是远程。在分布式数据库系统中,用户感觉不到数据是分布的,即用户不须知道关系是否分割、有无副本、数据存于哪个站点以及事务在哪个站点上执行等。
分布式系统中遇到的问题
通信异常
分布式系统需要在各个节点之间进行通信,因此每次网络通信都会伴随着网络不可用的风险(光纤、路由、DNS等硬件设备的不可用)。
网络分区
由于网络发生异常情况,导致分布式系统中部分节点之间的网络延迟不断增大,最终导致组成分布式系统中只有部分节点能够进行正常通信,这种情况为网络分区,俗称“脑裂”。当出现网络分区时,分布式系统就会出现局部小集群,在极端情况下,这些小集群会独立完成原本需要整个分布式系统才能完成的功能,包括数据的事务处理,这就对分布式一致性提出非常大的挑战。
三态
因为网络的问题,所以分布式系统每次请求与响应存在特有的“三态”概念,即成功、失败和超时。由于网络部可靠性,虽然绝大部分情况下,网络通信能够接受到成功失败响应,但网络异常下,就会出现超时现象,通常有以下两种情况:
由于网络原因,请求并没有被成功的发送到接收方,而是在发送过程就发生了丢失现象。
该请求成功的被接收方处理后,但在响应反馈给发送方过程中,发生丢失现场。
实现
关键点
锁的时效设置。避免单点故障造成死锁,影响其他客户端获取锁。但是也要保证一旦一个客户端持锁,在客户端可用时不会被其他客户端解锁。(网上很多解决方案都是其他客户端等待队列长度判断是否强制解锁,但其实在偶发情况下就不能保证一致性,也就失去了分布式锁的意义)。
持锁期间的check,尽量在关键节点检查锁的状态,所以要设计成可重入锁,但在客户端使用时要做好吞吐量的权衡。
减少获取锁的操作,尽量减少redis压力。所以需要让客户端的申请锁有一个等待时间,而不是所有申请锁的请求要循环申请锁。
加锁的事务或者操作尽量粒度小,减少其他客户端申请锁的等待时间,提高处理效率和并发性。
持锁的客户端解锁后,要能通知到其他等待锁的节点,否则其他节点只能一直等待一个预计的时间再触发申请锁。类似线程的notifyAll,要能同步锁状态给其他客户端,并且是分布式消息。
考虑任何执行句柄中可能出现的异常,状态的正确流转和处理。比如,不能因为一个节点解锁失败,或者锁查询失败(redis 超时或者其他运行时异常),影响整个等待的任务队列,或者任务池。
锁设计
加锁
// 检查是否key已经被占用,如果没有则设置超时时间和唯一标识,初始化value=1
if (redis.call('exists', key) == 0)
then
redis.call('hset', key, key-uunid, 1); //hset key field value
redis.call('pexpire', key, timeout); //设置超时时间为毫秒
return null;
end;
// 如果锁重入,需要判断锁的key field 的情况
if (redis.call('hexists', key, key-unuid) == 1)
then
redis.call('hincrby', key, key-unuid, 1);//使字段值增加指定的整数
redis.call('pexpire', key, timeout);//锁重入重新设置超时时间
return null;
end;
// 返回剩余的过期时间
return redis.call('pttl', key);
解锁
// 如果key已经不存在,说明已经被解锁,直接发布(publihs)redis消息
if (redis.call('exists', key) == 0)
then
redis.call('publish', channelName, ARGV[1]);
return 1;
end;
// key和field不匹配,说明当前客户端线程没有持有锁,不能主动解锁。
if (redis.call('hexists', key, key-uuid) == 0)
then
return null;
end;
// 将value减1
local counter = redis.call('hincrby', key, key-uuid, -1);
// 如果counter>0说明锁在重入,不能删除key
if (counter > 0)
then
redis.call('pexpire', key, timeout);
return 0;
else
// 删除key并且publish 解锁消息
redis.call('del', key);
redis.call('publish', channelName, ARGV[1]);
return 1;
end;
return null;
总结
以 上就是我对这篇文章及其优化总结,分享给大家,希望大家有所收获,觉得收获的话可以点个关注收藏转发一波喔,谢谢大佬们支持!
1、多写多敲代码,好的代码与扎实的基础知识一定是实践出来的
2、可以去百度搜索腾讯课堂图灵学院的视频来学习一下java架构实战案例,还挺不错的。
最后,每一位读到这里的网友,感谢你们能耐心地看完。希望在成为一名更优秀的Java程序员的道路上,我们可以一起学习、一起进步!都能赢取白富美,走向架构师的人生巅峰!
3丶想了解学习以上课程内容可加群:722040762 验证码:博客(06 必过)欢迎大家的加入哟!