// // Client <---> Proxy[BackendConn] <---> RPC Server[包含LB] // BackConn <====> RPC Server // loopReader从RPC Server读取数据,然后根据返回的结果来设置: Client的Request的状态 // // 1. bc.flushRequest // 2. bc.setResponse // func (bc *BackendConn) loopReader(c *TBufferedFramedTransport, connOver *sync.WaitGroup) { go func() { defer connOver.Done() defer c.Close() lastTime := time.Now().Unix() // Active状态,或者最近5s有数据返回 // 设计理由:服务在线,则请求正常发送; // 服务下线后,则期待后端服务的数据继续返回(最多等待5s) for bc.IsConnActive.Get() || (time.Now().Unix()-lastTime < 5) { // 读取来自后端服务的数据,通过 setResponse 转交给 前端 // client <---> proxy <-----> backend_conn <---> rpc_server // ReadFrame需要有一个度? 如果碰到EOF该如何处理呢? // io.EOF在两种情况下会出现 // resp, err := c.ReadFrame() lastTime = time.Now().Unix() if err != nil { err1, ok := err.(thrift.TTransportException) if !ok || err1.TypeId() != thrift.END_OF_FILE { log.ErrorErrorf(err, Red("[%s]ReadFrame From Server with Error: %v"), bc.service, err) } bc.flushRequests(err) break } else { bc.setResponse(nil, resp, err) } } bc.flushRequests(errors.New("BackendConn Timeout")) }() }
// // 目前有两类请求: // 1. ping request // 2. 正常的请求 func (bc *BackendConn) PushBack(r *Request) { if bc.IsConnActive.Get() && !bc.IsMarkOffline.Get() { // 1. 处于Active状态,并且没有标记下线, 则将 Request 添加到 input 中 r.Wait.Add(1) bc.input <- r } else { // 2. 直接报错(返回) r.Response.Err = errors.New(fmt.Sprintf("[%s] Request Assigned to inactive BackendConn", bc.service)) log.Warn(Magenta("Push Request To Inactive Backend")) } }
// // 将Request分配给BackendConnLB // func (bc *BackendConnLB) PushBack(r *Request) { // 关键路径必须有Log, 高频路径的Log需要受verbose状态的控制 if bc.IsConnActive.Get() { r.Service = bc.serviceName r.Wait.Add(1) bc.input <- r } else { r.Response.Err = errors.New(fmt.Sprintf("[%s] Request Assigned to inactive BackendConnLB", bc.serviceName)) log.Warn(Magenta("Push Request To Inactive Backend")) } }
const ( B = 1 << (10 * iota) KB MB GB TB PB ) var ( BytesizeRegexp = regexp.MustCompile(`(?i)^\s*(\-?[\d\.]+)\s*([KMGTP]?B|[BKMGTP]|)\s*$`) digitsRegexp = regexp.MustCompile(`^\-?\d+$`) ) var ( ErrBadBytesize = errors.New("invalid byte size") ErrBadBytesizeUnit = errors.New("invalid byte size unit") ) func Parse(s string) (int64, error) { if !BytesizeRegexp.MatchString(s) { return 0, errors.Trace(ErrBadBytesize) } subs := BytesizeRegexp.FindStringSubmatch(s) if len(subs) != 3 { return 0, errors.Trace(ErrBadBytesize) } size := int64(0) switch strings.ToUpper(string(subs[2])) {
func NewRollingFile(basePath string, logKeepDays int) (io.WriteCloser, error) { if logKeepDays <= 0 { return nil, errors.Errorf("invalid max file-frag = %d", logKeepDays) } if _, file := path.Split(basePath); file == "" { return nil, errors.Errorf("invalid base-path = %s, file name is required", basePath) } return &rollingFile{ logKeepDays: logKeepDays, basePath: basePath, nextStartOfDate: nextStartOfDay(time.Now()), }, nil } var ErrClosedRollingFile = errors.New("rolling file is closed") func (r *rollingFile) roll() error { if r.file != nil { // 还没有日期切换 now := time.Now() if now.Before(r.nextStartOfDate) { return nil } r.file.Close() r.file = nil r.nextStartOfDate = nextStartOfDay(now) // 将logKeepDays之前几天的日志删除 for i := r.logKeepDays; i < r.logKeepDays+4; i++ {
// // 将 bc.input 中的Request写入后端的服务器 // func (bc *BackendConn) loopWriter(c *TBufferedFramedTransport) error { defer func() { // 关闭心跳的Ticker bc.hbTicker.Stop() bc.hbTicker = nil }() var r *Request var ok bool // 准备HB Ticker bc.hbTicker = time.NewTicker(time.Second) bc.hbLastTime.Set(time.Now().Unix()) for true { // 等待输入的Event, 或者 heartbeatTimeout select { case <-bc.hbTicker.C: if time.Now().Unix()-bc.hbLastTime.Get() > HB_TIMEOUT { return errors.New(fmt.Sprintf("[%s]HB timeout", bc.service)) } else { // 定时添加Ping的任务; 如果标记下线,则不在心跳 if !bc.IsMarkOffline.Get() { // 发送心跳信息 r := NewPingRequest() bc.PushBack(r) // 同时检测当前的异常请求 expired := microseconds() - REQUEST_EXPIRED_TIME_MICRO // 以microsecond为单位 // microseconds() - request.Start > REQUEST_EXPIRED_TIME_MICRO // 超时: microseconds() - REQUEST_EXPIRED_TIME_MICRO > request.Start bc.seqNumRequestMap.RemoveExpired(expired) } } case r, ok = <-bc.input: if !ok { return nil } else { // // 如果暂时没有数据输入,则p策略可能就有问题了 // 只有写入数据,才有可能产生flush; 如果是最后一个数据必须自己flush, 否则就可能无限期等待 // if r.Request.TypeId == MESSAGE_TYPE_HEART_BEAT { // 过期的HB信号,直接放弃 if time.Now().Unix()-r.Start > 4 { log.Printf(Magenta("Expired HB Signal")) } } // 请求正常转发给后端的Rpc Server var flush = len(bc.input) == 0 // 1. 替换新的SeqId r.ReplaceSeqId(bc.currentSeqId) bc.IncreaseCurrentSeqId() // 2. 主动控制Buffer的flush // 先记录SeqId <--> Request, 再发送请求 // 否则: 请求从后端返回,记录还没有完成,就容易导致Request丢失 bc.seqNumRequestMap.Add(r.Response.SeqId, r) // 2. 主动控制Buffer的flush c.Write(r.Request.Data) err := c.FlushBuffer(flush) if err == nil { log.Debugf("--> SeqId: %d vs. %d To Backend", r.Request.SeqId, r.Response.SeqId) } else { bc.seqNumRequestMap.Pop(r.Response.SeqId) // 如果写错了,在删除 // 进入不可用状态(不可用状态下,通过自我心跳进入可用状态) return bc.setResponse(r, nil, err) } } } } return nil }
// // 数据: LB ---> backend services // // 如果input关闭,且loopWriter正常处理完毕之后,返回nil // 其他情况返回error // func (bc *BackendConnLB) loopWriter() error { // 正常情况下, ok总是为True; 除非bc.input的发送者主动关闭了channel, 表示再也没有新的Task过来了 // 参考: https://tour.golang.org/concurrency/4 // 如果input没有关闭,则会block c := NewTBufferedFramedTransport(bc.transport, 100*time.Microsecond, 20) // bc.MarkConnActiveOK() // 准备接受数据 // BackendConnLB 在构造之初就有打开的transport, 并且Active默认为OK defer func() { bc.hbTicker.Stop() bc.hbTicker = nil }() bc.loopReader(c) // 异步 // 建立连接之后,就启动HB bc.hbTicker = time.NewTicker(time.Second) bc.hbLastTime.Set(time.Now().Unix()) var r *Request var ok bool for true { // 等待输入的Event, 或者 heartbeatTimeout select { case <-bc.hbTicker.C: // 两种情况下,心跳会超时 // 1. 对方挂了 // 2. 自己快要挂了,然后就不再发送心跳;没有了心跳,就会超时 if time.Now().Unix()-bc.hbLastTime.Get() > HB_TIMEOUT { // 强制关闭c c.Close() return errors.New("Worker HB timeout") } else { if bc.IsConnActive.Get() { // 定时添加Ping的任务 r := NewPingRequest() bc.PushBack(r) // 同时检测当前的异常请求 expired := microseconds() - REQUEST_EXPIRED_TIME_MICRO // 以microsecond为单位 bc.seqNumRequestMap.RemoveExpired(expired) } } case r, ok = <-bc.input: if !ok { return nil } else { // 如果暂时没有数据输入,则p策略可能就有问题了 // 只有写入数据,才有可能产生flush; 如果是最后一个数据必须自己flush, 否则就可能无限期等待 // if r.Request.TypeId == MESSAGE_TYPE_HEART_BEAT { // 过期的HB信号,直接放弃 if time.Now().Unix()-r.Start > 4 { log.Warnf(Red("Expired HB Signals")) } } // 先不做优化 var flush = true // len(bc.input) == 0 // 1. 替换新的SeqId(currentSeqId只在当前线程中使用, 不需要同步) r.ReplaceSeqId(bc.currentSeqId) bc.IncreaseCurrentSeqId() // 2. 主动控制Buffer的flush // 先记录SeqId <--> Request, 再发送请求 // 否则: 请求从后端返回,记录还没有完成,就容易导致Request丢失 bc.seqNumRequestMap.Add(r.Response.SeqId, r) c.Write(r.Request.Data) err := c.FlushBuffer(flush) if err != nil { bc.seqNumRequestMap.Pop(r.Response.SeqId) log.ErrorErrorf(err, "FlushBuffer Error: %v\n", err) // 进入不可用状态(不可用状态下,通过自我心跳进入可用状态) return bc.setResponse(r, nil, err) } } } } return nil }