少年阿賓

          那些青春的歲月

            BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
            500 Posts :: 0 Stories :: 135 Comments :: 0 Trackbacks

          2016年6月14日 #

               摘要: 前陣子從支付寶轉賬1萬塊錢到余額寶,這是日常生活的一件普通小事,但作為互聯網研發人員的職業病,我就思考支付寶扣除1萬之后,如果系統掛掉怎么辦,這時余額寶賬戶并沒有增加1萬,數據就會出現不一致狀況了。上述場景在各個類型的系統中都能找到相似影子,比如在電商系統中,當有用戶下單后,除了在訂單表插入一條記錄外,對應商品表的這個商品數量必須減1吧,怎么保證?!在搜索廣告系統中,當用戶點擊某廣告后,除了在點擊...  閱讀全文
          posted @ 2018-01-04 00:01 abin 閱讀(716) | 評論 (0)編輯 收藏

          微服務架構采用Scale Cube方法設計應用架構,將應用服務按功能拆分成一組相互協作的服務。每個服務負責一組特定、相關的功能。每個服務可以有自己獨立的數據庫,從而保證與其他服務解耦。
          微服務優點
          1、通過分解巨大單體式應用為多個服務方法解決了復雜性問題,每個微服務相對較小
          2、每個單體應用不局限于固定的技術棧,開發者可以自由選擇開發技術,提供API服務。
          3、每個微服務獨立的開發,部署
          4、單一職責功能,每個服務都很簡單,只關注于一個業務功能
          5、易于規模化開發,多個開發團隊可以并行開發,每個團隊負責一項服務
          6、改善故障隔離。一個服務宕機不會影響其他的服務
          微服務缺點:
          1.開發者需要應對創建分布式系統所產生的額外的復雜因素
          l  目前的IDE主要面對的是單體工程程序,無法顯示支持分布式應用的開發
          l  測試工作更加困難
          l  需要采用服務間的通訊機制
          l  很難在不采用分布式事務的情況下跨服務實現功能
          l  跨服務實現要求功能要求團隊之間的緊密協作
          2.部署復雜
          3.內存占用量更高
          posted @ 2017-12-31 16:41 abin 閱讀(416) | 評論 (0)編輯 收藏

          JDK 的 HashMap 中使用了一個 hash 方法來做 bit shifting,在注釋中說明是為了防止一些實現比較差的hashCode() 方法,請問原理是什么?JDK 的源碼參見:GrepCode: java.util.HashMap (.java)
          /**
           * Applies a supplemental hash function to a given hashCode, which
           * defends against poor quality hash functions.  This is critical
           * because HashMap uses power-of-two length hash tables, that
           * otherwise encounter collisions for hashCodes that do not differ
           * in lower bits. Note: Null keys always map to hash 0, thus index 0.
           */
          static int hash(int h) {
              // This function ensures that hashCodes that differ only by
              // constant multiples at each bit position have a bounded
              // number of collisions (approximately 8 at default load factor).
              h ^= (h >>> 20) ^ (h >>> 12);
              return h ^ (h >>> 7) ^ (h >>> 4);
          }
          PS:網上看見有人說作者本人說原理需要參見圣經《計算機程序設計藝術》的 Vol.3 里頭的介紹,不過木有看過神書,求達人介紹





          這段代碼叫“擾動函數”。
          題主貼的是Java 7的HashMap的源碼,Java 8中這步已經簡化了,只做一次16位右位移異或混合,而不是四次,但原理是不變的。下面以Java 8的源碼為例解釋,

          //Java 8中的散列值優化函數staticfinalinthash(Objectkey){inth;return(key==null)?0:(h=key.hashCode())^(h>>>16);//key.hashCode()為哈希算法,返回初始哈希值}
          大家都知道上面代碼里的key.hashCode()函數調用的是key鍵值類型自帶的哈希函數,返回int型散列值。理論上散列值是一個int型,如果直接拿散列值作為下標訪問HashMap主數組的話,考慮到2進制32位帶符號的int表值范圍從-2147483648到2147483648。前后加起來大概40億的映射空間。只要哈希函數映射得比較均勻松散,一般應用是很難出現碰撞的。但問題是一個40億長度的數組,內存是放不下的。你想,HashMap擴容之前的數組初始大小才16。所以這個散列值是不能直接拿來用的。用之前還要先做對數組的長度取模運算,得到的余數才能用來訪問數組下標。源碼中模運算是在這個indexFor( )函數里完成的。

          bucketIndex = indexFor(hash, table.length);indexFor的代碼也很簡單,就是把散列值和數組長度做一個"與"操作,

          static int indexFor(int h, int length) {        return h & (length-1);}順便說一下,這也正好解釋了為什么HashMap的數組長度要取2的整次冪。因為這樣(數組長度-1)正好相當于一個“低位掩碼”。“與”操作的結果就是散列值的高位全部歸零,只保留低位值,用來做數組下標訪問。以初始長度16為例,16-1=15。2進制表示是00000000 00000000 00001111。和某散列值做“與”操作如下,結果就是截取了最低的四位值。
          10100101 11000100 00100101& 00000000 00000000 00001111---------------------------------- 00000000 00000000 00000101    //高位全部歸零,只保留末四位
          但這時候問題就來了,這樣就算我的散列值分布再松散,要是只取最后幾位的話,碰撞也會很嚴重。更要命的是如果散列本身做得不好,分布上成等差數列的漏洞,恰好使最后幾個低位呈現規律性重復,就無比蛋疼。這時候“擾動函數”的價值就體現出來了,說到這里大家應該猜出來了。看下面這個圖,


          右位移16位,正好是32bit的一半,自己的高半區和低半區做異或,就是為了混合原始哈希碼的高位和低位,以此來加大低位的隨機性。而且混合后的低位摻雜了高位的部分特征,這樣高位的信息也被變相保留下來。最后我們來看一下PeterLawley的一篇專欄文章《An introduction to optimising a hashing strategy》里的的一個實驗:他隨機選取了352個字符串,在他們散列值完全沒有沖突的前提下,對它們做低位掩碼,取數組下標。


          結果顯示,當HashMap數組長度為512的時候,也就是用掩碼取低9位的時候,在沒有擾動函數的情況下,發生了103次碰撞,接近30%。而在使用了擾動函數之后只有92次碰撞。碰撞減少了將近10%。看來擾動函數確實還是有功效的。但明顯Java 8覺得擾動做一次就夠了,做4次的話,多了可能邊際效用也不大,所謂為了效率考慮就改成一次了。
          ------------------------------------------------------








          https://www.zhihu.com/question/20733617



          posted @ 2017-12-24 22:38 abin 閱讀(442) | 評論 (0)編輯 收藏

          Go語言沒有沿襲傳統面向對象編程中的諸多概念,比如繼承、虛函數、構造函數和析構函數、隱藏的this指針等。

           

          方法

          Go 語言中同時有函數和方法。方法就是一個包含了接受者(receiver)的函數,receiver可以是內置類型或者結構體類型的一個值或者是一個指針。所有給定類型的方法屬于該類型的方法集。

          如下面的這個例子,定義了一個新類型Integer,它和int一樣,只是為它內置的int類型增加了個新方法Less()

          復制代碼
          type Integer int   func (a Integer) Less(b Integer) bool {     return a < b  }  func main() {     var a Integer = 1       if a.Less(2) {         fmt.Println("less then 2")     }    }
          復制代碼

          可以看出,Go語言在自定義類型的對象中沒有C++/Java那種隱藏的this指針,而是在定義成員方法時顯式聲明了其所屬的對象。

           

          method的語法如下:

          func (r ReceiverType) funcName(parameters) (results)

          當調用method時,會將receiver作為函數的第一個參數:

          funcName(r, parameters);

          所以,receiver是值類型還是指針類型要看method的作用。如果要修改對象的值,就需要傳遞對象的指針。

          指針作為Receiver會對實例對象的內容發生操作,而普通類型作為Receiver僅僅是以副本作為操作對象,并不對原實例對象發生操作。

          復制代碼
          func (a *Ingeger) Add(b Integer) {     *a += b }  func main() {     var a Integer = 1      a.Add(3)     fmt.Println("a =", a)     //  a = 4 }
          復制代碼

          如果Add方法不使用指針,則a返回的結果不變,這是因為Go語言函數的參數也是基于值傳遞。

          注意:當方法的接受者是指針時,即使用值類型調用那么方法內部也是對指針的操作。

           

          之前說過,Go語言沒有構造函數的概念,通常使用一個全局函數來完成。例如:

          復制代碼
          func NewRect(x, y, width, height float64) *Rect {     return &Rect{x, y, width, height} }     func main() {     rect1 := NewRect(1,2,10,20)     fmt.Println(rect1.width) }
          復制代碼

           

           


          匿名組合

          Go語言提供了繼承,但是采用了組合的語法,我們將其稱為匿名組合,例如:

          復制代碼
          type Base struct {     name string }  func (base *Base) Set(myname string) {     base.name = myname }  func (base *Base) Get() string {     return base.name }  type Derived struct {     Base     age int  }  func (derived *Derived) Get() (nm string, ag int) {     return derived.name, derived.age }   func main() {     b := &Derived{}      b.Set("sina")     fmt.Println(b.Get()) }
          復制代碼

          例子中,在Base類型定義了get()和set()兩個方法,而Derived類型繼承了Base類,并改寫了Get()方法,在Derived對象調用Set()方法,會加載基類對應的方法;而調用Get()方法時,加載派生類改寫的方法。

           

          組合的類型和被組合的類型包含同名成員時, 會不會有問題呢?可以參考下面的例子:

          復制代碼
          type Base struct {     name string     age int }  func (base *Base) Set(myname string, myage int) {     base.name = myname     base.age = myage }  type Derived struct {     Base     name string }  func main() {     b := &Derived{}      b.Set("sina", 30)     fmt.Println("b.name =",b.name, "\tb.Base.name =", b.Base.name)     fmt.Println("b.age =",b.age, "\tb.Base.age =", b.Base.age) }
          復制代碼

           

           

           


          值語義和引用語義

          值語義和引用語義的差別在于賦值,比如

          b = a b.Modify()

          如果b的修改不會影響a的值,那么此類型屬于值類型;如果會影響a的值,那么此類型是引用類型。

          Go語言中的大多數類型都基于值語義,包括:

          • 基本類型,如byte、int、bool、float32、string等;
          • 復合類型,如arry、struct、pointer等;

           

          C語言中的數組比較特別,通過函數傳遞一個數組的時候基于引用語義,但是在結構體定義數組變量的時候基于值語義。而在Go語言中,數組和基本類型沒有區別,是很純粹的值類型,例如:

          var a = [3] int{1,2,3} var b = a b[1]++ fmt.Println(a, b)   // [1 2 3] [1 3 3]

          從結果看,b=a賦值語句是數組內容的完整復制,要想表達引用,需要用指針:

          var a = [3] int{1,2,3} var b = &a    // 引用語義 b[1]++ fmt.Println(a, b)   // [1 3 3] [1 3 3]

           

           


          接口

          Interface 是一組抽象方法(未具體實現的方法/僅包含方法名參數返回值的方法)的集合,如果實現了 interface 中的所有方法,即該類/對象就實現了該接口。

          Interface 的聲明格式:

          type interfaceName interface {       //方法列表   }  

          Interface 可以被任意對象實現,一個類型/對象也可以實現多個 interface;
          interface的變量可以持有任意實現該interface類型的對象。

           如下面的例子:

          復制代碼
          package main      import "fmt"      type Human struct {         name string         age int         phone string     }      type Student struct {         Human //匿名字段         school string         loan float32     }      type Employee struct {         Human //匿名字段         company string         money float32     }      //Human實現SayHi方法     func (h Human) SayHi() {         fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)     }      //Human實現Sing方法     func (h Human) Sing(lyrics string) {         fmt.Println("La la la la...", lyrics)     }      //Employee重載Human的SayHi方法     func (e Employee) SayHi() {         fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,             e.company, e.phone)         }      // Interface Men被Human,Student和Employee實現     // 因為這三個類型都實現了這兩個方法     type Men interface {         SayHi()         Sing(lyrics string)     }      func main() {         mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}         paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}         sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}         tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}          //定義Men類型的變量i         var i Men          //i能存儲Student         i = mike             fmt.Println("This is Mike, a Student:")         i.SayHi()         i.Sing("November rain")          //i也能存儲Employee         i = tom         fmt.Println("This is tom, an Employee:")         i.SayHi()         i.Sing("Born to be wild")          //定義了slice Men         fmt.Println("Let's use a slice of Men and see what happens")         x := make([]Men, 3)         //這三個都是不同類型的元素,但是他們實現了interface同一個接口         x[0], x[1], x[2] = paul, sam, mike          for _, value := range x{             value.SayHi()         }     }
          復制代碼

           

          空接口

          空interface(interface{})不包含任何的method,正因為如此,所有的類型都實現了空interface。空interface對于描述起不到任何的作用(因為它不包含任何的method),但是空interface在我們需要存儲任意類型的數值的時候相當有用,因為它可以存儲任意類型的數值。它有點類似于C語言的void*類型。

          復制代碼
          // 定義a為空接口     var a interface{}     var i int = 5     s := "Hello world"     // a可以存儲任意類型的數值     a = i     a = s
          復制代碼

           

          interface的變量里面可以存儲任意類型的數值(該類型實現了interface),那么我們怎么反向知道這個interface變量里面實際保存了的是哪個類型的對象呢?目前常用的有兩種方法:switch測試、Comma-ok斷言。

           

          switch測試如下:

          復制代碼
          type Element interface{} type List [] Element  type Person struct {     name string     age int  }  //打印 func (p Person) String() string {     return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" }  func main() {     list := make(List, 3)     list[0] = 1 //an int      list[1] = "Hello" //a string     list[2] = Person{"Dennis", 70}       for index, element := range list{         switch value := element.(type) {             case int:                 fmt.Printf("list[%d] is an int and its value is %d\n", index, value)             case string:                 fmt.Printf("list[%d] is a string and its value is %s\n", index, value)             case Person:                 fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)             default:                 fmt.Println("list[%d] is of a different type", index)         }        }    }
          復制代碼

           

          如果使用Comma-ok斷言的話:

          復制代碼
          func main() {     list := make(List, 3)     list[0] = 1 // an int     list[1] = "Hello" // a string     list[2] = Person{"Dennis", 70}      for index, element := range list {         if value, ok := element.(int); ok {             fmt.Printf("list[%d] is an int and its value is %d\n", index, value)         } else if value, ok := element.(string); ok {             fmt.Printf("list[%d] is a string and its value is %s\n", index, value)         } else if value, ok := element.(Person); ok {             fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)         } else {             fmt.Printf("list[%d] is of a different type\n", index)         }     } }
          復制代碼

           

           

          嵌入接口

          正如struct類型可以包含一個匿名字段,interface也可以嵌套另外一個接口。

          如果一個interface1作為interface2的一個嵌入字段,那么interface2隱式的包含了interface1里面的method。

           

           

          反射

          所謂反射(reflect)就是能檢查程序在運行時的狀態。

          使用reflect一般分成三步,下面簡要的講解一下:要去反射是一個類型的值(這些值都實現了空interface),首先需要把它轉化成reflect對象(reflect.Type或者reflect.Value,根據不同的情況調用不同的函數)。這兩種獲取方式如下:

           t := reflect.TypeOf(i)    //得到類型的元數據,通過t我們能獲取類型定義里面的所有元素  v := reflect.ValueOf(i)   //得到實際的值,通過v我們獲取存儲在里面的值,還可以去改變值

           

          轉化為reflect對象之后我們就可以進行一些操作了,也就是將reflect對象轉化成相應的值,例如

          tag := t.Elem().Field(0).Tag  //獲取定義在struct里面的標簽 name := v.Elem().Field(0).String()  //獲取存儲在第一個字段里面的值

           

          獲取反射值能返回相應的類型和數值

          var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float())

           

          最后,反射的話,那么反射的字段必須是可修改的,我們前面學習過傳值和傳引用,這個里面也是一樣的道理。反射的字段必須是可讀寫的意思是,如果下面這樣寫,那么會發生錯誤

          var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1)

           

          如果要修改相應的值,必須這樣寫

          var x float64 = 3.4 p := reflect.ValueOf(&x) v := p.Elem() v.SetFloat(7.1)

          上面只是對反射的簡單介紹,更深入的理解還需要自己在編程中不斷的實踐。

           

           

          參考文檔:

          http://se77en.cc/2014/05/05/methods-interfaces-and-embedded-types-in-golang/

          http://se77en.cc/2014/05/04/choose-whether-to-use-a-value-or-pointer-receiver-on-methods/

           http://www.cnblogs.com/chenny7/p/4497969.html





          posted @ 2017-08-03 11:34 abin 閱讀(440) | 評論 (0)編輯 收藏

          不可或缺的函數,在Go中定義函數的方式如下:

          func (p myType ) funcName ( a, b int , c string ) ( r , s int ) {     return } 

          通過函數定義,我們可以看到Go中函數和其他語言中的共性和特性

          共性

          • 關鍵字——func
          • 方法名——funcName
          • 入參——— a,b int,b string
          • 返回值—— r,s int
          • 函數體—— {}

          特性

          Go中函數的特性是非常酷的,給我們帶來不一樣的編程體驗。

          為特定類型定義函數,即為類型對象定義方法

          在Go中通過給函數標明所屬類型,來給該類型定義方法,上面的 p myType 即表示給myType聲明了一個方法, p myType 不是必須的。如果沒有,則純粹是一個函數,通過包名稱訪問。packageName.funcationName

          如:

          //定義新的類型double,主要目的是給float64類型擴充方法 type double float64  //判斷a是否等于b func (a double) IsEqual(b double) bool {     var r = a - b     if r == 0.0 {         return true     } else if r < 0.0 {         return r > -0.0001     }     return r < 0.0001 }  //判斷a是否等于b func IsEqual(a, b float64) bool {     var r = a - b     if r == 0.0 {         return true     } else if r < 0.0 {         return r > -0.0001     }     return r < 0.0001 }  func main() {     var a double = 1.999999     var b double = 1.9999998     fmt.Println(a.IsEqual(b))     fmt.Println(a.IsEqual(3))     fmt.Println( IsEqual( (float64)(a), (float64)(b) ) )  } 

          上述示例為 float64 基本類型擴充了方法IsEqual,該方法主要是解決精度問題。 其方法調用方式為: a.IsEqual(double) ,如果不擴充方法,我們只能使用函數IsEqual(a, b float64)

          入參中,如果連續的參數類型一致,則可以省略連續多個參數的類型,只保留最后一個類型聲明。

          如 func IsEqual(a, b float64) bool 這個方法就只保留了一個類型聲明,此時入參a和b均是float64數據類型。 這樣也是可以的: func IsEqual(a, b float64, accuracy int) bool

          變參:入參支持變參,即可接受不確定數量的同一類型的參數

          如 func Sum(args ...int) 參數args是的slice,其元素類型為int 。經常使用的fmt.Printf就是一個接受任意個數參數的函數 fmt.Printf(format string, args ...interface{})

          支持多返回值

          前面我們定義函數時返回值有兩個r,s 。這是非常有用的,我在寫C#代碼時,常常為了從已有函數中獲得更多的信息,需要修改函數簽名,使用out ,ref 等方式去獲得更多返回結果。而現在使用Go時則很簡單,直接在返回值后面添加返回參數即可。

          如,在C#中一個字符串轉換為int類型時邏輯代碼

          int v=0;  if ( int.TryPase("123456",out v) ) {     //code } 

          而在Go中,則可以這樣實現,邏輯精簡而明確

          if v,isOk :=int.TryPase("123456") ; isOk {     //code } 

          同時在Go中很多函數充分利用了多返回值

          • func (file *File) Write(b []byte) (n int, err error)
          • func Sincos(x float64) (sin, cos float64)

          那么如果我只需要某一個返回值,而不關心其他返回值的話,我該如何辦呢? 這時可以簡單的使用符號下劃線”_“ 來忽略不關心的返回值。如:

          _, cos = math.Sincos(3.1415) //只需要cos計算的值 

          命名返回值

          前面我們說了函數可以有多個返回值,這里我還要說的是,在函數定義時可以給所有的返回值分別命名,這樣就能在函數中任意位置給不同返回值復制,而不需要在return語句中才指定返回值。同時也能增強可讀性,也提高godoc所生成文檔的可讀性

          如果不支持命名返回值,我可能會是這樣做的

          func ReadFull(r Reader, buf []byte) (int, error) {     var n int     var err error      for len(buf) > 0  {         var nr int         nr, err = r.Read(buf)          n += nr         if err !=nil {             return n,err         }         buf = buf[nr:]     }     return n,err } 

          但支持給返回值命名后,實際上就是省略了變量的聲明,return時無需寫成return n,err 而是將直接將值返回

          func ReadFull(r Reader, buf []byte) (n int, err error) {     for len(buf) > 0 && err == nil {         var nr int         nr, err = r.Read(buf)         n += nr         buf = buf[nr:]     }     return } 

          函數也是“值”

          和Go中其他東西一樣,函數也是值,這樣就可以聲明一個函數類型的變量,將函數作為參數傳遞。

          聲明函數為值的變量(匿名函數:可賦值個變量,也可直接執行)

          //賦值 fc := func(msg string) {     fmt.Println("you say :", msg) } fmt.Printf("%T \n", fc) fc("hello,my love") //直接執行 func(msg string) {     fmt.Println("say :", msg) }("I love to code") 

          輸出結果如下,這里表明fc 的類型為:func(string)

          func(string)  you say : hello,my love say : I love to code 

          將函數作為入參(回調函數),能帶來便利。如日志處理,為了統一處理,將信息均通過指定函數去記錄日志,且是否記錄日志還有開關

          func Log(title string, getMsg func() string) {     //如果開啟日志記錄,則記錄日志     if true {         fmt.Println(title, ":", getMsg())     } } //---------調用-------------- count := 0 msg := func() string {     count++     return "您沒有即使提醒我,已觸犯法律" } Log("error", msg) Log("warring", msg) Log("info", msg) fmt.Println(count) 

          這里輸出結果如下,count 也發生了變化

          error : 您沒有即使提醒我,已觸犯法律 warring : 您沒有即使提醒我,已觸犯法律 info : 您沒有即使提醒我,已觸犯法律 3 

          函數也是“類型”

          你有沒有注意到上面示例中的 fc := func(msg string)... ,既然匿名函數可以賦值給一個變量,同時我們經常這樣給int賦值 value := 2 ,是否我們可以聲明func(string) 類型 呢,當然是可以的。

          //一個記錄日志的類型:func(string) type saveLog func(msg string)  //將字符串轉換為int64,如果轉換失敗調用saveLog func stringToInt(s string, log saveLog) int64 {      if value, err := strconv.ParseInt(s, 0, 0); err != nil {         log(err.Error())         return 0     } else {         return value     } }  //記錄日志消息的具體實現 func myLog(msg string) {     fmt.Println("Find Error:", msg) }  func main() {     stringToInt("123", myLog) //轉換時將調用mylog記錄日志     stringToInt("s", myLog) } 

          這里我們定義了一個類型,專門用作記錄日志的標準接口。在stringToInt函數中如果轉換失敗則調用我自己定義的接口函數進行日志處理,至于最終執行的哪個函數,則無需關心。

          defer 延遲函數

          defer 又是一個創新,它的作用是:延遲執行,在聲明時不會立即執行,而是在函數return后時按照后進先出的原則依次執行每一個defer。這樣帶來的好處是,能確保我們定義的函數能百分之百能夠被執行到,這樣就能做很多我們想做的事,如釋放資源,清理數據,記錄日志等

          這里我們重點來說明下defer的執行順序

          func deferFunc() int {     index := 0      fc := func() {          fmt.Println(index, "匿名函數1")         index++          defer func() {             fmt.Println(index, "匿名函數1-1")             index++         }()     }      defer func() {         fmt.Println(index, "匿名函數2")         index++     }()      defer fc()      return func() int {         fmt.Println(index, "匿名函數3")         index++         return index     }() }  func main() {     deferFunc() } 

          這里輸出結果如下,

          0 匿名函數3 1 匿名函數1 2 匿名函數1-1 3 匿名函數2 

          有如下結論:

          • defer 是在執行完return 后執行
          • defer 后進先執行

          另外,我們常使用defer去關閉IO,在正常打開文件后,就立刻聲明一個defer,這樣就不會忘記關閉文件,也能保證在出現異常等不可預料的情況下也能關閉文件。而不像其他語言:try-catch 或者 using() 方式進行處理。

          file , err :=os.Open(file) if err != nil {     return err } defer file.Close()  //dosomething with file 

          后續,我將討論: 作用域、傳值和傳指針 以及 保留函數init(),main()

          本筆記中所寫代碼存儲位置:

          posted @ 2017-08-02 16:39 abin 閱讀(566) | 評論 (0)編輯 收藏

          MySQL觸發器Trigger實例篇
          發表于668 天前 ⁄ IT技術 ⁄ 暫無評論

          以前關注的數據存儲過程不太懂其中奧妙,最近遇到跨數據庫,同時對多個表進行CURD(Create增、Update改、Read讀、Delete刪),怎么才能讓繁瑣的數據CURD同步變得更容易呢?相信很多人會首先想到了MySQL存儲過程、觸發器,這種想法確實不錯。于是饒有興趣地親自寫了CUD(增、改、刪)觸發器的實例,用觸發器實現多表數據同步更新。

          MySQL觸發器Trigger實例篇

          定義: 何為MySQL觸發器?

          在MySQL Server里面也就是對某一個表的一定的操作,觸發某種條件(Insert,Update,Delete 等),從而自動執行的一段程序。從這種意義上講觸發器是一個特殊的存儲過程。下面通過MySQL觸發器實例,來了解一下觸發器的工作過程吧!

          一、創建MySQL實例數據表:

          在mysql的默認的測試test數據庫下,創建兩個表t_a與t_b:



              /*Table structure for table `t_a` */
              DROP TABLE IF EXISTS `t_a`;
              CREATE TABLE `t_a` (
                `id` smallint(1) unsigned NOT NULL AUTO_INCREMENT,
                `username` varchar(20) DEFAULT NULL,
                `groupid` mediumint(8) unsigned NOT NULL DEFAULT '0',
                PRIMARY KEY (`id`)
              ) ENGINE=MyISAM AUTO_INCREMENT=16 DEFAULT CHARSET=latin1;
               
              /*Data for the table `t_a` */
              LOCK TABLES `t_a` WRITE;
              UNLOCK TABLES;
               
              /*Table structure for table `t_b` */
              DROP TABLE IF EXISTS `t_b`;
              CREATE TABLE `t_b` (
                `id` smallint(1) unsigned NOT NULL AUTO_INCREMENT,
                `username` varchar(20) DEFAULT NULL,
                `groupid` mediumint(8) unsigned NOT NULL DEFAULT '0',
                PRIMARY KEY (`id`)
              ) ENGINE=MyISAM AUTO_INCREMENT=57 DEFAULT CHARSET=latin1;
               
              /*Data for the table `t_b` */
              LOCK TABLES `t_b` WRITE;
              UNLOCK TABLES;

          在t_a表上分創建一個CUD(增、改、刪)3個觸發器,將t_a的表數據與t_b同步實現CUD,注意創建觸發器每個表同類事件有且僅有一個對應觸發器,為什么只能對一個觸發器,不解釋啦,看MYSQL的說明幫助文檔吧。

          二、創建MySQL實例觸發器:

          在實例數據表t_a上依次按照下面步驟創建tr_a_insert、tr_a_update、tr_a_delete三個觸發器

          1、創建INSERT觸發器trigger_a_insert:



              DELIMITER $$
               
              USE `test`$$
               
              --判斷數據庫中是否存在tr_a_insert觸發器
              DROP TRIGGER /*!50032 IF EXISTS */ `tr_a_insert`$$
              --不存在tr_a_insert觸發器,開始創建觸發器
              --Trigger觸發條件為insert成功后進行觸發
              CREATE
                  /*!50017 DEFINER = 'root'@'localhost' */
                  TRIGGER `tr_a_insert` AFTER INSERT ON `t_a`
                  FOR EACH ROW BEGIN
                      --Trigger觸發后,同時對t_b新增同步一條數據
                      INSERT INTO `t_b` SET username = NEW.username, groupid=NEW.groupid;
                  END;
              $$
               
              DELIMITER;
          2、創建UPDATE觸發器trigger_a_update:


              DELIMITER $$
               
              USE `test`$$
              --判斷數據庫中是否存在tr_a_update觸發器
              DROP TRIGGER /*!50032 IF EXISTS */ `tr_a_update`$$
              --不存在tr_a_update觸發器,開始創建觸發器
              --Trigger觸發條件為update成功后進行觸發
              CREATE
                  /*!50017 DEFINER = 'root'@'localhost' */
                  TRIGGER `tr_a_update` AFTER UPDATE ON `t_a`
                  FOR EACH ROW BEGIN
                  --Trigger觸發后,當t_a表groupid,username數據有更改時,對t_b表同步一條更新后的數據
                    IF new.groupid != old.groupid OR old.username != new.username THEN
                      UPDATE `t_b` SET groupid=NEW.groupid,username=NEW.username WHEREusername=OLD.username AND groupid=OLD.groupid;
                    END IF;
                        
                  END;
              $$
               
              DELIMITER ;
          3、創建DELETE觸發器trigger_a_delete:


              DELIMITER $$
               
              USE `test`$$
              --判斷數據庫中是否存在tr_a_delete觸發器
              DROP TRIGGER /*!50032 IF EXISTS */ `tr_a_delete`$$
              --不存在tr_a_delete觸發器,開始創建觸發器
              --Trigger觸發條件為delete成功后進行觸發
              CREATE
                  /*!50017 DEFINER = 'root'@'localhost' */
                  TRIGGER `tr_a_delete` AFTER DELETE ON `t_a`
                  FOR EACH ROW BEGIN
                      --t_a表數據刪除后,t_b表關聯條件相同的數據也同步刪除
                      DELETE FROM `t_b` WHERE username=Old.username AND groupid=OLD.groupid;
                  END;
              $$
               
              DELIMITER ;

          三、測試MySQL實例觸發器:

          分別測試實現t_a與t_b實現數據同步CUD(增、改、刪)3個Triggers

          1、測試MySQL的實例tr_a_insert觸發器:

          在t_a表中新增一條數據,然后分別查詢t_a/t_b表的數據是否數據同步,測試觸發器成功標志,t_a表無論在何種情況下,新增了一條或多條記錄集時,沒有t_b表做任何數據insert操作,它同時新增了一樣的多條記錄集。

          下面來進行MySQL觸發器實例測試:



              --t_a表新增一條記錄集
                  INSERT INTO `t_a` (username,groupid) VALUES ('sky54.net',123)
                 
                  --查詢t_a表
                  SELECT id,username,groupid FROM `t_a`
                 
                  --查詢t_b表
                  SELECT id,username,groupid FROM `t_b`

          2、測試MySQL的實例tr_a_update、tr_a_delete觸發器:

          這兩個MySQL觸發器測試原理、步驟與tr_a_insert觸發器一樣的,先修改/刪除一條數據,然后分別查看t_a、t_b表的數據變化情況,數據變化同步說明Trigger實例成功,否則需要逐步排查錯誤原因。

          世界上任何一種事物都其其優點和缺點,優點與缺點是自身一個相對立的面。當然這里不是強調“世界非黑即白”式的“二元論”,“存在即合理”嘛。當然 MySQL觸發器的優點不說了,說一下不足之處,MySQL Trigger沒有很好的調試、管理環境,難于在各種系統環境下測試,測試比MySQL存儲過程要難,所以建議在生成環境下,盡量用存儲過程來代替 MySQL觸發器。

          本篇結束前再強調一下,支持觸發器的MySQL版本需要5.0以上,5.0以前版本的MySQL升級到5.0以后版本方可使用觸發器哦!








          http://blog.csdn.net/hireboy/article/details/18079183



          posted @ 2016-08-18 17:25 abin 閱讀(1075) | 評論 (0)編輯 收藏

               摘要: 在開發高并發系統時有三把利器用來保護系統:緩存、降級和限流。緩存的目的是提升系統訪問速度和增大系統能處理的容量,可謂是抗高并發流量的銀彈;而降級是當服務出問題或者影響到核心流程的性能則需要暫時屏蔽掉,待高峰或者問題解決后再打開;而有些場景并不能用緩存和降級來解決,比如稀缺資源(秒殺、搶購)、寫服務(如評論、下單)、頻繁的復雜查詢(評論的最后幾頁),因此需有一種手段來限制這些場景的并發/請求量,即限...  閱讀全文
          posted @ 2016-06-14 13:38 abin 閱讀(2705) | 評論 (1)編輯 收藏

          主站蜘蛛池模板: 东兰县| 宜州市| 安丘市| 星座| 沅陵县| 偏关县| 剑阁县| 道真| 灵璧县| 祁阳县| 武功县| 岐山县| 科技| 凉城县| 额济纳旗| 绥滨县| 公主岭市| 庆安县| 武威市| 潼关县| 忻城县| 博爱县| 大同县| 紫阳县| 建始县| 鸡西市| 长宁县| 西平县| 扎赉特旗| 白城市| 墨玉县| 东至县| 体育| 尤溪县| 麻江县| 南宫市| 玉林市| 宜君县| 白河县| 左云县| 阿拉善盟|