2020/06/06

SaaS权限设计总结

saas-access-control-design

转到SaaS部门之后期间断断续续做着权限相关的业务,这篇文章主要回顾下过往的设计以及其原因和利弊。
不过因为是线上业务,会省略掉很多细节以及账号体系和权益相关得部分,只讨论权限相关。

原初的混沌

SaaS和一些内部系统/2C业务的权限最大不同点是他是天然多租户的。
用户之上会有一层组织(Organization)的概念,组织只拥有所有权限的子集(取决于组织购买的服务),并且组织可以自行管理部分权限。
省略了部门,群组等等概念的简化图:

增加了组织概念:

刚接手的这块的时候发现因为历史原因设计得比较粗糙。
整个权限系统只有两个表:权限定义 和 组织权限关系。

默认情况下组织内的所有用户都能获得分配给组织的权限,需要区分对待管理员和用户的权限都是在代码中进行硬编码,手动去除对应权限。

当时的功能:

  • 组织权限分配 - ACL
  • 组织内用户权限分配 - 硬编码

这个模型严重限制了售卖策略和商家的灵活度,在系统中存在大量的硬编码为了某个业务去修改权限的关系。
后续在这一版上勉强引入了组织内角色分配的功能,但因为业务设计过于简单,没法支撑后续的操作,最后决定重构。

业务场景驱动

这中间经历了两次模型的调整和服务的变更。
第一次想做和业务无关之后其他业务可复用的模型,基于RBAC构造了角色,角色"用户"关系,角色权限关系;为了覆盖ACL场景构建了"用户"权限关系;为了多个业务方接入定义了domain,并且权限,"用户"的定义和角色都和domain挂钩。
对外提供的RBAC接口本质上是ACL,"用户"分配角色,角色内权限变更会引起"用户"和权限关系的变化。
至于为什么要这么设计,因为考虑到了一个分配角色后能手工修改用户权限的场景,初步评估这个场景是有必要的。
为了保证"用户"分配了多个角色后,如果存在同样的权限点不会因为之后取消某个角色被全部取消了引入了refCount

此时就存在了一个可以直接使用的ACL(obj_access_relation)和外观看上去是RBAC(但其本质还是ACL)的基础设施。

设置了两个domain,针对组织依旧使用ACL,针对组织内的分配场景使用RBAC。

增加权限定义概念

在这之前要说明的是在设计时,组织中存在了一个管理员的概念,他不是某个角色,而是类似于组织creator的概念,其权限等同于组织的权限并且仅有一个,他的定义是为了简化组织的管理,作为了这个组织的用户层面映射。

权限定义这一概念的引入是为了应对组织内分配关系。
因为现在存在了组织和用户两个维度,分配关系最简单的场景下会有几种:

  1. 权限用于售卖,组织需要分配,用户需要分配;
  2. 权限用于售卖,组织需要分配,用户自动获得;
  3. 权限用于售卖,组织需要分配,用户不能获得;(仅管理员使用)
  4. 权限用于管理用户,组织自动获得,用户需要分配;
  5. 权限用于管理用户,组织自动获得,用户自动获得。(这个场景就不要用权限了)
  6. 权限用于管理用户,组织自动获得,用户不能获得;(仅管理员使用)
    对于权限组织

权限定义内有两个维度: 组织分配关系(默认获得,需要分配),用户分配关系(默认获得组织的,需要分配,无法获得)


经过实践这一套不是特别方便:

  1. 不同domain需要定义不同的权限,但这个场景两个domain下的权限其实是一致的;
  2. 过于业务独立,一些业务场景自定义的东西难以插入其中,比如业务额外定义的权限定义表。

后续为了更好支持SaaS的权限系统把这套基础设施复制到了SaaS权限内,这套基础设施依旧留着给其他业务发光发热。

