dream

一个菜鸟程序员的成长历程

0%

好的Prompt事半功倍

万能公式:定义角色 + 需求背景 + 实现目标 + 补充要求 + 示例

本教程为零基础教程,零基础小白也可以直接学习,有基础的可以跳到后面的原理篇学习。
基础概念和SQL已经更新完成。

接下来是应用篇,应用篇的内容大致如下图所示。

概念学习

表设计

表设计可以聊的点其实是比较多的,这个也比较看具体的业务、流量等。

比如,某一个字段是否应该放在这张表里?一张表里应该有哪些字段?如何设计字段类型?

甚至于,如何设计字段的顺序?

这里很多人不知道的一个点在于,字段的顺序也会影响性能,至于为什么,这个就偏低层一些了,下面会讲到。

想要做表设计,那你首先需要知道是什么,所以我们先来看看表到底是什么东西。

表是什么?

有的人会说,表就是Navicat上看到的一张表呗,还能是什么啊?

还有的人说,表就是一行一行数据组成的。

其实说的都对,但是这是逻辑上的表,也就是mysql给我们展现出来的表。

大家有没有想过,表的物理形式是什么,msyql如何将它转化成逻辑上的表方便我们查看呢?

接下来进行揭秘吧!

总结

通常来说,ER图是在设计阶段完成的,先有ER图再有表结构。

可如果你已经有了表结构,有没有办法生成ER图呢?

也是有方法的,比如著名的Navicat工具,就支持这么做。

此外,还有一个方法,就是使用在线工具dbdiagram,这个工具可以导入现有的SQL,会生成ER图,如下。

概念学习

这个网站是通过左边的一个叫dbml的语言来生成ER图的,也支持直接导入SQL,转化成dbml格式再生成ER图。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

MIT6.824

你在尝试构建分布式系统之前,应该先尝试单机系统,如果能满足的话不要搞分布式。

因为单机系统比分布式简单的多。

分布式的原因是需要获得更高的性能、某种并行性、大量的CPU、大量的内存、大量的磁盘。另一个原因是容忍故障,一个机器挂了还有别的机器提供服务。
还有些可能是自然的物理分布式,比如银行多个地方的转账。还有安全性,可以隔离出环境运行代码。来保证主环境的安全。

分布式系统的挑战

  • 并发性
  • 部分故障
  • 性能

本课程的目标是构建面向应用程序的基础设施:

  • 存储
  • 网络通信
  • 计算

主要目的为抽象这些能力给外部应用提供接口,隐藏内部的分布式实现。

一些已有的实现示例:

  • RPC:隐藏了通信能力
  • Thread:隐藏了多核操作系统的并发能力

构建分布式系统的高层次的目标,这也解释了为什么构建分布式系统很难:

  1. 可扩展性():系统的横向扩展能力,理想情况是加n台机器可以获得n台机器的性能。
  2. 容错性(Fault Rable):
    • 可用性:系统的高可用能力,如果有多个机器的话,当一个机器挂了,还有其他机器可以提供服务,保证系统可用。
    • 故障恢复性:当故障恢复以后,系统可以和故障恢复之前一样运行,没有数据损失等。可以通过复制的能力实现,存储多个数据副本。
  3. 一致性(Consistency):语义是当put(k,v)以后,一定会get(k)能得到v。但是对于分布式系统来说这是不一定的。
    • 强一致性
    • 弱一致性

MapReduce

最开始是Google提出的MapReduce,这篇论文可以追溯到2004年。有兴趣的可以阅读这个论文:http://nil.csail.mit.edu/6.824/2020/papers/mapreduce.pdf

当时Google面临的问题是要对数TB的数据进行计算。因为他们要从海量的数据中找出优先级最高的页面展示出来。

他们迫切的希望用数千台计算机来共同完成,来加速这个工作,而不是用一台计算机独立完成。

MapReduce希望开发者只需要编写Map函数和Reduce函数,其他的交给MapReduce框架来做。将这些函数放到无数的计算机上执行。

核心思想:将输入分成多份,产生多个输入。并对每个输入调用Map函数。

Map

Map函数将输入内容进行处理,输出一组key=>value结构。你可以把 Map 理解成分类处理的过程。

示例1:Map将从输入中统计每个英文字母出现的次数。

1
2
3
Input1 => Map => 输出:a:1, b:1, c:0
Input2 => Map => 输出:a:0, b:1, c:1
Input3 => Map => 输出:a:1, b:0, c:0

简化的Map函数如下:
Map函数接收两个参数,k是文件名称,v是文件内容

1
2
3
4
5
6
7
8
9
10
11
12
split file // 拆分文件内容
Map(k, v){
// 循环输出每个字母
for(w:words) {
//3. 输出格式同样是k=》v,k是字母,v是出现次数。
// emit函数是输出函数,由MapReduce框架提供
// 为什么输出1?因为是简单的计数,每个字母出现1次
emit(w,1);
}
}

// 实际输出结果:a:1, b:1, b:1

Reduce

Map 阶段产生的键值对,按 key 分组并聚合处理,得到最终的结果。

你可以把 Reduce 理解成对同一类的东西做总结的过程。

而Reduce函数同样接收输入,在这个示例中,我们的Reduce函数可以接收某一个字母和出现的次数做为输入,输出总的出现次数。比如:

1
2
3
Input1 (a:1, a:0, a:1) => Reduce => 输出:a:2
Input2 (b:1, b:1, b:1) => Reduce => 输出:b:3
Input3 (c:0, c:1, c:0) => Reduce => 输出:c:1

简化的Reduce函数如下:
Reduce函数同样接收文件做为输入。

1
2
3
4
5
6
// reduce函数的输入,k是用来聚合的key,在这里,这个k就是字母,可能是a,b,c。对于计算来说,用不到这个k,v是map输出的值的list。
// 比如a:1, a:0, a:1, k是a, v是[1,1]
Reduce(k, v) {
// 直接输出计数即可 因为list的长度就是计数,这是因为每个值都是1.
emit(len(v));
}

这样就计算出了所有输出中,abc三个字母出现的次数。

最妙的设计在于,按照上述的例子,我们可以部署6个机器来同时完成任务。

而且,对于Map和Reduce函数来说,优点有两个:

  1. 逻辑简单,仅仅是简单的计算逻辑。因此运行速度快
  2. 可以方便的横向扩展。

最关键的点在于

1
程序员只需关心逻辑,不用操心分布、容错、调度等复杂细节

整体执行流程

[执行流程图片]

  1. 输入数据被分片(Split)
  • 原始的大数据(比如 1TB 的日志)被切分成多个小片(通常 64MB 或 128MB 一片)。
  • 每个数据片(split)会由一个 Map Task 处理。

👉 比喻:像把一本厚书分成一页一页,由多个读者同时阅读处理。

  1. Map 阶段执行(并行执行)
  • 系统在多台机器上启动多个 Map Worker,每个负责一个 split。
  • 每个 Map Worker:
    • 读取数据片
    • 执行用户定义的 Map() 函数
    • 输出一组键值对 (key, value)
    • 把输出缓存在本地磁盘上,并根据 key 做分区(为接下来的 Reduce 做准备)

👉 比喻:每位工人处理一摞原材料,并将成果放入不同颜色的桶(按 key 分类)。

  1. 分区与 Shuffle(洗牌阶段)
  • 系统自动将所有 Map 的输出,按 key 分发给不同的 Reduce Worker
  • 这个过程称为 shuffle,是 MapReduce 的核心。
  • Reduce Worker 从各个 Map Worker 取自己负责的那一部分 key。

👉 比喻:每个桶被送到对应的收集员手里,收集员只关心自己那种颜色的桶。

  1. Reduce 阶段执行

每个 Reduce Worker:

  • 接收所有属于自己负责 key 的 (key, [value list])
  • 执行 Reduce() 函数,输出最终结果

👉 比喻:每个收集员把收到的同一类物品合并、统计或总结。

  1. 结果输出
  • Reduce 结果被写入分布式文件系统(如 GFS 或 HDFS)
  • 每个 Reduce Worker 写一个文件,形成最终的输出集合。
  1. 容错机制(Fault Tolerance)
  • MapReduce 最大的优势之一是它对机器故障有强大容错支持:
  • 任务失败了?——Master 会把任务重新分配给另一台机器。
  • 机器宕机?——系统检测心跳超时,把任务转移。

Reduce 不会从内存里读数据,而是从 Map 的本地磁盘拉,这样更安全。

👉 比喻:如果一个工人累了/走了,另一个人接手继续干,不影响整体结果。

容错机制

这是 MapReduce 的亮点之一,它自动处理各种失败情况:

🧯 Map 或 Reduce 任务失败
- Worker 崩了?任务失败?
- ✅ Master 重新调度任务,由其他空闲 Worker 重做

💀 Worker 节点宕机
- Master 检测不到心跳信号(比如 10 秒没回应)
- ✅ 所有该节点上的任务都会被视为失败,重新调度

📉 Reduce 不会因为 Map 崩了而挂掉
- 因为 Map 的中间结果会写入磁盘,且 Reduce 是拉数据

优化点 说明
数据本地性 尽量将 Map 任务调度到数据所在的机器,减少网络传输
备份任务(Backup Tasks) 在任务快结束时,为剩余最慢的任务启动副本,避免尾部拖慢整个任务(称为“straggler mitigation”)
流水线执行 Reduce Worker 可以在 Map 未完全结束时,开始拉部分数据
特性 好处
主从架构(Master/Worker) 易于调度和管理
本地磁盘缓存中间结果 提高容错性和效率
Shuffle 自动进行 程序员无需处理网络传输
容错机制完备 任意节点失败不会影响整体任务
自动调度和重试 解放程序员双手

性能

🧪 案例1:构建倒排索引(Inverted Index)
🌟 应用背景:
Google 搜索引擎需要知道每个词在哪些网页中出现。这个操作就叫“构建倒排索引”。

📦 处理规模:

  • 输入数据:约 20TB(网页内容)
  • Map 任务数:1万个
  • Reduce 任务数:2千个

⏱ 执行时间:
整个任务在几百台机器上并行,只花了几小时

✅ 意义:
传统方式实现这样的任务要花几周甚至几个月,而 MapReduce 能快速完成,还能处理节点故障。

🧪 案例2:分析网页连接图(PageRank 计算)
🌟 应用背景:
PageRank 是 Google 搜索排名的核心算法,需要处理整个互联网的网页链接关系。

📦 处理规模:

  • 输入数据:超过 1TB 的链接图
  • 运行多个 MapReduce 迭代(每一轮都读取+写入)

⏱ 执行时间:
单轮耗时在几十分钟到几小时之间,取决于迭代次数

✅ 意义:
MapReduce 适合这种需要反复运行、聚合中间结果的图算法。

可扩展性实验

论文还专门做了 实验测试 MapReduce 的可扩展性,结果非常亮眼:

实验设置:

  • 任务:排序 1TB 的数据(标准大数据计算任务)
  • 测试变量:机器数量(从几十台到几百台)
机器数量 执行时间
100 台 ~60 分钟
200 台 ~35 分钟
400 台 ~20 分钟

✅ 说明:机器数量翻倍 → 执行时间几乎减半
这叫做“近线性扩展性”,是分布式系统性能的理想状态。

容错能力实验

论文还测试了在有机器故障的情况下系统能否稳住:

实验方法:

  • 在运行中故意杀掉部分 Worker
  • 查看任务是否恢复 + 时间是否增加很多

结果:

  • 系统能成功恢复失败任务
  • 整体执行时间仅略有增加(因为失败重试带来小延迟)

✅ 意义:说明 MapReduce 的容错机制在实践中可靠,不会因为单点失败拖垮整个任务。

一些优化细节

优化策略 效果
本地性调度(Data Locality) 避免 Map 任务跨机器读取数据,减轻网络负担
Map 输出写入本地磁盘 避免 Reduce 拉取失败,提高稳定性
Backup Task(备份任务) 减少 straggler 影响,加快尾部执行
Reduce 端部分排序 避免 Reduce 端内存爆炸,提高聚合效率

