设计分布式锁要注意的问题

分布式锁应该具备的条件:

1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用的获取锁与释放锁;
3、高性能的获取锁与释放锁;
4、具备可重入特性;
5、具备锁失效机制,防止死锁;
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

实现方式(方案):
1、基于数据库(mysql,sqlserver等)
2、基于redis
3、基于zookeeper

方案比较:

1、从理解的难易程度角度(从低到高)
数据库 > 缓存 > Zookeeper
2、从实现的复杂性角度(从低到高)
Zookeeper >= 缓存 > 数据库
3、从性能角度(从高到低)
缓存 > Zookeeper >= 数据库
4、从可靠性角度(从高到低)
Zookeeper > 缓存 > 数据库

面试问题整理

技术类:
1、redis如何水平扩展(一致性哈希)
2、parallelStream使用中的问题(线程安全,聚合与否,共享线程池问题)
3、dubbo的实现原理,调用过程,dubbo异步调用实现
4、future原理
5、jvm加载过程(校验,加载,空间分配,静态变量,静态方法等)
6、双亲委派机制
7、线程池常用核心参数,以及设置原因(IO密集型线程,CPU密集型线程)
8、redis为什么单线程,如何保证效率的(单线程只是用来连接)
9、redis的一些命令
10、redis的数据类型
11、jstack,jmap等线上排查问题工具,用法
12、Linux常用命令 (z)grep ,cat ,sed ,awk ,wc(统计),去重等
13、如何查看异常挂掉的进程日志
14、数据库事务 隔离级别,默认隔离级别,实现原理(mvvp)
15、数据库索引 主键索引,单列索引,组合索引,唯一索引(聚簇索引),覆盖索引;全文索引(非聚簇索引);命中逻辑,结构(B+树,适合区间查询,只在叶子节点存有数据,单节点能存更多数据,减少IO)
16、锁:乐观锁,悲观锁,各种锁,以及不同应用(如JAVA,MySQL)中的实现喝可用库
17、数组下标,为什么从0开始?数组的地址真的是连续的嘛?数组在栈还是堆上?(栈,jvm内存模型)多个进程共享的时候,如何保证数组在内存中的地址是连续的?
18、hashmap的推演及变形问题
19、spring 事务(@Transanctional)的实现原理
20、mysql事务索引,mysql非唯一索引条件下update的加锁问题
21、redis高并发多操作实现事务方案(lua脚本)

方案类:
1、分布式锁
2、幂等(最终一致性)
3、128位数字加减问题(数据结构如何设计,用什么基础类型,位数是否足够,怎么处理符号位)
4、计算10000或者更大数字的阶乘问题(算法,溢出,如何判断溢出)
5、实现手机后四位查找功能
6、

技能:
1、load飙高的常见排查思路

来自 Google 官方的代码评审最佳实践-摘录

  • 文章地址
  • CR的标准
  • 在 CR 中要看些什么
  • 如何浏览这次改动(CL)
  • Review 的速度
  • CR 要多快?
  • 速度 vs 中断
  • 快速回应

文章地址

原文地址:https://mp.weixin.qq.com/s/zIiDk7rcIKgvqh0YAvQPaA

英文地址:https://github.com/google/eng-practices/

CR的标准

  • Reviewer 有责任保证CL的质量,作为Review的代码的Owner
  • Reviewer不应追求完美,而应追求持续改进
  • CR要具有指导意义
  • 代码风格应该与现有的一致。如果项目没有统一风格,那就接受作者的风格
  • 解决冲突难以达成共识时,需要面对面或者拉起更大的团队讨论,带上Leader

在 CR 中要看些什么

  • 代码经过完善的设计
  • 功能性对于使用者们是好的
  • 对于任何UI改动要合理且好看
  • 任何并行编程的实现是安全
  • 代码不应该复杂超过原本所必须的
  • 开发者不该实现一个现在不用而未来可能需要的功能
  • 代码有适当的单元测试
  • 测试经过完善的设计
  • 开发者对于每样东西有使用清晰、明了的命名
  • 注释要清楚且有用,并只用来解释why而非what
  • 代码有适当的写下文件(一般在g3doc)
  • 代码风格符合style guide
  • 确保你查看被要求review的每一行代码、确认上下文、确保你正在改善代码质量,并赞扬开发人员所做的好事与优点吧!

如何浏览这次改动(CL)

步骤1: 用宏观的角度来看待改动,查看CL描述以及它做什么步骤2: 检查CL主要的部分步骤3: 用合理的顺序看CL 其余的改动

Review 的速度

为什么 Review 速度要快?

在Google我们优化开发团队 共同生产产品的速度,而不是优化个人开发的速度。个人的开发速度很重要,但它不如整个团队的开发速度重要。

CR 如果很慢,则:

①、团队整体的速度下降。

②、开发人员开始抗议 cr。

③、代码质量会收到影响。review 慢时,开发者提交的压力大。

CR 要多快?

如果你并没有处于需要专注工作的时候,那么应该在 review 完后尽快修改。回复最长的极限是一个工作日。

速度 vs 中断

我们可以在投入到处理他人给的review评论之前,找个适当的时机点来进行cr。这有可能是当你的当前开发任务完成后、午餐、刚从会议脱身或从微型厨房回来等等。

快速回应

个人回应评论的速度,比起让整个 cr 过程快速结束更重要。

 若在整个过程中能快速获得来自 reviewer 的回应,大大减轻开发者对缓慢 cr 过程的挫败感。

 reviewer员要花足够的时间来进行review,确保他们给出的LGTM,意味着“此代码符合我们的标准”。

 理想的个人的回应速度还是越快越好。

算法题题库

