异构SOA系统架构之Asp.net实现(兼容dubbo)
我们公司技术部门情况比较复杂,分到多个集团,每个集团又可能分为几个部门,每个部门又可能分为多个小组,组织架构比较复杂,开发人员比较多。
使用的编程语言也有点复杂,主流语言有.net(C#)、Java、PHP等。
所以SOA架构需要的是异构SOA。
有的同学可能说这个简单吗?“把部门合并扁平化合并为一个团队,把语言统一一种,要么.net要么Java。”
其实这样的简单粗暴并不能很好的解决问题的
首先公司组织架构就是不能随便修改的,一个公司的组织架构就是服务于这个公司的经营理念和营销模式,技术部门是服务机构并不直接产生价值,技术部门架构和公司组织架构高度一致能带来业务的高效性。
其次多语言技术体系也有其可取性
某个项目哪种语言能做的更快更好就用哪种语言
哪种语言的程序员好招,就多招一些,能在各种技术方向的变化中立于不败之地
现在继续说SOA,说起公司对SOA选型对于.net程序员开始还是一件挺悲催的事情,因为公司选的是dubbo
dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,说它是个伟大的开源项目并不为过,在很多互联网公司都有运用。
但是,dubbo是个Java项目,.net程序员就悲催了
为了更好的支持多语言的异构系统现状,具体选型是dubbox+ZooKeeper+Thrift,其中Thrift是facebook开发的高效RPC,支持语言非常多, C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml等。
有了Thrift,.net程序员的“春天”是不是就来了呢?
还是挺悲催,Java程序员几乎不用写额外代码配置一下就可以调用SOA服务或者发布服务,.net程序员要自己维护和ZooKeeper的通信和Thrift通信及日志统计和报送。
.net程序员苦不堪言,有些人质疑SOA选型(对.net程序员不公平),有些人"喊"着要.net程序员使用其他架构单干 ......
后来机缘巧合,.net的SOA这个事情就落在我的身上
领导把这个任务交给我的时候,我轻松的说没问题,但是时间证明这个事情比我原来想象的复杂得多,我也走了一些弯路,有过一些不太现实的想法,最终还是有了一个比较满意的结果
一、先说ZooKeeper
1、ZooKeeper是开源项目,其原理和作用这里不说,自行度娘
2、ZooKeeper的.net客户端使用nuget就可以安装使用
ZooKeeper客户端库也有很多开源项目支持,我这里选的是Apache基金会的官方版本
3、本地启动一个ZooKeeper来测试
ZooKeeper服务是Java开发的,ZooKeeper是个非常优秀的中间件,使用.net和Java调用区别并不大
这里查看ZooKeeper的工具也是Java开发的ZooInspector,正式环境我们有专门的后台来管理,本地调试ZooInspector就够用了。
二、再说Thrift
1、Thrift也是开源项目,其原理和作用这里不说,自行度娘
2、Thrift的.net库使用nuget就可以安装使用
Thrift客户端库也有很多开源项目支持,我这里还是选Apache基金会的官方版本
三、使用.net开发一个HelloWord服务
1、按Thrift的IDL规范定义接口Thrift文件
namespace java SOATest
namespace csharp SOATest
namespace php SOATest service HelloWorldService {
string sayHello(1:string username)
}
注:Thrift规范还是自行度娘
2、使用Thrift.exe生成代码
Thrift.exe使用方法可以使用Thrift的help指令查看,最好的方法还是自行度娘
3、到gen-csharp中找到刚生成的代码复制到项目中使用
/**
* Autogenerated by Thrift Compiler (0.9.3)
*
* DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
* @generated
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Thrift;
using Thrift.Collections;
using System.Runtime.Serialization;
using Thrift.Protocol;
using Thrift.Transport; namespace SOATest
{
public partial class HelloWorldService {
public interface Iface {
string sayHello(string username);
#if SILVERLIGHT
IAsyncResult Begin_sayHello(AsyncCallback callback, object state, string username);
string End_sayHello(IAsyncResult asyncResult);
#endif
} public class Client : IDisposable, Iface {
public Client(TProtocol prot) : this(prot, prot)
{
} public Client(TProtocol iprot, TProtocol oprot)
{
iprot_ = iprot;
oprot_ = oprot;
} protected TProtocol iprot_;
protected TProtocol oprot_;
protected int seqid_; public TProtocol InputProtocol
{
get { return iprot_; }
}
public TProtocol OutputProtocol
{
get { return oprot_; }
} #region " IDisposable Support "
private bool _IsDisposed; // IDisposable
public void Dispose()
{
Dispose(true);
} protected virtual void Dispose(bool disposing)
{
if (!_IsDisposed)
{
if (disposing)
{
if (iprot_ != null)
{
((IDisposable)iprot_).Dispose();
}
if (oprot_ != null)
{
((IDisposable)oprot_).Dispose();
}
}
}
_IsDisposed = true;
}
#endregion #if SILVERLIGHT
public IAsyncResult Begin_sayHello(AsyncCallback callback, object state, string username)
{
return send_sayHello(callback, state, username);
} public string End_sayHello(IAsyncResult asyncResult)
{
oprot_.Transport.EndFlush(asyncResult);
return recv_sayHello();
} #endif public string sayHello(string username)
{
#if !SILVERLIGHT
send_sayHello(username);
return recv_sayHello(); #else
var asyncResult = Begin_sayHello(null, null, username);
return End_sayHello(asyncResult); #endif
}
#if SILVERLIGHT
public IAsyncResult send_sayHello(AsyncCallback callback, object state, string username)
#else
public void send_sayHello(string username)
#endif
{
oprot_.WriteMessageBegin(new TMessage("sayHello", TMessageType.Call, seqid_));
sayHello_args args = new sayHello_args();
args.Username = username;
args.Write(oprot_);
oprot_.WriteMessageEnd();
#if SILVERLIGHT
return oprot_.Transport.BeginFlush(callback, state);
#else
oprot_.Transport.Flush();
#endif
} public string recv_sayHello()
{
TMessage msg = iprot_.ReadMessageBegin();
if (msg.Type == TMessageType.Exception) {
TApplicationException x = TApplicationException.Read(iprot_);
iprot_.ReadMessageEnd();
throw x;
}
sayHello_result result = new sayHello_result();
result.Read(iprot_);
iprot_.ReadMessageEnd();
if (result.__isset.success) {
return result.Success;
}
throw new TApplicationException(TApplicationException.ExceptionType.MissingResult, "sayHello failed: unknown result");
} }
public class Processor : TProcessor {
public Processor(Iface iface)
{
iface_ = iface;
processMap_["sayHello"] = sayHello_Process;
} protected delegate void ProcessFunction(int seqid, TProtocol iprot, TProtocol oprot);
private Iface iface_;
protected Dictionary<string, ProcessFunction> processMap_ = new Dictionary<string, ProcessFunction>(); public bool Process(TProtocol iprot, TProtocol oprot)
{
try
{
TMessage msg = iprot.ReadMessageBegin();
ProcessFunction fn;
processMap_.TryGetValue(msg.Name, out fn);
if (fn == null) {
TProtocolUtil.Skip(iprot, TType.Struct);
iprot.ReadMessageEnd();
TApplicationException x = new TApplicationException (TApplicationException.ExceptionType.UnknownMethod, "Invalid method name: '" + msg.Name + "'");
oprot.WriteMessageBegin(new TMessage(msg.Name, TMessageType.Exception, msg.SeqID));
x.Write(oprot);
oprot.WriteMessageEnd();
oprot.Transport.Flush();
return true;
}
fn(msg.SeqID, iprot, oprot);
}
catch (IOException)
{
return false;
}
return true;
} public void sayHello_Process(int seqid, TProtocol iprot, TProtocol oprot)
{
sayHello_args args = new sayHello_args();
args.Read(iprot);
iprot.ReadMessageEnd();
sayHello_result result = new sayHello_result();
result.Success = iface_.sayHello(args.Username);
oprot.WriteMessageBegin(new TMessage("sayHello", TMessageType.Reply, seqid));
result.Write(oprot);
oprot.WriteMessageEnd();
oprot.Transport.Flush();
} } #if !SILVERLIGHT
[Serializable]
#endif
public partial class sayHello_args : TBase
{
private string _username; public string Username
{
get
{
return _username;
}
set
{
__isset.username = true;
this._username = value;
}
} public Isset __isset;
#if !SILVERLIGHT
[Serializable]
#endif
public struct Isset {
public bool username;
} public sayHello_args() {
} public void Read (TProtocol iprot)
{
iprot.IncrementRecursionDepth();
try
{
TField field;
iprot.ReadStructBegin();
while (true)
{
field = iprot.ReadFieldBegin();
if (field.Type == TType.Stop) {
break;
}
switch (field.ID)
{
case :
if (field.Type == TType.String) {
Username = iprot.ReadString();
} else {
TProtocolUtil.Skip(iprot, field.Type);
}
break;
default:
TProtocolUtil.Skip(iprot, field.Type);
break;
}
iprot.ReadFieldEnd();
}
iprot.ReadStructEnd();
}
finally
{
iprot.DecrementRecursionDepth();
}
} public void Write(TProtocol oprot) {
oprot.IncrementRecursionDepth();
try
{
TStruct struc = new TStruct("sayHello_args");
oprot.WriteStructBegin(struc);
TField field = new TField();
if (Username != null && __isset.username) {
field.Name = "username";
field.Type = TType.String;
field.ID = ;
oprot.WriteFieldBegin(field);
oprot.WriteString(Username);
oprot.WriteFieldEnd();
}
oprot.WriteFieldStop();
oprot.WriteStructEnd();
}
finally
{
oprot.DecrementRecursionDepth();
}
} public override string ToString() {
StringBuilder __sb = new StringBuilder("sayHello_args(");
bool __first = true;
if (Username != null && __isset.username) {
if(!__first) { __sb.Append(", "); }
__first = false;
__sb.Append("Username: ");
__sb.Append(Username);
}
__sb.Append(")");
return __sb.ToString();
} } #if !SILVERLIGHT
[Serializable]
#endif
public partial class sayHello_result : TBase
{
private string _success; public string Success
{
get
{
return _success;
}
set
{
__isset.success = true;
this._success = value;
}
} public Isset __isset;
#if !SILVERLIGHT
[Serializable]
#endif
public struct Isset {
public bool success;
} public sayHello_result() {
} public void Read (TProtocol iprot)
{
iprot.IncrementRecursionDepth();
try
{
TField field;
iprot.ReadStructBegin();
while (true)
{
field = iprot.ReadFieldBegin();
if (field.Type == TType.Stop) {
break;
}
switch (field.ID)
{
case :
if (field.Type == TType.String) {
Success = iprot.ReadString();
} else {
TProtocolUtil.Skip(iprot, field.Type);
}
break;
default:
TProtocolUtil.Skip(iprot, field.Type);
break;
}
iprot.ReadFieldEnd();
}
iprot.ReadStructEnd();
}
finally
{
iprot.DecrementRecursionDepth();
}
} public void Write(TProtocol oprot) {
oprot.IncrementRecursionDepth();
try
{
TStruct struc = new TStruct("sayHello_result");
oprot.WriteStructBegin(struc);
TField field = new TField(); if (this.__isset.success) {
if (Success != null) {
field.Name = "Success";
field.Type = TType.String;
field.ID = ;
oprot.WriteFieldBegin(field);
oprot.WriteString(Success);
oprot.WriteFieldEnd();
}
}
oprot.WriteFieldStop();
oprot.WriteStructEnd();
}
finally
{
oprot.DecrementRecursionDepth();
}
} public override string ToString() {
StringBuilder __sb = new StringBuilder("sayHello_result(");
bool __first = true;
if (Success != null && __isset.success) {
if(!__first) { __sb.Append(", "); }
__first = false;
__sb.Append("Success: ");
__sb.Append(Success);
}
__sb.Append(")");
return __sb.ToString();
} } }
}
HelloWorldService
注:强烈建议大家别去修改Thrift生成的代码
4、新建类实现生成代码的服务接口(实际逻辑调用类)
实现接口HelloWorldService.Iface
public class HelloWorldImp : HelloWorldService.Iface
{
public string sayHello(string username)
{
if (string.IsNullOrWhiteSpace(username))
return null;
string msg = string.Concat("Hello ", username);
Console.WriteLine(msg);
return msg;
}
}
5、发布并注册服务到ZooKeeper
public class ServeTest
{
public static void Test()
{
ZKConsumer zooKeeper = ZKInit();
string serviceName = "com.fang.HelloWorld$Iface";//服务名
HelloWorldService.Iface service = new HelloWorldImp();//服务实现逻辑
string serviceIp = "192.168.109.166";//发布服务使用ip
int servicePort = ;//发布服务使用端口
string group = "kg";//应用程序分组
string serviceVersion = "1.0.0";//服务版本
int serviceTimeOut = ; //服务超时阈值(单位Millisecond)
int alertElapsed = ; //服务执行耗时监控报警阈值(单位Millisecond)
int alertFailure = ; //服务每分钟出错次数监控报警阈值
//注册并发布服务
zooKeeper.RegistService<HelloWorldService.Iface>(serviceName, service, serviceIp, servicePort, group, serviceVersion, serviceTimeOut, alertElapsed, alertFailure);
}
/// <summary>
/// 初始化zooKeeper
/// </summary>
/// <returns></returns>
private static ZKConsumer ZKInit()
{
ZKConsumer zooKeeper = new ZKConsumer();
zooKeeper.Connectstring = "192.168.109.166:2181";
zooKeeper.Logger = Fang.Log.Loger.CreateDayLog("ServeTest");
zooKeeper.Init();
return zooKeeper;
}
}
注:其中ZKConsumer就是我定义的和ZooKeeper交互的类,也几乎是.net SOA直接交互的唯一一个类,使用起来是不是非常简单,其实实现还是比较复杂的,随后再说
6、启动服务看一下
6.1 其实执行时候就是开了一个socket监听,很简单
6.2 看一下日志信息
11:46:21
ZooKeeper Init
11:46:21
ZooKeeper Connect
11:46:21
ZK触发了None事件(path:)!
11:46:21
ZooKeeper CONNECTED
11:46:21
Collecter Start
11:46:21
Consumer Subcribe:/dubbo/com.alibaba.dubbo.monitor.gen.thrift.MonitorService%24Iface/providers
11:46:22
Collecter Run
11:46:22
Collecter OnFail
11:46:27
Collecter Run
11:46:27
Collecter OnFail
以上是日志文件,有ZooKeeper连接信息和订阅日志收集服务信息及日志收集信息
日志收集是一个线程调度,由于还没有连接没有日志,所以Collecter都是Fail
6.3 看一下ZooKeeper的变化
ZooKeeper在dubbo节点下多出了一个节点"com.fang.HelloWorld%24Iface"及其多个子节点,其中重点是其providers子节点下有一个很长的节点,那个节点就是表示当前服务信息的,如果服务关闭,这个信息也会消失
都在dubbo下不难理解,因为我们选型就是dubbo,.net要兼容dubbo的一些特性
四、做个客户端来调用HelloWorld服务
1、使用Thrift.exe生成代码并复制到项目中
服务端和客户端生成代码是没有区别的,这个就不再展开,需要再了解参考服务端生成代码部分
2、调用调用HelloWorld服务的源码
public class HelloWorldTest
{
public static void Test()
{
ZKConsumer zooKeeper = ZKInit();
Subcribe(zooKeeper);//订阅com.fang.HelloWorld
string str = null;
do
{
str = Console.ReadLine();
if (string.Equals(str, "Exit", StringComparison.CurrentCultureIgnoreCase))
return;
Console.WriteLine("callDemo");
CallService();//调用服务
} while (true);
}
/// <summary>
/// 订阅AskSearchService
/// </summary>
/// <param name="zooKeeper"></param>
private static void Subcribe(ZKConsumer zooKeeper)
{
string serviceName = "com.fang.HelloWorld$Iface";//服务名
string serviceGroup = "kg";//服务分组
string serviceVersion = "1.0.0.0";//服务版本
int serviceTimeOut = ; //服务超时阈值(单位Millisecond)
int alertElapsed = ; //服务执行耗时监控报警阈值(单位Millisecond)
int alertFailure = ; //服务每分钟出错次数监控报警阈值
//订阅服务
bool state = zooKeeper.SubcribeService<HelloWorldService.Iface>(serviceName, serviceGroup, serviceVersion, serviceTimeOut, alertElapsed, alertFailure);
Console.WriteLine(string.Concat("SubcribeService(", serviceName, ") is ", state.ToString()));
}
/// <summary>
/// 初始化zooKeeper
/// </summary>
/// <returns></returns>
private static ZKConsumer ZKInit()
{
ZKConsumer zooKeeper = new ZKConsumer();
zooKeeper.Connectstring = "192.168.109.166:2181";
zooKeeper.Logger = Fang.Log.Loger.CreateDayLog("HelloWorldTest");
zooKeeper.Init();
return zooKeeper;
}
/// <summary>
/// 调用服务
/// </summary>
private static void CallService()
{
using (var resource = ZKConsumer.GetServiceByContainer<HelloWorldService.Iface>())
{
HelloWorldService.Iface service = resource.Service;
if (service == null)
Console.WriteLine("service is null");
string results = null;
try
{
results = service.sayHello("Word");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
if (results != null)
Console.WriteLine(results.ToString());
}
}
}
HelloWorldTest
注:以上看上去洋洋洒洒几十行,貌似很复杂,其实不然
3、以上代码解析
2.1 初始化和订阅服务
ZKInit是初始化ZooKeeper的,很简单
Subcribe是订阅服务,看上去很复杂,其实就是一行代码,只是为了便于理解拆分写成这样
以上初始化对于每个应用程序都只需要一次
web应用程序(站点)可以在Global.asax的Application_Start中初始化一次,也可以配置一个IHttpModule来初始化(在Init)
控制台和windows服务在Main方法中的开始部分初始化即可
2.2 Test是便于测试写了一个while循环,实际开发可以无视
2.3 CallService是实际调用服务代码
核心就是一个using及其中的GetServiceByContainer方法,及调用sayHello方法,其他都是安全检测异常处理测试代码,算下来核心代码也就是两三行
应该说还是挺简单的吧,当然没有java同学用dubbo简单,但至少这里封装了ZooKeeper、Thrift和日志等。让大家尽量少和业务无关的东西打交道
4、运行测试一下
4.1 执行之后效果如下
4.2 看一下ZooKeeper的变化
这次在consumers下增加了一个很长的节点,证明客户端和服务端都和ZooKeeper连接上了
5、调用几次试试
5.1 客户端截图
5.2 服务端截图
以上测试证明是服务端和客户端通信没有问题
实际上我和Java的同事也联测了,Java调用.net的服务也没有问题,.net调用dubbo(Java)的服务也没有问题
另外,多个服务端和多个客户端也是测试通过了,限于篇幅这个就不再举例
五、主要源码解析
1、项目截图
2、逐个解析一下
2.1 ApplicationInfo很简单就是读取一些应用程序配置信息
AppSettings["ZooKeeperConnectstring"]是ZooKeeper连接地址
AppSettings["ApplicationName"]是应用程序名用来程序定位及服务依赖关系图绘制
AppSettings["ApplicationOwner"]是项目负责人等
2.2 Collecter收集日志的逻辑及其线程调度
2.3 Connecter用来连接ZooKeeper及维护ZooKeeper连接(重连)
2.4 ConsumAop是客户AOP拦截,记录日志到队列
2.5 Consumer用来客户服务订阅及服务路由管理
2.6 HostStat用于服务主机信息解析
2.7 ISubcribe是ZooKeeper订阅接口
2.8 MethodReport是方法执行日志
2.9 Monitor是日志定时报送作业(线程调度)
2.10 Provider用于服务端启动Socket服务及注册到ZooKeeper
2.11 ReportAopHandler是方法执行Aop拦截,用于服务端执行拦截,ConsumAop继承该类
2.12 ReportStorage日志存储器,并维护一个Collecter和一个Monitor线程调度
2.13 ServiceConcurrent用于服务执行并发统计(客户端及服务端)
2.14 ServiceConfig是客户端和服务端的公共配置
2.15 ServiceFactor是客户端服务工厂及Socket连接池调用
2.16 ServiceHost是单个服务主机的Socket工厂及连接池
2.17 ServiceHostManager用于服务主机集群管理
2.18 ServiceResource用于服务资源管理,现在用于回收Socket主机(以后可能要做成服务对象也可以回收)
2.19 ServiceSocket,本来用于维护Socket连接,重连的,现在只是Socket包装类
2.20 Statistics用于日志统计汇总
2.21 ZKConsumer是ZooKeeper消费者,用来维护ZooKeeper连接及基于ZooKeeper的功能
注:另外Aop、容器、资源池。线程调度、类型转化等来源于面向接口主框架
以上功能虽然可以达到.net使用和dubbo兼容的服务功能,但是离dubbo在功能和稳定性上还有差距,这个建设过程需要持续下去
最后,我畅想到一个梦境。一个.net小伙深情的望着Java小姑娘说,我做好准备了,我们做朋友吧。Java小姑娘点点头。此时响起了优美的华尔兹。.net和Java手拉手在舞池里翩翩起舞...
异构SOA系统架构之Asp.net实现(兼容dubbo)的更多相关文章
- 异构(兼容dubbo)SOA系统架构(.net)优化升级
前面一片文章已经提到我司的异构(兼容dubbo)SOA系统架构,解决了不少技术痛点,也还算比较完善,也顺利推广开来. 但作为项目的开发者,自己产品的问题心里是清楚的,离自己满意还是有不小的距离. 在推 ...
- 大型高性能ASP.NET系统架构设计
大型动态应用系统平台主要是针对于大流量.高并发网站建立的底层系统架构.大型网站的运行需要一个可靠.安全.可扩展.易维护的应用系统平台做为支撑,以保证网站应用的平稳运行. 大型动态应用系统又可分为几个子 ...
- NET/ASP.NET Routing路由(深入解析路由系统架构原理)(转载)
NET/ASP.NET Routing路由(深入解析路由系统架构原理) 阅读目录: 1.开篇介绍 2.ASP.NET Routing 路由对象模型的位置 3.ASP.NET Routing 路由对象模 ...
- SOA 新业务语言 新系统架构——什么是SOA
原文地址:http://blog.csdn.net/ichaos/archive/2008/01/20/2054377.aspx SOA的概念是Gartner在1996年提出来的,并于2002年12月 ...
- SOA的企业系统架构
基于SOA的企业系统架构设计及IT治理日记 (引) TOGAF是一个架构框架,指导做企业架构的标准和方法,简而言之,是一种协助开发.验收.运行.使用和维护架构的工具,核心是架构开发方法(ADM)指导了 ...
- 系统架构-设计模式(适配器、观察者、代理、抽象工厂等)及架构模式(C/S、B/S、分布式、SOA、SaaS)(干货)
博客园首页是需要分享干货的地方,今天早上写的<HRMS(人力资源管理系统)-从单机应用到SaaS应用-系统介绍>内容下架了,所以我就按照相关规定,只分享干货,我把之前写完的内容整理发布上来 ...
- [转]系统架构演变--集中式架构-垂直拆分-分布式服务-SOA(服务治理)-微服务
一.系统架构演变 1.1. 集中式架构 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本.此时,用于简化增删改查工作量的数据访问框架(ORM)是影响项目开发的关键. 存在的 ...
- [转]【转】大型高性能ASP.NET系统架构设计
大型高性能ASP.NET系统架构设计 大型动态应用系统平台主要是针对于大流量.高并发网站建立的底层系统架构.大型网站的运行需要一个可靠.安全.可扩展.易维护的应用系统平台做为支撑,以保证网站应用的平稳 ...
- .NET/ASP.NET Routing路由(深入解析路由系统架构原理)
阅读目录: 1.开篇介绍 2.ASP.NET Routing 路由对象模型的位置 3.ASP.NET Routing 路由对象模型的入口 4.ASP.NET Routing 路由对象模型的内部结构 4 ...
随机推荐
- [译] 理解PHP内部函数的定义(给PHP开发者的PHP源码-第二部分)
文章来自:http://www.hoohack.me/2016/02/10/understanding-phps-internal-function-definitions-ch 原文:https:/ ...
- Guava Supplier实例
今天想讲一下Guava Suppliers的几点用法.Guava Suppliers的主要功能是创建包裹的单例对象,通过get方法可以获取对象的值.每次获取的对象都为同一个对象,但你和单例模式有所区别 ...
- SQL Server 深入解析索引存储(下)
标签:SQL SERVER/MSSQL SERVER/数据库/DBA/索引体系结构/非聚集索引 概述 非聚集索引与聚集索引具有相同的 B 树结构,它们之间的显著差别在于以下两点: 基础表的数据行不按非 ...
- Silverlight和WPF中DataContractJsonSerializer对时间的处理差异
原创文章转载请注明出处:@协思, http://zeeman.cnblogs.com Silverlight脱胎于WPF,他们的行为不完全并不完全相同,DataContractJsonSerializ ...
- 玩转动态编译 - 高级篇:一,IL访问静态属性和字段
IL介绍 通用中间语言(Common Intermediate Language,简称CIL,发音为"sill"或"kill")是一种属于通用语言架构和.NET ...
- Python3 的json 和 PHP的json
Python3操作json的标准api库参考:https://docs.python.org/3/library/json.html#module-json >>> aa = ['/ ...
- Atitit 分区后的查询 mysql分区记录的流程与原理
Atitit 分区后的查询 mysql分区记录的流程与原理 1.1.1. ibd是MySQL数据文件.索引文件1 1.2. 已经又数据了,如何分区? 给已有的表加上分区 ]1 1.3. 分成4个区, ...
- iOS开发-捕获程序崩溃日志
iOS开发中遇到程序崩溃是很正常的事情,如何在程序崩溃时捕获到异常信息并通知开发者,是大多数软件都选择的方法.下面就介绍如何在iOS中实现: 1. 在程序启动时加上一个异常捕获监听,用来处理程序崩溃时 ...
- 微小,但是美好的改变 G2 2.2发布
G2 2.2.0, 『微小,但是美好的改变.』 # 一.丰富可视化交互形式 #### 选择: [图形的单选.多选](https://g2.alipay.com/tutorial/geom-select ...
- OpenCASCADE DataExchange DWG
OpenCASCADE DataExchange DWG eryar@163.com Abstract. DWG is a file format created in the 70’s for th ...