小结:为什么 MapReduce 性能优秀?

方面 优势
并行计算 成千上万台机器并发执行任务
任务分片合理 拆成很多小任务,调度灵活
自动容错 节点失败不会拖垮任务
IO 优化好 避免不必要的网络流量
扩展性强 机器越多,速度越快,效率不降反升

经验

  1. 编程模型简单但表达力强
    作者观点:

    MapReduce 的接口非常简单(就两个函数:Map() 和 Reduce()),但几乎可以表达大部分并行数据处理逻辑。

实际例子:

  • 排序、去重、合并日志
  • 构建索引、计算网页权重、图处理
  • 数据挖掘任务如聚类、统计分析

🧠 体会:
你不需要了解线程、锁、通信协议这些“硬核分布式知识”,也能写出能在几千台机器上跑的大数据程序。

  1. 对“失败”高度容忍是必须的
    作者观点:

    在几百上千台机器上运行任务,机器故障是常态,不是例外。系统设计要“默认它会失败”。

做法:

  • Map/Reduce Task 自动重试
  • Master 负责监控和再调度
  • 中间结果写磁盘、持久化,方便恢复

🧠 体会:
不要去“防止失败”,要“拥抱失败”,让失败变得对用户透明,这才是工业级分布式系统。

  1. 数据本地性是性能关键
    作者观点:

    尽量把计算调度到数据所在机器,可以显著减少网络压力。

原因:

  • 在 Google 文件系统(GFS)中,数据有副本
  • Master 可以根据副本位置,把 Map 任务调到数据“身边”

🧠 体会:
在分布式系统中,“移动计算”比“移动数据”更高效

  1. Straggler 问题是真实存在的
    作者观点:

    在成百上千个任务中,总会有几个“掉队者”(straggler),它们可能因为磁盘慢、CPU 抢占等原因拖慢整个作业。

解决方案:

  • 启动 Backup Task(备份任务)
  • 哪个先完成就用哪个,放弃另一个

🧠 体会:
在大规模并发中,整体速度由“最慢的少数人”决定(这就是“长尾延迟”问题)

  1. 开发调试工具非常重要
    作者观点:

    运行成千上万个任务后,你很难靠肉眼看日志找问题,需要专门的 监控与调试工具。

Google 实践:

  • 为每个任务生成详细的 web 页面
  • 可以追踪任务状态、失败原因、数据流向
  • 所有任务的标准输出也会被收集并存档

🧠 体会:
好的工具不仅能“看见”问题,更能“预防”问题。

  1. 通用性强,支持跨部门复用
    作者观点:

    最开始 MapReduce 是为构建索引设计的,后来被应用于:

  • 日志分析
  • 机器学习数据预处理
  • 图结构计算
  • 分布式 Grep、排序、压缩
  • 多语言支持(C++、Java、Python 等)

🧠 体会:
一个简单的思想,配上良好封装与容错机制,就能成为全公司的“生产力工具”

✅ 最后,作者对读者说了什么?
他们希望告诉大家:

“MapReduce 的核心思想是抽象:程序员只需要关注如何写 Map 和 Reduce,不需要去处理分布式的复杂性。”

这种思想不仅影响了后来的 Hadoop/Spark/Flink,也启发了很多 “让人类专注业务逻辑,其余交给系统” 的工程思维。

总结:

教训/反思 含义
简单接口胜过复杂灵活 简单更易学更普及
容错不是加上去的,是设计进来的 面向失败编程
调度比你想象的重要 数据本地性和长尾问题会拖垮系统
工具让大规模系统可维护 千万别忽视监控、调试界面
通用性不是副产物,是目标 抽象设计时就考虑不同场景

和其他的对比

🧭 MapReduce 提出前,世界在干什么?
在 MapReduce 出现之前,“处理海量数据”是非常痛苦的事情,常常需要:

  • 自己手写分布式代码(多线程、RPC、容错逻辑)
  • 手动分片、调度、失败重试
  • 大量系统调优

也就是说:门槛高、出错多、效率低。

🧓 1. 前辈系统(先驱者)
MapReduce 借鉴并超越了很多已有的系统。作者提到了几个重要的前辈:

🧱 Parallel Databases(并行数据库系统)
比如:Teradata, Gamma, Volcano

  • 通过 SQL 自动并行执行、查询优化

但局限性明显:

  • 灵活性低,只适合结构化数据
  • 编程模型不够通用(不能表达复杂业务逻辑)
  • 扩展性不足(难以横向扩展到上千台机器)

MapReduce 与之不同:

  • 不需要预定义 schema
  • 可处理任意数据(文本、图像、日志)
  • 扩展性和容错机制是核心设计点

🧑‍🔧 Message Passing Systems(消息传递系统)
比如 MPI(Message Passing Interface)

  • 程序员手动控制数据传输、任务调度
  • 常用于科学计算、模拟类应用

缺点:

  • 编程复杂(需要手动处理并发、同步)
  • 容错性差(一个节点挂掉,全盘失败)
  • 不适合动态大规模分布式系统

MapReduce 优势:

  • 自动分发任务与数据
  • 自动重试失败任务
  • 容错、调度机制隐藏在框架里

🧑‍🏫 2. 编程模型的灵感来源
📚 Lisp、Functional Programming 的 Map 和 Reduce
“Map”和“Reduce”其实来自函数式编程语言 Lisp 的标准操作:

  • map(f, list):对列表中每个元素应用函数 f
  • reduce(f, list):将列表聚合为一个值(如求和)

作者把这个小而美的思想推广到了分布式系统中:

  • 把一个“大列表”切成几千块,每块并发 map
  • 最后汇总(reduce)各部分结果

创新点在于:

不是函数名的新瓶装旧酒,而是加上了调度、分布式运行、容错、持久化、分区等“工程魂”。
把“函数式思想”变成了“工业级工具”。

MapReduce 是在 Google 内部“全家桶式架构”中运行的,依赖以下底层支撑:

系统 作用
GFS(Google File System) 存储海量数据块,支持副本、高可用
Bigtable 类似 NoSQL 的结构化数据存储
Scheduler + Monitoring 提供任务调度与健康监控能力

一些其他的系统:

系统 简介 特点
Hadoop MapReduce Apache 开源实现 模仿 Google MapReduce,支持 HDFS
Dryad(微软) 更灵活的数据流图模型 支持 DAG,但复杂度也更高
Spark 更快的内存计算模型 适合交互式、大规模迭代任务
Flink 强实时数据处理 支持流+批,语义更强
Beam 通用数据处理 API 可部署到 Spark/Flink 等系统之上

对比:

角度 MapReduce 相比如何?
与并行数据库相比 更灵活、可扩展、面向通用计算
与消息传递系统相比 更易用、具备自动容错
与函数式编程相比 加入工程实现,能在真实集群跑
与后续系统相比 是“大数据系统”的思想源头,影响深远

MapReduce 的贡献不是提出了什么新理论,而是把“分布式计算”这件复杂的事做得像“写两个函数”那么简单,并真正让它在几千台机器上跑起来。

为什么这门课要使用GO语言

这门课之前使用过C++进行。

使用GO的原因如下:

  1. GO有现成的RPC包,而C++没有。
  2. GO有线程和垃圾回收的支持。而C++需要自己管理内存进行垃圾回收。
    • 因此,GO更加安全。
    • GO更加简单。
    • GO更不容易出错。
  3. GO更加简单,错误处理也更加容易,C++的错误信息很难看出来是什么错误。

这里指的线程是GO的协程。GO Routine。

线程是分布式最大的难题。

使用线程的原因:

  • IO并发:不同的程序可以处于不同的状态。比如A线程在读取磁盘信息,而B线程在执行计算。比如很多线程发送了RPC请求,等待请求响应后进行处理。
  • 多核并行性:当遇到大量的计算任务时,使用多线程同时计算会显著提高效率。两个线程会同时运行在不同的CPU上面。
  • 便捷性:可能你就是希望在后台执行某些操作,或者定时执行某些操作。比如master用来确认其他的线程的存活状态,定时每秒发送一个请求这样。

事件驱动:除了使用线程以外,还可以使用事件驱动来实现。

  • 优点:事件驱动的实现比线程更加高效,更好调试,可以用顺序的方式来编程。
  • 缺点:事件驱动只能实现并发性,而不能实现多核的并行性。无法发挥多核性能。
    • 当然了,可以通过在每个核心上启动一个线程来实现事件驱动来发挥多核性能。

线程开发的挑战:

  1. 内存共享:多个线程共享同样的内存数据,会产生数据竞争
    • 解决办法1:使用锁,但是这会导致锁开销,还要解决可能得死锁问题。有些内部的数据结构可能并不需要锁,但是你不得不支付锁的开销。这并不总是一个好主意。
    • 解决办法2:使数据不共享。
  2. 协调:当我们使用锁的时候,涉及的不同线程可能不知道其他线程的存在,他们只是想不在任何人干扰的情况下获取数据。但也有时候,你希望线程知道其他线程的存在,比如一个线程等待另一个线程完成后读取它的数据。
    • 可以使用Channels来通信
    • 使用条件变量来通信
    • 使用Wait Group来通信
  3. 死锁:两个线程互相等待对方释放锁。

GFS(谷歌文件系统)

主要是Big Storage大型分布式存储系统。为分布式系统提供底层的存储功能。

将存储的数据放到多个机器上面。

有趣的循环:

  • 性能:将数据分散到多个机器上面。通常叫做分片
  • 错误:当数据分散到多个机器上面,其中一个机器就有可能宕机,因此需要容错性
  • 容错性:可以通过存储多个副本来解决容错性的问题。
  • 副本:存储多个副本又会引入数据不一致的问题。
  • 一致性:如果需要数据强一致性。又会需要牺牲性能。

强一致性

来一个小示例:

对于一个简单的服务器来说,没有分布式的功能。

这个时候两个客户端同时发来了请求。

  • C1 写入x的值为1
  • C2 写入x的值为2

请问,这个时候服务器的x值应该是多少?

  • 答案是不确定。

但是对于后面的所有读请求来说,x的值要么都是1,要么都是2.

对于分布式系统来说,只要能达到这个效果即可。

复制版本1(不好的复制设计)

对于最简单的复制来说,就是直接启动两个服务器,S1和S2。

对于所有客户端来说,都分别请求S1和S2进行写入,但是读的时候只读S1.当S1宕机以后读取S2.

这个时候两个客户端同时发来了请求。

  • C1 请求S1 写入x的值为1
  • C2 请求S2 写入x的值为2
  • C1 请求S2 写入x的值为1
  • C2 请求S1 写入x的值为2

客户端写入

那么最终,S1存储x的值为2.S2存储x的值为1.也就产生了数据不一致。所以这是个最简单但也不好的复制设计。

GFS架构

GFS将一个文件分成多个,每个块是64MB大小。每个块都可以存在不同的服务器上面。

有一个主服务器,主服务器负责分发请求,记录了文件名称文件块的映射。

还有多个块服务器。块服务器存储了实际的块数据。

主服务器存储了以下数据:

  • 文件名称和chunk handles数组的映射。也就是每个文件分成了哪些块。这个数据是需要持久化的。
  • chunk handles和chunk servers数组的映射,也就是每个块存在哪些块服务器上。这是因为每个块都是有副本的,所以是一个服务器数组。并且客户端可以选择最近的服务器进行获取。
  • 服务器版本,需要持久化。
  • 是否是主服务器。
  • 任期结束时间。

GFS的持久化

GFS使用Log来记录持久化的信息,并通过check point来进行辅助。恢复的时候只需要从check point恢复就可以了。

其实大多数的存储系统都是这么干的。

GFS读取过程

读取过程

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

MySQL零基础教程

本教程为零基础教程,零基础小白也可以直接学习,有基础的可以跳到后面的原理篇学习。
基础概念和SQL已经更新完成。