序号题号题名难度URL标签备注
17171比特与2比特字符简单https://leetcode-cn.com/problems/1-bit-and-2-bit-characters/数组简单,使用过
250Pow(x, n)中等https://leetcode-cn.com/problems/powx-n数学,二分查找简单,使用过 
469x 的平方根简单https://leetcode-cn.com/problems/sqrtx/数学,二分查找简单,使用过注意边界,不能用库函数
51006笨阶乘中等https://leetcode-cn.com/problems/clumsy-factorial数学简单,使用过
6984不含 AAA 或 BBB 的字符串中等https://leetcode-cn.com/problems/string-without-aaa-or-bbb贪心算法中等,使用过 
8299猜数字游戏中等https://leetcode-cn.com/problems/bulls-and-cows哈希表中等,使用过 
14849到最近的人的最大距离简单https://leetcode-cn.com/problems/maximize-distance-to-closest-person/数组简单,使用过 
18236二叉树的最近公共祖先中等https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree中等 
19111二叉树的最小深度简单https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/树,深度优先搜索,广度优先搜索简单
24560和为K的子数组中等https://leetcode-cn.com/problems/subarray-sum-equals-k数组,哈希表简单,使用过
4961旋转链表中等https://leetcode-cn.com/problems/rotate-list链表,双指针简单
7354螺旋矩阵中等https://leetcode-cn.com/problems/spiral-matrix/数组中等,使用过
7471简化路径中等https://leetcode-cn.com/problems/simplify-path/submissions/栈,字符串简单,使用过
51468验证IP地址中等https://leetcode-cn.com/problems/validate-ip-address字符串简单,使用过
56678有效的括号字符串中等https://leetcode-cn.com/problems/valid-parenthesis-string字符串中等,使用过 
64459重复的子字符串简单https://leetcode-cn.com/problems/repeated-substring-pattern/字符串简单,使用过
2393复原IP地址中等https://leetcode-cn.com/problems/restore-ip-addresses字符串,回溯算法中等 
3222括号生成中等https://leetcode-cn.com/problems/generate-parentheses字符串,回溯算法中等
4046全排列中等https://leetcode-cn.com/problems/permutations回溯算法简单,使用过
6677组合中等https://leetcode-cn.com/problems/combinations回溯算法简单
63149直线上最多的点数困难https://leetcode-cn.com/problems/max-points-on-a-line/哈希表,数学中等,使用过
483无重复字符的最长子串中等https://leetcode-cn.com/problems/longest-substring-without-repeating-characters哈希表,双指针,字符串,Slinding Window 
9372超级次方中等https://leetcode-cn.com/problems/super-pow数学
57367有效的完全平方数简单https://leetcode-cn.com/problems/valid-perfect-square/数学,二分查找简单
44495提莫攻击中等https://leetcode-cn.com/problems/teemo-attacking数组简单
41746使用最小花费爬楼梯简单https://leetcode-cn.com/problems/min-cost-climbing-stairs/数组,动态规划 
4333搜索旋转排序数组中等https://leetcode-cn.com/problems/search-in-rotated-sorted-array数组,二分查找简单
6739组合总和中等https://leetcode-cn.com/problems/combination-sum数组,回溯算法简单
68670最大交换中等https://leetcode-cn.com/problems/maximum-swap数组,数学中等,使用过
50287寻找重复数中等https://leetcode-cn.com/problems/find-the-duplicate-number数组,双指针,二分查找 
60209长度最小的子数组中等https://leetcode-cn.com/problems/minimum-size-subarray-sum数组,双指针,二分查找 
28134加油站中等https://leetcode-cn.com/problems/gas-station贪心算法 
38321拼接最大数困难https://leetcode-cn.com/problems/create-maximum-number/贪心算法,动态规划 
26991坏了的计算器中等https://leetcode-cn.com/problems/broken-calculator贪心算法,数学中等,使用过
291053交换一次的先前排列中等https://leetcode-cn.com/problems/previous-permutation-with-one-swap贪心算法,数组 
4555跳跃游戏中等https://leetcode-cn.com/problems/jump-game贪心算法,数组中等 
65784字母大小写全排列简单https://leetcode-cn.com/problems/letter-case-permutation/位运算,回溯算法 
39169求众数简单https://leetcode-cn.com/problems/majority-element/位运算,数组,分治算法简单,使用过
52331验证二叉树的前序序列化中等https://leetcode-cn.com/problems/verify-preorder-serialization-of-a-binary-tree中等,使用过
341019链表中的下一个更大节点中等https://leetcode-cn.com/problems/next-greater-node-in-linked-list栈,链表 
5520有效的括号简单https://leetcode-cn.com/problems/valid-parentheses/栈,字符串简单 
37148排序链表中等https://leetcode-cn.com/problems/sort-list/排序,链表 
35518零钱兑换 II中等https://leetcode-cn.com/problems/coin-change-2/
54794有效的井字游戏中等https://leetcode-cn.com/problems/valid-tic-tac-toe-state递归,数学 
3670爬楼梯简单https://leetcode-cn.com/problems/climbing-stairs/动态规划中等
62650只有两个键的键盘中等https://leetcode-cn.com/problems/2-keys-keyboard动态规划 
71673最长递增子序列的个数中等https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence动态规划使用过 
46691贴纸拼词困难https://leetcode-cn.com/problems/stickers-to-spell-word/动态规划,回溯算法 
42215数组中的第K个最大元素中等https://leetcode-cn.com/problems/kth-largest-element-in-an-array堆,分治算法简单,使用过 
311054距离相等的条形码中等https://leetcode-cn.com/problems/distant-barcodes堆,排序 
70973最接近原点的 K 个点中等https://leetcode-cn.com/problems/k-closest-points-to-origin堆,排序,分治算法 
691046最后一块石头的重量简单https://leetcode-cn.com/problems/last-stone-weight/堆,贪心算法简单 
17354俄罗斯套娃信封问题困难https://leetcode-cn.com/problems/russian-doll-envelopes/二分查找,动态规划
11127单词接龙中等https://leetcode-cn.com/problems/word-ladder/广度优先搜索 
15491递增子序列中等https://leetcode-cn.com/problems/increasing-subsequences深度优先搜索 
13200岛屿数量中等https://leetcode-cn.com/problems/number-of-islands深度优先搜索,广度优先搜索,并查集中等,使用过 
58109有序链表转换二叉搜索树中等https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree深度优先搜索,链表 
12695岛屿的最大面积中等https://leetcode-cn.com/problems/max-area-of-island深度优先搜索,数组中等
5398验证二叉搜索树中等https://leetcode-cn.com/problems/validate-binary-search-tree树,深度优先搜索简单
16101对称二叉树简单https://leetcode-cn.com/problems/symmetric-tree/树,深度优先搜索,广度优先搜索简单
61513找树左下角的值中等https://leetcode-cn.com/problems/find-bottom-left-tree-value树,深度优先搜索,广度优先搜索简单,使用过
221043分隔数组以得到最大和中等https://leetcode-cn.com/problems/partition-array-for-maximum-sum 
3715Range 模块困难https://leetcode-cn.com/problems/range-module/线段树,Ordered Map 
47732我的日程安排表 III困难https://leetcode-cn.com/problems/my-calendar-iii/线段树,Ordered Map 
75564寻找最近的回文数困难https://leetcode-cn.com/problems/find-the-closest-palindrome/字符串困难
76102二叉树的层次遍历中等https://leetcode-cn.com/problems/binary-tree-level-order-traversal/树、广度优先搜索简单
7712整数转罗马数字中等https://leetcode-cn.com/problems/integer-to-roman/数组,字符串简单
7836有效的数独中等https://leetcode-cn.com/problems/valid-sudoku/哈希表中等
79845数组中的最长山脉中等https://leetcode-cn.com/problems/longest-mountain-in-array/双指针中等
8015三数之和中等https://leetcode-cn.com/problems/3sum/数组,双指针