到这一步的权限系统有如下几个特性:

  1. 组织权限可通过权限定义和分配获得,组织下存在一个管理者其权限等同于组织权限;
  2. 组织内用户权限通过权限定义和角色分配获得,并且约束用户权限不能大于组织(防止组织的某个权限过期后其用户还能继续使用);
  3. 存在系统预设的系统角色,出现条件为组织存在其角色依赖的权限;
  4. 组织可对其拥有的且定义为用户可分配的权限组装自定义角色分配给用户。

针对用户的高级功能。

上述特性中有提到用户权限不能大于组织,这其实仅仅是针对组织域。
如果针对用户层面贩卖高级功能,就不能被这一层限制。
于是又引入了另一个域,其和组织域是正交的,双方不存在逻辑层面上的关系。
也就是 管理员通过VIP获得的权限不会影响到组织权限,用户通过VIP获得的权限不受到组织权限约束。

更多KA定制场景

做SaaS有一点比较困难的是KA需求,作为最重要的一批客户,提供了大量现金流。KA的定制需求不能被忽略。
在迭代中增加了不少定制场景并泛化使用。
比如:

  • 组织层面的权限定义,为了应对客户嫌角色分配麻烦,可以组织内开关某个权限;
  • VIP继承组织权限设计,为了应对客户在大量购买某VIP分配之后不想重复分配角色;
  • 权限自动赋予某些部门下用户

等等

这些问题的共同点就是分配行为的繁琐。
之前引入的权限定义本身就是在组织分配层面解决这个问题,有了一些ABAC的特征。
在这些KA需求的迭代中也增加了更多subject attribute,例如组织ID,VIP类型,以及之后的更多拓展。

基于分配给用户和解耦用户直接分配的ACL和RBAC模型在这些领域都不能很好发挥,因为他们的作用前提是发生了分配关系,为了满足更多的KA场景以及系统本身迭代会引入更多的ABAC元素。

之后的规划

现在线上运行的这一套系统已经和整个商业链路打通,客户的服务购买/续期/增购会有一部分反应到权限系统中,新的功能需要商业化也都会统一接入其中,权限也从最开始的百来个发展到近千个。

但当前系统的不足也很明显,整套体系的架构比较杂乱。

  • 最开始做的伪RBAC那一套最后实践没有对应的场景,而且容易发生不一致的问题,需要在系统层面移除掉(但ACL本身保留);
  • ABAC实现零散且混乱,这一套要需要体系化重写;
  • 系统需要泛化到2C场景,打通2B和2C的商业化链路;
  • 缺失了数据权限控制(object),但这一套应该不会和当前权限这一套做在一起,两者的业务对象相差有点多(一个是组织用户和功能,一个是用户和各类数据)。

Written with StackEdit.

2020/04/23

我不是一个合格的面试官

not-a-good-interviewer

最开始公司扩张需要招人让我做面试官我是拒绝的,过了几年,从开始用系统记录的面试就有百来次,到现在依旧感觉自己不适合做一个面试官,并且不太合格。
这里所谈的都是业务开发的面试,自己也一直在做对应的工作,与其他的偏技术侧的面试可能会有出入。

做面试官很难

从还未成为面试官到现在作为面试官,这个想法一直没变过。
凭短时间的交流就要确认面试者能否胜任工作会存在很大的偏差,无论是判断可以还是不可以。

就算是和自己工作了挺久的同事,感觉对方能力还不错,合作也挺愉快,突然也会因为“无法胜任工作” "被优化"掉了。更何况与一个素不相识,就凭着一纸简历和1小时左右的问答就要确定 。

面试要考察技术能力,业务能力,沟通合作,主观能动性等等等方面,又不能读心,唯一的方式就只有沟通,而不同的面试官又有不同的衡量标准,我觉得不错对方觉得差也遇到过很多次。每个人有不同的侧重点,我觉得业务建模能力不错相谈甚欢,下一个面试官又嫌答不出来几个设计模式基础差,我觉得系统架构稍微有点弱,下一个面试官又赞赏技术视野,等等,屡见不鲜,其实这些都对又都错,只是对这短短时间交谈的一个感觉。