接下来是应用篇,应用篇的内容大致如下图所示。

概念学习

ER图

接下来讲讲ER图,这个是在实际工作中也会使用到的东西。

大家应该都听过ER图,全称是(Entity Relationship)实体关系图。

借用维基百科的介绍:

ER模型,全称为实体联系模型、实体关系模型或实体联系模式图(ERM)(英语:Entity-relationship model)由美籍台湾人计算机科学家陈品山发明,是概念数据模型的高层描述所使用的数据模型或模式图。

ER图通常用在设计数据库的阶段,当我们接到一个需求以后,我们要进行一些技术设计,在这个阶段如果涉及到一些对于数据库的修改,我们可以使用ER图进行设计。

万事万物都有两面性,因此我们看一下好处和坏处,如何使用由大家自己权衡。

  • 好处:好处是设计直观、在设计之后也方便给大家讲为什么这样设计,还有留存文档,当后续需要修改设计的时候直接修改即可。
  • 坏处:坏处就是需要花时间去画ER图了。

ER图的基本概念

ER图的主要组成部分

  • 实体(Entity):
    • 实体是数据库中具有相同属性集合的对象。例如,学生、课程、教师等。
    • 在ER图中,实体通常用矩形表示,矩形内写上实体的名称。
  • 属性(Attribute):
    • 属性是实体的特征或性质。例如,学生的属性可以包括学号、姓名、年龄等。
    • 在ER图中,属性通常用椭圆表示,椭圆内写上属性的名称,并用线连接到对应的实体。

简单来讲,实体就是一个表,属性就是表里面的一个字段。

下图就是一个ER图实体管理员用户属性有头像、密码、登录名、ID、邮箱、手机号。

概念学习

  • 关系(Relationship):
    • 关系描述了实体之间的联系。例如,学生和课程之间的关系可以是选修。
    • 在ER图中,关系通常用菱形表示,菱形内写上关系的名称,并用线连接到相关的实体。
  • 关系的类型:
    • 一对一关系(1:1):一个实体与另一个实体之间存在一对一的联系。例如,一个学生对应一个学号。
    • 一对多关系(1:N):一个实体与多个实体之间存在联系。例如,一个教师可以教授多个课程。
    • 多对多关系(M:N):多个实体与多个实体之间存在联系。例如,一个学生可以选修多个课程,一个课程也可以被多个学生选修。

下图就是一个ER图,实体是管理员用户和角色两个。关系拥有,表示管理员用户拥有角色的关系。关系的类型用m和n表示多对多关系。意思是一个管理员用户可以拥有多个角色,一个角色也可以被多个管理员用户拥有。

概念学习

关系这个东西,简单来说就是通过两个字段链接进行实现的。

多对多关系

比如我们有角色表和管理员用户表,这两个表,是多对多关系。

所以我们就需要用一张单独的表来存储这个关系。比如我们新建第三个表叫角色和管理员用户关系表。

角色和管理员用户关系表有字段内容如下:

  • id: 该表的主键。
  • roleId: 角色表的主键。用来关联角色信息。
  • adminId: 管理员用户表的主键。用来关联管理员用户信息。

可以看到,这个关系就是通过角色和管理员用户关系的两个字段来表示的。

角色表内容如下:

id name
1 前台角色
2 行政角色
3 开发角色

管理员用户表内容如下:

id name
1 张三
2 李四

角色和管理员用户关系表内容如下:

根据表的数据我们很清晰的能看出来,张三既拥有前台角色也拥有行政角色,可谓身兼数职。当代牛马。李四则拥有一个开发角色

id roleId adminId
1 1 1
2 2 1
3 1 2

记住上面说的,通过两个字段链接进行实现的。

这里的两个字段就是roleIdadminId

roleId = 1表示的是角色表中,id = 1前台角色,对应的数据是adminId = 1的数据,也就是管理员表中id = 1的管理员张三

如此,我们就知道了,张三拥有角色:前台角色。

一对多关系

一对多关系多对一关系基本一样。就是反过来了。

一个老师可以教授多个课程,比如我们有两个表,一个老师表,一个课程表

一对多关系比较灵活,记住上面说的,通过两个字段链接进行实现的。

先看老师表的数据如下:

id name
1 张老师
2 李老师

课程表的数据如下:

id name teacher_id
1 语文课程 1
2 数学课程 2
3 英语课程 1

为什么说一对多关系比较灵活呢,可以看到,我们这里用到的关联字段是老师表的id和课程表的teacher_id。而不需要再多加一张表了。

这里要记住一个重点!!!!
多出来的这个teacher_id 字段要放在一对多关系中的 多 的这个表里面。

什么是一对多关系中的的关系呢?

比如上面一个老师教多个课程,那么的关系就是课程信息。

当然了,如果你想要多加一张表来存储这两个字段的话也是可以的。

比如增加下表,教授课程表,如果增加了这张表,就可以把课程表中的teacher_id字段删除了:

下表也代表了张老师教授语文课程和英语课程,李老师教授数学课程。

id course_id teacher_id
1 1 1
2 2 2
3 3 1

一对一关系

一对一关系是最简单的。其实算是一对多关系的一个真子集

为什么这么说呢?

我们假设,一个老师只能教授一个课程,那么上面的两个表是不是就变成了一对一关系呢?所以我们说一对一关系是一对多关系的一个真子集。

ER图实践

接下来我们来实践一下ER图。

我这里使用的工具是免费的在线画图工具Processon

多对多关系的ER图

首先来画出多对多关系的ER图吧。

以上面的三个表为例子。

我们需要两个实体和一个关系。

  • 实体1: 管理员实体,管理员实体后面会转化成管理员表。
  • 实体2: 角色实体,角色实体后面会转化成角色表。
  • 关系:两个实体之间的多对多关系,多对多关系后期需要转换成一张关系表。

我们首先画出第一个实体,管理员实体使用矩形表示。如下图。

概念学习

接下来我们给管理员实体增加属性,属性使用椭圆来表示,如下图。

概念学习

现在我们画出第二个实体,角色实体和对应的属性。

概念学习

接下来我们画出关系,关系使用棱形来表示,并且使用线连接两个实体,表示是这两个实体之间的关系。多对多关系还需要在两边分别标记上nm

此外,关系还要有一个名称,用来描述,比如管理员拥有角色,角色被管理员拥有。所以我们可以用拥有关系来描述这两个实体之间的关系。

概念学习

如此,我们就完成了一个多对多关系的ER图。

一对多关系的ER图

接下来再实践一次一对多关系的ER图,最好大家先动手画一次,再跟我们的结果来对比,只有自己实操了才行。

实操时间。。。。。

好了,实操结束。

我们来看一下一对多关系如何画图。

我们需要两个实体和一个关系。

  • 实体1: 教师实体,后面变成教师表。
  • 实体2: 课程实体,后面变成课程表。
  • 关系:两个实体之间的一对多关系,一对多关系可以是一个字段也可以是一个表。

首先画出第一个实体和属性,也就是教师实体。

概念学习

接下来画出第二个实体和属性,也就是课程实体。课程实体这里有一个属性是教师ID,这代表我们使用这个字段,而不是增加了一个关系表。如果我们没有这个字段就代表我们要增加一个关系表。

概念学习

最后,画出关系并连线。这里关系名称我们选择教授,这个其实无所谓。能明白就好。重点是要标明一对多关系,可以看到教师实体上写的是1,课程实体上写的是n,代表一对多关系中,教师是1,课程是多。这个不能写反了。

概念学习

多对一关系的画起来是一样的,需要注意的就是关系上的1和n别写错了。

一对一关系的ER图

一对一关系的ER图和上面的差不多,我们举个例子吧。

  • 实体1: 用户实体,转化成用户表,存储用户信息。
  • 实体2: 用户账户实体,转化成用户账户表,存储用户的账号资金信息。
  • 关系:一对一关系,一个用户只能拥有一个用户账户。

首先画出第一个实体,用户实体。属性包括ID、用户名称、手机号。

概念学习

接下来画出第二个实体,用户账户实体,属性包括ID、账户余额、用户ID。用户ID作为关联关系的字段,和一对多关系一样,就不需要单独加表了。

概念学习

最后,画出关系并连线,标上关系是一对一关系就可以了。

概念学习

总结

通常来说,ER图是在设计阶段完成的,先有ER图再有表结构。

可如果你已经有了表结构,有没有办法生成ER图呢?

也是有方法的,比如著名的Navicat工具,就支持这么做。

此外,还有一个方法,就是使用在线工具dbdiagram,这个工具可以导入现有的SQL,会生成ER图,如下。

概念学习

这个网站是通过左边的一个叫dbml的语言来生成ER图的,也支持直接导入SQL,转化成dbml格式再生成ER图。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

什么是领域驱动设计DDD

领域驱动设计(Domain-Driven Design,简称DDD)是由美国软件专家埃里克・埃文斯(Eric Evans)在2004年提出的软件设计方法论,旨在解决复杂软件系统开发过程中业务逻辑与技术实现之间的矛盾,提升软件系统的可维护性、可扩展性和灵活性。

说人话就是:

  • What: 它是一种设计思想、一种指导原则。
  • When: 设计微服务的时候,或者说,不知道怎么拆分微服务的时候。
  • Why:为什么要用它,上面其实说了,不知道怎么拆分微服务的时候,可以用它来指导你如何拆分微服务。
  • How:这个后面讲。

很多人都说,DDD是用来处理复杂业务逻辑的,那多复杂才算复杂业务呢?

这个问题,其实和微服务什么时候用是一个问题。

所有的技术都不是银弹。都有适合它的使用场景。

拿微服务来说,你一个小公司,就两三个开发,硬要上微服务,拆好几个服务出来,有什么意义吗?

是提升性能了?

是增加开发效率了?

都不是,你会发现拆分完以后,程序反而三高了。

  • 高复杂度:程序变得更加复杂了。
  • 高维护成本:程序的维护成本增加了、当有需求需要修改的时候、开发效率反而降低了。
  • 高运维成本:原来一台机器就满足了,你拆的服务多了,一台机器不够了。要么加机器性能要么加机器数量。

所以,适合很重要。

俗话说的好,见人说人话,见鬼说鬼话。技术也一样。

基本概念

  • 实体:使用充血模型实现的实体,既有属性、也有方法。
  • 值对象:只有属性的类。
  • 聚合根:一个特殊的实体,聚合的入口。
  • 聚合:聚合是一个概念、也可以理解成一个模块。聚合内包含了聚合根、实体、值对象。
  • 限界上下文:分割领域的边界、也是分割微服务的边界,通过这个边界明确这个接口属于哪个领域,也就是属于哪个微服务。每个领域有每个领域的上下文。
  • 领域:领域也就是我们的领域模型,也可以是一个微服务。
  • 子领域:一个领域可以分成多个子领域。这个就是粒度的问题了。
  • 领域事件:领域之间通信的方法。通过这个来调用其他的微服务。

还有一些核心领域、支撑领域、通用领域等,都是领域的一种,作用不同而已。

领域

一个领域里面包含了多个子领域,如图所示。

ddd1-3

一个子领域里面包含了多个聚合,每个聚合里又有一个聚合根作为入口,还有若干个实体值对象

ddd1-4

那领域到底是什么?子领域又是什么?我们该如何划分领域?

如果大家看过仙侠小说、电视剧等,应该听过一些词,比如神之领域绝对领域恶魔的领地领主等等。

我们可以简单的把领域理解成领地、封地、画地为牢。

皇上将方圆百里的地分给你了,让你当这片封地的领主,那么这方圆百里就是你的领域了

那服务的领域是什么样子的呢?

同样的,我们把完整的服务想象成中国的领域,然后划分成各个省份。子领域就是每个省份下面的市区,再有子领域就是市区下面的县。

