1、总览

2、单体架构

2.1 单体架构好处

主要体现在早期

  • 应用开发简单
  • 易于对应用程序进行大规模的更改
  • 测试相对简单直观
  • 部署简单明了
  • 横向扩展不费吹灰之力

2.2 局限性

  • 过度的复杂性会吓退开发者
  • 开发速度缓慢
  • 从代码提交到实际部署的周期很长,而且容易出问题
  • 难以扩展
  • 交付可靠的单体应用是一项挑战
  • 需要长期依赖某个可能已经过时的技术栈

2.3 微服务架构

架构的重要性影响应用的非功能性需求,也称为质量属性或者其他的能力。最显著的影响软件交付速度的可维护性、可扩展性和可测试性。

微服务架构定义为面向服务的架构,它们由松耦合和具有边界上下文的元素组成。

2.3.1 扩展立方体

扩展一个应用程序的三种维度:X,Y和Z

x轴扩展:在多个实例之间实现请求的负载均衡

z轴扩展:根据请求的属性路由请求。不同于x轴扩展,每个实例仅负责数据的一个子集。相当于根据数据作分区。

y轴扩展:根据功能把应用拆分为服务,x轴和z轴扩展有效地提升应用的吞吐量和可用性,没有解决日益增长的开 问题和应用复杂性。y轴扩展也就是功能性分解。

2.3.2 微服务架构作为模块化的一种形式

使用服务作为模块化的单元。

2.3.3 每个服务都拥有自己的数据库

每一个服务之间都是松耦合的,服务之间仅通过API进行通信。实现松耦合的方式这一是每个服务都拥有自己的私有数据库。

2.3.4 微服务架构与SOA的异同

SOA 微服务
服务间通信 智能管道,如Enterprise Service Bus(ESB),往往采用重量级协议如SOA或其他WS*标准 使用哑管道,如消息代理或者服务之间点对点通信,使用REST或者gRPC类的轻量级协议
数据管理  全局数据模型并共享数据库 每个服务都有自己的数据模型和数据库
典型服务的规模 较大的单体应用 输小的服务

2.4 微服务架构的好处和弊端

2.4.1 好处

  • 使大型的复杂应用程序可以持续交付和持续部署
  • 每个服务相对较小并容易维护
  • 服务可以独立部署
  • 服务可以独立扩展
  • 微服务架构可以实现团队的自治
  • 更容易实验和采纳新的技术
  • 更好的容错性

2.4.2 弊端

  • 服务的拆分和定义是一项挑战
  • 分布式系统带来的各种复杂性,使开发、测试和部署变得更困难
  • 当部署跨越多个服务的功能时需要谨慎地协调更多开发团队
  • 开发者需要思考到底应该在应用的什么阶段使用微服务架构

2.5 微服务架构模式

2.5.1 模式结构组成

  • 需求:必须解决的问题和团结这个问题的特定上下文 环境
  • 结果上下文:采用模式可能带来的后果,包含三个部分:好处,弊端和问题
  • 相关模式:5种不同类型的关系。包含:前导、后续、替代、泛化和特化

模式分为三组

  • 基础设施相关模式组:解决通常是在开发环节跟基础设施相关的问题
  • 应用基础设施相关模式组:解决应用层面的基础设施相关问题
  • 应用相关模式组:解决开发人员面对的具体技术和架构问题

服务拆分的相关模式

  • 根据业务能力分解模式
  • 根据子域分解模式

通信的相关模式

  • 通信风格
  • 服务发现
  • 可靠性
  • 事务性消息
  • 外部API

实现事务管理的数据一致性相关模式

  • Saga模式

查询数据的相关模式

  • API组合
  • CQRS

服务部署相关模式

  • 虚拟机
  • 容器
  • Serverless

可观测性相关模式

  • 健康检查API
  • 日志聚合
  • 分布式跟踪
  • 异常跟踪
  • 应用指标
  • 审计日志

自动化测试相关模式

  • 消费端驱动的契约测试
  • 消费端契约测试
  • 服务组件测试

基础设施和逻辑问题的相关模式

  • 外部化配置模式

安全相关模式

  • 访问令牌模式

2.6 组织和流程

3、服务拆分策略

3.1 微服务架构

计算机系统的软件架构是构建这个系统所需要的一组结构,包括软件元素、它们之间的关系以及两者的属性

