分布式事务 ( DTS ) 你必须知道的事儿

目前云计算、大数据、互联网领域的大部分系统都采用了SOA、微服务化的架构。一个涉及端到端全链路的业务操作往往会由多个服务和数据库实例共同完成。因此,在一致性要求较高的业务场景中,如何保证多个服务之间RPC调用后的数据一致将成为关键点。 一、分布式系统/SOA/微服务架构的特点: 在大型分布式系统中要同时能够满足,分布式一致性(Consistency)、可用性(Availability)和分区容忍性(Partitiontolerance),是不存在的。在大多数情况下只能满足其中的2项,而实现系统的最终一致性(Base理论)。 ( 1 ) CAP特点: a.一致性(Consistency):( 同样数据在分布式系统的各个节点上都是一致的) b.可用性(Availability):( 所有在分布式系统活跃的节点都能够处理操作且能响应查询) c.分区容忍性(Partition Tolerance) :(如果出现了网络故障、一部分节点无法通信,但是系统仍能够工作) ( 2 ) ACID特点: a.原子性(Atomicity) 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。 b.一致性(Consistency) 事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。 c.隔离性(Isolation) 指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。 d.持久性(Durability) 指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。 二、分布式事务的基本介绍 分布式事务服务(Distributed Transaction Service,DTS)是一种分布式事务框架,用来确保在大规模分布式/微服务环境下端到端业务操作的最终一致性。 由CAP定理可知,任何大型的分布式系统/微服务在一致性、可用性和分区容忍这三点上只能保证其中的两点。由于在分布式系统中经常发生丢包、网络故障,分区容忍性是必须要满足的,同时为了兼顾高可用性,绝大部分系统都将强一致性需求转化成最终一致性的需求,并通过幂等机制保证了数据的最终一致性。 三、常用的分布式技术介绍 ( 1 ) 本地消息表(经典的ebay模式) 该方案的核心思想在于分布式系统在处理任务时通过消息日志的方式来异步执行。消息日志可以存储至本地文本、数据库或消息队列,然后再通过业务规则定时任务或人工自动重试。以在线支付系统的跨行转账为例: 第一步,伪代码如下,对用户id为A的账户扣款1000元,通过本地事务将事务消息(包括本地事务id、支付账户、收款账户、金额、状态等)插入至消息表:

  第二步,通知对方用户id为B,增加1000元,通常通过消息MQ的方式发送异步消息,对方订阅并监听消息后自动触发转账的操作;这里为了保证幂等性,防止触发重复的转账操作,需要在执行转账操作方新增一个trans_recv_log表用来做幂等,在第二阶段收到消息后,通过判断trans_recv_log表来检测相关记录是否被执行,如果未被执行则会对B账户余额执行加1000元的操作,并会将该记录增加至trans_recv_log,事件结束后通过回调更新trans_message的状态值。 ( 2 ) 消息中间件 a.非事务消息中间件 这里仍然以上面跨行转账为例,我们很难保证在扣款完成之后对MQ投递消息的操作就一定能成功。这样一致性似乎很难保证。以下伪代码说明了消息投递的异常:

