谈谈领域驱动设计的落地

前文提到了事件风暴产出的领域模型是概念模型,到实际落地还有些距离,而落地的结果也是各不相同,我觉得说落地,要先回顾一下领域驱动设计的两个作用。

  1. 通过战略设计拆分子域,指导微服务拆分。
  2. 通过事件风暴建立领域概念模型,指导代码设计。

也就是说领域驱动设计产出的结果是指导性的,并不是一个直接可落地的结果。落地的方案则是要通过架构设计和框架选择上来进行。架构是为了控制软件复杂性而做,就好像『一千个读者心中有一千个哈姆雷特』,不同人做架构不尽相同。下面说说我的落地方式。

阅读全文 »

领域驱动设计核心概念

领域驱动设计学习拦路虎之一就是众多的概念,第一次接触这些概念会有一定的理解成本,不过正是这些概念支撑起的领域驱动设计,接下来会以电商为例对其中的核心概念做介绍。

阅读全文 »

概述

概念可以简单描述某类事物,这类事物可以是实体也可以是问题。领域驱动设计是为了管理系统复杂性问题而生的一套方法论。

随着业务系统的复杂性不断提高,系统的性能和灵活性要求也会越来越高,如何构建一个扩展性强、可用性高的业务系统是需要我们不断思考的问题。

我们以交易系统为例,在互联网之初,实体商业占据绝对主导地位的时代,电子商务系统最初的目的就是把货物卖出去,业务需求很简单,就是一手付钱,一手交货,而更多的难点是在于如何让人们接受并认可在网络上进行交易。随着这几十年的发展,电商早已不是最初的样子,需求变为如何更快更多的把商品卖出去,于是产生出了层出不穷你算不清楚的促销活动,比如满减、凑单、会员价、拼团、优惠券等。你买东西的价格也许只有系统能真正算清楚。

系统的复杂性比起最初,呈几何倍的增长,如何控制并管理系统复杂度是我们需要在业务发展过程中需要解决的问题。复杂的业务各有各的复杂,而拆解之道也各有各的侧重,今天要介绍的是领域驱动设计如何帮助我们拆解需求,并建立一个灵活性高、可扩展的业务系统。

阅读全文 »

在加乌拉山口拍珠峰,这里可以一眼望尽五座八千米高山。

旅行是为了什么?有的人是为了好吃好玩,有的人是为了看风景。我就是后者,如果你喜欢游览祖国广袤的山川河流,看尽一望无际的高原雪山,那么你一定要来趟西藏,看看还是那么相对纯粹的自然风光。

去西藏的计划是从八月推迟到了十一黄金周,不过今年西藏的十一却少了往日的火爆,人不是那么多,主要还是因为前一段事件疫情的影响,所以路上的体验都还不错。

全国除了西藏都是可以想去就去的,而西藏是需要好好准备一下的,为此也踩了一些坑。就拿抗高反来说吧,去之前你需要做的是好好休息,减少运动量就可以了,不用喝什么红景天、高原康之类的,作用不大。在高原防止高反只需要做到以下几点即可:

  1. 晚上保证充足的睡眠。
  2. 多吃高热量实物来保障能量供应。
  3. 放平心态,不要有心理压力。
  4. 到高原先缓两天再安排行程。

刚去的前几天高反加水土不服,在羊卓雍措的湖边住房车,大半夜的跑到车外吐了好几次,第二天买了藿香正气就好了,然后就是听司机的话,觉得不舒服就喝点葡萄糖,确实很有效。

到了西藏,面对壮阔的雪山群,纯净的圣湖,当然要留下一些回忆。

在海拔 5200 米的珠峰大本营,珠峰山顶总被云雾缠绕着,能近距离的看到很难得。

羊卓雍措是西藏的三大圣湖之一,从山上看下去,湖水呈靛青色,就跟染料一般纯净,和其它湖水的蓝很不一样。

纳木错也是西藏的三大圣湖之一,蓝色湖水对面的雪山就是念青唐古拉山脉。

