性能优化总结(五):CSLA服务端如何使用多线程的解决方案

简介:

  前篇说到了使用异步线程来实现数据的预加载,以提高系统性能。

    这样的操作一般是在客户端执行,用以减少用户的等待时间。客户端发送多次异步请求,到达服务端后,如果服务端不支持多线程处理操作,线性处理各个请求,必然导致客户端的异步请求变得没有意义。

    大家肯定会说,谁会把服务端设计成单线程的啊,那不是明显的错误吗?是的!但是我们的系统使用了CSLA来作为实现分布式的框架,而它的服务端程序却只能支持单线程……这个问题我们一直想解决,但是查过CSLA官方论坛,作者说由于GlobalContext和ClientContext的一些原因,暂时不支持多线程。火大,这还怎么用啊!无奈目前系统已经极大地依赖了这个框架,一时半会儿要想换一个新的,也不太现实。所以只好自己动手修改CSLA里面的代码了:

 

修改WCF通信类

    要修改为多线程的服务端,首先得从服务端的请求处理处入手。.NET3.5的CSLA框架使用WCF实现数据传输。它在服务器端使用这个类来接收:

1
2
3
4
5
6
namespace  Csla.Server.Hosts
{
     [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
     [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
     public  class  WcfPortal : IWcfPortal { }
}

可以看到,这个类没有标注ConcurrencyMode = ConcurrencyMode.Multiple和UseSynchronizationContext = false,所以已经被设计为单线程操作。在这里,我们使用装饰模式来构造一个新的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// <summary>
/// 标记了ConcurrencyMode = ConcurrencyMode.Multiple
/// 来表示多线程进行
/// </summary>
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,
ConcurrencyMode = ConcurrencyMode.Multiple,
UseSynchronizationContext = false )]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public  class  MultiThreadsWCFPortal : IWcfPortal
{
     private  WcfPortal _innerPortal = new  WcfPortal();
     #region IWcfPortal Members
     public  WcfResponse Create(CreateRequest request)
     {
         return  this ._innerPortal.Create(request);
     }
     //...
     #endregion
}

同时,我们需要把配置文件和类的实例化两处代码都替换:

app.config:

1
< services >
1
<!--Csla.Server.Hosts.WcfPortal-->
1
2
3
4
     < service  name="OpenExpressApp.Server.WPFHost.MultiThreadsWCFPortal" behaviorConfiguration="returnFaults">
.....
</ service >
</ services >

 

factory method:

1
2
3
4
5
private  static  Type GetServerHostType()
{
     return  typeof (OpenExpressApp.Server.WPFHost.MultiThreadsWCFPortal);
     //return typeof(Csla.Server.Hosts.WcfPortal);
}

这样,在服务端接收到请求时,会自动开启多个线程来响应请求。同时,装饰模式的使用使得我们不需要对源代码进行任何更改。

 

修改ApplicationContext._principal字段

    按照上面的操作修改之后,已经在WCF级别上实现了多线程。但是当再次运行应用程序时,会抛出NullRefrenceException异常。代码出现在这里:

1
2
var  currentIdentity = Csla.ApplicationContext.User.Identity as  OEAIdentity;
currentIdentity.GetDataPermissionExpr(businessObjectId);

调试发现,Csla.ApplicationContext.User是一个UnauthenticatedIdentity的实例。可是我们已经登录了,这个属性为什么还是“未授权”呢?查看源代码,发现每次在处理请求的开始阶段,CSLA会设置这个属性为客户端传入的用户标识。那么我们来看这个属性在CSLA中的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private  static  IPrincipal _principal;
public  static  IPrincipal User
{
     get
     {
        IPrincipal current;
        if  (HttpContext.Current != null )
            current = HttpContext.Current.User;
         else  if  (System.Windows.Application.Current != null )
        {
            if  (_principal == null )
            {
                if  (ApplicationContext.AuthenticationType != "Windows" )
                    _principal = new  Csla.Security.UnauthenticatedPrincipal();
                else
                    _principal = new  WindowsPrincipal(WindowsIdentity.GetCurrent());
            }
            current = _principal;
        }
        else
            current = Thread.CurrentPrincipal;
        return  current;
     }
     set
     {
        if  (HttpContext.Current != null )
            HttpContext.Current.User = value;
        else  if  (System.Windows.Application.Current != null )
            _principal = value;
        Thread.CurrentPrincipal = value;
     }
}

