V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
douyacun
V2EX  ›  Go 编程语言

websocket 实现了统计在线人数,那 websocket 该如何防爆,防跨站?

  •  
  •   douyacun ·
    douyacun · 2021-01-25 10:27:27 +08:00 · 5290 次点击
    这是一个创建于 1406 天前的主题,其中的信息可能已经有所发展或是发生改变。

    站点在线人数统计实现思路: https://www.douyacun.com/article/d189d3d86915f5ff4c3be6a517570a0a

    1. 如何检测跨站点 WebSocket 劫持漏洞
      1. 我使用 jwt 来下发 token
      2. 申请 token 的接口也是对外暴露的
    2. 同一设备 ws 连接数如何限制,避免 bug 导致连接数过多导致服务挂掉
    29 条回复    2021-01-27 11:33:38 +08:00
    airyland
        1
    airyland  
       2021-01-25 10:57:15 +08:00
    侧边栏弹出来遮住右侧内容是 feature?
    ferock
        2
    ferock  
       2021-01-25 10:58:03 +08:00
    用 tcp 长链统计在线人数???也是醉了
    jobsofchina
        3
    jobsofchina  
       2021-01-25 10:59:56 +08:00 via Android
    @ferock 这种方式有什么问题吗?展开说说
    notgod
        4
    notgod  
       2021-01-25 11:17:37 +08:00
    @jobsofchina 量,N 万 百万 在线, 你试下
    douyacun
        5
    douyacun  
    OP
       2021-01-25 11:18:20 +08:00
    @ferock wss 统计在线人数,代价还是很低的,一般只是端口不够用

    我之前的测试,库使用 gorilla/websocket 1 万个连接测试

    占用 147.93MB RAM, 平均连接每个占用 15kb 测试代码见:[github gwebsocket]( https://github.com/douyacun/gwebsocket/blob/master/v3_ws_ulimit/wsserver.go)

    ```shell
    (pprof) top
    Showing nodes accounting for 137.93MB, 93.24% of 147.93MB total
    Dropped 6 nodes (cum <= 0.74MB)
    Showing top 10 nodes out of 51
    flat flat% sum% cum cum%
    73.79MB 49.88% 49.88% 73.79MB 49.88% bufio.NewWriterSize
    34.63MB 23.41% 73.29% 34.63MB 23.41% bufio.NewReaderSize
    11MB 7.44% 80.73% 11MB 7.44% runtime.malg
    4MB 2.70% 83.44% 5.50MB 3.72% net/textproto.(*Reader).ReadMIMEHeader
    3MB 2.03% 85.46% 3.50MB 2.37% github.com/gorilla/websocket.newConn
    3MB 2.03% 87.49% 10.50MB 7.10% net/http.readRequest
    2.50MB 1.69% 89.18% 16.50MB 11.16% net/http.(*conn).readRequest
    2.50MB 1.69% 90.87% 3.50MB 2.37% context.propagateCancel
    2MB 1.35% 92.23% 2MB 1.35% syscall.anyToSockaddr
    1.50MB 1.01% 93.24% 1.50MB 1.01% net.newFD
    (pprof) web
    failed to execute dot. Is Graphviz installed? Error: exec: "dot": executable file not found in $PATH
    (pprof) list flat
    Total: 147.93MB
    ```
    goroutine 是 10003,每个 goroutine 占用 4kb 的内存

    ```shell
    (pprof) top
    Showing nodes accounting for 10001, 100% of 10003 total
    Dropped 24 nodes (cum <= 50)
    Showing top 10 nodes out of 19
    flat flat% sum% cum cum%
    10001 100% 100% 10001 100% runtime.gopark
    0 0% 100% 9998 100% bufio.(*Reader).Peek
    0 0% 100% 9998 100% bufio.(*Reader).fill
    0 0% 100% 9999 100% github.com/gorilla/websocket.(*Conn).NextReader
    0 0% 100% 9999 100% github.com/gorilla/websocket.(*Conn).ReadMessage
    0 0% 100% 9999 100% github.com/gorilla/websocket.(*Conn).advanceFrame
    0 0% 100% 9998 100% github.com/gorilla/websocket.(*Conn).read
    0 0% 100% 9999 100% internal/poll.(*FD).Read
    0 0% 100% 10001 100% internal/poll.(*pollDesc).wait
    0 0% 100% 10001 100% internal/poll.(*pollDesc).waitRead (inline)
    (pprof) list flat
    Total: 10003
    (pprof)
    ```
    douyacun
        6
    douyacun  
    OP
       2021-01-25 11:21:25 +08:00
    @airyland 刚才本子打开也恶心到我自己了,都是在外接显示上开发的
    krixaar
        7
    krixaar  
       2021-01-25 11:21:56 +08:00
    @jobsofchina 第一,可能会有跨站点 WebSocket 劫持漏洞;第二,同一设备出 bug 导致连接数过多会让服务挂掉 🤣
    zxlzy
        8
    zxlzy  
       2021-01-25 11:25:39 +08:00
    在线人数这种需求建议还是轮询吧。
    douyacun
        9
    douyacun  
    OP
       2021-01-25 11:27:41 +08:00
    @krixaar 这几个问题也是这个帖子发布的目的,也是想看看大家是怎么玩的,有什么好的思路,之前有个安卓的同学就这么搞过我~,吐血的经历
    douyacun
        10
    douyacun  
    OP
       2021-01-25 11:29:02 +08:00
    @zxlzy 自己的站点,当然随意玩耍了,主要是有 wss 使用的场景,如何防护一下
    rust
        11
    rust  
       2021-01-25 12:53:43 +08:00
    直接把用户登入的状态放在 Redis 里边啊,设置个超时时间,然后没有统计有多少数据存在不就行了
    rust
        12
    rust  
       2021-01-25 12:56:04 +08:00
    @rust 我的错,没看清标题.
    抖音 APP 在使用 WS 传输 Protobuf 编码之后的数据,同时也使用了 token,这也算是一个比较好的解决方案吧
    bbao
        13
    bbao  
       2021-01-25 13:31:33 +08:00
    @rust 登入状态放 redis 里,如何统计有多少数据存在?命令是啥呀?
    SaltyLeo
        14
    SaltyLeo  
       2021-01-25 14:21:32 +08:00
    额,不同的语言写法不同。

    思路就是丢个唯一值到客户端 cookie,再把这个值丢到 redis 设置个超时,然后每次新请求获取这个值,如果这个值还在 redis 就不做操作,返回当前 redis 总计多少个 key 。

    如果这个值不在 redis 里,就再丢一个唯一值到客户端,本地新增一个记录,返回 key 总数。
    abersheeran
        15
    abersheeran  
       2021-01-25 14:27:30 +08:00
    初次访问你网站的时候,下发一个 cookie. 后续 websocket 携带这个 cookie 去访问. 同一个 cookie 的直接 close 掉. 不带 cookie 的也 close 掉.
    learningman
        16
    learningman  
       2021-01-25 17:19:17 +08:00
    所有用户的这个值都应该是一样的啊,为啥要 ws
    直接服务端统计然后直接暴露一个固定的接口轮询不就好了
    hantsy
        17
    hantsy  
       2021-01-25 19:54:40 +08:00
    Client 与 Server 之间可以用 RabbitMQ 来缓和压力,这个在 Spring 中很好的支持,通过 STOMP 协议,客户端也用 sockjs 支持。
    BBCCBB
        18
    BBCCBB  
       2021-01-25 20:28:08 +08:00
    设置一个能承受的连接上限, 比如好几十万. 网站最大就显示这么多个数..
    BBCCBB
        19
    BBCCBB  
       2021-01-25 20:28:26 +08:00
    超过就直接连接失败
    ihipop
        20
    ihipop  
       2021-01-25 21:24:09 +08:00 via Android
    @douyacun 一个端口服务能服务多少用户和服务器本地有多少个端口(也就是你认为的那个常规是 65535 的数值)没关系。
    ihipop
        21
    ihipop  
       2021-01-25 21:28:13 +08:00 via Android
    @ihipop 忽略我上面的发言,原来楼主是做压力测试

    我认为 ws,做在线是非常好的方案,实时性和性能开销比都不错。
    caola
        22
    caola  
       2021-01-25 21:40:21 +08:00
    @douyacun #5 对外的 websocket 服务不是以端口为单位,而是以 URL 路径为单位的,websocket 可以发布于某个域名的某个路径或多个不同的路径下,与其他路径的 URL 页面或服务互不影响
    cominghome
        23
    cominghome  
       2021-01-26 08:14:17 +08:00
    - -我一直以为这个数字是瞎整的(也不算瞎整,就是不需要那么严谨)
    douyacun
        24
    douyacun  
    OP
       2021-01-26 10:30:27 +08:00
    @ihipop @caola websocket 和 http 是占用同一端口的( 80 | 443 ),linux kernel 3.9 引入的 SO_REUSEPORT 的选项,允许多个进程分享同一地址同一端口的 TCP 连接
    > wss 统计在线人数,代价还是很低的,一般只是端口不够用
    这个说法是错误的,我测试的 端口不够用 是因为客户端会占用本地端口,不是服务端的占用端口~
    lesismal
        25
    lesismal  
       2021-01-27 11:16:01 +08:00
    跨站点劫持楼主已经写了,check origin + 业务层认证

    单设备连接数限制这个不太合理,通常应该按照身份限制比较好:既然有业务层认证,每个连接都有身份,如果不允许同一个身份多个连接、认证后就把之前的踢掉,如果允许,那就自己服务节点配置提高、节点数量增加之类的(如果怕统一身份的连接散到多个服务节点上,可以加个网关层,网关层按身份指定到实际的业务节点、由业务节点进行踢下线处理)。如果实在是想按照设备限制,那策略里使用身份的地方就改用 ip 或者你的算法能够生成的设备 id

    统计人数通常不需要太精确,即使是多个服务节点,每个节点定时(比如 5s )更新自己节点在线数到 redis/sql 都可以、更新多节点在线数量总和就可以了,实时在线本来就是不停跳动的,精确的意义不大。如果实在要求精确,自己再写个服务进行统计、并且同步到所有节点,或者直接用 redis incr 之类的,每秒查询、更新,但是都没法保证百分百精确,实时的本来就是跳动的数据,即使是股票 K 线的蜡烛图也都是按时间段的起值、止值、最高值、最低值进行统计的
    lesismal
        26
    lesismal  
       2021-01-27 11:21:06 +08:00
    BTW,我这有个 ARPC 的 golang 框架提供了 websocket 聊天的简单示例

    https://github.com/lesismal/arpc/tree/master/examples/webchat

    另外 ARPC 支持发布订阅,如果想自己实现个管理服务器进行多个服务节点的在线数统计,管理服务器接收上报人数、然后把多节点的业务服在线总和发布就行了

    想简单处理的话轮询写、读 redis 就好了
    lesismal
        27
    lesismal  
       2021-01-27 11:23:46 +08:00
    节点数不多、redis 的话,每个节点每秒 hset 、hmget 下就行了,没啥压力,而且实时性也足够
    lesismal
        28
    lesismal  
       2021-01-27 11:27:05 +08:00
    另外,怕连接数过多的话,单节点配置好最大在线数,新连接进来的时候判断下、超过了就拒绝掉,这个可以在网关或者业务节点的 upgrader checkorigion 里做,更好点的方式是自己 wrap 下 net.Listener,serve(listener),Accept 的地方直接做
    lesismal
        29
    lesismal  
       2021-01-27 11:33:38 +08:00
    端口数量通常不是问题,文件描述符上限设置个 10w 、100w,其他的几个内核参数设置合理就行,只要你硬件配置足够。不过还真有的站点设置的不合理,golang 中国报错文件打开数量过多我就遇到过好多次。socket 是 4 元组,单 IP 自己过来的最大端口 65535,不代表服务器对所有 IP 加起来只能 65535,而且单 IP 除非故意写 bug 或者攻击、否则也不至于有这么多,而且这些 CDN 、防火墙那里就能挡,还轮不到业务层来处理这个(并且业务曾代码也没有这个能力处理)

    如果是觉得某个设备只要超过两三个连接就算过多,那就看我上一楼说过,限制机制自己订制下就好
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2654 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 07:02 · PVG 15:02 · LAX 23:02 · JFK 02:02
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.