WCF技术剖析之三十二:一步步创建一个完整的分布式事务应用-程序员宅基地

技术标签: 运维  操作系统  数据库  

在完成了对于WCF事务编程(《上篇》、《中篇》、《下篇》)的介绍后,本篇文章将提供一个完整的分布式事务的WCF服务应用,通过本例,读者不仅仅会了解到如何编程实现事务型服务,还会获得其他相关的知识,比如DTC和AS-AT的配置等。本例还是沿用贯通本章的应用场景:银行转帐。我们将会创建一个BankingService服务,并将其中的转帐操作定义成事务型操作。我们先从物理部署的角度来了解一下BankingService服务,以及需要实现怎样的分布式事务。

一、从部署的角度看分布式事务

既然是实现分布式事务,那么事务会跨越多台机器。简单起见,我使用两台机器来模拟。有条件的读者可以在自己的局域网中进行练习,如果你没有局域网可用,你可以使用虚拟机来模拟局域网。假设两台机器名分别是Foo和Bar,整个应用的物理拓扑结构如图1所示。

image

图1 BankingService物理部署拓扑

BankingService和客户端部署与主机Foo,定义在BankingService的转账的两个子操作“提取(Withdraw)”和“存储(Deposit)”通过调用部署于主机Bar的同名服务(WithdrawService和DepositService)实现。而WithdrawService和DepositService最终实现对存储于数据库(这里是SQL Server)的数据进行修改,MSSQL部署与主机Bar中。实际上,整个应用主要涉及到对三个服务(BankingService、WithdrawService和DepositService)的实现,我们先来看看服务契约和服务的实现。

步骤1:服务契约和服务的实现

我们仍然采用契约共享的方式将服务契约定义在单独的项目之中,共服务端和客户端共享。涉及到的三个服务对应的服务契约定义如下,事务型操作的TransactionFlow选项被设置为Allwed(默认值)。

IBankingService:

   1: using System.ServiceModel;
   2: namespace Artech.TransactionalService.Service.Interface
   3: {
      
   4:     [ServiceContract(Namespace = "http://www.artech.com/banking/")]
   5:     public interface IBankingService
   6:     {       
   7:         [OperationContract]
   8:         [TransactionFlow(TransactionFlowOption.Allowed)]
   9:         void Transfer(string fromAccountId, string toAccountId, double amount);
  10:     }
  11: }

IWithdrawService:

   1: using System.ServiceModel;
   2: namespace Artech.TransactionalService.Service.Interface
   3: {
      
   4:     [ServiceContract(Namespace = "http://www.artech.com/banking/")]
   5:     public interface IWithdrawService
   6:     {
      
   7:         [OperationContract]
   8:         [TransactionFlow(TransactionFlowOption.Allowed)]
   9:         void Withdraw(string accountId, double amount);
  10:     }
  11: }

IDepositService:

   1: using System.ServiceModel;
   2: namespace Artech.TransactionalService.Service.Interface
   3: {
      
   4:     [ServiceContract(Namespace="http://www.artech.com/banking/")]
   5:     public interface IDepositService
   6:     {
      
   7:         [OperationContract]
   8:         [TransactionFlow(TransactionFlowOption.Allowed)]
   9:         void Deposit(string accountId, double amount);
  10:     }
  11: }

实现了服务操作的IWithdrawService和IDepositService的WithdrawService和DepositService分别实现基于给定银行账户的提取和存储操作。限于篇幅的问题,具体对数据库相应数据的更新操作就不再这里一一介绍了。下面是WithdrawService和DepositService的定义,由于不管是单独被调用,还是作为转帐的一个子操作,Withdraw和Deposit操作均需要在一个事务中执行,所以我们需要通过应用OperationBehaviorAttribute将TransactionScopeRequired属性设为True。

