WCF从理论到实践(15):响应变化 (转)

本文目的

需求变化是软件开发过程中的一大难题,我们经常扼腕叹息:面对变化,我们的软件为何如此不堪一击?我们常常在众多需求变化导致的功能爆炸中疲于奔命,甚至迷失自我!这到底为什么?面对"拥抱变化"这种得呐喊,我们的感受应该是震耳欲聋,还是振聋发聩?如果你仍在困惑,可以来看一看WCF是如何摆脱这种困境的!

序幕

小王效力于北京的一家系统集成公司,该公司内部有一个WCF服务为各个部门所共用,小王便是WCF服务的开发和维护人员。起初大家在一个办公楼工作,共用一个局域网,WCF运行的非常好,小王也因此获得了公司上下的一致好评。此时他们的WCF的使用状态如下图所示:

起初小王以为公司都在一个局域网之中,考虑到性能,WCF最早采用的是NetTcpBinding的绑定方式,契约部分是根据自己的业务需要开发的, 而他为服务公布的最早的地址为:net.tcp://127.0.0.1:6547/Service。他最早是用代码方式将服务托管到一个Console程序中的,托管代码如下:

ServiceHost host  =   new  ServiceHost( typeof (ServiceLib.Service)); 

NetTcpBinding bind 
=   new  NetTcpBinding(); 

Uri address 
=   new  Uri( " net.tcp://127.0.0.1:6547/Service " ); 

host.AddServiceEndpoint(
typeof (Contracts.IService), bind, address); 

而此时大家所使用的客户端代码均为: 


NetTcpBinding bind 
=   new  NetTcpBinding(); 

EndpointAddress address 
=   new  EndpointAddress( " net.tcp://127.0.0.1:6547/Service " ); 

Proxys.IService ws 
=   new  Proxys.ServiceClient(bind, address); 

这样一种情况下,小王的WCF服务与各个部门之间可谓是其乐融融。可好景不长。各位看官,请看下文:

使用范围扩大

随着近几年公司业务的持续发展,公司在唐山的业务量非常大,所以在唐山成立了一个办事处,而该办事处同样需要使用小王的WCF服务,可原本出乎小王意料的事情发生了:唐山办事处和公司总部处于不同的网络,如果还使用tcp这种传输方式,就不适用了。公司头们为这事也挺犯愁,本来以为还要劳烦小王大改一通,没想到小王却说:"小意思,马上解决问题!"他首先和公司网络管理人员联系,将WCF服务所在主机配置1个公网IP为202.120.120.3,然后再托管程序的代码中增加几行

BasicHttpBinding bind  =   new  BasicHttpBinding(); 

Uri address 
=   new  Uri( " http:// 202.120.120.3:80/Service " ); 

host.AddServiceEndpoint(
typeof (Contracts.IService), bind, address); 

然后将客户端程序场景中的代码修改如下:

BasicHttpBinding bind  =   new  BasicHttpBinding(); 

EndpointAddress address 
=   new  EndpointAddress( " http:// 202.120.120.3:80/Service " ); 

Proxys.IService ws 
=   new  Proxys.ServiceClient(bind, address); 

然后将托管程序重新发布了一下。而将修改之后的客户端部署到办事处一台服务器上面,经过测试,新的客户端运行正常,而总部那些客户端也没受任何影响。由此可见,双方相安无事,小王也松了一口气,原本另大家比较头疼的变化问题便迎刃而解了。下面是使用范围变化之后,WCF服务与客户端的工作情况:


停电了,殃及池鱼

昨天请假了,今天兴致勃勃来上班,没想到刚来,却被经理劈头盖脸的数落了一顿:" 昨天停电,来电后,你的服务没有启动,打你电话也不接,下次注意亚!",小王抓抓了头皮,不好意思的笑笑说:"好,我马上想办法,防止出现类似问题",回到座位上,小王就想到了用iis来替代console作宿主,这样机器重新启动后,服务便会自动加载,可转念一想,不对,iis支持http的访问方式,而且基地址必须和服务所在网站地址一致,我们局域网内部采用的是tcp,排除了iis,小王很快就想到了windows service,恩,就它了。所以问题也便成了下图所示的情形:

经过查证,小王先弄明白了下面的原理:

不仅如此,在不同宿主中寄宿的方法也一致:都是采用ServiceHost来进行托管。知道上面的知识,小王将原本Console宿主中 的托管代码拷贝到Windows服务的代码中,只不过,Console中托管代码写在Main方法,而Windows Service写在OnStart方法中。

秀才遇见兵

原本只是公司内部使用的WCF服务,前段时间也开放给了公司的几个大的合作商,和他们的平台作整合,前几家,合作的非常好,小王正津津乐道之时,却遇到了一个刺头,他是公司一个非常重要的合作商,但要求也非常特殊,服务中原本有个数据契约,用于描述公司订单,契约的定义如下:

旧版本数据协定
[DataContract] 

public class Order 



private string _orderID; 

[DataMember] 

public string OrderID 



get 



return _orderID; 

}
 

set 



_orderID 
= value; 

}
 

}
 

private string _content; 

[DataMember] 

public string Content 



get 



return _content; 

}
 

set 



_content 
= value; 

}
 

}
 

}
 