对于以上的运行情况主要有以下几种: 操作数据库成功,向MQ中投递消息也成功,该属于正常情况,一切都OK。 操作数据库失败,不会向MQ中投递消息了。 操作数据库成功,但是向MQ中投递消息时失败,向外抛出了异常,刚刚执行的更新数据库的操作将被回滚。 从上面分析的几种情况来看,基本上能确保,发送消息的可靠性。我们再来分析下消费者端的问题: 1.接收者取出消息后,消费者对应的业务操作要执行成功。如果业务执行失败,消息不能失效或者丢失。需要保证消息与业务操作一致。 2.尽量确保消息的幂等性。如果出现重复消息投递,能够进行幂等而不对业务产生影响。 b.支持事务的消息中间件 Apache开源的RocketMQ中间件能够支持一种事务消息机制,确保本地操作和发送消息的异步处理达到本地事务的结果一致。 第一阶段,RocketMQ在执行本地事务之前,会先发送一个Prepared消息,并且会持有这个消息的接口回查地址。 第二阶段,执行本地事物操作。 第三阶段,确认消息发送,通过第一阶段拿到的接口地址URL执行回查,并修改状态,如果本地事务成功,则修改状态为已提交,否则修改状态为已回滚。 其中,如果第三阶段的确认消息发送失败后,RocketMQ会有定时任务扫描集群中的事务消息,如果发现还是处于prepare状态的消息,它会向消息发送者确认本地事务是否已执行成功。RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息的发送与本地事务同时成功或同时失败。 再回到上面转账的例子,如果用户A的账户余额已经减少,且消息已经发送成功,作为消费者用户B开始消费这条消息,这个时候就会出现消费失败和消费超时两个问题,解决超时问题的思路就是一直重试,直到消费端消费消息成功,整个过程中有可能会出现消息重复的问题,就需要采用前面说的幂等方案来进行处理。 分布式事务—2PC协议 为了解决大型分布式/微服务系统中的一致性问题,业界比较流行的做法是采用比较著名的有二阶提交协议(2 Phase Commitment Protocol)和三阶提交协议(3 PhaseCommitment Protocol)。考虑到性能问题,三阶段提交协议目前较少被采用。本文也主要介绍二阶段协议。 2PC协议 二阶段提交协议是分布式系统中较为经典的处理数据一致性的解决方案。在大型的集群环境中,对于单体微服务本身而言虽然能够通过代码质量、Mock测试等方法来确保自身服务的可用性,但是无法能够保证其他服务的可用性。当一个全链路的端到端业务操作,常常会跨多个节点、多个应用,为了能够保证全局事务的ACID特性,需要引入一个协调组件(这里称之为TM)来控制所有服务参与者(这里称之为RM)的操作结果,根据所有参与者的反馈结果来决定整个分布式事务究竟是提交还是回滚的结果。 第一阶段:称为准备(prepare)阶段。事务协调者向各个服务应用发送prepare请求,服务应用在得到请求后做预处理操作,预处理可能是做预检查,也可能是把请求临时存储,可以理解为是一种试探性地提交。下面是一般的步骤: a.事务协调者会问所有的参与者服务,是否可以提交操作。 b.各个参与者开始事务执行的准备工作:如资源上锁,预留资源,写回滚/重试的log。 c.参与者响应协调者,如果事务准备工作成功,则回应“可以提交”,否则回应拒绝提交。 第二阶段:称为提交(commit)/回滚(rollback)阶段。是指事务真正提交或者回滚的阶段。如果事务协调者发现事务参与者有一个在prepare阶段出现失败,则会要求所有的参与者进行回滚。如果协调者发现所有的参与者都prepare操作都是成功,那么他将向所有的参与者发出提交请求,这时所有参与者才会正式提交。由此保证了要求全部提交成功,要么全部失败。下面是具体步骤: a.如果所有的参与者都回应“可以提交”,那么协调者向所有参与者发送“正式提交”的命令。参与者完成正式提交,并释放所有资源,然后回应“完成”,协调者收集各个服务的“完成”回应后结束事务。 b.如果有一个参与者回应“拒绝提交”,那么协调者向所有的参与者发送“回滚操作”,并释放所有的资源,然后回应“回滚完成”,协调者收集各个服务应用的“回滚”返回后,取消整体的分布式事务。 下图为二阶段的成功和失败示例图: 二阶段提交协议解决的是分布式系统/微服务架构中数据强一致性的问题,其原理简单,但缺点也是存在,主要缺点如下: a.单点问题:协调者在整个二阶段中的作用非常重要,一旦部署协调者组件服务的节点出现不可用宕机情况,那么会影响整个分布式系统的正常运行。 b.同步阻塞:二阶段提交执行过程中,所有服务参与者需要服从协调者的统一调度,期间处于阻塞状态,会一定程度上影响整个系统的效率。   from:https://blog.csdn.net/WuLex/article/details/79311776

