语法:
alter table 表名 add constraint FK_ID foreign key(你的外键字段名) REFERENCES 外表表名(对应的表的主键字段名)
例:
alter table tb_active add constraint FK_ID foreign key(user_id) REFERENCES tb_user(id)
//FK_ID是外键的名称
/*
CREATE TABLE `tb_active` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`content` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`user_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `user_id_2` (`user_id`),
CONSTRAINT `FK_ID` FOREIGN KEY (`user_id`) REFERENCES `tb_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
*/
什么是微服务?
微服务(Microservices Architecture)是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。
微服务的概念源于2014年3月Martin Fowler所写的文章“Microservices” martinfowler.com/articles/mi…
单体架构(Monolithic Architecture )
企业级的应用一般都会面临各种各样的业务需求,而常见的方式是把大量功能堆积到同一个单体架构中去。比如:常见的ERP、CRM等系统都以单体架构的方式运行,同时由于提供了大量的业务功能,随着功能的升级,整个研发、发布、定位问题,扩展,升级这样一个“怪物”系统会变得越来越困难。
这种架构模式就是把应用整体打包部署,具体的样式依赖本身应用采用的语言,如果采用java语言,自然你会打包成war包,部署在Tomcat或者Jetty这样的应用服务器上,如果你使用spring boot还可以打包成jar包部署。其他还有Rails和Node.js应用以目录层次的形式打包
上图:单体架构
大部分企业通过SOA来解决上述问题,SOA的思路是把应用中相近的功能聚合到一起,以服务的形式提供出去。因此基于SOA架构的应用可以理解为一批服务的组合。SOA带来的问题是,引入了大量的服务、消息格式定义和规范。
多数情况下,SOA的服务直接相互独立,但是部署在同一个运行环境中(类似于一个Tomcat实例下,运行了很多web应用)。和单体架构类似,随着业务功能的增多SOA的服务会变得越来越复杂,本质上看没有因为使用SOA而变的更好。图1,是一个包含多种服务的在线零售网站,所有的服务部署在一个运行环境中,是一个典型的单体架构。
单体架构的应用一般有以下特点:
微服务架构(Microservices Architecture)
微服务架构的核心思想是,一个应用是由多个小的、相互独立的、微服务组成,这些服务运行在自己的进程中,开发和发布都没有依赖。不同服务通过一些轻量级交互机制来通信,例如 RPC、HTTP 等,服务可独立扩展伸缩,每个服务定义了明确的边界,不同的服务甚至可以采用不同的编程语言来实现,由独立的团队来维护。简单的来说,一个系统的不同模块转变成不同的服务!而且服务可以使用不同的技术加以实现!
上图:微服务架构
微服务设计
那我们在微服务中应该怎样设计呢。以下是微服务的设计指南:
微服务消息
在单体架构中,不同功能之间通信通过方法调用,或者跨语言通信。SOA降低了这种语言直接的耦合度,采用基于SOAP协议的web服务。这种web服务的功能和消息体定义都十分复杂,微服务需要更轻量的机制。
同步消息 REST
同步消息就是客户端需要保持等待,直到服务器返回应答。REST是微服务中默认的同步消息方式,它提供了基于HTTP协议和资源API风格的简单消息格式,多数微服务都采用这种方式(每个功能代表了一个资源和对应的 *** 作)
异步消息 – AMQP, STOMP, MQTT
异步消息就是客户端不需要一直等待服务应答,有应到后会得到通知。某些微服务需要用到异步消息,一般采用AMQP, STOMP, MQTT 这三种通讯协议
消息格式 – JSON, XML, Thrift, ProtoBuf, Avro
消息格式是微服务中另外一个很重要的因素。SOA的web服务一般采用文本消息,基于复杂的消息格式(SOAP)和消息定义(xsd)。微服务采用简单的文本协议JSON和XML,基于HTTP的资源API风格。如果需要二进制,通过用到Thrift, ProtoBuf, Avro。
服务约定 – 定义接口 – Swagger, RAML, Thrift IDL
如果把功能实现为服务,并发布,需要定义一套约定。单体架构中,SOA采用WSDL,WSDL过于复杂并且和SOAP紧耦合,不适合微服务。
REST设计的微服务,通常采用Swagger和RAML定义约定。
对于不是基于REST设计的微服务,比如Thrift,通常采用IDL(Interface Definition Languages),比如Thrift IDL。
微服务集成 (服务间通信)
大部分微服务基于RPC、HTTP、JSON这样的标准协议,集成不同标准和格式变的不再重要。另外一个选择是采用轻量级的消息总线或者网关,有路由功能,没有复杂的业务逻辑。下面就介绍几种常见的架构方式。
点对点方式
点对点方式中,服务之间直接用。每个微服务都开放REST API,并且调用其它微服务的接口。
上图:通过点对点方式通信
很明显,在比较简单的微服务应用场景下,这种方式还可行,随着应用复杂度的提升,会变得越来越不可维护。这点有些类似SOA的ESB,尽量不采用点对点的集成方式。
API-网关方式
API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能个。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。
上图:通过API-网关暴露微服务
所有的业务接口通过API网关暴露,是所有客户端接口的唯一入口。微服务之间的通信也通过API网关。\
采用网关方式有如下优势:
目前,API网关方式应该是微服务架构中应用最广泛的设计模式。
消息代理方式
微服务也可以集成在异步的场景下,通过队列和订阅主题,实现消息的发布和订阅。一个微服务可以是消息的发布者,把消息通过异步的方式发送到队列或者订阅主题下。作为消费者的微服务可以从队列或者主题共获取消息。通过消息中间件把服务之间的直接调用解耦。
上图:异步通信方式
通常异步的生产者/消费者模式,通过AMQP, STOMP, MQTT 等异步消息通讯协议规范。
数据的去中心化
单体架构中,不同功能的服务模块都把数据存储在某个中心数据库中。
每个微服务有自己私有的数据库,其它微服务不能直接访问。单体架构,用一个数据库存储所有数据
微服务方式,多个服务之间的设计相互独立,数据也应该相互独立(比如,某个微服务的数据库结构定义方式改变,可能会中断其它服务)。因此,每个微服务都应该有自己的数据库。
每个微服务有自己私有的数据库,其它微服务不能直接访问。每个微服务有自己私有的数据库,其它微服务不能直接访问。
数据去中心话的核心要点:
数据的去中心化,进一步降低了微服务之间的耦合度,不同服务可以采用不同的数据库技术(SQL、NoSQL等)。在复杂的业务场景下,如果包含多个微服务,通常在客户端或者中间层(网关)处理。
微服务架构的优点:
微服务架构的缺点:
微服务的一些想法在实践上是好的,但当整体实现时也会呈现出其复杂性。
关于微服务架构的取舍
在微服务中,一个逻辑上原子 *** 作可以经常跨越多个微服务。即使是单片系统也可能使用多个数据库或消息传递解决方案。使用多个独立的数据存储解决方案,如果其中一个分布式流程参与者出现故障,我们就会面临数据不一致的风险 - 例如在未下订单的情况下向客户收费或未通知客户订单成功。在本文中,我想分享一些我为使微服务之间的数据最终保持一致而学到的技术。
为什么实现这一目标如此具有挑战性?只要我们有多个存储数据的地方(不在单个数据库中),就不能自动解决一致性问题,工程师在设计系统时需要注意一致性。 目前,在我看来,业界还没有一个广为人知的解决方案,可以在多个不同的数据源中自动更新数据 - 我们可能不应该等待很快就能获得一个。
以自动且无障碍的方式解决该问题的一种尝试是实现两阶段提交(2PC)模式的XA协议。但在现代高规模应用中(特别是在云环境中),2PC似乎表现不佳。为了消除2PC的缺点,我们必须交易ACID for BASE并根据要求以不同方式覆盖一致性问题。
在多个微服务中处理一致性问题的最着名的方法是Saga模式。 您可以将Sagas视为多个事务的应用程序级分布式协调 。 根据用例和要求,您可以优化自己的Saga实施。 相反,XA协议试图涵盖所有场景。 Saga模式也不是新的。 它在过去已知并用于ESB和SOA体系结构中。 最后,它成功地转变为微服务世界。 跨越多个服务的每个原子业务 *** 作可能包含技术级别的多个事务。 Saga Pattern的关键思想是 能够回滚其中一个单独的交易 。 众所周知,开箱即用的已经提交的单个事务无法进行回滚。 但这是通过引入 补偿 *** 作来 实现的 - 通过引入“取消” *** 作。
除了 取消 之外,您还应该考虑使您的服务具有 幂等性 ,以便在出现故障时重试或重新启动某些 *** 作。 应监控故障,并应积极主动地应对故障。
如果在进程的中间 负责调用补偿 *** 作的系统崩溃或重新启动 ,该怎么办? 在这种情况下,用户可能会收到错误消息,并且应该触发补偿逻辑,或者 - 当处理异步用户请求时,应该恢复执行逻辑。
要查找崩溃的事务并恢复 *** 作或应用补偿,我们需要协调来自多个服务的数据。 对账
是在金融领域工作的工程师所熟悉的技术。你有没有想过银行如何确保你的资金转移不会丢失,或者两个不同的银行之间如何汇款?快速回答是对账。
回到微服务,使用相同的原则,我们可以在一些 动作触发器 上协调来自多个服务的数据。当检测到故障时,可以按计划或由监控系统触发 *** 作。最简单的方法是运行逐记录比较。可以通过 比较聚合值来 优化该过程。在这种情况下,其中一个系统将成为每条记录的真实来源。
想象一下多步骤交易。如何在对帐期间确定哪些事务可能已失败以及哪些步骤失败?一种解决方案是 检查每个事务的状态 。在某些情况下,此功能不可用(想象一下发送电子邮件或生成其他类型消息的无状态邮件服务)。在其他一些情况下,您可能希望立即了解事务状态,尤其是在具有许多步骤的复杂方案中。例如,预订航班,酒店和转机的多步订单。
在这些情况下,事件日志可以提供帮助。 记录是一种简单但功能强大的技术 。 许多分布式系统依赖于日志 。 “ 预写日志记录 ”是数据库在内部实现事务行为或维护副本之间一致性的方式。相同的技术可以应用于微服务设计。在进行实际数据更改之前,服务会写入有关其进行更改的意图的日志条目。实际上, 事件日志可以是协调服务所拥有的数据库中的表或集合 。
事件日志不仅可用于 恢复事务处理 ,还可用于为系统用户,客户或支持团队提供 可见性 。但是,在简单方案中,服务日志可能是冗余的, 状态端点或状态字段 就足够了。
到目前为止,您可能认为sagas只是编配(orchestration )方案的一部分。但是sagas也可以用于编排(choreography ),每个微服务只知道过程的一部分。 Sagas包括处理分布式事务的正流和负流的知识。在编排(choreography )中,每个分布式事务参与者都具有这种知识。
到目前为止描述的一致性解决方案并不容易。他们确实很复杂。但有一种更简单的方法: 一次修改一个数据源 。我们可以将这两个步骤分开,而不是改变服务的状态并在一个过程中发出事件。
在主要业务 *** 作中,我们修改自己的服务状态,而单独的进程可靠地捕获更改并生成事件。这种技术称为变更数据捕获(CDC)。实现此方法的一些技术是Kafka Connect或Debezium。
但是,有时候不需要特定的框架。一些数据库提供了一种友好的方式来拖尾其 *** 作日志,例如MongoDB Oplog。如果数据库中没有此类功能,则可以通过时间戳轮询更改,或使用上次处理的不可变记录ID查询更改。避免不一致的关键是使数据更改通知成为一个单独的过程。在这种情况下,数据库记录是 单一的事实来源 。只有在首先发生变化时才会捕获更改。
更改数据捕获的最大缺点是业务逻辑的分离。更改捕获过程很可能与更改逻辑本身分开存在于您的代码库中 - 这很不方便。最知名的变更数据捕获应用程序是与域无关的变更复制,例如与数据仓库共享数据。对于域事件,最好采用不同的机制,例如明确发送事件。
让我们来看看颠倒的单一事实来源。如果不是先写入数据库,而是先触发一个事件,然后与自己和其他服务共享。在这种情况下,事件成为事实的唯一来源。这将是一种事件源的形式,其中我们自己的服务状态有效地成为读取模型,并且每个事件都是写入模型。
“事件优先”方法面临的挑战也是CQRS本身的挑战。想象一下,在下订单之前,我们想要检查商品的可用性。如果两个实例同时收到同一项目的订单怎么办?两者都将同时检查读取模型中的库存并发出订单事件。如果没有某种覆盖方案,我们可能会遇到麻烦。
处理这些情况的常用方法是乐观并发:将读取模型版本放入事件中,如果读取模型已在消费者端更新,则在消费者端忽略它。另一种解决方案是使用悲观并发控制,例如在检查项目可用性时为项目创建锁定。
“事件优先”方法的另一个挑战是任何事件驱动架构的挑战 - 事件的顺序。多个并发消费者以错误的顺序处理事件可能会给我们带来另一种一致性问题,例如处理尚未创建的客户的订单。
诸如Kafka或AWS Kinesis之类的数据流解决方案可以保证将按顺序处理与单个实体相关的事件(例如,仅在创建用户之后为客户创建订单)。例如,在Kafka中,您可以按用户ID对主题进行分区,以便与单个用户相关的所有事件将由分配给该分区的单个使用者处理,从而允许按顺序处理它们。相反,在Message Brokers中,消息队列具有一个订单,但是多个并发消费者在给定顺序中进行消息处理(如果不是不可能的话)。在这种情况下,您可能会遇到并发问题。
实际上,在需要线性化的情况下或在具有许多数据约束的情况(例如唯一性检查)中,难以实现“事件优先”方法。但它在其他情况下确实很有用。但是,由于其异步性质,仍然需要解决并发和竞争条件的挑战。
有许多方法可以将系统拆分为多个服务。我们努力将单独的微服务与单独的域匹配。但域名有多细化?有时很难将域与子域或聚合根区分开来。没有简单的规则来定义您的微服务拆分。
虽然匹配帐户余额至关重要,但有许多用例,其中一致性不那么重要。想象一下,为分析或统计目的收集数据。即使我们从系统中随机丢失了10%的数据,也很可能不会影响分析的业务价值。
数据的原子更新需要两个不同系统之间达成共识,如果单个值为0或1则达成协议。当涉及到微服务时,它归结为两个参与者之间的一致性问题,并且所有实际解决方案都遵循一条经验法则:
在给定时刻,对于每个数据记录,您需要找到系统信任的数据源
事实的来源可能是事件,数据库或其中一项服务。实现微服务系统的一致性是开发人员的责任。我的方法如下:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)