Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 中文字幕va一区二区三区,免费视频精品一区二区三区,久久九九亚洲精品

          整合營銷服務商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          精編長文!不為人所知的分布式鎖實現(xiàn)全都在這里了

          精編長文!不為人所知的分布式鎖實現(xiàn)全都在這里了

          源:Java愛好者社區(qū):https://www.cnblogs.com/ldws/p/12155003.html

          1、引入業(yè)務場景

          首先來由一個場景引入:

          最近老板接了一個大單子,允許在某終端設備安裝我們的APP,終端設備廠商日活起碼得幾十萬到百萬級別,這個APP也是近期產(chǎn)品根據(jù)市場競品分析設計出來的,幾個小碼農(nóng)通宵達旦開發(fā)出來的,主要功能是在線購物一站式服務,后臺可以給各個商家分配權(quán)限,來維護需要售賣的商品信息。

          老板大O:談下來不容易,接下來就是考慮如何吸引終端設備上更多的用戶注冊上來,如何引導用戶購買,這塊就交給小P去負責了,需求盡快做,我明天出差!

          產(chǎn)品小P:嘿嘿~,眼珠一轉(zhuǎn)兒,很容易就想到了,心里想:“這還不簡單,起碼在首頁搞個活動頁... ”。

          技術(shù)小T:很快了解了產(chǎn)品的需求,目前小J主要負責這塊,找了前端和后端同學一起將活動頁搞的快差不多了。

          業(yè)務場景一出現(xiàn)

          因為小T剛接手項目,正在吭哧吭哧對熟悉著代碼、部署架構(gòu)。在看代碼過程中發(fā)現(xiàn),下單這塊代碼可能會出現(xiàn)問題,這可是分布式部署的,如果多個用戶同時購買同一個商品,就可能導致商品出現(xiàn) 庫存超賣 (數(shù)據(jù)不一致) 現(xiàn)象,對于這種情況代碼中并沒有做任何控制。

          原來一問才知道,以前他們都是售賣的虛擬商品,沒啥庫存一說,所以當時沒有考慮那么多...

          這次不一樣啊,這次是售賣的實體商品,那就有庫存這么一說了,起碼要保證不能超過庫存設定的數(shù)量吧。

          小T大眼對著屏幕,屏住呼吸,還好提前發(fā)現(xiàn)了這個問題,趕緊想辦法修復,不賺錢還賠錢,老板不得瘋了,還想不想干了~

          業(yè)務場景二出現(xiàn)

          小T下面的一位兄弟正在壓測,發(fā)現(xiàn)個小問題,因為在終端設備上跟鵝廠有緊密合作,調(diào)用他們的接口時需要獲取到access_token,但是這個access_token過期時間是2小時,過期后需要重新獲取。

          壓測時發(fā)現(xiàn)當?shù)竭_過期時間時,日志看刷出來好幾個不一樣的access_token,因為這個服務也是分布式部署的,多個節(jié)點同時發(fā)起了第三方接口請求導致。

          雖然以最后一次獲取的access_token為準,也沒什么不良副作用,但是會導致多次不必要的對第三方接口的調(diào)用,也會短時間內(nèi)造成access_token的 重復無效獲取(重復工作)。

          業(yè)務場景三出現(xiàn)

          下單完成后,還要通知倉儲物流,待用戶支付完成,支付回調(diào)有可能會將多條訂單消息發(fā)送到MQ,倉儲服務會從MQ消費訂單消息,此時就要 保證冪等性,對訂單消息做 去重 處理。

          以上便于大家理解為什么要用分布式鎖才能解決,勾勒出的幾個業(yè)務場景。

          上面的問題無一例外,都是針對共享資源要求串行化處理,才能保證安全且合理的操作。

          用一張圖來體驗一下:

          此時,使用Java提供的Synchronized、ReentrantLock、ReentrantReadWriteLock...,僅能在單個JVM進程內(nèi)對多線程對共享資源保證線程安全,在分布式系統(tǒng)環(huán)境下統(tǒng)統(tǒng)都不好使,心情是不是拔涼呀。

          這個問題得請教 分布式鎖 家族來支持一下,聽說他們家族內(nèi)有很多成員,每個成員都有這個分布式鎖功能,接下來就開始探索一下。


          2、分布式鎖家族成員介紹

          為什么需要分布式鎖才能解決?

          聽聽 Martin 大佬們給出的說法:

          Martin kleppmann 是英國劍橋大學的分布式系統(tǒng)的研究員,曾經(jīng)跟 Redis 之父 Antirez 進行過關(guān)于 RedLock (Redis里分布式鎖的實現(xiàn)算法)是否安全的激烈討論。

          他們討論了啥,整急眼了?
          都能單獨寫篇文章了

          效率:

          使用分布式鎖可以避免多個客戶端重復相同的工作,這些工作會浪費資源。比如用戶支付完成后,可能會收到多次短信或郵件提醒。

          比如業(yè)務場景二,重復獲取access_token。

          對共享資源的操作是冪等性操作,無論你操作多少次都不會出現(xiàn)不同結(jié)果。
          本質(zhì)上就是為了避免對共享資源重復操作,從而提高效率。

          正確性:

          使用分布式鎖同樣可以避免鎖失效的發(fā)生,一旦發(fā)生會引起正確性的破壞,可能會導致數(shù)據(jù)不一致,數(shù)據(jù)缺失或者其他嚴重的問題。

          比如業(yè)務場景一,商品庫存超賣問題。

          對共享資源的操作是非冪等性操作,多個客戶端操作共享資源會導致數(shù)據(jù)不一致。

          分布式鎖有哪些特點呢?

          以下是分布式鎖的一些特點,分布式鎖家族成員并不一定都滿足這個要求,實現(xiàn)機制不大一樣。

          互斥性: 分布式鎖要保證在多個客戶端之間的互斥。

          可重入性:同一客戶端的相同線程,允許重復多次加鎖。

          鎖超時:和本地鎖一樣支持鎖超時,防止死鎖。

          非阻塞: 能與 ReentrantLock 一樣支持 trylock() 非阻塞方式獲得鎖。

          支持公平鎖和非公平鎖:公平鎖是指按照請求加鎖的順序獲得鎖,非公平鎖真好相反請求加鎖是無序的。

          分布式鎖家族實現(xiàn)者介紹

          分布式鎖家族實現(xiàn)者一覽:

          思維導圖做了一個簡單分類,不一定特別準確,幾乎包含了分布式鎖各個組件實現(xiàn)者。

          下面讓他們分別來做下自我介紹:

          1、數(shù)據(jù)庫

          排它鎖(悲觀鎖):基于 select * from table where xx=yy for update SQL語句來實現(xiàn),有很多缺陷,一般不推薦使用,后文介紹。

          樂觀鎖:表中添加一個時間戳或者版本號的字段來實現(xiàn),update xx set version=new... where id=y and version=old 當更新不成功,客戶端重試,重新讀取最新的版本號或時間戳,再次嘗試更新,類似 CAS 機制,推薦使用。

          2、Redis

          特點:CAP模型屬于AP | 無一致性算法 | 性能好

          開發(fā)常用,如果你的項目中正好使用了redis,不想引入額外的分布式鎖組件,推薦使用。

          業(yè)界也提供了多個現(xiàn)成好用的框架予以支持分布式鎖,比如Redissonspring-integration-redis、redis自帶的setnx命令,推薦直接使用。

          另外,可基于redis命令和redis lua支持的原子特性,自行實現(xiàn)分布式鎖。

          3、Zookeeper

          特點:CAP模型屬于CP | ZAB一致性算法實現(xiàn) | 穩(wěn)定性好

          開發(fā)常用,如果你的項目中正好使用了zk集群,推薦使用。

          業(yè)界有Apache Curator框架提供了現(xiàn)成的分布式鎖功能,現(xiàn)成的,推薦直接使用。

          另外,可基于Zookeeper自身的特性和原生Zookeeper API自行實現(xiàn)分布式鎖。

          4、其他

          Chubby,Google開發(fā)的粗粒度分布鎖的服務,但是并沒有開源,開放出了論文和一些相關(guān)文檔可以進一步了解,出門百度一下獲取文檔,不做過多討論。

          Tair,是阿里開源的一個分布式KV存儲方案,沒有用過,不做過多討論。

          Etcd,CAP模型中屬于CPRaft一致性算法實現(xiàn),沒有用過,不做過多討論。

          Hazelcast,是基于內(nèi)存的數(shù)據(jù)網(wǎng)格開源項目,提供彈性可擴展的分布式內(nèi)存計算,并且被公認是提高應用程序性能和擴展性最好的方案,聽上去很牛逼,但是沒用過,不做過多討論。

          當然了,上面推薦的常用分布式鎖Zookeeper和Redis,使用時還需要根據(jù)具體的業(yè)務場景,做下權(quán)衡,實現(xiàn)功能上都能達到你要的效果,原理上有很大的不同。

          畫外音: 你對哪個熟悉,原理也都了解,hold住,你就用哪個。

          3、分布式鎖成員實現(xiàn)原理剖析

          數(shù)據(jù)庫悲觀鎖實現(xiàn)

          以「悲觀的心態(tài)」操作資源,無法獲得鎖成功,就一直阻塞著等待。

          1、有一張資源鎖表

          CREATE TABLE `resource_lock` (
            `id` int(4) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
            `resource_name` varchar(64) NOT NULL DEFAULT '' COMMENT '鎖定的資源名',
            `owner` varchar(64) NOT NULL DEFAULT '' COMMENT '鎖擁有者',
            `desc` varchar(1024) NOT NULL DEFAULT '備注信息',
            `update_time` timestamp NOT NULL DEFAULT '' COMMENT '保存數(shù)據(jù)時間,自動生成',
            PRIMARY KEY (`id`),
            UNIQUE KEY `uidx_resource_name` (`resource_name `) USING BTREE
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='鎖定中的資源';

          resource_name 鎖資源名稱必須有唯一索引。

          2、使用姿勢

          必須添加事務,查詢和更新操作保證原子性,在一個事務里完成。

          偽代碼實現(xiàn):

          @Transaction
          public void lock(String name) {
             ResourceLock rlock=exeSql("select * from resource_lock where resource_name=name for update");
               if (rlock==null) {
                     exeSql("insert into resource_lock(reosurce_name,owner,count) values (name, 'ip',0)");
               } 
          }

          使用 for update 鎖定的資源。
          如果執(zhí)行成功,會立即返回,執(zhí)行插入數(shù)據(jù)庫,后續(xù)再執(zhí)行一些其他業(yè)務邏輯,直到事務提交,執(zhí)行結(jié)束;
          如果執(zhí)行失敗,就會一直阻塞著。

          你也可以在數(shù)據(jù)庫客戶端工具上測試出來這個效果,當在一個終端執(zhí)行了 for update,不提交事務。
          在另外的終端上執(zhí)行相同條件的 for update,會一直卡著,轉(zhuǎn)圈圈...

          雖然也能實現(xiàn)分布式鎖的效果,但是會存在性能瓶頸。

          3、悲觀鎖優(yōu)缺點

          優(yōu)點:簡單易用,好理解,保障數(shù)據(jù)強一致性。

          缺點一大堆,羅列一下:

          1)在 RR 事務級別,select 的 for update 操作是基于間隙鎖(gap lock) 實現(xiàn)的,是一種悲觀鎖的實現(xiàn)方式,所以存在阻塞問題。

          2)高并發(fā)情況下,大量請求進來,會導致大部分請求進行排隊,影響數(shù)據(jù)庫穩(wěn)定性,也會耗費服務的CPU等資源。

          當獲得鎖的客戶端等待時間過長時,會提示:

          [40001][1205] Lock wait timeout exceeded; try restarting transaction

          高并發(fā)情況下,也會造成占用過多的應用線程,導致業(yè)務無法正常響應。

          3)如果優(yōu)先獲得鎖的線程因為某些原因,一直沒有釋放掉鎖,可能會導致死鎖的發(fā)生。

          4)鎖的長時間不釋放,會一直占用數(shù)據(jù)庫連接,可能會將數(shù)據(jù)庫連接池撐爆,影響其他服務。

          1. MySql數(shù)據(jù)庫會做查詢優(yōu)化,即便使用了索引,優(yōu)化時發(fā)現(xiàn)全表掃效率更高,則可能會將行鎖升級為表鎖,此時可能就更悲劇了。

          6)不支持可重入特性,并且超時等待時間是全局的,不能隨便改動。

          數(shù)據(jù)庫樂觀鎖實現(xiàn)

          樂觀鎖,以「樂觀的心態(tài)」來操作共享資源,無法獲得鎖成功,沒關(guān)系過一會重試一下看看唄,再不行就直接退出,嘗試一定次數(shù)還是不行?也可以以后再說,不用一直阻塞等著。

          1、有一張資源表

          為表添加一個字段,版本號或者時間戳都可以。通過版本號或者時間戳,來保證多線程同時間操作共享資源的有序性和正確性。

          CREATE TABLE `resource` (
            `id` int(4) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
            `resource_name` varchar(64) NOT NULL DEFAULT '' COMMENT '資源名',
            `share` varchar(64) NOT NULL DEFAULT '' COMMENT '狀態(tài)',
              `version` int(4) NOT NULL DEFAULT '' COMMENT '版本號',
            `desc` varchar(1024) NOT NULL DEFAULT '備注信息',
            `update_time` timestamp NOT NULL DEFAULT '' COMMENT '保存數(shù)據(jù)時間,自動生成',
            PRIMARY KEY (`id`),
            UNIQUE KEY `uidx_resource_name` (`resource_name `) USING BTREE
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='資源';

          2、使用姿勢

          偽代碼實現(xiàn):

          Resrouce resource=exeSql("select * from resource where resource_name=xxx");
          boolean succ=exeSql("update resource set version='newVersion' ... where resource_name=xxx and version='oldVersion'");
          
          if (!succ) {
              // 發(fā)起重試
          }

          實際代碼中可以寫個while循環(huán)不斷重試,版本號不一致,更新失敗,重新獲取新的版本號,直到更新成功。

          3、樂觀鎖優(yōu)缺點

          優(yōu)點:簡單易用,保障數(shù)據(jù)一致性。

          缺點:

          1)加行鎖的性能上有一定的開銷

          2)高并發(fā)場景下,線程內(nèi)的自旋操作 會耗費一定的CPU資源。

          另外,比如在更新數(shù)據(jù)狀態(tài)的一些場景下,不考慮冪等性的情況下,可以直接利用 行鎖 來保證數(shù)據(jù)一致性,示例:update table set state=1 where id=xxx and state=0;

          樂觀鎖就類似 CAS Compare And Swap 更新機制,推薦閱讀 <<一文徹底搞懂CAS>>


          基于Redis分布式鎖實現(xiàn)

          基于SetNX實現(xiàn)分布式鎖

          基于Redis實現(xiàn)的分布式鎖,性能上是最好的,實現(xiàn)上也是最復雜的。

          前文中提到的 RedLock 是 Redis 之父 Antirez 提出來的分布式鎖的一種 「健壯」 的實現(xiàn)算法,但爭議也較多,一般不推薦使用。

          Redis 2.6.12 之前的版本中采用 setnx + expire 方式實現(xiàn)分布式鎖,示例代碼如下所示:

          public static boolean lock(Jedis jedis, String lockKey, String requestId, int expireTime) {
                  Long result=jedis.setnx(lockKey, requestId);
                  //設置鎖
                  if (result==1) {
                      //獲取鎖成功
                      //若在這里程序突然崩潰,則無法設置過期時間,將發(fā)生死鎖
                      //通過過期時間刪除鎖
                      jedis.expire(lockKey, expireTime);
                      return true;
                  }
                  return false;
              }

          如果 lockKey 存在,則返回失敗,否則返回成功。設置成功之后,為了能在完成同步代碼之后成功釋放鎖,方法中使用 expire() 方法給 lockKey 設置一個過期時間,確認 key 值刪除,避免出現(xiàn)鎖無法釋放,導致下一個線程無法獲取到鎖,即死鎖問題。

          但是 setnx + expire 兩個命令放在程序里執(zhí)行,不是原子操作,容易出事。

          如果程序設置鎖之后,此時,在設置過期時間之前,程序崩潰了,如果 lockKey 沒有設置上過期時間,將會出現(xiàn)死鎖問題。

          解決以上問題 ,有兩個辦法:

          1)方式一:lua腳本

          我們也可以通過 Lua 腳本來實現(xiàn)鎖的設置和過期時間的原子性,再通過 jedis.eval() 方法運行該腳本:

          // 加鎖腳本,KEYS[1] 要加鎖的key,ARGV[1]是UUID隨機值,ARGV[2]是過期時間
          private static final String SCRIPT_LOCK="if redis.call('setnx', KEYS[1], ARGV[1])==1 then redis.call('pexpire', KEYS[1], ARGV[2]) return 1 else return 0 end";
          
          // 解鎖腳本,KEYS[1]要解鎖的key,ARGV[1]是UUID隨機值
          private static final String SCRIPT_UNLOCK="if redis.call('get', KEYS[1])==ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

          2)方式二:set原生命令

          在 Redis 2.6.12 版本后 SETNX 增加了過期時間參數(shù):

          SET lockKey anystring NX PX max-lock-time

          程序?qū)崿F(xiàn)代碼如下:

          public static boolean lock(Jedis jedis, String lockKey, String requestId, int expireTime) {
                  String result=jedis.set(lockKey, requestId, "NX", "PX", expireTime);
                  if ("OK".equals(result)) {
                      return true;
                  }
                  return false;
              }

          雖然 SETNX 方式能夠保證設置鎖和過期時間的原子性,但是如果我們設置的過期時間比較短,而執(zhí)行業(yè)務時間比較長,就會存在鎖代碼塊失效的問題,失效后其他客戶端也能獲取到同樣的鎖,執(zhí)行同樣的業(yè)務,此時可能就會出現(xiàn)一些問題。

          我們需要將過期時間設置得足夠長,來保證以上問題不會出現(xiàn),但是設置多長時間合理,也需要依具體業(yè)務來權(quán)衡。如果其他客戶端必須要阻塞拿到鎖,需要設計循環(huán)超時等待機制等問題,感覺還挺麻煩的是吧。

          Spring企業(yè)集成模式實現(xiàn)分布式鎖

          除了使用Jedis客戶端之外,完全可以直接用Spring官方提供的企業(yè)集成模式框架,里面提供了很多分布式鎖的方式,Spring提供了一個統(tǒng)一的分布式鎖抽象,具體實現(xiàn)目前支持:

          • Gemfire
          • Jdbc
          • Zookeeper
          • Redis

          早期,分布式鎖的相關(guān)代碼存在于Spring Cloud的子項目Spring Cloud Cluster中,后來被遷到Spring Integration中。

          Spring Integration 項目地址 :https://github.com/spring-projects/spring-integration

          Spring強大之處在于此,對Lock分布式鎖做了全局抽象。

          抽象結(jié)構(gòu)如下所示:

          LockRegistry 作為頂層抽象接口:

          /**
           * Strategy for maintaining a registry of shared locks
           *
           * @author Oleg Zhurakousky
           * @author Gary Russell
           * @since 2.1.1
           */
          @FunctionalInterface
          public interface LockRegistry {
          
              /**
               * Obtains the lock associated with the parameter object.
               * @param lockKey The object with which the lock is associated.
               * @return The associated lock.
               */
              Lock obtain(Object lockKey);
          
          }

          定義的 obtain() 方法獲得具體的 Lock 實現(xiàn)類,分別在對應的 XxxLockRegitry 實現(xiàn)類來創(chuàng)建。

          RedisLockRegistry 里obtain()方法實現(xiàn)類為 RedisLock,RedisLock內(nèi)部,在Springboot2.x(Spring5)版本中是通過SET + PEXIPRE 命令結(jié)合lua腳本實現(xiàn)的,在Springboot1.x(Spring4)版本中,是通過SETNX命令實現(xiàn)的。

          ZookeeperLockRegistry 里obtain()方法實現(xiàn)類為 ZkLock,ZkLock內(nèi)部基于 Apache Curator 框架實現(xiàn)的。

          JdbcLockRegistry 里obtain()方法實現(xiàn)類為 JdbcLock,JdbcLock內(nèi)部基于一張INT_LOCK數(shù)據(jù)庫鎖表實現(xiàn)的,通過JdbcTemplate來操作。

          客戶端使用方法:

          private final String registryKey="sb2";
          RedisLockRegistry lockRegistry=new RedisLockRegistry(getConnectionFactory(), this.registryKey);
          Lock lock=lockRegistry.obtain("foo");
          lock.lock();
          try {
              // doSth...
          }
          finally {
              lock.unlock();
          }
          }

          下面以目前最新版本的實現(xiàn),說明加鎖和解鎖的具體過程。

          RedisLockRegistry$RedisLock類lock()加鎖流程:

          加鎖步驟:

          1)lockKey為registryKey:path,本例中為sb2:foo,客戶端C1優(yōu)先申請加鎖。

          2)執(zhí)行l(wèi)ua腳本,get lockKey不存在,則set lockKey成功,值為clientid(UUID),過期時間默認60秒。

          3)客戶端C1同一個線程重復加鎖,pexpire lockKey,重置過期時間為60秒。

          4)客戶端C2申請加鎖,執(zhí)行l(wèi)ua腳本,get lockKey已存在,并且跟已加鎖的clientid不同,加鎖失敗

          5)客戶端C2掛起,每隔100ms再次嘗試加鎖。

          RedisLock#lock()加鎖源碼實現(xiàn):

          大家可以對照上面的流程圖配合你理解。

          @Override
          public void lock() {
              this.localLock.lock();
              while (true) {
                  try {
                      while (!obtainLock()) {
                          Thread.sleep(100); //NOSONAR
                      }
                      break;
                  }
                  catch (InterruptedException e) {
                      /*
                       * This method must be uninterruptible so catch and ignore
                       * interrupts and only break out of the while loop when
                       * we get the lock.
                       */
                  }
                  catch (Exception e) {
                      this.localLock.unlock();
                      rethrowAsLockException(e);
                  }
              }
          }
          
          // 基于Spring封裝的RedisTemplate來操作的
          private boolean obtainLock() {
              Boolean success=    RedisLockRegistry.this.redisTemplate.execute(RedisLockRegistry.this.obtainLockScript,
                              Collections.singletonList(this.lockKey), RedisLockRegistry.this.clientId,
                              String.valueOf(RedisLockRegistry.this.expireAfter));
          
              boolean result=Boolean.TRUE.equals(success);
          
              if (result) {
                  this.lockedAt=System.currentTimeMillis();
              }
              return result;
          }

          執(zhí)行的lua腳本代碼:

          private static final String OBTAIN_LOCK_SCRIPT="local lockClientId=redis.call('GET', KEYS[1])\n" +
                      "if lockClientId==ARGV[1] then\n" +
                      "  redis.call('PEXPIRE', KEYS[1], ARGV[2])\n" +
                      "  return true\n" +
                      "elseif not lockClientId then\n" +
                      "  redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])\n" +
                      "  return true\n" +
                      "end\n" +
                      "return false";

          RedisLockRegistry$RedisLock類unlock()解鎖流程:

          RedisLock#unlock()源碼實現(xiàn):

          @Override
          public void unlock() {
              if (!this.localLock.isHeldByCurrentThread()) {
                  throw new IllegalStateException("You do not own lock at " + this.lockKey);
              }
              if (this.localLock.getHoldCount() > 1) {
                  this.localLock.unlock();
                  return;
              }
              try {
                  if (!isAcquiredInThisProcess()) {
                      throw new IllegalStateException("Lock was released in the store due to expiration. " +
                              "The integrity of data protected by this lock may have been compromised.");
                  }
          
                  if (Thread.currentThread().isInterrupted()) {
                      RedisLockRegistry.this.executor.execute(this::removeLockKey);
                  }
                  else {
                      removeLockKey();
                  }
          
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug("Released lock; " + this);
                  }
              }
              catch (Exception e) {
                  ReflectionUtils.rethrowRuntimeException(e);
              }
              finally {
                  this.localLock.unlock();
              }
          }
          
          // 刪除緩存Key
          private void removeLockKey() {
              if (this.unlinkAvailable) {
                  try {
                      RedisLockRegistry.this.redisTemplate.unlink(this.lockKey);
                  }
                  catch (Exception ex) {
                      LOGGER.warn("The UNLINK command has failed (not supported on the Redis server?); " +
                              "falling back to the regular DELETE command", ex);
                      this.unlinkAvailable=false;
                      RedisLockRegistry.this.redisTemplate.delete(this.lockKey);
                  }
              }
              else {
                  RedisLockRegistry.this.redisTemplate.delete(this.lockKey);
              }
          }

          unlock()解鎖方法里發(fā)現(xiàn),并不是直接就調(diào)用Redis的DEL命令刪除Key,這也是在Springboot2.x版本中做的一個優(yōu)化,Redis4.0版本以上提供了UNLINK命令。

          換句話說,最新版本分布式鎖實現(xiàn),要求是Redis4.0以上版本才能使用。

          看下Redis官網(wǎng)給出的一段解釋:

          This command is very similar to DEL: it removes the specified keys.
          Just like DEL a key is ignored if it does not exist. However the
          command performs the actual memory reclaiming in a different thread,
          so it is not blocking, while DEL is. This is where the command name
          comes from: the command just unlinks the keys from the keyspace. The
          actual removal will happen later asynchronously.

          DEL始終在阻止模式下釋放值部分。但如果該值太大,如對于大型LIST或HASH的分配太多,它會長時間阻止Redis,為了解決這個問題,Redis實現(xiàn)了UNLINK命令,即「非阻塞」刪除。如果值很小,則DEL一般與UNLINK效率上差不多。

          本質(zhì)上,這種加鎖方式還是使用的SETNX實現(xiàn)的,而且Spring只是做了一層薄薄的封裝,支持可重入加鎖,超時等待,可中斷加鎖。

          但是有個問題,鎖的過期時間不能靈活設置,客戶端初始化時,創(chuàng)建RedisLockRegistry時允許設置,但是是全局的。

          /**
               * Constructs a lock registry with the supplied lock expiration.
               * @param connectionFactory The connection factory.
               * @param registryKey The key prefix for locks.
               * @param expireAfter The expiration in milliseconds.
               */
          public RedisLockRegistry(RedisConnectionFactory connectionFactory, String registryKey, long expireAfter) {
              Assert.notNull(connectionFactory, "'connectionFactory' cannot be null");
              Assert.notNull(registryKey, "'registryKey' cannot be null");
              this.redisTemplate=new StringRedisTemplate(connectionFactory);
              this.obtainLockScript=new DefaultRedisScript<>(OBTAIN_LOCK_SCRIPT, Boolean.class);
              this.registryKey=registryKey;
              this.expireAfter=expireAfter;
          }

          expireAfter參數(shù)是全局的,同樣會存在問題,可能是鎖過期時間到了,但是業(yè)務還沒有處理完,這把鎖又被另外的客戶端獲得,進而會導致一些其他問題。

          經(jīng)過對源碼的分析,其實我們也可以借鑒RedisLockRegistry實現(xiàn)的基礎(chǔ)上,自行封裝實現(xiàn)分布式鎖,比如:

          1、允許支持按照不同的Key設置過期時間,而不是全局的?

          2、當業(yè)務沒有處理完成,當前客戶端啟動個定時任務探測,自動延長過期時間?

          自己實現(xiàn)?嫌麻煩?別急別急!業(yè)界已經(jīng)有現(xiàn)成的實現(xiàn)方案了,那就是 Redisson 框架!

          站在Redis集群角度看問題

          從Redis主從架構(gòu)上來考慮,依然存在問題。因為 Redis 集群數(shù)據(jù)同步到各個節(jié)點時是異步的,如果在 Master 節(jié)點獲取到鎖后,在沒有同步到其它節(jié)點時,Master 節(jié)點崩潰了,此時新的 Master 節(jié)點依然可以獲取鎖,所以多個應用服務可以同時獲取到鎖。

          基于以上的考慮,Redis之父Antirez提出了一個RedLock算法。

          RedLock算法實現(xiàn)過程分析:

          假設Redis部署模式是Redis Cluster,總共有5個master節(jié)點,通過以下步驟獲取一把鎖:

          1)獲取當前時間戳,單位是毫秒

          2)輪流嘗試在每個master節(jié)點上創(chuàng)建鎖,過期時間設置較短,一般就幾十毫秒

          3)嘗試在大多數(shù)節(jié)點上建立一個鎖,比如5個節(jié)點就要求是3個節(jié)點(n / 2 +1)

          4)客戶端計算建立好鎖的時間,如果建立鎖的時間小于超時時間,就算建立成功了

          5)要是鎖建立失敗了,那么就依次刪除這個鎖

          6)只要有客戶端創(chuàng)建成功了分布式鎖,其他客戶端就得不斷輪詢?nèi)L試獲取鎖

          以上過程前文也提到了,進一步分析RedLock算法的實現(xiàn)依然可能存在問題,也是Martain和Antirez兩位大佬爭論的焦點。

          問題1:節(jié)點崩潰重啟

          節(jié)點崩潰重啟,會出現(xiàn)多個客戶端持有鎖。

          假設一共有5個Redis節(jié)點:A、B、 C、 D、 E。設想發(fā)生了如下的事件序列:

          1)客戶端C1成功對Redis集群中A、B、C三個節(jié)點加鎖成功(但D和E沒有鎖住)。

          2)節(jié)點C Duang的一下,崩潰重啟了,但客戶端C1在節(jié)點C加鎖未持久化完,丟了。

          3)節(jié)點C重啟后,客戶端C2成功對Redis集群中C、D、 E嘗試加鎖成功了。

          這樣,悲劇了吧!客戶端C1和C2同時獲得了同一把分布式鎖。

          為了應對節(jié)點重啟引發(fā)的鎖失效問題,Antirez提出了延遲重啟的概念,即一個節(jié)點崩潰后,先不立即重啟它,而是等待一段時間再重啟,等待的時間大于鎖的有效時間。

          采用這種方式,這個節(jié)點在重啟前所參與的鎖都會過期,它在重啟后就不會對現(xiàn)有的鎖造成影響。

          這其實也是通過人為補償措施,降低不一致發(fā)生的概率。

          問題2:時鐘跳躍

          假設一共有5個Redis節(jié)點:A、B、 C、 D、 E。設想發(fā)生了如下的事件序列:

          1)客戶端C1成功對Redis集群中A、B、 C三個節(jié)點成功加鎖。但因網(wǎng)絡問題,與D和E通信失敗。

          2)節(jié)點C上的時鐘發(fā)生了向前跳躍,導致它上面維護的鎖快速過期。

          3)客戶端C2對Redis集群中節(jié)點C、 D、 E成功加了同一把鎖。

          此時,又悲劇了吧!客戶端C1和C2同時都持有著同一把分布式鎖。

          為了應對時鐘跳躍引發(fā)的鎖失效問題,Antirez提出了應該禁止人為修改系統(tǒng)時間,使用一個不會進行「跳躍式」調(diào)整系統(tǒng)時鐘的ntpd程序。這也是通過人為補償措施,降低不一致發(fā)生的概率。

          但是...,RedLock算法并沒有解決,操作共享資源超時,導致鎖失效的問題。

          存在這么大爭議的算法實現(xiàn),還是不推薦使用的。

          一般情況下,本文鎖介紹的框架提供的分布式鎖實現(xiàn)已經(jīng)能滿足大部分需求了。

          小結(jié):

          上述,我們對spring-integration-redis實現(xiàn)原理進行了深入分析,還對RedLock存在爭議的問題做了分析。

          除此以外,我們還提到了spring-integration中集成了 Jdbc、Zookeeper、Gemfire實現(xiàn)的分布式鎖,Gemfire和Jdbc大家感興趣可以自行去看下。

          為啥還要提供個Jdbc分布式鎖實現(xiàn)?

          猜測一下,當你的應用并發(fā)量也不高,比如是個后臺業(yè)務,而且還沒依賴Zookeeper、Redis等額外的組件,只依賴了數(shù)據(jù)庫。

          但你還想用分布式鎖搞點事兒,那好辦,直接用spring-integration-jdbc即可,內(nèi)部也是基于數(shù)據(jù)庫行鎖來實現(xiàn)的,需要你提前建好鎖表,創(chuàng)建表的SQL長這樣:

          CREATE TABLE INT_LOCK  (
              LOCK_KEY CHAR(36) NOT NULL,
              REGION VARCHAR(100) NOT NULL,
              CLIENT_ID CHAR(36),
              CREATED_DATE DATETIME(6) NOT NULL,
              constraint INT_LOCK_PK primary key (LOCK_KEY, REGION)
          ) ENGINE=InnoDB;

          具體實現(xiàn)邏輯也非常簡單,大家自己去看吧。

          集成的Zookeeper實現(xiàn)的分布式鎖,因為是基于Curator框架實現(xiàn)的,不在本節(jié)展開,后續(xù)會有分析。

          基于Redisson實現(xiàn)分布式鎖

          Redisson 是 Redis 的 Java 實現(xiàn)的客戶端,其 API 提供了比較全面的 Redis 命令的支持。

          Jedis 簡單使用阻塞的 I/O 和 Redis 交互,Redission 通過 Netty 支持非阻塞 I/O。

          Redisson 封裝了鎖的實現(xiàn),讓我們像操作我們的本地 Lock 一樣去使用,除此之外還有對集合、對象、常用緩存框架等做了友好的封裝,易于使用。

          截止目前,Github上 Star 數(shù)量為 11.8k,說明該開源項目值得關(guān)注和使用。

          Redisson分布式鎖Github:

          https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers

          Redisson 可以便捷的支持多種Redis部署架構(gòu):

          1. Redis 單機
          2. Master-Slave + Sentinel 哨兵
          3. Redis-Cluster集群
          // Master-Slave配置
          Config config=new Config();
          MasterSlaveServersConfig serverConfig=config.useMasterSlaveServers()
                      .setMasterAddress("")
                      .addSlaveAddress("")
                      .setReadMode(ReadMode.SLAVE)
                      .setMasterConnectionPoolSize(maxActiveSize)
                      .setMasterConnectionMinimumIdleSize(maxIdleSize)
                      .setSlaveConnectionPoolSize(maxActiveSize)
                      .setSlaveConnectionMinimumIdleSize(maxIdleSize)
                      .setConnectTimeout(CONNECTION_TIMEOUT_MS) // 默認10秒
                      .setTimeout(socketTimeout)
                      ;
          
          RedissonClient redisson=Redisson.create(config);
          RLock lock=redisson.getLock("myLock");
          
          // 獲得鎖
          lock.lock();
          
          // 等待10秒未獲得鎖,自動釋放
          lock.lock(10, TimeUnit.SECONDS);
          
          // 等待鎖定時間不超過100秒
          // 10秒后自動釋放鎖
          boolean res=lock.tryLock(100, 10, TimeUnit.SECONDS);
          if (res) {
             try {
               ...
             } finally {
                 lock.unlock();
             }
          }

          使用上非常簡單,RedissonClient客戶端提供了眾多的接口實現(xiàn),支持可重入鎖、、公平鎖、讀寫鎖、鎖超時、RedLock等都提供了完整實現(xiàn)。

          lock()加鎖流程:

          為了兼容老的版本,Redisson里都是通過lua腳本執(zhí)行Redis命令的,同時保證了原子性操作。

          加鎖執(zhí)行的lua腳本:

          Redis里的Hash散列結(jié)構(gòu)存儲的。

          參數(shù)解釋:

          KEY[1]:要加鎖的Key名稱,比如示例中的myLock。

          ARGV[1]:針對加鎖的Key設置的過期時間

          ARGV[2]:Hash結(jié)構(gòu)中Key名稱,lockName為UUID:線程ID

          protected String getLockName(long threadId) {
                 return id + ":" + threadId;
          }

          1)客戶端C1申請加鎖,key為myLock。

          2)如果key不存在,通過hset設置值,通過pexpire設置過期時間。同時開啟Watchdog任務,默認每隔10秒中判斷一下,如果key還在,重置過期時間到30秒。

          開啟WatchDog源碼:

          3)客戶端C1相同線程再次加鎖,如果key存在,判斷Redis里Hash中的lockName跟當前線程lockName相同,則將Hash中的lockName的值加1,代表支持可重入加鎖。

          4)客戶單C2申請加鎖,如果key存在,判斷Redis里Hash中的lockName跟當前線程lockName不同,則執(zhí)行pttl返回剩余過期時間。

          5)客戶端C2線程內(nèi)不斷嘗試pttl時間,此處是基于Semaphore信號量實現(xiàn)的,有許可立即返回,否則等到pttl時間還是沒有得到許可,繼續(xù)重試。

          重試源碼:

          Redisson這樣的實現(xiàn)就解決了,當業(yè)務處理時間比過期時間長的問題。

          同時,Redisson 還自己擴展 Lock 接口,叫做 RLock 接口,擴展了很多的鎖接口,比如給 Key 設定過期時間,非阻塞+超時時間等。

          void lock(long leaseTime, TimeUnit unit);
          
          boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;

          redisson里的WatchDog(看門狗)邏輯保證了沒有死鎖發(fā)生。

          如果客戶端宕機了,WatchDog任務也就跟著停掉了。此時,不會對Key重置過期時間了,等掛掉的客戶端持有的Key過期時間到了,鎖自動釋放,其他客戶端嘗試獲得這把鎖。

          可以進一步看官網(wǎng)的關(guān)于WatchDog描述:

          If Redisson instance which acquired lock crashes then such lock could hang forever in acquired state. To avoid this Redisson maintains lock watchdog, it prolongs lock expiration while lock holder Redisson instance is alive. By default lock watchdog timeout is 30 seconds and can be changed through Config.lockWatchdogTimeout setting.

          unlock()解鎖過程也是同樣的,通過lua腳本執(zhí)行一大坨指令的。

          解鎖lua腳本:


          解鎖lua腳本

          根據(jù)剛剛對加鎖過程的分析,大家可以自行看下腳本分析下。

          基于Zookeeper實現(xiàn)分布式鎖

          Zookeeper 是一種提供「分布式服務協(xié)調(diào)」的中心化服務,是以 Paxos 算法為基礎(chǔ)實現(xiàn)的。Zookeeper數(shù)據(jù)節(jié)點和文件目錄類似,同時具有Watch機制,基于這兩個特性,得以實現(xiàn)分布式鎖功能。

          數(shù)據(jù)節(jié)點:

          順序臨時節(jié)點:Zookeeper 提供一個多層級的節(jié)點命名空間(節(jié)點稱為 Znode),每個節(jié)點都用一個以斜杠(/)分隔的路徑來表示,而且每個節(jié)點都有父節(jié)點(根節(jié)點除外),非常類似于文件系統(tǒng)。

          節(jié)點類型可以分為持久節(jié)點(PERSISTENT )、臨時節(jié)點(EPHEMERAL),每個節(jié)點還能被標記為有序性(SEQUENTIAL),一旦節(jié)點被標記為有序性,那么整個節(jié)點就具有順序自增的特點。

          一般我們可以組合這幾類節(jié)點來創(chuàng)建我們所需要的節(jié)點,例如,創(chuàng)建一個持久節(jié)點作為父節(jié)點,在父節(jié)點下面創(chuàng)建臨時節(jié)點,并標記該臨時節(jié)點為有序性。

          Watch 機制:

          Zookeeper 還提供了另外一個重要的特性,Watcher(事件監(jiān)聽器)。

          ZooKeeper 允許用戶在指定節(jié)點上注冊一些 Watcher,并且在一些特定事件觸發(fā)的時候,ZooKeeper 服務端會將事件通知給用戶。

          圖解Zookeeper實現(xiàn)分布式鎖:


          zk臨時順序節(jié)點機制

          首先,我們需要建立一個父節(jié)點,節(jié)點類型為持久節(jié)點(PERSISTENT)如圖中的 /locks/lock_name1 節(jié)點 ,每當需要訪問共享資源時,就會在父節(jié)點下建立相應的順序子節(jié)點,節(jié)點類型為臨時節(jié)點(EPHEMERAL),且標記為有序性(SEQUENTIAL),并且以臨時節(jié)點名稱 + 父節(jié)點名稱 + 順序號組成特定的名字,如圖中的 /0000000001 /0000000002 /0000000003 作為臨時有序節(jié)點。

          在建立子節(jié)點后,對父節(jié)點下面的所有以臨時節(jié)點名稱 name 開頭的子節(jié)點進行排序,判斷剛剛建立的子節(jié)點順序號是否是最小的節(jié)點,如果是最小節(jié)點,則獲得鎖。

          如果不是最小節(jié)點,則阻塞等待鎖,并且獲得該節(jié)點的上一順序節(jié)點,為其注冊監(jiān)聽事件,等待節(jié)點對應的操作獲得鎖。當調(diào)用完共享資源后,刪除該節(jié)點,關(guān)閉 zk,進而可以觸發(fā)監(jiān)聽事件,釋放該鎖。

          // 加鎖
          InterProcessMutex lock=new InterProcessMutex(client, lockPath);
          if ( lock.acquire(maxWait, waitUnit) ) 
          {
              try 
              {
                  // do some work inside of the critical section here
              }
              finally
              {
                  lock.release();
              }
          }
          
          public void acquire() throws Exception
              {
                      if ( !internalLock(-1, null) )
                      {
                              throw new IOException("Lost connection while trying to acquire lock: " + basePath);
                      }
              }
          
          private boolean internalLock(long time, TimeUnit unit) throws Exception
              {
                      /*
                           Note on concurrency: a given lockData instance
                           can be only acted on by a single thread so locking isn't necessary
                      */
          
                      Thread currentThread=Thread.currentThread();
          
                      LockData lockData=threadData.get(currentThread);
                      if ( lockData !=null )
                      {
                              // re-entering
                              lockData.lockCount.incrementAndGet();
                              return true;
                      }
          
                      String lockPath=internals.attemptLock(time, unit, getLockNodeBytes());
                      if ( lockPath !=null )
                      {
                              LockData newLockData=new LockData(currentThread, lockPath);
                              threadData.put(currentThread, newLockData);
                              return true;
                      }
          
                      return false;
              }
          // ... 其他代碼略

          InterProcessMutex 是 Curator 實現(xiàn)的可重入鎖,可重入鎖源碼過程分析:

          加鎖流程:

          1)可重入鎖記錄在 ConcurrentMap<Thread, LockData> threadData 這個 Map 里面。

          2)如果 threadData.get(currentThread) 是有值的那么就證明是可重入鎖,然后記錄就會加 1。

          3)資源目錄下創(chuàng)建一個節(jié)點:比如這里創(chuàng)建一個 /0000000002 這個節(jié)點,這個節(jié)點需要設置為 EPHEMERAL_SEQUENTIAL 也就是臨時節(jié)點并且有序。

          4)獲取當前目錄下所有子節(jié)點,判斷自己的節(jié)點是否是最小的節(jié)點。

          5)如果是最小的節(jié)點,則獲取到鎖。如果不是最小的節(jié)點,則證明前面已經(jīng)有人獲取到鎖了,那么需要獲取自己節(jié)點的前一個節(jié)點。

          6)節(jié)點 /0000000002 的前一個節(jié)點是 /0000000001,我們獲取到這個節(jié)點之后,再上面注冊 Watcher,Watcher 調(diào)用的是 object.notifyAll(),用來解除阻塞。

          7)object.wait(timeout) 或 object.wait() 進行阻塞等待

          解鎖流程:

          1)如果可重入鎖次數(shù)減1后,加鎖次數(shù)不為 0 直接返回,減1后加鎖次數(shù)為0,繼續(xù)。

          2)刪除當前節(jié)點。

          3)刪除 threadDataMap 里面的可重入鎖的數(shù)據(jù)。

          最后的總結(jié)

          上面介紹的諸如Apache Curator、Redisson、Spring框架集成的分布式鎖,既然是框架實現(xiàn),會考慮用戶需求,盡量設計和實現(xiàn)通用的分布式鎖接口。

          基本都涵蓋了如下的方式實現(xiàn):


          分布式鎖實現(xiàn)接口

          當然,Redisson和Curator都是自己定義的分布式鎖接口實現(xiàn)的,易于擴展。

          Curator里自定義了InterProcessLock接口,Redisson里自定義RLock接口,繼承了 java.util.concurrent.locks.Lock接口。

          對于Redis實現(xiàn)的分布式鎖:

          大部分需求下,不會遇到「極端復雜場景」,基于Redis實現(xiàn)分布式鎖很常用,性能也高。

          它獲取鎖的方式簡單粗暴,獲取不到鎖直接不斷嘗試獲取鎖,比較消耗性能。

          另外來說的話,redis的設計定位決定了它的數(shù)據(jù)并不是強一致性的,沒有一致性算法,在某些極端情況下,可能會出現(xiàn)問題,鎖的模型不夠健壯。

          即便有了Redlock算法的實現(xiàn),但存在爭議,某些復雜場景下,也無法保證其實現(xiàn)完全沒有問題,并且也是比較消耗性能的。

          對于Zookeeper實現(xiàn)的分布式鎖:

          Zookeeper優(yōu)點:

          天生設計定位是分布式協(xié)調(diào),強一致性。鎖的模型健壯、簡單易用、適合做分布式鎖。

          如果獲取不到鎖,只需要添加一個監(jiān)聽器就可以了,不用一直輪詢,性能消耗較小。

          如果客戶端宕機,也沒關(guān)系,臨時節(jié)點會自動刪除,觸發(fā)監(jiān)聽器通知下一個節(jié)點。

          Zookeeper缺點:

          若有大量的客戶端頻繁的申請加鎖、釋放鎖,對于ZK集群的壓力會比較大。

          另外,本文對spring-integration集成redis做了詳細分析,推薦可以直接使用,更推薦直接使用 Redisson,實現(xiàn)了非常多的分布式鎖各種機制,有單獨開放Springboot集成的jar包,使用上也是非常方便的。

          文章開頭部分提到的幾個業(yè)務場景,經(jīng)過對分布式鎖家族的介紹和原理分析,可以自行選擇技術(shù)方案了。

          以上,一定有一款能滿足你的需求,希望大家有所收獲!

          章我們就來說說網(wǎng)頁中的交互必備屬性, 表單 form, 這個是頁面交互用戶錄入的關(guān)鍵知識點, 在 Vue.js 中使用指令 v-model 進行數(shù)據(jù)的雙向綁定。它會根據(jù)控件類型自動的更新數(shù)據(jù)到頁面中。需要注意的是, 在數(shù)據(jù)初始化的時候, v-model 會忽略頁面中自帶有的屬性值, 只會使用組件中的 data 函數(shù)進行數(shù)據(jù)的初始化。

          本章思維導圖

          Form 表單包含的元素有:

          • <button> 按鈕
          • <datalist> 下拉列表,(需較新瀏覽器支持)
          • <fielddset> 對表單的控制元素進行分組 內(nèi)置了一個 <legend> 元素作為標題。(需較新瀏覽器支持)
          • <input> 表單元素, 根據(jù)類型又分為: text, checkbox, radio, submit, reset, button, file。
          • <select> 下拉選擇 可以使用 <optgroup> 標識一個分組信息
          • <textarea> 文本域, 多行文本框
          • <progress> 任務完成的進度, 新標準(需較新瀏覽器支持)


          我們從最常用且最簡單的 文本內(nèi)容說起。看看 Vue.js 給我們帶來了什么便利的改變。

          單行文本 (input type='text')

          input 默認類型為: text, 并且使用 v-model 進行數(shù)據(jù)雙向綁定, 直接查看代碼:

          運行 F5 調(diào)試, 瀏覽器查看效果:

          這就是雙向綁定的好處, 如果沒有這個雙向綁定之前, 你需要先綁定 2 個事件, 一個是表單的 change 事件, 一個是元素的更新時間, 在 change 事件觸發(fā)的時候, 使用 innerText 更新頁面元素的內(nèi)容。

          多行文本(Textarea)

          查看案例:

          調(diào)試查看頁面, 在第二個文本域中寫入文字, 你會發(fā)現(xiàn)所有的文本域也會跟著改變, 但是這個不是最好的方式, 如果修改了第一個文本域的值, 你會發(fā)現(xiàn), 后面 v-model 綁定的數(shù)據(jù)并沒有變化 查看效果:

          最好的方式就是使用: <textarea v-model="userName"></textarea>

          復選框 (input type='checkbox')

          復選框允許你為表單提交時選擇一個值或者一組值。單個復選框, 可以綁定到布爾值類型,舉例如下:

          調(diào)試查看未選中:

          選中之后的效果:

          在多個復選框的情況下, 可以將 input 值綁定到一個數(shù)組中, 代碼如下:

          重新打開該頁面, 進行查看效果。 選擇之后。 就可以看到效果。這里只是為了演示, 實際中的 value 都是標志字符串。

          在復選框中, 可以使用 true-value 和 false-value 進行選中和未選中的值的賦值。

          <input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />
          // 選中的時候值就為 yes:
          vm.toggle==='yes'
          // 未選中的時候值就為 no:
          vm.toggle==='no'


          單選框 (input type='radio')

          單選框按鈕允許你選擇單一的值來提交表單。 默認的情況下為小型圓圈圖表。一個單選按鈕由具有相同 name 屬性的單選按鈕組成的。舉個例子:

          打開瀏覽器查看效果:

          選擇框 ( Select )

          HTML <select> 元素是一個提供選項的菜單控件。每個 select 都包含 option 子元素, 并且 option子元素中包含 value 屬性, 用于標識當前的會提交的數(shù)據(jù)值。還可以將 option 放到 optgroup 中, 標識不同的組別。 并且可以在 select 屬性上使用 multiple 決定該選擇框是否可以多選。查看單選例子:

          單選選擇框

          調(diào)試查看效果:

          如果選擇了 甲 就可以看到下面的效果。

          看 乙 的代碼重新指定了 value 值。 所以看到效果如下:

          多選選擇框 multiple

          只需要指定 select 的 multiple 屬性。

          查看效果, 這里需要注意, 使用 ctrl 按下不松進行鼠標單擊進行多項選擇:

          從上面我們可以看到我們的數(shù)據(jù)基本上就是一個數(shù)組項, 這里既然是個數(shù)組, 就可以使用 v-for 標簽進行渲染, 在實際開發(fā)的過程中, 也多數(shù)都是使用 v-for 進行渲染。 舉個例子:

          查看效果:



          表單中的修飾符

          前面在說事件的時候, 說過事件的修飾符, 在表單中也有修飾符。 分別是: .lazy, .number, .trim

          .lazy

          在默認渲染的情況下,v-model 在每次 input 事件觸發(fā)后都將輸入的值數(shù)據(jù)進行雙向綁定, 數(shù)據(jù)更新, 如果不想要這種效果的時候, 可以嘗試使用 lazy 修飾符, 這樣數(shù)據(jù)就只會在 change 事件觸發(fā)之后, 數(shù)據(jù)才會修改。

          這個時候, 不觸發(fā) change 事件就不會更新數(shù)據(jù)到頁面上。

          .number

          將用戶輸入的數(shù)據(jù)轉(zhuǎn)換成數(shù)值類型。但是需要注意的是, 必須是能夠正常轉(zhuǎn)換的字符串才有效, 負責只會返回原始值。

          <input v-model.number="age" type="number" />

          .trim

          這個就是很容易理解了, 就是給字符串去除首尾的空白字符。

          <input v-model.trim="msg" />


          表單的相關(guān)內(nèi)容這里暫時告一段落。 后續(xù)會針對這塊進行源碼級別分析。 關(guān)注我。 看后續(xù)的內(nèi)容。

          每天都強制自己做點非舒服區(qū)的知識學習, 你會發(fā)現(xiàn)你進步神速的。加油!

          過一段時間公測,得到廣大客戶的熱烈支持,阿里云時空數(shù)據(jù)庫已經(jīng)于2019年9月10日正式商業(yè)化售賣!

          產(chǎn)品介紹

          時空數(shù)據(jù)庫能夠存儲、管理包括時間序列以及空間地理位置相關(guān)的數(shù)據(jù)。我們的社會生產(chǎn)、經(jīng)濟活動和社會交往同時空數(shù)據(jù)密切相關(guān),比如傳感器網(wǎng)絡、移動互聯(lián)網(wǎng)、射頻識別、全球定位系統(tǒng)等設備時刻輸出時間和空間數(shù)據(jù),數(shù)據(jù)量增長非常迅速,這對存儲和管理時空數(shù)據(jù)帶來了挑戰(zhàn),傳統(tǒng)數(shù)據(jù)庫很難應對時空數(shù)據(jù)。時空數(shù)據(jù)是一種高維數(shù)據(jù),普通的關(guān)系型數(shù)據(jù)庫更適合于存儲數(shù)值和字符類型數(shù)據(jù),也缺少相關(guān)的算子。阿里云時空數(shù)據(jù)庫具有時空數(shù)據(jù)模型、時空索引和時空算子,完全兼容SQL及SQL/MM標準,支持時空數(shù)據(jù)同業(yè)務數(shù)據(jù)一體化存儲、無縫銜接,易于集成使用。

          產(chǎn)品首頁:https://www.aliyun.com/product/hitsdb_spatialpre

          產(chǎn)品使用手冊詳見:https://help.aliyun.com/document_detail/116088.html?spm=a2c4g.11174283.6.727.1b22130eu4OBeh

          適用場景

          交通監(jiān)控與分析、物流配送、可穿戴設備監(jiān)測、新能源車輛監(jiān)測、LBS、地圖服務等。

          產(chǎn)品特性

          • 時間序列數(shù)據(jù)與空間數(shù)據(jù)有效統(tǒng)一,滿足大規(guī)模時空數(shù)據(jù)存儲和查詢,方便從多個維度分析和利用數(shù)據(jù);
          • 基于PostgreSQL擴展,改進PostgreSQL索引,大幅度提升時空檢索性能,同時兼容PostgreSQL現(xiàn)有生態(tài);
          • 存儲依托于阿里云盤古系統(tǒng),數(shù)據(jù)可靠性超過6個9;
          • 具有自動備份和恢復能力;
          • 具有完善的高可用架構(gòu),支持自動化主機與備機切換;
          • 有時空領(lǐng)域資深專家提供支持,為客戶的業(yè)務保駕護航;

          產(chǎn)品購買流程

          在購買時空數(shù)據(jù)庫之前,需要先滿足以下前提條件:

          • 您已經(jīng)注冊了阿里云賬號并完成實名認證。否則,請先注冊阿里云賬號。
          • 您已擁有阿里云專有網(wǎng)絡(VPC)。時空數(shù)據(jù)庫只支持在 VPC 網(wǎng)絡創(chuàng)建實例(但之后您可以選擇通過 VPC 或公網(wǎng)訪問實例)。如果沒有 VPC 網(wǎng)絡,請登錄專有網(wǎng)絡(VPC)控制臺開通 VPC,然后在對應的地域和可用區(qū)創(chuàng)建專有網(wǎng)絡和交換機。關(guān)于創(chuàng)建 VPC 的具體信息,請參考創(chuàng)建 VPC。

          以“華北1(杭州)” 地域為例,下面演示具體創(chuàng)建流程。

          ACTION1: 在使用的區(qū)域內(nèi),創(chuàng)建VPC實例

          https://vpc.console.aliyun.com/vpc/cn-hangzhou/vpcs

          以“華北1(杭州)”區(qū)為例,選擇地域:華東1(杭州),交換機選項中,選擇需要的可用區(qū)如“杭州 可用區(qū)B”, 后續(xù)創(chuàng)建時空數(shù)據(jù)庫實例,會用到 “地域” 、“可用區(qū)”、“VPC”、“交換機” 這幾個概念。

          創(chuàng)建VPC完成之后,可以查看VPC的詳情

          ACTION2: 創(chuàng)建時空數(shù)據(jù)庫實例

          https://common-buy.aliyun.com/?commodityCode=hitsdb_spatialpre#/buy

          1. 地域:華東1(杭州)
          2. 可用區(qū):華東1 可用區(qū)B
          3. 版本:選擇基礎(chǔ)版或高可用版。詳細介紹請參考文檔
          4. 專有網(wǎng)絡(VPC): 選擇剛剛創(chuàng)建的VPC
          5. 專有網(wǎng)絡交換機:選擇剛剛創(chuàng)建的VPC下的交換機

          ACTION3: 購買成功之后,登錄TSDB控制臺,查看實例詳情

          https://tsdb.console.aliyun.com/?spm=5176.11182172.console-base-top.dconsoleEntry.60ec4882eEzNPU#/cluster/cn-hangzhou

          ACTION4: 在“實例詳情”頁面中,查看“公共網(wǎng)絡地址” 和 “VPC網(wǎng)絡地址”,設置網(wǎng)絡白名單。

          這里為了測試方便,VPN和公共網(wǎng)絡的參數(shù)都設置成“0.0.0.0/0”

          ACTION5: 在“實例詳情”頁面中,左側(cè)選擇“賬戶管理”進入賬戶創(chuàng)建頁面,創(chuàng)建高權(quán)限賬號

          至此,整個時空數(shù)據(jù)庫的初始化工作已經(jīng)完成,可以通過外部網(wǎng)絡或VPC專有網(wǎng)絡,連接時空數(shù)據(jù)庫交互。

          數(shù)據(jù)寫入查詢

          時空數(shù)據(jù)庫寫入和查詢非常便利,讀寫采用標準SQL,用戶可以通過JDBC/ODBC驅(qū)動操作數(shù)據(jù)庫,進行讀寫操作。

          用戶也可以通過psql交互式終端向時空數(shù)據(jù)庫寫入和查詢數(shù)據(jù),下面是幾個簡單的例子:

          創(chuàng)建一個時空表:

          CREATE TABLE tsdb_test( uid bigint, time timestamp, 
           speed float, position geometry(Point,4326) );
          SELECT create_hypertable('tsdb_test', 'time', chunk_time_interval=> interval '1 hour');
          

          寫入數(shù)據(jù):

          INSERT INTO tsdb_test 
          VALUES (1001, '2019-03-11 16:34:15', 102.2, ST_SetSRID(ST_MakePoint(10.3,20.1),4326)),
           (1001, '2019-03-11 16:34:16', 100.1, ST_SetSRID(ST_MakePoint(10.4,20.1),4326)),
           (1002, '2019-03-11 16:34:17', 60.0, ST_SetSRID(ST_MakePoint(10.5,20.2),4326)),
           (1002, '2019-03-11 16:34:18', 61.0, ST_SetSRID(ST_MakePoint(10.6,20.2),4326)),
           (1003, '2019-03-11 16:34:20', 39.0, ST_SetSRID(ST_MakePoint(10.7,20.2),4326)),
           (1003, '2019-03-11 16:34:21', 30.0, ST_SetSRID(ST_MakePoint(10.8,20.2),4326));
          

          用戶通過交互終端查詢數(shù)據(jù),可以如下:

          SELECT time,uid,speed,ST_AsText(position) 
          FROM tsdb_test 
          WHERE time >'2019-03-11 16:00:00' AND 
           time < '2019-03-11 18:00:00' AND 
           ST_Contains(ST_SetSRID(ST_MakeBox2D(ST_Point(2.4, 5.5),ST_Point(13.0,26.1)),4326),position) ;
          +---------------------+---------------+-----------------+---------------------+
          | TIME | UID | SPEED | ST_ASTEXT |
          +---------------------+---------------+-----------------+---------------------+
          | 2019-03-11 16:34:15 | 1001 | 102.2 | POINT(10.3 20.1) |
          | 2019-03-11 16:34:16 | 1001 | 100.1 | POINT(10.4 20.1) |
          | 2019-03-11 16:34:17 | 1002 | 60 | POINT(10.5 20.2) |
          | 2019-03-11 16:34:18 | 1002 | 61 | POINT(10.6 20.2) |
          | 2019-03-11 16:34:20 | 1003 | 39 | POINT(10.7 20.2) |
          | 2019-03-11 16:34:21 | 1003 | 30 | POINT(10.8 20.2) |
          +---------------------+---------------+-----------------+---------------------+
          

          更新數(shù)據(jù):

          UPDATE tsdb_test 
          set position=ST_SetSRID(ST_MakePoint(11.1,22.2),4326) 
          WHERE uid=1002;
          

          時空分析功能

          用戶可以使用時間&空間分析函數(shù),對時空數(shù)據(jù)庫中的表做分析查詢。以共享汽車平臺中車輛數(shù)據(jù)為背景,舉幾個簡單的例子。

          按時間窗口聚合

          按照5分鐘為一個聚合時間窗口,獲取共享汽車平臺中車輛的最大速度;常見聚合函數(shù)如:sum,max,min,avg等

          SELECT uid,time_bucket('5 minutes', time) AS interval, max(speed) 
          FROM tsdb_test 
          WHERE uid='1002' and 
           time < '2019-04-01 01:13:42' 
          GROUP BY uid, interval 
          ORDER BY interval DESC;
          +---------------+---------------------+---------------+
          | UID | INTERVAL | MAX |
          +---------------+---------------------+---------------+
          | 1002 | 2019-03-11 16:30:00 | 61 |
          +---------------+---------------------+---------------+
          

          按時間段和距離過濾

          返回某個時間段,與指定對象的距離大于“17米”的車輛。空間范圍函數(shù)比如: ST_Distance等使用,參考:空間對象關(guān)系函數(shù)。

          SELECT time,uid,speed,ST_AsText(position) 
          FROM tsdb_test 
          WHERE time > '2019-01-01 01:02:00' and 
           time < '2019-04-01 01:11:02' and 
           ST_Distance('SRID=4326;POINT(2.4 5.5)'::geometry, position)>17.0;
          +---------------------+---------------+-----------------+---------------------+
          | TIME | UID | SPEED | ST_ASTEXT |
          +---------------------+---------------+-----------------+---------------------+
          | 2019-03-11 16:34:17 | 1002 | 60 | POINT(11.1 22.2) |
          | 2019-03-11 16:34:18 | 1002 | 61 | POINT(11.1 22.2) |
          +---------------------+---------------+-----------------+---------------------+
          

          普通屬性值過濾

          根據(jù)用戶設置的數(shù)值限制條件,返回某時間段內(nèi)“速度>60”的車輛記錄。比如: “>”, “<”, “=”, “<=”, “>=”, “!=”。ST_AsText的使用,參考:空間對象輸出函數(shù)

          SELECT time,uid,speed,ST_AsText(position) 
          FROM tsdb_test 
          WHERE time > '2019-03-01 01:02:00' and 
           time < '2019-03-15 01:11:02' and 
           speed > 60;
          +---------------------+---------------+-----------------+---------------------+
          | TIME | UID | SPEED | ST_ASTEXT |
          +---------------------+---------------+-----------------+---------------------+
          | 2019-03-11 16:34:15 | 1001 | 102.2 | POINT(10.3 20.1) |
          | 2019-03-11 16:34:16 | 1001 | 100.1 | POINT(10.4 20.1) |
          | 2019-03-11 16:34:18 | 1002 | 61 | POINT(11.1 22.2) |
          +---------------------+---------------+-----------------+---------------------+
          

          關(guān)于時空數(shù)據(jù)庫的具體用法,可以參考阿里云時空數(shù)據(jù)庫-開發(fā)指南

          阿里云時空數(shù)據(jù)庫致力于推動時空領(lǐng)域生態(tài)發(fā)展,為客戶提供低成本高性能服務,讓時空數(shù)據(jù)價值在線化!

          產(chǎn)品首頁:https://www.aliyun.com/product/hitsdb_spatialpre

          產(chǎn)品使用手冊詳見:https://help.aliyun.com/document_detail/116088.html?spm=a2c4g.11174283.6.727.1b22130eu4OBeh

          時空數(shù)據(jù)庫實例創(chuàng)建:https://common-buy.aliyun.com/?commodityCode=hitsdb_spatialpre#/buy

          本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。


          主站蜘蛛池模板: 国内自拍视频一区二区三区| 无码精品蜜桃一区二区三区WW| 成人国产精品一区二区网站| 国产亚洲福利精品一区| 91video国产一区| 岛国无码av不卡一区二区| 国产精品一区二区久久乐下载 | 日本免费一区二区三区四区五六区 | 国产在线精品一区二区三区直播 | 在线成人一区二区| 精品久久久久中文字幕一区| 多人伦精品一区二区三区视频| 免费av一区二区三区| 视频在线一区二区三区| 视频在线观看一区二区三区| 欧洲精品一区二区三区| 四虎在线观看一区二区| 一本大道在线无码一区| 日韩精品一区二区三区中文精品 | 亚洲av区一区二区三| 日韩精品一区二区三区影院| 国产精品免费视频一区| 欧美日韩精品一区二区在线观看| 亚洲一区精品视频在线| 亚洲福利秒拍一区二区| 中文字幕一区日韩精品| 国产自产对白一区| 日韩精品久久一区二区三区| 日本一区中文字幕日本一二三区视频| 国产一区二区三区乱码在线观看| 亚洲国产精品综合一区在线 | 亚洲AV日韩AV天堂一区二区三区| 国产成人精品一区二区三区免费| 亚洲成AV人片一区二区密柚| 无码少妇一区二区性色AV| 亚洲码一区二区三区| 天美传媒一区二区三区| 一区二区视频在线免费观看| 久久精品国产一区| 色综合视频一区中文字幕| 精品国产一区二区麻豆|