WithdrawService:

   1: using System.ServiceModel;
   2: using Artech.TransactionalService.Service.Interface;
   3: namespace Artech.TransactionalService.Service
   4: {
      
   5:     public class WithdrawService : IWithdrawService
   6:     {
      
   7:         [OperationBehavior(TransactionScopeRequired = true)]
   8:         public void Withdraw(string accountId, double amount)
   9:         {
      
  10:             //省略实现
  11:         }
  12:     }
  13: }

DepositService:

   1: using System.ServiceModel;
   2: using Artech.TransactionalService.Service.Interface;
   3: namespace Artech.TransactionalService.Service
   4: {
      
   5:       public class DepositService : IDepositService
   6:     {       
   7:         [OperationBehavior(TransactionScopeRequired = true)]
   8:         public void Deposit(string accountId, double amount)
   9:         {
      
  10:              //省略实现
  11:         }
  12:     }
  13: }

定义在BankingService的Transfer操作就是调用上述的两个服务,由于服务调用设置到对服务代理的关闭以及异常的处理(相关的内容在《WCF技术剖析(卷1)》的第8章有详细的介绍),为了实现代码的复用,我定义了一个静态的ServiceInvoker类。ServiceInvoker定义如下,泛型方法Invoke<TChannel>用于进行服务的调用,并实现了服务代理的关闭(Close),以及异常抛出是对服务代理的中止(Abort)。Invoke<TChannel>的泛型参数类型为服务契约类型,方法接受两个操作,委托action代表服务调用操作,endpointConfigurationName表示配置的终结点名称。

   1: using System;
   2: using System.ServiceModel;
   3: namespace Artech.TransactionalService.Service.Interface
   4: {
      
   5:     public static class ServiceInvoker
   6:     {
      
   7:         public static void Invoke<TChannel>(Action<TChannel> action, string endpointConfigurationName)
   8:         {
      
   9:             Guard.ArgumentNotNull(action, "action");
  10:             Guard.ArgumentNotNullOrEmpty(endpointConfigurationName, "endpointConfigurationName");
  11:  
  12:             using (ChannelFactory<TChannel> channelFactory = new ChannelFactory<TChannel>(endpointConfigurationName))
  13:             {
      
  14:                 TChannel channel = channelFactory.CreateChannel();
  15:                 using (channel as IDisposable)
  16:                 {
      
  17:                     try
  18:                     {
      
  19:                         action(channel);
  20:                     }
  21:                     catch (TimeoutException)
  22:                     {
      
  23:                         (channel as ICommunicationObject).Abort();
  24:                         throw;
  25:                     }
  26:                     catch (CommunicationException)
  27:                     {
      
  28:                         (channel as ICommunicationObject).Abort();
  29:                         throw;
  30:                     }
  31:                 }
  32:             }
  33:         }
  34:     }
  35: }

那么,借助于ServiceInvoker,BankingService的定义就很简单了。对于Transfer操作,我们依然通过OperationBehaviorAttribute特性将TransactionScopeRequired设置成True。

   1: using System;
   2: using System.Collections.Generic;
   3: using System.ServiceModel;
   4: using Artech.TransactionalService.Service.Interface;
   5: namespace Artech.TransactionalService.Service
   6: {
      
   7:     public class BankingService : IBankingService
   8:     {        
   9:         [OperationBehavior(TransactionScopeRequired = true)]
  10:         public void Transfer(string fromAccountId, string toAccountId, double amount)
  11:         {
      
  12:             ServiceInvoker.Invoke<IWithdrawService>(proxy => proxy.Withdraw(fromAccountId, amount), "withdrawservice");
  13:             ServiceInvoker.Invoke<IDepositService>(proxy => proxy.Deposit(toAccountId, amount), "depositservice");
  14:         }
  15:     }
  16: }

步骤2:部署服务