应用程序的架构是将软件分解为元素和这些元素之间的关系。软件架构的4+1视图模型

应用程序有两个层面的需求。第一类是功能性需求,这些需求决定一个应用程序做什么,通常包含在用例或者用户故事中。应用的架构其实跟这些功能性需求没什么关系。架构的重要性在于,帮助应用程序满足第二类需求,非功能性需要,也称为质量属性需求,或者简称为能力。这些非功能性需求决定一个应用程序在运行时的质量,比如可扩展性和可靠性,它们也决定了开发阶段的质量,包括可维护性,可测试性,可扩展笥和可部署性。

3.2 架构风格

3.2.1 分层式架构风格

流行的三层架构是应用于逻辑视图的分层架构

  • 表现层:包含实现用户界面或外部API的代码
  • 业务逻辑层:包含业务逻辑
  • 数据持久化层:实现与数据库交互的逻辑

分层架构弊端:

  • 单个表现层:无法展现应用程序可能不仅仅由单个系统调用的事实
  • 单一数据持久化层:无法展现应用程序可能与多个数据库进行交互的事实
  • 将业务逻辑层定义为依赖于数据持久化层:这样的依赖性会妨碍在没有数据库的情况下测试业务逻辑

3.2.2 六边形架构风格

是分层架构风格的替代品。六边形架构风格选择以业务逻辑为中心的方式组织逻辑视图。应用程序具有一个或多个入站适配器,而不是表示层,它通过调用业务逻辑来处理来自外部的请求。同样具有一个或者多个出站适配器,而不是数据持久化层,这些出站适配器由业务逻辑调用外部应用程序 。此架构的一个关键特性和优点是业务逻辑不依赖于适配器,相反,各种适配器都依赖于业务逻辑。

3.2.3 微服务架构风格

服务:是一个单一,可独立部署的软件组件,实现了一些有用的功能。

松耦合:服务之间的松耦合

共享类库的角色:一些通用的功能打包到库或者模块中。

3.3 定义微服务架构

步骤

  • 定义系统操作
  • 定义服务
  • 定义服务API和协作方式

系统操作:应用程序必须处理的请求的一种抽象描述,既可以是更新数据的命令,也可以检索数据的查询。每个命令的行为都是根据抽象领域模型定义的。

分解服务:有几种策略,一种是源于业务架构学派的策略是定义与业务能力相对应的服务,另一种策略是围绕领域驱动设计的子域来分解和设计服务。

确定服务API和协作:将第一步中标识的每个系统操作分配给服务,可以完全的独立地实现操作,可能需要与其他服务协作,在这种情况下,可以确定服务的协作方式,通常需要服务来支持其他操作。

3.3.1 识别系统操作

  • 创建由关键类组成的抽象领域模型
  • 确定系统操作,并根据领域模型描述每个系统操作的行为

3.3.2 根据业务能力进行服务拆分

  • 业务能力定义了一个组织的工作
  • 识别业务能力
  • 从业务能力到服务

3.3.3 根据子域进行服务拆分

子域是领域的一部分。领域模型的边界为限界上下文。

4、进程间通信

服务可以使用基于同步请求/响应的通信机制,如REST和gRPC。也可以使用异步的基于消息的通信机制。消息模式也不尽相同,可以基于JSON或XML,也可以基于二进制的 Avro或Protocol Buffers格式 。

4.1 进程间通信概述

4.1.1 交互方式

有两个维度,一个关注的是一对一和一对多。

一对一:每个客户端请求由一个服务实例来处理

一对多:每个客户端请求由多个服务实例来处理。

第二个维度关注的是同步和异步。

同步模式:客户端请求需要服务端实时响应,客户端等待响应时可能导致堵塞

异步模式:客户端请求不会阻塞进程 ,服务端的响应可以是非实时的。

4.1.2 API定义

设计良好的接口暴露有用功能隐藏实现细节。

使用接口定义语言(IDL)

API优先设计

4.1.3 API演化

语义化版本控制,是一组规则,用于规定如何使用版本号,并且以正确的方式递增版本号。

要求版本号由三部分组成:MAJOR.MINOR.PATCH。MAJOR当你对API进行不兼容的更改时。MINOR当你对API进行向后兼容的增强时。PATCH当你进行向后兼容的错误修复时。

4.1.4 消息格式

分为文本和二进制。

4.2 同步调用

4.2.1 REST

REST定义了成熟度模型,有四个层次

