环境是这æ ïLš„åQšæœåŠ¡å™¨æ˜¯ç”¨Javaåšçš„åQ?æ•°æ®åº“是MongoDB
需求是˜q™æ ·çš„:我们的系¾lŸé‡Œè¦ç”Ÿæˆä¸€ä¸ªå”¯ä¸€IDåQŒå‰é¢çš„éƒ¨åˆ†æœ‰ä¸€å®šçš„æ ¼å¼åQŒåƈ和时间关è”, ¾_„¡¡®åˆ°å¾®¿U’,考虑到åŒä¸€å¾®ç§’内有å¯èƒ½å˜åœ¨òq¶å‘情况åQ? 所以åŽé¢åœ¨åР䏤ä½åºåˆ—å·åQ?¾pÈ»Ÿéœ€è¦å®šä¹‰äØ“1毫秒内的òq¶å‘ž®äºŽ100个,所以åŽé¢ä¸¤ä½å°±å¤Ÿç”¨äº†ã€? JavaæœåŠ¡å™¨ç«¯æœ‰å¤šå°æœºå™¨éƒ½å¯ä»¥ç”¨æ¥ç”Ÿæˆ˜q™ä¸ªå”¯ä¸€IDåQŒæ‰€ä»¥éœ€è¦åœ¨ä¸åŒçš„æœºå™¨ä¸Šä¸èƒ½ç”Ÿæˆç›¸åŒçš„åºåˆ—å·åQŒæ‰€ä»¥éœ€è¦åœ¨æŸä¸€ç‚¹ä¸Šåšå…¨å±€çš„èŒƒå›´åŒæ¥æ¥ä¿å˜˜q™åºåˆ? åïLš„唯一性ã€?其实如果ä¸è€ƒè™‘需求里的唯一ID是有一定æ„ä¹‰çš„æ ¼å¼çš„, 用UUID或MongoDBçš„ObjectId都是更好的选择åQŒå®Œå…¨ä¸éœ€è¦åœ¨æŸä¸€ç‚¹ä¸Š˜q›è¡ŒåŒæ¥åQŒæ€§èƒ½ä¼šæ›´å¥½ã€?/p>
˜q™ä¸ªå¯ä»¥ç”Ÿæˆåºåˆ—åïLš„点, 我们å¯ä»¥åšä¸€ä¸ªåºåˆ—å·ç”ŸæˆæœåС噍æ¥å¯¹åº”åQ?也å¯ä»¥ç”¨æ•°æ®åº“æ¥å¯¹åº”ã€? å•å•䏸™¿™ä¸ªç®€å•的功能准备一个æœåС噍æ¥åšæ˜„¡„¶ä¸åˆé€‚ã€?但是我们用的MongoDBòq¶æ²¡æœ‰ç±»ä¼égºŽMySQL或Oracleä¸çš„SELECT FOR UPDATE˜q™æ ·çš„锿œºåˆ¶ã€?所以没有办法简å•的对这个åºåˆ—å·åšåŽŸåæ“ä½œã€? 但是MongoDB的对å•个document˜q›è¡Œupdateæ“ä½œä¸æœ‰å¾ˆæ˜¯å…ähœ‰åŽŸåæ€§çš„åQ?例如
- $set
- $unset
- $inc
- $push
- $pushAll
- $pull
- $pullAll
我们å¯ä»¥åˆ©ç”¨˜q™äº›åŽŸåæ“ä½œåQŒåœ¨æ•°æ®åº“层以ä¹è§‚é”çš„åÅžå¼æ¥å®žçŽ°å¾ªçŽ¯åºåˆ—å—æ®µã€‚äØ“äº†æ–¹ä¾¿è°ƒç”¨æˆ‘æŠŠè¿™ŒDµé€»è¾‘åšæˆæ•°æ®åº“ä¸çš„Javascript函数ã€?¾cÖM¼¼ä¸ŽMySQLä¸çš„å˜å‚¨˜q‡ç¨‹ã€?/p>
首先我们需è¦ä¸€ä¸ªcollectionæ¥å˜æ”‘ֺ列å·åQŒåƈ寚wœ€è¦çš„需è¦çš„åºåˆ—寂¿›è¡Œåˆå§‹åŒ–。我们å«å®ƒcountersã€?/p>
- db.counters.save({_id:"SerialNo1", val:0, maxval:99})
ç„¶åŽæˆ‘们想system.jsé‡Œæ·»åŠ ä¸€ä¸ªJavascript函数
- db.system.js.save({_id:"getNextUniqueSeq",
- value:function (keyName) {
- var seqObj = db.counters.findOne({_id:keyName});
- if (seqObj == null) {
- print("can not find record with key: " + keyName);
- return -1;
- }
- // the max value of sequence
- var maxVal = seqObj.maxval;
- // the current value of sequence
- var curVal = seqObj.val;
- while(true){
- // if curVal reach max, reset it
- if(curVal >= maxVal){
- db.counters.update({_id : keyName, val : curVal}, { $set : { val : 0 }}, false, false);
- var err = db.getLastErrorObj();
- if( err && err.code ) {
- print( "unexpected error reset data: " + tojson( err ) );
- return -2;
- } else if (err.n == 0){
- // fail to reset value, may be reseted by others
- print("fail to reset value: ");
- }
- // get current value again.
- seqObj = db.counters.findOne({_id:keyName});
- maxVal = seqObj.maxval;
- curVal = seqObj.val;
- continue;
- }
- // if curVal not reach the max, inc it;
- // increase
- db.counters.update({_id : keyName, val : curVal}, { $inc : { val : 1 }}, false, false);
- var err = db.getLastErrorObj();
- if( err && err.code ) {
- print( "unexpected error inc val: " + tojson( err ) );
- return -3;
- } else if (err.n == 0){
- // fail to reset value, may be increased by others
- print("fail to inc value: ");
- // get current value again.
- seqObj = db.counters.findOne({_id:keyName});
- maxVal = seqObj.maxval;
- curVal = seqObj.val;
- continue;
- } else {
- var retVal = curVal + 1;
- print("success to get seq : " + retVal);
- // increase successful
- return retVal;
- }
- }
- }
- });
上题q™æ®µä¼šæŠŠæŒ‡å®šçš„åºåˆ—å·çš„valå€?1åQŒå¦‚æžœval辑ֈ°ä¸Šé™åˆ™ä»Ž0开始。所以å«å¾ªçޝåºåˆ—ã€?/p>
其实上é¢çš„实现在原ç†ä¸Šå’ŒJava里的AtomicInteger¾pÕdˆ—的功能实现是¾cÖM¼¼çš„,利用循环é‡è¯•å’ŒåŽŸåæ€§çš„CASæ¥å®žçŽ°ã€‚è¿™¿U实现方å¼åœ¨å¤šçº¿½E‹çš„环境里由于é”åQˆMonitoråQ‰çš„范围很å°åQŒæ‰€ä»¥åÆˆå‘æ€§ä¸Šæ¯”排他é”è¦å¥½ä¸€äº›ã€?/p>
䏋颿ˆ‘们用Javaæ¥æµ‹è¯•一下这个函数的æ£ç¡®æ€§ã€?å›_œ¨å¤šçº¿½E‹çš„æƒ…况下会ä¸ä¼šå¾—到é‡å¤çš„åºåˆ—å·ã€?/p>
½W¬ä¸€ä¸ªæµ‹è¯•,val=0åQ?maxval=2000åQ?Javaç«?0个线½E‹æ¯ä¸ªçº¿½E‹åó@环调ç”?00‹Æ¡ã€?å…?000‹Æ¡ã€?所以棼‹®çš„æƒ…况下,ä»?åˆ?999应该æ¯ä¸ªæ•°å—åªå‡ºçŽîC¸€‹Æ¡ã€?/p>
- @Test
- public void testGetNextUniqueSeq1() throws Exception {
- final int THREAD_COUNT = 20;
- final int LOOP_COUNT = 100;
- Mongo mongoClient = new Mongo("172.17.2.100", 27017);
- DB db = mongoClient.getDB("im");
- db.authenticate("imadmin", "imadmin".toCharArray());
- BasicDBObject q = new BasicDBObject();
- q.put("_id", "UNIQUE_KEY");
- BasicDBObject upd = new BasicDBObject();
- BasicDBObject set = new BasicDBObject();
- set.put("val", 0);
- set.put("maxval", THREAD_COUNT * LOOP_COUNT);
- upd.put("$set", set);
- db.getCollection("counters").update(q, upd);
- Thread[] threads = new Thread[THREAD_COUNT];
- final int[][] results = new int[THREAD_COUNT][LOOP_COUNT];
- for (int i = 0; i < THREAD_COUNT; i++) {
- final int temp_i = i;
- threads[i] = new Thread("" + i) {
- @Override
- public void run() {
- try {
- Mongo mongoClient = new Mongo("172.17.2.100", 27017);
- DB db = mongoClient.getDB("im");
- db.authenticate("imadmin", "imadmin".toCharArray());
- for (int j = 0; j < LOOP_COUNT; j++) {
- Object result = db.eval("getNextUniqueSeq(\"UNIQUE_KEY\")");
- System.out.printf("Thread %s, seq=%d\n", Thread.currentThread().getName(), ((Double) result).intValue());
- results[temp_i][j] = ((Double) result).intValue();
- }
- } catch (UnknownHostException e) {
- e.printStackTrace();
- }
- }
- };
- }
- for (Thread thread : threads) {
- thread.start();
- }
- for (Thread thread : threads) {
- thread.join();
- }
- for (int num = 1; num <= LOOP_COUNT * THREAD_COUNT; num++) {
- // every number appear 1 times only!
- int times = 0;
- for (int j = 0; j < THREAD_COUNT; j++) {
- for (int k = 0; k < LOOP_COUNT; k++) {
- if (results[j][k] == num)
- times++;
- }
- }
- assertEquals(1, times);
- }
- }
ç„¶åŽæˆ‘们冿µ‹è¯•一下åó@环的情况ã€?val=0, maxval=99ã€?åŒæ ·æ˜¯Javaç«?0个线½E‹æ¯ä¸ªçº¿½E‹åó@环调ç”?00‹Æ¡ã€?å…?000‹Æ¡ã€‚è¿™‹Æ¡ä»Ž0åˆ?9çš„æ•°å—æ¯ä¸ªåº”该å–å¾?0‹Æ¡ã€?/p>
- @Test
- public void testGetNextUniqueSeq2() throws Exception {
- final int THREAD_COUNT = 20;
- final int LOOP_COUNT = 100;
- Mongo mongoClient = new Mongo("172.17.2.100", 27017);
- DB db = mongoClient.getDB("im");
- db.authenticate("imadmin", "imadmin".toCharArray());
- BasicDBObject q = new BasicDBObject();
- q.put("_id", "UNIQUE_KEY");
- BasicDBObject upd = new BasicDBObject();
- BasicDBObject set = new BasicDBObject();
- set.put("val", 0);
- set.put("maxval", LOOP_COUNT);
- upd.put("$set", set);
- db.getCollection("counters").update(q, upd);
- Thread[] threads = new Thread[THREAD_COUNT];
- final int[][] results = new int[THREAD_COUNT][LOOP_COUNT];
- for (int i = 0; i < THREAD_COUNT; i++) {
- final int temp_i = i;
- threads[i] = new Thread("" + i) {
- @Override
- public void run() {
- try {
- Mongo mongoClient = new Mongo("172.17.2.100", 27017);
- DB db = mongoClient.getDB("im");
- db.authenticate("imadmin", "imadmin".toCharArray());
- for (int j = 0; j < LOOP_COUNT; j++) {
- Object result = db.eval("getNextUniqueSeq(\"UNIQUE_KEY\")");
- System.out.printf("Thread %s, seq=%d\n", Thread.currentThread().getName(), ((Double) result).intValue());
- results[temp_i][j] = ((Double) result).intValue();
- }
- } catch (UnknownHostException e) {
- e.printStackTrace();
- }
- }
- };
- }
- for (Thread thread : threads) {
- thread.start();
- }
- for (Thread thread : threads) {
- thread.join();
- }
- for (int num = 1; num <= LOOP_COUNT; num++) {
- // every number appear 20 times only!
- int times = 0;
- for (int j = 0; j < THREAD_COUNT; j++) {
- for (int k = 0; k < LOOP_COUNT; k++) {
- if (results[j][k] == num)
- times++;
- }
- }
- assertEquals(20, times);
- }
- }
˜q™ä¸ª‹¹‹è¯•è·‘äº†å‡ æ¬¡éƒ½æ˜¯æ£ç¡®çš„ã€?/p>
ç”׃ºŽæ²¡æœ‰å¯ä»¥˜q›è¡Œå¯Òޝ”其他的实现方å¼ï¼ˆä¾‹å¦‚排他é”ï¼‰æ‰€ä»¥æ²¡æœ‰åšæ€§èƒ½‹¹‹è¯•ã€?/p>
写在最åŽã€?虽然MongoDB支挾cÖM¼¼äºŽå˜å‚¨è¿‡½E‹çš„Stored JavascriptåQŒä½†æ˜¯å…¶å®žä¸å»ø™®®ä½¿ç”¨˜q™ä¸ªæ¥è§£å†›_¤æ‚问题。主è¦åŽŸå› æ˜¯æ²¡æ³•è°ƒè¯•åQŒç»´æŠ¤è“væ¥å¤ªä¸æ–¹ä¾Ñ€‚而且åœ?.4之å‰MongoDBå¯ÒŽœåŠ¡ç«¯ Javascript支æŒòq¶ä¸æ˜¯å¾ˆå¥½ï¼Œ 一个mongod˜q›ç¨‹åŒæ—¶åªèƒ½æ‰§è¡Œä¸€ŒDµJavascript。如果能在应用层解决掉还是在应用层里实现逻辑比较好ã€?/p>