用统一的流程和题库可以解决掉一些面试官标准不匹配的场景,但无法消除,毕竟就算有标准答案每个人心里都有个随着时间不断变化的天平,学生时代同样的题错误的解法不同老师会打出不同的分。

除了让面试者自己表达,面试官也需要引导面试者更好表达自己。这个我连对自己都做不好,更别提对别人了。

面试提问的改变

顺带说说面试问题的一些改变。
下面说到是各个时期的方式,但不是整个面试过程的全部,只是说了那些时期比较象征性的提问和侧重点。

题库. Yes or No

最开始的面试问题,感觉过于草率,在此向最开始1年里我面试的人道个歉。就是题库里的题整合一下然后拿出来做,美其名曰考察技术能力和思维能力,倒不如是比题库的丰富度。
看到的多记忆的多就能获得我不错的评价。
不过题库的好处是一些基础能力的缺失是可以一眼看出来的,坏处是可能会让自己拘泥于答案而不是思路,一些题本身的答案是没有太大意义的,what不重要主要是why和how,当然这不是题库的错,这是面试官的错。
更糟糕的是如果题库的答案是片面的,甚至就是错的,且自己没有识别出来,能在面试的时候被面试者教育还好,但一些面试官可能还会死不承认。之前群友讨论自己面试面试官问TCP连接握手第三次能否携带应用数据,他回答可以结果被面试官怼,还让他想想清楚回答。有意思吗?

技术为主 步步深入

在舍弃了题库地图炮的方式后慢慢转变成这种方式,当时大抵上觉得业务无关轻重,技术才是最重要的吧。
这种面试简单来说就是看到简历上写着这个技术组件,就问很多细节性的问题或者罕见case和特殊优化方式。
找人来造火箭的吗?

这种形式没有持续很久,因为实际上自己太菜,很多东西也没法追问太深,换位思考后也就不再这么做了,取而代之的是日常使用,以及基础概念,比如问kafka就问问哪些业务用了,kafka的大体架构有哪些组件,组件之间怎么协同工作之类。而换成之前可能就是如果设置client参数会导致消息重复这类,说真的自己不去翻翻参数列表和笔记都记不清楚,这类偶尔也还会问一些,但能答出来是bouns,答不出也不影响评价。

之前工作内容为主 考察设计

也算是当前阶段主要的方式了。
现在会更看重之前的项目,在和之前问完工作经历之后更深入其中的细节,并给出一些扩展场景看看能否在原有基础上实现。
要求画出之前项目的整体架构图,考察对于技术栈的熟悉度;要求给出完整的业务链路,各个业务组件的关系,考察对于业务的理解力。
不仅仅是你这个是怎么做的,增加更多的你这个为什么这么做以及如果要满足另一个场景要怎么做。


总体感觉可能现在的方式更能看出面试者的一些能力,前两个时期的侧重着实有点扯淡。

但不得不说对于在技术深度上的考察的确是比之前更弱了,理由之一是自己也就那样,平时做业务也没有够到多深,另外是很多问题是"伪深度"的,本质上是技术的广度(但深度和广度一定程度也相辅相成),知道就是知道,不知道就是不知道,无法推演。
“你知道XXX吗”的问题价值可能不如“你知道XXX,那你为什么要用XXX,那你怎么去XXX”。

当然现在这种做法也有很多改善空间,但大体方向就是这样了。

我不是一个合格的面试官

结合此前的面试和当下,这是我对自己作为一个面试官的评价,我也无法成为一个能让自己觉得合格的面试官。

究其原因,我不觉得自己的技术能力和业务建模等能力有多强(甚至还有点菜),在深度和广度上都有不少问题,并且随着学习这种感觉愈发加强。
其次我也不觉得自己能较好考查面试者,和其他面试者的评价出入比较多,出现不少误判。