龙生   07 Jun 2018
View Details

NuGet学习笔记(3) 搭建属于自己的NuGet服务器

在上一篇NuGet学习笔记(2) 使用图形化界面打包自己的类库 中讲解了如何打包自己的类库,接下来进行最重要的一步,从零开始搭建属于自己的NuGet服务器,诚然园子里及其它很多地方已经有完全写好的NuGet服务源码,我们只需要拿来发布一下就ok了,运行也很正常,但作为一名合格的程序猿,不建议这种拿来主义尤其时你自己可以搞定的时候,用别人写好的东西总有些心里不踏实,当有一天它出问题了你的悲剧就开始了,下面进入我们今天的主题 创建NuGetServer Web站点 1.新建Web站点,选择 文件-->新建项目-->Asp.Net 空 Web应用程序 2.选择 工具-->Library Package Manager-->Package Manager Console 打开NuGet命令行控制台 3.在控制台输入Install-Package NuGet.Server (请保持Package source:官方源,即 Nuget offical package source,一般默认即为官方Package源) 4.回车确认后开始下载并安装NuGet.Server类库及相关引用,并自动添加引用到项目 注:具体输出根据版本不同会略有不同 自动添加的引用 5.在控制台输入 Get-Package 可以看到刚刚安装的类库 6.除添加类库引用外,还自动添加了一些文件(关于这些文件及其作用本文不做探讨,有兴趣的可以自行研究下,都很简单) /App_Readme/ /App_Readme/Elmah.txt /DataServices/ /DataServices/Packages.svc /DataServices/Routes.cs /Packages/ /Packages/Readme.txt /Default.aspx /favicon.ico /packages.config 同时Web.config也被修改,添加了一些配置项 7.至此NuGet.Server站点已经创建完毕,恩,你没有看错,就是这么简单,接下来部署到本地或IIS站点即可使用了 发布站点到IIS 1.发布网站到本地 2.IIS里面建立站点www.mynuget.com,主机头www.mynuget.com,应用程序池为.Net4.0集成模式,目录指向站点发布的目录 3.设置hosts 将www.mynuget.com执向本机 4.设置好hosts后,重新打开浏览器输入网址:http://www.mynuget.com/ 5.查看已经发布的类库包 http://www.mynuget.com/nuget/Packages (可以把已经打好包的类库放到网站下的Packages文件夹下即可) 添加本地站点到包包数据源 1.选择 工具-->Library Package Manager-->Package Manager Settings 2.输入名称 MyNuGetServer及包包源地址 http://www.mynuget.com/nuget 点击Add完成源添加   7.查看刚刚添加的类库源 工具-->Library Package Manager-->Manager NuGet Packages for Solution… 点击刚添加的源MyNuGetServer可以看到我们放到站点下的类库包,选中需要的包包点击 Install完成安装 8.也可以在控制台下安装,将类库源执行我们建立的源 MyNuGetServer,控制台下输入Install-Package Winista.HtmlParser完成安装   现在我们已经成功的搭建了自己的NuGet服务器。 注意点: 1.NuGet包包是放置在站点根目录下面的Packages文件夹下,如需要改变可以在配置文件packagesPath中进行设置,可以设置为相对目录或者物理目录

2.如何管理我们的包包,可以根据需要自行写个上传下载的文件管理功能,需要时上传到Packages文件下,不需要了删除即可,也可以像我一样写了个FTP服务,使用ftp上传下载 3.最好是为NuGet单独建立个站点这样管理着方便,若不方便建立独立站点可以随便找个现有的web站点,在里面安装NuGet.Server然后发布即可 回头看整个过程:安装NuGet扩展,下载网上的类库包供自己使用,打包自己的类库,搭建自己的NuGet服务器,整个流程基本走完了,剩下的就是我们如何去利用好NuGet这个强大的工具了。 源文件下载:NuGet.rar   from:https://www.cnblogs.com/lzrabbit/archive/2012/05/01/2477873.html