关于子领域也有一些划分,比如

  • 核心领域:核心领域是业务的核心竞争力所在,直接体现业务的独特价值。领域中的核心业务,核心竞争力,领域的重点。比如湖北省这个领域,他的核心子领域就是武汉市
  • 通用领域:通用领域是指那些在多个业务中都可以复用的领域,通常不包含业务特有的逻辑。一个领域中通用的能力,可以放到通用领域。比如湖北省这个领域,他的通用领域就有交通。因为不管是哪个市,下面都会用到交通,交通还可以划分为地铁、公交、高铁、火车等。
  • 支撑领域:既不属于核心领域、也不是通用领域。但是又必不可少的领域。为核心领域提供支持的领域,虽然重要但不直接体现业务的核心价值。

核心领域

特征:

  • 对业务成功至关重要。
  • 具有高复杂性和高价值。
  • 通常需要投入最多的资源和精力。
  • 一个领域中的核心,业务的重点发展对象。

为什么现在很多人开始使用DDD?无外乎是因为DDD更加契合业务,可以随着业务形态的改变,来带动代码的改变。

比如一个电商公司,一开始是以产品质量作为卖点,那么他们的核心领域就是产品质量领域。或者说商品领域。而商品领域再进行划分子领域,他们的核心子领域就是质量领域

而另外一家公司虽然也是电商公司,但是主要依靠广告业务赚钱,那么他们的核心领域就是广告领域

同样的,核心领域是会变化的,比如第一家公司一开始的产品卖点是质量过硬。后来做大了以后主要依靠流量的增涨来扩大收益,那么核心领域就会变为流量领域

在演进到后面,可能如何为用户推荐合适的商品来促成交易就变成了业务重点,那么这个时候的核心领域就是推荐算法领域了。

具体哪个领域是核心领域,需要大家一起讨论、深入了解公司的业务。

这样才能决定出核心领域。

通用领域

特征:

  • 低复杂性,易于复用。
  • 通常可以通过第三方工具或框架实现。
  • 不需要大量定制开发。

通用领域顾名思义,就是提供一些通用能力的领域,比如发送消息、权限认证等等。

这个根据公司的业务来看的。

比如一些公司需要签合同使用电子签章,那么这也算是一个通用的功能,可以是一个通用领域。

再比如常用的限流功能。也可以是一个通用领域。

支撑领域

特征:

  • 中等复杂性和中等价值。
  • 主要用于辅助核心领域的实现。
  • 可以通过一定程度的定制开发满足需求。
  • 支撑领域既不属于核心领域

支撑领域和通用领域不同的点就在于它和业务有一些关系。

比如,对于电商公司来说,核心可能是商品领域,那么为商品领域提供支撑的业务领域就是订单领域库存领域物流领域等。

如何划分不同的领域

  • 关注业务价值
    • 优先识别对业务成功最重要的部分,这通常是核心领域。
    • 核心领域的设计和实现需要投入最多的资源。
  • 关注领域的独立性
    • 每个领域应尽量独立,避免领域之间的强耦合。
    • 独立性强的领域更容易划分为限界上下文(Bounded Context)。
  • 关注领域的复用性
    • 通用领域应尽量复用现有的工具或框架,避免重复开发。
    • 支撑领域可以适当定制,但不应过度复杂化。
  • 动态调整
    • 领域划分并非一成不变,随着业务发展和需求变化,领域的类型可能需要重新评估和调整。

领域的划分是前期的重要工作,只有领域划分好了,后续的路才好走,如果领域划分的不对,那么后续还需要重构,就会比较浪费时间。

一般来说,领域划分都是首先通过事件风暴来进行的,参与整个事件暴风的人包括领域专家、业务专家、研发、架构师、产品等等。

须知人力有尽时,一个人的想法终归是有限的,只有集思广益,才是长久之道。大家在事件风暴之中应该畅所欲言、没有上下级的关系,如此方可,要不然很多人摄于上级的压迫,不敢说话,或是说了以后被上级否定,这样的话是不利于事件风暴进行的。

在事件风暴之中应该鼓励大家发言,不要去盲目的否定,更不要搞什么一言堂。

通过事件风暴我们可以识别出领域、子领域,还有聚合、事件等。

接下来我们就需要判断哪些是核心领域,哪些是通用领域,哪些是支撑领域。

有些人可能会问,为什么要划分出核心领域、支撑领域、通用领域呢?

这是因为大家的精力有限,要把大部分的精力投入到最重要的事情上去,也就是放到核心领域的建设上面。至于通用领域和支撑领域,如果人手不够也可以通过外包解决。

毕竟,核心领域才是最重要的领域。

上面也说过了,公司的不同时期,关注的业务重点不同,那么核心领域也会变化。

同样的,哪怕都是电商公司,他们的核心领域也不一定一样,一切都要看具体的业务。

而我们的重点就是要放在核心领域的建设上面。

总结

我们主要介绍了领域的概念,包括如何划分,还有核心领域、通用领域以及支撑领域等。还介绍了一些DDD的基本概念。

领域的核心思想就是两点

  • 将我们的业务自顶向下的进行细分,逐步的拆解。
  • 划分出不同的领域,将我们有限的精力投入到最重要的事情当中。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

什么是领域驱动设计DDD

领域驱动设计(Domain-Driven Design,简称DDD)是由美国软件专家埃里克・埃文斯(Eric Evans)在2004年提出的软件设计方法论,旨在解决复杂软件系统开发过程中业务逻辑与技术实现之间的矛盾,提升软件系统的可维护性、可扩展性和灵活性。

说人话就是:

  • What: 它是一种设计思想、一种指导原则。
  • When: 设计微服务的时候,或者说,不知道怎么拆分微服务的时候。
  • Why:为什么要用它,上面其实说了,不知道怎么拆分微服务的时候,可以用它来指导你如何拆分微服务。
  • How:这个后面讲。

很多人都说,DDD是用来处理复杂业务逻辑的,那多复杂才算复杂业务呢?

这个问题,其实和微服务什么时候用是一个问题。

所有的技术都不是银弹。都有适合它的使用场景。

拿微服务来说,你一个小公司,就两三个开发,硬要上微服务,拆好几个服务出来,有什么意义吗?

是提升性能了?

是增加开发效率了?

都不是,你会发现拆分完以后,程序反而三高了。

  • 高复杂度:程序变得更加复杂了。
  • 高维护成本:程序的维护成本增加了、当有需求需要修改的时候、开发效率反而降低了。
  • 高运维成本:原来一台机器就满足了,你拆的服务多了,一台机器不够了。要么加机器性能要么加机器数量。

所以,适合很重要。

俗话说的好,见人说人话,见鬼说鬼话。技术也一样。

基本概念

  • 实体:使用充血模型实现的实体,既有属性、也有方法。
  • 值对象:只有属性的类。
  • 聚合根:一个特殊的实体,聚合的入口。
  • 聚合:聚合是一个概念、也可以理解成一个模块。聚合内包含了聚合根、实体、值对象。
  • 限界上下文:分割领域的边界、也是分割微服务的边界,通过这个边界明确这个接口属于哪个领域,也就是属于哪个微服务。每个领域有每个领域的上下文。
  • 领域:领域也就是我们的领域模型,也可以是一个微服务。
  • 子领域:一个领域可以分成多个子领域。这个就是粒度的问题了。
  • 领域事件:领域之间通信的方法。通过这个来调用其他的微服务。

还有一些核心领域、支撑领域、通用领域等,都是领域的一种,作用不同而已。

聚合

总结

我们主要介绍了领域的概念,包括如何划分,还有核心领域、通用领域以及支撑领域等。还介绍了一些DDD的基本概念。

领域的核心思想就是两点

  • 将我们的业务自顶向下的进行细分,逐步的拆解。
  • 划分出不同的领域,将我们有限的精力投入到最重要的事情当中。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

什么是领域驱动设计DDD

领域驱动设计(Domain-Driven Design,简称DDD)是由美国软件专家埃里克・埃文斯(Eric Evans)在2004年提出的软件设计方法论,旨在解决复杂软件系统开发过程中业务逻辑与技术实现之间的矛盾,提升软件系统的可维护性、可扩展性和灵活性。

说人话就是:

  • What: 它是一种设计思想、一种指导原则。
  • When: 设计微服务的时候,或者说,不知道怎么拆分微服务的时候。
  • Why:为什么要用它,上面其实说了,不知道怎么拆分微服务的时候,可以用它来指导你如何拆分微服务。
  • How:这个后面讲。

很多人都说,DDD是用来处理复杂业务逻辑的,那多复杂才算复杂业务呢?

这个问题,其实和微服务什么时候用是一个问题。

所有的技术都不是银弹。都有适合它的使用场景。

拿微服务来说,你一个小公司,就两三个开发,硬要上微服务,拆好几个服务出来,有什么意义吗?

是提升性能了?

是增加开发效率了?

都不是,你会发现拆分完以后,程序反而三高了。

  • 高复杂度:程序变得更加复杂了。
  • 高维护成本:程序的维护成本增加了、当有需求需要修改的时候、开发效率反而降低了。
  • 高运维成本:原来一台机器就满足了,你拆的服务多了,一台机器不够了。要么加机器性能要么加机器数量。

所以,适合很重要。

俗话说的好,见人说人话,见鬼说鬼话。技术也一样。

为什么大厂都开始使用DDD了?

回到我们的问题,为什么大厂都开始使用DDD了?

因为大厂人傻钱多

不是,有的人会说,因为大厂的业务足够复杂

说对了一半。

大厂通过DDD来指导微服务的拆分,解决了复杂的业务逻辑。

这里面有一些点,我们再细细的拆分一下。

为什么要拆分微服务?

再问大家一个问题,为什么要拆分微服务?

这个问题,千人千面。没有标准答案。

但是呢,总的有一些所谓的最佳实践

  1. 当公司成长了,流量增长了,单机很难支撑了。

比如,你是一个做电商的公司,你某天的成交量突然飙升。如何解决?最简单的做法,扩容机器。

如果你是单机系统,你扩容的机器其实相当于扩容了整个系统,但是,你只有某几个接口的流量很大而已,其他的接口白白浪费了机器的成本。

再比如。你家公司的流量不是突然飙升,而是每天都很大,但是呢,仅限于交易模块。和上面的问题是一样的。

  1. 当公司成长了,需求变多了。

很多人开发一个项目,代码写的很乱,大家每次合并代码都会出现一堆冲突

如果你拆分成微服务的话,天然的限制和约束就可以减少冲突,因为粒度变小了。

至于为什么拆分微服务,不再赘述了。

如何拆分微服务?

先给大家看两个例子吧。

拆分方案1

小李在一家电商公司A,A公司目前的代码架构如下:

ddd1-1

现在,A公司说,要开始拆分微服务了。小李负责拆分微服务。

小李一想,这个很简单啊,直接拆呗,一个模块一个服务就行了。交易量大只需要扩容交易服务,很完美啊。

所以,拆分完成以后,架构如下:

ddd1-2

一开始,小李觉得挺好,但是,逐渐发现问题了。

依赖严重,一个购买接口要跨域多个微服务。

既要从用户服务获取用户信息,又要从商品服务获取商品信息,还要从支付服务进行支付,还要从库存服务扣减库存,等等。。。

导致链路很长,接口的响应速度反而降低了。因为网络请求太多了。

写代码的时候又发现问题了,原来呢,只需要写逻辑就行了,现在还要写RPC接口。开发效率也降低了。

最后吧,代码写完了,又发现问题了,这事务怎么处理啊,只能上分布式事务了。开发成本又上去了。

结果就是,小李被领导一顿臭骂。

小李不语,只是默默承受着。。。

拆分方案2

小李觉得诸事不顺,跳槽去了另外一家电商公司B。

B公司也要拆分微服务了,领导见小李有过拆分微服务的经验,就将这个重要的任务交给了小李。

小李:。。。

小李无奈、只好继续重操旧业。

有了上次的失败经验,小李也学聪明了。

小李接下来复盘了上次的问题:

  1. 微服务粒度不对
  2. 接口链路太长导致速度下降
  3. 分布式事务等导致开发效率下降,且分布式事务也导致响应时间增加。