后端开发能力模型(后端)v1.0

能力模型(后端)
   T3T4T5T6T7T8
岗位职责业务能力业务梳理和设计Xa. 能清晰的梳理和描述完整的业务流程,理解并指导开发工作b. 能指出明显的业务流程错误和不足a. 能发现业务流程中较深入的问题a. 帮助业务方设计合理的流程b. 用逻辑和数字作为辅助工具a. 使用数字和逻辑主动发现流程问题b. 主动推进流程的设计和改进 
开发能力




需求分析a. 能理解并分解小规模的需求并指导开发小规模 < 3pda. 能理解并分解中等规模的需求并指导开发中规模 < 10pda. 能理解并分解大规模的需求并指导开发大规模 < 30pd经验要求. 领导3个以上 10~20pd级别的项目a. 能理解并分解超大规模需求并指导开发超大规模 >= 30经验要求. 领导3个以上超过30pd的项目a. 有能力领导超过100pd的需求经验要求. 领导过5个以上超过50pd的项目 
架构设计XXa. 能理解架构设计原理b. 能理解自己参与的系统架构c. 能设计小规模的系统架构d. 能在设计中考虑并处理高并发问题/大数据量问题a. 深入理解架构设计和常用模式b. 能进行大规模的系统架构设计或大规模系统重构经验要求. 参与3个以上部门内系统架构的设计与实现,并至少负责其中1个子系统的设计与实现a. 能主导设计部门级系统架构b. 能参与规划公司的架构模型和演进方向c. 在设计中充分考虑并平衡 业务要求/可扩展性/并行开发能力/高并发要求/大数据量要求/高可恢复性 等经验要求. 领导3个以上部门内架构的设计与实现经验要求. 参与制定过部门级的年度技术规划a. 能主导设计公司级别的系统架构经验要求. 领导2个以上跨部门系统架构的设计与实现经验要求. 参与制定过公司级的年度技术规划
详细设计和编码a. 能理解设计的目的和常用设计模式,范式/反范式b. 能够使用UML图/ER图等工具c. 规避并处理常见的边界问题/异常问题等d. 代码严谨并符合代码规范e. 能够进行简单的领域建模a. 熟练掌握设计过程和工具b. 能在设计中考虑并处理幂等问题/一致性问题c. 代码高效d. 能处理基本的性能优化e. 熟练掌握常用的领域建模方法并用于实践a. 代码优雅,可扩展性强b. 掌握性能优化的常用规律和方法,能处理日常遇到的绝大多数性能问题c. 能优化领域模型或者重构a. 除个别case外完全处理日常遇到的各种技术问题和性能问题b. 具备丰富的领域建模经验,理解业务本质经验要求. 编写或抽取系统核心代码或核心结构,有效的提升开发效率和可读性经验要求. 编写过框架/中间件级别代码,能高效的处理一类业务问题 
测试a. 掌握单测和集成测试方法b. 构思工作中的测试方法并实现a. 掌握测试的常见场景和方法,能快速识别风险和影响范围,给出高效的测试方案b. 积累日常测试的工具和用例a. 设计相对长期的测试方法和测试用例,提升测试效率b. 掌握从可测试行角度出发设计和实现的能力c. 能从测试的角度出发影响详细设计和编码a. 能从测试的角度出发影响架构设计  
前端技能      
运维技能a. 能合理埋点和设置监控b. 使用shell/python分析问题c. 习惯性查看监控/日常并处理问题a. 高效的排查和解决问题b. 积累日常运维的工具和用例a. 能梳理和设计高效的监控体系/日志体系   
技术技能a. 基础过关,能熟练使用常用框架b. 知道常用组件的使用方法c. 知道常用中间件的使用方法a. 了解常用中间件的原理并用于排查问题a. 深入理解常用中间件中的至少一类b. 能够快速学习新知识技能,直接用于简单的日常应用a. 有擅长的技术,熟知其实现和原理,在团队内形成优势互补a. 是某一领域的专家,公司内解决该问题的大拿 
领导能力制定标准XX经验要求. 领导制定过团队内的标准经验要求. 参与制定过部门内的标准经验要求. 参与制定过公司级别的标准 
团队管理Xa. 能指导低级别工程师进行日常开发a. 有能力带领2-3人b. 敢于公开表扬a. 有能力带领5人以上的团队b. 了解每个人的能力,为每个人制定发展规划并实施c. 敢于维护公司的价值观并用于团队管理a. 有能力带20人以上的团队 
通用素质学习能力 a. 能快速了解任务和简单业务b. 能快速学习一个技术的用法a. 能迅速并深入理解新的业务和系统 a. 理解技术领域的最新动态并推进团队使用b. 对使用的技术有体系性的认知,在某一方面有特长并持续学习  
逻辑思维 a. 具备基本的逻辑思维能力和数字感b. 能简单的用于日常工作a. 能理解具备一定深度的逻辑链条 a. 能主动作有一定深度的逻辑思考b. 能利用手边的工具获取数据证明自己的观点a. 主动用严密的逻辑来规划工作和方案b. 具备用全面而恰当的数据证实或者证伪的能力  
沟通能力 a. 能在team内分享技术和业务,时间不少于30分钟a. 能在团队内分享技术和业务,时间不少于1个小时b. 能掌控并转换自己的表达方式以让对方听懂a. 在团队内做过有深度的分享,描述清楚并让大家听懂经验要求. 领导3个10pd以上跨团队项目的沟通工作a. 具备面试更低级别工程师的能力经验要求. 领导3个30pd以上跨团队项目的沟通工作经验要求. 做过公司级的分享经验要求. 领导5个以上超过50pd的跨团队项目的沟通工作 

