V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Loyalsoldier
V2EX  ›  程序员

针对用户 IP,返回随机 URL,并需要存储 URL 到数据库,这个随机 URL 数据可以每天清空一遍,用什么方案性能最好?

  •  
  •   Loyalsoldier · 2017-10-11 19:47:02 +08:00 · 3406 次点击
    这是一个创建于 2605 天前的主题,其中的信息可能已经有所发展或是发生改变。

    RT,例如假设,每天有 50W IP 的访问,用户进入网站,会随机返回一个 302 的响应,然后跳转到一个随机的 URL。用户第二次进来,会查询数据库中是否存在该 IP,存在,则跳转到第一次访问的随机 URL。

    也就是说,要做一个 IP 和 随机 URL 的映射关系表,这个表数据量很大,假设是 50W,这些数据可以在一天后清除的。怎么做才能尽可能实现高性能,不影响网站用户体验?

    目前想到 Redis,但是好像 Redis 每秒只能查询 20W 次?那要遍历 50W 次的话,好像挺久的……

    非科班设计师出身,轻喷……

    第 1 条附言  ·  2017-10-12 21:04:20 +08:00

    我原本以为 Redis 的 Hash 是遍历完成查询的,基础不行啊……

    最终采用的方案是 Redis。就用 Redis 里简单的 HSET 和 HGET 就好了。过期时间的话,设置了一个定时任务每次自动清理数据。

    27 条回复    2017-10-12 21:30:03 +08:00
    qiayue
        1
    qiayue  
       2017-10-11 19:50:28 +08:00
    “ Redis 每秒只能查询 20W 次”
    这个结论哪里来的?
    187j3x1
        2
    187j3x1  
       2017-10-11 19:53:46 +08:00
    hash table 原理嘛? 50 万 50 亿都是差不多速度的,吃内存而已
    Loyalsoldier
        3
    Loyalsoldier  
    OP
       2017-10-11 20:01:48 +08:00
    @qiayue #1

    http://www.runoob.com/redis/redis-intro.html

    我还说高了…………之前看过一个文档写着,但是一时间找不到了
    yankebupt
        4
    yankebupt  
       2017-10-11 20:13:28 +08:00
    为什么要遍历?由于 ip 地址特性 int32(ipv4)可以加速不用遍历的。
    一提到这个就想起最近的动态路由屏蔽表...估计规则数也比你那个不少...那个是真的大访问量...我一直在想那个是怎么跑起来的...有可能是特殊硬件吧?...
    我看到第一个回复查询 20W 次,还以为 20W+访问量.....
    yankebupt
        5
    yankebupt  
       2017-10-11 20:16:33 +08:00
    总有股维和感,我总觉得楼主的意思是不是反过来,是不是想根据 url 记录如果没有随机返回一个 ip 并绑定。
    Loyalsoldier
        6
    Loyalsoldier  
    OP
       2017-10-11 20:20:49 +08:00
    @yankebupt #5

    不是,就是根据 IP,返回一个随机 URL 并存进数据库。当天的后续访问,就查询数据库,取出该随机 URL,跳转到该 URL
    yankebupt
        7
    yankebupt  
       2017-10-11 20:34:40 +08:00
    我觉得楼主如果之后回帖:
    我发现一个好方法,把库分成两部分,一部分存国内 ip 和 url,一部分存国外 ip 和 url...根据访问热度和管理要求实现不同查询性能......实现 90%以上的满意度...
    的话
    我会后悔在这个主题回帖的.....

    所以楼主,稍微详细一点说明你的实际使用情况?
    还有为什么就选定 redis 了
    yankebupt
        8
    yankebupt  
       2017-10-11 20:45:16 +08:00
    不要误解我。我是说大多数情况下技术人员会根据热度,分不同的使用用户,高频用户给一个很小的高速查询库,响应极其迅速,查不到再查之外的逐步扩大。但是涉及你这种情况如果这样做实际上是违背网络公平性的,会加大非高频用户的延迟,加之我 redis 基本没碰过,所以完全不能给出有效建议,请见谅。
    xmcp
        9
    xmcp  
       2017-10-11 20:46:13 +08:00   ❤️ 1
    二分查找是 O(logN) 级别的,50W 肯定没问题呀。楼主要是挨个遍历强行卡成 O(N) 肯定就 gg 了呀……

    另外不知道你的需求是什么,如果你只需要“清空所有映射”,不需要“删除特定映射”的话,不妨直接弄个 hash 的算法,URL=hash(salt+IP),想要清空的话换个 salt 就行了,查询 O(1),岂不美哉?
    Loyalsoldier
        10
    Loyalsoldier  
    OP
       2017-10-11 20:53:24 +08:00
    @yankebupt #7

    用户从 a.com (入口域名) 访问网站,后端拿到该用户的 IP 地址(假设是 1.1.1.1),随机从可选的 URL 中返回一个给该用户,假设返回 b.com (目标域名),这时候 1.1.1.1 就对应 b.com 。在当天内,用户 1.1.1.1 再次从 a.com 访问进行访问,为了保证用户在当天内看到同样的页面内容,这时候需要给用户返回跟第一次访问时一样的 URL。查询数据库发现,1.1.1.1 对应 b.com ,这时候给用户 1.1.1.1 返回 b.com

    但是当一天内访问的用户量足够大的时候,查询“目标域名”的操作就会很慢了吧,所以问,用什么技术方案来实现这个需求,用户能最快访问到目标 URL
    Loyalsoldier
        11
    Loyalsoldier  
    OP
       2017-10-11 20:54:54 +08:00
    @xmcp #9

    用户从 a.com (入口域名) 访问网站,后端拿到该用户的 IP 地址(假设是 1.1.1.1),随机从可选的 URL 中返回一个给该用户,假设返回 b.com (目标域名),这时候 1.1.1.1 就对应 b.com 。在当天内,用户 1.1.1.1 再次从 a.com 访问进行访问,为了保证用户在当天内看到同样的页面内容,这时候需要给用户返回跟第一次访问时一样的 URL。查询数据库发现,1.1.1.1 对应 b.com ,这时候给用户 1.1.1.1 返回 b.com

    但是当一天内访问的用户量足够大的时候,查询“目标域名”的操作就会很慢了吧,所以问,用什么技术方案来实现这个需求,用户能最快访问到目标 URL
    lianyue
        12
    lianyue  
       2017-10-11 20:56:27 +08:00
    这需要 考虑这么多干嘛 动态网页 基本每次打开都会有查询 正常 以内

    其实普通数据库 什么的
    id, ip date
    就好了 多一个字段超过 24 小时自动过期 根本不需要什么删除

    或者每天一个新表 为什么非要操作 删除旧的数据


    表名比如
    access_2017_10_11
    access_2017_10_12
    access_2017_10_13

    结构
    id, ip


    实现方式很多
    binux
        13
    binux  
       2017-10-11 20:58:13 +08:00
    随机选一个域名是 O(1) 的,查询一个 ip 对应的域名也是 O(1) 的,不管用户量有多大,就算 13 亿它也是一样的速度。
    syhsyh9696
        14
    syhsyh9696  
       2017-10-11 20:59:32 +08:00
    为何不是根据当前的时间和 ip 地址生成当天的 temp url ?
    xmcp
        15
    xmcp  
       2017-10-11 21:03:25 +08:00   ❤️ 1
    @Loyalsoldier 这用什么数据库呀?直接用 IP 地址做种子生成随机数呀。伪(?)代码如下:

    SALT='foo' # 改这个数来重置所有用户访问到的网址
    WEBSITES=['b.com','c.com','d.com']

    def get_url(ip):
    ___ r=random.Random()
    ___ r.seed(SALT+str(ip))
    ___ return r.choice(WEBSITES)

    如果真要上数据库的话 redis 也不是不行,人家 redis 也有 hash 数据结构: http://www.runoob.com/redis/redis-hashes.html,O(logN) 肯定能满足正常网站的速度要求的。
    yankebupt
        16
    yankebupt  
       2017-10-11 21:12:29 +08:00
    @Loyalsoldier 但是 ip 可以 hash 的...
    不懂 hash 会加速的道理也没关系,这么解释一下,假设你为 0.0.x.x-255.255.x.x 的 ip 建 65536 张表,则每张表最多也就是 65536 个记录(实际每个记录都会很少会更快,因为需要访问一次才有记录)。只查对应的表只需遍历很少的记录....

    不过我愈发觉得楼主可能是在钓鱼...专门钓我们这种不懂 memcached/LRU 的...
    yankebupt
        17
    yankebupt  
       2017-10-11 21:17:23 +08:00
    @xmcp 也好,但我看到楼主说了每天数据可以清空,可能意味着楼主想在 SALT 里加入当前日期或者有自己的生成算法,咱们继续看看楼主怎么发展吧....
    bydmm
        18
    bydmm  
       2017-10-11 22:03:24 +08:00   ❤️ 1
    楼主你逗我把,

    ip 到一段字符串的映射关系根本不用遍历整个 redis 空间啊。 他们之间是 hash 映射的关系。

    你用 ip 作为 key,url 作为 value,再多 ip 都是一次好吗。。
    dbw9580
        19
    dbw9580  
       2017-10-11 22:13:12 +08:00 via Android
    for ip in seen_ips: if ip != remote_ip, continue; else return ip2urltable[ip]

    op 的思考方式
    0ZXYDDu796nVCFxq
        20
    0ZXYDDu796nVCFxq  
       2017-10-11 22:30:46 +08:00 via iPhone   ❤️ 1
    用 IP 做 key 查询不是 O(n)的,是 O(1)
    50 万数据小得可怜,估计几百 M 内存搞定
    nazor
        21
    nazor  
       2017-10-11 22:34:31 +08:00
    每秒查询 20W 是指每秒遍历数据 20 次,不是说一秒只能遍历 20W 的一次……
    hand515
        22
    hand515  
       2017-10-11 22:45:37 +08:00   ❤️ 1
    这种情况 redis 单机就能处理得过来,才 50w
    set ip domain NX EX 24*60*60

    get ip
    基本只要这两个命令就能解决你的问题
    johnnie502
        23
    johnnie502  
       2017-10-12 06:58:27 +08:00
    这不是一个存放在客户端的 24 小时过期 cookie 吗?除非有特殊的要求必须放在服务器端,不然放在客户端不就完事了
    LeoSocks
        24
    LeoSocks  
       2017-10-12 08:35:29 +08:00 via iPhone
    那文章写的 11 万次每秒是并发吧!可以支持并发 11 万个用户同时读。而不是 11 万条记录
    blaxmirror
        25
    blaxmirror  
       2017-10-12 09:28:37 +08:00   ❤️ 2
    直接用 IP+一个每天变化的 seed 做映射不就好了吗?为啥要存储下来呢?
    你应该不需要通过 URL 反向查询 IP 的吧?
    (并不了解技术细节,仅供参考)
    Loyalsoldier
        26
    Loyalsoldier  
    OP
       2017-10-12 21:07:45 +08:00
    @xmcp #9
    @binux #13
    @yankebupt #16
    @bydmm #18
    @dbw9580 #19
    @gstqc #20
    @hand515 #22
    @blaxmirror #25

    我原本以为 Redis 的 Hash 是遍历完成查询的,基础不行啊……

    最终采用的方案是 Redis。就用 Redis 里简单的 HSET 和 HGET 就好了。过期时间的话,设置了一个定时任务每次自动清理数据。
    hand515
        27
    hand515  
       2017-10-12 21:30:03 +08:00
    @Loyalsoldier #26 别用 hashtable,直接用 set 和 get 命令,配合 expire 设置自动过期,还剩了定时任务
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1253 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 23:16 · PVG 07:16 · LAX 15:16 · JFK 18:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.