因为服务都是受HA管理的,我们通常会用HAConsole发消息给HACenter,HACenter管理多个HAWorker,创建的这个类,就是一个worker。
ServiceSettings.InitService(serverName);
SipcStack.Initialize();--协议栈
ServiceHelper.Initialize();
RouteDirector.Initialize();--路由
最大连接数设置
ServicePointManager.DefaultConnectionLimit= IDCSConfig.Current.DefaultConnectionLimit;
ServicePointManager.SetTcpKeepAlive(true,IDCSConfig.Current.KeepLiveTime,
IDCSConfig.Current.IntervalTime);
////注册RPC通道,server,其他服务调用自己会用到
RpcSipcServerChannel _sipChannel = newRpcSipcServerChannel(IPAddress.Parse("0.0.0.0"),ServiceSettings.Current.RpcServerPort);
RpcServiceManager.RegisterServerChannel(_sipChannel);
////注册client通道,调用其他服务会用到
RpcProxyFactory.RegisterClientChannel(newRpcSipcClientChannel());
//RpcHttpServerChannel_httpChannel = newRpcHttpServerChannel(ServiceSettings.Current.HttpServerPort);
//RpcServiceManager.RegisterServerChannel(_httpChannel);
RpcServiceManager.RegisterService<IIDCService>(new IDCService());
RpcServiceManager.Start();
先写一个类,对应HA里面的配置节。例如这个类:
[IICConfigSection("IDCService")]
public class IDCSConfig : IICConfigSection
{
private static object _syncObject = newobject();
private static IDCSConfig _current;
public static IDCSConfig Current
{
get
{
if (_current == null)
{
lock (_syncObject)
{
if (_current == null)
{
_current =IICConfigurationManager.Configurator.GetConfigSecion<IDCSConfig>(
"IDCService",
delegate(IDCSConfig settings)
{
_current =settings;
}
);
}
}
}
return _current;
}
}
[IICConfigField("ClientId",DefaultValue = 0)]
public int ClientId;
[IICConfigField("DefaultConnectionLimit", DefaultValue = 500)]
public int DefaultConnectionLimit;
[IICConfigField("KeepLiveTime", DefaultValue = 30000000)]
public int KeepLiveTime;
[IICConfigField("IntervalTime", DefaultValue = 2000000)]
public int IntervalTime;
[IICConfigField("EnableAESKey", DefaultValue = false)]
public bool EnableAESKey;
[IICConfigField("EnableSubscribe", DefaultValue = false)]
public bool EnableSubscribe;
[IICConfigField("EnableListener", DefaultValue = false)]
public bool EnableListener;
[IICConfigField("EnableEncrypt", DefaultValue = false)]
public bool EnableEncrypt;
[IICConfigField("SSODomains",DefaultValue = "")]
public string SSODomains;
[IICConfigField("IDCAuthKey",DefaultValue = "")]
public string IDCAuthKey;
[IICConfigField("EnableRmsMsg", DefaultValue = false)]
public bool EnableRmsMsg;
[IICConfigField("MailClientId", DefaultValue ="01")]
public string MailClientId;
[IICConfigField("MailAppKey",DefaultValue = "mail_user")]
public string MailAppKey;
[IICConfigField("MailAppSecret", DefaultValue ="123456")]
public string MailAppSecret;
[IICConfigItemCollection("Url")]
publicIICConfigItemCollection<string, IDCUrlConfigItem> Urls;
[IICConfigItemCollection("SqApi")]
publicIICConfigItemCollection<string, IDCSqApiConfigItem> SqApi;
}
[IICConfigItem("Url", KeyField = "Key")]
public class IDCUrlConfigItem : IICConfigItem
{
[IICConfigField("Key")]
public string Key;
[IICConfigField("Value")]
public string Value;
}
[IICConfigItem("SqApi", KeyField = "Key")]
public class IDCSqApiConfigItem : IICConfigItem
{
[IICConfigField("Key")]
public string Key;
[IICConfigField("Value")]
public string Value;
}
打开app.config,把RunMode设成local。尽量本地调试,一步一步跟,毕竟这是最直接最高效的排查问题的方式。
但是有些情况下,本地会无法调试,比如你挂了一个EFS的服务,它初始化的时候肯定会去连功能,这时候连不上就会报错。本地没法调试,只能重现问题,看日志了。
可以针对各个层级的代码进行测试,功能上也有保障,而且在技术调研、功能测试都可以写单元测试来完成。
l 例如想测试WEB服务是否工作,可以模拟客户端写一个发请求的页面。
l 再如想重现WEB BAR或者WEB IM的一个问题,就可以用winform写一个模拟客户端(因为我认为这个交互性还是比较强的,单元测试模拟起来和场景有偏差,并且写一个工具今后遇到类似的问题就不用再写单元测试代码了)模拟客户端的操作。
l 构造自己的工具,可以重现出问题,然后可以再查日志或者跟踪代码找原因,再解决问题。
l 可以在开发过程中,积累越来越多的工具(例如http发请求工具,http挡板),简化今后的调试工作,有时也会给测试带来好处。
当问题出现,首先是通过看日志能否找到原因,如果找不到再自己想办法重现问题(通过工具模拟客户端操作),重新找原因。当问题重现的时候,看一下日志为什么没有记,可以把这部分的日志追加上,下次遇到相同的问题就节省的时间。
所有异常的地方一定要记error级别的log
尽量多记trace。因为服务无论部署到功能环境还是现网(尤其是现网,日志记录不充分就只能通过抓dump文件来排查问题了),调试是个问题,所以诊断问题通常就靠记的trace。记录日志也不要太过频繁,因为太频繁的I/O操作会影响性能。
不是异常就尽量不要记error级别的log。如果一个业务上的错误是正常允许的并且是频繁发生的,可以记成Warning级别的,如果记成error就会导致巡检里面大片的异常。之前是IDC网盘订购失败了,记成了error级别的错误,结果在巡检上面出现大片异常。
XML序列化/反序列化。功能上来讲,可以实现XML—实体类之间的映射,并且性能也还可以,10000/3-5秒左右。不过100000次以上序列化的话直接崩溃,如果选择用字符串去拼的话300000次打开10秒左右。所以高并发的情况下,还是要用原始的拼字符串的方法。各有利弊,序列化维护起来容易点,改实体就行了。
HttpListener。可以在服务中起一个监听,监听Http请求,这部分一开始用的MSDN的demo,自己扩展了一下,发现性能太差了,并且很不稳定,目前用的离线文件传输的代码,性能上也不错,大概可以支持1000个请求/每秒。有需要的也可以参考一下。
在routePolicy里面检查是否配置了路由策略,确保EFS可以找到自己。
例如IDCS一开始只配置了srv,后来加的id。至于是id还是srv,要看你订阅事件的生产者,比如是从CS那里订阅了一个事件,那么它用的是id就是id。具体要看生产者那个服务的代码。
这个配置完了,要跟运维说刷一下配置。
还要注意一点,别忘了更新SiteB上面的路由策略配置,否则在巡检平台上面刘宝那里会报一大堆EFS找不到目标服务的异常。
在EFS的配置节中检查是否正确的配置。例如我订阅了RMS事件,就需要去EFS里面配置一下:(在ToServices里面,追加一个IDCS服务即可)
EFS配置完了,需要重启EFS服务。
// "EfsSubscribeArgs"
string[] efsEventNameList = newstring[] { "RmsMessageEvent" };
EventConsumer.Initialize(efsEventNameList);
EventConsumer.RegisterHandler<RmsMessageArgs>(efsEventNameList[0],Singleton<AddMsgHandler>.Instance.Handle);
//EventConsumer.RegisterHandler<EfsSubscribeArgs>(efsEventNameList[1],Singleton<AddMsgHandler>.Instance.Handle);
EventConsumer.Start();
Tracing.InfoFmt("eventconsumer started");
l 作为互联网应用的服务端,涉及到调服务,发http请求这些操作,都需要写成异步的。同步和异步的差别是相当明显的,曾经就是因为同步,导致一个任务hang死,其他请求进不来,不得不重构将近60%的代码,把同步改成了异步。
l 即使是挡板,记得也要做成异步的。不要觉得挡板无所谓,写一个固定返回值的放在那里就ok了,我第一次做挡板就写成了同步,结果压力一直上不去,只压到了500,当时cpu都已经70%了,后来仔细检查代码发现,挡板是同步的,改成了异步问题解决,1300/每秒,cpu 60%。
彩云项目里面有两个技术点:
l 发Http请求
已有了EA这个项目,由于EA也是久经考验的项目了,所以第一选择一定是把EA里面的发请求部分搬进来使用,这样节省了时间也降低了风险,还提高了性能。当时还是选择了技术调研,自己绕了大弯子,最后还是选择重用EA的代码。
另外海涛提的建议不错,在此分享一下,当给第三方发请求的时候,把请求时间放header里面发过去。
l 接收http请求
对于接收http请求这部分,传离线文件的代码也是现成的,并且支持了1000个以上的并发,所以可以直接搬过来用的,当时也走了弯路。
和第三方打交道,一个请求慢了,究竟是哪边慢,最好是记录一下请求过去的时间和对方应答的时间。如果没记,就只有抓包了(这次和岳元就上了19层抓包)。同样,如果涉及到客户端访问自己,也记录一下收到请求的时间和应答的时间。这样可以找到处理慢的原因在哪里,再去做相关优化。
提到了异步编程,不得不提异常捕获。异常捕获可以一句话概括,一定要记得捕捉到所有的异常。比如异步监听,新来一个请求就会新起一个线程去处理它,那么在这个新起的线程里面一定要捕获异常,因为外层的try –catch捕捉不到新线程里面的异常,出异常了就会直接crash。
另外,记得所有地方一定要记得回调,因为是异步编程,函数口都会给一个callback,如果不回调,它就会一直hang住。除了异常没捕捉,没有回调也是异步编程很危险的一个地方。
检查dll列表和现网是否一致(尤其是引入了新的dll的时候,如ssi,aspnetCommon等),如果不确定,就让现网运维抓一个dll列表下来吧,核对了再上线,上次IDCS因为这个来回折腾了两次。
整理一下代码里都调用了哪些服务(比如调用了IBS),哪些服务调用了自己的服务(比较典型的例子,EFS),这些都需要在现网配置相应的路由策略。比如上次IDCS没有提这些,导致现网运维在这上面浪费了两个小时。
如果新加了配置,先在配置节模板里面添加一下,更新配置节。最好养成一个习惯,每增加一个配置,都记在文档里面,上线的时候把这个文档递给运维就行了。
Review dll。检查一下代码的版本,以及异常是否全部捕捉。