最近有几次面试感觉对方不错,但后面的面试官又觉得对方差。这其实受到了不少打击,感觉浪费了面试者不少时间,真的是深感抱歉 :(。

Written with StackEdit.

2019/12/24

goodbye eclipse

goodbye-eclipse

从大一开始自学java开始一路用到现在,终于要告别陪伴了快9年的eclipse。全部替换为IDEA

作为一个只索取不付出的码畜说来也有些惭愧,毕竟使用这么久,一行代码也没贡献,一毛钱都没捐献过,最多就是去网上问问对应的问题,提过几个issue。

因为其他地方都在使用jetbrains家的东西,整个切换是比较平滑的,2019.02这个版本已经相当顺滑,依旧使用了eclipsekeymap,换成dark plus的主题之后,使用起来和之前的eclipse也没差多少,一天写下来没发现什么问题。

除了这次较为成功并且不会再退回来(可预见的未来内吧…)之前,还有过三次的迁移,第一次是大二,当时自学java是为了安卓开发(结果也就大二学了点…做了几个没人用的app),尝试了下IDEA,结果因为小破电脑退了,不过当时的安卓开发在IDEA上似乎只是个插件还不是android studio,后面两次都是公司里,当时还是eclipse主流,现在基本都是IDEA了。8G的运存不能支撑同时打开过多项目(一个项目一个窗口,一个项目多module的使用方式),整体使用会比较卡顿。
但那时候还是很多人迁移过去了,可能信仰的加成比较多,看着同事一个智能提示要卡个几秒才能出来,但还是选择继续用着。

但之前2,3年电脑的配置就已经足够支撑了,运存也到了16G,硬盘也都是SSD,迁过去已经没有物理环境上的限制了。

但心里肯定还是对eclipse有点感情的,也因为习惯了各种操作快捷键,界面上倒是不怎么考虑,毕竟一个全屏写代码也看不到toolbar和menu之类的东西。

最终决定迁移的,还是因为文本渲染的问题。
https://bugs.eclipse.org/bugs/show_bug.cgi?id=536562,首先是之前就有的问题,有一定记录下,注释如果以非英文数字开头,缩进的显示会很奇怪,几个空格会被“压”在一起,但这种情况下,绝大多数连字符是正常的 ->之类的会正常显示为,还有些情况是缩进正常了,但只有少数连字符能工作了,绝大部分的箭头都阵亡了。
缩进不正常不打紧不过是注释,我更在意连字符显示,而且打开IDE又是那种十天半个月不会关的,一次打开连字符不正常多试几次就可以了。
然后上周看到出了新版,也周期性升级到最新,结果发现缩进的问题可能已经fix了(issue也VERIFIED FIXED了),然后这个状态下连字符爆炸。
在92楼已经有人发现这个问题:

It’s good, but still doesn’t support font ligatures because fMergeNeutralItems is false. so some font such as ‘Cascadia Code’ or ‘Fire Code’ has no completely effective. see also https://bugs.eclipse.org/bugs/show_bug.cgi?id=398656
The following code has been tested, seems ugly but maybe worth considering:

    segmentsText.getChars(0, length, chars, 0);
    // enable font ligatures and avoid ligatures between ascii and non-ascii chars
    scriptControl.fMergeNeutralItems = true;
    for (int i = length - 2; i > 0; i--) {
        char i0 = chars[i];
        char i1 = chars[i + 1];
        if (i0 > 255 && i1 < '~' && i1 >= ' ') {
            chars[i + 1] = 'A';
        } else if (i1 > 255 && i0 < '~' && i0 >= ' ') {
            chars[i] = 'A';
            i--;
        }
    }
    OS.ScriptItemize(chars, length, MAX_ITEM, scriptControl, scriptState, pItems, pcItems);

这就是所谓的修好了一个bug引入了个新bug。
去爆栈问也被人说箭头之类的连字符从来没有支持,附上截图后也没了回复,顺带close vote被投了一票。
也失去了耐心,刚升好级却看到了如此。恰好一些插件还没导入重新配置,索性主力直接切到IDEA
没想到箭头符号成为了压倒骆驼的最后一根稻草/(ㄒoㄒ)/~~

不过不得不说,eclipse本身没有网传的那么糟糕,很多卡顿问题并不是来源其本身还是插件,比如装了spotbugs又开启了修改后就检查,这样保存文件的时候就会感觉卡卡的,又比如一些优化极差的插件疯狂在workspace下的log里刷报错。
其实很多时候都是有点妖魔化的,一些较为基础的功能都可能被认为缺失,但其实都有,Ctrl + 3找一找就可以了(对应Shift + Shift)。很多诸如quick action可以在problemview里批量部分报错和警告的功能也不被人所知。
但细节上的确有很大差距,已经做了差不多两年的code mining依旧是个半成品,以及上面说的这个bug愣是改了一年半(不过受影响的主要是诸如中文之类的字符,本身重视程度也很低吧);EJC有自己的优势但是对annotation processor支持比较稀烂,等等。

也许之后的升级会再回来看看。
但现在就暂时告别了~
(我也是闲得蛋疼换个IDE也要写篇文… …

Written with StackEdit.

2019/12/14

如何处理业务上的较多表对接

handle-lots-of-data-tables
最近这2个月来的主要业务是和数据组打交道,将他们提供的各个维度的数据表(实质上是基于底层的各种事件已经计算并聚合过一层的view,这边统一用表表述)在接口层面进行封装为前端提供接口。简单写文回顾加总结一下。

场景描述

和一般的重查询的业务基本相同,但有个比较明显的问题是表的数目比较可观。
现在的业务已经涉及到了几百张表,主要是接入的领域,以及领域下的各种指标累加起来比较多。
举一个业务无关例子,比如用户访问论坛的行为,需要展示用户的发帖数,编辑数,回复数,查看数,点赞数这几个指标,功能上分为了单个用户的指标汇总,单个用户指标趋势和某个指标的具体实体排行榜(比如查看数是展示具体的帖子数),时间维度分为日,周,月,季度和年。
数据组会根据业务需要的指标给出对应的表,但他们给的表不会直接业务相关,所以可能发帖,编辑在一张表里,回复在另一张表里,查看和点赞在第三张表里,这边获取一个单用户指标的汇总就需要查3类表,其次因为有去重的操作,数据组会选择将不同的聚合时间段分成不同的表,于是根据时间的维度在这个方向上又会有5类表(当然时间段内可累计的话就直接日期表累加就好了,不可累计指的是比如在一周内7天都看了同一个帖子,那按每天累加的话是7,但是按一周内看的帖子应该是1),指标可能有不同的筛选维度,不同的维度也是不同类的表,比如可能需要按帖子主题,帖子分类,帖子用户所在地区这3个维度分类,如果维度间有交叉那所需要的表就更多,主题x分类,主题x所在地,分类x所在地,主题x分类x所在地等等,假设没有交叉,那就是3类维度表,那总的表的数量就是3 * 5 * 3 = 45,这只是单个用户的汇总数据一个功能。
对于上面为什么不同筛选维度要有不同的表,而不是全部放在一张表里,这其实是数据组底层实现的选择。一来是数据量过大底层会聚合过多的物理表,对之后的扩展和维护是个很大的挑战;另一方面则是数据查询的意义,你只设定了一个筛选维度,那要返回的是选定的筛选维度+所有其他维度的值的汇总数据呢,还是选定维度+所有其他维度的值的行呢(也就是你到底需要的是一个group by distinct sum 还是一个返回所有维度值的行),当然这可以使用类似特殊值的方式来区分;最后一个问题就是之后增加新的筛选维度怎么办,只在一张表里的话新增字段数据会翻倍增长。
如下图,当前只有两个筛选维度,各自都只有1这个值,我们用all这个特殊值来表示筛选项不指定的情况(也就是group by distinct sum的值)。
filter1 filter2 value
all all 1
1 all 1
all 1 1
1 1 1
那增加一个新的filter3会变成如何。
filter1 filter2 filter3 value
all all all 1
1 all all 1
all 1 all 1
1 1 all 1
首先之前的数据要先填充filter3是all的情况,接着要写入filter3的所有值,原来表的大小是(m+1)x(n+1),现在就会变成(m+1)x(n+1)x(z+1),m,n,z是筛选维度的所有值。

解决方式

初期

表的数量已经无法减少,在第一次接入的时候使用了之前的方式,结果变成了实打实的重复劳作机器,经历了漫长的半个月完成了一个领域的接口,但接下来还有更多的领域,以及会有一些额外的短时离线任务和长时离线任务,用这种方式不可控。
但在最开始还是大致摸通了一些规律,比如时间维度拆分的同类表有同样的表名模式,以及内部只有时间字段不同,相同指标系列的表的指标名都是一致的,筛选维度的同类指标表的指标也是一致的只有筛选项不同。

模式化

结合初期发现的规律,基础的解决方式有了一定的雏形。
不需要写每一个表查询,只需要抽取合理的模式。
  • 日期+指标类型决定表名
  • 选择的筛选维度可以确定路由到某张表
  • 同样指标类型的表可以共享字段名等元数据
因为项目安排比较紧的关系,在编码上慢慢往这个方面靠拢,但是还没有抽取出通用的方法。

现在

省略更多中间的探索,直接说下结论。
现在要接入新的已经有比较明确的编码流程。
  1. 元数据抽取和整理(这部可工具化):
    将数据组给的表进行分类和整理,在代码中定义表名,表名模式(比如user_posts_d,user_posts_w,user_posts_m,定义为user_posts_{0}),定义同一类指标的字段名,定义表名和字段名的映射关系。
  2. 写对应router,(筛选项,日期类型)-> 表名,这边要注意这个router是个通用的router,那种只选定一个筛选项,要返回其他所有筛选项值的功能还是单独写的(比如选中一个一级分类,返回不是一级分类的汇总值,而是底下所有二级分类的值)。
  3. 主流程。传入参数 -> 构建filter list -> 选择表名 -> 根据表名-字段映射返回需要filters。
上面举的例子中的45张表就在这一套流程中完成。
时间上有了很大的收缩,之前可能需要2个星期才能接入完毕的一套表,现在大致可以缩短到2天(毕竟还有其他衍生功能要做)。

总结

如上面括号里的,这一套方式其实有很多可以自动化的点,比如根据数据组给到的数据定义自动全面地(不全面的已经有了脚本)生成代码里的元数据定义,甚至router和最后的代码,然后进行微调,在理论上完全可行,至于实践层面可能就需要耗费比较多的精力,而且可能ROI不高,现在已经在用一些脚本自动生成些代码了,完整的工具的必要性和能提高的工作效率。
Written with StackEdit.

2019/12/05

又搬了一个新家

这两年来搬了第三次了,从最初的cnblogsgithub pages,再到这里。

从在线的平台到免费的自己维护的静态页面平台又回到了在线平台。

能随时打开浏览器,编辑内容不断保存,最后发布还是要比生成静态页面来得便利。
特别是笔记本不在身边的时候,而且就算在手边,一想到要打开笔记本,自己编写好markdown,构建发布就觉得有点消磨意志(虽然这个步骤很简单。 后果就是这两个月来想写点什么,但又什么都没写,给自己常用的几台电脑都装上hexo也不失为一个办法,但想着在单位的工作机上放着自己的博客还是不怎么合适。

也看了几个平台,还是选择了blogger,简单免费但也有不少扩展点,不用操心其他的事情,至于没有markdown,用stackedit就解决了。
之后有什么点子也可以直接写草稿,最后在发布,也不用把文章之类放在私有仓库在不同地方clonepush了。

应该可以稍微多写点把…应该
enter image description here