背景 相信前面几篇关于微服务的文章也介绍了那么多了,在构建微服务的过程中确实需要这么一个东西,即便不是在构建微服务,那么在构建分布式应用的过程中也会遇到分布式事务的问题,那么 CAP 就是在这样的背景下诞生的。 最初打算做这个东西是在去年(2016)年底,最初是为了解决分布式系统中的分布式事务的问题,然后当时有了一个大概的概念轮廓,当时我对于前面两篇文章中关于异步消息和微服务之间通讯还不是太了解,只是觉得这样能够解决这一系列的问题,然后就着手做了,最后发现和这些概念竟然不谋而合。 经过大半年的不断重构以及修改,最终 CAP 1.0 版本发布了。作为一个开源项目,最初项目是在我的个人Github下,然后于上个月已经贡献给了 .NET China Foundation 组织,目前该项目由我和 DotNetCore 项目组共同维护。 CAP 介绍 Github:https://github.com/dotnetcore/CAP 开源协议:MIT CAP 是一个在分布式系统中(SOA,MicroService)实现事件总线及最终一致性(分布式事务)的一个开源的 C# 库,她具有轻量级,高性能,易使用等特点。 你可以轻松的在基于 .NET Core 技术的分布式系统中引入CAP,包括但限于 ASP.NET Core 和 ASP.NET Core on .NET Framework。 CAP 以 NuGet 包的形式提供,对项目无任何入侵,你仍然可以以你喜爱的方式来构建分布式系统。 CAP 具有 Event Bus 的所有功能,并且CAP提供了更加简化的方式来处理EventBus中的发布/订阅。 CAP 具有消息持久化的功能,也就是当你的服务进行重启或者宕机时,她可以保证消息的可靠性。 CAP 实现了分布式事务中的最终一致性,你不用再去处理这些琐碎的细节。 CAP 提供了基于 Microsoft DI 的 API 服务,她可以和你的 ASP.NET Core 系统进行无缝结合,并且能够和你的业务代码集成支持强一致性的事务处理。 CAP 是开源免费的。CAP基于MIT协议开源,你可以免费的在你的私人或者商业项目中使用,不会有人向你收取任何费用。 Getting Started 目前, CAP 同时支持使用 RabbitMQ 或 Kafka 进行底层之间的消息发送,你不需要具备 RabbitMQ 或者 Kafka 的使用经验,仍然可以轻松的集成到项目中。 CAP 目前支持使用 MS Sql Server 数据库的项目,其他数据库正在支持中… CAP 同时支持使用 EntityFrameworkCore 和 Dapper 的项目,你可以根据需要选择不同的配置方式。 下面是CAP在系统中的一个不完全示意图: 图中实线部分代表用户代码,虚线部分代表CAP内部实现。 下面,我们看一下 CAP 怎么集成到项目中: […]
View Details前言 在上一篇文章中,我们说到了异步消息通讯,下面这篇文章呢,大部分内容是翻译来自于这篇微软的文章,所以其内容还是具有一定的理论指导意义的。 当我们跨多个微服务进行内部通讯的时候,异步消息和事件驱动至关重要。我们可能需要在不同的边界上下文中进行域模型的更新。 我们举个例子,比如 eShop 这个项目中,Ording 服务在下单的时候要和 Catelog 服务进行通讯进行库存的扣减操作,这个时候我们就需要一种方式来做这个事情,并且能够在发生故障的时候也能正常工作,也就说需要进行基于异步消息和最终一致性的通讯方式。 当使用基于消息的通讯方式的时候,进程中是采用的异步的方式通讯的。客户端向某个服务发送消息,如果这个消息需要回复,那么另一个服务会向客户端发送一个不同的消息,并且客户端会认为该消息不会立即被接收到,并且不存在响应,这就是一种基于消息的通讯方式。 消息由标题(name 或者 title)和内容(Body)共同构成。消息通常会通过一些异步协议进行发送(如AMQP,kafka协议)。 异步消息通讯有两种:一种是单接收者(端到端),另外一种是多接收者(广播)。 如果有同学对消息队列比较了解的话,这就是消息队列的两种典型使用方式。 基于消息的单接收者 单接收者也就是说是点到点的通讯,将消息使用队列等方式从一点发送的另外一点,并且该消息仅会被处理(消费)一次。这中间一个特殊情况就是,当队列在尝试从故障中恢复时候,有可能会多次发送相同的消息,客户端必须实现幂等性以便能够处理相同的消息一次。 单接收器消息通讯的方式适用于将异步命令从一个微服务发送到另一个微服务。如下图: 一旦开始使用了基于消息的通讯,你应该避免将基于消息的通讯和同步的HTTP通讯混合起来。 注意:当command来到客户端应用程序时候,它们可以实现为HTTP的同步命令。当你需要更高的可扩展性或者你业务流程中已经使用了基于消息的方式时,那么你就应该使用基于消息的通讯方式。 基于消息的多接收者 多接收者是消息通讯中一种更加灵活的方式,你可能还需要使用 发布/订阅 这种机制,以便于接收来自发送方或者其他微服务或者外部应用程序的消息。 这样,将来可以添加更多的其他消费者用户,而无需修改发送方的服务代码。 当你使用发布/订阅这种通讯方式的时候,在发送端和订阅端你也许会用到事件总线的接口。 异步事件驱动通讯 当使用异步事件驱动通信时, 一个微服务当域模型发生更新时,会发布一个集成事件,然后另外一个微服务可能需要关注这个事件,比如 eShop 中,当 product catelog 微服务发生一个价格变动的时候。另外的微服务需要订阅这个事件,这样就可以以异步的方式来接收这个事件。然后当事件触发的时候,订阅端就可以更新自己的 Domain Model,从而集成发送端的事件。 事件总线(Event Bus)可以设计为一个抽象类或接口,集成API 订阅或取消订阅事件和发布事件。事件总线还可以有一个或多个实现基于任何进程间消息传递代理,像一个消息队列或服务总线支持异步通信和发布/订阅模型。 如果事件驱动中集成了最终一致性,那么用户应该清楚这种行为,客户端用户及其业务必须显式地拥抱最终一致性并且意识到在许多情况下这种业务没有任何问题。 你可以跨越多个微服务来集成事件驱动,这些服务之间拥有最终一致性。 一个最终一致性的“事务”可能是由多个分布式的事件操作组成的一个集合。在每一个事件中,相关的微服务都在更新自己的领域实体并且发布另外一个需要集成的事件到Eventbus中。 很重要的一点是 , 你可能需要多个微服务订阅一个事件。因此, 您可以使用基于事件驱动的发布/订阅消息模式的消息通讯, 如下图所示。这种发布/订阅的机制不是微服务独有的。它类似于DDD中边界上下文之间的通讯方式, 或者类似于CQRS架构中的从写库更新数据到读库的这种模式。它最终的目标是在整个分布式系统多个数据源之间的保持最终一致性。 你将实现基于消息的事件驱动通信协议。AMQP可以帮助实现可靠的排队通信。 当您使用事件总线时,您可能希望使用的是抽象级别的东西(如Eventbus interface),它使用类似于 RabbitMQ 或服务总线(如Azure Service Bus及 Topic)来作为底层,然后提供相关API。或者,您可能希望使用更高级别的服务总线,如NServiceBus,MassTransit或Brighter来作为Eventbus和发布/订阅系统。 关于生产环境中的消息通讯技术 在消息通讯技术中,实现抽象级别的事件总线是存在不同的级别的。例如,像RabbitMQ 和Azure Event Bus这样的产品比其他产品(如NServiceBus,MassTransit或Brighter)级别就更低一些,NServiceBus这些他们可能基于底层的这些之上,当然后者也更加的重量级。 但是很多时候,我们可能学习这些重量级的东西需要花费很多的成本,而且我们也用不到那么重量级的东西,正如在eShopOnContainers示例中所做的那样,在Docker容器上运行的RabbitMQ之上的简单实现可能就足够了。 但是,在生产系统中对于需要可扩展性的关键型任务,您可能需要进行评估一下。 为了使分布式应用程序开发更容易的并且提供高级抽象的功能,我们建议您评估其他商业和开源的服务总线,如NServiceBus,MassTransit和Bright。当然,您可以在像RabbitMQ和Docker这样的低级技术的基础上构建自己的服务总线功能。但是,这种工作对于企业应用来说可能花费的太多。 异步消息解决方案 到这里,我们会发现,我们真的是太需要这么样一个组件来帮助我们实现这些东西了,既能提供高抽象级别的API帮助我们简化操作,又能轻量级并且容易学习和集成到项目中,并且能够帮助我们解决分布式事务中的一致性问题,如果是开源免费的,那就更好了。 然后,重点来了~ 请期待下一篇,异步消息,分布式事务解决方案:(保密脸^_^)… 你可以关注一下博主,会第一时间收到通知哦~ 放心不会太久。 本文地址:http://www.cnblogs.com/savorboard/p/microservice-eventbus.html 作者博客:Savorboard 欢迎转载,请在明显位置给出出处及链接
View Details前言 接上一篇。 上一篇未完待续的原因是当时刚好是6-30号晚上马上12点了还没写完,然后我想赶在7月1号之前发出去,所以当时就发了。然后在发的时候出了一点问题,结果发出去的时候刚好是 7.1号 00:00分,所以就很尴尬~~ 这一篇,我们就接着说一说微服务吧。 接上文 第四步,重构。 当你写完代码之后,我认为有一个比较重要的步骤就是对写的代码进行一番重构,重构一般从两方面下手,第一方面是代码的命名以及格式,第二方面是代码的组织结构。 针对于代码命名以及格式的重构其实是有方法和技巧的,比如方法的命名以及方法的拆分等可以从<<重构>>这些书中来获取一些指导意见等,对于代码格式的话基本上现代的IDE都提供了很好的格式化工具,使用便是。 针对于组织结构的重构有时候是需要依赖很多你的经验的,经验丰富的程序员知道如果的去对写过的代码进行抽象,然后利用某种设计模式或者是面向对象的原则来让代码更加的利于维护和扩展,这种技能往往更难掌握,需要你去阅读很多的别人优秀的代码,然后去思考和学习。 OK,以上就是在构建单个微服务程序的个人总结的一些指导原则吧。 部署方式 指导原则可以帮助我们在构建系统的时候使其保持一个良好的结构,但是你还需要从整体上来把控整个微服务的布局。什么意思呢? 我们知道,微服务最良好的部署方式就是使用 Docker 容器进行部署,因为这样便于管理和配置。 在以前的单体结构的项目中也可以使用Docker进行整块的部署,我们可能部署到多个容器中,然后前置一个负载均衡器进行路由的转发,这样也是可以的。 通常情况下,即使我们的程序架构风格不是微服务,那么在组织代码结构时,也会进行模块的划分,比如划分为会员,商品,库存等。下面是一个单体应用整块部署使用Docker的部署图: 但是这种模式其实与容器的初衷是有一点违背的,容器所倡导的是一个容器只做一件事情。整块部署有一个明显的缺点是,如果随着应用程序的扩展那么每次代码的修改都要全部进行重新发布,但是我们经常修改的代码可能就是某一快的功能,而另外一些代码则永远不会动,这样不但发布程序的时候发布包很大,也容易出错,出问题造成的影响也比较广。 比如,在一个电商网站中,有一些模块是经常发生变化的,比如一些促销,产品等页面,这些页面的访问量也很大,而另外一些页面比如用户中心积分查看,历史订单查看这些功能则不会经常变动,并且访问量要小很多。那么如果他们都在一个系统中,势必会引起这些问题:1、性能优化,如果访问量很高的模块出现性能问题,那么你只能针对整个程序进行扩展部署,而不是单个模块。2、测试,由于模块的依赖,那么在修改一块地方的时候,必须重新对整个应用程序进行一次测试,并且重新部署所有这些实例。3、无法进行扩展,你无法简单的进行接口或者服务的扩展,这会使SOA变得很困难。 那么我们就再顺便说一下SOA,我们知道大多数的公司在 .NET FX 时代使用 WCF 技术进行项目的 SOA 化,比如常见简单的会使用 SOAP ,HTTP,MQ等进行通讯,他们也会把系统进行划分(子系统)和分层。听起来可能和微服务有点像,那么他们有什么区别呢? 想必这是一个很多人讨论过的话题,那么直接说结论吧。 微服务它来自于SOA,但是和SOA不同的是它并没有那么重量级,什么意思呢?比如它没有SOA中的像集群的Broker, 那么大的组织的划分,中央负责人, 还有企业服务总线 (ESB)等。 然后就是我们的主题微服务,微服务架构的定义就是一组小型的服务。每一个服务都位于自己的进程中,并且使用诸如HTTP, WebSockets,或者 AMQP 之类的协议进行通讯。它很小,并且专注于做好一件事,这个很重要,它看起来像OOP中的单一职责原则,如果你2周之内不能完成一个微服务模块,那么可能你对于边界划分出了点问题。 关于微服务的优缺点不做过多的介绍了,有兴趣的同学可以看一下在我博客里面的 Martin Fowler 的 这篇文章。 这篇文章提到了『微服务设计』这本书,如果你想对微服务有更多了解的话可以看一下这本书,建议购买。 微服务中的一些技术挑战 下面需要说的是个人对于在构建微服务的过程中会面临的一些问题,或者说叫做挑战吧。 1、微服务的边界怎么定义。 上一篇文章已经提到过了,在定义微服务边界的过程中,DDD中的指导原则会帮助你大忙。 这可能是你在构建微服务过程中遇到的第一个难题,一个良好的微服务能够对其他微服务尽可能少的依赖,同一个应用程序中你需要用不同的上下文进行解耦,每个上下文有可能是使用不同的程序语言的。这些上下文应该被独立的定义和管理。比如一个User,在 Identity 上下文可能是一个用户,在 CRM 中是一个客户,在订单上下文是一个买家,等等。 2、如何跨微服务进行查询。 因为我们已经微服务化了,所以我们的应用程序数据可能分布在不同的数据库中,那么如何实现从多个为微服务器数据库中查询数据成为一个难题了。 比如,我们前台的数据展示页面需要一个销售统计的报表,其中的数据分别来源于订单,库存和商品。那么我们应该怎么样来处理这种复杂性呢? 目前流行的解决方案有以下几种: API网关。 使用API网关来对多个微服务器的数据库进行聚合。 但是在实现这种模式的时候你需要非常的小心,它有可能是你系统中性能的瓶颈,甚至它有可能违背微服务的自治原则。为了尽可能避免这个陷阱,你需要设计多个细粒度的 API 网关,每个网关关注系统一个垂直领域的“切片”区域,或者是一个业务领域。现在大部分的云提供商都提供的有 API 网关相关服务,比如AWS的 Amazon API Gateway,Azure 的 Establish API Gateways 等,借助于这些服务可以方便的对 API 进行管理。 CQRS与读表。 不知道大家有没有听说话物化视图(Materialized View)这个名词。你可以理解为远程视图,使用这种方法,你可以提前准备好一个只读表,其中包括多个微服务的数据,这个只读表的结构和你展示给客户的页面数据是对应的。 那么有同学可能会存在这样一个问题,假如我基于不同的数据库建立一个物化视图,那么在我建立物化视图的过程中,我应该怎么样进行查询,因为对于单个数据库的查询可能仍然是复杂的。确实如此,在以前单个应用程序的时候,我们在呈现个客户端需要的数据的时候,可能会是一个复杂的SQL Join连接查询的结果。那么这里的解决方案就是,我们需要建立一个和我们业务无关的一个单独的数据库,然后这个数据库中会包含一些和界面上需要的数据进行一一对应的一些查询用的表,然后我们应用程序中引入 CRQS 这种模式,将需要的数据写入到这些查询表中。 这不仅解决了跨微服务查询这个难题,并且也提高了性能。但是引入CQRS也就意味着你需要拥抱最终一致性。 数据中心的 “冷数据”。 对于一些不需要做实时数据的复杂查询或者报表,通常是将微服务的“热数据”作为“冷数据”导出到数据中心以供报表。这个数据中心可能是一个基于大数据的系统,比如 Hadoop,AWS的Redshit,Azure的SQL Data warehosue等。 同步的过程你可以使用事件驱动这种通讯技术,或者是一些数据库提供的基础设施中的导入/导出工具等。如果使用事件驱动的话,其过程有点类似上面的CRQS查询过程。 3、如何实现多个微服务的数据一致性。 […]
View Details前言 前几天在博客园看到有园友在分享关于微软的一个微服务架构的示例程序,想必大家都已经知道了,那就是eShopOnContainers。 我们先不看项目的后缀名称 OnXXX ,因为除了 OnContainers 还有 OnAzure,OnWeb,OnKubernetes 以及 OnServiceFabric。 我们就还是来先说说 eShop 这个项目吧,eShop 是 ASP.NET Core 发布之后微软新开源出来的一个示例项目,想必大家之前也都知道微软放出来的关于 Web 的示例项目还有 PetShop, Music Store 这两个项目。关于这两个项目我们就不做过多的介绍了,但是关于这两个项目的架构风格我们不得不提起。 PetShop:WebFrom 的示例程序。典型的三层架构风格的应用程序。 MusicStore: 针对于 MVC3-5 框架和 EF 的一个示例程序。无明显架构风格。 eShop: 针对于 ASP.NET Core 的示例程序。它是一个 Rest 架构风格的应用程序。 我们从微软放出来的这些示例程序中也许可以看出些许东西,那就是近些年来关于架构风格的演变,或者叫微软架构风格的演变,在这里我不打算讨论关于软件架构更加深层次的一些这种东西,我们只从我们能够理解的东西看起。 微软架构风格 我不知道有没有人看过这本书,目前已经绝版了,它是早年间关于微软架构风格的一本指南书,里面描述了微软体系的架构风格的一些汇总。 这本书中列出来了以下这些架构风格: Client/Server Architectural Style Component-Based Architectural Style Domain Driven Design Architectural Style Layered Architectural Style Message-bus Architectural Style N-Tier / 3-Tier Architectural Style Object-Oriented Architectural Style Service-Oriented Architectural Style 我们可以看到微软所开源出来的这些示例程序其实都是在遵循这些架构风格中的某一种或者是多种。 PetShop 属于 N-Trie ,Music Store 属于 Layered,eShop 属于 Service-Oriented。 当然在 eShop 中微软不但使用了 Service-Oriented ,其中还包括 Domain Driver Design(DDD), […]
View Details原文链接: BASE: An Acid Alternative 数据库 ACID,都不陌生:原子性、一致性、隔离性和持久性,这在单台服务器就能搞定的时代,很容易实现,但是到了现在,面对如此庞大的访问量和数据量,单台服务器已经不可能适应了,而 ACID 在集群环境,几乎不可能达到我们的预期,保证了 ACID,效率就会大幅度下降,更要命的是,这么高的要求,不好扩展~于是又了 CAP 原则(Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性))和 BASE 原则(Basically Available(基本可用)、Soft state(软状态)、Eventually consistent(最终一致)),看看它们的英文,Availability/Basically Available,Consistency/Eventually consistent,基本上,BASE 原则对 CAP 原则的进一步诠释。 本文是Ebay的架构师在2008年发表给ACM的文章,是一篇解释BASE原则,或者说最终一致性的经典文章. 文中Dan讨论了BASE与ACID原则的基本差异, 以及如何设计大型网站以满足不断增长的可伸缩性需求,期间如何对业务做调整与折衷. 以及一些具体的折衷技术的介绍. 在对数据库进行分区后,为了可用性(Availability)牺牲部分一致性(Consistency)可以显著的提升系统的可伸缩性(Scalability). ——By DAN PRITCHETT, EBAY ,Translated by Jametong Web应用在过去10年变得越来越普及.无论是为最终用户还是为应用开发者构建的应用,对这个应用的希望很可能都是,此应用被最广泛的用户使用-广泛的使用会带来交易的增长.业务如果依赖于持久化,数据存储就很可能成为瓶颈. 扩展任何应用都有两种策略.第一种,也是最简单的一种,就是 纵向扩展 :将应用迁移到更大更强的计算机上. 目前可用的最大的机器也满足不了它的容量是它最明显的限制.纵向扩展也很昂贵,增加交易容量通常都需要购买下一个更大的机器.纵向扩展通常还会产生对供应商的依赖,从而进一步增加成本. 横向扩展 (Horizontal Scaling)提供了更多的灵活性,但也会显著的增加复杂度.横向数据扩展可能沿着两个方向发展.按 功能扩展 (Functional Scaling)牵涉到按功能对数据进行分组,并将不同的功能组分布在多个不同的数据库上.在功能内部将数据拆分到多个数据库上,也就是进行 分片 (Sharding),它为横向扩展增加一个新的维度.图-1简要阐释了横向数据扩展策略. 图-1 如图-1所示,横向扩展的两种方法可以同时进行运用.用户信息(Users)、产品信息(Products)与交易信息 (Transactions)可以存储在不同的数据库中.另外,每个功能区域根据其交易容量(transactional capacity)可以再拆分到多个数据库中.如图所示,功能区域可以相互独立地进行扩展. 功能分区(Functional Partitioning) 功能分区对于实现高可伸缩性相当重要.每一种好的数据库架构都会根据功能将概要(Schema)分解到多张表中.用户(Users)、产品 (Products)、交易(Transactions)以及通讯都是功能分区的例子. 常用的方法是,利用诸如外键(foreign key)一类的数据库概念来维持这些功能区域之间的数据一致性. 依赖数据库的约束保证功能组之间的一致性,会导致数据库的不同概要(schema)在部署策略上高度耦合.要支持约束,表必须存在单一的数据库服务器上,当交易率(transaction rate)增长时也无法对其进行横向扩展.很多情况下, 将数据的不同功能组迁移到相互独立的数据库服务器上是最容易实现的向外扩展(Scale-out)方案. 可扩展到非常高的交易量的概要会将不同的功能的数据放置在不同的数据库服务器上.这需要将数据之间的约束从数据库迁移到应用中去. 同时这也将引入一些新的挑战,本文的后续内容会对此进行深入探讨. CAP定理(CAP Theorem) Eric Brewer,一位加州大学伯克利分校的教授,Inktomi公司的共同创办人以及首席科学家,作出了以下推测,Web服务无法同时满足以下3个属性(由其首字母构成缩写CAP): 一致性(Consistency).客户端知道一系列的操作都会同时发生(生效). 可用性(Availability).每个操作都必须以可预期的响应结束. 分区容错性(Partition tolerance).即使出现单个组件无法可用,操作依然可以完成. 具体地讲,在任何数据库设计中,一个Web应用至多只能同时支持上面的两个属性.显然,任何横向扩展策略都要依赖于数据分区;因此,设计人员必须在一致性与可用性之间做出选择. ACID解决方案 ACID数据库事务极大地简化了应用开发人员的工作.正如其缩写标识所示,ACID事务提供以下几种保证: 原子性(Atomicity).事务中的所有操作,要么全部成功,要么全部不做. 一致性(Consistency).在事务开始与结束时,数据库处于一致状态. 隔离性(Isolation). 事务如同只有这一个操作在被数据库所执行一样. 持久性(Durability). 在事务结束时,此操作将不可逆转.(也就是只要事务提交,系统将保证数据不会丢失,即使出现系统Crash,译者补充). 数据库厂商在很久以前就认识到数据库分区的必要性,并引入了一种称为2PC(两阶段提交)的技术来提供跨越多个数据库实例的ACID保证.这个协议分为以下两个阶段: 第一阶段,事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交. 第二阶段,事务协调器要求每个数据库提交数据. 如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此事务中的那部分信息.这样做的缺陷是什么呢? 我们可以在分区之间获得一致性.如果Brewer的猜测是对的,那么我们一定会影响到可用性,但,怎么可以这样呢? 任何系统的可用性都是执行操作的相关组件的可用性的产物.此陈述的后半段尤其重要.系统中可能会使用但又不是必需的组件,不会降低系统的可用性.在两阶段提交中涉及到两个数据库的事务,它的可用性是这两个数据库中每一个的可用性的产物.例如,如果我们假设每个数据库都有为99.9%的可用性,那么这个事务的可用性就是99.8%,或者说每月43分钟的额外停机时间. 关于两阶段提交,你可以看看"改变未来的九大算法",里边有精辟的讲解~ 一种ACID的替代方案 如果ACID为分区的数据库提供一致性的选择,那么你如何实现可用性呢?答案是BASE(基本上可用、软(弱)状态、最终一致性). BASE与ACID截然相反.ACID比较悲观,在每个操作结束时都强制保持一致性,而BASE比较乐观,接受数据库的一致性处于一种动荡不定的状态.虽然,听起来很难应付,实际上这相当好管理,并且可带来ACID无法企及的更高级别的可伸缩性. BASE的可用性是通过支持局部故障而不是系统全局故障来实现的.下面是一个简单的例子:如果用户分区在5个数据库服务器上,BASE设计鼓励类似的处理方式,这样一个用户数据库的故障只会影响这台特定主机上的那20%的用户.这里不涉及任何魔法,不过,它确实可以带来更高的可感知的系统可用性. 因此,到目前为止,你已经将数据分解到了多个功能组中,并将最繁忙的功能组分区到了多个数据库中,如何在你的应用中应用BASE原则呢?与ACID 的典型应用场景相比,BASE需要对逻辑事务中的操作进行更加深入的分析.到底该如何进行分析呢?后续的内容将提供部分指导原则. 一致性模式(Consistency Patterns) 沿着Brewer的猜测,如果BASE在分区数据库中选择保留可用性(Availability), 那么,弱化一定程度的一致性就成为必然的选择.这通常难以决策,因为商业投资方与开发人员都倾向于认为一致性(Consistency)对应用的成功至关重要.哪怕是临时的不一致也瞒不过最终用户,因此,技术部门与产品部门都需要参与进来,以决定将一致性弱化到什么程度. 图-2是一个简单的概要,它阐释了BASE中一致性要考虑的事情.用户表存储用户信息,同时还包含总销售额与总购买额.这些都是运行时的统计.交易表存储每一笔交易,将买家、卖家以及交易金额关联在一起.这些是对实际使用的表进行过度简化后的结果,不过,它已经包含阐释一致性的多个方面的必要元素. […]
View Details前言 去年 12 月,我移植了大家所熟知 NPOI 到 .NET Core 版本,这里是当时发的博客,当时得到了很多同学的支持,社区反应也很好,在这里非常感谢当时推荐的朋友们。 去年的那个版本是针对于 .NET Core 1.0 的,从发布截止现在在 NuGet 大概有 2K 多的下载量,说明还是有很多同学在使用 NPOI 的,社区中也得到了很多同学的推广。 但是上一个移植的版本也有诸多缺陷和 bug,在 Github 上也收到了一些 Issue 进行反馈,很多 Bug 可能是移植过程中的bug,但是对于这些 Bug 可能我也无能为力,因为 NPOI 的代码是非常庞大和复杂的。 随着 .NET Core 2.0 的发布,我又重新移植了一遍 NPOI,注意是重新移植而不是从 1.0 版本迁移过来,由于 .NET Standard 2.0 的 API 增加了很多,所以移植过程还算顺利,这次移植应该是最大限度的保持了 NPOI 的原汁原味,敬请客官体验。 什么是 NPOI NPOI 是 构建在POI 3.x 版本之上的一个C#库,NPOI 可以在没有安装Office的情况下对 Word 或 Excel 文档进行读写操作。 POI是一个开源 的Java 读写Excel、WORD等微软OLE2组件文档的项目。 NPOI 由瞿总和他的团队由 Apache POI 移植到 .NET 的,以下是NPOI Github 地址: https://github.com/tonyqus/npoi Getting Started 移植版的 NPOI 是基于 .NET Standard 2.0 的,也就是说你可以在基于 .NET Core 2.0, .NET Frameework 4.6.1 等项目中进行引用使用。 新的 NPOI […]
View Details前言 有时候,我们在开发一个程序集供其他项目引用的时候,可能需要对外输出一些HTML的结构数据。 还有一些情况我们可能开发的是一个中间件,这个中间件需要提供一些界面来对外展示数据或者是内部的一些程序的运行信息,这个时候我们也需要一个界面来做这件事情。 那么,做这些界面最适合的结构非html莫属,在 ASP.NET 中那就是Razor视图了。 怎么样才能在程序集中集成 Razor 视图,并且能够以 NuGet 包的形式提供出去呢?也就是说别人只需要引用包,然后在浏览器敲指定的地址就可以看到你提供的界面了呢? 最典型的就是给你的用户提供Dashboard界面。 其实,做到这些并不难,现在,我来教你一步一步做。 Getting Started 这里我就以一个中间件程序为例,给用户提供一个Dashboard页面。 1、创建 Middleware Library 首先,我们参照我的这篇文章,使用一秒钟的时间新建一个中间件的模板项目。 这个模板项目还是 xproj 的产物,我也一直没有时间更新,所以vs打开的时候升级一下吧。 创建一个文件件,使用 yo aspnetcore-middleware生成一个模板项目。 然后使用VS打开并升级到 2.0 版本,测试项目被我卸载掉了。 我们还需要打开 Dashboard.csproj ,添加一些我们需要引用的包。
1 2 3 4 5 6 |
<PackageReference <span class="hljs-keyword">Include</span>=<span class="hljs-string">"Microsoft.AspNetCore.Http.Extensions"</span> Version=<span class="hljs-string">"2.0.0"</span> /> <PackageReference <span class="hljs-keyword">Include</span>=<span class="hljs-string">"Microsoft.AspNetCore.WebUtilities"</span> Version=<span class="hljs-string">"2.0.0"</span> /> <PackageReference <span class="hljs-keyword">Include</span>=<span class="hljs-string">"Microsoft.Extensions.RazorViews.Sources"</span> Version=<span class="hljs-string">"2.0.0"</span> PrivateAssets=<span class="hljs-string">"All"</span> /> <DotNetCliToolReference <span class="hljs-keyword">Include</span>=<span class="hljs-string">"RazorPageGenerator"</span> Version=<span class="hljs-string">"2.0.0"</span> /> |
其中RazorPageGenerator是一个工具包,他主要是用来编译 Razor 视图生成我们需要的Razor视图的对象。 另外 Microsoft.Extensions.RazorViews.Sources 这个包是一个源码包,通过 dotnet resore 还原过后你会发现项目中多个几个class类,他们的图标类似一个快捷方式的样式。 这几个第三方 class 类你可以不用,自己来写,这样会更加的灵活。 BaseView.cs 是编译 Razor 的时候默认集成的基类,你可以通过修改这个类来更加另外操控编译后的Razor对象具有的行为。 2、添加视图文件 接下来,我们就需要添加我们用到的视图文件了。 在 Dashboard 项目下新建一个 Views 文件夹,注意文件夹名称要用 Views。 在 Views 中添加需要的 cshtml 文件,这里要注意和 ASP.NET Core MVC 中的用法有一点不太一样,具体关于Razor的模板引擎支持的语法可以查看这里。 在 Views 文件夹中添加如下两个文件: Home.cshtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<span class="dt"><span class="hljs-meta"><!DOCTYPE </span></span><span class="hljs-meta">html</span><span class="dt"><span class="hljs-meta">></span></span> <span class="kw"><span class="hljs-tag"><<span class="hljs-name">html</span></span></span><span class="ot"><span class="hljs-tag"> <span class="hljs-attr">lang</span>=</span></span><span class="st"><span class="hljs-tag"><span class="hljs-string">"en"</span></span></span><span class="ot"><span class="hljs-tag"> <span class="hljs-attr">xmlns</span>=</span></span><span class="st"><span class="hljs-tag"><span class="hljs-string">"http://www.w3.org/1999/xhtml"</span></span></span><span class="kw"><span class="hljs-tag">></span></span> <span class="kw"><span class="hljs-tag"><<span class="hljs-name">head</span>></span></span> <span class="kw"><span class="hljs-tag"><<span class="hljs-name">meta</span></span></span><span class="ot"><span class="hljs-tag"> <span class="hljs-attr">charset</span>=</span></span><span class="st"><span class="hljs-tag"><span class="hljs-string">"utf-8"</span></span></span> <span class="kw"><span class="hljs-tag">/></span></span> <span class="kw"><span class="hljs-tag"><<span class="hljs-name">title</span>></span></span>Dashboard<span class="kw"><span class="hljs-tag"></<span class="hljs-name">title</span>></span></span> <span class="kw"><span class="hljs-tag"><<span class="hljs-name">style</span>></span></span> <span class="kw"><span class="hljs-tag"></<span class="hljs-name">style</span>></span></span> <span class="kw"><span class="hljs-tag"></<span class="hljs-name">head</span>></span></span> <span class="kw"><span class="hljs-tag"><<span class="hljs-name">body</span>></span></span> <span class="kw"><span class="hljs-tag"><<span class="hljs-name">h1</span>></span></span>This is Dashboard home page.<span class="kw"><span class="hljs-tag"></<span class="hljs-name">h1</span>></span></span> <span class="er"><span class="hljs-tag"><</span></span><span class="hljs-tag"><span class="hljs-name">%$</span> <span class="hljs-attr">include:</span> <span class="hljs-attr">body.html</span> %></span> <span class="kw"><span class="hljs-tag"></<span class="hljs-name">body</span>></span></span> <span class="kw"><span class="hljs-tag"></<span class="hljs-name">html</span>></span></span> |
body.html
1 2 3 4 5 |
<span class="kw"><span class="hljs-tag"><<span class="hljs-name">div</span>></span></span> <span class="kw"><span class="hljs-tag"><<span class="hljs-name">h2</span>></span></span> This is body content. <span class="kw"><span class="hljs-tag"></<span class="hljs-name">h2</span>></span></span> <span class="kw"><span class="hljs-tag"></<span class="hljs-name">div</span>></span></span> |
3、生成视图对象class 使用 dotnet razorpagegenertor Dashboard 命令来变成生成Razor对象文件。 其中最后一个参数Dashboard为生成的文件 Home.Design.cs 的命名空间。 这个时候,其实 body.html 和 Home.cshtml 已经没有用了,因为程序在执行的时候会执行Home.Design.cs里面的代码。 4、调用对象class 接下来只需要在 DashboardMiddleware.cs 里面调用 Home.Design.cs 中的 Home 类就可以了 修改一下DashboardMiddleware.cs的构造函数,把不需要的Options配置类删掉,然后修改Invoke代码: DashboardMiddleware.cs
1 2 3 4 |
<span class="kw"><span class="hljs-function"><span class="hljs-keyword">public</span></span></span><span class="hljs-function"> <span class="hljs-keyword">async</span> Task </span><span class="fu"><span class="hljs-function"><span class="hljs-title">Invoke</span></span></span><span class="hljs-function">(<span class="hljs-params">HttpContext context</span>) </span>{ <span class="hljs-keyword">await</span> <span class="kw"><span class="hljs-keyword">new</span></span> <span class="fu">Home</span>().<span class="fu">ExecuteAsync</span>(context); } |
这里不需要再await _next(context) 来接着执行下一个中间件了,因为我们提供的页面不应该受到MVC管道的控制,在启动中间件的时候放到末尾即可。 现在,我们已经做好了一个中间件。我们需要建一个示例项目测试一下。 […]
View Details前言 最近在看《C# 并发编程 · 经典实例》这本书,这不是一本理论书,反而这是一本主要讲述怎么样更好的使用好目前 C#.NET 为我们提供的这些 API 的一本书,书中绝大部分是一些实例,在日常开发中还是经常会使用到。 书中一些观点还是比较赞同,比如作者说目前绝大多数的图书对关于并发多线程等这些内容放到最后,而缺少一本介绍并发编程的入门指引和参考。另外一个观点是绝大多数国内的技术人员认为技术越底层就牛逼,而做上层应用的就是“码农”,作者反对了这一观点,其实能利用好现有的库也是一种能力,虽然说理解基础知识对日常生活仍然有帮助,但最好从更高级的抽象概念来学习。 异步基础 任务暂停,休眠 异步方式暂停或者休眠任务,可以使用 Task.Delay();
1 2 3 4 |
<span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task<T> DelayResult<T>(T result, TimeSpan delay) { <span class="hljs-keyword">await</span> Task.Delay(delay); <span class="hljs-keyword">return</span> result; } |
异步重试机制 一个简单的指数退避策略,重试的时间会逐次增加,在访问 Web 服务时,一般采用此种策略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task<<span class="hljs-keyword">string</span>> <span class="hljs-title">DownloadString</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> uri</span>) </span>{ <span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> client = <span class="hljs-keyword">new</span> HttpClient()) { <span class="hljs-keyword">var</span> nextDealy = TimeSpan.FromSeconds(<span class="hljs-number">1</span>); <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i != <span class="hljs-number">3</span>; ++i) { <span class="hljs-keyword">try</span> { <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> client.GetStringAsync(uri); } <span class="hljs-keyword">catch</span> { } <span class="hljs-keyword">await</span> Task.Delay(nextDealy); nextDealy = nextDealy + nextDealy; } <span class="hljs-comment">//最后重试一次,抛出出错信息 </span> <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> client.GetStringAsync(uri); } } |
报告进度 异步操作中,经常需要展示操作进度,可以使用 IProcess<T> 和 Process<T>。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">MyMethodAsync</span>(<span class="hljs-params">IProgress<<span class="hljs-keyword">double</span>> progress</span>) </span>{ <span class="hljs-keyword">double</span> precentComplete = <span class="hljs-number">0</span>; <span class="hljs-keyword">bool</span> done = <span class="hljs-keyword">false</span>; <span class="hljs-keyword">while</span> (!done) { <span class="hljs-keyword">await</span> Task.Delay(<span class="hljs-number">100</span>); <span class="hljs-keyword">if</span> (progress != <span class="hljs-keyword">null</span>) { progress.Report(precentComplete); } precentComplete++; <span class="hljs-keyword">if</span> (precentComplete == <span class="hljs-number">100</span>) { done = <span class="hljs-keyword">true</span>; } } } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>) </span>{ Console.WriteLine(<span class="hljs-string">"starting..."</span>); <span class="hljs-keyword">var</span> progress = <span class="hljs-keyword">new</span> Progress<<span class="hljs-keyword">double</span>>(); progress.ProgressChanged += (sender, e) => { Console.WriteLine(e); }; MyMethodAsync(progress).Wait(); Console.WriteLine(<span class="hljs-string">"finished"</span>); } |
等待一组任务 同时执行几个任务,等待他们全部完成
1 2 3 4 5 |
Task task1 = Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">1</span>)); Task task2 = Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">2</span>)); Task task3 = Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">1</span>)); Task.WhenAll(task1, task2, task3).<span class="hljs-keyword">Wait</span>(); |
等待任意一个任务完成 执行若干任务,只需要对其中一个的完成进行响应。主要用于对一个操作进行多种独立的尝试,只要其中一个尝试完成,任务就算完成。
1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task<<span class="hljs-keyword">int</span>> <span class="hljs-title">FirstResponseUrlAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> urlA, <span class="hljs-keyword">string</span> urlB</span>) </span>{ <span class="hljs-keyword">var</span> httpClient = <span class="hljs-keyword">new</span> HttpClient(); Task<<span class="hljs-keyword">byte</span>[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA); Task<<span class="hljs-keyword">byte</span>[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB); Task<<span class="hljs-keyword">byte</span>[]> completedTask = <span class="hljs-keyword">await</span> Task.WhenAny(downloadTaskA, downloadTaskB); <span class="hljs-keyword">byte</span>[] data = <span class="hljs-keyword">await</span> completedTask; <span class="hljs-keyword">return</span> data.Length; } |
集合 不可变栈和队列 需要一个不会经常修改,可以被多个线程安全访问的栈和队列。他们的API和 Stack<T> 和 Queue<T> 非常相似。性能上,不可变栈(LIFO)和队列(FIFO)与标准的栈和队列具有相同的时间复杂度。但是在需要频繁修改的简单情况下,标准栈和队列速度更快。 在内部实现上,当对一个对象进行覆盖(重新赋值)的时候,不可变集合采用的是返回一个修改过的集合,原始集合引用是不变化的,也就是说如果另外一个变量引用了相同的对象,那么它(另外的变量)是不会变化的。 ImmutableStack
1 2 3 4 5 6 7 8 9 10 11 |
<span class="hljs-keyword">var</span> stack = ImmutableStack<<span class="hljs-keyword">int</span>>.Empty; stack = stack.Push(<span class="hljs-number">11</span>); <span class="hljs-keyword">var</span> biggerstack = stack.Push(<span class="hljs-number">12</span>); <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> item <span class="hljs-keyword">in</span> biggerstack) { Console.WriteLine(item); } <span class="hljs-comment">// output: 12 11</span> <span class="hljs-keyword">int</span> lastItem; stack = stack.Pop(<span class="hljs-keyword">out</span> lastItem); Console.WriteLine(lastItem); <span class="hljs-comment">//output: 11</span> |
实际上,两个栈内部共享了存储 11 的内存,这种实现方式效率很高,而且每个实例都是线程安全的。 ImmutableQueue
1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="hljs-keyword">var</span> queue = ImmutableQueue<<span class="hljs-keyword">int</span>>.Empty; queue = queue.Enqueue(<span class="hljs-number">11</span>); queue = queue.Enqueue(<span class="hljs-number">12</span>); <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> item <span class="hljs-keyword">in</span> queue) { Console.WriteLine(item); } <span class="hljs-comment">// output: 11 12</span> <span class="hljs-keyword">int</span> nextItem; queue = queue.Dequeue(<span class="hljs-keyword">out</span> nextItem); Console.WriteLine(nextItem); <span class="hljs-comment">//output: 11</span> |
不可变列表和集合 ImmutableList 时间复杂度 操作 List ImmutableList Add O(1) O(log N) Insert O(log N) O(log N) RemoveAt O(log N) O(log N) Item[index] O(1) O(log N) 有些时候需要这样一个数据结构:支持索引,不经常修改,可以被多线程安全的访问。
1 2 3 4 5 6 7 8 9 |
<span class="hljs-keyword">var</span> list = ImmutableList<<span class="hljs-keyword">int</span>>.Empty; list = list.Insert(<span class="hljs-number">0</span>, <span class="hljs-number">11</span>); list = list.Insert(<span class="hljs-number">0</span>, <span class="hljs-number">12</span>); <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> item <span class="hljs-keyword">in</span> list) { Console.WriteLine(item); } <span class="hljs-comment">// 12 11</span> |
ImmutableList<T> 可以索引,但是注意性能问题,不能用它来简单的替代 List<T>。它的内部实现是用的二叉树组织的数据,这么做是为了让不同的实例之间共享内存。 ImmutableHashSet 有些时候需要这样一个数据结构:不需要存放重复内容,不经常修改,可以被多个线程安全访问。时间复杂度 O(log N)。
1 2 3 4 5 6 7 8 |
<span class="hljs-keyword">var</span> <span class="hljs-keyword">set</span> = ImmutableHashSet<<span class="hljs-keyword">int</span>>.Empty; <span class="hljs-keyword">set</span> = <span class="hljs-keyword">set</span>.Add(<span class="hljs-number">11</span>); <span class="hljs-keyword">set</span> = <span class="hljs-keyword">set</span>.Add(<span class="hljs-number">12</span>); <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> item <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span>) { Console.WriteLine(item); } <span class="hljs-comment">// 11 12 顺序不定</span> |
线程安全字典 一个线程安全的键值对集合,多个线程读写仍然能保持同步。 ConcurrentDictionary 混合使用了细粒度的锁定和无锁技术,它是最实用的集合类型之一。
1 2 |
<span class="hljs-keyword">var</span> dictionary = <span class="hljs-keyword">new</span> ConcurrentDictionary<<span class="hljs-keyword">int</span>, <span class="hljs-keyword">string</span>>(); dictionary.AddOrUpdate(<span class="hljs-number">0</span>, key => <span class="hljs-string">"Zero"</span>, (key, oldValue) => <span class="hljs-string">"Zero"</span>); |
如果多个线程读写一个共享集合,实用 ConcurrentDictionary<TKey,TValue> 是最合适的。如果不会频繁修改,那么更适合使用 ImmutableDictionary<TKey,TValue> 。 它最适合用于在需要共享数据的场合,即多个线程共享一个集合,如果一些线程只添加元素一些线程只移除元素,那最好使用 生产者/消费者集合(BlockingCollection<T>)。 初始化共享资源 程序多个地方使用一个值,第一次访问时对它进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> _simpleVluae; <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> Lazy<Task<<span class="hljs-keyword">int</span>>> shardAsyncInteger = <span class="hljs-keyword">new</span> Lazy<Task<<span class="hljs-keyword">int</span>>>(<span class="hljs-keyword">async</span> () => { <span class="hljs-keyword">await</span> Task.Delay(<span class="hljs-number">2000</span>).ConfigureAwait(<span class="hljs-keyword">false</span>); <span class="hljs-keyword">return</span> _simpleVluae++; }); <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>) </span>{ <span class="hljs-keyword">int</span> shareValue = shardAsyncInteger.Value.Result; Console.WriteLine(shareValue); <span class="hljs-comment">// 0</span> shareValue = shardAsyncInteger.Value.Result; Console.WriteLine(shareValue); <span class="hljs-comment">// 0</span> shareValue = shardAsyncInteger.Value.Result; Console.WriteLine(shareValue); <span class="hljs-comment">// 0</span> } |
本文地址:http://www.cnblogs.com/savorboard/p/csharp-concurrency-cookbook.html 作者博客:Savorboard 欢迎转载,请在明显位置给出出处及链接
View Details前言 最新项目中要用到消息队列来做消息的传输,之所以选着 Kafka 是因为要配合其他 java 项目中,所以就对 Kafka 了解了一下,也算是做个笔记吧。 本篇不谈论 Kafka 和其他的一些消息队列的区别,包括性能及其使用方式。 简介 Kafka 是一个实现了分布式的、具有分区、以及复制的日志的一个服务。它通过一套独特的设计提供了消息系统中间件的功能。它是一种发布订阅功能的消息系统。 一些名词 如果要使用 Kafka ,那么在 Kafka 中有一些名词需要知道,文本不讨论这些名词是否在其他消息队列中具有相同的含义。所有名词均是针对于 Kafka。 Message 消息,就是要发送的内容,一般包装成一个消息对象。 Topic 通俗来讲的话,就是放置“消息”的地方,也就是说消息投递的一个容器。假如把消息看作是信封的话,那么 Topic 就是一个邮筒,如下图所示: Partition && Log Partition 分区,可以理解为一个逻辑上的分区,像是我们电脑的磁盘 C:, D:, E: 盘一样, Kafka 为每个分区维护着一份日志Log文件。 每个分区是一个有序的,不可修改的,消息组成的队列。 当消息过来的时候,会被追加到日志文件中,这个追加是根据 commit 命令来执行的。 分区中的每一条消息都有一个编号,叫做 offset id,这个 id 在当前分区中是唯一的,并且是递增的。 日志,就是用来记录分区中接收到的消息,因为每一个 Topic 可以同时向一个或者多个分区投递消息,所以实际在存储日志的时候,每个分区会对应一个日志目录,其命名规则一般为 <topic_name>-<partition_id>, 目录中就是一个分区的一份 commit log 日志文件。 Kafka 集群会保存一个时间段内所有被发布出来的信息,无论这个消息是否已经被消费过,这个时间段是可以配置的。比如日志保存时间段被设置为2天,那么2天以内发布的消息都是可以消费的;而之前的消息为了释放空间将会抛弃掉。Kafka的性能与数据量不相干,所以保存大量的消息数据不会造成性能问题。 对日志进行分区主要是为了以下几个目的:第一、这可以让log的伸缩能力超过单台服务器上线,每个独立的partition的大小受限于单台服务器的容积,但是一个topic可以有很多partition从而使得它有能力处理任意大小的数据。第二、在并行处理方面这可以作为一个独立的单元。 生产者 Producers 和其他消息队列一样,生产者通常都是消息的产生方。 在 Kafka 中它决定消息发送到指定Topic的哪个分区上。 消费者 Consumers 消费者就是消息的使用者,在消费者端也有几个名词需要区分一下。 一般消息队列有两种模式的消费方式,分别是 队列模式 和 订阅模式。 队列模式:一对一,就是一个消息只能被一个消费者消费,不能重复消费。一般情况队列支持存在多个消费者,但是对于一个消息,只会有一个消费者可以消费它。 订阅模式:一对多,一个消息可能被多次消费,消息生产者将消息发布到Topic中,只要是订阅改Topic的消费者都可以消费。 Consumer && Subscriber Group: 组,是一个消费者的集合,每一组都有一个或者多个消费者,Kafka 中在一个组内,消息只能被消费一次。 在发布订阅模式中,消费者是以组的方式进行订阅的,就是Consumer Group,他们的关系如下图: 每个发布到Topic上的消息都会被投递到每个订阅了此Topic的消费者组中的某一个消费者,也就是每个组都会被投递,但是每个组都只会有一个消费者消费这个消息。 开头介绍了Kafka 是 发布-订阅 功能的消息队列,所以在Kafka中,队列模式是通过单个消费者组实现的,也就是整个结构中只有一个消费者组,消费者之间负载均衡。 Kafka 集群 Borker: Kafka 集群有多个服务器组成,每个服务器称做一个 Broker。同一个Topic的消息按照一定的key和算法被分区存储在不同的Broker上。 上图引用自:http://blog.csdn.net/lizhitao 因为 […]
View Details前言 今天给大家介绍一下在 ASP.NET Core 日常开发中用的比较多的两个中间件,它们都是出自于微软的 ASP.NET 团队,他们分别是 Microsoft.AspNetCore.ResponseCompression 和 Microsoft.AspNetCore.ResponseCaching , 下面让我们一起看看的功能以及如何去使用吧。 Getting Started Microsoft.AspNetCore.ResponseCompression Microsoft.AspNetCore.ResponseCompression 这个中间件是 .NET Core 1.1 版本中新增加的,看名字应该知道,它主要是负责对输出的内容进行压缩, 那么在我们WEB开发中主要就是 GZip 压缩了。 Gzip 压缩是我们在 WEB 中经常会使用的一项性能优化技术,它可以对页面输出的内容使用压缩算法(GZip)进行体积的压缩, 那在以前的时候,我们可以使用 IIS 来做这项工作,但是现在我们的程序脱离 IIS了,就必须有一个中间件来帮我们做这件事情了,它就是我们要介绍的这个中间件。 1、添加 Microsoft.AspNetCore.ResponseCompression 包 你可以使用 Visual Studio 打开 NuGet 包管理器控制台输入一下命令安装
1 |
Install-Package Microsoft.AspNetCore.ResponseCompression |
也可以使用 NuGet包管理器UI界面安装。 添加完成之后,你就可以在 project.json 中看到你添加的包了。注意目前版本是 1.0.0. 2、更新 Startup.cs 文件 修改 Startup , 在ConfigureServices 和Configure 两个方法中添加如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Startup { ... public void ConfigureServices(IServiceCollection services) { services.AddResponseCompression(); ... } public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { app.UseResponseCompression(); ... } } |
现在你就可以测试一下输入的 Http Response 是否被压缩了。 前: 后: 通过 前 后 对比,可以看出来,在 Response Headers 里面多了一个 Content-Encoding:gzip 的头部信息,说明我们的中间件生效了。 Microsoft.AspNetCore.ResponseCaching Microsoft.AspNetCore.ResponseCaching 这个中间件也是 .NET Core 1.1 版本中新增加的,同样看名字应该知道,它主要是负责对输出的内容进行缓存设置。在以前我们可以同样在 IIS 中设置这些东西,但是粒度可能并没有这么细。 我之前写过一篇关于 ASP.NET Core 缓存的文章,里面介绍了 ASP.NET Core MVC 中的 Response 缓存,它是通过一个 ResponseCacheAttribute 来实现的设置缓存头信息:
1 2 3 4 |
[ResponseCache(VaryByHeader ="Accept-Encoding", Location = ResponseCacheLocation.Any, Duration = 10)] public IActionResult About() { } |
那,除了 MVC […]
View Details