Level0:只是向服务端点发起HTTP POST请求,进行服务调用,每个请求都指明了需要执行的操作、操作针对的目标和必要的参数。

Level1:引入了资源的概念,要执行对资源的操作,客户端需要发出指定要执行的操作和包含任何参数的POST请求。

Level2:使用HTTP动词来执行操作。请求查询参数和主体指定操作的参数。

Level3:基于HATEOAS原则设计,基本思想是在由GET请求返回的资源信息中包含链接。

REST好处:

  • 非常简单,大家都很熟悉
  • 可以使用浏览器扩展或者curl之类的命令行来测试HTTP API
  • 直接支持请求/响应方式的通信
  • HTTP对防火墙友好
  • 不需要中间代理,简化了系统架构

弊端:

  • 只支持请求/响应方式的通信
  • 可能导致可用性降低。
  • 客户端必须知道服务实例的位置。
  • 在单个请求中获取多个资源具有挑战性
  • 有时很难将多个更新操作映射到HTTP动词。

4.2.2 gRPC

是基于二进制消息的协议,可以使用基于Protocol Buffer的IDL定义gRPC API。可以使用Protocol Buffer编译器生成客户端的桩和服务端骨架。

gRPC API由一个或多个服务和请求/响应消息定义组成。服务定义类似于Java接口,是强类型方法的集合,除了支持简单的请求/响应RPC之外,gRPC还支持流式RPC。服务器可以使用消息流回复客户端。客户端也可以向服务发送消息流。

弊端:

  • 与基于REST/JSON的API机制相比,Javascript客户端使用基于gRPC的API需要做更多的工作。
  • 旧式防火墙可能不支持HTTP/2

4.2.3 断路器模式处理局部故障

  • 让远程过程调用代理有正确处理无响应服务的能力。
  • 决定如何从失败的远程服务中恢复

针对第一条,使用以下机制

网络超时:在等待针对请求的响应时,一定不要做成无限阻塞,而是要设定一个超时,使用超时可以保证不会一直在无响应的请求上浪费资源。

限制客户端向服务端发出请求的数量:把客户端能够向特定服务发起的请求设置一个上限,如果请求达到了这样的上限,很有可能发起更多的请求也无济于事,应该让请求立刻失败。

断路器模式:监控客户端发出请求的成功和失败数量,如果失败的比例超过一定的阈值,就启动断路器,让后续的调用立刻失效。如果大量的请求都以失败而告终,说明被调服务不可用。在经过一段时间后,客户端应该继续尝试,如果调用成功,则解除断路器。

对于第二条,使用以下机制

根据具体情况决定如何从无响应的远程服务中恢复你的服务,一种选择是服务只是向其客户端返回错误。另外一种是返回备用值可能会有意义。

4.2.4 服务发现

两种实现方式

  • 服务及其客户直接与服务注册表交互
  • 通过部署基础设施来处理服务发现

应用层服务发现模式

这种服务发现模式是两种模式的组合。第一种模式是自注册模式。服务实例调用服务注册表的注册API来注册其网络位置。第二种模式是客户端发现模式。当客户端想要调用服务时,会查询服务注册表以获取服务实例的列表。

好处:

可以处理多平台部署的问题

弊端:

需要 为使用的每种编程语言提供服务发现库。

平台层服务发现模式

以下两种模式组合

  • 第三方注册模式:由第三方负责处理注册,而不是服务本身向服务注册表注册自己
  • 服务端发现模式:客户端不再需要查询服务注册表,而是向DNS名称发出请求,对该DNS名称的请求被解析到路由器,路由器查询服务注册表并对请求进行负载均衡。

4.3 异步消息模式

使用消息代理 或者无代理架构。

4.3.1 消息传递

消息由消息头部和消息主体组成。标题是名称与值对的集合,描述正在发送的数据的元数据。消息头部包含消息发送者提供的名称与值对之外,还包含其他信息,如发件人或消息传递基础设施生成的唯一消息ID,以及可选的返回地址,该地址指定发送回复的消息通道。消息正文是以文本或者二进制格式发送的数据。

消息类型包含

  • 文档:仅包含数据的通用消息。接收者决定如何解释它,对命令式消息的回复是文档消息一种使用场景
  • 命令:一条等同于RPC请求的消息,它指定要调用的操作及其参数
  • 事件:表示发送方这一端发生了重要的事件。事件通常是领域事件,表示领域对象的状态更改。