而且其他的合作商,对此都没有任何的异议,小王将代理已经发出去10几家了,可现在遇到这位要求必须再上面的基础上再提供订单的创建时间,小王打电话过去,和他解释:"目前我们的系统已经被很多厂商使用,而且别人的代理目前也运行的很好,你的这个需求是正确的,但我们如果修改数据契约会影响其它客户端的使用,请理解",本以为自己苦口婆心的解释,人家会买账,谁想,对方根本不听解释,一个劲地说:"这是我们的需求,必须的,必须的!"。气得小王直想摔电话。可"用户是上帝,胳膊扭不过大腿么",后来一高人指点道:"WCF在设计的时候已经考虑到了版本管理的问题,这种问题已经非常容易解决,你可以用IExtensibleDataObject",经查阅还真有此类问题的解决方案,小王将上面的契约代码更改如下:

新版本数据协定
[DataContract] 

public class Order:IExtensibleDataObject 



private string _orderID; 

[DataMember] 

public string OrderID 



get 



return _orderID; 

}
 

set 



_orderID 
= value; 

}
 

}
 

private string _content; 

[DataMember] 

public string Content 



get 



return _content; 

}
 

set 



_content 
= value; 

}
 

}
 

 

private ExtensionDataObject _exData; 

public ExtensionDataObject ExtensionData 



get 



return _exData; 

}
 

set 



_exData 
= value; 

}
 

}
 

 

private DateTime _createTime; 

public DateTime CreateTime 



get 



return _createTime; 

}
 

set 



_createTime 
= value; 

}
 

}
 

}
 

然后新生成一个代理,发给了刁蛮用户,而其他客户的代理版本却不用任何变动。新的用户的代理运行和很正常,而老的也很正常,真爽!小王此时方体会到WCF的强大之处。

凡是符合下面的变动情形的,均可视为非重大更改:

  1. 更改成员名称,且将DataMemberAttribute的Name属性和旧版本保持一致
  2. 大多数添加和移除数据成员的操作
  3. 将成员的特性IsRequired从True更改为False
  4. 实现IExtensibleDataObject接口
  5. 更改枚举成员名称,但用EnumMemberAttribute将协定名称和老版本保持一致
  6. 大多数数据集合的更改

服务也会累么?

问题是一个接一个,解决完上面的几个问题没多长时间,这个WCF又有新问题了,现在客户端越来越多,很多客户反映,最近服务端很不稳定,有时还连接不上去,小王检察了半天业务代码,发现没有问题,而且之前人数少得时候,运行的挺好的亚,此时他经过仔细的查阅,终于找到了一些用的信息:WCF为了改善性能,是有限流(Throttling)措施的。主要包括三种

  1. maxConcurrentCalls :最大并发数,默认为16
  2. maxConcurrentSessions :最大的会话数,主要针对于PerSession的情况,默认为10
  3. maxConcurrentInstances:最大实例数,默认为Int.MaxValue

默认的设置比较保守,最大并发数仅为16,非常容易造成排队或者超时现象。修改或者添加限流也非常简单,只需要在服务的配置中修改或者增加ServiceBehavior中的

< serviceThrottling  maxConcurrentCalls ="16"  maxConcurrentSessions ="10"   />

 

后记

拥抱变化,拥抱变化,我们口号喊得非常响亮,可为什么面对变化的时候,我们有时会暴跳如雷呢?一边骂客户bt,一边还要通宵达旦的加班来完成工作呢?问题的原因不在于用户,不在于变化本身,更多的是我们一味的将一堆垃圾改造成另外一堆垃圾,要想真正解决问题,做好架构,做好基础是关键。我们常常因为时间,人力等因素,刹不住车似的的制造garbage,却忘记了什么叫细节,什么叫品质。无论针对产品还是市场,当前更重视不是你做了多少,而是做了多少有意义的事情,多少别人没有做过或者做不了的事情。品质,细节,ThinkPad,Google。总之我会毫不犹豫地转向WCF,因为它有品,也更能适应变化

 

转载于:https://www.cnblogs.com/yanbinboy/archive/2008/06/05/1214401.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"java.io.IOException: Stream closed"是一个常见的错误消息,它表示在处理输入或输出流时出现了问题。在Java中,输入和输出流是用于读取和写入数据的工具。 通常情况下,抛出这个异常的原因是由于输入或输出流在操作之前已被关闭。当我们使用Java的IO类进行输入或输出操作时,我们需要按照一定的顺序正确关闭流,以避免出现此错误。 在遇到这个错误消息时,我们可以检查以下几个方面: 1. 检查是否正确地打开和关闭了输入或输出流。在使用完流之后,我们应该使用`close()`方法来关闭流。 例如,在读取文件时,我们应该使用以下代码片段: ```java try { FileInputStream file = new FileInputStream("myfile.txt"); // 读取文件的代码逻辑 } catch (IOException e) { e.printStackTrace(); } finally { try { if (file != null) { file.close(); // 关闭流 } } catch (IOException e) { e.printStackTrace(); } } ``` 2. 检查代码中是否存在多个线程尝试共享同一个流。当多个线程同时对同一个流进行操作时,可能会导致其中一个线程关闭了流,而其他线程尝试读取或写入数据时抛出"Stream closed"异常。确保在多线程环境中正确同步流的访问。 3. 检查流对象是否被重复使用。有时我们可能会在多个地方使用相同的流对象进行读写操作。如果在一次操作之后关闭了流,在后续操作中再次使用该流对象将导致"Stream closed"异常。确保每次操作都使用一个新的流对象。 总之,当遇到"java.io.IOException: Stream closed"异常时,我们应该仔细检查流的打开和关闭过程,确保在正确的时间关闭流,并避免多个线程或重复使用流对象造成的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值