grpc的goAway和keepalive

  • 虽是Http2的东西, 但也可以通过grpc的源码来侧面加深下理解
  • 告诉客户端, 服务端准备关闭了, 本连接不要发新请求过来了 (发一半的请求还是会处理完的)
  • 当client收到这个包之后就会主动关闭连接。下次需要发送数据时,就会重新建立连接
  • 流程: client收到Goaway -> client主动关闭http2连接 -> channel变为IDLE -> 用户发起新请求 -> 创建新连接 -> channel变为CONNECTING
  • 另外GoAway是实现优雅关闭的基石, 因为是client主动关闭(不同于服务端关闭), 可以避免很多无效的请求
  • 源码:
    • google.golang.org/grpc/clientconn.go: errConnDrain (drain是接收到goaway后的连接状态)
    • google.golang.org/grpc/internal/transport/http2_client.go: func (t *http2Client) Close(err error)
    • google.golang.org/grpc/internal/transport/http2_client.go: func (t *http2Client) handleGoAway(f *http2.GoAwayFrame)
      • 里面的 t.onClose(t.goAwayReason)的onClose在”grpc/clientconn.go/addrConn createTransport”里面定义的:
阅读更多

go源码阅读:TLS handshake

  • tls/handshake_server.go: serverHandshakeState

  • tls/common.go: Conn.PeerCertificates

  • tls/handshake_client.go: doFullHandshake:

  • tls/handshake_client.go: clientHandshake

    • makeClientHello
    • loadSession : 会生成sessionID
    • writeRecord : 将ClientHello发送给server
    • readHandshake: 等server回复
  • tls/handshake_server.go: serverHandshake

    • readClientHello
    • processClientHello
    • pickCipherSuite
    • doFullHandshake
    • establishKeys
    • readFinished
    • sendSessionTicket
    • sendFinished
  • tls.Conn: handlePostHandshakeMessage/handleKeyUpdate: KeyUpdate指的就是tls最后的那个对称密钥(变量名叫trafficSecret)

  • tls.Conn实现了net.Conn接口, 在Write和Read包裹了一层handShake, 后面的读写也是带了加密的(readRecordOrCCS)
阅读更多

mongodb官方备份工具, MongoDump源码分析

  • 备份逻辑就在MongoDump的Dump方法 (mongodump/mongodump.go/MongoDump.Dump)

  • Dump主要分4步:

    1. 各种初始化工作
    2. 备份metaData, index, users, roles, version 等基础数据
    3. 备份collections
    4. 备份oplog
  • 后面会经常提到Intent, 这是MongoDump自己的一个抽象概念, 可以简单理解为备份任务单元, 例如一个collection的备份对应一个Intent, oplog的备份对应一个Intent等等; 在阅读源码时你可以将Intent在脑海里替换成Task. 关于Intent详见本文后面章节

  • 核心逻辑见以下源码及注释(为了方便阅读, 这里我删减了些不关键的逻辑):

阅读更多

Redis数据迁移:RedisShake源码阅读

  • 近期用到了RedisShake做数据迁移, 源码代码量不多于是看了一遍, 本篇为阅读源码的笔记
  • 本篇重点讲解RedisShake的数据迁移功能, 其他几个功能dump,decode,restore,rump只简单提及
  • 原理用一句话概述就是: 假装成源Redis的slave, 利用pSync接收来自源Redis的数据再回放到目标Redis
  • redisShake架构图cluster版
  • redisShake架构图standalone版
阅读更多

go源码阅读:database/sql包

  • Open会返回DB对象并开启一条connectionOpener线程

    • connectionOpener主要处理下面提到的”当前连接数大于maxOpen会陷入等待”的连接资源请求
  • DB的核心方法: conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error)

    • 优先返回连接池里的连接
    • 当前连接数大于maxOpen会陷入等待, 等待取决于ctx, 也即由调用方控制
    • 其余情况则返回一个新连接
  • DB的连接池: freeConn []*driverConn

  • DB的核心方法: putConnDBLocked(conn, err) bool

    • 如果当前连接数大于maxOpen则直接返回false, 上层看到false则直接关闭该连接conn
    • 优先满足正在等待连接资源请求的(connRequests是一个map,可以看出请求连接资源并无优先级的说法)
    • 如果MaxIdleConns < len(freeConn), 即连接池满了, 则直接返回false, 上层看到false则直接关闭该连接conn
    • 否则丢到连接池freeConn中
  • 清理线程(connectionCleaner):

    • SetConnMaxLifetime,SetConnMaxIdleTime时才会去起唯一的一条清理线程
    • 清理线程定期清理连接池freeConn的连接, 根据maxLifetime和createAt清理, 也根据maxIdleTime和returnedAt清理
    • 有趣的是清理线程并不理会MaxOpen和MaxIdleConns是多少, 只关注MaxLifetime和MaxIdleTime, 反正过期了就清理
  • sql执行方法如Query,Exec,Ping等, 都会先去调conn获取连接, 再用其连接执行sql, 最后将putConnDBLocked(conn), (query是等rows全部scan完close再putConnDBLocked(conn))

  • 关于resetSession最终的去处, 总的来说就是reset了个寂寞, 最终居然只是conn.SetReadDeadline(time.Time{})?

阅读更多

go源码阅读:net/http的Transport

  • Transport是RoundTripper接口的实现: func RoundTrip(req *Request) (*Response, error)

  • Transport对外只提供方法: func RoundTrip(req *Request) (*Response, error)

  • Transport的内部对象idleLRU connLRU写得不错, 简单实现了LRU

  • http.Client就是在Tranport上简单封装一层

  • Transport就是一个连接池, 池子里面放着persistConn连接对象(idleConn map[connectMethodKey][]*persistConn)

  • queueForIdleConn: 根据请求的connectMethodKey从t.idleConn获取一个[]*persistConn切片, 并从切片中,根据算法获取一个有效的空闲连接。如果未获取到空闲连接,则将wantConn结构体放入t.idleConnWait[w.key]等待队列

  • 连接释放逻辑在 (t *Transport) tryPutIdleConn(pconn *persistConn)

    • 哪些情况才回去调 tryPutIdleConn:
      1. 大部分的异常情况
      2. responseBody read完: 代码详细见 case bodyEOF := <-waitForBodyRead
  • dialConnFor: 会调用t.dialConn获取一个真正的*persistConn。并将这个连接传递给w, 如果w已经获取到了连接,则会传递失败,此时调用t.putOrCloseIdleConn将连接放回空闲连接池。

  • dialConn:

    1. 调用t.dial(ctx, “tcp”, cm.addr())创建TCP连接并将其赋予刚new的persistConn
    2. 如果是https的请求,则对请求建立安全的tls传输通道
    3. 为persistConn创建读写buffer,如果用户没有自定义读写buffer的大小,读写bufffer的大小默认为4096
    4. 执行go pconn.readLoop()和go pconn.writeLoop()开启读写循环然后返回连接
  • dialConn里面这段代码是开启http2的核心

阅读更多

go-爬点Pixiv画师图

  • 刚好需要某个画师的插画, 故写了个简单无需登录的爬图工具
  • 根据Pixiv画师ID, 爬完直接保存在当前目录的
  • 代码很简单, 就100多行
阅读更多