BankingService和依赖的WithdrawService与DepositService已经定义好了,现在我们需要对它们进行部署。本实例采用基于IIS的服务寄宿方式,在进行部署之前需要为三个服务创建.svc文件。在这里,我.svc文件命名为与服务类型相同的名称(BankingService.svc、WithdrawService.svc和DepositService.svc)。关于.svc文件的具体定义,在这里就不再重复介绍了,对此不了解的读者,可以参阅《WCF技术剖析(卷1)》第7章关于IIS服务寄宿部分。

我们需要分别在主机Foo和Bar上创建两个IIS虚拟目录(假设名称为Banking),并将定义服务契约和服务类型的两个程序集拷贝到Foo\Banking\Bin和Bar\Banking\Bin。然后再将BankingService.svc拷贝到Foo\ Banking下,将WithdrawService.svc和DepositService.svc拷贝到Bar\Banking下。

最后,我们需要创建两个Web.config,分别拷贝到Foo\Banking\Bin和Bar\Banking下面。下面两段XML代表两个Web.config的配置。

Foo\Banking\Web.config:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>  
   3:     <system.serviceModel>
   4:         <bindings>
   5:           <customBinding>
   6:             <binding name="transactionalBinding">
   7:               <textMessageEncoding />
   8:               <transactionFlow/>
   9:               <httpTransport/>
  10:             </binding>
  11:           </customBinding>
  12:         </bindings>
  13:         <services>
  14:             <service name="Artech.TransactionalService.Service.WithdrawService">
  15:                 <endpoint  binding="customBinding" bindingConfiguration="transactionalBinding" contract="Artech.TransactionalService.Service.Interface.IWithdrawService" />
  16:             </service>
  17:           <service name="Artech.TransactionalService.Service.DepositService">
  18:             <endpoint binding="customBinding" bindingConfiguration="transactionalBinding" contract="Artech.TransactionalService.Service.Interface.IDepositService" />
  19:           </service>
  20:         </services>     
  21:     </system.serviceModel>
  22: </configuration>

Bar\Banking\Web.config:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>  
   3:   <system.serviceModel>
   4:     <bindings>
   5:       <customBinding>
   6:         <binding name="transactionalBinding">
   7:           <textMessageEncoding />
   8:           <transactionFlow/>
   9:           <httpTransport/>
  10:         </binding>
  11:       </customBinding>
  12:     </bindings>
  13:     <services>
  14:       <service name="Artech.TransactionalService.Service.BankingService">
  15:         <endpoint  binding="customBinding" bindingConfiguration="transactionalBinding" contract="Artech.TransactionalService.Service.Interface.IBankingService" />
  16:       </service>
  17:     </services>
  18:     <client>
  19:       <endpoint name="withdrawservice" address="http://Bar/banking/withdrawservice.svc"  binding="customBinding" bindingConfiguration="transactionalBinding"
  20:             contract="Artech.TransactionalService.Service.Interface.IWithdrawService" />
  21:       <endpoint name="depositservice" address="http://Bar/banking/depositservice.svc"  binding="customBinding" bindingConfiguration="transactionalBinding"
  22:           contract="Artech.TransactionalService.Service.Interface.IDepositService" />
  23:     </client>
  24:   </system.serviceModel>
  25: </configuration>

步骤3:调用BankingService

现在我们已经部署好了定义的三个服务,现在我们可以调用它们实施转帐处理了。我们可以像调用普通服务一样调用BankingService,无须考虑事务的问题。因为我们通过OperationBehaviorAttribute特性将BankingService的Transfer操作的TransactionScopeRequired设置成True,这会确保整个操作的执行是在一个事务中进行(可能是流入的事务,也可能是重新创建的事务)。下面进行转帐处理的客户端代码和配置。

   1: string fromAccountId = "123456789";
   2: string tooAccountId = "987654321";
   3: double amount = 1000;
   4: ServiceInvoker.Invoke<IBankingService>(proxy => proxy.Transfer(fromAccountId, tooAccountId, amount), "bankingservice");