后端开发技能树

atop工具

背景

1、某次收到某个应用一台机器14:32:02挂掉的报警。

2、于是一顿操作猛如虎,然后一脸懵逼:究竟是什么导致了系统挂掉!

3、于是求助了公司ops热线,发现一个好用的工具。解决了这个疑惑。这个工具就是atop。

先上一张图来加深印象:

正是从上图,得知,是有同学在14:32:02秒执行了less命名导致了系统cpu飙升。(这里先不分析为什么less会导致cpu飙升,本文主要讲解atop工具)。

现在开始介绍atop工具。

简介

 atop是一款用于监控Linux系统资源与进程的工具,它以一定的频率记录系统的运行状态,所采集的数据包含系统资源(CPU、内存、磁盘和网络)使用情况和进程运行情况,并能以日志文件的方式保存在磁盘中,服务器出现问题后,我们可获取相应的atop日志文件进行分析,其比较强大的地方是其支持我们分析数据时进行排序、视图切换、正则匹配等处理。

监控字段的含义

ATOP列:该列显示了主机名、信息采样日期和时间点

PRC列:该列显示进程整体运行情况

  1. sys、usr字段分别指示进程在内核态和用户态的运行时间
  2. #proc字段指示进程总数
  3. #zombie字段指示僵死进程的数量
  4. #exit字段指示atop采样周期期间退出的进程数量

CPU列:该列显示CPU整体(即多核CPU作为一个整体CPU资源)的使用情况,我们知道CPU可被用于执行进程、处理中断,也可处于空闲状态(空闲状态分两种,一种是活动进程等待磁盘IO导致CPU空闲,另一种是完全空闲)

  1. sys、usr字段指示CPU被用于处理进程时,进程在内核态、用户态所占CPU的时间比例
  2. irq字段指示CPU被用于处理中断的时间比例
  3. idle字段指示CPU处在完全空闲状态的时间比例
  4. wait字段指示CPU处在“进程等待磁盘IO导致CPU空闲”状态的时间比例

CPU列各个字段指示值相加结果为N00%,其中N为cpu核数。

cpu列:该列显示某一核cpu的使用情况,各字段含义可参照CPU列,各字段值相加结果为100%

CPL列:该列显示CPU负载情况

  1. avg1、avg5和avg15字段:过去1分钟、5分钟和15分钟内运行队列中的平均进程数量
  2. csw字段指示上下文交换次数
  3. intr字段指示中断发生次数

MEM列:该列指示内存的使用情况

  1. tot字段指示物理内存总量
  2. free字段指示空闲内存的大小
  3. cache字段指示用于页缓存的内存大小
  4. buff字段指示用于文件缓存的内存大小
  5. slab字段指示系统内核占用的内存大小

SWP列:该列指示交换空间的使用情况

  1. tot字段指示交换区总量
  2. free字段指示空闲交换空间大小

PAG列:该列指示虚拟内存分页情况

swin、swout字段:换入和换出内存页数

DSK列:该列指示磁盘使用情况,每一个磁盘设备对应一列,如果有sdb设备,那么增多一列DSK信息

  1. sda字段:磁盘设备标识
  2. busy字段:磁盘忙时比例
  3. read、write字段:读、写请求数量

NET列:多列NET展示了网络状况,包括传输层(TCP和UDP)、IP层以及各活动的网口信息

  1. XXXi  字段指示各层或活动网口收包数目
  2. XXXo 字段指示各层或活动网口发包数目

视图模视

1、默认视图(Generic information)

进入atop信息界面,我们看到的就是进程信息的默认视图(上图下半部分),按g键可以从其他视图跳到默认视图。以简介里的截图为例,我们可以看到PID为31085的find进程在退出前在内核模式下占用了0.45秒CPU时间,在用户模式下占用了0.10秒CPU时间,相对10分钟采样周期,CPU时间占用比例为3%,ST列表示进程状态,N表示该进程是前一个采样周期新生成的进程,E表示该进程已退出,EXC列指示进程的退出码。从进程名在“<>”符号中,我们亦可知该进程已退出。