在西藏还有一点独特的体验就是看星空,与平原不同地是这里的高海拔,使得星空银河非常清晰明显,你会看到银河从地上的一端穿过天空扎向另一端。站在山顶的那一刻我终于体会到儿时的一句话『天空是一块幕布,星星和月亮就是装饰这块幕布的花纹。』望着繁星与银河,感觉到自己渺小,如这沧海中的一粟。

西藏之旅还有很多有意思的故事与见闻,虽然这篇文章鸽了这么久,但是我还是不想再去讲了。总之这是一场头疼并快乐的旅途~

调研环境说明

etcd –version
etcd Version: 3.5.1
Git SHA: d42e8589e
Go Version: go1.17.2
Go OS/Arch: darwin/amd64

参数说明

启动参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
--name etcd-1 // 节点名称
--data-dir /Users/noogel/Debug/data/etcd1 // 数据目录
--initial-advertise-peer-urls http://127.0.0.1:238
--listen-peer-urls http://127.0.0.1:2381
--listen-client-urls http://127.0.0.1:2379
--advertise-client-urls http://127.0.0.1:2379
--initial-cluster-token etcd-lock-cluster-1 // 集群 token
--initial-cluster etcd-1=http://127.0.0.1:2381,etcd-2=http://127.0.0.1:2382,etcd-3=http://127.0.0.1:2383 // 集群节点信息
--initial-cluster-state new // 初始化的集群状态
--heartbeat-interval 1000 // 心跳间隔
--auto-compaction-retention 1 // 开启自动压缩,间隔 1h 执行
--auto-compaction-mode periodic
--quota-backend-bytes 8589934592 // 后端存储大小
--election-timeout 5000 // 选举超时时间

关于自动压缩
https://etcd.io/docs/v3.4/op-guide/maintenance/#defragmentation

  • --auto-compaction-mode=revision --auto-compaction-retention=1000 每5分钟自动压缩”latest revision” - 1000
  • --auto-compaction-mode=periodic --auto-compaction-retention=12h 每1小时自动压缩并保留12小时窗口。

自动压缩碎片后还需要单独再清理占用的系统存储空间,etcdctl defrag

最佳实践

  1. heartbeat timeout 默认为 100ms,推荐配置为 1s;
  2. election timeout 默认为 1000ms,推荐为 5s(election timeout >= 5 * heartbeat timeout);
  3. quota-backend-bytes 默认为 2G(最大值8G),推荐根据集群容量预估调整;
  4. 配置 auto-compaction-retention=1 和 auto-compaction-mode=periodic 参数,定期压缩历史数据;
  5. 推荐通过 cronjob 定期执行 etcdctl defrag(如果 defrag 执行时间 > election timeout,则集群会进入重新选主模式)

环境模拟

端口映射

单机环境写集群搭建,以下是端口映射

etcd1 2379 -> 2391 2380 -> 2381

etcd2 2379 -> 2392 2380 -> 2382

etcd3 2379 -> 2393 2380 -> 2383

启动命令

1
2
3
4
5
ETCDCTL_API=3 etcd --name etcd-1 --data-dir /Users/noogel/Debug/data/etcd1 --initial-advertise-peer-urls http://127.0.0.1:2381 --listen-peer-urls http://127.0.0.1:2381 --listen-client-urls http://127.0.0.1:2379 --advertise-client-urls http://127.0.0.1:2379 --initial-cluster-token etcd-lock-cluster-1 --initial-cluster etcd-1=http://127.0.0.1:2381,etcd-2=http://127.0.0.1:2382,etcd-3=http://127.0.0.1:2383 --initial-cluster-state new --heartbeat-interval 1000 --auto-compaction-mode=revision --auto-compaction-retention=1000 --quota-backend-bytes 8589934592 --election-timeout 5000 > /Users/noogel/Debug/data/etcd1/run.log 2>&1 &

