提交 0079e35a 编写于 作者: 沉默王二's avatar 沉默王二 💬

微信公众号文章摘录

上级 1b97cfd2
---
title: 面试官:如果让你设计一个秒杀系统,你会如何设计?
shortTitle: 面试官:如果让你设计一个秒杀系统,你会如何设计?
author: 陈树义
category:
- 微信公众号
---
>[二哥的编程星球](https://mp.weixin.qq.com/s/3RVsFZ17F0JzoHCLKbQgGw)已经有 **560 多名** 小伙伴加入了,如果你也需要一个良好的学习氛围,[戳链接](https://mp.weixin.qq.com/s/3RVsFZ17F0JzoHCLKbQgGw)加入我们吧!这是一个 Java 学习指南 + 编程实战的私密圈子,你可以向二哥提问、帮你制定学习计划、跟着二哥一起做项目、刷力扣,冲冲冲。
秒杀系统的设计是高级职位面试中非常高频的一道题目,它可以较好地考察候选人的知识体系情况。对于我们来说,学习秒杀系统的设计,能够让我们学以致用,设计系统的时候考虑得更加全面。今天就带你一起来看看怎么设计一个秒杀系统!
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-miansgrgrnsjygmsxtnhrhsj-bc1898e6-ed8b-4594-bdd5-165886ce9f97.jpg)
活动一般出现在电商的促销活动中,一般是指定了很少数量的商品,以极低的价格,让大量的用户参与,从而造成大量用户在极短的时间内参与活动,进而造成系统在极短的时间内有极高的流量。系统设计的目的是使系统能够稳定地支撑活动的进行,因此其稳定性、高可用是我们考虑的第一位。
要知道如何进行秒杀系统的优化,那我们需要先对请求的整个流程有个全局的认识。**一般来说,秒杀活动请求以公网为划分点,可以分为:前端部分、后端部分。** 前端部分指的是从用户端到进入后端服务前的部分,包括了移动端的处理、DNS 解析、公网的数据传递等。
后端部分指的是经公网进入了后端的服务器网络里,包括了前置的负载均衡(Nginx 等)、应用服务器、数据库层等。秒杀活动的整个流程可以用下图来表示。
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-miansgrgrnsjygmsxtnhrhsj-9f9def72-fb75-4e36-9529-61f4cb93d830.jpg)
the-process-of-network-request
我们要去设计一个秒杀系统,那自然也是从这两大部分来进行优化。整体思路是尽量将流量挡在前面,让尽量少的流量留到后端部分。因为越往后端,我们的处理逻辑就越重,其处理能力也越弱。
## 前端优化
对于前端部分来说,常见的优化手段有:页面静态化 + CDN、请求频率限制。
### 页面静态化 + CDN
一般来说,活动页面是流量最大的地方。活动页面上绝大部分内容都是固定的,比如:商品描述、图片等。这时候没有必要每次都去请求服务端,而是将这些静态的内容放到 CDN 上。
每次打开页面的时候,直接去请求 CDN 服务器,能极大地减少后端的请求流量。加入了 CDN 之后,其请求过程如下:
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-miansgrgrnsjygmsxtnhrhsj-7d04e7cc-949d-4a03-884f-0178b303f196.jpg)
CDN 优化静态数据
所谓的 CDN 就是内容分发网络,它由非常多台分布在世界各地的缓存服务器组成。每次用户请求特定域名的时候,会转发到对应 CDN 的 DNS 解析服务器,随后会返回一台离用户地理位置最近的一台 CDN 服务器。
随后,用户直接请求这台 CDN 服务器获取数据,从而极大地减少了长途网络传输的时间,并且也减少了后端服务器的压力。
**因此,对于秒杀活动设计来说,我们可以将所有可以静态化的内容全部静态化,然后将其配置在 CDN 服务器上。这样既提高了用户打开页面的时间,又减少了后端服务器的压力。**
### 请求频率限制
请求频率限制,指的是根据业务的特点,在前端做一些流量拦截,减少后端服务器的压力。常见的拦截方式有:
1. 设定一个请求概率,只允许 30% 的概率向后端发送接口请求。
2. 设定一个请求频率,例如 10 秒钟只能请求 1 次,随后按钮置灰。
通过这种方式,我们可以减少很大一部分流量。但在具体实现的时候,可能需要考虑安全问题,预防某些用户直接调用后台接口,绕过前端的频率检查。
常见的方法是在频率检查时生成一个参数,随后请求后端服务时携带上该参数。没有该参数的请求,都视为非法请求,直接拒绝该请求。
## 后端优化
无论我们做多大的努力,始终还是会有不少流量会来到后端服务器这里。一般来说,后端的优化有如下几种方式:
1. 增加缓存层 + 预热数据
2. MQ 异步处理
3. 限流、熔断、兜底
4. 业务侧优化
### 增加缓存层 + 预热数据
如果我们所有数据都去读取数据库,数据库可能无法承受较大的流量,此时一个常见的优化就是增加缓存层。
当我们需要查询数据库之前,我们先去查询缓存,这样可以减少绝大部分的数据库请求,减轻数据库压力。如果在缓存中找不到数据,我们再去请求数据库,随后再将数据缓存到缓存中。
在引入缓存层的时候,我们需要考虑缓存击穿、缓存穿透的可能性,在写相关代码的时候就要做好这些优化。另外,我们在秒杀活动开始之前,可以手动将热点数据加载到缓存中,从而避免秒杀时去请求数据库。
### MQ 异步处理
我们知道秒杀活动一般涉及抢购、下单、支付、发货等阶段,而抢购与后续的几个阶段是可以异步执行的。为了避免对下单、支付、发货等阶段产生影响,我们可以将抢购阶段与后续阶段用 MQ 进行解耦处理。当用户抢购成功后,往消息队列中丢入一台消息,随后再由订单系统消费进行下单处理。
通过各系统之间的解耦处理,我们可以将原本同步的处理方式变为异步处理,从而大大的减少了请求的处理时间,提高了系统的并发处理能力。其次,也能避免系统之间相互影响,提高了整体系统的稳定性。
### 限流、熔断、降级
虽然我们做了非常多的优化措施,但还是可能存在请求超量的可能性,那怎么办呢?
我们可以在每个业务系统做限流操作,从而避免因为请求太多,导致整个系统都无法工作。当并发请求在正常范围内时,我们正常处理请求。当超过设置的限流阈值时,我们则直接拒绝该请求,提示用户抢购失败。
如果没有限流操作,那么系统直接崩溃了,一个请求都处理不了。而通过限流这种方式,系统至少还可以保持正常工作,而不至于一个请求都处理不了。而超量的需求,本来就处理不了,因此提示失败也是情理之中。
除了限流之外,不同的系统还可以采用熔断、降级的服务治理措施。
熔断指的是请求的错误次数超过阈值时,不再到用后端服务,直接返回失败。同时每隔一定时间放几个请求去重试后端服务,看看是否正常。如果正常则关闭熔断状态,如果失败则继续快速失败。**熔断的目的是避免因下游短暂的异常,导致上游不断重试,最终造成下游有太多请求,最终压垮下游系统。**
降级指的是当服务失败或异常后,返回指定的默认信息。**降级的目的是保证有基本的信息,当下游异常时,与其返回空信息,不如返回一个有业务含义的默认信息,可以提高用户体验。**
### 业务侧优化
一般来说,经过上述的整体优化之后,系统已经能够比较稳当地应对秒杀活动了。如果此时还是流量比较大,那么或许应该从业务侧去进行优化了。
例如 12306 刚开始的时候,购买时间都在同一时刻,这导致同一时刻并发量太大,系统经常支撑不住。后来 12306 将购票周期放长,可以提前 20 天购买火车票。通过业务侧的优化,我们将本来在 1 个小时的抢购分摊到了 20 天,服务器压力一下子降低了 480 倍!
**张小龙也说过:如果公司最厉害的程序员来实现业务都觉得复杂,那很可能就是业务确实不合理,这时候应该从业务侧进行优化。**
例如一个存储了 10 亿条记录的消息记录表,业务侧既想查询速度快,又想进行 1 年数据范围的数据查询,这无论如何都是无法实现的。这时候就需要从业务需求侧进行优化,否则是无法两全其美的。
对于这个场景,一个合理的实现方式是:要实现 1 年数据范围的查询,那么只能根据消息 ID 进行,因为这样可以使用上索引。而要根据时间范围进行查询,只能缩短查询时间到 3 天内,这样也可以满足业务需求。
**因此从业务侧进行优化,是一个四两拨千斤的办法,可以极大地降低技术侧实现的难度。**
## 总结
设计一个秒杀系统,整体而言可以从前端与后端进行优化。
**对于前端优化而言,可以从「页面静态化 + CDN」、请求频率限制进行优化。**
其中「页面静态化 + CDN」指的是将不变的静态数据固定下来,然后放入 CDN 服务器,从而降低用户请求的响应速度,降低服务器的并发压力。请求频率限制,则是通过抢购概率与抢购频率限制,降低后端服务器的服务压力。
**对于后端优化而言,一般有「增加缓存层 + 预热数据」、「MQ 异步处理」、「限流、熔断、降级」、业务侧优化这 4 种优化方式。**
其中「增加缓存层 + 预热数据」指的是将热点数据存入缓存,并在活动开始前提前加载到缓存中,降低数据库层的读取压力。「MQ 异步处理」指的是对于非必要的业务逻辑,通过 MQ 进行异步处理,降低请求处理延时,同时提高业务系统整体稳定性。
「限流、熔断、降级」是对于整体微服务的保护,其中限流指的是对请求进行限制,当超过限流阈值时,直接拒绝请求,保护系统本身;熔断指的是保护下游系统,当请求下游系统连续错误超过阈值时,自动不去请求下游系统,避免因重试流量过大击垮下游系统。
降级指的是当请求失败时,自动返回默认数据,提高用户体验。业务侧优化,则是指从业务层面去进行逻辑优化,从而降低技术复杂度,使得业务与技术复杂度达到一个平衡的状态,有利于更好地实现秒杀系统的高可用与高并发。
上面说到的 6 个优化思路,是设计秒杀系统常见的优化思路。**但在实际业务场景中,除了要保障正常的功能设计之外,还还考虑防刷、安全、黑产等问题**,此时可能需要多考虑一些其他优化,例如:黄牛利用抢购工具抢购,导致正常用户无法抢到商品等。
这时候可能需要考虑增加验证码,用 App 设备指纹等风控措施。**此外,对于秒杀系统而言,做好业务指标和系统指标的埋点监控也是非常重要的。**
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-miansgrgrnsjygmsxtnhrhsj-bc1898e6-ed8b-4594-bdd5-165886ce9f97.jpg)
* * *
没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。
**推荐阅读**
- [新一代开源免费的终端工具,太酷了](https://mp.weixin.qq.com/s/2IUe50xBhuEWKDzARVd51A)
- [最大成就,拿到一等奖学金](https://mp.weixin.qq.com/s/3lqp4x1B5LI1hNjWAi6v1g)
- [银行开发太安逸,奋发图强要跳槽](https://mp.weixin.qq.com/s/ZeA-mEyMkEeSHRtd8Pob9A)
- [这个大专生,强的离谱!](https://mp.weixin.qq.com/s/fNMhpER0tp5RO5TGcgALMQ)
- [一怒之下,退伍转码](https://mp.weixin.qq.com/s/IEEkWiI9iN4MEhoHvrTgcg)
- [没必要为实习碰的头破血流](https://mp.weixin.qq.com/s/KxUMq2YmlIBMbAeRwUm8JA)
- [网站挣了 200 美刀后的感触](https://mp.weixin.qq.com/s/PxgZkuA_SnAgG7xfwlKLgw)
- [在 IDEA 里下五子棋不过分吧?](https://mp.weixin.qq.com/s/R13FkPipfEMKjqNaCL3UoA)
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-quanxxtjgzysjyyds-33afdc45-d78b-46e0-91c2-1107161496e9.jpg)
>转载链接:[https://mp.weixin.qq.com/s/ksJcA-8v2kY5J0dkMF6W5Q](https://mp.weixin.qq.com/s/ksJcA-8v2kY5J0dkMF6W5Q),出处:JavaGuide,整理:沉默王二
此差异已折叠。
---
title: 权限系统就该这么设计,yyds!
shortTitle: 权限系统就该这么设计,yyds!
description: 今天来聊聊权限系统的设计以及主流的五种权限模型。
author: 不才陈某
category:
- 微信公众号
head:
- - meta
- name: description
content: 今天来聊聊权限系统的设计以及主流的五种权限模型。
---
>[二哥的编程星球](https://mp.weixin.qq.com/s/3RVsFZ17F0JzoHCLKbQgGw)已经有 **560 多名** 小伙伴加入了,如果你也需要一个良好的学习氛围,[戳链接](https://mp.weixin.qq.com/s/3RVsFZ17F0JzoHCLKbQgGw)加入我们吧!这是一个 Java 学习指南 + 编程实战的私密圈子,你可以向二哥提问、帮你制定学习计划、跟着二哥一起做项目、刷力扣,冲冲冲。
今天来聊聊权限系统的设计以及主流的五种权限模型。
权限管控可以通俗的理解为权力限制,即不同的人由于拥有不同权力,他所看到的、能使用的可能不一样。对应到一个应用系统,其实就是一个用户可能拥有不同的数据权限(看到的)和操作权限(使用的)。
主流的权限模型主要分为以下五种:
* **ACL模型**:访问控制列表
* **DAC模型**:自主访问控制
* **MAC模型**:强制访问控制
* **ABAC模型**:基于属性的访问控制
* **RBAC模型**:基于角色的权限访问控制
## ACL模型:访问控制列表
**Access Control List**,ACL是最早的、最基本的一种访问控制机制,是基于客体进行控制的模型,在其他模型中也有ACL的身影。为了解决相同权限的用户挨个配置的问题,后来也采用了用户组的方式。
**原理**:每一个客体都有一个列表,列表中记录的是哪些主体可以对这个客体做哪些行为,非常简单。
**例如**:当用户A要对一篇文章进行编辑时,ACL会先检查一下文章编辑功能的控制列表中有没有用户A,有就可以编辑,无则不能编辑。再例如:不同等级的会员在产品中可使用的功能范围不同。
**缺点**:当主体的数量较多时,配置和维护工作就会成本大、易出错。
## DAC模型:自主访问控制
Discretionary Access Control,DAC是ACL的一种拓展。
**原理**:在ACL模型的基础上,允许主体可以将自己拥有的权限自主地授予其他主体,所以权限可以任意传递。
**例如**:常见于文件系统,LINUX,UNIX、WindowsNT版本的操作系统都提供DAC的支持。
**缺点**:对权限控制比较分散,例如无法简单地将一组文件设置统一的权限开放给指定的一群用户。主体的权限太大,无意间就可能泄露信息。
## MAC模型:强制访问控制
**Mandatory Access Control**,MAC模型中主要的是双向验证机制。常见于机密机构或者其他等级观念强烈的行业,如军用和市政安全领域的软件。
**原理**:主体有一个权限标识,客体也有一个权限标识,而主体能否对该客体进行操作取决于双方的权限标识的关系。
**例如**:将军分为上将>中将>少将,军事文件保密等级分为绝密>机密>秘密,规定不同军衔仅能访问不同保密等级的文件,如少将只能访问秘密文件;当某一账号访问某一文件时,系统会验证账号的军衔,也验证文件的保密等级,当军衔和保密等级相对应时才可以访问。
**缺点**:控制太严格,实现工作量大,缺乏灵活性。
## ABAC模型:基于属性的访问控制
**Attribute-Based Access Control**,能很好地解决RBAC的缺点,在新增资源时容易维护。
**原理**:通过动态计算一个或一组属性是否满足某种机制来授权,是一种很灵活的权限模型,可以按需实现不同颗粒度的权限控制。
属性通常有四类:
1. 主体属性,如用户年龄、性别等;
2. 客体属性,如一篇文章等;
3. 环境属性,即空间限制、时间限制、频度限制;
4. 操作属性,即行为类型,如读写、只读等。
**例如**:早上9:00,11:00期间A、B两个部门一起以考生的身份考试,下午14:00,17:00期间A、B两个部门相互阅卷。
**缺点**:规则复杂,不易看出主体与客体之间的关系,实现非常难,现在应用的很少。
## RBAC,基于角色的权限访问控制
**Role-Based Access Control**,核心在于用户只和角色关联,而角色代表对了权限,是一系列权限的集合。
RBAC三要素:
1. **用户**:系统中所有的账户
2. **角色**:一系列权限的集合(如:管理员,开发者,审计管理员等)
3. **权限**:菜单,按钮,数据的增删改查等详细权限。
**RBAC**中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。
角色是为了完成各种工作而创造,用户则依据它的责任和资格来被指派相应的角色,用户可以很容易地从一个角色被指派到另一个角色。
角色可依新的需求和系统的合并而赋予新的权限,而权限也可根据需要而从某角色中回收。角色与角色的关系同样也存在继承关系防止越权。
**优点**:便于角色划分,更灵活的授权管理;最小颗粒度授权;
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-quanxxtjgzysjyyds-08622141-9cdc-46ce-bd9c-584bd267e3d8.jpg)
## RBAC的深度拓展
RBAC模型可以分为:**RBAC0****RBAC1****RBAC2****RBAC3** 四个阶段,一般公司使用**RBAC0**的模型就可以。另外,**RBAC0**相当于底层逻辑,后三者都是在**RBAC0**模型上的拔高。
我先简单介绍下这四个RBAC模型:
### 1\. RBAC0模型
用户和角色、角色和权限多对多关系。
简单来说就是一个用户拥有多个角色,一个角色可以被多个用户拥有,这是用户和角色的多对多关系;同样的,角色和权限也是如此。
**RBAC0**模型如下图:没有画太多线,但是已经能够看出多对多关系。
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-quanxxtjgzysjyyds-9519e9d7-3145-4b6d-b54e-506034b7db5f.jpg)
### 2\. RBAC1模型
相对于**RBAC0**模型,增加了**角色分级**的逻辑,类似于树形结构,下一节点继承上一节点的所有权限,如**role1**根节点下有**role1.1****role1.2**两个子节点
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-quanxxtjgzysjyyds-86906439-9dcd-4e1c-a8c7-d680d97b9831.jpg)
角色分级的逻辑可以有效的规范角色创建(主要得益于权限继承逻辑),我之前做过BD工具(类CRM),BD之间就有分级(经理、主管、专员),如果采用RBAC0模型做权限系统,我可能需要为经理、主管、专员分别创建一个角色(角色之间权限无继承性),极有可能出现一个问题,由于权限配置错误,主管拥有经理都没有权限。
而RBAC1模型就很好解决了这个问题,创建完经理角色并配置好权限后,主管角色的权限继承经理角色的权限,并且支持针对性删减主管权限。
### 3\. RBAC2模型
基于**RBAC0**模型,对角色增加了更多约束条件。
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-quanxxtjgzysjyyds-88ed54f3-233b-48a8-83df-6b783fa6bc10.jpg)
**角色互斥**,比较经典的案例是财务系统中出纳不得兼管稽核,那么在赋予财务系统操作人员角色时,同一个操作员不能同时拥有出纳和稽核两个角色。
**角色数量限制**,例如:一个角色专门为公司CEO创建的,最后发现公司有10个人拥有CEO角色,一个公司有10个CEO?这就是对角色数量的限制,它指的是有多少用户能拥有这个角色。
**RBAC2** 模型主要是为了增加角色赋予的限制条件,这也符合权限系统的目标:权责明确,系统使用安全、保密。
### 4\. RBAC3模型
同样是基于**RBAC0**模型,但是综合了**RBAC1****RBAC2**的所有特点
这里就不在多描述,读者返回去看**RBAC1****RBAC2**模型的描述即可。
## RBAC 权限管理的在实际系统中的应用
RBAC 权限模型由三大部分构成,即**用户管理****角色管理****权限管理**
用户管理按照企业架构或业务线架构来划分,这些结构本身比较清晰,扩展性和可读性都非常好。
角色管理一定要在深入理解业务逻辑后再来设计,一般使用各部门真实的角色作为基础,再根据业务逻辑进行扩展。
权限管理是前两种管理的再加固,做太细容易太碎片,做太粗又不够安全,这里我们需要根据经验和实际情况来设计。
### 1.用户管理
用户管理中的用户,是企业里每一位员工,他们本身就有自己的组织架构,我们可以直接使用企业部门架构或者业务线架构来作为线索,构建用户管理系统。
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-quanxxtjgzysjyyds-840538da-9803-4a12-94de-fd696a710968.jpg)
**需要特殊注意**:实际业务中的组织架构可能与企业部门架构、业务线架构不同,需要考虑数据共享机制,一般的做法为授权某个人、某个角色组共享某个组织层级的某个对象组数据。
### 2.角色管理
在设计系统角色时,我们应该深入理解公司架构、业务架构后,再根据需求设计角色及角色内的等级。
一般角色相对于用户来说是固定不变的,每个角色都有自己明确的权限和限制,这些权限在系统设计之处就确定了,之后也轻易不会再变动。
#### **1\. 自动获得基础角色**
当员工入职到某部门时,该名员工的账号应该自动被加入该部门对应的基础角色中,并拥有对应的基础权限。这种操作是为了保证系统安全的前提下,减少了管理员大量手动操作。使新入职员工能快速使用系统,提高工作效率。
#### **2\. 临时角色与失效时间**
公司业务有时需要外援来支持,他们并不属于公司员工,也只是在某个时段在公司做支持。此时我们需要设置临时角色,来应对这种可能跨多部门协作的临时员工。
如果公司安全级别较高,此类账号默认有固定失效时间,到达失效时间需再次审核才能重新开启。避免临时账号因为流程不完善,遗忘在系统中,引起安全隐患。
#### **3\. 虚拟角色**
部门角色中的等级,可以授权同等级的员工拥有相同的权限,但某些员工因工作原因,需要调用角色等级之外的权限,相同等级不同员工需要使用的权限还不相同。
这种超出角色等级又合理的权限授予,我们可以设置虚拟角色。这一虚拟角色可集成这一工作所需的所有权限,然后将它赋予具体的员工即可。这样即不用调整组织架构和对应的角色,也可以满足工作中特殊情况的权限需求。
#### **4\. 黑白名单**
**白名单**:某些用户自身不拥有某部门的顶级角色,但处于业务需求,需要给他角色外的高级权限,那么我们可以设计限制范围的白名单,将需要的用户添加进去即可。
在安全流程中,我们仅需要对白名单设计安全流程,即可审核在白名单中的特殊用户,做到监控拥有特殊权限的用户,减少安全隐患。
**黑名单**:比较常见的黑名单场景是某些犯了错误的员工,虽然在职,但已经不能给他们任何公司权限了。这种既不能取消角色关联,也不能完全停用账号的情况,可以设置黑名单,让此类用户可以登录账号,查看基本信息,但大多数关键权限已经被黑名单限制。
### 3\. 权限管理
权限管理一般从三个方面来做限制。**页面/菜单权限****操作权限****数据权限**
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-quanxxtjgzysjyyds-d798a7f6-a1b9-4b62-84bd-9412ff8610a0.jpg)
#### **1\. 页面/菜单权限**
对于没有权限操作的用户,直接隐藏对应的页面入口或菜单选项。这种方法简单快捷直接,对于一些安全不太敏感的权限,使用这种方式非常高效。
#### **2\. 操作权限**
操作权限通常是指对同一组数据,不同的用户是否可以增删改查。对某些用户来说是只读浏览数据,对某些用户来说是可编辑的数据。
#### **3\. 数据权限**
对于安全需求高的权限管理,仅从前端限制隐藏菜单,隐藏编辑按钮是不够的,还需要在数接口上做限制。如果用户试图通过非法手段编辑不属于自己权限下的数据,服务器端会识别、记录并限制访问。
#### **4\. 数据权限如何管控**
数据权限可以分为行权限和列权限。行权限控制:看多少条数据。列权限控制:看一条数据的多少个字段
简单系统中可以通过组织架构来管控行权限,按照角色来配置列权限,但是遇到复杂情况,组织架构是承载不了复杂行权限管控,角色也更不能承载列的特殊化展示。
目前行业的做法是提供行列级数据权规则配置,把规则当成类似权限点配置赋予某个角色或者某个用户。
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-quanxxtjgzysjyyds-a9c99d87-c5c2-47e7-9c14-99eb536d7789.jpg)
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-quanxxtjgzysjyyds-771379da-f2d0-4459-bfcd-8f67661a6694.jpg)
## 用户管理系统权限设计中的更多实践细节
### 1.超级管理员
超级管理员是用来启动系统,配置系统的账号。这个账号应该在配置好系统,创建管理员之后被隐藏起来。超级管理员账号拥有系统中全部权限,可穿梭查看各部门数据,如果使用不恰当,是系统管理的安全隐患。
### 2.互斥角色如何处理
当用户已经有用的角色和即将添加的角色互相互斥时,应该在添加新角色时,提示管理员因角色互斥的原因,无法进行新角色添加。如需添加,要先撤销掉前一个角色,再添加新角色。
### 3.用户管理权限系统设计一定要简单清晰
在设计权限系统之处,一定要理清思路,一切从简,能不增加的多余角色和权限逻辑,就一定不要增加。因为随着公司业务的扩大,权限和角色也会随之增多,如果初期设计思路不严谨,那么权限系统会随着业务的扩大而无限混乱下去,此时再来整理权限,已经太晚了。所以初期设计就一定要条理清晰,简单明了,能避免后续非常多不必要的麻烦。
### 4.无权提示页
有时员工 A 会直接给员工 B 分享他当下正在操作的页面,但有可能员工 B 无权查看。此时我们应该在这里考虑添加「无权提示页」,避免粗暴的 404 页面让员工 B 以为是系统出错了。
* * *
没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。
**推荐阅读**
- [新一代开源免费的终端工具,太酷了](https://mp.weixin.qq.com/s/2IUe50xBhuEWKDzARVd51A)
- [最大成就,拿到一等奖学金](https://mp.weixin.qq.com/s/3lqp4x1B5LI1hNjWAi6v1g)
- [银行开发太安逸,奋发图强要跳槽](https://mp.weixin.qq.com/s/ZeA-mEyMkEeSHRtd8Pob9A)
- [这个大专生,强的离谱!](https://mp.weixin.qq.com/s/fNMhpER0tp5RO5TGcgALMQ)
- [一怒之下,退伍转码](https://mp.weixin.qq.com/s/IEEkWiI9iN4MEhoHvrTgcg)
- [没必要为实习碰的头破血流](https://mp.weixin.qq.com/s/KxUMq2YmlIBMbAeRwUm8JA)
- [网站挣了 200 美刀后的感触](https://mp.weixin.qq.com/s/PxgZkuA_SnAgG7xfwlKLgw)
- [在 IDEA 里下五子棋不过分吧?](https://mp.weixin.qq.com/s/R13FkPipfEMKjqNaCL3UoA)
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-quanxxtjgzysjyyds-33afdc45-d78b-46e0-91c2-1107161496e9.jpg)
>转载链接:[https://mp.weixin.qq.com/s/FnBgM4m593e8M_UkJ_RWSg](https://mp.weixin.qq.com/s/FnBgM4m593e8M_UkJ_RWSg),出处:macrozheng,整理:沉默王二
---
title: 如何组装一个注册中心?
shortTitle: 如何组装一个注册中心?
author: 小楼MrRoshi
category:
- 微信公众号
---
>[二哥的编程星球](https://mp.weixin.qq.com/s/3RVsFZ17F0JzoHCLKbQgGw)已经有 **560 多名** 小伙伴加入了,如果你也需要一个良好的学习氛围,[戳链接](https://mp.weixin.qq.com/s/3RVsFZ17F0JzoHCLKbQgGw)加入我们吧!这是一个 Java 学习指南 + 编程实战的私密圈子,你可以向二哥提问、帮你制定学习计划、跟着二哥一起做项目、刷力扣,冲冲冲。
今天来聊一聊注册中心。标题本来想叫《如何设计一个注册中心》,但网上已经有好多类似标题的文章了。所以打算另辟蹊径,换个角度,如何**组装**一个注册中心。
**组装**意味着不必从0开始造轮子,这也比较符合许多公司对待自研基础组件的态度。
**知道如何组装一个注册中心有什么用呢?**
第一可以**更深入理解注册中心**。以我个人经历来说,注册中心的第一印象就是Dubbo的`Zookeeper`(以下简称zk),后来逐渐深入,学会了如何去zk上查看Dubbo注册的数据,并能排查一些问题。后来了解了Nacos,才发现,原来注册中心还可以如此简单,再后来一直从事服务发现相关工作,对一些细枝末节也有了一些新的理解。
第二可以学习**技术选型的方法**,注册中心中的每个模块,都会在不同的需求下有不同的选择,最终的选择取决于对需求的把握以及技术视野,但这两项是内功,一时半会练不成,学个选型的方法还是可以的。
本文打算从需求分析开始,一步步拆解各个模块,整个注册中心以一种**如无必要,勿增实体**的原则进行组装,但也不会是个玩具,向**生产可用**对齐。
当然在实际项目中,不建议重复造轮子,尽量用现成的解决方案,所以本文仅供学习参考。
### 需求分析
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-ruhzzygzczx-f40c0f66-433e-43f3-bafa-cc310f5fadd1.jpg)
本文的注册中心需求很简单,就三点:**可注册****能发现****高可用**
服务的注册和发现是注册中心的基本功能,高可用则是生产环境的基本要求,如果高可用不要求,那本文可讲解的内容就很少,上图中的高可用标注只是个示意,高可用在很多方面都有体现。
至于其他花里胡哨的功能,我们暂且不表。
我们这里介绍三个角色,后文以此为基础:
* 提供者(Provider):服务的提供方(被调用方)
* 消费者(Consumer):服务的消费方(调用方)
* 注册中心(Registry):本文主角,服务提供列表、消费关系等数据的存储方
### 接口定义
注册中心和客户端(SDK)的交互接口有三个:
* 注册(register),将服务提供方注册到注册中心
* 注销(unregister),将注册的服务从注册中心中删除
* 订阅(subscribe),服务消费方订阅需要的服务,订阅后提供方有变更将通知到对应的消费方
注册、注销可以是服务提供方的进程发起,也可以是其他的旁路程序辅助发起,比如发布系统在发布一台机器完成后,可调用注册接口,将其注册到注册中心,注销也是类似流程,但这种方式并不多见,而且如果只考虑实现一个注册中心,必然是可以单独运行的,所以通常注册、注销由提供方进程负责。
有了这三个接口,我们该如何去定义接口呢?注册服务到底有哪些字段需要注册?订阅需要传什么字段?以什么序列化方式?用什么协议传输?
这些问题接踵而来,我觉得我们先不急着去做选择,先**看看这个领域有没有相关标准**,如果有就参考或者直接按照标准实现,如果没有,再来分析每一点的选择。
服务发现还真有一套标准,**但又不完全有**。它叫`OpenSergo`,它其实是服务治理的一套标准,包含了服务发现:
> OpenSergo 是一套开放、通用的、面向分布式服务架构、覆盖全链路异构化生态的服务治理标准,基于业界服务治理场景与实践形成通用标准规范。OpenSergo 的最大特点就是**以统一的一套配置/DSL/协议定义服务治理规则,面向多语言异构化架构,做到全链路生态覆盖**。无论微服务的语言是 Java, Go, Node.js 还是其它语言,无论是标准微服务还是 Mesh 接入,从网关到微服务,从数据库到缓存,从服务注册发现到配置,开发者都可以通过同一套 OpenSergo CRD 标准配置针对每一层进行统一的治理管控,而无需关注各框架、语言的差异点,降低异构化、全链路服务治理管控的复杂度。
>
> 官网:https://opensergo.io/
我们需要的服务注册与发现也被纳入其中:
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-ruhzzygzczx-4cdaa4cc-c4a7-4f5b-8bc8-da38424f277b.jpg)
说有但也不是完全有是因为这个标准还在建设中,服务发现相关的标准在写这篇文章的时候还没有给出。
既然没有标准,可以结合现有的系统以及经验来定义,这里我用**json**的序列化方式给出,以下为笔者的总结,不能囊括所有情形,需要时根据业务适当做一些调整:
1. 服务注册入参
```
{
  "application":"provider_test", // 应用名
  "protocol":"http", // 协议
  "addr":"127.0.0.1:8080", // 提供方的地址
  "meta":{ // 携带的元数据,以下三个为示例
    "cluster":"small",
    "idc":"shanghai",
    "tag":"read"
  }
}
```
2. 服务订阅入参
```
{
    "subscribes":[
        {
            "provider":"test_provider1", // 订阅的应用名
            "protocol":"http", // 订阅的协议
            "meta":{ // 携带的元数据,以下为示例
                "cluster":"small",
                "idc":"shanghai",
                "tag":"read"
            }
        },
        {
            "provider":"test_provider2",
            "protocol":"http",
            "meta":{
                "cluster":"small",
                "tag":"read"
            }
        }
    ]
}
```
3. 服务发现出参
```
{
    "version":"23des4f", // 版本
    "endpoints":[ // 实例
        {
            "application":"provider_test",
            "protocol":"http",
            "addr":"127.0.0.1:8080",
            "meta":{
                "cluster":"small",
                "idc":"shanghai",
                "tag":"read"
            }
        },
        {
            "application":"provider_test",
            "protocol":"http",
            "addr":"127.0.0.2:8080",
            "meta":{
                "cluster":"small",
                "idc":"shanghai",
                "tag":"read"
            }
        }
    ]
}
```
### 变更推送 & 服务健康检查
有了定义,我们如何选择序列化方式?选择序列化方式有两个重要参考点:
* 语言的适配程度,比如 json 几乎所有编程语言都能适配。除非能非常确定5-10年内不会有多语言的需求,否则我还是非常建议你选择一个跨语言的序列化协议
* 性能,序列化的性能包含了两层意思,序列化的速度(cpu消耗)与序列化后的体积,设想一个场景,一个服务被非常多的应用订阅,如果此时该服务发布,则会触发非常庞大的推送事件,此时注册中心的cpu和网络则有可能被打满,导致服务不可用
至于编程语言的选择,我觉得应该更加偏向团队对语言的掌握,以能hold住为最主要,这点没什么好说的,一般也只会在 Java / Go 中去选,很少见用其他语言实现的注册中心。
对于注册、订阅接口,无论是基于TCP的自定义私有协议,还是用HTTP协议,甚至基于HTTP2的gRPC我觉得都可以。
但变更推送这个技术点的实现,有多种实现方式:
1. 定时轮询,每隔一段时间向注册中心请求查询订阅的服务提供列表
2. 长轮询,向注册中心查询订阅的服务提供列表,如果列表较上次没有变化,则服务端hold住请求,等待有变化或者超时(较长时间)才返回
3. UDP推送,服务列表有变化时通过UDP将事件通知给客户端,但UDP推送不一定可靠,可能会丢失、乱序,故要配合定时轮询(较长时间间隔)来作为一个兜底
4. TCP长连接推送,客户端与注册中心建立一个TCP长连接,有变更时推送给客户端
从实现的难易、实时性、资源消耗三个方面来比较这四种实现方式:
|实现难易|实时性|资源消耗|备注|
---|---|---|---|---|
定时轮询|简单|低|高|实时性越高,资源消耗越多|
长轮询|中等|高|中等|服务端hold住很多请求|
UDP推送|中等|高|低|推送可能丢失,需要配合定时轮询(间隔较长)|
TCP长连接推送|中等|高|中等|服务端需要保持很多长连接|
似乎我们不好抉择到底使用哪种方式来做推送,但以我自己的经验来看,定时轮询应该首先被排除,因为即便是一个初具规模的公司,定时轮询的消耗也是巨大的,更何况这种消耗随着实时性以及服务的规模日渐庞大,最后变得不可维护。
剩下三种方案都可以选择,我们可以继续结合服务节点的健康检查来综合判断。
服务启动时注册到注册中心,当服务停止时,从注册中心摘除,通常摘除会借助劫持`kill`信号实现,如果是Java则有封装好的`ShutdownHook`,当进程被 kill 时,触发劫持逻辑,从注册中心摘除,实现优雅退出。
但事情不总是如预期,如果有人执行了`kill -9`强制杀死进程,或者机器出现硬件故障,会导致提供者还在注册中心,但已无法提供服务。
此时需要一种**健康检查机制**来确保服务宕机时,消费者能正常感知,从而切走流量,保证线上服务的稳定性。
关于健康检查机制,在之前的文章[《服务探活的五种方式》](https://mp.weixin.qq.com/s?__biz=MzI5NjE2MDQwNg==&mid=2247486141&idx=1&sn=074744ce828516f98d3fa0ea725693ac&scene=21#wechat_redirect)中有专门的总结,这里也列举一下,以便做出正确的选择:
|优点|缺点|
---|---|---|
消费者被动探活|不依赖注册中心|需在服务调用处实现逻辑;用真实流量探测,可能会有滞后性|
消费者主动探活|不依赖注册中心|需在服务调用处实现逻辑|
提供者上报心跳|对调用无入侵|需消费者服务发现模块实现逻辑,服务端处理心跳消耗资源大|
注册中心主动探测|对客户端无要求|资源消耗大,实时性不高|
提供者与注册中心会话保持|实时性好,资源消耗少|与注册中心需保持TCP长连接|
我们暂时无法控制调用动作,故而前2项依赖消费者的方案排除,提供者上报心跳如果规模较小还好,上点规模也会不堪重任,这点在Nacos中就体现了,Nacos 1.x版本使用提供者上报心跳的方式保持服务健康状态,由于每次上报健康状态都需要写入数据(最后健康检查时间),故对资源的消耗是非常大的,所以Nacos 2.0版本后就改为了长连接会话保持健康状态。
所以健康检查我个人比较倾向最后两种方案:**注册中心主动探测****提供者与注册中心会话保持**的方式。
结合上述变更推送,我们发现**如果实现了长连接,好处将很多**,很多情况下,一个服务既是消费者,又是提供者,此时一条TCP长连接可以解决推送和健康检查,甚至在注册注销接口的实现,我们也可以复用这条连接,可谓是一石三鸟。
### 长连接技术选型
长连接的技术选型,在《Nacos架构与原理》这本电子书中有有详细的介绍,我觉得这部分堪称技术选型的典范,我们参考下,本节内容大量参考《Nacos架构与原理》,如有雷同,那便是真是雷同。
首先是长连接的核心诉求:
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-ruhzzygzczx-d376a064-1b0c-4f45-8d72-8f0554e8ef9e.jpg)
> 图来自《Nacos架构与原理》
* 低成本快速感知:客户端需要在服务端不可用时尽快地切换到新的服务节点,降低不可用时间
* 客户端正常重启:客户端主动关闭连接,服务端实时感知
* 服务端正常重启 : 服务端主动关闭连接,客户端实时感知
* 防抖:网络短暂不可用,客户端需要能接受短暂网络抖动,需要一定重试机制,防止集群抖动,超过阈值后需要自动切换 server,但要防止请求风暴
* 断网:断网场景下,以合理的频率进行重试,断网结束时可以快速重连恢复
* 低成本多语言实现:在客户端层面要尽可能多的支持多语言,降低多 语言实现成本
* 开源社区:文档,开源社区活跃度,使用用户数等,面向未来是否有足够的支持度
据此,我们可选的轮子有:
|gRPC|Rsocket|Netty|Mina|
---|---|---|---|---|
客户端感知断连|基于 stream 流 error complete 事件可实现|支持|支持|支持|
服务端感知断连|支持|支持|支持|支持|
心跳保活|应用层自定义,ping-pong 消息|自定义 kee palive frame|TCP+ 自定义|自定义 kee palive filter|
多语言支持|强|一般|只Java|只Java|
我比较倾向gRPC,而且gRPC的社区活跃度要强于Rsocket。
### 数据存储
注册中心数据存储方案,大致可分为2类:
* 利用第三方组件完成,如Mysql、Redis等,好处是有现成的水平扩容方案,稳定性强;坏处是架构变得复杂
* 利用注册中心本身来存储数据,好处是无需引入额外组件;坏处是需要解决稳定性问题
第一种方案我们不必多说,第二种方案中最关键的就是解决数据在注册中心各节点之间的同步,因为在数据存储在注册中心本身节点上,如果是单机,机器故障或者挂掉,数据存在丢失风险,所以必须得有副本。
数据不能丢失,这点必须要保证,否则稳定性就无从谈起了。保证数据不丢失怎么理解?在客户端向注册中心发起注册请求后,收到正常的响应,这就意味着数据存储了起来,除非所有注册中心节点故障,否则数据就一定要存在。
如下图,比如提供者往一个节点注册数据后,正常响应,但是数据同步是异步的,在同步完成前,nodeA节点就挂掉,则这条注册数据就丢失了。
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-ruhzzygzczx-a139f210-09c5-492b-88ab-639cb7bc79ad.jpg)
所以,我们要极力避免这种情况。
而一致性算法(如raft)就解决了这个问题,一致性算法能保证大部分节点是正常的情况下,能对外提供一致的数据服务,但牺牲了性能和可用性,raft算法在选主时便不能对外提供服务。
有没有退而求其次的算法呢?还真有,像Nacos、Eureka提供的AP模型,他们的核心点在于客户端可以recover数据,也就是注册中心追求最终一致性,如果某些数据丢失,服务提供方是可以重新将数据注册上来。
比如我们将提供方与注册中心之间设计为长连接,提供方注册服务后,连接的节点还没来得及将数据同步到其他节点就挂了,此时提供方的连接也会断开,当连接重新建立时,服务提供方可以重新注册,恢复注册中心的数据。
对于注册中心选用AP、还是CP模型,业界早有争论,但也基本达成了共识,AP要优于CP,因为数据不一致总比不可用要好吧?你说是不是。
### 高可用
其实高可用的设计散落在各个细节点,如上文提到的**数据存储**,其基本要求就是高可用。除此之外,我们的设计也都必须是面向失败的设计。
假设我们的服务器会全部挂掉,怎样才能保持服务间的调用不受影响?
通常注册中心不侵入服务调用,而是在内存(或磁盘)中缓存一份服务列表,当注册中心完全挂了,大不了这份缓存不再更新,但也不影响现有的服务调用,但新应用启动就会受到影响。
### 总结
本文内容略多,用一幅图来总结:
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-ruhzzygzczx-790cdf94-a0ce-4461-9af5-b850e4de2bfc.jpg)
组装一个线上可用的注册中心最小集,从需求分析出发,每一步都有许多选择,本文通过一些核心的技术选型来描绘出一个大致蓝图,剩下的工作就是用代码将这些组装起来。
* * *
没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。
**推荐阅读**
- [新一代开源免费的终端工具,太酷了](https://mp.weixin.qq.com/s/2IUe50xBhuEWKDzARVd51A)
- [最大成就,拿到一等奖学金](https://mp.weixin.qq.com/s/3lqp4x1B5LI1hNjWAi6v1g)
- [银行开发太安逸,奋发图强要跳槽](https://mp.weixin.qq.com/s/ZeA-mEyMkEeSHRtd8Pob9A)
- [这个大专生,强的离谱!](https://mp.weixin.qq.com/s/fNMhpER0tp5RO5TGcgALMQ)
- [一怒之下,退伍转码](https://mp.weixin.qq.com/s/IEEkWiI9iN4MEhoHvrTgcg)
- [没必要为实习碰的头破血流](https://mp.weixin.qq.com/s/KxUMq2YmlIBMbAeRwUm8JA)
- [网站挣了 200 美刀后的感触](https://mp.weixin.qq.com/s/PxgZkuA_SnAgG7xfwlKLgw)
- [在 IDEA 里下五子棋不过分吧?](https://mp.weixin.qq.com/s/R13FkPipfEMKjqNaCL3UoA)
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/weixin-quanxxtjgzysjyyds-33afdc45-d78b-46e0-91c2-1107161496e9.jpg)
>转载链接:[https://mp.weixin.qq.com/s/eyTe0iTvMgW-T4haBMXYhw](https://mp.weixin.qq.com/s/eyTe0iTvMgW-T4haBMXYhw),出处:JavaGuide,整理:沉默王二
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册