小李痛定思痛。要解决这几个问题。

终于,小李想到了好主意。

我不按照模块拆分不就完了!!!

我按照业务拆分。

比如,购买接口就放在交易服务里面。

那么他需要用户信息的时候自己取,不调用用户服务了,其他的逻辑也是。

这样确实解决了上面的一些问题,但是,购买还应该放在交易服务里面吗?

当需要增加一个接口的时候,我们如何判断它属于哪个服务呢?

小李又陷入了另外一个问题。

最后的结果,导致微服务里面代码很乱。

使用DDD拆分微服务

上面的两个案例,不知道大家遇到过没有呢?

还有很多其他的错误案例,大抵意思都差不多,就是不知道如何正确的拆分微服务。

DDD就是干这个活的。

1
DDD是指导我们如何正确拆分微服务的一种方法。
DDD的一些基本概念

DDD里面有很多的概念,很难一下子说清楚,因此,这里简单介绍一下。

  • 实体:使用充血模型实现的实体,既有属性、也有方法。
  • 值对象:只有属性的类。
  • 聚合根:一个特殊的实体,聚合的入口。
  • 聚合:聚合是一个概念、也可以理解成一个模块。聚合内包含了聚合根、实体、值对象。
  • 限界上下文:分割领域的边界、也是分割微服务的边界,通过这个边界明确这个接口属于哪个领域,也就是属于哪个微服务。每个领域有每个领域的上下文。
  • 领域:领域也就是我们的领域模型,也可以是一个微服务。
  • 子领域:一个领域可以分成多个子领域。这个就是粒度的问题了。
  • 领域事件:领域之间通信的方法。通过这个来调用其他的微服务。

还有一些核心领域、支撑领域、通用领域等,都是领域的一种,作用不同而已。

一个领域里面包含了多个子领域,如图所示。

ddd1-3

一个子领域里面包含了多个聚合,每个聚合里又有一个聚合根作为入口,还有若干个实体值对象

ddd1-4

通过DDD我们可以来拆分微服务。

DDD是围绕业务概念来进行领域建模的,后续的接口也是根据业务概念来划分到对应的领域中。从而解决开发过程中,业务演进的问题。

因为当业务改变了,那么领域就改变了,对应的代码也就跟着变就好了。

所以DDD和微服务不一样,不是一种具体的架构,只是一种指导方法。

它可以划分出清晰的业务边界也就是微服务的边界,从而让微服务的拆分更加符合业务。而不是乱拆分。

他也有一些步骤

  • 战略设计:战略设计从业务的视角上看待问题,建立领域边界、划分领域、子领域等等。
  • 战术设计:战术设计从技术的视角上看待问题,将领域转化成微服务,将业务实体转化成代码实体等等。
  • 事件风暴:通过事件风暴,大家一起来想业务,并将业务转化成领域、子领域、领域事件、实体等等。
电子商务DDD示例

首先,需要进行事件风暴,梳理出一些和业务逻辑有关的词汇

以电商举例,用户购买者订单商品库存收货地址商家价格购买行为下单支付取消订单退款发货等等。

接下来进行一些识别

识别什么呢?识别上面的概念,也就是哪些是实体值对象领域事件

说的再简单一些,名词就是实体或者值对象。如果只有属性值的就是值对象。动词就是领域事件。

比如上面的这些里面,哪些是领域事件呢?

  • 下单
  • 支付
  • 取消订单
  • 购买
  • 退款
  • 发货

哪些是实体呢?

  • 用户
  • 购买者
  • 商家
  • 商品

哪些是值对象呢?

  • 价格:价格仅仅是一个属性,价格变化的时候就是整个值对象进行变化。
  • 收货地址:收货地址也仅仅是一个属性或者一些属性,没有方法。
  • 库存:库存也同样。

有些人看到这里就会有疑问了?用户和购买者不是一个东西吗?

这里就需要提到另外一个概念了。限界上下文

这个在上面简单介绍过。这里再说一下。

这个概念有两个意思

  • 边界:用来划分领域的边界,也就是微服务的边界。
  • 上下文:在划分的边界之内,有着上下文。同样的一个东西,在不同领域里面,也就是在不同的上下文环境中,意思是不一样的。

比如笨蛋这个词吧。

在小情侣你侬我侬之间说,哎呀,你个笨蛋。就是打情骂俏的意思。当然不是说你真的笨了。

那换一个环境呢,你在学习的时候,老师跟你说:你真是个笨蛋!。这里就是真的在说你笨了。

所以呢,不同的环境,不同的上下文里面,一个词语的意思是不一样的。

这里也是,都是用户,但是在浏览商品的时候他只是用户。但是在购买的时候,他就是购买者了。

这样,他就在两个领域里面存在了。在用户领域里面是用户实体,在交易领域里面是购买者实体。

接下来可以进行聚合操作,也就是将意思相近、内容相近的放到一起,放到一个聚合里面。再根据聚合的内容划分出领域、子领域等。

比如

  • 用户聚合:里面就包含了用户实体。
  • 商品聚合:里面包含了商品实体、价格值对象、库存值对象。
  • 订单聚合:里面包含了订单实体、购买者实体、收货地址值对象。

。。。

每个聚合里面还要有一个聚合根作为聚合的入口。

接下来将聚合划分到领域中就可以了。

比如

  • 用户领域:包含了用户聚合
  • 交易领域:包含了商品聚合和订单聚合。也可以划分成两个子领域,一个子领域包含一个聚合。

。。。

还有一些领域事件。作为通信。

比如

  • 下单领域事件:用户聚合发起事件 -》 订单聚合接受事件。完成下单操作。

接下来我们需要把上面梳理出来的内容映射到代码上。

比如,我们将上面的内容放到代码里面。这里面有两种方式,聚合是DDD中的最小单元,所以可以把一个聚合部署为一个微服务。

当然了,也可以一个领域作为一个微服务,具体的情况,根据自身业务和流量这些具体考虑就可以了。

我们这里以领域做为微服务来示例:

  • 用户微服务
  • 交易微服务

先看用户微服务的文件夹吧,因为分层放在了下面介绍,所以这里的代码我们仅展示领域层的代码。

  • userAgg: userAgg文件夹,代表了用户聚合。
    • core: 聚合的核心代码,包含了聚合根、实体、值对象。一个文件夹。
      • userAggRoot.java: 用户聚合根实体,是一个java文件,一个class类。
    • event: 聚合的一些领域事件代码。
      • buyEvent.java: 下单的领域事件。

这个实体采用的是充血模型

简单看一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class userAggRoot {
private Integer userId; //userId是这个实体中的一个值对象。
private String name; //name也是一个值对象。

public UserAggRoot getUserInfo(Integer userId) {
// 获取用户实体的信息
}

public void updateUserName(String name) {
// 更新用户名称
}

//getter和setter....
}

领域事件的代码,其实简单来说,因为跨越微服务了,所以可以直接通过消息队列来进行事件通信。

1
2
3
4
5
6
public class buyEvent {

public void buy() {
// 组装事件信息并且发送事件
}
}

再看一下交易微服务:

  • shopAgg: 代表了商品聚合,是一个文件夹
    • core:
      • shopAggRoot.java: 商品聚合根实体。
      • priceVO.java: 价格值对象的类,一个class。VO代表的是Value Object的意思。
      • inventoryVO.java: 库存值对象的类,一个class。
  • orderAgg: 代表了订单聚合,是一个文件夹
    • core:
      • orderAggRoot.java: 订单聚合根实体。
      • buyerEntity.java: 购买者实体,一个java的class。
      • addressVO.java: 收货地址值对象。
    • event:
      • orderEvent: 订单事件,发送订单事件可以给物流微服务,进行物流发货。还有积分服务进行积分处理等等。。。

代码类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class shopAggRoot {
private Integer shopId; // 商品id值对象
private String shopName; //商品名称值对象
private PriceVO price; //价格值对象
private InventoryVO inventory; //库存值对象

public void subInventory() {
// 扣减库存
// 1. 创建新的库存值对象

// 2. 替换inventory属性
// 。。。。
}
}

看一些值对象:

1
2
3
4
5
6
7
8
public class priceVO{
private BigDecimal originalPrice; //原价格
private BigDecimal currentPrice; //当前价格,可能是优惠后的价格等等
}

public class inventoryVO{
private Long inventory; //库存
}
DDD的分层架构

DDD的分层和传统的MVC分层不太一样。

传统的后端服务一般是这样的层次

  • controller:入口层
  • service:业务逻辑层
  • dao:数据层

DDD则是分成了下面四层

  • 用户接口层:也就是传统的入口层
  • 应用层:应用层调用领域层的聚合来完成操作,进行服务编排。可以调用多个聚合来共同完成操作。但是应用层和传统的业务逻辑层不同,它不负责完成业务逻辑,仅仅是服务编排,具体的业务逻辑由领域层实现。
  • 领域层:领域层是核心,包含了聚合、实体、值对象和聚合根。每个实体都是充血模型,包含了逻辑操作。
  • 基础层:基础层提供了基础服务,比如缓存、队列、数据库等等。

总结

我们简单的走了一遍DDD进行领域拆分。也让大家明白了为什么大厂会采用DDD。

因为大厂的微服务架构很完善了,他们的微服务复杂,使用DDD来指导微服务的拆分是一种有效的方法。

可以让代码和业务契合,当业务改变的时候代码随之改变。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。

七招教你玩转DeepSeek,我求求你别再花钱买课了!

最近这段时间DeepSeek越来越火,很多人都开始使用了,以至于DeepSeek频繁出现“服务器繁忙,请稍后再试”问题。

其实,随着前几年OpenAI发布了ChatGPT开始,这种生成式AI大模型就开始爆火。

到今年DeepSeek爆火,又一次点燃了大家的热情。

但是。。。。。。

很多人还是不知道该怎么和AI聊天,毕竟AI不是真人,大家都知道AI能力强大,可是如何用好它呢?

很多人在使用过程中都发现AI给的回答不理想,和自己想要的相差甚远。

这是AI的问题?还是使用者的问题呢?

这就是信息差,有些人已经用AI年入百万了,有些人还觉得AI能力太弱,回答的问题乱七八糟

网上还有很多人靠着信息差挣钱割韭菜。

很多付费教程多如牛毛,还是有很多人乐此不疲。

今天,大头免费教你们如何使用AI,解放他们真正的能力,不要再去花钱买课了,不要再当韭菜了,那些钱你买点好吃的好玩的给自己它不香吗?

为什么大家用不好AI?

大家在使用过程中,可以问问自己,为什么同样都是AI,别人用的AI跟爱迪生似的,自己用起来就跟人工智障一样?

有的人可能知道一点,就是Prompt,中文就是AI提示词。也就是你需要问的准确,AI才能给你想要的回答。

来看一个例子:

背景:你要和对象出去旅游,出发地是北京,目的地是成都。两个人打算玩五一5天。
需求:你想要AI给你出一个旅游规划。

大家通常会这么询问AI。

给我写一个去成都的旅游攻略

来看一下AI的回答。

deepseek3-2

乍一看,这个推荐也像这么回事,但是你仔细看一看,会发现AI回答的很宽泛

  • 给出了时间建议
  • 给出了景点建议
  • 给出了交通住宿建议
  • 给出了3天的旅游规划

可是再看我们的需求,明显不符合我们的需求啊。

问题出在哪呢?出在我们给AI的问题上面!

我们问的问题不明确,至少在AI的理解里面,我们的问题不明确!

第一招:问题要具体

第一招:问题要具体

你不能只告诉他给一个成都的旅游攻略,这就像你问别人一样,他也只能给你一个宽泛的回答,一些简单的推荐而已。

✅ 正确示范:我要和我对象出去旅游,从北京到成都,给我规划一个五一的旅游攻略。

这里交代清楚了时间、地点、人物要素。

deepseek3-1

看看AI的回答。跟上面比明显更好了!