2、内存视图(Memory consumption)

内存视图展示了进程使用内存情况,按m键可进入内存视图。如下图:

上图下半部分展示了每个进程占用的虚拟内存空间(VSIZE)、内存空间(RSIZE)大小,以及在上一个采样周期中虚拟内存和物理内存增长大小(VGROW、RGROW),MEM列指示进程所占物理内存大小。从上图的PAG列的信息,我们可以知道此时系统内存负载较高,页交换比较频繁,而且可以看出物理内存几乎完全不可用,swap分区也比较繁忙,从进程视图中VGROW和RGROW列可看出 lekker 进程占用内存量大量增长,部分进程占用的内存减少(VGROW或RGROW字段为负值),为lekker进程腾出空间。

3、命令视图(Command line)

这个对于查看具体某个命令的详细参数,很容易通过该模式下查看到。比如,我们有多个java程序,普通视图下,可能看到的只显示为java ,但通过命令模式,我们可以方便的区分出,到底是哪个java程序占用资源比较高。如下图:

4、磁盘视图

通过按键d 可以进入磁盘视图,可以查看每个进程占用IO的情况。

快捷键汇总

进入atop目录: /var/log/atop

读取atop日志文件: atop  -r  XXX

读取atop日志文件(更具时间范围): atop -r atop_20190325 -b 14:34 -e 14:35

前进翻页: t

后退翻页: T

进程列表前进翻页: ctrl + f

进程列表后退翻页: ctrl + b

按时间跳转:

 b

 Enter new time (format hh:mm):

 按hh:mm格式输入时间

进程视图:

g   —— 默认输出

m  —— 内存相关输出

d   —— 磁盘相关输出

n   —— 网络相关输出

c   —— 命令行输出

u  查看对应的用户资源使用情况

p  显示所有每个进程的所有信息占用情况(disk、mem、io)

P(大写) 正则匹配,显示所有匹配到的进程

退出atop:q

更加详细的说明:http://www.361way.com/atop/5162.html

架构师设计大纲

哪些项目必须写设计文档

DEV工作量大于等于2PD的项目必须写设计文档,建议所有项目都根据设计大纲确认一下有没有问题。

设计的坏味道

线上频繁执行SQL来修复问题

有大量的JSP后门

机制和策略不分离,任何修改都牵一发而动全身

设计大纲

按照项目研发流程设计

需求解析

分析相关方、计算收益,定义评估方法

描述背景、要解决的问题

名词及术语定义

总体设计

技术选型:涉及技术选型,需要对比候选方案的优缺点,特别要关注一个技术方案的缺点自己是否可以接受。

确定边界:系统边界、代码边界、接口边界等

确定交互流程:

  • 交互流程划分:业务交互流程、系统交互流程、模块交互流程
  • 交互流程检查点:一致性、性能
  • 策略算法相关功能:需要画出流程图

结构设计:

  • 逻辑结构:数据模型、时序模型、状态机模型;
  • 物理结构:DB;
  • 部署结构:分析依赖关系,后续要根据以来关系来撰写上线步骤

详细设计

存储设计:

  • 抽象出业务实体
  • 画出实体之间的ER关系图
  • 定义表结构,写出MySQL scheme变化
  • 考虑数据如何初始化

接口设计:

  • 接口组成:存储层接口、模块间接口、系统间接口
  • 接口应该抽象一组能力,良好的设计应该做到“机制和策略分离”,而接口应该承担分离的职责
  • 接口变化需要检查是否有兼容性问题

类图:类描述业务实体,需要描述清楚类之间的关系;类图的设计可以参考各种设计模式,

测试设计

可测性:尽量减少对环境的依赖,确定自测方法;策略算法相关的功能,需要提前想好如何获取数据、如何度量好坏;

测试范围:写明项目的影响面、确定测试范围。测试范围评估不准,会影响自测质量,进而导致联调时间偏长、QA测试时间偏长、QA测试评估不充分导致质量偏低。

测试用例:包括单元测试用例、接口测试用例、系统测试用例、面向C端的用例等

发布流程

写上线步骤:包括上线步骤、回滚步骤(必须和上线步骤相反,每一步都需要有回滚方案)、线上检查方法、应急方案(回滚或者降级);要重点检查兼容性。

上线前评估:上线时间(上线要评估对业务的影响,进而决定可行的上线时间)

阿里技术三板斧:可灰度,可监控,可回滚。

按照系统层次结构设计

存储设计

  • 抽象出业务实体
  • 画出实体之间的ER关系图
  • 定义表结构,写出MySQL scheme变化
  • 考虑数据如何初始化

API设计

构成:API可以划分为存储API、模块间API、对外API

方法:建议使用OOA、DDD等设计方法;一个良好的接口应该描述一组能力,可以尝试使用一句话来描述接口的能力。

模块设计

根据API粒度,按照服务原则,规划模块及模块关系

流程设计

  • 交互流程划分:业务交互流程、系统交互流程、模块交互流程
  • 交互流程检查点:一致性、性能
  • 策略算法相关功能:需要画出流程图

系统结构设计

  • 逻辑结构:数据模型、时序模型、状态机模型;
  • 物理结构:DB;
  • 部署结构:分析依赖关系

关键检查点和设计组成

工作量拆分

性能:

  • 存储性能:选择DB、设计表结构、设计Cache机制
  • 接口性能
  • 吞吐量
  • 数据规模

一致性:

  • 数据一致性:事务处理是否正确,是否涉及分布式数据一致性,是否需要分布式事务
  • 并发处理:锁、幂等机制等

监控