ETCDCTL_API=3 etcd --name etcd-2 --data-dir /Users/noogel/Debug/data/etcd2 --initial-advertise-peer-urls http://127.0.0.1:2382 --listen-peer-urls http://127.0.0.1:2382 --listen-client-urls http://127.0.0.1:2378 --advertise-client-urls http://127.0.0.1:2378 --initial-cluster-token etcd-lock-cluster-1 --initial-cluster etcd-1=http://127.0.0.1:2381,etcd-2=http://127.0.0.1:2382,etcd-3=http://127.0.0.1:2383 --initial-cluster-state new --heartbeat-interval 1000 --auto-compaction-mode=revision --auto-compaction-retention=1000 --quota-backend-bytes 8589934592 --election-timeout 5000 > /Users/noogel/Debug/data/etcd2/run.log 2>&1 &

ETCDCTL_API=3 etcd --name etcd-3 --data-dir /Users/noogel/Debug/data/etcd3 --initial-advertise-peer-urls http://127.0.0.1:2383 --listen-peer-urls http://127.0.0.1:2383 --listen-client-urls http://127.0.0.1:2377 --advertise-client-urls http://127.0.0.1:2377 --initial-cluster-token etcd-lock-cluster-1 --initial-cluster etcd-1=http://127.0.0.1:2381,etcd-2=http://127.0.0.1:2382,etcd-3=http://127.0.0.1:2383 --initial-cluster-state new --heartbeat-interval 1000 --auto-compaction-mode=revision --auto-compaction-retention=1000 --quota-backend-bytes 8589934592 --election-timeout 5000 > /Users/noogel/Debug/data/etcd3/run.log 2>&1 &

历史数据清理命令

1
2
3
4
rm -rf /Users/noogel/Debug/data/etcd1
rm -rf /Users/noogel/Debug/data/etcd2
rm -rf /Users/noogel/Debug/data/etcd3
mkdir etcd1 etcd2 etcd3

日常运维

常规命令

1
2
3
4
5
6
7
export ETCDCTL_API=3
// 节点列表查询
etcdctl member list
// 节点状态
etcdctl --endpoints=127.0.0.1:2381,127.0.0.1:2382,127.0.0.1:2383 endpoint status --write-out=table
// 整理磁盘碎片
etcdctl --endpoints=127.0.0.1:2381,127.0.0.1:2382,127.0.0.1:2383 --user root:123456 defrag

开启鉴权

1
2
3
4
5
6
7
8
// 添加 root 用户
etcdctl --endpoints=127.0.0.1:2381,127.0.0.1:2382,127.0.0.1:2383 user add root
// 授权 root 角色
etcdctl --endpoints=127.0.0.1:2381,127.0.0.1:2382,127.0.0.1:2383 user grant-role root root
// 查看用户列表
etcdctl --endpoints=127.0.0.1:2381,127.0.0.1:2382,127.0.0.1:2383 --user=root:123456 user list
// 开启鉴权
etcdctl --endpoints=127.0.0.1:2381,127.0.0.1:2382,127.0.0.1:2383 auth enable

生产集群节点启动方式

在生产机通过 systemd 启动。第一次启动命令--initial-cluster-state new,后续节点的增加需要修改为 --initial-cluster-state existing,不明白看节点增加部分。

修改配置

1
2
3
4
5
6
7
8
// 编辑配置
vim /lib/systemd/system/etcd.service
// 重新加载配置
systemctl daemon-reload
// 启动服务
systemctl start etcd.service
// 查看服务状态
systemctl status etcd.service

问题处理

Etcd 的 compact 机制

Etcd 默认不会自动 compact,需要设置启动参数,或者通过命令进行compact,如果变更频繁建议设置,否则会导致空间和内存的浪费以及错误。Etcd v3 的默认的 backend quota 2GB,如果不 compact,boltdb 文件大小超过这个限制后,就会报错:”Error: etcdserver: mvcc: database space exceeded”,导致数据无法写入。

要从空间不足配额警报中恢复:

  1. Compact etcd的历史。
  2. 对每个etcd端点进行碎片整理。
  3. 解除警报。
