浅谈用户系统设计

1,102 阅读4分钟

表设计

image.png

  • 账户表:保存登陆相关的数据 以及 热数据(可以长久保存在缓存中的数据),头像,性别等字段保存在同一张的目的是为了登录的时候只需要访问一次DB。
  • 用户信息表:辅助查询表,存放地址等冷数据,和账号是1:1的关系

缓存设计

  1. 精简缓存长度,考虑性价比。比如用户密码,保存的是hash以后的值,一般比较长。保存在缓存中,虽然登录的时候可以不用访问DB,但是仅仅只是在登录的时候使用,后续使用不到,浪费存储空间。
  2. 减少表承担的业务职能。比如你在缓存里直接缓存了用户访问列表,同时保存好友状态。目的是为了获取用户访问列表时获取一次缓存就好。但是当好友关系变了,需要更新大量的数据。更好的做法是,好友关系,访问列表分开存储。
  3. 有一些缓存是热缓存,但是条件查询的缓存。比如说 某某大V昨天的新增关注数,这种可以使用脚本定时更新,但是会减少实时性。
  4. 什么样的数据适合做缓存。首先,根据 ID 能够精准匹配的数据实体很适合做缓存;而通过 String、List 或 Set 指令形成的有多条 value 的结构适合做(1:1、1:n、m:n)辅助或关系查询;最后还有一点要注意,虽然 Hash 结构很适合做实体表的属性和状态,但是 Hgetall 指令性能并不好,很容易让缓存卡顿,建议不要这样做。

token相关

目前主流的身份校验方式是token,jwt是其中一种。

  • jwt的核心思想是空间换时间,jwt一般比较长,但是包含了一些数据,有些情况下不需要去请求数据库/rpc服务。如果说解析出来数据后,还要去请求数据库/rpc服务,则没有发挥jwt的优势。
    • 有的做法是返回随机字符串,在redis里保存随机字符串和jwt的对应关系,网关/bff层通过短token获取到jwt后再透传到其它服务。这样就没体现出jwt的优势。
  • jwt的缺点是 payload 可以被解析出来,所以出于安全性考虑,返回的jwt可以加密一次。
  • jwt怎么提前过期,比如用户修改密码了,需要让jwt提前失效。
    • 最简单的办法把jwt保存在缓存中,提前失效的话就删除缓存,但是这样违背了jwt的初衷。
    • 维护一个jwt黑名单列表,缓存时间为jwt的最长有效期。比第一种方法少占用一些空间。
    • 生成jwt的密钥为用户级别,一个用户一个密钥,提前失效的话,修改密钥即可。如果只是针对修改用户密码场景,可以直接将用户密码作为密钥。但是这种方法需要快速获取用户的密钥,也就是要保存在缓存中,还是避免不了要访问一次数据库。
    • 不管哪种方法,都需要数据库支持。
  • jwt里保存了用户用户名,当用户用户名修改了怎么办?
    • 最简单的就是不保存可修改的数据,只保存用户ID这种无法修改的。需要的时候访问DB。
    • 服务端增加标志,保存是否需要更新token
  • 为什么需要refresh_token?
    • access_token 有效期一般比较短,refresh_token 是为了 access_token 过期后还能刷新token。不能用已经过期的token去获取新的token,因为过期的token无法校验用户身份。
    • 如果用户一直使用,可以在token快过期的时候依靠token来刷新token,此时用不到refresh_token。如果用户一段时间没使用,access_token已经过期了,那么需要refres_token。
    • 为什么要access_token的有效时间要短?
      • 为了防止access_token泄漏,而refresh_token是不会泄漏的,因为它保存在使用方的服务器上,而不是浏览器,app上,增加了安全性。
  • 使用UUID,而不是JWT 有啥缺点么?
    • 通过上面描述,很多情况下,使用了jwt,还是需要访问一次缓存获取完整的数据。如果我们使用UUID,通过UUID获取用户信息,这样有啥问题呢?
    • 首先UUID的算法是公开的,攻击者可以不断生成UUID来访问接口,从而实现盗取token的目的。可以对UUID加密后再返回。
      • 那么如果是随机的字符串,还有会这问题么?读者可以自己思考。
    • UUID在极高并发下还是有可能重复,但是概率不高,毕竟只有在登录,刷新的时候才会生成UUID。
    • 需要缓存UUID和用户UID的关系,相比于JWT来说占用了更多的存储空间,但是过期等方案实现更加简单了。

常见问题

  1. 为什么第三方登录后还需要手机号?

为了防止重复创建账号,比如qq登陆一个账号,微信登陆一个账号