龙生   06 Jun 2018
View Details

NuGet学习笔记(2) 使用图形化界面打包自己的类库

上文NuGet学习笔记(1) 初识NuGet及快速安装使用说到NuGet相对于我们最重要的功能是能够搭建自己的NuGet服务器,实现公司内部类库的轻松共享更新。在安装好NuGet扩展后,我们已经能够通过NuGet轻松下载自己需要的类库,下面来说一说如何将自己的项目类库进行打包发布 使用图形界面打包自己的类库 NuGet可以使用NuGet.exe在命令行下进行类库打包,也可以使用图形化界面进行打包,估计很多朋友都和我一样对不熟悉的命令行有些感冒,所以在此仅讲述下使用图形化界面进行类库打包 1.首先下载 包包管理器 NuGetPackageExplorer,顾名思义,包包管理器可以用来创建新的类库包,也可以浏览已经创建好的类库包 2.下载完成后,双击 NuGetPackageExplorer.application 进行安装,安装完成后会在桌面上生成一个NuGet Package Explorer 的快捷方式   3.双击NuGet Package Explorer出现启动界面,选择Create a new package(Ctrl+N)创建一个新包 4.选择菜单 Edit > Edit Package Metadata (或者 CTRL + K) 编辑包包信息 这里可以编辑类库包的信息和添加类库、程序集的引用解释下几个比较重要的字段 Id:用于定位类库包的唯一标示,如在命令行下执行 Install-Package MyPackage 这里的MyPackage就是包包的Id值 Version:类库的版本号,这里建议和程序集的真实版本号保持一致,以免发生混淆 Dependencies:此类库依赖的程序集,根据实际情况添加依赖类库的Id和版本号,若无留空即可,这里需要注意下填写的依赖类库需要在NuGet服务器上能找到,否则无法完成自动引用添加 Framework assemblies:依赖的Framework 程序集,根据实际需要选择即可,一般无需设置 除以上四项以外其他属性根据实际情况修改即可,若自己用不修改也中,不过为了使用方便还是建议把类库描述(Description)填写下 5.编辑好包包信息后接下来添加DLL到包包中 可以选择 右键-->Add Existinng File… 选择dll添加,也可以直接拖拽DLL到 Package contents 窗口,此例选择拖拽方式 直接拖拽dll到窗口后,会出现提示:是否将 Winista.HtmlParser.dll放到lib 文件夹下,一般我们选择是   6.DLL添加完成后,快捷键Ctrl + K 修改包包信息Id为Winista.HtmlParser,版本号为1.8.0 7.信息修改和dll都添加完毕后,选择File --> Save(或Ctrl + S)将文件进行保存,默认文件名为Id + 版本号.nupkg 8.完成保存后如需要发布到网上,选择File--> Publish…(或Ctrl + P) 默认的发布地址是官网,发布 Key需要到官网去注册一个账号,然后就可以得到,这里不在复述,若无需发布直接关闭即可   ps:这里没有讲述如何打包项目文件,一般情况下我们自己将项目文件编译成DLL然后使用图形化界面打包即可满足需求,若实际工作需要高度集成完全自动化操作可自行去研究下官方文档http://docs.nuget.org/ 添加DLL默认会添加Lib文件夹,额外的菜单上还有很多.net文件夹,src等文件夹,这些文件夹只是对文件上的一个物理上的划分而已,只是为了帮助我们做好文件分类,没有其他的用途,dll文件可以随便放置,不过为了规范还是建议按照通常习惯来放置。   from:http://www.cnblogs.com/lzrabbit/archive/2012/05/01/2477607.html

龙生   06 Jun 2018
View Details

Autofac 组件、服务、自动装配 《第二篇》

一、组件 创建出来的对象需要从组件中来获取,组件的创建有如下4种(延续第一篇的Demo,仅仅变动所贴出的代码)方式: 1、类型创建RegisterType AutoFac能够通过反射检查一个类型,选择一个合适的构造函数,创造这个对象的实例。主要通过RegisterType<T>() 和 RegisterType(Type) 两个方法以这种方式建立。 ContainerBuilder使用 As() 方法将Component封装成了服务使用。