附录:设计流水账

  1. 明确交互流程:系统之间、业务、模块
  2. 交互流程关键节点:一致性、性能
  3. 底层API(对外、存储)、DB数据库设计(实体关系、scheme-SQL)
  4. 对现有系统影响范围,评估、应对方案
  5. 详细设计:接口、前端接口、类图
  6. 发布流程:兼容性、上线步骤、历史刷数
  7. 数据:增加字段如何处理,如何刷数据
  8. 用例Case(C端)
  9. 上线前前评估:吞吐量、性能、时间、数据规模
  10. 涉及状态:状态机
  11. 测试要点:TestCase、单元测试Case、自测方法、测试范围(事后用联调、QA测试状况来评估)、可测性(免环境)
  12. 业务模型、模块、层次图
  13. 工作量拆分
  14. 背景,要解决的问题
  15. 采用的代码设计模式
  16. 监控
  17. 性能(核心API)
  18. 边界:系统、代码、接口
  19. 拆出来提测文档
  20. 需求解析
  21. 逻辑结构:数据模型、时序模型、状态机模型;物理结构:DB;部署结构:依赖=>上线步骤
  22. 线上检查方法
  23. 事务:特例考虑分布式事务
  24. 数据处理方式:DB、Cache;一致性处理
  25. 并发处理:锁、幂等操作
  26. 发布考虑:监控、灰度、应急方案(回滚、降级)
  27. 技术选型:对比优缺点
  28. 数据模型
  29. 策略算法:需要画流程图
  30. 名词定义、术语
  31. 相关方、利益(收益)
  32. 问题:代码与设计不符
  33. 线上问题度量:线上执行SQL刷数据次数、JSP后门执行次数、蜂利器任务提报问题数量
  34. API类别:存储、对外、模块间
  35. API设计之前:先一句话描述

领域驱动设计在马蜂窝优惠中心重构中的实践

前言

正如领域驱动设计之父 Eric Evans 所著一书的书名所述,领域驱动设计(Domain Driven Design)是一种软件核心复杂性应对之道。

在我们解决现实业务问题时,会面对非常复杂的业务逻辑。即使是同一个事物,在多个子业务单元下代表的意思也是不完全一样的。比如「商品」这个词,在商品详情页语境中,是指「商品基本信息」;在下单页语境中,是指「购买项」;而在物流页面语境中,又变成了「被运送的货物」。

DDD 的核心思想就是让正确的领域模型发挥作用。所谓「术业有专攻」,DDD 指导软件开发人员将不同的子业务单元划分为不同的子领域,在各个子领域内部分别对事物进行建模,来应对业务的复杂性。

Part 1

重构优惠中心的背景

我们在实际的开发过程中都遇到过这种情况,最初因为业务逻辑比较单一,为了快速实现功能, 以及对成本、风险等因素的综合考虑,我们会为业务统一创建一个大的模型,各个模块都使用这同一个模型。但随着业务的发展,各子领域的逻辑越来越复杂,对这个大模型的修改就会变成一种灾难,有时明明是要改一个 A 子领域的逻辑,却莫名其妙影响到了 B 或者 C 子领域的线上功能。

优惠中心就是一个例子。优惠中心主要负责马蜂窝各业务线商品的优惠活动管理,以及计算不同用户的优惠结果。「商品管理」和「优惠管理」作为两个不同的业务单元,在初期被设计为共用一个商品模型,由商品模块统一管理。

图1 :初期商品模型

出现的问题

随着业务的发展,优惠的形式不断推陈出新,业务形态逐渐多样,业务方的需求也越来越个性化,导致后期的优惠中心无论从功能上还是系统上都出现了一些具体的问题:

1. 功能上来说,不够灵活

优惠信息是作为商品信息的一个属性在商品管理模块配置的。比如为了引导用户使用 App 需要设置 A 类型优惠,就通过在商品信息的编辑页面增加一个 A 类型优惠配置项实现;如果某个商品的 A 类型优惠需要在 0:00 分生效,业务同学就必须在电脑前等到 0:00 更新商品信息来上线优惠活动。

另外,如果想要创建针对所有商品都适用的优惠,按照之前的模式,所有的商品都要设置一遍,这几乎是不可接受的。

2. 从系统层面看,不易扩展

优惠信息存储在商品信息中,优惠信息是通过商品管理模块的接口输出的。如果要新增一种优惠类型,商品信息相关的表就要增加字段,商品的表会越来越大;如果要迭代一个优惠的逻辑,就有可能影响到商品管理模块的功能。

3. 不利于迭代

由于优惠信息仅仅作为商品的一个属性,没有自己的生命周期,所以很难去统计某一次设置的优惠的投入产出比,从而指导后续的功能优化。

重构优惠中心的预期

  • 系统层面上,要把优惠相关的业务逻辑独立出来,单独设计和实现;
  • 应用层面上,优惠中心会有自己的独立后台,负责管理优惠活动;也会有独立的优惠计算接口,负责 C 端用户使用优惠时的计算。

Part 2

为什么选择 DDD

避免贫血模型

基于传统的 MVC 架构开发功能的时候,Model 层本质上是一个 DAO 层,业务逻辑通常会封装在 Service 层,然后 Controller 通过调用 Service 层来完成对外的功能。这种模式下,数据和行为分别被割裂到了 Model 和 Service 两层。我们把这种只承载数据,但没有业务行为的 Model 称为「贫血模型」。

我们在和业务方了解需求的过程中,使用到的对象都是现实业务的映射,是行为和属性的综合体。需求确定好之后,我们开发的过程中,人为把行为和数据拆分成了两部分,做了一次转换。随着需求的迭代,人员的更迭,开发看到的代码和业务方的需求越来越对应不上,导致很多代码谁也不知道对应的是什么业务逻辑,这种现象被称为由贫血模型带来的「失忆症」,最终导致的是一个维护成本极高的大泥潭系统。

领域驱动设计的核心就是基于业务逻辑去建模,避免贫血模型,减少设计和开发过程中对业务信息的丢失和转换。在业务逻辑迭代的过程中,系统通过调整对应的业务模型就可以完成迭代。