消息通道类型

  • 点对点:向正在从通道读取的一个消费者传递消息
  • 发布-订阅:将一条消息发给所有订阅的接收方。

4.3.2 消息代理

使用消息代理实现消息通道。ActiveMQ用队列和主题 ,RabbitMQ用交换和队列

4.3.3 处理并发和消息顺序

常见的解决方案是使用分片。

4.3.4 处理重复消息

理想情况下,消息代理应该只传递一次消息,但保证有且仅有一次的消息传递通常成本很高,大多数消息代理承诺至少成功传递一次消息。

处理重复消息方式:

  • 编写幂等消息处理程序
  • 跟踪消息并丢弃重复项

4.3.4 事务性消息

使用数据库表作为消息队列

发送消息的服务有一个OUTBOX数据库表,作为创建、更新和删除业务对象的数据库事务的一部分,服务通过消息插入到OUTBOX表中来发送消息。这样可以保证原子性。OUTBOX表充当临时消息队列,MessageRelay是一个读取OUTBOX表并将消息发布到消息代理的组件。

通过轮询模式发布事件

让MessageRelay在表中轮询未发布的消息。定期查询表,把这些消息发送给消息代理,它把每个消息发送给它们的目的消息通道。最后,MessageRelay把完成发送的消息从OUTBOX表中删除。

事务日志拖尾模式发布事件

让MessageRelay拖尾数据库的事务日志文件。每次应用程序提交到数据库的更新都对应着数据库事务日志中的一个条目。事务日志挖掘器可以读取事务日志,把每条跟消息有关的记录发送给消息代理。

4.4 异步消息提高可用性

异步交互模式,采用复制数据的方式来提高可用性。服务维护一个数据副本,这些数据是服务在处理请求时需要使用的,这些数据的源头会在数据变化时发生消息,服务订阅这些消息来确保数据副本的实时更新。

5、Saga管理事务

saga只满足ACD特性,缺乏隔离性。

saga协调有两种方式:

  • 协同式,saga的参与方在没有集中控制器的情况下交换事件式消息。
  • 编排式,集中控制器告诉saga参与方要执行的操作。

5.1 微服务架构下的事务管理

5.1.1 微服务架构对分布式事务的需求

每个服务都有自己的私有数据库,需要一种机制来保障多数据库环境下的数据一致性。

5.1.2 分布式事务的挑战

在多个服务、数据库和消息代理之间维持数据一致性的传统方式是采用分布式事务。分布式事务管理的事实标准是XA。XA采用了两阶段提交来保证事务中的所有参与方同时完成提交,或者在失败时同时回滚。

应用程序的整个技术栈需要满足 XA标准,包括符合XA要求的数据库、消息代理、数据库驱动、消息API,以及用来传播XA全局事务ID的进程间通信机制。问题是许多新技术,包括NoSQL数据库,并不支持XA标准的分布式事务。同样,一些流行的消息代理,如RabbitMQ和Apache Kafka并不支持分布式事务。

分布式事务的另一个问题在于,本质上都是同步进程间通信,这会降低分布式系统的可用性。为了让一个分布式事务完成提交,所有参与事务的服务都必须可用。

Saga松耦合,异步服务的。

5.1.3 使用saga模式维护数据一致性

Saga是一种在微服务架构中维护数据一致性的机制,它可以避免分布式事务所带来的问题。一个Saga表示需要更新多个服务中数据的一个系统操作。Saga由一连串的本地事务组成。每一个本地事务负责更新它所在服务的私有数据库。

使用补偿事务回滚Saga。使用异步消息进行通信。当本地事务完成后,服务会发布消息,使用消息可以确保Saga参与方之间的松散耦合,还可以保证Saga完成。

5.2 Saga协调模式

  • 协同式:把Saga的决策和执行顺序逻辑分布在Saga的每一个参与方中,它们通过交换事件的方式进行沟通。
  • 编排式:把Saga的决策和执行顺序逻辑集中在一个Saga编排器类中。Saga编排器发出命令式消息给各个Saga参与方,指示这些参与方服务完成具体操作。

5.2.1 协同式Saga

好处

  • 简单:服务在创建、更新或删除业务对象进发布事件
  • 松耦合:参与方订阅事件并且彼此之间不会因此而产生耦合