2、实例创建

单例 提供示例的方式,还有一个功能,就是不影响系统中原有的单例:

这种方法会确保系统中的单例实例最终转化为由容器托管的单例实例。 3、Lambda表达式创建 Lambda的方式也是Autofac通过反射的方式实现

4、程序集创建 程序集的创建主要通过RegisterAssemblyTypes()方法实现,Autofac会自动在程序集中查找匹配的类型用于创建实例。

5、泛型注册 泛型注册通过RegisterGeneric() 这个方法实现,在容易中可以创建出泛型的具体对象。

6、默认的注册 如果一个类型被多次注册,以最后注册的为准。通过使用PreserveExistingDefaults() 修饰符,可以指定某个注册为非默认值。

如果不使用PreserveExistingDefaults(),那么将输出“我是一个学生”。 二、服务 Autofac有三种典型的方式区分服务,同一个服务的不同实现可以由类型,名称和键区分。 1、类型 类型是描述服务的基本方法

并且上面的服务在自动装备中也有效

2、名字 服务可以进一步按名字识别。使用这种方式时,用 Named()注册方法代替As()以指定名字:

使用Name可以检索服务创建实例:

ResolveNamed()只是Resolve()的简单重载,指定名字的服务其实是指定键的服务的简单版本。 3、键 有Name的方式很方便,但是值支持字符串,但有时候我们可能需要通过其他类型作键。 例如,使用枚举作为key:

使用key注册服务,通过Keyed<T>()方法:

显式检索 使用key检索服务以创建实例,通过ResolveKeyd()方法:

ResolveKeyd()会导致容器被当做 Service Locator使用,这是不被推荐的。应该使用IIndex type替代。 IIndex索引 Autofac.Features.Indexed.IIndex<K,V>是Autofac自动实现的一个关联类型。component可以使用IIndex<K,V>作为参数的构造函数从基于键的服务中选择需要的实现。

IIndex中第一个泛型参数要跟注册时一致,在例子中是DeviceState枚举。其他两种注册方法没有这样的索引查找功能,这也是为什么设计者推荐Keyed注册的原因之一。 三、自动装配 从容器中的可用服务中选择一个构造函数来创造对象,这个过程叫做自动装配。这个过程是通过反射实现的,所以实际上容器创造对象的行为比较适合用在配置环境中。 1、选择构造函数 Autofac默认从容器中选择参数最多的构造函数。如果想要选择一个不同的构造函数,就需要在注册的时候就指定它。

这种写法将指定调用Worker(int)构造函数,如该构造函数不存在则报错。 2、额外的构造函数参数 有两种方式可以添加额外的构造函数参数,在注册的时候和在检索的时候。在使用自动装配实例的时候这两种都会用到。 注册时添加参数 使用WithParameters()方法在每一次创建对象的时候将组件和参数关联起来。

  在检索阶段添加参数 在Resolve()的时候提供的参数会覆盖所有名字相同的参数,在注册阶段提供的参数会覆盖容器中所有可能的服务。 3、自动装配 至今为止,自动装配最大的作用就是减少重复配置。许多相似的component无论在哪里注册,都可以通过扫描使用自动装配。

在需要的时候,依然可以创建指定的构造函数创建指定的类。

四、程序集扫描 1、扫描 Autofac可以使用约定在程序集中注册或者寻找组件。 Autofac可以根据用户指定的规则在程序集中注册一系列的类型,这种方法叫做convention-driven registration或者扫描。

[…]

龙生   04 Jun 2018
View Details

C# 遍历类的属性并取出值

最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来。 十年河东十年河西,莫欺少年穷 学无止境,精益求精    今天有点胡思乱想,想遍历MVC Model的属性并取值: 这个方法还是很简单的,通过反射即可遍历属性,我总结的方法如下:

下面我们来简单测试下: 新建Model如下:

