极客时间已完结课程限时免费阅读

01|结构梳理:大并发下,你的数据库表可能成为性能隐患

01|结构梳理:大并发下,你的数据库表可能成为性能隐患-极客时间

01|结构梳理:大并发下,你的数据库表可能成为性能隐患

讲述:徐长龙

时长16:23大小14.96M

你好,我是徐长龙,欢迎进入第一章节的学习。
这一章我们主要讲解怎么对读多写少的系统进行高并发优化,我会拿用户中心作为例子,带你来看改造的几个要点。
用户中心是一个典型的读多写少系统,可以说我们大部分的系统都属于这种类型,而这类系统通过缓存就能获得很好的性能提升。并且在流量增大后,用户中心通常是系统改造中第一个要优化的模块,因为它常常和多个系统重度耦合,所以梳理这个模块对整个系统后续的高并发改造非常重要。
今天这节课,我会带你对读多写少的用户中心做数据整理优化,这会让数据更容易缓存。数据梳理是一个很重要的技巧,任何老系统在做高并发改造时都建议先做一次表的梳理。
因为老系统在使用数据库的时候存在很多问题,比如实体表字段过多、表查询维度和用途多样、表之间关系混乱且存在 m:n 情况……这些问题会让缓存改造十分困难,严重拖慢改造进度。
如果我们从数据结构出发,先对一些场景进行改造,然后再去做缓存,会让之后的改造变得简单很多。所以先梳理数据库结构,再对系统进行高并发改造是很有帮助的
这节课我会给你讲几个具体的规律和思路,帮助你快速判断当前的表结构是否适用于高并发场景,方便后续的系统升级和改造。

精简数据会有更好的性能

