websocket 实现了统计在线人数,那 websocket 该如何防爆,防跨站? - V2EX
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 5766 次点击
    这是一个创建于 1722 天前的主题,其中的信息可能已经有所发展或是发生改变。

    站点在线人数统计实现思路: 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  
    PRO
       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     Solana     968 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 34ms UTC 22:39 PVG 06:39 LAX 15:39 JFK 18:39
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86