这里的mongod实现的比较简单,它主要是通过在配置服务器结点上创建并维护两个锁定集合(lockpings和锁定),并mongos启动时会在这两个集合中各创建一条数据记录,然后通过维护该记录的状态(包括时间戳,状态)来标识当前mongos结点的锁使用情况。如果多台机器之间想使用一个锁,只要确保锁的名称一致就行,这里就会在configsvr结点的相应集合中共享这一条记录以确保只有一个mongos结点获得了该锁。
一、分布式锁使用场景:代码部署在多台服务器上,即分布式部署。
多个进程同步访问一个共享资源。
二、需要的技术: 数据库:mongo
java:mongo操作插件类 mongotemplate(maven引用),如下:
1 <!--mongodo开始--> 2
<dependency> 3
<groupid>org.springframework.data</groupid> 4
<artifactid>spring-data-mongodb</artifactid> 5
<version>1.8.2.release</version> 6 </dependency> 7
<dependency> 8 <groupid>org.springframework.data</groupid> 9
<artifactid>spring-data-commons</artifactid>10
<version>1.10.0.release</version>11
</dependency>12
<dependency>13
<groupid>org.mongodb</groupid>14
<artifactid>mongo-java-driver</artifactid>15
<version>2.13.0-rc2</version>16
</dependency>17 <!--mongodo结束-->
view code 三、实现代码:主实现逻辑及外部调用方法,获得锁调用getlock,释放锁调用releaselock,详情如下:
1 import java.util.hashmap; 2 import java.util.list; 3 import java.util.map; 4 5 6
public class mongodistributedlock { 7 8 static mongolockdao mongolockdao; 9 10
static {11 mongolockdao = springbeanutils.getbean(mongolockdao);12 }
/**15 * 获得锁的步骤:16
* 1、首先判断锁是否被其他请求获得;如果没被其他请求获得则往下进行;17
* 2、判断锁资源是否过期,如果过期则释放锁资源;18 * 3.1、尝试获得锁资源,如果value=1,那么获得锁资源正常;(在当前请求已经获得锁的前提下,还可能有其他请求尝试去获得锁,此时会导致当前锁的过期时间被延长,由于延长时间在毫秒级,可以忽略。)19 * 3.2、value>1,则表示当前请求在尝试获取锁资源过程中,其他请求已经获取了锁资源,即当前请求没有获得锁;20 * !!!注意,不需要锁资源时,及时释放锁资源!!!。21 *22 * @param key23 * @param expire24 * @return25 */26 public static boolean getlock(string key, long expire) {27 list<mongolock> mongolocks = mongolockdao.getbykey(key);28 //判断该锁是否被获得,锁已经被其他请求获得,直接返回29 if (mongolocks.size() > 0 && mongolocks.get(0).getexpire() >= system.currenttimemillis()) {30 return false;31 }32 //释放过期的锁33 if (mongolocks.size() > 0 && mongolocks.get(0).getexpire() < system.currenttimemillis()) {34 releaselockexpire(key, system.currenttimemillis());35 }36 //!!(在高并发前提下)在当前请求已经获得锁的前提下,还可能有其他请求尝试去获得锁,此时会导致当前锁的过期时间被延长,由于延长时间在毫秒级,可以忽略。37 map<string, object> mapresult = mongolockdao.incrbywithexpire(key, 1, system.currenttimemillis() + expire);38 //如果结果是1,代表当前请求获得锁39 if ((integer) mapresult.get(value) == 1) {40 return true;41 //如果结果>1,表示当前请求在获取锁的过程中,锁已被其他请求获得。42 } else if ((integer) mapresult.get(value) > 1) {43 return false;44 }45 return false;46 }47 48 /**49 * 释放锁50 *51 * @param key52 */53 public static void releaselock(string key) {54 map<string, object> condition = new hashmap<>();55 condition.put(key, key);56 mongolockdao.remove(condition);57 }58 59 /**60 * 释放过期锁61 *62 * @param key63 * @param expiretime64 */65 private static void releaselockexpire(string key, long expiretime) {66 mongolockdao.removeexpire(key, expiretime);67 }68 }
view codemongolockdao实现代码:
1 import org.springframework.data.mongodb.core.findandmodifyoptions; 2
import org.springframework.data.mongodb.core.query.criteria; 3
import org.springframework.data.mongodb.core.query.query; 4
import org.springframework.data.mongodb.core.query.update; 5
import org.springframework.stereotype.repository; 6 7
import java.util.hashmap; 8 import java.util.list; 9
import java.util.map;10 11 12 @repository13 public class mongolockdao <mongolock> {14
private class<?> clz;15 16 public class<?> getclz() {17
if (clz == null) {18 //获取泛型的class对象19
clz = ((class<?>)20
(((parameterizedtype) (this.getclass().getgenericsuperclass())).getactualtypearguments()[0]));21 }22
return clz;23 }24 25 /**26 * 返回指定key的数据27 *28
* @param key29 * @return30 */31 public list<mongolock> getbykey(string key) {32
query query = new query();33 query.addcriteria(criteria.where(key).is(key));34
return (list<mongolock>) mongotemplate.find(query, getclz());35 }36 37 38 /**39
* 指定key自增increment(原子加),并设置过期时间40 *41 * @param key42
* @param increment43 * @param expire44 * @return45 */46
public map<string, object> incrbywithexpire(string key, double increment, long expire) {47
//筛选48 query query = new query();49 query.addcriteria(new criteria(key).is(key));50 51
//更新52 update update = new update();53 update.inc(value, increment);54
update.set(expire, expire);55 //可选项56
findandmodifyoptions options = findandmodifyoptions.options();57
//没有则新增58 options.upsert(true);59
//返回更新后的值60 options.returnnew(true);61
map<string, object> resultmap = new hashmap<>();62
resultmap.put(value, double.valueof(((mongolock)63
mongotemplate.findandmodify(query, update, options, getclz())).getvalue()).intvalue());64
resultmap.put(expire, long.valueof(((mongolock)65
mongotemplate.findandmodify(query, update, options, getclz())).getexpire()).longvalue());66
return resultmap;67 }68 69 70 /**71 * 根据value删除过期的内容72 *73
* @param key74 * @param expiretime75 */76
public void removeexpire(string key, long expiretime) {77
query query = new query();78 query.addcriteria(criteria.where(key).is(key));79
query.addcriteria(criteria.where(expire).lt(expiretime));80 mongotemplate.remove(query, getclz());81 }82 83 public void remove(map<string, object> condition) {84 query query = new query();85 set set = condition.entryset();86 int flag = 0;87 for (map.entry<string, object> entry : set) {88 query.addcriteria(criteria.where(entry.getkey()).is(entry.getvalue()));89 flag = flag + 1;90 }91 if (flag == 0) {92 query = null;93 }94 mongotemplate.remove(query, getclz());95 }96 97 }
view codemongolock实体:
1 public class mongolock { 2 3
private string key; 4 private double value; 5
private long expire; 6 7 public double getvalue() { 8
return value; 9 }10 11 public void setvalue(double value) {12
this.value = value;13 }14 15 public long getexpire() {16
return expire;17 }18 19 public void setexpire(long expire) {20
this.expire = expire;21 }22 23 public string getkey() {24
return key;25 }26 27 public void setkey(string key) {28
this.key = key;29 }30 }
view code四、设计思路前提:利用mongo实现id自增,且自增过程为原子操作,即线程安全。
假设有a、b两个请求通过请求资源。
当a请求到资源是调用mongo自增 +1,并将结果返回给a,即1,此时结果等于1则表明,a请求过程中没有其他请求请求到资源,将锁资源分配给a。
当b请求到资源是调用mongo自增 +1,并将结果返回给a,即2。此时结果大于1则表明,b请求过程中有其他请求请求到资源,锁资源不能分配给b。
这样就是实现了多个请求请求同一个锁并且排队。
关于锁过期时间 :
如果图中代码1releaselockexpire(key, system.currenttimemillis())修改为releaselockexpire(key),即在释放锁的时候没有传入过期时间,会产生如下情况:
a、b两个请求同时通过条件,进入到代码 1
b执行完删除操作,进入代码2,并且刚刚获得到锁资源,而此时a及有可能刚开始执行释放锁的操作。
此时就会发生,a释放了b刚刚获得的锁,这样b就会失去刚刚获得的锁,而b确没有感知,从而造成逻辑错误。
而releaselockexpire(key, system.currenttimemillis()),即在释放锁的时候判断一下过期时间,这样就不会误删b刚刚获得的锁。
以上就是mongo分布式锁的实例讲解的详细内容。