浅谈微服务架构中的鉴权体系

在微服务架构中,有一个核心的问题是处理好“集权”(中心化)和“放权”(去中心化)的关系。虽然微服务的主旋律是把数据和业务拆成小而独立的模块,但我们仍然需要一个强力的中央安保体系来确保“数据分散,权限集中”。这一篇就谈谈微服务架构中的鉴权体系。

身份认证

身份认证(Authentication)的目的是证明“你是你(所号称的那个人)”。

要证明这一点,你必须掌握一个只有你自己和认证机构才知道的机密信息。在现实中,这个信息可能是 DNA、指纹、虹膜这样的生物识别特征,但由于这种特征跟人身直接绑定且又不可修改,一旦泄露,可能被持续冒用,造成不可挽回的严重后果,所以现实中较少采用这些生物识别特征作为识别之用。

如果不采用机密信息作为判断标准,就需要一个持续的、不易伪造的“证明材料”。在中国,这个证明材料就是户口或身份证。中国对公民信息的登记相对严格,所以会在小孩出生的时候要求把身份信息登记到户口之中,形成身份证明,跟随一生。在需要证明“你是你”的时候,拿出身份证就行了。

(生物特征不可废弃,所以我们必须把它包一层,形成证明材料和对应的 Persona)

与生物识别特征不同的是,身份证如果丢失,从理论上说,应该可以挂失并且让其失效,然后办理一张新的身份证。不过,设计我国身份证的机构和供应商也许没有考虑到这个问题,或者考虑到现实情况太复杂,导致身份证无法挂失,丢失的身份证仍然具备证明效力,但这个是后话了。

为了避免身份证被冒用,在对身份认证要求比较严格的场合(比如银行),会附加一些别的检查,比如对比照片等等。

那么,现在我们来对身份认证进行规划。

  • 身份认证机构可以颁发两个东西给用户,作为身份认证的输入:机密信息或证明材料。
  • 身份认证机构可以通过对比用户提交和机构保存的机密信息来判断用户身份。
  • 身份认证机构可以通过检查和对比用户提交的证明材料来判断用户身份。
  • 如果需要,身份认证机构可能会附加别的验证来增加认证可信度。
  • 用户可以变更机密信息,避免冒用。
  • 用户可以挂失证明材料,使证明材料失效,避免冒用。

身份认证中的机密信息在 Web 环境中通常以用户名和密码的形式存在。由于 HTTP 协议没有“状态”的概念,所以对于 Web 服务器来说,每次请求都是全新的体验,都必须验明请求者的正身。要做到这一点,客户端可以在每次请求的时候都附上用户名和密码(或者别的凭据),表明身份。

可是,每次都发送用户名和密码增加了泄露风险,所以在第一次验明正身(登录)之后,服务器可以发给调用者一个“令牌”(Token)。这样,后端的后续身份认证,无外乎就是把令牌换成“身份”(Identity)。这个令牌实际上就是前面说的证明材料。

我们应该尽量让令牌不容易仿造,但是技术上无法做到完全杜绝。所以,在敏感操作的时候可能会附加一些别的验证,比如再次输入密码或者用短信验证码做二次校验,这也就是前面所说的附加验证。

权限验证

和身份认证相比,权限验证(Authorization)要复杂一些。

身份认证的输入,要么是用户名和密码(或别的身份凭据),要么是令牌,只需要通过一个检查,就能输出身份信息。而权限的验证要检查的是“某用户能不能做某事情”,所以,至少需要有两个输入:“用户身份”和“想要执行的动作”。除了这两个输入之外,还需要有一个具体的“判断规则”,验证者才能根据规则,输出“同意”或者“拒绝”。

在现实中,这个判断规则有很多种可能。

  • 在等级森严的军队里面,所有的动作和文档都有明确的“查阅级别”,而每个人也有自己的“查阅级别”。只有用户的级别高于动作的级别,才能执行这个动作。
  • 在分工明确的工厂里面,每个人都只负责自己的工作,那么,所有的动作和资源都按照不同的工种来进行分配。各工种只能执行属于自己负责范围的动作,获取属于自己负责范围的资源。
  • 在架构明确的公司里面,每个人都属于公司行政架构中的一个节点,可以执行属于这个节点的动作,并且访问这个节点及其下属节点的信息。
  • 在专家主导的医院里面,所有人都围绕专家的需要服务,而专家则为病人服务(执业)。根据专家的需要,同时保护敏感信息,我们可能会设置更加复杂的判断规则,比如根据时间段、服务流程阶段等来判断,或者提供一个特定的委托授权的流程用于临时开放权限。

不管怎么变,只要有了身份、动作和规则,我们就能做判断。当然,如果规则要求我们核实部分数据,我们还需要这部分的数据作输入。不过,由谁来执行这个判断比较合适,值得我们探讨一下。

举一个生活中的例子。

有这么一家公司,在 A 市有个办公室,办公室有个戴经理。戴经理有一天兴之所至,想起来要查一下员工老王的工资。他来到了 HR 部门,找到了 HR 主管,想要调老王的工资出来看看。

HR 看了看公司规定,经理只能看自己所辖办公室的员工工资,然后又看了看戴经理,是负责成都的,再看看老王,是成都员工,然后,就把老王的工资调出来给了戴经理。戴经理看了,然后说,再给我看看老陈的工资啊。然后 HR 调出档案一看,老陈是北京办公室的,就拒绝了。

又有一天,员工老王也兴之所至,想要查一下戴经理的工资。他也来到 HR 部门,找到 HR 主管,问戴经理的工资。HR 一看,你这不是经理啊,怎么能查别人工资呢,就直接拒绝了。

如果看这个例子,我们就会发现,这个规则的检查是 HR 做的。实际上,绝大部分非 IT 的业务流程中,权限的检查都由信息的保管方来执行。