想要更细致的回答,还有其他的招式。可以继续优化。

第二招:设定角色,让AI知道自己是谁

第二招:设定角色,让AI知道自己是谁

解释一下,你可以给AI设定一个角色,让他代入进去,那么他就会更擅长做某事。

大家都知道,JAVA开发更擅长JAVA、川菜大厨更擅长做川菜、成都本地导游更擅长做成都的旅游规划。

数学专家更擅长数学题、物理专家更擅长物理题。当你赋予AI一个角色以后,AI就会摇身一变,变成数学专家、物理专家、川菜大厨、成都本地导游等。

这样的话,他就能更好的回答你的问题了!

❌ 错误示范:帮我优化这个代码…

✅ 正确示范:假设你是一个有十多年经验的JAVA专家,你会如何优化这个代码…

❌ 错误示范:给我写一个去成都的旅游攻略

✅ 正确示范:假设你是一个有二十多年导游经验的成都本地资深导游。我要和我对象出去旅游,从北京到成都,请给我规划一个五一的旅游攻略。

AI给出的回答如下,可以看到,给出来的回答更加好了。不仅有具体的时间安排,还有对应的推荐菜以及推荐玩法。还有本地人的放坑指南等等。

deepseek3-3

这份攻略明显更好了!

第三招:细化问题,逐步求精

有的时候,可能我们希望得到更加细化的东西,比如说上面的旅游攻略,我们还想知道住宿安排,交通推荐。

再比如交通上我们希望比较火车、高铁、飞机的价格和时间等等。

为什么不一次性给AI呢?

因为AI无法理解太过复杂的问题,他毕竟不是人类。但是你一步步跟他说,他就能明白了。每次简单一些。

这就像大象装进冰箱有几步

  1. 打开冰箱
  2. 放大象
  3. 关冰箱

我们也可以继续追问

  • 请给出详细的交通方案,首先给出火车的
  • 接下来给出高铁的
  • 接下来给出飞机的

❌ 错误示范:帮我规划一个五一的旅游攻略,从北京到成都,要求给出详细的交通方案、住宿方案、游玩方案。包括时间、价格、性价比等细节。

✅ 正确示范:帮我规划一个五一的旅游攻略,从北京到成都,第一步,给出交通方案的时间、价格信息。第二步,给出住宿的推荐、价格、和景点的距离。第三步,给出景点的游玩信息、高峰期、路线、门票。

deepseek3-3

第四招:结合上下文,深入交流

上面的结果AI确实按照我们的要求来了,但是结果并不是太好。

我们一次性给出三步,对于AI来说,还是有点理解困难

所以我们可以让AI结合上下文。来一步步问。

什么是上下文

就是在一次AI对话里面的所有内容。当我们点击开启新对话。就开启了一个新的上下文。这个对话里面的所有信息,都是上下文,AI可以根据上下文来回答我们的问题。

✅ 正确示范:假设你是一个有二十多年导游经验的成都本地资深导游。我要和我对象出去旅游,从北京到成都,请给我规划一个五一的旅游攻略。

接下来,我们再次输入:

  • 请帮我细化从北京到成都的交通方案,给出交通方案的时间、价格信息等
  • 请帮我细化成都的住宿推荐,给出住宿的推荐、价格、和景点的距离等
  • 根据这些信息帮我细化景点的游玩信息、高峰期、路线、门票等

deepseek3-3

可以看到同样的问题,AI的回答明显别上次的更加好了。还给出了一些方案对比。很适合我们选择。

第五招:提供参考方案

就像写论文的时候,大家都需要参考文献一样,如果你想让AI给你输出一些很好的内容,也可以给AI一些参考文献。

大家都知道,AI有一个联网搜索功能。这其实就是AI会去网上搜索一些文章做为参考文献

我们也可以自主给他一些参考文献。

❌ 错误示范:帮我写一遍古诗词

✅ 正确示范:假如你是一个诗人,请帮我写一遍类似《将进酒》的古诗词。

第六招:多轮对话引导

之前已经经过上下文了。那么基于上下文,我们就可以进行多轮提问。

有很多人使用AI的时候,问了一次得不到正确答案,就不再继续提问了。

❌ 错误示范:帮我写一个牙齿的广告词。

✅ 正确示范:帮我写一个牙齿的广告词。

接下来的回答不满意,我们可以继续让AI修改回答。

✅ 第二轮问题:要求体现出我们的技术,口腔技术一流。

接下来的回答可能还是不满意,我们继续引导AI修改。

✅ 第二轮问题:修改广告词,控制在10个字以内。

就这样一轮一轮的引导。通过不断的引导修正。最终可以得到我们想要的答案。

第七招:多个方案。不同角度对比选择

除了上面的方法以外,还可以让AI从不同的角度给出多个方案,我们自己从中选出一个适合的方案。

当然了,如果多个方案都不满意,我们还可以通过多轮对话的方式,引导AI再次给出多个方案。

❌ 错误示范:帮我写一个牙齿的广告词。

✅ 正确示范:从不同的角度,给出5个牙齿的广告词。

如果对5个不满意,可以继续多轮对话。

…….

一些万能模板推荐

👉 设定角色:如资深导游、JAVA专家等。
👉 设定字数要求:如广告词给出10个字以内的、爆款标题给出20个字以内的。论文开题报告1000字以内。
👉 设定不同角度
👉 设定不想要的回答
👉 给出参考文献
👉 给定格式、语气、输入输出等。

总结

通过这些技巧,可以让AI更好的为我们服务,你会发现AI变成了生活中的小助手,而不再是人工智障

工具永远都只是工具。如何用好工具才是我们应该做的。

没有完美的工具,只有完美使用工具的人类。

通过AI提效,解放双手。有更多的时间学习、玩耍、祝大家都升职加薪!!!

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

还在手动管理资源?还在经常陷入OOM?一招教你快速解决

你是否还在为了手动管理资源而发愁?打开文件以后还要记得关闭文件,这些资源全部要手动管理,一旦忘了就有可能OOM!

一招教你使用try-with-resources来自动管理资源,不需要再手动关闭资源了,再也不怕忘记关闭资源了。

try-with-resources

在Java开发中,资源管理一直是一个重要的问题。

无论是文件操作、数据库连接还是网络通信,都需要确保资源在使用后能够正确关闭,以避免资源泄漏和潜在的错误。

try-with-resources语句的引入,为Java开发者提供了一种简洁而优雅的资源管理方式,大大简化了代码的复杂性,同时也提高了代码的可读性和安全性。

传统资源管理的痛点

首先,我们来看看try-with-resources解决了什么问题。

  1. 冗长的代码:传统资源管理的方式,通常需要在finally块里面手动关闭资源。但是这样既麻烦,又导致代码很多,而这些代码增加以后,比如多个资源关闭,嵌套关闭等,会导致可读性变差,代码难以管理。
  2. 容易出错:当代码复杂、可读性差、难以管理以后,就很容易出错了。还有如果在关闭资源的时候抛出异常,那么资源可能就无法关闭了,从而导致资源泄漏问题。
  3. 异常处理复杂:通常在finally中关闭资源的时候,还要在套一层try...catch,导致异常处理变多、复杂等问题

冗长的代码

try-with-resources出现之前,Java开发者通常使用try-finally语句来确保资源被正确关闭。例如,当操作文件时,代码通常如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream("example.txt");
// 读取文件内容
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

这种写法虽然能够确保资源被关闭,但代码显得冗长且复杂。特别是当需要管理多个资源时,finally块中的关闭逻辑会变得更加繁琐。

容易出错

手动管理资源时,开发者需要在finally块中显式调用资源的close()方法。如果忘记关闭资源,或者在关闭资源时抛出异常而未正确处理,就可能导致资源泄漏或其他问题。例如:

1
2
3
FileInputStream inputStream = new FileInputStream("example.txt");
// 使用inputStream读取文件内容
inputStream.close(); // 如果这里抛出异常,资源可能无法正确关闭

异常处理复杂

finally块中关闭资源时,如果资源的close()方法抛出异常,而try块中也抛出了异常,处理这些异常会变得非常复杂。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream("example.txt");
// 读取文件内容,可能抛出异常
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

如果try块和finally块都抛出异常,处理这些异常会变得非常复杂,容易导致错误。

什么是try-with-resources?

try-with-resourcesJava 7引入的一种语法结构,用于自动管理资源

它允许在try语句中声明一个或多个资源,这些资源必须实现了AutoCloseable接口或其子接口Closeable。

当try语句块执行完毕后,无论是否发生异常,try-with-resources都会自动调用资源的close()方法来关闭资源。

核心特性

  • 自动关闭资源:try-with-resources会自动调用资源的close()方法,确保资源被正确关闭。
  • 简化代码:无需手动在finally块中关闭资源,减少了代码冗余。
  • 支持多个资源:可以同时管理多个资源,每个资源之间用分号隔开。
  • 异常处理:如果资源的close()方法抛出异常,而try语句块中也抛出了异常,try语句块中抛出的异常会被传播,而资源关闭时抛出的异常会被抑制(suppressed)。

为什么使用try-with-resources

上面已经介绍了传统资源管理的痛点问题,所以使用try-with-resources主要就是解决这些痛点。

当然了,除了能解决这些问题以外,还有一些其他的好处。

毕竟,Java语言使用了这么长时间,能使它自身提供的一些特性,都是很有用的。

  • 解决传统资源管理的痛点:解决上述的痛点问题。
  • 提高代码的可读性和安全性:try-with-resources通过自动管理资源,简化了代码结构,提高了代码的可读性和安全性。它确保了资源在使用后能够正确关闭,减少了因资源泄漏导致的潜在问题。
  • 支持自定义资源类:除了Java标准库中提供的资源类(如FileInputStream、FileOutputStream等),我们还可以自定义实现AutoCloseable接口的资源类,以便在try-with-resources中使用。

何时使用try-with-resources

使用场景

上面已经介绍了很多了,接下来看一下,有哪些使用场景。

  • 文件操作:读取或写入文件时,使用FileInputStream、FileOutputStream等类。
  • 数据库操作:使用Connection、Statement、ResultSet等资源时。
  • 网络通信:使用Socket、ServerSocket等资源时。
  • 自定义资源:任何实现了AutoCloseable接口的资源类。

注意事项

没有任何方法是一种银弹。技术方案没有黑魔法!所以在使用的时候也需要注意一些事项。

  • 确保资源实现AutoCloseable接口:只有实现了AutoCloseable接口或Closeable接口的资源类才能在try-with-resources中使用。
  • 异常处理:虽然try-with-resources会自动关闭资源,但仍然需要处理可能抛出的异常。
  • 性能考虑:虽然try-with-resources简化了代码,但调用close()方法可能会增加一定的开销。在资源较多时,需要注意性能影响。

如何使用try-with-resources

经过上面的介绍,相信你已经对try-with-resources很了解了,接下来看一下到底该如何实战吧!

基本语法

try-with-resources语句允许在try块中声明一个或多个资源,这些资源必须实现了AutoCloseable接口或其子接口Closeable。当try块执行完毕后,无论是否发生异常,try-with-resources都会自动调用资源的close()方法来关闭资源。

1
2
3
4
5
try (资源声明) {
// 使用资源
} catch (异常类型 异常变量) {
// 处理异常
}

示例代码

以下是一个使用try-with-resources读取文件内容的示例:

可以看到,我们把资源的获取放在了try后面的小括号里面,而不是以前一样放在了大括号里面。

这样的话,就不需要在finally里面关闭资源了,因为会自动关闭。

这也是因为FileInputStream实现了Closeable接口,因此可以作为资源在try-with-resources中声明。

1
2
3
4
5
try (FileInputStream inputStream = new FileInputStream("example.txt")) {
// 使用inputStream读取文件内容
} catch (IOException e) {
e.printStackTrace();
}

管理多个资源

try-with-resources不仅可以管理单个资源,还可以同时管理多个资源。例如,当需要同时读取和写入文件时,可以这样写:

