分类 技术 下的文章 - 第 5 页 - 酷游博客
首页
关于
友链
Search
1
阿里的简历多久可以投递一次?次数多了有没有影响?可以同时进行吗?
45 阅读
2
Java中泛型的理解
40 阅读
3
Java 14 发布了,再也不怕 NullPointerException 了!
38 阅读
4
Java中的可变参数
37 阅读
5
该如何创建字符串,使用" "还是构造函数?
29 阅读
技术
登录
/
注册
找到
556
篇与
技术
相关的结果
- 第 5 页
2025-01-22
设计模式(十二)——策略模式
新专题:设计模式,我会在博客(http://www.hollischuang.com)及微信公众号(hollischuang)同步更新,欢迎共同学习。 前几篇文章主要介绍了几种创建型模式,本文开始介绍行为型模式。首先介绍一个比较简单的设计模式——策略模式。 概念 学习过设计模式的人大概都知道Head First设计模式这本书,这本书中介绍的第一个模式就是策略模式。把策略模式放在第一个,笔者认为主要有两个原因:1、这的确是一个比较简单的模式。2、这个模式可以充分的体现面向对象设计原则中的封装变化、多用组合,少用继承、针对接口编程,不针对实现编程等原则。 策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。 用途 结合策略模式的概念,我们找一个实际的场景来理解一下。 假设我们是一家新开的书店,为了招揽顾客,我们推出会员服务,我们把店里的会员分为三种,分别是初级会员、中级会员和高级会员。针对不同级别的会员我们给予不同的优惠。初级会员买书我们不打折、中级会员买书我们打九折、高级会员买书我们打八折。 我们希望用户在付款的时候,只要刷一下书的条形码,会员再刷一下他的会员卡,收银台的工组人员就能直接知道应该向顾客收取多少钱。 在不使用模式的情况下,我们可以在结算的方法中使用if/else语句来区别出不同的会员来计算价格。 但是,如果我们有一天想要把初级会员的折扣改成9.8折怎么办?有一天我要推出超级会员怎么办?有一天我要针对中级会员可打折的书的数量做限制怎么办? 使用if\else设计出来的系统,所有的算法都写在了一起,只要有改动我就要修改整个类。我们都知道,只要是修改代码就有可能引入问题。为了避免这个问题,我们可以使用策略模式。。。 对于收银台系统,计算应收款的时候,一个客户只可能是初级、中级、高级会员中的一种。不同的会员使用不同的算法来计算价格。收银台系统其实不关心具体的会员类型和折扣之间的关系。也不希望会员和折扣之间的任何改动会影响到收银台系统。 在介绍策略模式的具体实现方式之前,再来巩固一下几个面向对象设计原则:封装变化、多用组合,少用继承、针对接口编程,不针对实现编程。想一想如何运用到策略模式中,并且有什么好处。 实现方式 策略模式包含如下角色: Context: 环境类 Strategy: 抽象策略类 ConcreteStrategy: 具体策略类 我们运用策略模式来实现一下书店的收银台系统。我们可以把会员抽象成一个策略类,不同的会员类型是具体的策略类。不同策略类里面实现了计算价格这一算法。然后通过组合的方式把会员集成到收银台中。 先定义一个接口,这个接口就是抽象策略类,该接口定义了计算价格方法,具体实现方式由具体的策略类来定义。 /** * Created by hollis on 16/9/19. 会员接口 */ public interface Member { /** * 计算应付价格 * @param bookPrice 书籍原价(针对金额,建议使用BigDecimal,double会损失精度) * @return 应付金额 */ public double calPrice(double bookPrice); } 针对不同的会员,定义三种具体的策略类,每个类中都分别实现计算价格方法。 /** * Created by hollis on 16/9/19. 初级会员 */ public class PrimaryMember implements Member { @Override public double calPrice(double bookPrice) { System.out.println("对于初级会员的没有折扣"); return bookPrice; } } /** * Created by hollis on 16/9/19. 中级会员,买书打九折 */ public class IntermediateMember implements Member { @Override public double calPrice(double bookPrice) { System.out.println("对于中级会员的折扣为10%"); return bookPrice * 0.9; } } /** * Created by hollis on 16/9/19. 高级会员,买书打八折 */ public class AdvancedMember implements Member { @Override public double calPrice(double bookPrice) { System.out.println("对于中级会员的折扣为20%"); return bookPrice * 0.8; } } 上面几个类的定义体现了封装变化的设计原则,不同会员的具体折扣方式改变不会影响到其他的会员。 定义好了抽象策略类和具体策略类之后,我们再来定义环境类,所谓环境类,就是集成算法的类。这个例子中就是收银台系统。采用组合的方式把会员集成进来。 /** * Created by hollis on 16/9/19. 书籍价格类 */ public class Cashier { /** * 会员,策略对象 */ private Member member; public Cashier(Member member){ this.member = member; } /** * 计算应付价格 * @param booksPrice * @return */ public double quote(double booksPrice) { return this.member.calPrice(booksPrice); } } 这个Cashier类就是一个环境类,该类的定义体现了多用组合,少用继承、针对接口编程,不针对实现编程两个设计原则。由于这里采用了组合+接口的方式,后面我们在推出超级会员的时候无须修改Cashier类。只要再定义一个SuperMember implements Member 就可以了。 下面定义一个客户端来测试一下: /** * Created by hollis on 16/9/19. */ public class BookStore { public static void main(String[] args) { //选择并创建需要使用的策略对象 Member strategy = new AdvancedMember(); //创建环境 Cashier cashier = new Cashier(strategy); //计算价格 double quote = cashier.quote(300); System.out.println("高级会员图书的最终价格为:" + quote); strategy = new IntermediateMember(); cashier = new Cashier(strategy); quote = cashier.quote(300); System.out.println("中级会员图书的最终价格为:" + quote); } } //对于中级会员的折扣为20% //高级会员图书的最终价格为:240.0 //对于中级会员的折扣为10% //中级会员图书的最终价格为:270.0 从上面的示例可以看出,策略模式仅仅封装算法,提供新的算法插入到已有系统中,策略模式并不决定在何时使用何种算法。在什么情况下使用什么算法是由客户端决定的。 策略模式的重心 策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。 算法的平等性 策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。 所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。 运行时策略的唯一性 运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。 公有的行为 经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口。(《JAVA与模式》之策略模式) 策略模式的优缺点 优点 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。 使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。 缺点 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。 由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。可以通过使用享元模式在一定程度上减少对象的数量。 文中所有代码见GitHub 参考资料 《JAVA与模式》之策略模式
技术
# 设计模式
酷游
1月22日
0
8
0
2025-01-22
ssh: connect to host 192.0.0.0 port 22: Connection refused
场景描述: 在远程服务器上向本地scp文件,或者在其他机器上登录自己的电脑。使用ssh命令或者scp命令时提示连接被拒绝。 解决方法(MAC BOOK): 系统偏好设置——>共享——>打开远程登录
技术
# Debug
酷游
1月22日
0
21
0
2025-01-22
SynchronizedList和Vector的区别
Vector是java.util包中的一个类。 SynchronizedList是java.util.Collections中的一个静态内部类。 在多线程的场景中可以直接使用Vector类,也可以使用Collections.synchronizedList(List list)方法来返回一个线程安全的List。 那么,到底SynchronizedList和Vector有没有区别,为什么java api要提供这两种线程安全的List的实现方式呢? 首先,我们知道Vector和Arraylist都是List的子类,他们底层的实现都是一样的。所以这里比较如下两个list1和list2的区别: List list = new ArrayList(); List list2 = Collections.synchronizedList(list); Vector list1 = new Vector(); 一、比较几个重要的方法。 1.1 add方法 Vector的实现: public void add(int index, E element) { insertElementAt(element, index); } public synchronized void insertElementAt(E obj, int index) { modCount++; if (index > elementCount) { throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount); } ensureCapacityHelper(elementCount + 1); System.arraycopy(elementData, index, elementData, index + 1, elementCount - index); elementData[index] = obj; elementCount++; } private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } synchronizedList的实现: public void add(int index, E element) { synchronized (mutex) { list.add(index, element); } } 这里,使用同步代码块的方式调用ArrayList的add()方法。ArrayList的add方法内容如下: public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } 从上面两段代码中发现有两处不同: 1.Vector使用同步方法实现,synchronizedList使用同步代码块实现。 2.两者的扩充数组容量方式不一样(两者的add方法在扩容方面的差别也就是ArrayList和Vector的差别。) 1.2 remove方法 synchronizedList的实现: public E remove(int index) { synchronized (mutex) {return list.remove(index);} } ArrayList类的remove方法内容如下: public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; } Vector的实现: public synchronized E remove(int index) { modCount++; if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); E oldValue = elementData(index); int numMoved = elementCount - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--elementCount] = null; // Let gc do its work return oldValue; } 从remove方法中我们发现除了一个使用同步方法,一个使用同步代码块之外几乎无任何区别。 通过比较其他方法,我们发现,SynchronizedList里面实现的方法几乎都是使用同步代码块包上List的方法。如果该List是ArrayList,那么,SynchronizedList和Vector的一个比较明显区别就是一个使用了同步代码块,一个使用了同步方法。 三、区别分析 数据增长区别 从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。 同步代码块和同步方法的区别 1.同步代码块在锁定的范围上可能比同步方法要小,一般来说锁的范围大小和性能是成反比的。 2.同步块可以更加精确的控制锁的作用域(锁的作用域就是从锁被获取到其被释放的时间),同步方法的锁的作用域就是整个方法。 3.静态代码块可以选择对哪个对象加锁,但是静态方法只能给this对象加锁。 因为SynchronizedList只是使用同步代码块包裹了ArrayList的方法,而ArrayList和Vector中同名方法的方法体内容并无太大差异,所以在锁定范围和锁的作用域上两者并无却别。 在锁定的对象区别上,SynchronizedList的同步代码块锁定的是mutex对象,Vector锁定的是this对象。那么mutex对象又是什么呢? 其实SynchronizedList有一个构造函数可以传入一个Object,如果在调用的时候显示的传入一个对象,那么锁定的就是用户传入的对象。如果没有指定,那么锁定的也是this对象。 所以,SynchronizedList和Vector的区别目前为止有两点: 1.如果使用add方法,那么他们的扩容机制不一样。 2.SynchronizedList可以指定锁定的对象。 但是,凡事都有但是。 SynchronizedList中实现的类并没有都使用synchronized同步代码块。其中有listIterator和listIterator(int index)并没有做同步处理。但是Vector却对该方法加了方法锁。 所以说,在使用SynchronizedList进行遍历的时候要手动加锁。 但是,但是之后还有但是。 之前的比较都是基于我们将ArrayList转成SynchronizedList。那么如果我们想把LinkedList变成线程安全的,或者说我想要方便在中间插入和删除的同步的链表,那么我可以将已有的LinkedList直接转成 SynchronizedList,而不用改变他的底层数据结构。而这一点是Vector无法做到的,因为他的底层结构就是使用数组实现的,这个是无法更改的。 所以,最后,SynchronizedList和Vector最主要的区别: 1.SynchronizedList有很好的扩展和兼容功能。他可以将所有的List的子类转成线程安全的类。 2.使用SynchronizedList的时候,进行遍历时要手动进行同步处理。 3.SynchronizedList可以指定锁定的对象。
技术
# Java
酷游
1月22日
0
8
0
2025-01-22
webx学习(二)——Webx Framework
Webx Framework负责完成一系列基础性的任务Webx Framework的任务初始化Spring容器初始化日志系统增强request/response/session的功能提供pipeline流程处理机制异常处理开发模式 一、初始化spring容器,在web.xml中添加监听器: com.alibaba.citrus.webx.context.WebxContextLoaderListener WebxContextLoaderListener用于初始化Spring,搜索WEB-INF目录下的子应用配置文件,为每个子应用创建Spring容器WebxContextLoaderListener是从spring的ContextLoaderListener派生出来的。ContextLoaderListener我们还是比较熟悉的,在使用spring的时候会在web.xml中配置。 二、初始化日志系统 com.alibaba.citrus.logconfig.LogConfiguratorListener LogConfigurationListener用于初始化日志系统(使用配置文件log4j.xml或logback.xml) 三、webx响应请求 当Webx Framework接收到一个来自WEB的请求以后,实际上它主要做了两件事:首先,它会增强request、response、session的功能,并把它们打包成更易使用的RequestContext对象。其次,它会调用相应子应用的pipeline,用它来做进一步的处理。 假如在上面的过程中出现异常,则会触发Webx Framework处理异常的过程。 四、pipeline流程控制 Pipeline的意思是管道,管道中有许多阀门(Valve),阀门可以控制水流的走向。Webx Framework中的pipeline可以控制处理请求的流程的走向 五、异常处理机制 当应用发生异常时,Webx Framework可以处理这些异常。 六、开发模式工具 Webx Framework提供了一个开关,可以让应用运行于“生产模式(Production Mode)”或是“开发模式(Development Mode)” 。 ${productionMode:true} productionMode:true表示开发模式启动 在生产模式和开发模式下有一些不同的地方,具体的内容见官方文档。
技术
# webx
酷游
1月22日
0
23
0
2025-01-22
分布式锁的几种实现方式~
目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。 在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,Java中其实提供了很多并发处理相关的API,但是这些API在分布式场景中就无能为力了。也就是说单纯的Java Api并不能提供分布式锁的能力。所以针对分布式锁的实现目前有多种方案。 针对分布式锁的实现,目前比较常用的有以下几种方案: 基于数据库实现分布式锁 基于缓存(redis,memcached,tair)实现分布式锁 基于Zookeeper实现分布式锁 在分析这几种实现方案之前我们先来想一下,我们需要的分布式锁应该是怎么样的?(这里以方法锁为例,资源锁同理) 可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。 这把锁要是一把可重入锁(避免死锁) 这把锁最好是一把阻塞锁(根据业务需求考虑要不要这条) 有高可用的获取锁和释放锁功能 获取锁和释放锁的性能要好 基于数据库实现分布式锁 基于数据库表 要实现分布式锁,最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了。 当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录。 创建这样一张数据库表: CREATE TABLE `methodLock` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名', `desc` varchar(1024) NOT NULL DEFAULT '备注信息', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成', PRIMARY KEY (`id`), UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法'; 当我们想要锁住某个方法时,执行以下SQL: insert into methodLock(method_name,desc) values (‘method_name’,‘desc’) 因为我们对method_name做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。 当方法执行完毕之后,想要释放锁的话,需要执行以下Sql: delete from methodLock where method_name ='method_name' 上面这种简单的实现有以下几个问题: 1、这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。 2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。 3、这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。 4、这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。 当然,我们也可以有其他方式解决上面的问题。 数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。 没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。 非阻塞的?搞一个while循环,直到insert成功再返回成功。 非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。 基于数据库排他锁 除了可以通过增删操作数据表中的记录以外,其实还可以借助数据中自带的锁来实现分布式的锁。 我们还用刚刚创建的那张数据库表。可以通过数据库的排他锁来实现分布式锁。 基于MySql的InnoDB引擎,可以使用以下方法来实现加锁操作: public boolean lock(){ connection.setAutoCommit(false) while(true){ try{ result = select * from methodLock where method_name=xxx for update; if(result==null){ return true; } }catch(Exception e){ } sleep(1000); } return false; } 在查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁(这里再多提一句,InnoDB引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁。这里我们希望使用行级锁,就要给method_name添加索引,值得注意的是,这个索引一定要创建成唯一索引,否则会出现多个重载方法之间无法同时被访问的问题。重载方法的话建议把参数类型也加上。)。当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁。 我们可以认为获得排它锁的线程即可获得分布式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,再通过以下方法解锁: public void unlock(){ connection.commit(); } 通过connection.commit()操作来释放锁。 这种方法可以有效的解决上面提到的无法释放锁和阻塞锁的问题。 阻塞锁? for update语句会在执行成功后立即返回,在执行失败时一直处于阻塞状态,直到成功。 锁定之后服务宕机,无法释放?使用这种方式,服务宕机之后数据库会自己把锁释放掉。 但是还是无法直接解决数据库单点和可重入问题。 这里还可能存在另外一个问题,虽然我们对method_name 使用了唯一索引,并且显示使用for update来使用行级锁。但是,MySql会对查询进行优化,即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同执行计划的代价来决定的,如果 MySQL 认为全表扫效率更高,比如对一些很小的表,它就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。如果发生这种情况就悲剧了。。。 还有一个问题,就是我们要使用排他锁来进行分布式锁的lock,那么一个排他锁长时间不提交,就会占用数据库连接。一旦类似的连接变得多了,就可能把数据库连接池撑爆 总结 总结一下使用数据库来实现分布式锁的方式,这两种方式都是依赖数据库的一张表,一种是通过表中的记录的存在情况确定当前是否有锁存在,另外一种是通过数据库的排他锁来实现分布式锁。 数据库实现分布式锁的优点 直接借助数据库,容易理解。 数据库实现分布式锁的缺点 会有各种各样的问题,在解决问题的过程中会使整个方案变得越来越复杂。 操作数据库需要一定的开销,性能问题需要考虑。 使用数据库的行级锁并不一定靠谱,尤其是当我们的锁表并不大的时候。 基于缓存实现分布式锁 相比较于基于数据库实现分布式锁的方案来说,基于缓存来实现在性能方面会表现的更好一点。而且很多缓存是可以集群部署的,可以解决单点问题。 目前有很多成熟的缓存产品,包括Redis,memcached以及我们公司内部的Tair。 这里以Tair为例来分析下使用缓存实现分布式锁的方案。关于Redis和memcached在网络上有很多相关的文章,并且也有一些成熟的框架及算法可以直接使用。 基于Tair的实现分布式锁其实和Redis类似,其中主要的实现方式是使用TairManager.put方法来实现。 public boolean trylock(String key) { ResultCode code = ldbTairManager.put(NAMESPACE, key, "This is a Lock.", 2, 0); if (ResultCode.SUCCESS.equals(code)) return true; else return false; } public boolean unlock(String key) { ldbTairManager.invalid(NAMESPACE, key); } 以上实现方式同样存在几个问题: 1、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在tair中,其他线程无法再获得到锁。 2、这把锁只能是非阻塞的,无论成功还是失败都直接返回。 3、这把锁是非重入的,一个线程获得锁之后,在释放锁之前,无法再次获得该锁,因为使用到的key在tair中已经存在。无法再执行put操作。 当然,同样有方式可以解决。 没有失效时间?tair的put方法支持传入失效时间,到达时间之后数据会自动删除。 非阻塞?while重复执行。 非可重入?在一个线程获取到锁之后,把当前主机信息和线程信息保存起来,下次再获取之前先检查自己是不是当前锁的拥有者。 但是,失效时间我设置多长时间为好?如何设置的失效时间太短,方法没等执行完,锁就自动释放了,那么就会产生并发问题。如果设置的时间太长,其他获取锁的线程就可能要平白的多等一段时间。这个问题使用数据库实现分布式锁同样存在 总结 可以使用缓存来代替数据库来实现分布式锁,这个可以提供更好的性能,同时,很多缓存服务都是集群部署的,可以避免单点问题。并且很多缓存服务都提供了可以用来实现分布式锁的方法,比如Tair的put方法,redis的setnx方法等。并且,这些缓存服务也都提供了对数据的过期自动删除的支持,可以直接设置超时时间来控制锁的释放。 使用缓存实现分布式锁的优点 性能好,实现起来较为方便。 使用缓存实现分布式锁的缺点 通过超时时间来控制锁的失效时间并不是十分的靠谱。 基于Zookeeper实现分布式锁 基于zookeeper临时有序节点可以实现的分布式锁。 大致思想即为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。 来看下Zookeeper能不能解决前面提到的问题。 锁无法释放?使用Zookeeper可以有效的解决锁无法释放的问题,因为在创建锁的时候,客户端会在ZK中创建一个临时节点,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁。 非阻塞锁?使用Zookeeper可以实现阻塞的锁,客户端可以通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点有变化,Zookeeper会通知客户端,客户端可以检查自己创建的节点是不是当前所有节点中序号最小的,如果是,那么自己就获取到锁,便可以执行业务逻辑了。 不可重入?使用Zookeeper也可以有效的解决不可重入的问题,客户端在创建节点的时候,把当前客户端的主机信息和线程信息直接写入到节点中,下次想要获取锁的时候和当前最小的节点中的数据比对一下就可以了。如果和自己的信息一样,那么自己直接获取到锁,如果不一样就再创建一个临时的顺序节点,参与排队。 单点问题?使用Zookeeper可以有效的解决单点问题,ZK是集群部署的,只要集群中有半数以上的机器存活,就可以对外提供服务。 可以直接使用zookeeper第三方库Curator客户端,这个客户端中封装了一个可重入的锁服务。 public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { try { return interProcessMutex.acquire(timeout, unit); } catch (Exception e) { e.printStackTrace(); } return true; } public boolean unlock() { try { interProcessMutex.release(); } catch (Throwable e) { log.error(e.getMessage(), e); } finally { executorService.schedule(new Cleaner(client, path), delayTimeForClean, TimeUnit.MILLISECONDS); } return true; } Curator提供的InterProcessMutex是分布式锁的实现。acquire方法用户获取锁,release方法用于释放锁。 使用ZK实现的分布式锁好像完全符合了本文开头我们对一个分布式锁的所有期望。但是,其实并不是,Zookeeper实现的分布式锁其实存在一个缺点,那就是性能上可能并没有缓存服务那么高。因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同不到所有的Follower机器上。 其实,使用Zookeeper也有可能带来并发问题,只是并不常见而已。考虑这样的情况,由于网络抖动,客户端可ZK集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。就可能产生并发问题。这个问题不常见是因为zk有重试机制,一旦zk集群检测不到客户端的心跳,就会重试,Curator客户端支持多种重试策略。多次重试之后还不行的话才会删除临时节点。(所以,选择一个合适的重试策略也比较重要,要在锁的粒度和并发之间找一个平衡。) 总结 使用Zookeeper实现分布式锁的优点 有效的解决单点问题,不可重入问题,非阻塞问题以及锁无法释放的问题。实现起来较为简单。 使用Zookeeper实现分布式锁的缺点 性能上不如使用缓存实现分布式锁。 需要对ZK的原理有所了解。 三种方案的比较 上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。 从理解的难易程度角度(从低到高) 数据库 > 缓存 > Zookeeper 从实现的复杂性角度(从低到高) Zookeeper >= 缓存 > 数据库 从性能角度(从高到低) 缓存 > Zookeeper >= 数据库 从可靠性角度(从高到低) Zookeeper > 缓存 > 数据库
技术
# 分布式
酷游
1月22日
0
5
0
上一页
1
...
4
5
6
...
112
下一页
易航博客