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)
阅读更多

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多行
阅读更多

golang/net包与epoll

  • linux下go的网络包底层如tcp也是采用epoll来实现, 你可以从Accept方法一路追下去, 追到尽头你会看到internal/poll/fd_poll_runtime.go里面这些在runtime实现的方法:
  • 此时到src/runtime/netpoll.go就能看到上述这些方法的实现, 再往下追下去就可以看到各个平台的具体实现了, 如netpoll_epoll.go netpoll_kqueue.go netpoll_windows.go, 看到netpoll_epoll.go里面的epollcreate, epollctl, epollwait了吧, 多么熟悉的几个函数!
  • 虽然net包底层用epoll实现了, 但是实际我们在用tcp还是开goroutine来serve
  • net包就是推荐我们用goroutine来玩tcp, 应对大部分场景妥妥的
  • 面对比较变态的场景并发量贼高时, goroutine尽管只有消耗2k~8k的栈空间, 连接一多还是耗不起, 此时就只能用一些黑魔法来使用epoll了
  • 具体怎么玩可以参照 https://github.com/mailru/easygo
阅读更多

OTP动态口令及底层实现

  • 最近用到了OTP, 遂mark一下
  • 我们常用的那种倒计时验证码就是TOTP, 既不是叫OTP也不是叫MFA, 经常听有人这么说所以提一嘴
  • 动态口令验证可以看作是服务端和客户端之间通过约定相同的算法来实现验证功能, 也即你在客户端看到的动态口令是客户端通过算法生成的无需请求服务端获取
阅读更多

Redis源码阅读-事件模型ae

  • src/ae.c
  • src/ae.c下的void aeMain(aeEventLoop *eventLoop)函数; 推荐从这个函数开始阅读
  • 我们着重看下aeMain里面aeProcessEvents(eventLoop, AE_ALL_EVENTS)做了什么; 这里我们留意一下里面的aeApiPoll函数, 该函数用于获取可执行的事件, 获取之后在下面的for循环中处理事件, 执行事件处理器 fe->rfileProc(eventLoop,fd,fe->clientData,mask)
  • aeApiPoll函数是ae模块提供的一个接口, 在ae_epoll.c ae_kqueue.c ae_select.c ae_evport.c都做了相应的具体实现, 也是所谓IO多路复用各平台的具体实现, 目的为了兼容不同平台
  • 备注: 也许你会好奇为啥IO多路复用没有iocp的实现难道windows就没人权吗, 其实redis的官方版本是不支持windows的, windows版本在https://github.com/microsoftarchive/redis由微软团队自己维护, 里面就有ae_wsiocp.ciocp版的实现
  • 通常说的redis的reactor模型(反应堆)其实说的就是aeMain的大循环中aeProcessEvents做的那些事情: 监听网络连接的FD的文件事件---> 获取事件---> 执行事件回调
  • 剩下具体细节不多赘述, 顺着思路看源码即可
阅读更多