Part 3

落地过程

关键点:业务逻辑抽象

要做到基于业务逻辑建模,就要合理地抽象。因为业务表象千差万别,产品经理和软件设计人员需要和业务专家深入交流,并且从离散的信息中抽象出业务内在的逻辑。

比如旅游业务售卖的商品和标品不同,有些优惠是不考虑人群的,比如使用优惠券,所有类型的库存都可以享受;但如 N 人 N 折这类优惠,成人价可以享受,儿童价和单房差就不可以。基于这个特点,我们对优惠中心的商品模型做了抽象,抽象出来「是否可以参与件数计算」和 「是否可以参与价格计算」两个通用属性。这样既实现了基于业务逻辑建模,又不会陷入业务逻辑千差万别的表象中。

3.1 战术设计

第一步:统一语言,提炼关键词

准确的语言对于产品、运营、开发等各方对齐需求非常重要,我们需要将优惠逻辑当中的概念抽象为各方都能理解的词语,以达成共识。作为开发人员来说,对领域的理解一般来说是比较少的,为了抽象出合理的语言让产品和业务方都能理解,就需要充分理解业务背景和需求。在熟悉业务和需求的过程中,提炼出若干关键字,这些关键词就是最初产生的领域概念和通用语言。比如:

  • 优惠类型:表示一种优惠规则和对应的优惠方案。比如早鸟优惠,就是早多少钱买(优惠规则),减多少钱/打几折(优惠方案);
  • 优惠活动:拥有完整的生命周期,需要包含时间、平台、人员、商品等(限制维度)的某种优惠类型的使用过程信息;
  • 优惠发现:根据指定的商品、人员和平台,找出可以使用的优惠活动列表服务;
  • 优惠计算:根据指定的商品、人员、平台以及购买数量,计算出这一次购买行为可以享受的优惠金额及优惠明细;
  • 优惠排序:各种优惠类型在计算的时候是有先后顺序的,如果有打折的优惠存在,那顺序不同,计算的结果也会不同;
  • 优惠互斥:某些优惠之间存在互斥的关系,比如使用了金卡 96 折优惠,就不能使用马蜂窝优惠券。

第二步:抽象领域模型

根据单一职责的原则,一个领域概念对应一个领域对象。领域对象有实体值对象之分:

  • 实体:实体是有状态的和唯一标识的,包含属性和行为;
  • 值对象:值对象是无状态的,是只读的,包含属性和行为。

区分实体和值对象对系统设计有很大意义,实体是我们需要重点关注和设计的,而值对象则只使用它的「值」就可以了。这样可以简化系统的复杂度,将精力聚焦在核心领域对象。不难理解,优惠活动毋庸置疑是一个实体,优惠类型就是一个值对象。

但也存在某些业务行为是不能归于某个实体或值对象的,可以将它们归为领域服务:

  • 领域服务:领域服务本质上就是一些操作,不包含状态,通常用于协调多个实体。实体和值都属于领域对象,领域对象之间的交互逻辑不能放在领域对象内部,必须由服务来实现,从而有效地保护领域模型。

有一些领域逻辑,比如「优惠排序」和「优惠互斥」,他们涉及到多个优惠类型,也就是多个领域对象。如果也被设计为领域对象,就打破了单一职责的原则,所以我们把这部分跨多个领域对象的业务逻辑放到「领域服务」层。

第三步:抽象领域对象之间的关联关系

将相关联的领域对象进行显式分组,来表达整体的概念(也可以是单一的领域对象),也就是「聚合」

比如优惠活动是优惠类型、优惠范围等的聚合;优惠类型是优惠规则和优惠方案的聚合;优惠规则是限制维度的聚合;优惠方案是优惠手段的聚合:

图2 :关联关系示意

聚合的主要功能是把领域对象分组,外部的唯一访问点就是聚合根,这样可以避免处理领域对象间的一一对应关系,只需要处理聚合和聚合之间的关系就行了。

第四步:走查场景,调整领域模型

领域模型的调整是贯穿整个设计和开发过程的,随着业务的调整,领域模型也需要调整。比如优惠中心后期引入了会员卡的优惠类型,那么就需要把优惠券这个优惠类型的显示,调整为与会员卡互斥的优惠券和与会员卡不互斥的两种。

第五步:简化设计,降低系统复杂度

建模的本质是对现实事物的一种简化和抽象,指导我们忽略和问题域无关的事实,提取和问题域息息相关的信息。以优惠中心为例,最初的方案里我们设计了优惠类型管理的功能,根据不同的优惠规则和优惠方案自动组合成不同类型的优惠类型。但是可以预见,未来的优惠类型是有限的,并且每个优惠类型都有会自己的特殊配置,比如 N 人优惠里的 每 N 人/第 N 人;早鸟中的提前 N 天等。也就是说,根据优惠规则和优惠方案自动生成优惠类型基本是没有使用场景的,因此也就去掉了这个设计。

再如,对优惠的限制我们最初是设计在优惠活动维度,经过权衡,为了降低系统复杂度,最后实现在了优惠类型层面。以「蜂抢」优惠类型为例,它的规则是所有的蜂抢活动都是 1 个用户只能抢一次,没有必要把这个限制放在优惠活动维度,在优惠类型层面控制就可以了。

3.2 战略设计

战略设计处理的是不同限界上下文之间的拆分和集成逻辑。限界上下文比较抽象,结合我们在文章开始提到的不同语境中的「商品」例子来理解,同一个词如果不说明白所处的语境,是无法准确描述清楚其表达的含义的。「语境」其实就是「上下文」,对应不同「子领语」。同理,如果不在一个限定好的上下文中去设计领域模型,设计出的领域模型是不清晰的,它就会同时支持多个上下文。

