少年阿宾 -凯发k8网页登录

那些青春的岁月

  凯发k8网页登录-凯发天生赢家一触即发官网 :: 凯发k8网页登录首页 :: 联系 :: 聚合  :: 管理
  500 posts :: 0 stories :: 135 comments :: 0 trackbacks

2018年1月4日 #

     摘要: 前阵子从支付宝转账1万块钱到余额宝,这是日常生活的一件普通小事,但作为互联网研发人员的职业病,我就思考支付宝扣除1万之后,如果系统挂掉怎么办,这时余额宝账户并没有增加1万,数据就会出现不一致状况了。上述场景在各个类型的系统中都能找到相似影子,比如在电商系统中,当有用户下单后,除了在订单表插入一条记录外,对应商品表的这个商品数量必须减1吧,怎么保证?!在搜索广告系统中,当用户点击某广告后,除了在点击...  阅读全文
posted @ 2018-01-04 00:01 abin 阅读(688) | 评论 (0)编辑 收藏

2017年12月31日 #

微服务架构采用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 阅读(384) | 评论 (0)编辑 收藏

2017年12月24日 #

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 阅读(417) | 评论 (0)编辑 收藏

2017年8月3日 #

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://www.cnblogs.com/chenny7/p/4497969.html





posted @ 2017-08-03 11:34 abin 阅读(411) | 评论 (0)编辑 收藏

2017年8月2日 #