弊端

  • 更难理解:没有单一地方定义Saga,很难理解特定的Saga是如何工作的。
  • 服务之间的循环依赖关系:Saga参与方订阅彼此的事件,会导致循环依赖关系。
  • 紧耦合风险:每个Saga参与方都需要订阅所有影响它们的事件。

5.2.2 编排式Saga

编排器作为一个状态机,由一组状态和一组由事件触发的状态之间的转换组成。每个转换都可以有一个动作。动作就是对某个参与方的调用。状态之间的转换由Saga参与方执行的本地事务完成触发。当前状态和本地事务的特定结果决定状态转换以及执行的动作。使用状态机模型可以更轻松地设计、实现和测试Saga。

好处

  • 简单的依赖关系:不会引入循环依赖关系
  • 较少的耦合:每个服务实现供编排器调用的API,因此它不需要知道Saga参与方发布的事件。
  • 改善关注点隔离,简化业务逻辑:Saga的协调逻辑本地化在Saga编排器中。

弊端

在编排器中存在集中过多业务逻辑的风险。

5.3 解决隔离问题

5.3.1 缺乏隔离导致的问题

  • 丢失更新:一个Saga没有读取更新,而是直接覆盖了另一个Saga所做的更改
  • 脏读:一个事务或一个Saga读取了尚未完成的Saga所做的更新
  • 模糊或不可重复读:一个Saga的两个不同步骤读取相同的数据却获得了不同的结果,因为另一个Saga已经进行了更新

5.3.2 隔离对策

语义锁:应用程序级的锁

交换式更新:把更新操作设计成可以按任何顺序执行

悲观视图:重新排序Saga的步骤,以最大限度地降低业务风险

重读值:通过重写数据来防止脏写,以在覆盖数据之前验证它是否保持不变

版本文件:将更新记录下来,以便可以对它们重新排序

业务风险评级:使用每个请求的业务风险来动态选择并发机制。

一个Saga包含三种类型的事务

可补偿性事务:可以使用补偿事务回滚的事务

关键性事务:Saga执行过程的关键点。如果关键性事务成功,则Saga将一直运行到完成。关键性事务不一定是可补偿性事务,或者可重复性事务。但是它可以是最后一个可补偿的事务或第一个可重复的事务。

可重复性事务:在关键事务之后的事务,保证成功。

6、业务逻辑设计

6.1 业务逻辑组织模式

分为面向过程的事务脚本模式和面向对象的领域建模模式

6.1.1 事务脚本模式

重要特征是实现行为的类与存储状态的类是分开的。

脚本位于服务类中,每个服务类都有一个用于请求或者系统操作的方法。这个方法实现请求的业务逻辑。数据对象是纯数据,没有行为。

适用于简单的业务逻辑。

6.1.2 领域模型模式

业务逻辑由对象模型和相对较小的一些类的网络组成。这些类通常对应于问题域中的概念。有些类只有状态或行为,但大多数类是包含状态和行为。

服务类具有针对每个请求或系统操作的方法,但是服务方法通常很简单,总是调用领域对象,这些对象中包含大量的业务逻辑。

好处:易于理解和维护。它不是由一个完成的所有事情的大类来完成,而是由许多小类组成,每个不类都有少量职责。面向对象的设计更容易测试,更容易扩展。

6.1.3 关于领域驱动设计

子域概念有助于把应用程序分解为服务。

战略性模式:子域和相关联的限界上下文

战术性模式:

  • 实体(entity):具有持久化id的对象。具有相同属性值的两个实体仍然是不同的对象
  • 值对象(value object):作为值集合的对象。具有相同属性值的两个值对象可以互换使用。
  • 工厂(factory):负责实现对象创建逻辑的对象或方法,该逻辑过于复杂,无法由类的构造函数直接完成。还可以隐藏被实例化的具体类。
  • 存储库(repository):用于访问持久化实体的对象,存储库封装了访问数据库的底层机制
  • 服务(service):实现不属于实体或值对象的业务逻辑的对象。