调用如下:

测试结果如下: 经过测试,我们可以得到对象的各个属性及对应的值、 其实这块内容输入C# 反射系列,小弟也是误打误撞,撞入了C# 反射,索性每天学一点 from:https://www.cnblogs.com/chenwolong/p/fanshe.html

龙生   04 Jun 2018
View Details

C#遍历类的属性,然后给其赋值

from:https://www.cnblogs.com/yanglang/p/6830982.html

龙生   04 Jun 2018
View Details

sqlserver的四种分页方式

第一种:ROW_NUMBER() OVER()方式 select * from ( select *, ROW_NUMBER() OVER(Order by ArtistId ) AS RowId from ArtistModels ) as b where RowId between 10 and 20 —where RowId BETWEEN 当前页数-1*条数 and 页数*条数— 执行结果是: 第二种方式:offset fetch next方式(SQL2012以上的版本才支持:推荐使用 ) select * from ArtistModels  order by ArtistId offset 4 rows fetch next 5 rows only --order by ArtistId offset 页数*条数 rows fetch next 条数 rows only —- 执行结果是: 第三种方式:--top not in方式 (适应于数据库2012以下的版本) select top 3 * from ArtistModels where ArtistId not in (select top 15 ArtistId from ArtistModels) ——where Id not in (select top 条数*页数  ArtistId  from ArtistModels) […]

龙生   04 Jun 2018
View Details

VS2017多项目模板

解决方案:

其中一个解决方案:

 

龙生   02 Jun 2018
View Details

模板参数

若要了解有关 Visual Studio 2017 RC 的最新文档,请参阅 Visual Studio 2017 RC 文档。 在对模板进行实例化时,您可以通过模板的参数,替换模板的关键部分的值,如类名和命名空间。 当用户在“新建项目”或“添加新项”对话框中单击“确定”时,这些参数将由后台运行的模板向导替换。 声明和启用模板参数 模板参数以 $parameter$ 格式进行声明。 例如: $safeprojectname$ $guid1$ $guid5$ 启用模板中的参数替换 在模板的 .vstemplate 文件中,定位到与要为其启用参数替换的项对应的 ProjectItem 元素。 将 ProjectItem 元素的 ReplaceParameters 特性设置为 true。 在项目项的代码文件中,在合适的位置包括参数。 例如,下面的参数指定用于文件中的命名空间的安全项目名称:

保留的模板参数 下表列出了可供所有模板使用的保留的模板参数。  说明 模板参数区分大小写。 Parameter 描述 clrversion 公共语言运行时 (CLR) 的当前版本。 GUID [1-10] 用于替换项目文件中的项目 GUID 的 GUID。 最多可以指定 10 个唯一的 GUID(例如,guid1))。 itemname 用户在添加新项对话框中提供的名称。 machinename 当前的计算机名称(例如,Computer01)。 projectname 用户在新建项目对话框中提供的名称。 registeredorganization HKLM\Software\Microsoft\Windows NT\CurrentVersion\RegisteredOrganization 中的注册表项值。 rootnamespace 当前项目的根命名空间。 此参数仅适用于项目模板。 safeitemname 用户在“添加新项”对话框中提供的名称,名称中移除了所有不安全的字符和空格。 safeprojectname 用户在“新建项目”对话框中提供的名称,名称中移除了所有不安全的字符和空格。 time 以 DD/MM/YYYY 00:00:00 格式表示的当前时间。 SpecificSolutionName 解决方案的名称。 当“创建解决方案的目录”被选中,SpecificSolutionName 具有解决方案的名称。 当“创建解决方案的目录”没有被选中,SpecificSolutionName是空。 userdomain 当前的用户域。 username 当前的用户名。 webnamespace 当前网站的名称。 在 Web 窗体模板中使用此参数以确保类名称是唯一的。 如果网站位于 Web 服务器的根目录下,则此模板参数将解析为 Web 服务器的根目录。 […]

龙生   02 Jun 2018
View Details
1 186 187 188 432