我们当然也可以按照这个来建模,但是稍等,再深入分析一下。

  • 首先,“谁能看谁的工资”这个规则,是不是 HR 部门来决定的呢?不是。公司的规章制度决定了“谁能看谁的工资”,规章制度由公司管理者制定。
  • 然后,当公司制度需要调整的时候,是不是由 HR 部门来调整呢?不是。 还是由管理者来制定,然后由各个部门来执行。各个部门实际上是收到了制度调整的结果,而不是自己去调整制度。
  • 最后,“谁能看谁的工资”这个规则,是不是 HR 的专业范围?不是。只有“调出工资档案”这个动作是 HR 的专业范围,至于“谁能看”,其实跟 HR 的专业知识没有直接关系。

要理解最后这一点,我们可以看两个场景。

  • 某 HR 换了一家新公司。现在这个新的公司很有意思,允许所有人看所有人的工资,层级也不同,规章完全不同,但 HR 仍然可以按照自己的专业来工作不受影响。不只是 HR,对其他部门的人来说也是如此。规章制度的变化,对它们的职责没有实质的影响。
  • 某 HR 换了一家新公司。这家公司专门搞高精尖的研究,对于员工的信息和商业机密控制极其严格。只要有人来调取数据,都必须经过专门的审核人员审核放行。这对 HR 的职责也没有实质影响,只要能通过审核,照办即可。审核人员也不知道 HR 具体的工作是什么,只知道规则要求检查什么,就去检查什么。

总结就是以下几点。

  • 专业知识(领域逻辑、业务规则)和权限是相对独立的东西。
  • 要运用专业知识,不需要知道权限。
  • 要检查权限,不需要用到专业知识。

既然如此,为什么在现实中还是由专业人士来兼职检查权限呢?这也许是因为对于很多公司来说,绝大部分的数据都没有那么敏感,所以为了降低管理成本,绝大部分的数据访问都没有那么严格地用专职人员去检查,而是由专业人士代劳。

了解了这些之后,我们就可以开始规划了。

  • 权力机构会制定一套权限规则,并且可能调整这套规则。
  • 这套规则可能会用到一些外部的输入,比如员工所在的办公室。
  • 有了这套规则和查验数据的权力,任何人都可以判断一个动作是否合规。

这样做的好处,就是业务变得非常纯粹,而权限相关的东西完全挪出业务层面,即便业务或者权限需要频繁变化,问题也不大。

说到这里,也顺便抛一个待验证的设想:不同公司的业务逻辑总是高度雷同,差别最大(妨碍复用)的其实是公司的管理体系。我们把组织结构和与之相关的安全权限单独拎出来,也许可以更好地促进业务逻辑的复用。

鉴权服务

为了提升服务的效率,我们一般会希望尽早地做完身份认证和权限验证。如果用户执行了越权操作,那我们应该及早中止访问并返回错误提示。

前面提到,权限验证的输入之一是用户身份,所以身份认证和权限验证通常是前后脚来做。二者组合,形成鉴权服务(Auth Service)。鉴权服务负责维护令牌身份映射以及权限规则,它的输入是“令牌”和“想执行的动作”,输出是“身份”和“是否允许执行”。

几个例子

现在,我们用这样一个场景来实验一下整个鉴权流程。

设有这么一个订单管理系统,其中有一个订单查询功能。其权限要求如下。

  • 买家只能看到自己下的订单
  • 卖家只能看到下给自己的订单
  • 卖家管辖多个小二,小二可以分组给不同的权限,有的只能看分配给自己的订单,有的可以查看分配到自己组的订单
  • 运营商可以看到所有订单

要对这个权限体系进行建模,我们必须认识到,这些操作,虽然查看的都是订单,但是因为是不同的业务上下文,表现到 API 呈现上也会有不同。

  • 买家看自己的订单:/CustomerViewOrders
  • 卖家看自己的订单:/MerchantViewOrders
  • 运营商看任意订单:/AdminViewOrders

然后,我们可以制订如下规则。

  • 所有这些 API 都要求用户处于已登录状态
  • 对于 /CustomerViewOrders ,访问者必须有 customer 身份
  • 对于 /MerchantViewOrders ,访问者必须有 merchant 身份
  • 对于 /AdminViewOrders,要求当前用户必须有 admin 身份

这样,鉴权服务就可以根据身份、动作和规则三者来判断访问权限了。