在这个例子中,inputStream和outputStream都被声明为资源,它们的close()方法都会在try语句块执行完毕后自动调用。

1
2
3
4
5
6
try (FileInputStream inputStream = new FileInputStream("input.txt");
FileOutputStream outputStream = new FileOutputStream("output.txt")) {
// 使用inputStream读取文件内容并写入outputStream
} catch (IOException e) {
e.printStackTrace();
}

自定义资源类

除了Java标准库中提供的资源类(如FileInputStream、FileOutputStream等),我们还可以自定义实现AutoCloseable接口的资源类,以便在try-with-resources中使用。例如:

只要我们自己的资源类,实现了AutoCloseable接口,就可以了。很简单吧!

1
2
3
4
5
6
public class MyResource implements AutoCloseable {
@Override
public void close() throws Exception {
System.out.println("资源已关闭");
}
}

接下来就可以和上面一样的使用方法来使用我们自定义的资源类了!

1
2
3
4
5
try (MyResource myResource = new MyResource()) {
// 使用myResource
} catch (Exception e) {
e.printStackTrace();
}

总结

try-with-resources是Java 7引入的一种非常有用的语法结构,

它能够自动管理资源,简化了资源关闭的代码,提高了代码的可读性和安全性。

通过实现AutoCloseable接口或Closeable接口,我们可以自定义资源类,并在try-with-resources中使用它们。

在实际开发中,合理使用try-with-resources可以有效避免资源泄漏,提高代码质量。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

DeepSeek R1本地部署

DeepSeek大火,但天下苦服务器繁忙,请稍后再试久矣.

近期,DeepSeek大模型大火,一举超越ChatGPT登顶下载榜首.

DeepSeek从很少人知道一下子变成了人尽皆知的大厂,招聘薪资更是开出了年薪百万的价格,应届生都可以去.可谓是梦中情厂.

但是,就连DeepSeek自己可能都没想到自己这么火.因此招架不住大家的热情,频繁的出现服务器繁忙,请稍后再试.

使用体验实在糟糕.

好在,DeepSeek开源了自己的大模型,我们可以将DeepSeek部署到本地进行使用,这样的话就可以不再担心服务器繁忙了.可以尽情的蹂躏DeepSeek了!!!

关于DeepSeek的技术有兴趣的可以看看他们的论文.
DeepSeekR1论文

  • DeepSeek-R1 遵循 MIT License,允许用户通过蒸馏技术借助 R1 训练其他模型。
  • DeepSeek-R1 上线 API,对用户开放思维链输出,通过设置 model=’deepseek-reasoner’ 即可调用。
  • DeepSeek 官网与 App 即日起同步更新上线。

DeepSeek-R1 在后训练阶段大规模使用了强化学习技术,在仅有极少标注数据的情况下,极大提升了模型推理能力。在数学、代码、自然语言推理等任务上,性能比肩 OpenAI o1 正式版。

环境准备

  1. 硬件要求

    • 需要至少一个 CPU 核心(推荐使用多核处理器)。
    • 内存建议至少 4GB,具体内存可以根据实际需求调整。
    • 磁盘空间建议至少 20GB 可用空间。
  2. 操作系统

    • Windows、Linux 或 macOS 均可支持。

蒸馏模型

DeepSeek本地部署的基本是蒸馏模型,简单理解为阉割版.

为什么?因为本地无法支持真正大模型的算力.

蒸馏模型虽然无法和完整版一样,但是胜在我们可以本地部署,自己玩.还避免了服务器繁忙的苦恼.毕竟,东西再好,你用不了也是白搭啊.

DeepSeek 在开源 DeepSeek-R1-ZeroDeepSeek-R1 两个 660B 模型的同时,通过 DeepSeek-R1 的输出,蒸馏了 6个小模型开源给社区,其中 32B 和 70B 模型在多项能力上实现了对标 OpenAI o1-mini 的效果。

deepseek2-1

满血版DeepSeek 671B的要求:

  • 显存需求:完整版(未量化)的显存需求极高,BF16精度下需 1342GB显存,即使使用FP16精度也需约 350GB显存
  • 硬件配置:需多节点分布式计算,例如8张NVIDIA A100/H100(每卡80GB显存)并行运行,或更高端的超算集群
  • 性能限制:单卡无法支持,即使最新RTX 5090(32GB显存)也无法有效运行,推理速度极低(低于每秒10个token)

deepseek2-1

看一下蒸馏版的要求和推荐配置.

版本名称 参数数量 显存需求 (FP16) 内存需求 推荐Mac配置 推荐Windows配置
DeepSeek-R1-1.5B 1.5B ~3GB 8GB+ M1/M2芯片,8GB统一内存[^2^] GTX 1650/RTX 2060,4GB+显存[^5^]
DeepSeek-R1-7B 7B ~14GB 16GB+ M1 Pro/M2 Pro,16GB统一内存[^2^] RTX 3060/4070 Ti,12GB显存[^2^]
DeepSeek-R1-14B 14B ~28GB 32GB+ M1 Max/M2 Max,32GB统一内存[^2^] RTX 4090/A100 40G,24GB+显存[^2^]
DeepSeek-R1-32B 32B ~64GB 64GB+ M1 Ultra/M2 Ultra,64GB统一内存[^2^] 2x RTX 4090/A100 80G,48GB+显存[^2^]
DeepSeek-R1-70B 70B ~140GB 128GB+ 需要更高配置的Mac Pro[^6^] 4x RTX 4090/A100 80G[^6^]

deepseek2-1

安装

介绍完以后,开始安装吧.

安装相对来说比较简单,可以使用Ollama这个东西.

直接在Ollama官网下载就可以了.

在如下界面,直接点击DownLoad就可以了.

deepseek2-1

接下来选择版本,Mac、Linux或者Windows.

deepseek2-1

等待下载完成以后,运行Ollama.这个东西可以理解为大模型的运行环境.

deepseek2-1

接下来点击Ollama官网左上角的Models可以准备大模型了.

deepseek2-1

选择我们要部署的DeepSeek R1大模型.

接下里可以选择要部署的版本.版本信息上面已经介绍过了.

deepseek2-8

选择以后右侧的命令会改变,直接复制右侧的命令即可.比如我选择7b版本,右侧的命令就是ollama run deepseek-r1:7b.

deepseek2-9

直接在命令行中输入这个命令就可以了.Windows使用CMD或者终端都可以.Mac使用终端或者Iterm2都可以.运行以后会先开始下载大模型.

在windows下,可以在下面的搜索里面输入CMD打开终端程序,或者按住键盘Win + R两个键,然后在里面输入CMD打开终端程序.

deepseek2-9

下载完成以后进入大模型的输入界面,是命令行格式的,可以直接输入.

比如我输入你是谁.回答中的标签里面对应的是思考的内容.

deepseek2-9

到这里其实就安装完成了,不过如果需要UI界面在浏览器中使用的话,也可以再安装一个UI界面.

后续如果还想运行的话依然是两个步骤

  1. 打开Ollama程序
  2. 运行命令ollama run deepseek-r1:7b, 注意替换成你自己的命令.

可视化界面安装

有很多人可能使用不惯命令行程序来输入,因此可以安装一个可视化界面,这样就和在DeepSeek官网使用一样了.

接下来我们看看如何安装UI界面吧!

可视化界面使用Open-Web UI提供的界面程序.可以先下载Docker,使用Docker运行Open-Web UI.

安装Docker

进入Docker官网,进行下载.选择要下载的版本.

deepseek2-9

下载以后进行安装,安装完成以后,打开程序,使用推荐配置就可以了.

deepseek2-9

安装Open Web-UI

接下来就可以安装Open Web-UI了.

使用如下命令即可.和之前一样,在CMD或者终端程序中运行如下命令.

1
docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main

接下来等待下载完成就可以了.

deepseek2-9

现在打开浏览器,输入http://localhost:3000/即可进入本地部署的可视化界面了!

第一次的话需要创建管理员账号,输入名称、邮箱、密码就可以了.

进来以后如图所示.

deepseek2-9

使用体验如下

deepseek2-9

总结

这样就算安装完成了,可以使用命令行或者界面,看个人喜好了!

通过以上步骤,您可以在本地部署 DeepSeek。请根据实际需求调整配置并确保所有依赖项已正确安装。如果在运行过程中遇到问题,请参考官方文档或联系技术支持团队。

希望这份指南对您有所帮助!

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习

大家好,我是大头,职高毕业,现在大厂资深开发,前上市公司架构师,管理过10人团队!
我将持续分享成体系的知识以及我自身的转码经验、面试经验、架构技术分享、AI技术分享等!
愿景是带领更多人完成破局、打破信息差!我自身知道走到现在是如何艰难,因此让以后的人少走弯路!
无论你是统本CS专业出身、专科出身、还是我和一样职高毕业等。都可以跟着我学习,一起成长!一起涨工资挣钱!
关注我一起挣大钱!文末有惊喜哦!

“凌晨1点,你躺在床上第103次对自己说‘再看最后一个视频就睡’。手指机械地上滑,屏幕突然跳出一只圆滚滚的橘猫——和你上周走丢的那只简直一模一样。你瞬间清醒,长按屏幕点了收藏,下一秒,系统又推来三个宠物视频:一只撒娇的布偶、一只拆家的二哈,甚至还有宠物殡葬的科普……

你后背一凉:抖音怎么比男朋友还懂我?!”

「用户画像」是什么?抖音如何用代码“算”出你的喜好?揭秘背后的技术逻辑!

代码正在“偷窥”你的生活

“上瘾”背后的数据陷阱:

你刷到的每一个“刚刚好”的视频——深夜放毒的美食、周末必推的露营攻略、甚至分手后突然涌现的伤感BGM——都不是偶然。

真实案例:

1
2
程序员小李的“社死现场”:
“上周开会摸鱼搜了‘痔疮药’,当晚抖音首页全是肛肠医院广告……同事围观我手机时,我恨不得当场卸载APP!”

用户行为显微镜:

1
你的每一次点击、停留、点赞、划走,甚至视频看到一半突然锁屏——这些动作在后台早已被拆解成无数个“0”和“1”,拼凑出连你自己都未曾察觉的“兴趣密码”。

程序员如何用代码“造”出一个你?

“你以为你在刷视频?其实是抖音在用用户画像和推荐算法反向‘刷’你!”

  • 行为追踪
    • 你滑动屏幕的0.5秒里,后台如何通过埋点代码实时捕获你的动作?(Hint:API接口暗中“贴”在你的手指上)
  • 数据炼金术
    • 每天数亿用户产生PB级数据,系统怎样在毫秒间从海量内容中捞出“最对你胃口”的那条视频?(分布式架构:让十万台服务器替你“打工”)
  • 算法攻心计
    • 为什么连你妈都不知道你爱吃螺蛳粉,抖音却能在三次滑动内精准推荐?(协同过滤算法:找到和你“臭味相投”的陌生人)

今天,我们从代码层面掀开抖音的“读心术”

接下来,你将看到:

  • 你的‘数据分身’如何被埋点、清洗、打标签,最终成为算法眼中的‘透明人’;
  • 推荐系统怎样用‘召回-排序-多样性控制’三连招,让你欲罢不能;
  • 程序员为扛住全民刷抖音的流量,在后台默默承受的‘福报’(高并发场景下的极限操作)。

用户画像:你的“数据分身”是如何炼成的?

数据采集:代码像“狗仔队”一样追踪你

  1. 埋点监听:

前端埋点:用户每次点击、滑动、点赞时,客户端自动触发埋点事件(如 onClick、onScroll),通过API将行为数据上报到服务器。

1
2
3
4
5
6
7
8
// 伪代码示例:点赞事件埋点
video.likeButton.addEventListener('click', () => {
trackEvent('video_like', {
video_id: '123',
user_id: '456',
timestamp: Date.now()
});
});