配置:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:   <system.serviceModel>
   4:     <bindings>
   5:       <customBinding>
   6:         <binding name="transactionalBinding">
   7:           <textMessageEncoding />
   8:           <transactionFlow />
   9:           <httpTransport/>
  10:         </binding>
  11:       </customBinding>
  12:     </bindings>   
  13:     <client>
  14:       <endpoint address="http://Foo/banking/bankingservice.svc"
  15:        binding="customBinding" bindingConfiguration="transactionalBinding" contract="Artech.TransactionalService.Service.Interface.IBankingService"
  16:        name="bankingservice" />      
  17:     </client>
  18:   </system.serviceModel>
  19: </configuration>

实际上,由于定义在BankingService的Transfer操作完全是通过调用WithdrawService和DepositService实现的,我们也可以绕过BankingService直接调用这两个服务实现转账的处理。为此我们需要在配置中添加调用WithdrawService和DepositService的终结点:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:   <system.serviceModel>
   4:     ......
   5:     <client>
   6:       <endpoint name="withdrawservice" address="http://Bar/banking/withdrawservice.svc"  binding="customBinding" bindingConfiguration="transactionalBinding"            contract="Artech.TransactionalService.Service.Interface.IWithdrawService" />
   7:       <endpoint name="depositservice" address="http://Bar/banking/depositservice.svc"  binding="customBinding" bindingConfiguration="transactionalBinding"          contract="Artech.TransactionalService.Service.Interface.IDepositService" />
   8:     </client>
   9:   </system.serviceModel>
  10: </configuration>

由于整个转帐的操作必须纳入到一个事务中进行,并且客户端主动发起对WithdrawService和DepositService两个服务的调用,所以客户端是事物的初始化者。为此,我们需要将对这两个服务的调用放到一个TransactionScope中进行,相应的代码如下所示:

   1: string fromAccountId = "123456789";
   2: string tooAccountId = "987654321";
   3: double amount = 1000;
   4: using (TransactionScope transactionScope = new TransactionScope())
   5: {
      
   6:     ServiceInvoker.Invoke<IWithdrawService>(proxy => proxy.Withdraw(fromAccountId, 100), "withdrawservice");
   7:     ServiceInvoker.Invoke<IDepositService>(proxy => proxy.Deposit("B001", 100), "depositservice");
   8:     transactionScope.Complete();
   9: }

对于上面两种不同的实现方式,实际上都已经涉及到了分布式事务的应用,所以需要借助于DTC。如果读者在运行该实例的时候,两个主机的DTC没有进行合理的设置,将不会成功运行,现在我们简单介绍一下如何进行DTC的设置。

步骤4:设置DTC

通过“控制面板”|“管理工具”|“组件服务”打开组件服务的对话框。然后右击“组件服务”\“计算机”\“我的电脑”结点,并在的上下文菜单中选择“属性”,会弹出“我的电脑 属性”对话框。在该对话框的“MSDTC”Tab页,选择默认的协调器,一般地我们选择“使用本地协调器”选项。

当我们选择使用本地协调器作为默认的DTC之后,在组件服务对话框的“组件服务”\“计算机”\“我的电脑”\“Distributed Transaction Coordinator”结点下面会出现“本地DTC”结点。右击该节点选择“属性”选项,会弹出如图2所示的“本地DTC属性”。你可以对DTC的跟踪(Trace)方式、日志记录、安全和WS-AT进行相应的设置。在这里要是DTC在本实例中可用,重点是对“安全”进行正确的设置。图2是我机器上的设置,限于篇幅问题,我不能对每一个选项进行详细说明,有兴趣的读者相信很容易从网上找到相关的参考资料。此外,如果在已经对DTC作了相应设置之后还出现DTC的问题,你可以看看DTC通信是否被防火墙屏蔽。

image

图2 本地DTC设置对话框

步骤5:采用WS-AT协议