(图片来自:http://t.cn/RHRujGG)

至于卖家给小二的权限分配,根据不同需要,我们可以选择两个方案。第一是让卖家自己去处理这个细粒度的权限,形成自己的一套小的权限体系,这也意味着小二访问的可能是因卖家中转而暴露出来的新 API。第二是把这个细粒度的权限也建模到原来的权限体系里面,加入如下新的 API 和判断规则。

  • 小二查看订单: /ClerkViewOrders,检查:
    • 用户必须是 clerk 身份
    • 用户在组织结构上必须属于某个 merchant
    • 如果用户类别是 1,那么他可以查看所有分配到自己组内任意小二的订单
    • 如果用户类别是 2,那么他可以查看分配给自己的订单

我们再来看另一个场景,查看员工信息。API 的和规则的设计如下。

  • 所有 API 要求用户处于登录状态
  • 员工查看自己的信息:/EmployeeViewOwnProfile
    • 所有员工均可访问
  • 员工查看其他员工的信息:/EmployeeViewProfile
    • 所有员工均可访问
  • 经理查看员工信息:/ManagerViewProfile
    • 当前用户必须为 manager 角色
    • 请求中的员工必须属于该 manager 负责的 location

不同的 API 返回的数据可能有差别,比如看自己的信息可以看全,看别人的只能看名字、照片和联系方式,经理则可以看所有人的完整信息,这由应用逻辑决定。

(图片来自:http://t.cn/RHRu1e3)

再来看一个医院的。医院有一点不同的是,病人和病历实际上需要在多个部门之间周转,而不同的角色处在不同部门的时候,其职能和权限会有变化。比如, 有时候实习医生会守急诊室,住院医生不在的时候护士也需要代理执行医嘱,职工可能会轮岗到不同部门,等等。

基于这样一些假想场景,我们可能会有如下一些 API 和权限。

  • 挂号处,要求用户必须有 clerk 身份
    • 建档:/RegistrationsCreateMedicalRecord
    • 挂号:/RegistrationsCreateVisit
    • 查看病历(用于确认病人已建档):/RegistrationViewMedicalRecord
  • 门诊部,要求用户必须有 doctor 身份
    • 诊断:/OutPatientCreateDiagnosis
    • 开药:/OutPatientCreatePrescription
    • 查看病历:/OutPatientViewMedicalRecord
  • 急诊室,要求用户必须有 doctor 身份
    • 查看病历:/EmergencyViewMedicalRecord
  • 住院部,要求用户必须有 doctor 或者 nurse 身份
    • 入院:/InPatientAdmitPatient
      • 仅 nurse 可以执行入院
    • 日常检查记录:/InPatientCreateRoutineRecord
      • doctor 只能给自己分管的病人创建检查记录
      • nurse 只能给自己负责区域的病人创建检查记录
    • 创建医嘱:/InPatientCreateOrder
      • doctor 只能给自己分管的病人创建医嘱
      • nurse 不能创建医嘱
    • 出院:/InPatientDismissPatient
      • 仅 nurse 可以执行出院
    • 查看病历:/InPatientViewMedicalRecord
      • doctor 只能查看自己分管病人的病历
  • 手术室,要求用户必须有 doctor-surgeon 身份
    • 准备材料:/OpRoomPrepareMaterial
    • 记录结果:/OpRoomCreateOpRecord
  • 检查部,要求用户必须有 technician 身份
    • 录入结果:/LabsCreateExaminationRecord
    • 查看病历:/LabsViewMedicalRecord
  • 药房,要求用户必须有 pharmacist 身份
    • 看处方:/PharmacyViewPrescription
    • 放药:/PharmacyDeliverMedicine

上述 API 能访问到的数据和权限主要根据部门来进行划分,方便轮岗。比如,医生在门诊的时候,可以查看完整的病人病历,但轮岗到挂号处的时候,虽然也查看病历,但就只能查看最基本的个人信息了,用于给病人补办卡片之类。

功能和数据权限

从上面几个例子看来,我们通常可以把权限的验证分成两个步骤:先确定职能,然后确定职能作用范围。

比如,先确定你能看订单,然后确定你能看哪些订单;先确定你能看工资,然后确定你能看谁的工资。再比如,某国法律规定,当一个案件发生在某地,警察来调查,但只有该辖区的警察有调查权,跨区域的案件必须交给联邦警察。如此等等。

既然这两步看上去分得很清楚,那么我们不妨给它们分别取名。用户能不能执行某个动作,使用某个功能,是功能权限,而能不能在某个数据上执行该功能(访问某部分数据),是数据权限。

促成这种拆分方式的原因可能有下面几种。

  • 现实中,很多组织采取了这种“职能 + 组织节点”的形式来确定权限,所以这样的拆分实际上为建模提供了方便。
  • 由于功能权限通常会直接对应应用的 API 列表,所以权限验证可以及早失败,而无需把数据取出来做对比,提升了鉴权的效率。
  • 方便我们把所有的功能 API 提取出来形成一个列表或者表格,可以更好地查看和管理权限。

此外,这种形式的权限管理还可以让业务人员在不写代码的情况下对功能权限进行重新分配。如果涉及数据权限,则必然会有某种形式的判断逻辑,写代码也就必不可少了。

话说回来,尽管这种拆分很常见,我们仍应该认识到这只是人为的一种拆分。二者都是权限验证的一部分,都是为了回答“该用户能不能做某件事”这个问题,本质没变。

需要注意的是,在制定权限规则时,制订者需要参考业务规则,但是反之则不然。业务规则可以在完全不了解权限验证规则的情况下执行。甚至,从理论上说,所有的业务单元都应该可以在完全没有权限验证的情况下“正常裸奔”,即假设所有人可以做所有事情,但业务应该被正常执行,业务规则应该被正常遵守。用语言学的词汇来说,就是在没有权限验证的情况下,业务数据中也许会有语义问题(semantic problem),但是不会有句法错误(syntax error)。

鉴权体系回顾

我们来回顾一下这篇文章中提到的鉴权体系。

  • 身份认证。确认“你是你”,获取你的身份信息。
  • 权限验证。确认“你能做某件事”。

二者合称为“鉴权”。身份认证输入令牌,输出身份。权限验证输入身份、动作(包括动作范围),输出“同意”或“拒绝”。我们希望身份和权限在一个体系内高度一致,所以,鉴权是一个半中心化的行为,权限规则在一个体系(比如组织、应用)内是中心化管理的。

权限的形成需要对业务知识的了解,但规则抽象出来之后,要使用它就不需要业务知识了。权限验证的独立,意味着我们把“权限规则”和“业务规则”拆成了两个部分。前者拥抱变化,而后者追求稳定;前者在意的是业务的意义,后者在意的是业务的逻辑。

为了适应现有组织形态和更清晰地展示权限信息,在给权限建模的时候我们常常会把它拆分成功能和数据权限两种。我们应该认识到二者都是权限验证的一部分,都是为了回答同一个问题:这个用户能不能做某事。

从整个分析脉络我们可以看到,这个鉴权体系是通用的。在设计任意一个系统的过程中,我们都应该注意尽量把安全相关的判断和业务规则拆开对待,方便集中管理权限,把业务规则提纯。

对于微服务架构来说,鉴权是一个重要的节点,它和应用场景密切结合,是安保的最后一道关口。在对权限进行建模的时候,我们应该尤其谨慎。希望这篇文章能给大家一些启示。


更多精彩洞见,请关注微信公众号:思特沃克

Share

浅谈微服务基建的逻辑

这篇文章主要目的是面向初接触微服务的朋友简单介绍微服务基础建设所需要的各个模块以及缘由。

起点

首先,我们得有一个“服务”。根据定义,我们可以把每个服务实例都视作一个黑盒。这个盒子有着明确的输入点和输出点,并且(理想情况下)仅通过这些输入和输出点和外界产生关联。每个服务实例会拥有专属的网络地址、独立的计算资源,并且独立部署。客户端通过访问服务实例的地址来调用服务 API。不同服务也可以相互调用。

配置管理器:统一管理配置

在微服务体系中,每个服务都独立部署和运行,团队可以根据需要自行选择增加和减少计算资源。一个服务可能会跑多个实例,每个服务实例都会需要做配置。为了方便统一调整配置,我们可以把配置中心化,每个服务实例都去找配置管理器(Configuration Manager)拿配置。当配置更新的时候,我们也可以让服务实例再去拿新的配置。

服务名册:解耦主机地址

这也引出了一个问题:网络地址(比如 IP)很容易因为扩容、维护而变动,调用者难以实时获知可用的地址。

鉴于此,我们可以把网络地址抽象成不容易变动的概念,比如给每个服务一个固定的名字。互联网使用 DNS 来解决这个问题,对应到微服务基建里面就是服务名册(Service Registry)。

每个服务实例在运行期间,都会以心跳的形式向服务名册发送注册信息,包括服务的 ID 、访问地址以及健康状况。这样,需要访问服务的时候,客户端就可以先问服务名册拿可用的实例地址,然后再访问实例来调用服务。除了更好地定位实例地址,服务名册还可以在某些实例下线、维护或升级的时候把其临时从名册中去掉,让服务不断线。

服务之间的调用也是如此,先找名册拿网络地址,再进行调用。

API 网关:入口和路由

找名册要地址,然后调用服务 API,这些是每个客户端都会去做的琐事,我们完全可以把这些事情抽象、集中,把服务的 API 整合到一个大的中心点,然后把要地址和调用服务 API 这样的细节封装起来,所有客户端都只跟这个中心点对话,不再直接访问单个服务。

从结构上看,这个中心点把整个架构划分成了内外两部分,内部是所有的服务,客户端则在外部,中心点站在中间。它作为内外的唯一通道,被顺理成章地命名作“API 网关”(API Gateway),有时候也被称做“边缘服务”(Edge Service)。

API 网关作为唯一出入口,又占据了最前沿的有利位置,所以有时还会承载别的公共功能,比如我们马上会提到的鉴权。

鉴权服务:身份和权限问题

顺着这个架构继续开发,我们会遇到新的问题:不方便的鉴权。

鉴权(Auth)包括了两个部分:身份认证(Authentication)和权限验证(Authorization)。身份认证关心的是“你是谁”,权限验证关心的是“你能不能做某件事”。

身份和权限都是高度中心化的概念。

对于一个系统来说,用户的身份必须是统一的。不能说这个用户在做这个事情的时候是张三,做那个事情的时候是李四。此外,用户的认证状态也应该是统一的。不能说用户访问这个服务的时候是已登录认证,访问另一个服务时又是未登录状态。所以,只能有一个身份认证方。

权限稍微复杂一点。和身份不同,权限通常分成两种类别:功能权限和数据权限。这样的划分对应了现实世界中常见的权限模式:你的角色决定了你的职能,而职能范围通常由附加条件来限制。比如,你是一个法官,对案件有裁决权,但是你是 A 区的法官,只能判 A 区的案子。再比如,某个快餐门店的经理有权看员工的详细资料,但是只能看自己门店的员工资料。

两种权限都由全局的规则来确定,而不掌握在执行部门。比如,谁来判案,取决于法律,而不取决于法院。谁能查看谁的资料,也不由资料保管部门决定,而由规章制度决定。

在现实的情况中,组织可能会有专门的审核部门来验证权限,但对那些不是特别敏感的权限,企业会让各个部门自行验证。不过不管谁来执行验证,都必须拿着同一份规章制度,不能各说各话。这份制度必须由中心机构来统一制定、维护。也就是说,权限的管理也应该中心化。

明确鉴权中心化之后,我们就可以开发一个公用的鉴权服务,执行身份认证和权限验证。下一个问题是:谁来发起鉴权?

所有服务的调用都要求调用者明确自己的身份,所以自然身份认证越靠前越好。作为出入口的 API 网关自然是发起身份认证的不二之选。权限验证则稍微复杂,完全值得另起一文详述。此处我们暂时假定权限验证也由 API 网关来发起。

消息中介:异步和通知

开发继续进行,一切风平浪静,技术上暂时没有什么问题。不过,业务上有一个问题需要解决。

比如,我们做一个在线商城,要求在订单成功创建的一刻,仓库就要启动备货和发货的流程。问题是,订单和仓储是两个服务,不同团队在负责,而且从关注点来说,订单服务并不关心仓储相关的问题,所以订单服务不可能在创建订单的时候去主动通知仓储服务。仓储服务只能定时轮询订单服务,看看有没有新的订单。这不仅麻烦,而且实时性不够。

仔细想想,我们会发现这种需求很常见,信息的产生者并不知道(也不关心)谁会对信息产生兴趣。比如我们可能会有一个监控服务需要实时展示产品销量,有一个 BI 服务需要获取客户购买产品的信息来做分析,等等。既然这是一个常见需求,我们不妨把它模式化,形成一个机制:信息产生者把通知发出来,收到通知的人再确定是否需要采取行动。

这就意味着我们需要再引入一个中心化的公共服务:消息中介(Message Broker)。当某个事件发生的时候(比如用户激活成功、订单创建成功),服务可以朝消息队列发一条消息。而其他服务可以订阅这些消息,并针对这些消息做出反应。

比如,仓储服务可以订阅订单创建成功的消息。这样,订单成功创建后,订单服务将这个消息发到消息中介,消息中介通知仓储服务,仓储服务一看,就问订单服务要新的订单信息,最后,启动出库流程。

消息中介除了能广播事件之外,还能做异步调用。把同步的调用转化成异步的回调。针对调用时间长和不要求实时结果的调用,可以增加性能,提升体验。

前置后端:优化前端开发

走到这里,其实体系已经比较完备。现在的问题是,如何让微服务基建结构和研发团队常见的结构更好地对应起来。这要求我们从康威定律的角度来看待整个基建的设计。

在围绕用户和价值的软件研发流程中,我们常用用户历程和用户故事来捕捉和跟踪价值的实现。一个用户故事通常会包含一个有明确边界、明确验收标准和明确价值的业务步骤。

问题在于,支撑一个故事有前后两端的研发工作,二者是不同步的。前端由业务流程和设计来驱动,希望按顺序产出;后端则由业务资源和建模来驱动,希望按模块来产出。

比如说,前端常常会因为设计的原因调整自己需要的字段,而后端从建模的角度并没有这个需要,也没有动力频繁地去跟随前端的调整,使得前端不得不在不稳定的网络条件下传输多余的信息,占用了宝贵的网络带宽。

此外,前端呈现某个业务步骤的时候,有两种信息不属于当前必备信息,但常常需要和必要信息一起展示。一种是状态信息,比如当前的登录状态和用户名,短消息的数量等等。一种是垂直相关的信息,比如在展示文章的时候顺便展示一下相关的文章。

这就要求前端在调用主服务的同时还要再调用多个不同的服务。且不说这些服务有可能会有调用超时、出错的可能,仅仅是多出来一堆异步请求,就已经足够让前端效率降低一大截了。

在微服务体系下,这些问题更加严重,因为现在不仅仅是前后端的差别,不同服务还由不同团队负责。这些团队的诉求和日程不一,很难做到前端所需要的快速响应。

这些问题和麻烦可能会催生一个“缓冲带”,比如后端出专人来负责对接前端的需要,或者前端派驻一个人到后端来谈需求。按康威定律,这种沟通体系,久而久之,很容易以软件的形式沉淀下来,形成一个专属的中间层。

要调和前后端的不同步是不可能的,而这种中间层是自然催生的解决方案,可以保留。新的问题是,它的职责是什么?应该把它放在哪?应该由谁来维护?

分析下来,其责任有二。第一是解耦前后端的工作,降低相互的影响。前端需要的东西可以写在中间层里,让它频繁变化也没有关系。后端如果还没有准备好,前端也可以在这一层模拟假的数据,不至于被阻塞。第二则是提升前端的运行效率。前端可以把所需要的多个服务的东西统一汇总,一次拿完,免得发多个请求。

放置的位置则在 API 网关之内,让它可以享有 API 网关所带来的好处和保护。

最后是维护问题。按照“谁主张,谁举证”的原则,既然有了这个中间层,好处让前端得了,那么,理论上应该由前端来维护。

这样,一个主要为前端服务的中间层就定义好了。不同类型的前端(桌面、移动)可能会有不同的需要,为了避免中间层的碎片化,我们可以让各个中间层都特定的前端类型紧密耦合,比如桌面专用、移动专用。如此,每个中间层都像是某类型前端的专享后端,所以“前置后端”(Backend-for-Frontend,简称 BFF)也因此得名。

回路熔断器:提高容错度

现在,调试也方便了,我们又继续开发。一开始没有什么问题,但部署到预生产环境的时候,又一个问题出现了:整个体系的容错度很低。一个小错误容易被层层传递和放大,导致整个体系的崩溃。

我们都知道,编程最麻烦的就是远程调用。本地调用大部分时候结果是“成功”或“失败”,但远程调用则很可能是“无响应”。“无响应”有可能是正常的,对方可能稍后会给你结果,也可能是因为对方已经死了,没法给你响应。最坏的结果,就是门口挤满了人,大家都在等你给结果,而你也在等别人给结果,资源全部占用来等,什么也做不了。

不过,远程调用是无法避免的。在微服务体系中,这个问题被进一步放大。这是因为微服务的模块化以服务为单位,而每个服务独立部署和运维,使得服务之间的调用成了家常便饭。

在这种严峻的情况下,我们必须从架构上尽量提高整个服务体系的容错度,让个别服务的问题不至于影响到全局。

具体的做法,则是给远程调用加一个熔断阈值检查,当调用超时次数超过阈值时,就不再调用,直接返回错误。过一段时间之后,再把阈值恢复,尝试继续调用,重复前面的过程。这个机制就是回路熔断,而这个工具则是回路熔断器(Circuit Breaker)。

除了隔离已经出错的服务实例,熔断器还有一个重要的功能是提供备用方案。虽然我们把所有业务都拆成了服务,但服务有高低贵贱之分。有一些服务属于关键服务,一旦出问题,则整个流程无法继续,有一些则属于分支服务,即便错了,也不会影响大局。

比如说,购买商品的时候,常常会根据用户的习惯和当前正在购买的东西做一些推荐。负责推荐的服务出问题的话,大不了就不推荐了,不应该影响用户正常的购买流程。同理,如果是在线点餐的地址定位服务出问题了,我们也应该允许用户手动选择餐厅进行点餐——体验虽然不佳,但至少正常的流程仍然可以走完。基于这个考虑,熔断器应该为非必要的服务调用提供备用方案,尽量保证核心流程的顺畅。

有了回路熔断器,远程调用出错的问题就从一定程度上缓解了。结合回路熔断器和对熔断阈值变化的监控,开发者可以更容易地发现问题,并及时采取行动。

负载均衡器:提升服务弹性

要正式上线,我们还必须做好负载均衡(Load Balancing,下简称 LB),提升整个服务的弹性。要做负载均衡,从理论上有两种方式:

客户端负载均衡(Client-Side LB):由客户端来决定如何分散请求。 中间方负载均衡(Mid-Tier LB):由 DNS、网关等中间方来决定如何分散请求。

现在,服务名册中已经有了服务及其对应的实例地址列表,所以客户端的负载均衡最简便的方式就是把地址拉下来,然后依次或者随机选择可用的地址。中间方的负载均衡则选择面较多,从最外层的 DNS 到网关都可以不同程度地去按需要去做。

扩展基建

现在,微服务基建基本完成了。如果有需要,我们可以对这个基建进行扩展。在做扩展时,架构师应该注意区分哪些东西应该中心化,哪些东西应该由服务自行决定。 比如说,在本文提到的基建之中,(几乎是)强制完全中心化的模块有:

  • 配置管理
  • 服务名册
  • 消息队列

其中,配置管理和服务名册是所有服务都需要的基础设施,必然需要统一。消息队列和日志收集都是为了跨服务的操作和追踪,也必须中心化。

半中心化的模块则有:

  • 路由
  • 鉴权

路由和鉴权都必须统一,我们前面讨论过。不过,微服务可能会向外界暴露“自用”和“客用”等多套公共 API(比如快递公司内部使用的物流 API 和开放给第三方使用的物流 API),所以可能会有两个 API 网关,对应会有两套 API 目录和两套鉴权体系,所以,它们是“半中心化”。

这些都是中心化、半中心化的选择范例。每一次中心化的选择都可能会让整个架构变得死板,失去灵活性,所以,我们在设计和扩展基建的时候应该特别注意这个问题。

除了中心化的选择之外,架构发展的另一个关注点,是让业务保持“黑盒”。

我们把每个服务之间的关联抽取了出来,也把权限的定义和验证抽取了出来,每个服务变得简单而纯粹,成了“纯业务式服务”,等同于一个仅包含了业务规则的黑盒。这样,不管服务和模块再多,也没有影响。业务的重用性也很高。

总而言之,搭建好了微服务的必要设施之后,剩下的就要根据实际情况和项目经验来继续调整了。比如,我们可能会选择把很多功能合并到一层,以避免过度分层所带来的不必要的性能损失,或者对整个基建进行一些细节微调。只要把控好“中心-自理”和“业务-非业务”之间的关系,这个基础设施就能健康地发展。

微服务基建总结

总结此文,微服务的基建应该包括如下一些组件(按请求流中的出场顺序):

  • 配置管理:配置集中管理。
  • API 网关:对外的 API 总目录;API 依赖关系;发起鉴权。
  • 服务名册:服务的注册和发现。
  • 鉴权服务:提供鉴权服务:认证身份,验证功能权限。
  • 前置后端:按前端的需求拆解请求、调用服务,并汇总、转换结果。
  • 消息中介:全局通知机制;异步调用机制。
  • 回路熔断:隔离出问题的服务并等待其恢复;提供备用方案。
  • 负载均衡:避免服务过载。

需要说明的是,这些组件的组合形式,具体拆分形式,是否需要,都需要结合实际项目和团队的情况来调整。本文权作抛砖引玉,请读者知悉。

Share

警惕“拼命三郎”文化

中国有不少公司提倡“拼命三郎”文化。所谓的拼命三郎文化,就是长时间在充满压力的情况下忙碌地工作。华为、中兴、富士康等等,都提倡拼命三郎文化。职员猝死,压力过大跳楼的事件,对我们来说已经不是什么新鲜事了。以前我们笑谈日本人过劳死,现在阴云却笼罩在了我们自己身上。

我们应该对这样的文化保持高度警惕,因为它往往来源于错误的管理方式,并且会导致创造力下降,而创造力正是现代企业最重要的生存根本。此外,它还是一种不可持续的文化,不管对员工,企业,甚至从更大层面来说,都是弊大于利。

为什么拼命

在谈论弊端之前,我们先来分析一下,拼命三郎文化是怎么产生的。(讨论的前提是,不考虑特殊的情况,比如创业。)

在我看来,高压忙碌的工作往往是从上至下的。管理者本身就很忙碌,就很容易导致员工跟着忙碌。

《哈佛商业评论》有一篇文章叫做《当心那些忙碌的经理》(Beware the Busy Manager)。文章开篇这样写道:

如果你问管理者什么资源最稀缺,他们肯定会告诉你最稀缺的是时间。他们无时无刻不在搞战略规划,关注降低成本,和竞争对手斗智斗勇。不过,如果你仔细观察一下,就会发现,他们不停地开会,总是在查邮件,随时在灭火,而且电话不断——说到底,你会看到他们在急速地做着各种各样的事情,几乎没有时间来自我反思。

我想,这就是忙碌管理者的真实写照。那么,管理者为什么忙碌呢,很明显,不懂管理。管理是一种工具,忙碌的管理者是让工具控制了自己,而无法完全掌控工具,简而言之,不懂管理。不懂管理也分两种:

  • 能力弱又不懂管理
  • 能力强而不懂管理

前者基本上可以说是在假忙,不忙起来似乎体现不了自己的作用。这种人常常存在于旧式大型企业之中。后者则是不懂如何安排自己的时间和精力,又或者不懂如何放权,把工作委派给更多的人。

我们都能理解能力弱的人庸庸碌碌,但能力强的人亦难幸免。能力强的人往往会把很多事情揽在身上,事情一多,就容易导致时间和精力安排得不好,导致各种混乱和干扰,使专注广度变得极短。

此外,能力强的人会有自然的权力欲,对制度和他人不够信任,事必躬亲,运气好的就形成能人式管理,还能好过一阵,运气不好的则因为管不过来而导致管理失效。

诸葛亮就是这样的例子。虽然个人能力很强,但由于管得过于细腻,“罚二十以上皆亲览焉”,导致在他过世之后蜀国迅速出现了管理人才的真空。这和蜀国的灭亡有着很强的关系。

管理者一旦忙起来了,被管理者岂有不忙之理?普通职员忙,有几种原因:

  • 误认为忙碌等于能力
  • 被利益所吸引
  • 管理者失误

当“忙碌”被等同于“能力”的时候,那么就人人都愿意忙起来,显示自己的能力。似乎自己越忙,就越资深,越高级。要是按时完成了目标,倒会被人视作异类。

虽然有时候人们会把忙碌等同于能力,不过,更多的人愿意忙碌,乃是被利益所吸引。这里的利益可以是显性的,比如说华为提倡的“狼性”,其中有一条很重要的,就是——狼是要吃肉的,所以,你忙起来,我这里工资高,我拿工资换你的生命;它也可以是隐性的,比如,忙起来的人或者加班的人能得到更好的晋升机会。

当然,很多时候普通职员并没有选择的自由。如果遇到差劲的领导,以加班为荣,随意增加工作量,就会非常被动。

拼命为什么不好

拼命的弊端体现在从小到大四个层面:

  • 对员工不好
  • 对企业不好
  • 对社会不好
  • 对国家不好

对员工本身不好就不用说了。因为劳累过度而导致的各种疾病,亚健康状态,心理压力。此外,占用的额外时间让员工无法获得足够的社交和家庭生活,把人变成了机器。

对员工不好的事情对企业能好得了吗?这种忙碌文化本身作为一种不良因子,会影响公司本身的文化。虽然有一些公司确实把忙碌,加班,高压作为企业文化(这类公司常被称作“军队式公司”,这名字其实并不恰当),但我相信更多的公司会把达成对客户的承诺,提供更好的服务,或者对社会做出更多贡献作为文化。可一旦忙碌起来,什么文化都会被冲淡。文化是人和人的互动产生的,人都埋头苦干了,哪儿还有那么多时间互动,互动少了,文化哪里来?

不仅如此,一旦员工的时间利用率达到了阈值,其效率反而会迅速下降。The Connected Company 一书就举了这样一个例子:

使用 ATM 的人越多,平均等待时间就会越长。那么到底需要多少 ATM 才比较合适呢?经验表明,越 70% 的利用率能够确保 ATM 不过于闲置,而等待时间又可以忍受。超过则等待时间将急剧上升。

在做管理的时候,管理者总是思考如果最大限度的提升人员利用率。可需求是不稳定的,如果等待时间过长,那极致利用率反而意味着效率的降低。这也是为什么 Google 会鼓励员工每周花一天来做自己的事情,不仅追求创新,还可以在整个管理体系中形成冗余,以在需求不稳定时仍然保持高效运转。

忙碌不仅对企业不好,对社会也不好。如果你一个人做了两个人,三个人做的事情,加班加点,那就会占用一个工作名额,妨碍其他人就业。

对社会不好的事情,能对国家好吗?依靠压榨自己人来获取外部资源的青睐,这种模式无法长久。经济的迅速发展必然导致成本的上升,成本上升又会导致进一步的压榨,压榨到一定程度,成本优势消失,市场环境竞争力就会下降。接下来就是需求下降,被压榨很久的人又被一脚踢开。裁员这种事情,近年来也是家常便饭了。可怜的国人,以前被抓到外国去剥削,现在又在自己国家被自己人剥削。

所以说,“拼命文化”从小到大,从内至外的弊端很多,我们必须警醒。

拼命不是专业

一种常见的误解是:不是我拼命,而是我答应客户的事情就应该做到,也是专业人士的素养。这是本末倒置。你在你不拼命就做不到的情况下,对客户做出承诺,把客户至于风险之下,本身就是相当不专业的。此外,专业精神和拼命不同,拼命是不可持续的,而专业则意味着持续稳定地发挥效果。所以,拼命不等于专业。

解决办法

我们谈了拼命文化的来源,其害处,那么,怎么解决?

在我看来,最重要的就是解决管理者这个罪恶的根源。首先,为了避免瞎忙,管理者的管理目标应该公开、透明。此外,管理者本身就不应该形成忙碌的榜样,不能事事都靠自己,要学会委任和放权,培养次级管理者。最后,如果市场不稳定,就不要非把目标设立成增长,可以是保持,甚至该收缩就收缩。

公司也应该形成制度,不能无偿加班。应该增加人手就增加人手,应该做出变革就做出变革。此外,所谓生于忧患,死于安乐,不仅员工应该有危机感,公司也应该有危机感。培养危机感的方式就是员工与公司之间的对等、平等的选择关系,不要用隐性或者显性的利益来拴住员工。既然过高的利用率不好,那么就保持一定的利用率,并留出 20% 左右的时间给员工来做创新或者把企业文化活络起来。

总结

我们属于东亚文化圈,而东亚文化推崇勤奋,但是,勤奋是由内至外的自主行动,是快乐的事情。而拼命并不是如此,它让士气消沉,阻碍创新,从个人到公司再到社会,都会受其害。身在这种文化之中的管理者和员工都应该对其有所警惕,并积极地寻求更好的解决办法。希望大家都能以按时完成工作,按时下班为荣。

(本文作者终其一生都以调戏和对抗领导以及权威为荣,领导看见了不要太在意哟。♥)

Share

什么是服务设计

近几年来,体验设计界有一个热门的名词,叫做“服务设计”。在第一次接触这个名词的时候,我有点摸不着头脑:

  • 它和体验设计有什么不同?
  • 它是一种新的设计方法,抑或只是对旧设计方法的一种包装?
  • 服务二字到底指的是什么?

抱着这样的疑问,我翻阅了很多书籍、文章,看了不少演讲,但非但没有让我对它更多了解,反而让我更疑惑了。似乎每个人都对服务设计有自己的解读,而且它似乎无所不包。

后来,我有一次住酒店的时候走楼梯下楼,看到坐在楼梯间休息的清洁师傅,突然明白了服务设计的意义。这次,我就跟大家谈谈什么是服务设计。

从一个用户到多个用户

作为体验设计师,我们最关注的是用户的体验。在我们看来,用户要使用一个产品,必然要达成某种目标,而我们则需要帮助他们以最快或者最舒适的方式来达成目标。

tumblr_inline_nfpbiki6BW1rxo9iy

:只有针对特定人群的设计才是体验设计

为了达到良好的体验,我们不断对用户群体进行细化,最后剩下了一个用户。我们专门为她来做设计,希望给她最佳的体验。问题在于,工具是一个人用的,但现实生活中的服务常常不止一个人参与。

比如说,“教学”这个服务就至少包括了“老师”和“学生”这样两种人参与。对于一个服务来说,二者都是“用户”,而他们的体验也都是我们关心的对象。除了教学之外,通常IT系统都存在管理员,比如以前论坛的版主,或者内部系统的网管等等。互联网产品也是如此,使用方,管理方,两种角色。

tumblr_inline_nfpc2ft9qP1rxo9iy

:一项服务往往有多个参与方

不过,虽然角色变多,但由于他们属于管理关系,相对独立,所以二者的体验通常不会有太多交集。管理者和使用者一般会使用各自的界面,也可以有针对性地地进行设计。

支撑服务的其他角色

现实生活中,要实现一项服务,参与的角色远远不止两个,而且相互之间常常会造成影响,不像理想中分割得那么清楚。

比如一个网上商城。从我们打开一个网站,找到一个商品,下单,支付,最后快递上门收到货物,其实中间要经历很多步骤,而且有很多人参与:

  • 下单之后,我们需要支付,这里面就涉及到和银行和支付机构的对接
  • 支付的钱要进入财务的系统,财务需要记账,并通知仓库备货和发货
  • 仓库人员收到货物之后,需要找到货物,打包,打印单据,准备发货,并更新库存
  • 物流公司会来取货,留下一个快递单号,仓库人员需要把快递单号和订单号对应,录入系统
  • 货物送到之后,快递状态更新,订单状态随之更新

这还仅仅是正常情况,如果要加上退货、换货的流程,或者加入赠品、套餐、促销、礼品卡等等概念,则整个流程会更加复杂。它的复杂不在于步骤的多少,而在于需要多次和第三方进行合作,用户的行为也不可控。整个流程中出现任何问题,都会对用户——那个我们称之为“最终用户”(end user)的人造成麻烦,导致体验变差。

tumblr_inline_nfpcs54wMZ1rxo9iy

:用户体验是服务最重要的部分,但也只是其中一小部分(粉色 = 用户体验相关;橙色 = 服务运营相关)

如果单从体验设计的角度来看,也许我们的目光会集中在商城的商品列表、展示、支付和搜索等等页面,大不了再加上管理端的设计。这样的设计也许看上去体验很好,但有可能会造成其他部分的麻烦,或者遇到实施方面的问题,又或者根本就无法落实。

所以,我们必须注意角色和需求之间的搭配,平衡,哪些地方该标准化,哪些地方不能标准化,使整个服务能够完善起来。

领域内外专家的配合

也许看到这里,你已经知道当时我是怎么“顿悟”的了。

我因为工作的关系经常都会出差,所以也常常会住酒店。作为一名住客,又是做体验设计的,所以我会对酒店的楼层、房间、装修、设备等等比较敏感。不过,我通常只会看到最后的结果,也就是我亲自会接触和使用的部分。

在楼梯间看到休息的师傅之后,我意识到:每天房间都会有师傅来打扫,但实际上并不是换个床单和洗漱用品那么简单。

tumblr_inline_nfpdc2DoHK1rxo9iy

:酒店房间的清洁这样简单的事情也需要一个服务体系的支撑

换下来的毛巾和床单如果要清洗,那么就必然涉及到一个洗衣房。这个洗衣房如果是内部经营,那么就还会涉及到大型洗涤设备、操作人员、洗涤剂、排污和环卫措施、设备维护保养,等等。除了洗涤,还有晾晒,还有储存,消毒等等一系列的工作需要做。如果是和外部合作,又会涉及到运输,等待时间,质量保障,财务来往,等等。

最后一步,才是给房间换上清洁的毛巾和床单。

洗漱用品也是如此。向哪家供应商订购,如何运输,如何确保质量,如何储存,库存如何清算……有了这些,才有最后师傅来更换的那一步。

所有这些都是整个酒店生态体系中的节点。虽然“客户体验”的重要性不言而喻,但它其实只是这个生态体系中的一个层面。要设计这样一个生态体系,就必须对整个生态体系和流程有比较深入的了解,也就是所谓的领域知识。

领域知识包括了有形和无形的经验。有形的经验,比如对流程的理解,对服务中各方的需要和诉求的理解,对行业标准和现状的理解等等。这些知识容易落成文字和传授,也相对容易学到。还有就是无形的经验,比如对异常状况的处理,对服务中各方“脾性”的了解从而能采取合适的行动,等等。

我们可以用个很简单的例子来理解无形的经验。在使用一款手机之后,我们往往会发现它有一些小问题,比如点击了某个图标之后,会有一秒钟左右的黑屏,但用久了之后就明白了它这个脾性。手机有脾性,同理,服务的参与方也有脾性。对这些细节的了解累积起来,就形成了深厚的隐式知识(tacit knowledge),难以言传,但是遇到之后却能够从容应对。

掌握充分领域知识的人我们称之为领域专家。要设计一个服务,有一个服务所在领域的专家是很有必要的,因为领域专家知道这个领域的基本原则和边界,可以及早发现问题。否则,做出来的设计很可能无法落地或者难以实现。

tumblr_inline_nfpdjnH6xp1rxo9iy

:他可不是专业做音乐播放器的,或者手机的,或者手表的,或者……

不过,由领域专家来牵头做设计也会有很大的局限性。领域专家一般在一个领域扎根,这就有可能导致思维的固化。我们可以看看各个行业的创新者,那些体验好的产品往往是“局外人”提出。没有了原来的既有观念和条条框框,往往能做出好的设计。苹果公司做音乐播放器,踏入了索尼 Walkman 的领域,做手机,又踏入了诺基亚和微软的市场,但都“搅局”成功,就是很好的例子。

总结

tumblr_inline_nfpdwxzCCp1rxo9iy

:服务设计意味着视角的转变和提升

在一开始,我们谈产品或者系统设计,站在产品、系统、服务本身的角度来看设计(“房间干不干净?”)。后来,我们开始谈体验设计,转换成用户视角,站在用户的角度来看设计(“肥皂是不是年轻客人喜欢的品牌?”)。继续发展下去,体验设计都跟上之后,就应该转向做服务设计了——站在上帝视角,俯视和规划整个服务的运转,以满足多方需求,使整个服务体系达到平衡(“酒店能不能持续稳定地兑现口号宣传的承诺?”)。

要设计好的服务,就需要领域专家和体验设计师以及其他非领域专家的合作。我想,这也是为什么我们经常听到平面设计师、互动设计师、体验设计师,却很少听到“服务设计师”——因为这事根本不能成为一个职业,必须由一个团队来做。

tumblr_inline_nfox9rB1561rxo9iy

:1988 年,十二届三中全会的代表参观北京第一家超市——京华超市

虽然服务设计听上去很有意思,真正把服务设计当成一个议题,是最近才开始的事——这也正常,因为服务设计需要成熟的用户体验文化,并需要有很多领域专家。要知道,中国出现真正意义上的现代零售业,是 1984 年的第一家超市。出现现代的快递服务,是 1993 年。也就是说,很多行业都还很新,而且还有很多古老的行业(比如酒店业)正在经历革新。用户体验也是上世纪 90 年代才提出来的理念。

经历了几十年的发展之后,领域专家开始出现。有了互联网和一众互联网公司的强力推动,用户体验的理念也开始铺开。二者结合之下,相信服务设计会逐渐迎来春天,成为接下来设计公司的重要业务。

Share