为了方便讨论,我先对用户中心做一些简单介绍,如图:
用户中心功能
用户中心的主要功能是维护用户信息、用户权限和登录状态,它保存的数据大部分都属于读多写少的数据。用户中心常见的优化方式主要是将用户中心和业务彻底拆开,不再与业务耦合,并适当增加缓存来提高系统性能。
我举一个简单的例子:当时整表内有接近 2000 万的账号信息,我对表的功能和字段进行了业务解耦和精简,让用户中心的账户表里只会保留用户登陆所需的账号、密码:
CREATE TABLE `account` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`account` char(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`password` char(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`salt` char(16) COLLATE utf8mb4_unicode_ci NOT NULL,
`status` tinyint(3) NOT NULL DEFAULT '0',
`update_time` int(10) NOT NULL,
`create_time` int(10) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `login_account` (`account`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
我们知道数据库是系统的核心,如果它缓慢,那么我们所有的业务都会受它影响,我们的服务很少能超过核心数据库的性能上限。而我们减少账号表字段的核心在于,长度小的数据在吞吐、查询、传输上都会很快,也会更好管理和缓存。
精简后的表拥有更少的字段,对应的业务用途也会比较单纯。其业务主要功能就是检测用户登陆账号密码是否正确,除此之外平时不会有其他访问,也不会被用于其他范围查询上。可想而知这种表的性能一定极好,虽然存储两千万账号,但是整体表现很不错。
不过你要注意,精简数据量虽然能换来更好的响应速度,但不提倡过度设计。因为表字段如果缺少冗余会导致业务实现更为繁琐,比如账户表如果把昵称和头像删减掉,我们每次登录就需要多读取一次数据库,并且需要一直关注账户表的缓存同步更新;但如果我们在账户表中保留用户昵称和头像,在登陆验证后直接就可以继续其他业务逻辑了,无需再查询一次数据库。
所以你看,有些查询往往会因为精简一两个字段就多查一次数据库,并且还要考虑缓存同步问题,实在是得不偿失,因此我们要在“更多的字段”和“更少的职能”之间找到平衡。

数据的归类及深入整理

除了通过精简表的职能来提高表的性能和维护性外,我们还可以针对不同类型的表做不同方向的缓存优化,如下图用户中心表例子:
一个用户中心的表关系
数据主要有四种:实体对象主表、辅助查询表、实体关系和历史数据,不同类型的数据所对应的缓存策略是不同的,如果我们将一些职能拆分不清楚的数据硬放在缓存中,使用的时候就会碰到很多烧脑的问题。
我之前就碰到过这样的错误做法——将用户来访记录这种持续增长的操作历史放到缓存里,这个记录的用途是统计有多少好友来访、有多少陌生人来访,但它同时保存着和用户是否是好友的标志。这也就意味着,一旦用户关系发生变化,这些历史数据就需要同步更新,否则里面的好友关系就“过时”了。
来访记录
将历史记录和需要实时更新的好友状态混在一起,显然不合理。如果我们做归类梳理的话,应该拆分成三个职能表,分别进行管理:
历史记录表,不做缓存,仅展示最近几条,极端情况临时缓存;
好友关系(缓存关系,用于统计有几个好友);
来访统计数字(临时缓存)。
明白了数据归类处理的重要性后,我们接下来分别看看如何对上述四种类型的数据做缓存优化。

数据实体表

先看一下用户账号表,这个表是一个实体表,实体表一般会作为主表 ,它的一行数据代表一个实体,每个实体都拥有一个独立且唯一的 ID 作为标识。其中,“实体”代表一个抽象的事物,具体的字段表示的是当前实体实时的状态属性。
这个 ID 对于高并发环境下的缓存很重要,用户登录后就需要用自己账户的 ID 直接查找到对应的订单、昵称头像和好友列表信息。如果我们的业务都是通过这样的方式查找,性能肯定很好,并且很适合做长期缓存。
但是业务除了按 ID 查找外,还有一些需要通过组合条件查询的,比如:
在 7 月 4 日下单购买耳机的订单有哪些?
天津的用户里有多少新注册的用户?有多少老用户?
昨天是否有用户名前缀是 rick 账户注册?
这种根据条件查询统计的数据是不太容易做缓存的,因为高并发服务缓存的数据通常是能够快速通过 Hash 直接匹配的数据,而这种带条件查询统计的数据很容易出现不一致、数据量不确定导致的性能不稳定等问题,并且如果涉及的数据出现变化,我们很难通过数据确定同步更新哪些缓存。
因此,这类数据只适合存在关系数据库或提前预置计算好结果放在缓存中直接使用,做定期更新。
除了组合条件查询不好缓存外,像 count() 、sum() 等对数据进行实时计算也有更新不及时的问题,同样只能定期缓存汇总结果,不能频繁查询。所以,我们应该在后续的开发过程中尽量避免使用数据库做计算。
回到刚才的话题,我们继续讨论常见的数据实体表的设计。其实这类表是针对业务的主要查询需求而设计的,如果我们没有按照这个用途来查询表的时候,性能往往会很差。
比如前面那个用于账户登录的表,当我们拿它查询用户昵称中是否有“极客”两个字的时候,需要做很多额外的工作,需要对“用户昵称”这个字段增加索引,同时这种 like 查询会扫描全表数据进行计算。
如果这种查询的频率比较高,就会严重影响其他用户的登陆,而且新增的昵称索引还会额外降低当前表插入数据的性能,这也是为什么我们的后台系统往往会单独分出一个从库,做特殊索引。
一般来说,高并发用缓存来优化读取的性能时,缓存保存的基本都是实体数据。那常见的方法是先通过“key 前缀 + 实体 ID”获取数据(比如 user_info_9527),然后通过一些缓存中的关联关系再获取指定数据,比如我们通过 ID 就可以直接获取用户好友关系 key,并且拿到用户的好友 ID 列表。通过类似的方式,我们可以在 Redis 中实现用户常见的关联查询操作。
总体来说,实体数据是我们业务的主要承载体,当我们找到实体主体的时候,就可以根据这个主体在缓存中查到所有和它有关联的数据,来服务用户。现在我们来稍微总结一下,我们整理实体表的核心思路主要有以下几点:
精简数据总长度;
减少表承担的业务职能;
减少统计计算查询;
实体数据更适合放在缓存当中;
尽量让实体能够通过 ID 或关系方式查找;
减少实时条件筛选方式的对外服务。
下面我们继续来看另外三种表结构,你会发现它们不太适合放在缓存中,因为维护它们的一致性很麻烦。

实体辅助表

为了精简数据且方便管理,我们经常会根据不同用途对主表拆分,常见的方式是做纵向表拆分
纵向表拆分的目的一般有两个,一个是把使用频率不高的数据摘出来。常见主表字段很多,经过拆分,可以精简它的职能,而辅助表的主键通常会保持和主表一致或通过记录 ID 进行关联,它们之间的常见关系为 1:1。
而放到辅助表的数据,一般是主要业务查询中不会使用的数据,这些数据只有在极个别的场景下才会取出使用,比如用户账号表为主体用于做用户登陆使用,而辅助信息表保存家庭住址、省份、微信、邮编等平时不会展示的信息。
辅助表的另一个用途是辅助查询,当原有业务数据结构不能满足其他维度的实体查询时,可以通过辅助表来实现
比如有一个表是以“教师”为主体设计的,每次业务都会根据“当前教师 ID+ 条件”来查询学生及班级数据,但从学生的角度使用系统时,需要高频率以“学生和班级”为基础查询教师数据时,就只能先查出 “学生 ID”或“班级 ID”,然后才能查找出老师 ID”,这样不仅不方便,而且还很低效,这时候就可以把学生和班级的数据拆分出来,额外做一个辅助表包含所有详细信息,方便这种查询。
另外,我还要提醒一下,因为拆分的辅助表会和主体出现 1:n 甚至是 m:n 的数据关系,所以我们要定期地对数据整理核对,通过这个方式保证我们冗余数据的同步和完整。
不过,非 1:1 数据关系的辅助表维护起来并不容易,因为它容易出现数据不一致或延迟的情况,甚至在有些场景下,还需要刷新所有相关关系的缓存,既耗时又耗力。如果这些数据的核对通过脚本去定期执行,通过核对数据来找出数据差异,会更简单一些。
此外,在很多情况下我们为了提高查询效率,会把同一个数据冗余在多个表内,有数据更新时,我们需要同步更新冗余表和缓存的数据。
这里补充一点,行业里也会用一些开源搜索引擎,辅助我们做类似的关系业务查询,比如用 ElasticSearch 做商品检索、用 OpenSearch 做文章检索等。这种可横向扩容的服务能大大降低数据库查询压力,但唯一缺点就是很难实现数据的强一致性,需要人工检测、核对两个系统的数据。

实体关系表

接下来我们再谈谈实体之间的关系。
在关系类型数据中,我强烈建议额外用一个关系表来记录实体间 m:n 的关联关系,这样两个实体就不用因为相互依赖关系,导致难以维护。
在对 1:n 或 m:n 关系的数据做缓存时,我们建议提前预估好可能参与的数据量,防止过大导致缓存缓慢。同时,通常保存这个关系在缓存中会把主体的 ID 作为 key,在 value 内保存多个关联的 ID 来记录这两个数据的关联关系。而对于读取特别频繁的的业务缓存,才会考虑把数据先按关系组织好,然后整体缓存起来,来方便查询和使用。
需要注意的是,这种关联数据很容易出现多级依赖,会导致我们整理起来十分麻烦。当相关表或条件更新的时候,我们需要及时同步这些数据在缓存中的变化。所以,这种多级依赖关系很难在并发高的系统中维护,很多时候我们会降低一致性要求来满足业务的高并发情况。
总的来说,只有通过 ID 进行关联的数据的缓存是最容易管理的,其他的都需要特殊维护,我会在下节课给你介绍怎么维护缓存的更新和一致性,这里就不展开说了。
现在我们简单总结一下,到底什么样的数据适合做缓存。一般来说,根据 ID 能够精准匹配的数据实体很适合做缓存;而通过 String、List 或 Set 指令形成的有多条 value 的结构适合做(1:1、1:n、m:n)辅助或关系查询;最后还有一点要注意,虽然 Hash 结构很适合做实体表的属性和状态,但是 Hgetall 指令性能并不好,很容易让缓存卡顿,建议不要这样做。

动作历史表

介绍到这里,我们已经完成了大部分的整理,同时对于哪些数据可以做缓存,你也有了较深理解。为了加深你的印象,我再介绍一些反例。
一般来说,动作历史数据表记录的是数据实体的动作或状态变化过程,比如用户登陆日志、用户积分消费获取记录等。这类数据会随着时间不断增长,它们一般用于记录、展示最近信息,不建议用在业务的实时统计计算上。
你可能对我的这个建议存有疑虑,我再给你举个简单的例子。如果我们要从一个有 2000 万条记录的积分领取记录表中,检测某个用户领取的 ID 为 15 的商品个数:
CREATE TABLE `user_score_history` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`uid` int(10) NOT NULL DEFAULT '',
`action` varchar(32) NOT NULL,
`action_id` char(16) NOT NULL,
`status` tinyint(3) NOT NULL DEFAULT '0'
`extra` TEXT NOT NULL DEFAULT '',
`update_time` int(10) NOT NULL DEFAULT '0',
`create_time` int(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY uid(`uid`,`action`),
) ENGINE=InnoDB AUTO_INCREMENT=1
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci;
select uid, count(*) as action_count, product_id
from user_score_history
where uid = 9527 and action = "fetch_gift"
and action_id = 15 and status = 1
group by uid,action_id
不难看出,这个表数据量很大,记录了大量的实体动作操作历史,并且字段和索引不适合做这种查询。当我们要计算某个用户领取的 ID 为 15 的商品个数,只能先通过 UID 索引过滤数据,缩小范围。但是,这样筛选出的数据仍旧会很大。并且随着时间的推移,这个表的数据会不断增长,它的查询效率会逐渐降低。
所以,对于这种基于大量的数据统计后才能得到的结论数据,我不建议对外提供实时统计计算服务,因为这种查询会严重拖慢我们的数据库,影响服务稳定。即使使用缓存临时保存统计结果,这也属于临时方案,建议用其他的表去做类似的事情,比如实时查询领取记录表,效果会更好。

总结

在项目初期,数据表的职能设计往往都会比较简单,但随着时间的推移和业务的发展变化,表经过多次修改后,其使用方向和职能都会发生较大的变化,导致我们的系统越来越复杂。
所以,当流量超过数据库的承受能力需要做缓存改造时,我们建议先根据当前的业务逻辑对数据表进行职能归类,它能够帮你快速识别出,表中哪些字段和功能不适合在特定类型的表内使用,这会让数据在缓存中有更好的性价比。
一般来说,数据可分为四类:实体表、实体辅助表、关系表和历史表,而判断是否适合缓存的核心思路主要是以下几点:
能够通过 ID 快速匹配的实体,以及通过关系快速查询的数据,适合放在长期缓存当中;
通过组合条件筛选统计的数据,也可以放到临时缓存,但是更新有延迟;
数据增长量大或者跟设计初衷不一样的表数据,这种不适合、也不建议去做做缓存。

思考题

请你思考一下,用户邀请其他用户注册的记录,属于历史记录还是关系记录?
欢迎你在留言区与我交流讨论,我们下节课见!
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 16

提建议

上一篇
开篇词|高并发系统,技术实力的试金石
下一篇
02|缓存一致:读多写少时,如何解决数据更新缓存不同步?
unpreview
 写留言

精选留言(29)

  • peter
    置顶
    2022-10-25 来自北京
    请教老师几个问题: Q1:MySQL一个表最大记录是2000万吗? 多个地方看到一种说法:MySQL的表,记录数不要超过两千万,根据是什么?经验值吗?还是和MySQL的底层结构有关? Q2:亿级用户是分表吗? 比如微信,十亿用户,要分成多个表吗?分的话,一般根据什么分?
    展开

    作者回复: 你好,peter,感谢你的留言~由于问题比较多,我只能简略回答一下,后续我们的课程内容也会有相关的内容 Q1:MySQL innoDB的2000w性能下降的说法,不一定准确,这个数字和单条数据大小以及数据量有直接关系,我们的数据是行存储并且和聚簇索引在一起,并且查询索引是B+Tree,如果这个服务用途不是马上要求返回结果,也是能用的,只不过响应变得缓慢了,但是大部分我们对外服务对响应速度和并发是有要求的。缓慢原因是我们的MySQL的索引是整棵树完整结构,并且我们的实际数据都在最深处保存,如果树深度过深(比如超过3层),查询时和回表时相应的我们查询对比的次数和io会变多,这会直接导致了性能会有下降,能够承担的服务请求量也相应下降,我们用MySQL不推荐这么多的原因核心在于,我们还是想让接口请求能够快速回复查询请求 Q2:我想这个重点在于,业务怎么用这些数据,这决定了我们怎么分表,我们分表的目的主要是为了提高响应速度,我的建议是,如果是存储可以按取值范围拆分,这样可以简单做二分法查找确定数据服务在哪里,如果是用来对用户查询,那么在他的上层加上多层的缓存分片,类似LevelDB的层级,被查询的时候可以回源,当然这个是适合读多写少的场景,如果是其他场景,需要另外设计对应的方式,这些方式在后面都有介绍,敬请期待 你的问题很有趣,期待你的下次留言!

    共 6 条评论
    5
  • 业余草
    2022-10-25 来自北京
    https://static001.geekbang.org/horde/8c/8c13453d81da3149b58334b4625d2788.jpeg 这里放一张我在部落里发的图。用户邀请其他用户注册的记录,属于历史记录还是关系记录,主要取决于业务结构。我推荐使用历史记录

    作者回复: 你好,业余草,感谢你的分享,当我们需要查询这个用户的邀请人时,还需要查询这个表,那么和我们对表的职能拆分有一定冲突,那么如何改进这个实现呢

    4
  • 移横为固
    2022-11-03 来自北京
    思考题:一开始觉得注册邀请表应该作为历史表. 思考了下作为关系表也是可以的 在满足下面的注册邀请前提下: 1.邀请人用类似二维码分享方式,注册人主动扫码注册。(不使用点对点邀请,被邀请人可能不接受) 2.只能注册成功一次 这样每一条邀请记录都是一个用户的注册记录:可以定义如下字段 (邀请者,注册人,注册时间,邀请方式) 表的字段结构都非常简单,记录的总量最多就是账号量,并不会随时间不断膨胀。因此可以胜任关系表的查询需求。
    展开

    作者回复: 你好,移横为固,很高兴收到你的思考,你的回答和我当时想法很一致,我们做项目会碰到很多类似的情况,需要我们去预防超出预期的操作。 简单的说就是:我们怎么约束用的人,以及我们怎么用这个数据。

    共 4 条评论
    2
  • Daniel
    2022-10-24 来自北京
    我认为是“历史记录”, 因为在统计一个用户一共邀请了多少个人的时候,是需要在总体的邀请人数中去筛选这部分人,而总共邀请的人数会是一个动态不断增长的数字。 老师,我想请问一下,在什么业务场景下(是不是历史记录的表信息就可以用非关系型数据库来处理),可以考虑把关系型数据库的数据转移到类似于mongoDB这类Nosql类型的数据库,而不是用缓存来处理呢,二者选取的关键因素有哪些?
    展开

    作者回复: mongoDB是一个很有趣的数据库,很多设计开创了行业先河,但有几个老版本因为全局锁问题导致使用的时候,需要慎重,因为这个锁在并发高的时候加服务器也不能提升性能,后续的版本我没有太关注。 建议在选择的时候能够压测验证下现状来确认是否给不确定用户流量的系统使用,即使使用也建议对他加一层缓存。 至于历史记录是否选择mongodb,主要看我们的业务场景,如果是给大量用户使用不推荐,如果是我们存起来用来做数据分析,并且不会有大量流量的话可以使用。核心在于是否能够高可用,它的性能是否符合我们场景需要,是否满足我们的业务需要。

    2
  • 一步
    2022-10-30 来自北京
    邀请注册记录:如果 历史表个关系表(关系表中保存 邀请人ID, 邀请历史记录 ID)同时保存呢? 一般邀请统计的业务,会要求看到邀请的具体统计信息,比如:每天的邀请人数,邀请总人数等,这里邀请关系可以放到缓存中; 邀请历史表保存 具体的邀请历史记录,这样就可以通过邀请关系缓存拿到记录id 进而获取到邀请历史记录详情信息

    作者回复: 你好,一步,很高兴收到你的心得,邀请关系放缓存是个好办法,建议表也拆一下,然后查找历史详情这样感觉会更方便一些~

    1
  • 人无远虑,必有近忧
    2022-10-25 来自北京
    感谢老师,获益匪浅值得学习!

    作者回复: 你好,感谢你的支持!学习过程中有任何问题,欢迎提出,多多交流

    1
  • 拾掇拾掇
    2022-10-24 来自北京
    邀请很像转介绍业务,所以应该是历史记录

    作者回复: 你好,拾掇拾掇,感谢你的留言,在使用中,如果我们想查询这个人的邀请人,这个表还是会被业务查询,那么如何设计改进呢?

    1
  • dk.wu
    2022-12-07 来自北京
    个人倾向:历史记录 有个场景是我邀请了,但是邀请的人并没有注册,那么用户表也就不存在,不属于真实的关系。 本身通过用户拉人头的方式,就是记录有效的邀请数,进而给予对应奖励。

    作者回复: 你好,这里未注册确实是一个需要思考的地方

    共 2 条评论
  • Geek_e7d396
    2022-12-02 来自北京
    老师,请教一下,上面说 "长度小的数据在吞吐、查询、传输上都会很快" 这是为什么呢? 只要不是SELECT *,而只取所需要的的字段,对传输的影响差别不大 索引只是保存主键和索引的字段,对查询没有影响 感觉只是影响缓存池内能存储的数据大小

    作者回复: 你好,其实有影响只不过现代使用SSD已经不明显了,只有在数据量大的时候会有区别,可以看看第三章内容,行存储和列存储心中会有解答

  • 刘章
    2022-11-30 来自北京
    老师你好: 我们现在用的是一主多从的数据库,设置了读写分离,写是 主库, 读是从库。 有时间会出现一些莫名问题,就是同步延迟问题,明明是修改完成了,但是读取不到就会是程序出现问题,这个有什么好的办法吗

    作者回复: 你好,刘章,可以用MySQL proxy一类的中间件自动确认数据情况切换主从,如果是云服务,像阿里云的polardb可以在代理设置强一致。如果都没有可以业务上优先更新缓存,优先读缓存数据

    共 2 条评论
  • 请叫我和尚
    2022-11-26 来自北京
    我们要解决一些问题: 问题1. 用户 A 邀请了哪些用户 问题2. 用户 B 是被哪个用户邀请的 根据具体的业务场景来分析: 方案一、如果邀请关系是 1:1 那就是做历史记录表 1.2 点都可以解决 方案二、如果邀请关系是 N:1,但是最终只能一个人邀请成功 那就是关系记录表 但是最终会在用户的其他表里记录最终被邀请成功的 user_id 1.2 点也都可以解决 但是现在出现了一个问题,如果考虑到分表,然后分表键的设计: 如果是以 user_id 分表,那现有方案一不能解决问题 2,要解决问题 2,同理需要在用户的其他表记录最终被邀请成功的 user_id
    展开

    作者回复: 你好,这个思路更详细,点赞~

  • ls
    2022-11-18 来自北京
    请教老师一个问题: 对于一些上下级关系的数据该怎么建模和保存?比如代理商下面有下级代理,下级代理下面还有下下级,层级有10多级,这种怎么去保存?是一个树形层次结构

    作者回复: 你好,ls,虽然他们是树形依赖关系,但是他们的数据结构是相同的,可以做在一个表内,类似parent_id方式去记录上级关联关系,当我们需要按这个树形去处理时完全可以在界面让前端处理好给服务端具体树形层级,同时可以记录一些冗余的数据,如当前所在层级,同时注意如果是代理商,很有可能存在循环依赖情况,如a供货商同时是b供货商的上游,b供货商是c供货商的上游,但是c供货商还是会从a供货商拿一些配件~所以这里要做好预防规划

  • edward
    2022-11-17 来自北京
    我认为属于历史记录,因为邀请动作是发生过就不会再改变了。

    作者回复: 你好,edward,很高兴收到你的思考,但是有个问题,如果是多个活动,会出现多次邀请时这个结论就会发生变化

  • 库嚓嚓
    2022-11-09 来自北京
    请教老师一个和用户系统相关的问题: 我们有几个spring mvc的web项目,这几个项目都涉及简单的用户和权限体系,几个服务都使用了spring security框架做权限控制。 现在想抽取一个用户服务(非web服务)出来进行业务和代码的复用,将用户表和权限表都统一迁移到用户库中,这一步没啥问题。但在处理spring security相关代码,发现不能直接迁移到用户服务中,而几个web项目中的spring security代码都是类似的模板代码。关于这种情况,请问老师有什么好的解决思路?
    展开

    作者回复: 你好,库嚓嚓,这个组件我没有研究过,我的大部分业务相关的服务都是自制,我理解需要看看官方架构相关文档了

  • Geek_lucas
    2022-11-09 来自北京
    你思考一下,用户邀请其他用户注册的记录,属于历史记录还是关系记录? 从关系来说,这是关系记录,然而从业务上来说,这个其实很少会去查询的,应用场景也不是很多的,所以应该归属到‘历史记录’,毕竟用的少。你们觉得呢?

    作者回复: 你好,lucas,没错~取决于怎么用,但是怎么保证别人按我们设计初衷使用

  • Geek_96685a
    2022-11-07 来自北京
    看业务场景吧: 1,如果需要查看邀请人的邀请记录,邀请成功或者邀请未成功的记录,此时需要设计为邀请记录表吧 2,如果不需要查看邀请人记录,只关注成功邀请的话,而且一个人只能被邀请一次的话,可以设计为关系表

    作者回复: 你好,很高兴收到你的思考,没错~同时还要注意,这个数据不会被别人滥用

  • Mr.Tree
    2022-11-07 来自北京
    这个不同的业务需求划分归属就会不同吧,比如邀请记录不会发生变化且数据量庞大,对于历史记录访问较少,这些都属于冷数据,它应该作为历史记录,如果属于访问较平凡的数据,考虑一下分区,并且储存较少的字段,统计邀请人id和被邀请人id等少量信息,作为关系表存在

    作者回复: 你好,很高兴收到你的思考,这个思考没错,核心在于我们怎么用它,但是我们的数据放在哪里,别人怎么用是不好把控的,如何防止别人滥用这些数据,是我们需要思考的

  • 张申傲
    2022-10-29 来自北京
    对数据库表进行分类的思想很有启发,学习了~

    作者回复: 你好,张申傲,感谢你的留言~

  • frag007
    2022-10-26 来自北京
    是关系还是历史记录,还是要看具体业务决定的。如果有需求是查询推荐人是xx的话,就是关系了

    作者回复: 你好,frag007,很高兴收到你的留言,首先,你的理解没错。 如果这个表里可以有多人邀请一个人,历史的职能就会十分明显~,这样的话给他放缓存会很不方便,所以,还是业务形态决定了他的职能,这也意味着这个表的数据决定了这个表的职能,而不是单单的结构就能看出来

  • 徐曙辉
    2022-10-26 来自北京
    1. 为什么时间字段是int格式不是timestamp或datetime? 2. 历史记录,当我们在查询用户A和邀请人关系的时候其实也可以看作关系记录,邀请表有两个字段用户user_id和邀请人用户ID invite_user_id,如果需要查询用户的邀请人要么通过invite_user_id 联用户表,要么增加冗余字段invite_user_XXX等,当然可以选择把一部分用户信息缓存到redis,拿invite_user_id去缓存查

    作者回复: 你好,徐曙辉,很高兴收到你的回复 1.这个和个人习惯有关,我个人喜欢用unix time,因为这个不带时区,不同服务器环境时区不同容易乱 2.我们排斥历史表是因为他数据量太多,查询缓慢,所以虽然我们用它当作关系表也可以,但是不太好用它做缓存,最后一句拆一部分进入redis是个很好的办法,同时要考虑下第一次查询很缓慢,然后才能放入缓存缓解压力