在本例中,所有终结点采用的绑定类型均是包含有TransactionFlowBindingElement的CustomBinding。通过前面的介绍我们知道,默认采用的事务处理协议是OleTx,如果我们希望采用WS-AT协议,我们需要通过配置将协议类型改成WSAtomicTransactionOctober2004或者WSAtomicTransaction11。下面的配置中,我们将实例中使用的绑定支持的事务处理协议设置成WSAtomicTransaction11,使之采用WS-AT协议进行事务处理。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:   <system.serviceModel>
   4:     <bindings>
   5:       <customBinding>
   6:         <binding name="TransactionalBinding">
   7:           <textMessageEncoding />
   8:           <transactionFlow transactionProtocol="WSAtomicTransaction11" />
   9:           <httpTransport/>
  10:         </binding>
  11:       </customBinding>
  12:     </bindings>
  13:   </system.serviceModel>
  14: </configuration>

DTC通过基于HTTPS的通信方式为WS-AT(1.0和1.1)提供实现,我们只知道HTTPS通过SSL实现传输安全,而SSL需要将相应的证书(Certificate)绑定在HTTPS站点上面。为了让DTC支持WS-AT,我们需要对DTC进行相关的配置。

首先我们需要为参与到事务的两台主机创建相对应的证书,在这里我们直接采用Makecert.exe这个X.509证书生成工具。我们可以通过下面两个命令行创建两张X.509证书,它们分别代表Foo和Bar两台主机,读者在做练习这个例子时,需要换成自己相应的机器名称。关于Makecert.exe的命令行选项的含义,可以参考MSDN(http://msdn.microsoft.com/zh-cn/library/bfsktky3%28VS.80%29.aspx)

MakeCert –n CN=Foo –pe –sky exchange –sr LocalMachine –ss MY
MakeCert –n CN=Bar –pe –sky exchange –sr LocalMachine –ss MY

在主机Foo运行上面的命令行后,会创建两张个X.509证书并将其存入“本地计算机\个人(LocalMachine\MY)”存储中。你需要通过MMC将其导出成证书文件,并将其导入到主机Foo的“本地计算机\受信任的根证书颁发机构(LocalMachine\Root)”存储中,以及主机Bar的“本地计算机\个人(LocalMachine\MY)”和“本地计算机\受信任的根证书颁发机构(LocalMachine\Root)”存储中。需要注意的是,在到处的时候务必选择“导出私钥”选项,因为不包含私钥的正式是不能和SSL站点绑定的。

DTC的WS-AT可以借助WS-AT配置工具wsatConfig .exe以命令行的方式进行设置,该工具位于"%WINDIR%\Microsoft.NET\Framework\v3.0\Windows Communication Foundation"目录下面。关于wsatConfig.exe配置工具的用法,可以参考MSDN相关文档:http://msdn.microsoft.com/zh-cn/library/ms732007.aspx。在这里,我需要介绍的是一种可视化的WS-AT配置方式。

图2所示的DTC设置对话框中,有一个WS-AT Tab页,通过它我们可以很容易地进行WS-AT的相关配置。不过,在默认的情况下,这个Tab页是不存在的。我们需要借助Regasm.exe这个程序集注册工具以命令行的形式对包含有WS-AT配置界面的程序集进行注册,该程序集位于Windows SDK目录下:%PROGRAMFILES%\Microsoft SDKs\Windows\v6.0\Bin或者%PROGRAMFILES%\Microsoft SDKs\Windows\v6.0A\Bin。当运行下面的命令行后,DTC设置对话框中将会出现WS-AT Tab页了。

regasm.exe /codebase WsatUI.dll 

选择WS-AT Tab,你将会看到如图3所示的界面。然后,我们针对主机Foo和Bar分别进行如下的设置,使之建立相互信任关系:

  • Foo选择证书Foo(CN=Foo)和Bar(CN=Bar)分别作为终结点证书(Endpoint certificate)和授权证书(Authorized certificates);
  • Bar选择证书Bar(CN=Bar)和Foo(CN=Foo)分别作为终结点证书(Endpoint certificate)和授权证书(Authorized certificates)。

image

图3 WS-AT设置界面

对于本实例来说,既是你将绑定的事务处理协议设置成WSAtomicTransactionOctober2004或者WSAtomicTransaction11,并对WS-AT进行了正确的设置,但是依然采用的是OleTx协议。这是由于DTC的OleTx提升(OleTx Upgrade)机制导致。关于OleTx提升机制,会在本章后续部分介绍。如果希望本实例真正采用WS-AT进行事务事务,你需要显示关闭OleTx的自动提升。我们只需要通过注册表编辑器在HKLM\SOFTWARE\Microsoft\WSAT\3.0结点下添加一个名称为OleTxUpgradeEnabled的双字节(DWORD)注册表项,比较值设为0。


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33757609/article/details/90336560

智能推荐

[Mysql] CONVERT函数_mysql convert-程序员宅基地

文章浏览阅读3.1w次,点赞23次,收藏109次。本文主要讲解CONVERT函数_mysql convert

【Android】Retrofit入门详解-程序员宅基地

文章浏览阅读1.6k次,点赞23次,收藏2次。简介:大三学生党一枚!主攻Android开发,对于Web和后端均有了解。个人语录:取乎其上,得乎其中,取乎其中,得乎其下,以顶级态度写好一篇的博客。Retrofit入门一.Retrofit介绍二.Retrofit注解2.1 请求方法注解2.1.1 GET请求2.1.2 POST请求2.2 标记类注解2.2.1 FormUrlEncoded2.2.2 Multipart2.2.3 Streaming2.3 参数类注解2.3.1 Header和Headers2.3.2 Body2.3.3 Path2.3.4_retrofit

教你拷贝所有文件到指定文件夹_所有文件夹下文件的 拷贝怎么弄-程序员宅基地

文章浏览阅读1.9k次。在处理文件的时候,如何将文件、文件夹复制到指定文件夹之中呢?打开【文件批量改名高手】,在“文件批量管理任务”中,先点“添加文件”,将文件素材导入。选好一系列的复制选项,单击开始复制,等全部复制好了,提示“已完成XX%”然后可以任意右击一个文件夹路径,在显示出的下拉列表中,选择“打开文件夹”在“复制到的目标文件夹(目录)”中,导入文件夹,多个文件夹,一行一个。最后,即可看到文件、文件夹都复制到各个指定的文件夹之中一一显示着啦。导入后,在表格中我们就可以看到文件或文件夹的名称以及所排列的序号。..._所有文件夹下文件的 拷贝怎么弄

win10和linux双系统安装步骤(详细!)_怎么装双系统win10和linux-程序员宅基地

文章浏览阅读5k次,点赞11次,收藏42次。Windows10安装ubuntu双系统教程ubuntu分区方案_怎么装双系统win10和linux

从图的邻接表表示转换成邻接矩阵表示_typedef struct arcnode{int adjvex;-程序员宅基地

文章浏览阅读1.1k次。从图的邻接表表示转换成邻接矩阵表示typedef struct ArcNode{ int adjvex;//该弧指向的顶点的位置 struct ArcNode *next;//下一条弧的指针 int weight;//弧的权重} ArcNode;typedef struct{ VertexType data;//顶点信息 ArcNode *firstarc;} VNode,AdList[MAXSIZE];typedef struct{ int vexnum;//顶点数 int _typedef struct arcnode{int adjvex;

学好Python开发你一定会用到这30框架种(1)-程序员宅基地

文章浏览阅读635次,点赞18次,收藏26次。14、fabric是基于Python实现的SSH命令行工具,简化了SSH的应用程序部署及系统管理任务,它提供了系统基础的操作组件,可以实现本地或远程shell命令,包括命令执行,文件上传,下载及完整执行日志输出等功能。7、pycurl 是一个用C语言写的libcurl Python实现,功能强大,支持的协议有:FTP,HTTP,HTTPS,TELNET等,可以理解为Linux下curl命令功能的Python封装。Scipy是Python的科学计算库,对Numpy的功能进行了扩充,同时也有部分功能是重合的。

随便推点

成功Ubuntu18.04 ROS melodic安装Cartograhper+Ceres1.13.0,以及错误总结_ros18.04 安装ca-程序员宅基地

文章浏览阅读1.8k次。二.初始化工作空间三.设置下载地址四.下载功能包此处可能会报错,请看:rosdep update遇到ERROR: error loading sources list: The read operation timed out问题_DD᭄ꦿng的博客-程序员宅基地接下来一次安装所有功能包,注意对应ROS版本 五.编译功能包isolated:单独编译各个功能包,每个功能包之间不产生依赖。编译过程时间比较长,可能需要几分钟时间。此处可能会报错:缺少absl依赖包_ros18.04 安装ca

Harbor2.2.1配置(trivy扫描器、镜像签名)_init error: db error: failed to download vulnerabi-程序员宅基地

文章浏览阅读4.1k次,点赞3次,收藏7次。Haobor2.2.1配置(trivy扫描器、镜像签名)docker-compose下载https://github.com/docker/compose/releases安装cp docker-compose /usr/local/binchmod +x /usr/local/bin/docker-composeharbor下载https://github.com/goharbor/harbor/releases解压tar xf xxx.tgx配置harbor根下建立:mkd_init error: db error: failed to download vulnerability db: database download

openFOAM学习笔记(四)—— openFOAM中的List_openfoam list-程序员宅基地

文章浏览阅读3.2k次。又是一个很底层的部分,但是也非常重要_openfoam list

C++对象的JSON序列化与反序列化探索_c++对象 json 序列化和反序列化 库-程序员宅基地

文章浏览阅读1.7w次,点赞3次,收藏15次。一:背景作为一名C++开发人员,我一直很期待能够像C#与JAVA那样,可以轻松的进行对象的序列化与反序列化,但到目前为止,尚未找到相对完美的解决方案。本文旨在抛砖引玉,期待有更好的解决方案;同时向大家寻求帮助,解决本文中未解决的问题。 二:相关技术介绍本方案采用JsonCpp来做具体的JSON的读入与输出,再结合类成员变量的映射,最终实现对象的JSON序列化与反序列化。本文不再_c++对象 json 序列化和反序列化 库

linux x window 详解,王垠:详解Xwindow(插窗户)的工作原理-程序员宅基地

文章浏览阅读523次。该楼层疑似违规已被系统折叠隐藏此楼查看此楼(本文作者貌似是王垠,在某处扒拉出来的转载过来)Xwindow 是非常巧妙的设计,很多时候它在概念上比其它窗口系统先进,以至于经过很多年它仍然是工作站上的工业标准。许多其它窗口系统的概念都是从 Xwindow 学来的。Xwindow 可以说的东西太多了。下面只分辨一些容易混淆的概念,提出一些正确使用它的建议。分辨 X server 和 X client这..._整个插入的窗叫什么

AHAS arms调用链查询中,接口实际耗时和监听耗时差异在什么地方?_arms调用链路耗时看不懂-程序员宅基地

文章浏览阅读109次。监听耗时仅代表了 AHAS ARMS Listener(即调用链收集器)在收集并处理当前请求的调用信息时所需要的时间。它不包括网络传输、处理请求、执行操作、处理响应等其他阶段的时间,仅代表 Listener 所需的时间。通常这个时间会很短,只有几毫秒甚至更短。接口实际耗时包括了整个请求/响应周期中的所有时间,包括网络传输、处理请求、执行操作、处理响应等阶段消耗的时间。它代表了该请求在客户端发起到最终服务器响应完成所花费的总时间。_arms调用链路耗时看不懂