微服务架构设计模式读书笔记相关推荐

  1. 微服务架构设计模式 读书笔记一

    作者:[美] 克里斯·理查森(Chris Richardson) 是Java社区的著名布道师.JavaOne等知名技术大会的常年主讲人,也是<POJOs in Action>(中文名< ...

  2. 轻量级微服务架构【读书笔记2】

    1. Spring Boot 是什么(What) Spring Boot 是为生产级 Spring 应用而生的,它使得开发 Spring 应用程序更加高效.简洁. 1.1 由来 Spring 1.0 ...

  3. 微服务架构设计模式学习笔记——六边形架构

    目录 一 软件架构的4+1模型 二 分层架构风格 三 六边型架构 四 代码示例 五 总结 一 软件架构的4+1模型 先上图,软件架构的4+1模型如图1.1所示: 图1.1 4+1模型 注:上图中的元素 ...

  4. 规模化微服务——《微服务设计》读书笔记

    改变思维的角度:故障无处不在 当微服务规模化后,故障是无可避免的,以往我们总是想尽力避免故障的发生,而当故障实际发生时,我们往往束手无策.我们花了很多时间在流程设计和应用设计的层面上来阻止故障的发生, ...

  5. 康威定律和系统设计——《微服务设计》读书笔记

    康威定律 任何组织在设计一套系统时,所交付的设计方案在结构上都与该组织的沟通结构保持一致. --梅尔.康威 如何理解这句话在软件工程上的含义?埃里克.S.雷蒙德说:如果你有四个小组开发一个编译器,那你 ...

  6. 安全——《微服务设计》读书笔记

    身份认证和授权       1.单点登录(SSO) 当主体试图访问一个资源,他会被定向到一个身份提供者那里进行身份验证,身份提供者验明正向后会发消息给服务提供者,让服务提供者来决定是否允许它访问资源. ...

  7. 监控——《微服务设计》读书笔记

    在单块应用的世界里,当我们遇到问题时,我们至少清楚从哪里开始调查.网站访问速度?网站访问异常?CPU占用过高?这些都是单块应用程序的问题,单一的故障点会极大地简化对问题的排查. 而现在我们面对了多个微 ...

  8. 测试——《微服务设计》读书笔记

    一.测试象限(Brain Marick) 二.测试金字塔(Mike Cohn)       1.单元测试 通常只测试一个函数或方法调用,通过TDD或者基于属性而写的测试就属于这一类,在UnitTest ...

  9. 部署:持续集成(CI)与持续交付(CD)——《微服务设计》读书笔记

    一.CI(Continuous Integration)简介  CI规则1:尽量频繁地把代码签入到分支中以进行集成 CI规则2:不光要对语法进行验,也要提供一系列的自动化来验证 CI规则3:CI失败后 ...

最新文章

  1. 【python练习】支付宝自动偷取能量
  2. BaseAction
  3. Docker : 数据卷(创建、挂载、查看、删除)
  4. php7数据库备份还原,基于thinkphp的数据库在线备份还原
  5. java conditionobject_Java AbstractQueuedSynchronizer源码阅读4-ConditionObject
  6. 用HTML5的DOCTYPE标签兼容各版本IE浏览器的方法技术
  7. GsonFormat的使用
  8. Microsoft Office 不同电脑不同电脑登录用户的数据同步
  9. python语言创始人中文名_十大编程语言创始人,看看有没有你在用的语言?
  10. system函数的详细使用
  11. python爬取网络中的QQ号码
  12. 制作谷歌浏览器 Google Chrome 免安装绿色版!
  13. 多目标人工秃鹫优化算法(MATLAB源码分享,智能优化算法) 提出了一种多目标版本的人工秃鹫优化算法(AVOA)
  14. 向日葵设置开机自启动
  15. java 数学公式解析框架有哪些_开源工具 | 推荐几个Gitee火热Java项目
  16. pandas读取文件参数
  17. 年月日时天干地支推算(农历、公历)
  18. 如何双开或多开skype
  19. CSS实现3D书本效果
  20. predis操作redis方法大全

热门文章

  1. [maven] settings 文件 本地maven仓库
  2. SSAS分区数据量过大处理参考 转- Recommended Practices with Partitions and Aggregations
  3. python入门到精通需要学多久-廖雪峰python教程要学多久-零基础学Python需要多久...
  4. python程序员工资低吗-程序员嫌工资低拒绝offer,HR:估计你一辈子就是个程序员...
  5. python学习官网-Python学习(一)—— 初识python
  6. python在线课程价格-杭州python课程价格
  7. python语言命令大全-python常用命令
  8. python游戏程序-python游戏程序
  9. python绘制简单直方图-Python数据分析:统计函数绘制简单图形
  10. python matplotlib散点图-Matplotlib scatter绘制散点图的方法实现