后端日志:服务器接收请求后,记录用户IP、设备ID、请求路径等原始日志,形成“用户行为流水账”。

  1. 设备指纹:
  • 通过 User-Agent 获取手机型号、操作系统;
  • 利用GPS、Wi-Fi SSID/IP定位常驻区域(比如“北京海淀区中关村打工人”);
  • 甚至通过陀螺仪数据判断用户是躺着刷还是坐着刷(影响推荐时段策略)。

热知识:

为什么你切换到4G网络,推荐内容突然变“土味”?
——因为系统发现你从一线城市CBD的WiFi切换到老家县城的基站IP,立刻调高“下沉市场”内容权重!

数据清洗与存储:把“生肉”加工成“熟数据”

技术流程:

  1. 实时流处理(如Kafka + Flink):
  • 用户滑动屏幕后,行为数据在 5毫秒内 进入Kafka消息队列;
  • Flink实时过滤无效数据(比如误触导致的0.1秒停留)。
  1. 离线分析(如Hadoop + Hive):
  • 每天凌晨启动MapReduce任务,统计用户长期行为(例如“过去30天每晚8点必看游戏直播”)。
  1. 存储优化:
  • 列式存储(HBase):将用户标签按列存储,快速查询“所有喜欢猫的用户”;
  • Redis缓存:高频标签(如“近期热搜词”)常驻内存,应对瞬时高并发请求。

标签体系:给你的兴趣“贴满小纸条”

标签类型与算法逻辑:

  1. 基础标签(直接提取):
  • 性别:通过头像、昵称、搜索词NLP分析(比如昵称含“喵”大概率女性);
  • 消费能力:根据手机型号(iPhone 15 Pro Max vs 千元机)、是否开通抖音会员。
  1. 兴趣标签(行为分析):
  • 短期兴趣:基于实时行为(例如连续刷10条滑雪视频,立刻打上“滑雪”标签);
  • 长期兴趣:通过TF-IDF算法从历史行为中提取关键词(比如“编程”“显卡评测”)。
  1. 动态标签(实时调整):
  • 衰减机制:上周的“螺蛳粉”标签权重每天下降10%,防止过期兴趣干扰;
  • 突发兴趣:若用户突然搜索“三亚旅游”,权重瞬间提升至0.8,触发紧急推荐。

热知识

为什么你只是看了一眼美女视频,系统却疯狂推荐?
——因为算法发现该视频的“完播率”达95%,且你的“停留时长比均值高3秒”,立刻判定为潜在兴趣!

技术难点:如何让“数据分身”逼近真实的你?

  • 去重与纠错:
    • 用布隆过滤器(Bloom Filter)排除重复点击(比如误触同一视频3次);
    • 通过行为序列分析识别“借用手机场景”(例如家长手机突然出现小学生爱看的动画片)。
  • 冷启动问题:
    • 新用户首次打开抖音时,推荐“地域热门+设备均价内容”(比如深圳用户默认推科技测评,县城用户推本地新闻)。
  • 存储成本优化:
    • 对低频标签(如“用户5年前点赞过的冷门歌曲”)使用廉价对象存储(如AWS S3),高频标签用SSD硬盘扛压力。

推荐算法:如何让代码“猜”中你的心思?

召回层:从“大海捞针”到“精准撒网”

技术目标:从亿级视频池中快速筛选出 千分之一 的候选内容。

核心策略:

  1. 协同过滤(CF):
  • User-Based:找到和你行为相似的用户,推荐他们喜欢的视频。
1
2
3
4
5
6
7
# 伪代码:计算用户相似度(余弦相似度)  
def user_similarity(user1, user2):
common_videos = user1.likes & user2.likes
dot_product = sum(user1.likes[v] * user2.likes[v] for v in common_videos)
norm_user1 = sqrt(sum(val**2 for val in user1.likes.values()))
norm_user2 = sqrt(sum(val**2 for val in user2.likes.values()))
return dot_product / (norm_user1 * norm_user2)
  • Item-Based:推荐与你喜欢过的视频相似的内容(例如:看完《流浪地球》后推《三体》解说)。
  1. 内容召回:
  • 用NLP提取视频标题/字幕关键词(如“露营”“新手教程”),匹配用户兴趣标签。
  • 图像识别分析视频封面(比如宠物视频中的猫狗品种)。
  1. 热门兜底:
  • 新用户首次打开APP时,直接推荐当日全站Top 100视频(避免“冷启动”尴尬)。

冷知识

为什么你的“小众爱好”突然被推荐?
——因为协同过滤发现某个和你有 97%相似度 的用户,昨晚点赞了这条视频,系统立刻“抄作业”推给你!

排序层:给视频“贴分数”,争夺你的注意力

技术目标:对召回层的千条候选视频,预测你对每条内容的 点击率(CTR) 和 完播率,精准排序。

模型架构:

  1. 特征工程:
  • 用户特征:标签权重、近期行为序列(如“最近3次点击均与露营相关”)。
  • 内容特征:视频时长、关键词、作者粉丝量、实时点赞增速。
  • 环境特征:当前时段(深夜推助眠视频)、网络类型(WiFi下推高清长视频)。
  1. 深度学习模型:
  • DNN(深度神经网络):将特征向量输入多层网络,输出点击概率。
  • 多任务学习:同时预测点击率、点赞率、评论率,综合计算“吸引力总分”。
  • 实时更新:模型每隔15分钟增量训练,吸收最新用户反馈数据。
1
2
3
4
5
6
7
8
9
# 伪代码:排序模型预测(简化版)  
def predict_ctr(user_features, video_features):
# 拼接用户特征和内容特征
input_vector = concat(user_features, video_features)
# 深度神经网络推理
ctr = dnn_model.predict(input_vector)
# 加入多样性惩罚(避免同类视频扎堆)
ctr *= diversity_penalty
return ctr

热知识

为什么你刚买的手机,抖音立马推测评视频?
——因为排序模型发现“设备型号”与“开箱测评”类视频的 特征交叉权重 突然飙升,判定你处于“购物决策期”!

多样性控制:打破“信息茧房”的代码心机

技术目标:防止推荐内容过于单一(例如全是美女视频),引发用户厌倦。

核心策略:

  1. 类别打散:
  • 每刷10条视频,强制插入1条非兴趣标签内容(如教育、新闻)。
  • 使用 MAB(多臂老虎机算法) 试探用户对新类型的反应。
  1. 时间衰减:
  • 连续推荐同一标签视频后,逐渐降低该标签权重(例如“露营”连推5次后权重减半)。
  1. 突发热点:
  • 监测全网爆款视频(如“某明星官宣”),强行插入到推荐流中,无论用户是否感兴趣。

热知识

为什么你总能看到老板的“正能量鸡汤视频”?
——因为系统默认所有用户对“平台热门内容”有 最低曝光阈值,哪怕你点了100次“不感兴趣”!

实时反馈:你的每一次滑动,都是算法的“调参器”

技术链条:

  1. 动作捕获:
  • 划走(负反馈):触发降权(降低相似内容权重);
  • 完播 + 点赞(正反馈):权重飙升,类似内容立即进入下一轮推荐。
  1. 流式更新:
  • 用户行为数据通过 Kafka 实时流入Flink,5秒内更新用户画像。
  • 排序模型根据实时特征调整下一次推荐结果(比如你刚点赞“滑雪”,下一条立刻推滑雪装备广告)。
  1. 后端架构挑战:
  • 毫秒级响应:从用户滑动到推荐更新,全程不超过100ms(需分布式计算 + 缓存优化)。
  • 数据一致性:确保画像更新与推荐结果同步(使用分布式锁或事务消息)。

后端工程师的挑战:高并发下的毫秒级博弈

实时性:你的“滑动”有多快,代码就得跑多快

技术场景:

用户每次滑动屏幕,系统需在 100ms内 完成:

  1. 捕获行为 → 2. 更新画像 → 3. 召回排序 → 4. 返回推荐结果

核心方案:

  • 流式计算(Apache Flink):
    • 行为数据实时进入Flink流水线,通过状态(State)管理快速更新用户标签权重。
1
2
3
4
5
6
// 伪代码:Flink实时处理用户滑动事件  
DataStream<UserAction> actions = env.addSource(kafkaSource);
actions
.keyBy(userId)
.process(new UpdateProfileFunction()) // 实时更新画像
.addSink(recommendSink); // 触发推荐计算
  • 分布式存储:
    • 用户画像分片存储(如按UserID哈希分片到不同Redis集群),避免单点瓶颈。

高并发:每秒百万级请求下的“极限求生”

技术场景:

晚高峰时段,抖音推荐系统每秒处理 数百万次请求,相当于春运期间所有火车站同时检票。

架构设计:

  1. 水平扩展:
  • 无状态服务:推荐API服务实例可动态扩容(Kubernetes自动伸缩);
  • 分片负载均衡:用户请求按地域/设备类型路由到最近机房。
  1. 缓存风暴防御:
  • 多级缓存:
    • 本地缓存(Guava Cache)→ Redis集群 → 数据库。
  • 缓存击穿方案:
    • 使用分布式锁(如Redisson)防止单个热点Key失效导致数据库雪崩。
1
2
3
4
5
6
7
8
9
10
// 伪代码:缓存击穿防护  
RLock lock = redisson.getLock("VIDEO:123");
if (lock.tryLock()) {
try {
data = db.getVideo(123);
redis.set("VIDEO:123", data, 60);
} finally {
lock.unlock();
}
}
  1. 降级熔断:
  • 若召回服务超时,自动降级为“热门视频兜底”;
  • 使用Hystrix或Sentinel监控服务熔断状态。

热知识:

为什么明星官宣时抖音不崩?
——后台早已启动“ 大V预案 ”:提前预热缓存、限流非核心接口(比如评论加载),把资源留给推荐系统。

数据隐私:在“精准推荐”与“窥私骂名”间走钢丝

技术挑战:

既要利用用户数据优化推荐,又要避免法律风险(如GDPR、中国个人信息保护法)。

解决方案:

  1. 匿名化处理:
  • 用户标签与真实身份脱钩,使用哈希加密的UUID替代UserID。
  1. 差分隐私:
  • 在数据统计时注入噪声(如拉普拉斯噪声),防止通过数据反推个人身份。
1
2
3
4
5
# 伪代码:拉普拉斯噪声注入  
def laplace_noise(data, epsilon):
scale = 1.0 / epsilon
noise = np.random.laplace(0, scale)
return data + noise
  1. 权限隔离:
  • 开发/运维人员访问生产数据需动态申请权限,操作日志全程审计。

热知识:

为什么你从未搜索“植发”,却总看到生发广告?
——因为系统通过“协同过滤”发现,和你同年龄、同地域的男性用户都在点击这类广告!

容灾与一致性:当机房爆炸时,代码如何“自救”?

极端场景应对:

  1. 多活架构:
  • 用户流量分流到北京、上海、深圳三地机房,数据库通过Paxos/Raft协议同步数据。
  1. 故障转移:
  • 若某机房网络中断,5秒内切换DNS解析至备用机房(你只会感觉视频加载稍卡一下)。
  1. 最终一致性:
  • 允许短暂画像延迟(如你刚点赞的视频下一刷才生效),用消息队列保证数据最终一致。

总结

上头解密
你以为你在抖音刷视频,实则是无数行代码在“刷”你。

每一次滑动、点赞、停留,都在为你的“数字分身”添砖加瓦。

用户画像像一位24小时不打烊的侦探,通过埋点、流式计算、标签体系,将你的兴趣拆解得明明白白;推荐算法则化身精准的注意力猎手,用协同过滤、深度学习、实时反馈,操控你的每一次心跳加速。

文末福利

关注我发送“MySQL知识图谱”领取完整的MySQL学习路线。
发送“电子书”即可领取价值上千的电子书资源。
发送“大厂内推”即可获取京东、美团等大厂内推信息,祝你获得高薪职位。
发送“AI”即可领取AI学习资料。
部分电子书如图所示。

概念学习

概念学习

概念学习

概念学习