代码中显示,如果服务端使用的是WPF应用程序时,就使用一个静态字段保存当前的用户。这就是说服务端的所有线程都只能获取到最后一个请求的用户,当然就不能提供多线程的服务!这里,其实是作者的一个小BUG:他认为使用WPF的程序应该就是客户端,所以直接存储在静态变量中。但是我们的服务端也是WPF来实现的,所以就导致了无法为每个线程使用独立的数据。

这个类同时被客户端和服务端所使用,所以改动不能影响客户端的正常使用。为了最少地改动原有代码,我把字段的代码修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[ThreadStatic]
private  static  IPrincipal __principalThreadSafe;
private  static  IPrincipal __principal;
private  static  IPrincipal _principal
{
     get
     {
        return  _executionLocation == ExecutionLocations.Client ? __principal : __principalThreadSafe;
     }
     set
     {
        if  (_executionLocation == ExecutionLocations.Client)
        {
            __principal = value;
        }
        else
        {
            __principalThreadSafe = value;
        }
     }
}

这里把原来的字段变为了一个属性!实现它时,如果是在客户端,还是使用一个一般的静态字段。如果是在服务端时,就换成了一个标记了[ThreadStatic]的字段,该标记表示:这个字段会为每一个线程分配独立的值。这样,服务端在请求被处理的开始阶段对_principal赋值时,就存储在了当前线程中,而不会影响其它线程。

 

手动开启的线程

    上面已经解决了两个问题:1、默认没有打开多线程;2、多个线程对ApplicationContext.User类赋值时,使用静态字段导致值的冲突。

    这样就高枕无忧了吗?答案是不!:)

    这样只是保证了WCF用于处理请求的线程中,ApplicationContext.User属性的值是正确的。但是我们在处理一个单独的请求时,又很有可能手工打开更多的线程来为它服务。这些线程的ApplicationContext.User字段并没有被CSLA框架赋值,如果这时使用到它时,又会出现NullRefrenceException……

    由于我们进行异步处理时的代码都是经过一层细微的封装的,所以这时候好处就体现出来了。我们的处理方案是,在手工申请异步执行的方法实现中,为传入的异步操作加一层“包裹器”,例如下面这个API,它是用来给客户程序调用异步操作的,当时只是封装了线程池的简单调用,为的就是方便将来做扩展(例如我们可以改为Task来实现……)。

1
2
3
4
public  static  void  SafeInvoke(Action action)
{
     ThreadPool.QueueUserWorkItem(o => action());
}

我们添加了一个扩展方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/// <summary>
/// 这里生成的wrapper会保证,在执行action前后,新开的线程和主线程都使用同一个Principel。
///
/// 解决问题:
/// 由于ApplicationContext.User是基于线程的,
/// 所以如果在同一次请求中,如果在服务端打开一个新的线程做一定的事情,
/// 这个新开的线程可能会和打开者使用不同的Principle而造成代码异常。
/// </summary>
/// <param name="action">
/// 可能会使用ApplicationContext.User,并需要在服务端另开线程来执行的操作。
/// </param>
/// <returns></returns>
public  static  Action AsynPrincipleWrapper( this  Action action)
{
     if  (ApplicationContext.ExecutionLocation == ApplicationContext.ExecutionLocations.Client)
     {
        return  action;
     }
     var  principelNeed = ApplicationContext.User;
     return  () =>
     {
        var  oldPrincipel = ApplicationContext.User;
        if  (oldPrincipel != principelNeed)
        {
            ApplicationContext.User = principelNeed;
        }
        try
        {
            action();
        }
        finally
        {
            if  (oldPrincipel != principelNeed)
            {
                ApplicationContext.User = oldPrincipel;
            }
        }
     };
}