1
2
3
4
5
6
7
8
9
10
11
# 1、获取当前的版本
$ rev=$(ETCDCTL_API=3 etcdctl --endpoints=:2379 endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*')
# 2、压缩当前版本之前的所有记录
$ ETCDCTL_API=3 etcdctl compact $rev
compacted revision 1516
# 3、清理多余的碎片空间
$ ETCDCTL_API=3 etcdctl defrag
Finished defragmenting etcd member[127.0.0.1:2381]
# 4、解除警告
$ ETCDCTL_API=3 etcdctl alarm disarm
memberID:13803658152347727308 alarm:NOSPACE

需要注意的是整理碎片释放空间,要一个一个节点执行,因为在执行期间节点是无响应的,直到处理完。防止因为全部节点无响应导致的服务不可用

碎片整理

压缩key空间后,会出现内部碎片,这些压缩出来的碎片空间可以被etcd使用,但是不会真正的释放物理空间,需要进行碎片整理,如:

1
2
$ etcdctl defrag
Finished defragmenting etcd member[127.0.0.1:2379]以上指令只作用于当前所在的主机,不会在集群

环境中复刻。可以使用–cluster标记指定所有成员以自动查找所有集群成员。如:

1
2
3
4
$ etcdctl defrag --cluster
Finished defragmenting etcd member[http://127.0.0.1:2381]
Finished defragmenting etcd member[http://127.0.0.1:2382]
Finished defragmenting etcd member[http://127.0.0.1:2383]

节点增减

1
2
3
4
5
6
7
8
# 查看成员信息
ETCDCTL_API=3 etcdctl member list
# 移除节点
ETCDCTL_API=3 etcdctl member remove wallet0x
# 添加节点
ETCDCTL_API=3 etcdctl member add wallet0x --peer-urls="http://10.137.158.119:2380"
# 最后再启动服务
# 其中启动命令 --initial-cluster-state 需要设置为 existing。

需要先移除故障节点成员,再添加进去成员列表。然后清理掉故障节点的工作目录内容,之后再启动服务,启动后服务会自动同步数据。

其中启动命令需要设置为 --initial-cluster-state existing

参考链接

http://www.zhaowenyu.com/etcd-doc/ops/data-space-manage.html
https://xieys.club/etcd-backup-restore
https://www.cnblogs.com/lowezheng/p/10307592.html
https://bbotte.github.io/service_config/etcd-cluster-troubleshooting.html
https://www.mytecdb.com/blogDetail.php?id=211
https://www.cnblogs.com/tencent-cloud-native/p/14893209.html
http://www.dockone.io/article/2955
https://mytecdb.com/blogDetail.php?id=199

概况

作为一个北方汉子对于面食真的是十分热爱的,尤其是发面后的。从小在家兜包子都是只能看不让参与的,长大后在外面都是买现成的,如今也想自己做一做。也许是从小在家耳濡目染,第一次做整体的效果还不错。
宋丹丹老师曾经说过把大象装进冰箱总共要分三步,那么蒸包子总共需要分为如下五步:

  1. 准备原材料
  2. 发面
  3. 调馅
  4. 擀面片
  5. 蒸包子

可是,真的就是只蒸了包子吗?

关于蒸包子的一些思考

分治法

就像我之前聊到过的,复杂的东西是由许多简单的东西组成的。就像蒸包子这件事不一定多么复杂,但也不那么简单,要想做好也是要拆分成上面五个相对简单的步骤执行。作为一个后端工程师,现在做事情总是会带入一些思维模型去看,这个例子是分治法来将蒸包子这件事简单化,只要我们把其中简单的每一步做好就可以最终把包子蒸好。

批处理

当我把包子蒸好以后,发现耗时很长,那么怎么提高效率呢。刨除在网上买菜等菜的时间,大致的时间线如下:

发面准备:15:00 -> 15:30
发面期间:15:30 -> 17:00
做、调馅:16:30 -> 17:30
擀皮 :17:30 -> 18:00
包、蒸 :18:00 -> 19:00

总计人力耗时 3 小时,出锅 18 个包子。如果我要是蒸 36 个包子就要消耗 6 小时的时间吗?答案是要小于 6 小时的,主要原因有以下几点:

  1. 蒸笼仍有一半的空间未用,可以节省掉一次烧水蒸包子的时间。
  2. 每个环节都可以节省准备工具,收拾工具的时间。

但是我要再多蒸一倍的包子是不是平均耗时会更短,这个就不一定了,如果蒸锅的承载上限就是 36 个包子,那平均耗时就不会再减少。
批处理的方案在一定程度上会提高我们的效率,但不会无限提高,而是有一个最优解,这个最优解取决于外在条件。

并发思维

以上情况分析的是单人力情况下,如果再有一个人一起做。可以有以下方面提升:

  1. 我们发现面团在 17:00 发好的,擀皮在 17:30 才开始的,中间阻塞的时间在弄馅。把弄馅的时间交给第二个人做,可以减少 30 分钟阻塞。
  2. 发面准备分两个人做可以大致减少 15 分钟耗时。
  3. 两个人一起包包子可以大致减少15分钟耗时。
  4. 额外损耗,例如工具准备上因为多加了一套工具会产生额外耗时。

最后大致的总人力耗时会大于 3 小时,平均人力耗时在 1.5 到 2小时之间。并发思维又是我们另外一个手段。

流水线模型

上面需要人操作的蒸包子需要四步,其中每一步都会有一些内耗是在每步切换时都需要思考下一步该怎么做,以及准备对应步骤的工具撤掉上一步骤的工具。如果我们厨房能供四人同时使用,并且每人只做一步的事,那么每一步的耗时就会因为熟能生巧而使时间大大缩短。如果这是一家包子店的厨房,那么这四个人就可以源源不断的高效生产包子。通过流水线模型来提高效率,这也是并发的一种。

工具化思维

再假如,北京所有的人早餐都要来这家店吃包子,那么任凭这四个人怎么日夜生产,也不能满足整个北京的需求。如果还是这四个人怎么做?如果恰好其中有一个人学过机械相关的知识并且动手能力又很强,这时候他可以和其他三个人一起交流蒸包子的心得,然后结合整个知识,设计出蒸包子机器,然后找工厂生产出几十台日夜生产,我想北京的包子供应应该就没问题了。他们四个人只需要盯盯机器,坏了修一修就好了。这里面就用到了工具化思维,可以极大的提高我们的生产效率。工业革命的意义之一就是创造了巨大的生产力。

说了这么多,下面附上我的蒸包子攻略~

蒸包子攻略

准备原材料

  • 准备面皮
    • 面粉 1 kg
    • 酵母粉 5g
    • 温水
  • 准备馅(猪肉大葱)
    • 猪肉馅 500g
    • 大葱 500g
    • 姜、料酒、胡椒粉
    • 耗油、鸡精、生抽
    • 香油
    • 老抽

发面

  1. 用温水冲开酵母粉,混合均匀后加入面粉中和面。

  1. 慢慢慢慢~加水,搅拌成絮状。

  1. 最后柔成光滑的圆面团。

  1. 放到温暖的地方发酵,一小时左右吧,面团最后会放大一倍左右。

加水的时候需要慢慢加慢慢抓,慢慢会形成一个光滑的圆团,面团的软硬跟水的多少有关,如果面硬的话可以适当加一些水调整软硬程度。

调馅

  1. 趁着面团发酵期间,可以开始准备调馅了。买的是绞好的猪肉馅,然后就是把两颗大葱切碎,切点姜碎进去。
  2. 加入上述的各种调料调整肉馅的口味。由于放了老抽和生抽,盐可以少放或者不放,根据个人口味来看。
  3. 加好以后搅拌均匀就行了。

其中老抽用来调色,生抽、鸡精、耗油用来调味,胡椒粉、姜碎、料酒用来去腥,加一些香油可以让肉馅变得超级香。

擀面片

  1. 调好馅可以休息一会,待发好面取出来继续揉捏,直到里面没有气泡了。
  2. 然后揉成一个细条状,用刀切成一段一段的。用手掌按成扁圆的。
  3. 用擀面杖一点点擀成扁片,厚度比饺子皮厚一些,标准的面片是边缘较薄中心较厚的圆形。

蒸包子

  1. 准备一个篦子,上面放好屉布,待包子包好放上去。至于兜包子的手法直接网上查吧。
  2. 准备好蒸锅,放上包子开火后蒸20分钟左右即可,关火后放 5分钟。
  3. 最后的出锅,因为包子会变大导致互相黏连,可以准备一些清水滴到屉布和包子连接处湿润,这样包子就可以完好的取下了。

最后来一张出锅照,年轻人的第一锅包子就这样做好了。

最后

工作和生活中我们会遇到很多事和物,事事物物之间有很多共通之处,包括问题的产生和解决办法。不同表象的背后相同的本质的东西是思维方式还是抽象模型?看清它们,能带给我的是做出好吃的包子,不仅仅是这些,还有更多。

什么是复杂性

复杂或复杂性与简单相对立,那么复杂是什么?它是我们大脑中的一个概念,但是我在网上找不到一个给复杂恰当的定义描述,它会有不同的解释。
其中洛克在《人类理解论》中说道:『一些思想是由简单的思想组合而成,我称此为复杂;比如美、感激、人、军队、宇宙等。』
作为研究复杂系统的专家 Melanie Mitchell,也没有给出一个明确的公认的定义。她在《复杂》一书中给出了复杂系统加以定义:『复杂系统是由大量组分组成的网络,不存在中央控制,通过简单运作规则产生出复杂的集体行为和复杂的信息处理,并通过学习和进化产生适应性。』
上述复杂系统中的组分对应软件系统中的组成部分,基于不同粒度可以是对象、函数、类、包、模块、组件和服务等。每一部分都应该是相对单一的职责,细粒度部分之间耦合提供更粗粒度功能,不同组分之间相互协作来提供系统功能,继而组合成我们复杂的软件系统。

软件系统复杂性由何而来

计算机的产生对我们生产生活产生的影响不言而喻,其中软件系统的功能是随着我们实际生活需求的变化而变化的。人有七情六欲带来的各种需求,接收信息的方式主要是视觉、听觉。而机器擅长的只是简单的逻辑处理和数值计算,两者之间有着巨大的鸿沟。如何让机器提供视觉和听觉的手段来满足人们的需求,这里抛开硬件不谈,软件层面有操作系统提供基本的软件运行环境。
软件系统则只需要专注于如何组织和管理数据来满足人们的工作生活娱乐需求,一方面要关注人的需求和需求变化,另一方面要关注机器层面能提供的计算能力。
软件系统的复杂性来自于两个方面,一方面是需求侧复杂,导致大多数系统的功能都难以理解;另一方面是难以把控需求的变化,虽然我们遵循一些设计原则可以对未来进行一些预判,但还是存在不可预测的风险。

如何度量复杂度

在《复杂》一书中作者列举了不同角度可能度量复杂性的方法。

  • 生物学上尝试通过基因组的规模来度量。
  • 信息学上尝试通过熵、信息量、交互信息来度量。
  • 用算法信息量度量复杂性(能够产生对事物完整描述的最短计算机程序的长度。)
  • 此外还有逻辑深度、热力学深度、分形维度等方面。

复杂度并没有一个统一明确的度量方式,我们可以站在一个角度上对具体的某类或粒度提供一个可供参考的度量方法。不论我们如何度量,我们在开发软件系统中的一个重要目标就是控制和降低系统复杂度。在巨著《人月神话》中提出了两个重要概念:

  • 本质复杂度:指由于一问题的本质不适合简单的求解方式,所有可行的求解方式都很复杂的情形。
  • 偶然复杂度:指电脑软件开发过程中所引入不必要的复杂度。

偶然复杂度不是待求解问题的本质,相对而言, 本质复杂度和待求解问题的本质有关,是无法避免的。偶然复杂度一般是因为选用求解问题的方法时所引入的。

在源代码层面为了描述工程质量有以下两个方面衡量:

  • 圈复杂度:根据代码中的路径数量计算的循环复杂性。每当一个函数的控制流发生分裂时,复杂度计数器就会增加1。每个函数的最小复杂度为1。由于关键字和功能的不同,这种计算方法在语言上略有不同。以 Java 为例增加复杂度的关键字有:if, for, while, case, catch, throw, &&。
  • 认知复杂度:是由sonarQube设计的一个算法,算法将一段程序代码被理解的复杂程度,估算成一个整数——可以等同于代码的理解成本。作为指导程序员编写“既可测试又可维护”的方法。

在认知复杂度的计算方法中主要基于以下三条规则:

  1. 忽略那些允许将多个语句可读性地速记为一个的结构。
  2. 在代码的线性流程中,每中断一次就累加 1。
  3. 当断流结构被嵌套时难度累加 1。

下面实例对比两种复杂度度量方法的差异,在不同写法上圈复杂度的统计和认知复杂度的统计有何差异。

上图是两种写法在圈复杂度的统计方法,得出的值都是 4,也就是从逻辑上来说是相同的。但是在可读性上来说,明显右侧的 switch 代码更高。认知复杂度就是为了度量人的易于理解性上存在的。

以上是认知复杂度算法给这两种方法打出了明显不同的分数,这些分数更能反映出它们的相对可理解性。更具体的内容可以查看 CognitiveComplexity

如何管理系统复杂度

架构的本质目标就是管理复杂度,而管理复杂度有以下三种有效的手段:

  • 抽象:从众多的具体事物当中抽取共同的、本质的属性,摒弃差异的非本质属性,简化描述形成概念。
  • 分治:把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
  • 领域知识:是指一组有内在联系的知识的集合,它往往与特定的职业、研究方向、兴趣、社群或文化圈层等相关联。

初步认知

以 Drools 为例子

这个组件是什么,有什么功能?

Drools 是一个基于Charles Forgy’s的RETE算法的,易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准,速度快、效率高。业务分析师人员或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。
Drools相关概念

  • 事实(Fact):对象之间及对象属性之间的关系
  • 规则(rule):是由条件和结论构成的推理语句,一般表示为if…Then。一个规则的if部分称为LHS,then部分称为RHS。
  • 模式(module):就是指IF语句的条件。这里IF条件可能是有几个更小的条件组成的大条件。模式就是指的不能在继续分割下去的最小的原子条件。

Drools通过 事实、规则和模式相互组合来完成工作,drools在开源规则引擎中使用率最广,但是在国内企业使用偏少,保险、支付行业使用稍多。

能解决什么问题?

「规则引擎主要完成的就是将业务规则从代码中分离出来。」 在规则引擎中,利用规则语言将规则定义为if-then的形式,if中定义了规则的条件,then中定义了规则的结果。规则引擎会基于数据对这些规则进行计算,找出匹配的规则。这样,当规则需要修改时,无需进行代码级的修改,只需要修改对应的规则,可以有效减少代码的开发量和维护量。

这个组件对比竞品有什么优势和劣势?

易用性、广泛性、高性能、高可用、高一致性等方面。
Java开源的规则引擎有:Drools、Easy Rules、Mandarax、IBM ILOG。使用最为广泛并且开源的是Drools。
规则引擎优点

  • 声明式编程
  • 逻辑和数据分离
  • 速度和可扩展性
  • 知识集中化

规则引擎缺点

  • 复杂性提高
  • 需要学习新的规则语法
  • 引入新组件的风险

原理了解

这个组件实现机制是什么样的?

Drools规则引擎的结构示意图:


在 Drools 中,规则被存 放在 Production Memory(规则库)中,推理机要匹配的 facts(事实)被存在 Working Memory(工作内存)中。当时事实被插入到工作内存中后,规则引擎会把事实和规则库里的模式进行匹配,对于匹配成功的规则再由 Agenda 负责具体执行推理算法中被激发规则的结论部分,同时 Agenda 通过冲突决策策略管理这些冲突规则的执行顺序。
Drools 中规则冲突决策策略有

  • 优先级策略
  • 复杂度优先策略
  • 简单性优先策略
  • 广度策略
  • 深度策略
  • 装载序号策略
  • 随机策略

使用了什么算法\模型\框架?

Rete 算法
最初是由卡内基梅隆大学的 Charles L.Forgy 博士在 1974 年发表的论文中所阐述的算法 , 该算法提供了专家系统的一个高效实现。自 Rete 算法提出以后 , 它就被用到一些大型的规则系统中 , 像 ILog、Jess、JBoss Rules 等都是基于 RETE 算法的规则引擎。

Rete 在拉丁语中译为”net”,即网络。Rete 匹配算法是一种进行大量模式集合和大量对象集合间比较的高效方法,通过网络筛选的方法找出所有匹配各个模式的对象和规则。

其核心思想是将分离的匹配项根据内容动态构造匹配树,以达到显著降低计算量的效果。Rete 算法可以被分为两个部分:规则编译和规则执行。当Rete算法进行事实的断言时,包含三个阶段:匹配、选择和执行,称做 match-select-act cycle。

Drools 中的 Rete 算法被称为 ReteOO,表示 Drools 为面向对象系统(Object Oriented systems)增强并优化了 Rete 算法。

上手使用

使用场景有哪些?

从Drools规则引擎的使用模版来看,输入、输出和判断三个中,判断是变化的,而输入和输出是基本固定的,所以适用的场合可以分为下面几种:

  • 输入和输出的参数不变,即:规则文件接收固定的参数,产生固定的输出。比如:根据货物重量计算运输价格,输入参数是货物重量,规则根据级差价格表,输出运输价格。
  • 输入和输出的JavaBean Object不变,即:规则文件接收固定类型的JavaBean,产生固定类型的JavaBean。比如:根据顾客信息和当前购物信息计算优惠价格,输入参数是顾客当前的类别(VIP客户等)和当前购物的种类、数量,规则根据顾客类别、商品种类和购买数量输出优惠价格。

所以,规则引擎适用于「问题确定」的场景,并且存在比较复杂的业务规则并且业务规则会「频繁变动」的系统。比如:

  • 风险控制系统(风险贷款、风险评估)
  • 反欺诈项目(银行贷款、征信验证)
  • 决策平台系统(财务计算)
  • 促销平台系统(满减、打折、加价购)

在项目中使用上需要怎么做?

TODO

在使用过程中容易踩到哪些坑?

TODO

参考资料

网络文章、杂志专栏、论文等
https://blog.csdn.net/Taobaojishu/article/details/108231696

模板的好处就在于可以让你快速且全面的规划方案,一些你能想到和不能想到的地方。既能避免你在做旅游规划时漏掉什么,又能节省你思考的时间。有了模板你只需要按照大纲去调研即可。

如果你旅游前需要详细规划,那么这个模板很适合你。
如果你是想走开车就走的那种,可以跳过这篇。

模板按需填充即可,比如去西藏就需要特别关注海拔信息。去三亚就需要关注一下日出日落和潮汐时间。下面是我在做西藏旅游攻略做的一个简单攻略,仅供参考。

阅读全文 »

你有没有遇到过一个函数几百行长度,如果没有可以跳过本文章了,如果没有那么应该看看下面的内容。一个超长函数的复杂性不在于那些胶水代码,而是其中的逻辑分支,大量的逻辑分支导致你的代码难以理解。而重构的过程分为两步:

  1. 编写单元测试,如果覆盖到了函数的每个分支,那么重构的风险性就会降到最低。
  2. 重构代码,对代码进行拆分重写,为了使代码变得易于维护。

重构分为三个层次:

  1. 小重构
    • 消除重复代码
    • 拆分小函数(单一职责)
  2. 设计模式
    • GoF 23种
  3. 抽象建模
    • 四色建模法
    • 风暴建模法

复杂度问题的应对办法,防止偶然复杂性。

TODO