隨筆-4  評論-10  文章-0  trackbacks-0
          創建日期:2012年7月3日
          修改日期:2012年7月19日
          前段時間細節的了解了Jedis的使用,Jedis是redis的java版本的客戶端實現。
          本文做個總結,主要分享如下內容:

          【pipeline】【分布式的id生成器】【分布式鎖【watch】【multi】】【redis分布式】
          好了,一個一個來。
          一、 Pipeline
          官方的說明是:starts a pipeline,which is a very efficient way to send lots of command and read all the responses when you finish sending them。簡單點說pipeline適用于批處理。當有大量的操作需要一次性執行的時候,可以用管道。
          示例:
          Jedis jedis = new Jedis(String, int);
          Pipeline p = jedis.pipelined();
          p.set(key,value);//每個操作都發送請求給redis-server
          p.get(key,value);

          p.sync();//這段代碼獲取所有的response
          這里我進行了20w次連續操作(10w讀,10w寫),不用pipeline耗時:187242ms,用pipeline耗時:1188ms,可見使用管道后的性能上了一個臺階??戳舜a了解到,管道通過一次性寫入請求,然后一次性讀取響應。也就是說jedis是:request response,request response,...;pipeline則是:request request... response response的方式。這樣無需每次請求都等待server端的響應。

          二、 跨jvm的id生成器 
          談到這個話題,首先要知道redis-server端是單線程來處理client端的請求的。
          這樣來實現一個id生成器就非常簡單了,只要簡單的調用jdeis.incr(key);就搞定了。
          你或許會問,incr是原子操作嗎,能保證不會出現并發問題嗎,前面說過,server端是單線程處理請求的。

          三、 【跨jvm的鎖實現【watch】【multi】】
          首先說下這個問題的使用場景,有些時候我們業務邏輯是在不同的jvm進程甚至是不同的物理機上的jvm處理的。這樣如何來實現不同jvm上的同步問題呢,其實我們可以基于redis來實現一個鎖。
          具體事務和監聽請參考文章:redis學習筆記之事務
           暫時找到三種實現方式:
          1. 通過jedis.setnx(key,value)實現
               import java.util.Random;

          import org.apache.commons.pool.impl.GenericObjectPool.Config;

          import redis.clients.jedis.Jedis;
          import redis.clients.jedis.JedisPool;
          import redis.clients.jedis.Transaction;

          /**
           * 
          @author Teaey
           
          */
          public class RedisLock {
              //加鎖標志
              public static final String LOCKED = "TRUE";
              public static final long ONE_MILLI_NANOS = 1000000L;
              //默認超時時間(毫秒)
              public static final long DEFAULT_TIME_OUT = 3000;
              public static JedisPool pool;
              public static final Random r = new Random();
              //鎖的超時時間(秒),過期刪除
              public static final int EXPIRE = 5 * 60;
              static {
                  pool = new JedisPool(new Config(), "host", 6379);
              }
              private Jedis jedis;
              private String key;
              //鎖狀態標志
              private boolean locked = false;

              public RedisLock(String key) {
                  this.key = key;
                  this.jedis = pool.getResource();
              }

              public boolean lock(long timeout) {
                  long nano = System.nanoTime();
                  timeout *= ONE_MILLI_NANOS;
                  try {
                      while ((System.nanoTime() - nano) < timeout) {
                          if (jedis.setnx(key, LOCKED) == 1) {
                              jedis.expire(key, EXPIRE);
                              locked = true;
                              return locked;
                          }
                          // 短暫休眠,nano避免出現活鎖
                          Thread.sleep(3, r.nextInt(500));
                      }
                  } catch (Exception e) {
                  }
                  return false;
              }
              public boolean lock() {
                  return lock(DEFAULT_TIME_OUT);
              }

              // 無論是否加鎖成功,必須調用
              public void unlock() {
                  try {
                      if (locked)
                          jedis.del(key);
                  } finally {
                      pool.returnResource(jedis);
                  }
              }
          }
          2. 通過事務(multi)實現
          由于采納第一張方法,第二種跟第三種實現只貼了關鍵代碼,望諒解。^_^
               public boolean lock_2(long timeout) {
                  long nano = System.nanoTime();
                  timeout *= ONE_MILLI_NANOS;
                  try {
                      while ((System.nanoTime() - nano) < timeout) {
                          Transaction t = jedis.multi();
                          // 開啟事務,當server端收到multi指令
                          
          // 會將該client的命令放入一個隊列,然后依次執行,知道收到exec指令
                          t.getSet(key, LOCKED);
                          t.expire(key, EXPIRE);
                          String ret = (String) t.exec().get(0);
                          if (ret == null || ret.equals("UNLOCK")) {
                              return true;
                          }
                          // 短暫休眠,nano避免出現活鎖
                          Thread.sleep(3, r.nextInt(500));
                      }
                  } catch (Exception e) {
                  }
                  return false;
              }
          3. 通過事務+監聽實現
              public boolean lock_3(long timeout) {
                  long nano = System.nanoTime();
                  timeout *= ONE_MILLI_NANOS;
                  try {
                      while ((System.nanoTime() - nano) < timeout) {
                          jedis.watch(key);
                          // 開啟watch之后,如果key的值被修改,則事務失敗,exec方法返回null
                          String value = jedis.get(key);
                          if (value == null || value.equals("UNLOCK")) {
                              Transaction t = jedis.multi();
                              t.setex(key, EXPIRE, LOCKED);
                              if (t.exec() != null) {
                                  return true;
                              }
                          }
                          jedis.unwatch();
                          // 短暫休眠,nano避免出現活鎖
                          Thread.sleep(3, r.nextInt(500));
                      }
                  } catch (Exception e) {
                  }
                  return false;
              }
          最終采用第一種實現,因為加鎖只需發送一個請求,效率最高。
          四、 【redis分布式】
              最后一個話題,jedis的分布式。在jedis的源碼里發現了兩種hash算法(MD5,MURMUR Hash(默認)),也可以自己實現redis.clients.util.Hashing接口擴展。
              List<JedisShardInfo> hosts = new ArrayList<JedisShardInfo>();
                  //server1
                  JedisShardInfo host1 = new JedisShardInfo("", 6380, 2000);
                  //server2
                  JedisShardInfo host2 = new JedisShardInfo("", 6381, 2000);
                  hosts.add(host1);
                  hosts.add(host2);
                  ShardedJedis jedis = new ShardedJedis(hosts);
                  jedis.set("key", "");


          另外寫博客真費力。。。
          posted on 2012-07-03 13:32 沖杯茶喝 閱讀(24083) 評論(6)  編輯  收藏

          評論:
          # re: Jedis使用總結【pipeline】【分布式的id生成器】【分布式鎖【watch】【multi】】【redis分布式】 2013-01-25 10:08 | finallygo
          2.6之后,支持了script,又有新的辦法了  回復  更多評論
            
          # re: Jedis使用總結【pipeline】【分布式的id生成器】【分布式鎖【watch】【multi】】【redis分布式】 2013-05-03 09:54 | 沖杯茶喝
          沒時間研究了,如果有新的,可以完善本文@finallygo
            回復  更多評論
            
          # re: Jedis使用總結【pipeline】【分布式的id生成器】【分布式鎖【watch】【multi】】【redis分布式】 2013-05-23 00:53 | leealways887
          請問博主,為什么我用了pipeline之后,設置10萬條數據,程序反應不過來,倒是不用pipeline,程序還能正常運行呢?  回復  更多評論
            
          # re: Jedis使用總結【pipeline】【分布式的id生成器】【分布式鎖【watch】【multi】】【redis分布式】[未登錄] 2014-02-11 15:44 | young
          @leealways887
          數據量太大了吧 內存用完了  回復  更多評論
            
          # re: Jedis使用總結【pipeline】【分布式的id生成器】【分布式鎖【watch】【multi】】【redis分布式】 2014-11-03 17:36 | louzhu
          什么叫反應不過來!據我說之,redis服務器應該不會反應不過來。

          @leealways887
            回復  更多評論
            
          # re: Jedis使用總結【pipeline】【分布式的id生成器】【分布式鎖【watch】【multi】】【redis分布式】 2016-05-20 10:13 | fruwei
          第二種的t.expire(key, EXPIRE);有問題吧 ,如果一直有鎖請求,豈不是每次都把這個過期時間重新設置了?應該先判斷下有沒有超時,沒有就設置,有的話就不設置。  回復  更多評論
            

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 大姚县| 扶风县| 惠州市| 新绛县| 泸西县| 武定县| 通榆县| 北票市| 疏勒县| 介休市| 故城县| 温泉县| 商水县| 公主岭市| 辽阳市| 扎鲁特旗| 兴海县| 天门市| 洛川县| 高州市| 涞水县| 岳阳县| 贺兰县| 永和县| 兴业县| 澄江县| 武强县| 新宾| 普陀区| 唐河县| 镇巴县| 波密县| 从江县| 三原县| 大竹县| 乐昌市| 抚州市| 顺义区| 华蓥市| 阿鲁科尔沁旗| 筠连县|