原来的API改为:

1
2
3
4
5
public  static  void  SafeInvoke(Action action)
{
     action = action.AsynPrincipleWrapper();
     ThreadPool.QueueUserWorkItem(o => action());
}
这样就实现了:手工打开的线程,使用和打开者线程相同的一个ApplicationContext.User。

小结

    本文主要介绍了如何把CSLA框架的服务端打造为支持多线程。可能会对使用CSLA框架的朋友会有所帮助。

    下一篇应用一个在GIX4项目中的实例,说明一下在具体项目中如何应用这几篇文章中提到的方法。

目录
相关文章
|
8月前
|
缓存 NoSQL Java
后端接口性能优化分析-多线程优化(中)
后端接口性能优化分析-多线程优化
146 0
|
8月前
|
消息中间件 存储 监控
后端接口性能优化分析-多线程优化(上)
后端接口性能优化分析-多线程优化
249 0
|
8月前
|
NoSQL 算法 Java
后端接口性能优化分析-程序结构优化(中)
后端接口性能优化分析-程序结构优化
81 0
|
Arthas 运维 Java
arthas 的使用场景、优点和缺点
Arthas 是Alibaba开源的Java诊断工具,它可以帮助开发人员或者运维人员查找问题、分析性能和bug追踪。以下是Arthas的一些使用场景: 1. 查看目标服务器应用程序的JVM信息。 2. 方法性能的排查和跟踪。例如,在实际使用过程中发现某个接口很耗时,但是无法在本地环境复现的时候,可以通过Arthas的trace来跟踪,它会输出方法内部路径每个节点的耗时。 3. 查找全局视角查看系统的运行状况、健康状况的信息。 4. 反编译源码,查看JVM加载的是否为预期的文件内容。
686 0
|
8月前
|
NoSQL Java Redis
后端接口性能优化分析-程序结构优化(上)
后端接口性能优化分析-程序结构优化
205 0
|
4月前
|
JavaScript 前端开发 数据库
优化后端性能:如何使用异步编程提升系统响应速度
异步编程已成为现代后端系统性能优化的重要策略。通过避免阻塞操作,异步编程可以显著提高系统的响应速度和并发处理能力。本文章深入探讨了异步编程的基本概念,比较了常见的异步编程模型,并通过实际案例演示如何在Node.js和Python中实现异步操作,以提升系统性能。
|
5月前
|
缓存 监控 中间件
构建高效的Go语言Web服务器:基于Fiber框架的性能优化实践
在追求极致性能的Web开发领域,Go语言(Golang)凭借其高效的并发处理能力、垃圾回收机制及简洁的语法赢得了广泛的青睐。本文不同于传统的性能优化教程,将深入剖析如何在Go语言环境下,利用Fiber这一高性能Web框架,通过精细化配置、并发策略调整及代码层面的微优化,构建出既快速又稳定的Web服务器。通过实际案例与性能测试数据对比,揭示一系列非直觉但极为有效的优化技巧,助力开发者在快节奏的互联网环境中抢占先机。
|
6月前
|
安全 NoSQL PHP
Laravel框架进阶:掌握队列系统,优化应用性能
Laravel框架进阶:掌握队列系统,优化应用性能
73 0
|
6月前
|
设计模式 存储 缓存
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
70 0
|
8月前
|
安全 中间件 Go
Go语言Web服务性能优化与安全实践
【2月更文挑战第21天】本文将深入探讨Go语言在Web服务性能优化与安全实践方面的应用。通过介绍性能优化策略、并发编程模型以及安全加固措施,帮助读者理解并提升Go语言Web服务的性能表现与安全防护能力。

热门文章

最新文章

下一篇
开通oss服务