不可或缺的函数,在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 阅读(537) | 评论 (0)编辑 收藏

    2016年8月18日 #

    mysql触发器trigger实例篇
    发表于668 天前 ⁄  ⁄ 

    以前关注的数据存储过程不太懂其中奥妙,最近遇到跨,同时对多个表进行curd(create增、update改、read读、delete删),怎么才能让繁琐的数据curd同步变得更容易呢?相信很多人会首先想到了存储过程、触发器,这种想法确实不错。于是饶有兴趣地亲自写了cud(增、改、删)触发器的实例,用触发器实现多表数据同步更新。

    定义: 何为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 阅读(1037) | 评论 (0)编辑 收藏

    2016年6月14日 #

         摘要: 在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹;而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开;而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限...  阅读全文
    posted @ 2016-06-14 13:38 abin 阅读(2664) | 评论 (1)编辑 收藏

    2016年5月13日 #

    install the command line client

    if you prefer command line client, then you can install it on your linux with the following command.

    debian

    sudo apt-get install python-pip sudo pip install shadowsocks

    ubuntu

    yes, you can use the above commands to install shadowsocks client on ubuntu. but it will install it under ~/.local/bin/ directory and it causes loads of trouble. so i suggest using su to become root first and then issue the following two commands.

    apt-get install python-pip pip install shadowsocks

    fedora/centos

    sudo yum install python-setuptools   or   sudo dnf install python-setuptools sudo easy_install pip sudo pip install shadowsocks

    opensuse

    sudo zypper install python-pip sudo pip install shadowsocks

    archlinux

    sudo pacman -s python-pip sudo pip install shadowsocks

    as you can see the command of installing shadowsocks client is the same to the command of installing shadowsocks server, because the above command will install both the client and the server. you can verify this by looking at the installation script output

    downloading/unpacking shadowsocks downloading shadowsocks-2.8.2.tar.gz running setup.py (path:/tmp/pip-build-pqigug/shadowsocks/setup.py) egg_info for package shadowsocks  installing collected packages: shadowsocks running setup.py install for shadowsocks  installing sslocal script to /usr/local/bin installing ssserver script to /usr/local/bin successfully installed shadowsocks cleaning up...

    sslocal is the client software and ssserver is the server software. on some linux distros such as ubuntu, the shadowsocks client sslocal is installed under /usr/local/bin. on others such as archsslocal is installed under /usr/bin/. your can use whereis command to find the exact location.

    user@debian:~$ whereis sslocal sslocal: /usr/local/bin/sslocal

    create a configuration file

    we will create a configuration file under /etc/

    sudo vi /etc/shadowsocks.json

    put the following text in the file. replace server-ip with your actual ip and set a password.

    {
    "server":"server-ip",
    "server_port":8000,
    "local_address": "127.0.0.1",
    "local_port":1080,
    "password":"your-password",
    "timeout":600,
    "method":"aes-256-cfb"
    }

    save and close the file. next start the client using command line

    sslocal -c /etc/shadowsocks.json

    to run in the background

    sudo sslocal -c /etc/shadowsocks.json -d start

    auto start the client on system boot

    edit /etc/rc.local file

    sudo vi /etc/rc.local

    put the following line above the exit 0 line:

    sudo sslocal -c /etc/shadowsocks.json -d start

    save and close the file. next time you start your computer, shadowsocks client will automatically start and connect to your shadowsocks server.

    check if it works

    after you rebooted your computer, enter the following command in terminal:

    sudo systemctl status rc-local.service

    if your sslocal command works then you will get this ouput:


    ● rc-local.service - /etc/rc.local 

    compatibility loaded: loaded (/etc/systemd/system/rc-local.service; enabled; vendor preset: enabled)
    active: active (running) since fri 2015-11-27 03:19:25 cst; 2min 39s ago
    process: 881 execstart=/etc/rc.local start (code=exited, status=0/success)
    cgroup: /system.slice/rc-local.service
    ├─ 887 watch -n 60 su matrix -c ibam
    └─1112 /usr/bin/python /usr/local/bin/sslocal -c /etc/shadowsocks....

    as you can see from the last line, the sslocal command created a process whose pid is 1112 on my machine. it means shadowsocks client is running smoothly. and of course you can tell your browser to connect through your shadowsocks client to see if everything goes well.

    if for some reason your /etc/rc.local script won’t run, then check the following post to find the solution.

    install the command line client

    debian

    ubuntu

    fedora/centos

    opensuse

    archlinux

    create a configuration file

    to run in the background

    auto start the client on system boot

    check if it works

    posted @ abin 阅读(786) | | 编辑 收藏

    2016年4月27日 #

    废话少说,直接上代码,以前都是调用别人写好的,现在有时间自己弄下,具体功能如下:
    1、httpclient http 线程池:
    2、httpclient https(单向不验证证书) 线程池:

    https在%tomcat_home%/conf/server.xml里面的配置文件
         maxthreads="150" scheme="https" secure="true" 
         clientauth="false" keystorefile="d:/tomcat.keystore" 
         keystorepass="heikaim" sslprotocol="tls"  executor="tomcatthreadpool"/> 
    其中 clientauth="false"表示不开启证书验证,只是单存的走https



    package com.abin.lee.util;

    import org.apache.commons.collections4.maputils;
    import org.apache.commons.lang3.stringutils;
    import org.apache.http.*;
    import org.apache.http.client.httprequestretryhandler;
    import org.apache.http.client.config.cookiespecs;
    import org.apache.http.client.config.requestconfig;
    import org.apache.http.client.entity.urlencodedformentity;
    import org.apache.http.client.methods.closeablehttpresponse;
    import org.apache.http.client.methods.httpget;
    import org.apache.http.client.methods.httppost;
    import org.apache.http.client.protocol.httpclientcontext;
    import org.apache.http.config.registry;
    import org.apache.http.config.registrybuilder;
    import org.apache.http.conn.connecttimeoutexception;
    import org.apache.http.conn.socket.connectionsocketfactory;
    import org.apache.http.conn.socket.plainconnectionsocketfactory;
    import org.apache.http.conn.ssl.noophostnameverifier;
    import org.apache.http.conn.ssl.sslconnectionsocketfactory;
    import org.apache.http.entity.stringentity;
    import org.apache.http.impl.client.closeablehttpclient;
    import org.apache.http.impl.client.httpclients;
    import org.apache.http.impl.conn.poolinghttpclientconnectionmanager;
    import org.apache.http.message.basicheader;
    import org.apache.http.message.basicnamevaluepair;
    import org.apache.http.protocol.httpcontext;
    import org.apache.http.util.entityutils;

    import javax.net.ssl.*;
    import java.io.ioexception;
    import java.io.interruptedioexception;
    import java.net.unknownhostexception;
    import java.nio.charset.charset;
    import java.security.cert.certificateexception;
    import java.security.cert.x509certificate;
    import java.util.*;

    /**
    * created with intellij idea.
    * user: abin
    * date: 16-4-18
    * time: 上午10:24
    * to change this template use file | settings | file templates.
    */
    public class httpclientutil {
    private static closeablehttpclient httpsclient = null;
    private static closeablehttpclient httpclient = null;

    static {
    httpclient = gethttpclient();
    httpsclient = gethttpsclient();
    }

    public static closeablehttpclient gethttpclient() {
    try {
    httpclient = httpclients.custom()
    .setconnectionmanager(poolmanager.gethttppoolinstance())
    .setconnectionmanagershared(true)
    .setdefaultrequestconfig(requestconfig())
    .setretryhandler(retryhandler())
    .build();
    } catch (exception e) {
    e.printstacktrace();
    }
    return httpclient;
    }


    public static closeablehttpclient gethttpsclient() {
    try {
    //secure protocol implementation.
    sslcontext ctx = sslcontext.getinstance("ssl");
    //implementation of a trust manager for x509 certificates
    trustmanager x509trustmanager = new x509trustmanager() {
    public void checkclienttrusted(x509certificate[] xcs,
    string string) throws certificateexception {
    }
    public void checkservertrusted(x509certificate[] xcs,
    string string) throws certificateexception {
    }
    public x509certificate[] getacceptedissuers() {
    return null;
    }
    };
    ctx.init(null, new trustmanager[]{x509trustmanager}, null);
    //首先设置全局的标准cookie策略
    // requestconfig requestconfig = requestconfig.custom().setcookiespec(cookiespecs.standard_strict).build();
    connectionsocketfactory connectionsocketfactory = new sslconnectionsocketfactory(ctx, hostnameverifier);
    registry socketfactoryregistry = registrybuilder.create()
    .register("http", plainconnectionsocketfactory.instance)
    .register("https", connectionsocketfactory).build();
    // 设置连接池
    httpsclient = httpclients.custom()
    .setconnectionmanager(poolsmanager.gethttpspoolinstance(socketfactoryregistry))
    .setconnectionmanagershared(true)
    .setdefaultrequestconfig(requestconfig())
    .setretryhandler(retryhandler())
    .build();
    } catch (exception e) {
    e.printstacktrace();
    }
    return httpsclient;
    }

    // 配置请求的超时设置
    //首先设置全局的标准cookie策略
    public static requestconfig requestconfig(){
    requestconfig requestconfig = requestconfig.custom()
    .setcookiespec(cookiespecs.standard_strict)
    .setconnectionrequesttimeout(20000)
    .setconnecttimeout(20000)
    .setsockettimeout(20000)
    .build();
    return requestconfig;
    }

    public static httprequestretryhandler retryhandler(){
    //请求重试处理
    httprequestretryhandler httprequestretryhandler = new httprequestretryhandler() {
    public boolean retryrequest(ioexception exception,int executioncount, httpcontext context) {
    if (executioncount >= 5) {// 如果已经重试了5次,就放弃
    return false;
    }
    if (exception instanceof nohttpresponseexception) {// 如果服务器丢掉了连接,那么就重试
    return true;
    }
    if (exception instanceof sslhandshakeexception) {// 不要重试ssl握手异常
    return false;
    }
    if (exception instanceof interruptedioexception) {// 超时
    return false;
    }
    if (exception instanceof unknownhostexception) {// 目标服务器不可达
    return false;
    }
    if (exception instanceof connecttimeoutexception) {// 连接被拒绝
    return false;
    }
    if (exception instanceof sslexception) {// ssl握手异常
    return false;
    }

    httpclientcontext clientcontext = httpclientcontext.adapt(context);
    httprequest request = clientcontext.getrequest();
    // 如果请求是幂等的,就再次尝试
    if (!(request instanceof httpentityenclosingrequest)) {
    return true;
    }
    return false;
    }
    };
    return httprequestretryhandler;
    }



    //创建hostnameverifier
    //用于解决javax.net.ssl.sslexception: hostname in certificate didn't match: <123.125.97.66> != <123.125.97.241>
    static hostnameverifier hostnameverifier = new noophostnameverifier(){
    @override
    public boolean verify(string s, sslsession sslsession) {
    return super.verify(s, sslsession);
    }
    };


    public static class poolmanager {
    public static poolinghttpclientconnectionmanager clientconnectionmanager = null;
    private static int maxtotal = 200;
    private static int defaultmaxperroute = 100;

    private poolmanager(){
    clientconnectionmanager.setmaxtotal(maxtotal);
    clientconnectionmanager.setdefaultmaxperroute(defaultmaxperroute);
    }

    private static class poolmanagerholder{
    public static poolmanager instance = new poolmanager();
    }

    public static poolmanager getinstance() {
    if(null == clientconnectionmanager)
    clientconnectionmanager = new poolinghttpclientconnectionmanager();
    return poolmanagerholder.instance;
    }

    public static poolinghttpclientconnectionmanager gethttppoolinstance() {
    poolmanager.getinstance();
    // system.out.println("getavailable=" clientconnectionmanager.gettotalstats().getavailable());
    // system.out.println("getleased=" clientconnectionmanager.gettotalstats().getleased());
    // system.out.println("getmax=" clientconnectionmanager.gettotalstats().getmax());
    // system.out.println("getpending=" clientconnectionmanager.gettotalstats().getpending());
    return poolmanager.clientconnectionmanager;
    }


    }

    public static class poolsmanager {
    public static poolinghttpclientconnectionmanager clientconnectionmanager = null;
    private static int maxtotal = 200;
    private static int defaultmaxperroute = 100;

    private poolsmanager(){
    clientconnectionmanager.setmaxtotal(maxtotal);
    clientconnectionmanager.setdefaultmaxperroute(defaultmaxperroute);
    }

    private static class poolsmanagerholder{
    public static poolsmanager instance = new poolsmanager();
    }

    public static poolsmanager getinstance(registry socketfactoryregistry) {
    if(null == clientconnectionmanager)
    clientconnectionmanager = new poolinghttpclientconnectionmanager(socketfactoryregistry);
    return poolsmanagerholder.instance;
    }

    public static poolinghttpclientconnectionmanager gethttpspoolinstance(registry socketfactoryregistry) {
    poolsmanager.getinstance(socketfactoryregistry);
    // system.out.println("getavailable=" clientconnectionmanager.gettotalstats().getavailable());
    // system.out.println("getleased=" clientconnectionmanager.gettotalstats().getleased());
    // system.out.println("getmax=" clientconnectionmanager.gettotalstats().getmax());
    // system.out.println("getpending=" clientconnectionmanager.gettotalstats().getpending());
    return poolsmanager.clientconnectionmanager;
    }

    }

    public static string httppost(map request, string httpurl){
    string result = "";
    closeablehttpclient httpclient = gethttpclient();
    try {
    if(maputils.isempty(request))
    throw new exception("请求参数不能为空");
    httppost httppost = new httppost(httpurl);
    list nvps = new arraylist();
    for(iterator> iterator=request.entryset().iterator(); iterator.hasnext();){
    map.entry entry = iterator.next();
    nvps.add(new basicnamevaluepair(entry.getkey(), entry.getvalue()));
    }
    httppost.setentity(new urlencodedformentity(nvps, consts.utf_8));
    system.out.println("executing request: " httppost.getrequestline());
    closeablehttpresponse response = httpclient.execute(httppost);
    result = entityutils.tostring(response.getentity());
    system.out.println("executing response: " result);
    } catch (exception e) {
    throw new runtimeexception(e);
    } finally {
    try {
    httpclient.close();
    } catch (ioexception e) {
    e.printstacktrace();
    }
    }
    return result;
    }

    public static string httppost(string json, string httpurl, map headers){
    string result = "";
    closeablehttpclient httpclient = gethttpclient();
    try {
    if(stringutils.isblank(json))
    throw new exception("请求参数不能为空");
    httppost httppost = new httppost(httpurl);
    for(iterator> iterator=headers.entryset().iterator();iterator.hasnext();){
    map.entry entry = iterator.next();
    header header = new basicheader(entry.getkey(), entry.getvalue());
    httppost.setheader(header);
    }
    httppost.setentity(new stringentity(json, charset.forname("utf-8")));
    system.out.println("executing request: " httppost.getrequestline());
    closeablehttpresponse response = httpclient.execute(httppost);
    result = entityutils.tostring(response.getentity());
    system.out.println("executing response: " result);
    } catch (exception e) {
    throw new runtimeexception(e);
    } finally {
    try {
    httpclient.close();
    } catch (ioexception e) {
    e.printstacktrace();
    }
    }
    return result;
    }

    public static string httpget(string httpurl, map headers) {
    string result = "";
    closeablehttpclient httpclient = gethttpclient();
    try {
    httpget httpget = new httpget(httpurl);
    system.out.println("executing request: " httpget.getrequestline());
    for(iterator> iterator=headers.entryset().iterator();iterator.hasnext();){
    map.entry entry = iterator.next();
    header header = new basicheader(entry.getkey(), entry.getvalue());
    httpget.setheader(header);
    }
    closeablehttpresponse response = httpclient.execute(httpget);
    result = entityutils.tostring(response.getentity());
    system.out.println("executing response: " result);
    } catch (exception e) {
    throw new runtimeexception(e);
    } finally {
    try {
    httpclient.close();
    } catch (ioexception e) {
    e.printstacktrace();
    }
    }
    return result;
    }


    public static string httpget(string httpurl) {
    string result = "";
    closeablehttpclient httpclient = gethttpclient();
    try {
    httpget httpget = new httpget(httpurl);
    system.out.println("executing request: " httpget.getrequestline());
    closeablehttpresponse response = httpclient.execute(httpget);
    result = entityutils.tostring(response.getentity());
    system.out.println("executing response: " result);
    } catch (exception e) {
    throw new runtimeexception(e);
    } finally {
    try {
    httpclient.close();
    } catch (ioexception e) {
    e.printstacktrace();
    }
    }
    return result;
    }





    maven依赖:
     
           
                org.apache.httpcomponents
                httpclient
                4.5.2
           
           
                org.apache.httpcomponents
                httpcore
                4.4.4
           
           
                org.apache.httpcomponents
                httpmime
                4.5.2
           

    <dependency>
    <groupid>org.apache.commonsgroupid>
    <artifactid>commons-collections4artifactid>
    <version>4.1version>
    dependency>
    posted @ 2016-04-27 19:04 abin 阅读(2955) | 评论 (0)编辑 收藏

    2015年11月3日 #

    1、twemproxy explore

          当我们有大量 redis 或 memcached 的时候,通常只能通过客户端的一些数据分配算法(比如一致性哈希),来实现集群存储的特性。虽然redis 2.6版本已经发布redis cluster,但还不是很成熟适用正式生产环境。 redis 的 cluster 方案还没有正式推出之前,我们通过 proxy 的方式来实现集群存储

           twitter,世界最大的redis集群之一部署在twitter用于为用户提供时间轴数据。twitter open source部门提供了twemproxy。

         twemproxy,也叫nutcraker。是一个twtter开源的一个redis和memcache代理服务器。 redis作为一个高效的缓存服务器,非常具有应用价值。但是当使用比较多的时候,就希望可以通过某种方式 统一进行管理。避免每个应用每个客户端管理连接的松散性。同时在一定程度上变得可以控制。

          twemproxy是一个快速的单线程代理程序,支持memcached ascii协议和更新的redis协议:

         它全部用c写成,使用apache 2.0 license授权。项目在linux上可以工作,而在osx上无法编译,因为它依赖了epoll api.

          twemproxy 通过引入一个代理层,可以将其后端的多台 redis 或 memcached 实例进行统一管理与分配,使应用程序只需要在 twemproxy 上进行操作,而不用关心后面具体有多少个真实的 redis 或 memcached 存储。 

    2、twemproxy特性:

      • 支持失败节点自动删除

        • 可以设置重新连接该节点的时间
        • 可以设置连接多少次之后删除该节点
        • 该方式适合作为cache存储
      • 支持设置hashtag

        • 通过hashtag可以自己设定将两个keyhash到同一个实例上去。
      • 减少与redis的直接连接数

        • 保持与redis的长连接
        • 可设置代理与后台每个redis连接的数目
      • 自动分片到后端多个redis实例上

        • 多种hash算法:能够使用不同的策略和散列函数支持一致性hash。
        • 可以设置后端实例的权重
      • 避免单点问题

        • 可以平行部署多个代理层.client自动选择可用的一个
      • 支持redis pipelining request

             支持请求的流式与批处理,降低来回的消耗

      • 支持状态监控

        • 可设置状态监控ip和端口,访问ip和端口可以得到一个json格式的状态信息串
        • 可设置监控信息刷新间隔时间
      • 高吞吐量

        • 连接复用,内存复用。
        • 将多个连接请求,组成reids pipelining统一向redis请求。

         另外可以修改redis的源代码,抽取出redis中的前半部分,作为一个中间代理层。最终都是通过linux下的epoll 事件机制提高并发效率,其中nutcraker本身也是使用epoll的事件机制。并且在性能测试上的表现非常出色。

    3、twemproxy问题与不足


    twemproxy 由于其自身原理限制,有一些不足之处,如: 
    • 不支持针对多个值的操作,比如取sets的子交并补等(mget 和 del 除外)
    • 不支持redis的事务操作
    • 出错提示还不够完善
    • 也不支持select操作

    4、安装与配置 

    具体的安装步骤可用查看github:
    twemproxy 的安装,主要命令如下: 
    apt-get install automake  
    apt-get install libtool  
    git clone git://github.com/twitter/twemproxy.git  
    cd twemproxy  
    autoreconf -fvi  
    ./configure --enable-debug=log  
    make  
    src/nutcracker -h

    通过上面的命令就算安装好了,然后是具体的配置,下面是一个典型的配置 
        redis1:  
          listen: 127.0.0.1:6379 #使用哪个端口启动twemproxy  
          redis: true #是否是redis的proxy  
          hash: fnv1a_64 #指定具体的hash函数  
          distribution: ketama #具体的hash算法  
          auto_eject_hosts: true #是否在结点无法响应的时候临时摘除结点  
          timeout: 400 #超时时间(毫秒)  
          server_retry_timeout: 2000 #重试的时间(毫秒)  
          server_failure_limit: 1 #结点故障多少次就算摘除掉  
          servers: #下面表示所有的redis节点(ip:端口号:权重)  
           - 127.0.0.1:6380:1  
           - 127.0.0.1:6381:1  
           - 127.0.0.1:6382:1  
          
        redis2:  
          listen: 0.0.0.0:10000  
          redis: true  
          hash: fnv1a_64  
          distribution: ketama  
          auto_eject_hosts: false  
          timeout: 400  
          servers:  
           - 127.0.0.1:6379:1  
           - 127.0.0.1:6380:1  
           - 127.0.0.1:6381:1  
           - 127.0.0.1:6382:1 

    你可以同时开启多个 twemproxy 实例,它们都可以进行读写,这样你的应用程序就可以完全避免所谓的单点故障。


    http://blog.csdn.net/hguisu/article/details/9174459/
    posted @ 2015-11-03 19:30 abin 阅读(956) | 评论 (0)编辑 收藏

    网站地图