这里需要说明一点,如果是从零搭建一个全新的电商系统,首先需要做的应该是战略设计。而优惠中心是建立在现有大的电商系统基础上,相当于作为其中一个子领域进行重构,所以我们才会先来做战术设计,再考虑在完整的电商系统下它与外部其他环境之间的关系,也就是战略设计。

优惠中心内部场景区分

优惠中心包括了服务于 B 端用户的优惠活动管理和服务于 C 端用户的优惠计算这两个不同的子业务单元:

图3 :优惠中心内部场景区分

  • 优惠活动处理的是优惠活动的增删改查,以及配套的统计等业务;优惠活动在这里是一个实体,有完整的生命周期,有上线、下线等状态,可以被创建和删除;
  • 优惠计算处理的是一个订单能享受哪些优惠,并减多少钱的问题;在这个场景里,优惠活动是一个值对象,只提供优惠计算需要的必要参数即可。

优惠中心与外部系统集成

在整个电商系统的环境下,优惠中心作为一个子域,处于自己的限界上下文当中。使用优惠中心服务的详情页、下单页都处于自己各自的限界上下文,所以调用优惠中心的时候就需要设计它们之间的上下文映射方式。

调用和被调用方使用的战略设计方法通常有以下几种:

  • 客户方-供应方:适用于同一个团队之间的协作,上游会有严格的自动化测试,来保证给到下游的数据是一定符合约定的;
  • 遵奉者:适用于不同团队协作,且上游不关心下游的标准,下游又完全「逆来顺受」地接受了上游给的数据的场景;
  • 防腐层:适用于上游不关心下游的标准,但是下游不甘心「逆来顺受」,就增加一层,来做转换处理,保持下游系统的独立性;
  • 开放主机服务:适用于中台(通用能力平台),对接方非常多,业务重复度高,并且已经有完善的测试机制和通用的模型。

结合我们的实际情况来看,调用优惠中心的可能会是不同团队的开发人员,而优惠中心又不想被不同的上游侵入内部设计中,所以「客户方-供应方」和「遵奉者」模型都不适合;另外优惠中心前期接入方会比较少,而且会不断迭代,使用「开放主机服务」也不太合适。综合考虑下,防腐层的设计比较适合优惠中心。

下图是优惠中心的业务架构示意,中间的应用服务层采用的就是防腐层的设计,反映优惠中心与外部系统集成时的上下文映射关系:

图4 :优惠中心业务架构

3.3 架构实现

优惠中心选择的是经典的分层架构。从上到下为用户接口层、应用服务层、领域层和仓储层。图中不同的颜色块分别对映外部服务、应用服务、领域服务、聚合根、实体、值对象和仓储。

图5 :优惠中心分层领域模型

  • 用户接口层:处理和终端用户的交互逻辑;
  • 应用服务层:负责封装和转换领域层的返回数据给用户接口层;
  • 领域层:优惠中心的核心逻辑都在这一层,包括领域对象和领域服务。
  • 仓储层:仓储层负责把内存中的领域对象落地到存储介质,也负责从存储介质拿到原始数据后构造领域对象给领域层使用;这一层对领域层隐藏了底层的存储细节。虽然仓储层处在领域层下方,但是我们实现过程中采用了依赖注入的方式,将仓储层的具体实现注入到领域层中。

Part 4

问题及近期规划

1. 价格层优惠

现在公司面没有一个统一的商品中心,并且各业务线对商品的定义差别很大。比如自由行的商品包括出行日期、价格类别(成人价、儿童价)和套餐类别等层级;而火车票的商品包含座次、席别、目的地和出发地等层级。

如果优惠中心抽象出一种通用的商品层级来适配各个业务线,那实际上就是优惠中心要对商品进行标准定义,但是这个标准与后续商品中心的标准定义很有可能是不一致的,如果不一致优惠中心就要做大的改版。所以最终的解决方案可能还要通过推进统一商品中心的建立来解决。

2. 性能问题

领域驱动设计带来的弊端就是类的增多。目前优惠中心的技术栈基于 PHP, PHP 是一种解释型语言,在DDD 模式下即使有了 OPCode 等缓存技术,执行阶段的耗时相对其他静态数据类型的语言还是较大。所以后面计划将优惠中心使用 Java 技术栈重构,来进行性能上的优化。

Part 5

小结

本文介绍了马蜂窝电商优惠中心基于 DDD 进行重构的一些实践经验。DDD 的思想也帮助我们在业务迭代的过程中将架构设计得更加合理。

当然,是否采用业务驱动设计的思想,需要取决于业务和团队的实际情况。在马蜂窝业务的快速发展下,我们在架构设计上还将做更多的探索,也将持续与大家交流。

天亮以前说再见 – 康梓峰

水花只能开在雨天

烟花要绽放在黑夜

雪花多舍不得冬天

像我舍不得和说你再见

谎言并不代表欺骗

诺言也不一定兑现

誓言就都留给时间

就请把从前留在今天

天亮以前说再见

笑着泪流满面

去迎接

应该你的

更好的明天

昙花若只一现

更要开的耀眼

别回头去拥有

属于你更好的世界

天亮以前说再见

让我留在今天

去保护

我和你的

最好的夏天

情丝若水三千

只取一瓢眷恋

当你来过的纪念

水花只能开在雨天

烟花要绽放在黑夜

雪花多舍不得冬天

像我舍不得和说你再见

谎言并不代表欺骗

诺言也不见得兑现

誓言就都留给时间

就请把从前留在今天

天亮以前说再见

笑着泪流满面

去迎接

应该你的

更好的明天

昙花若只一现

更要开的耀眼

别回头去拥抱

属于你更好的世界

天亮以前说再见

让我留在今天

去保护

我们最美

最好的今夜

情丝若水三千

只取一瓢眷恋

当你来过的纪念