Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)

时间:2022-08-21 04:36:37

Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)

效率问题

django 内部的 url 调度机制说白了就是给一张有关匹配信息的表, 这张表中有着 url -> action 的映射, 当请求到来的时候, 一个一个(遍历)去匹配. 中, 则调用 action, 产生相应数据返回; 不中, 则会产生 404 等的错误, 而 django 中有内置 404 等错误响应方法.

这种方法和 MFC 里 message map 差不多, 从项目实践(特别是配置 urls.py 文件)就可以猜到大概是这样一种工作模式.

注意上面关于 django url 调度机制的白话描述中的「一个一个」, 这里就有效率上的问题了. 倘若业务逻辑不复杂, 且访问量不高, 系统是没有问题的; 但如果业务逻辑太复杂(直观的表述是 urls.py 中的匹配条目繁杂), 如此加之高访问量, 会加重系统的负担, 试想最坏的情况是每一个请求都会从头到尾匹配一次, 让人感到不爽.

一种更好的解决方法

策略还是有的, 因为业务逻辑不会经常变更, 至少不会没几分钟就变更一次, 所以可以借助哈希表来达到快速匹配的目的. 有关哈希表可以参照之前写的一篇文章: 私房STL之hash_set和hash_map

Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)
哈希表

一般的做法举例如下:

http://example.com/aaaaa/
http://example.com/bbbbb/
http://example.com/ccccc/
http://example.com/ddddd/
http://example.com/eeeee/
abcde 表示 web 应用的功能模块.

为 aaaaa,bbbbb...都计算得到哈希值 hash(aaaaa),hash(bbbbb)...

当请求 http://example.com/aaaaa/ 到来时候, 提取 aaaaa, 计算哈希值直接在哈希表中定位匹配条目. 一般的做法是这样, 还可以顺着这样的思路继续改进. 譬如, 存在更为复杂的功能, 而且这个功能在 aaaaa 的基础上建立起来: http://example.com/aaaaa/xxxxx, 这时候, 也可以计算 hash(aaaaaxxxxx) 加入到哈希表中.

那是不是每个请求到来启动 web 应用程序的时候都要计算 urls.py 中的条目的哈希值? 并不需要, 可以新建一个 url 服务进程, 只专门维护哈希表, 从 urls.py 中计算哈希表; 通由内存映射技术, web 应用程序可以方便读取哈希表, 并且可以重复利用.

什么提到会调用 url 对应的 action, 并返回响应数据, django 是怎么返回的. 我已经在 github 备份了 Django 源码的注释: Decode-Django, 有兴趣的童鞋 fork 吧.

捣乱 2013-9-18

http://daoluan.net

 

 

反射的妙用_项目中的时间配置问题

 

内容摘要

1:阐述问题

2:分析问题,解决问题

3:演示解决方案

1:阐述问题

      有时候,我们会遇上这样一个问题:有很多条件 condition1 、condition2 、condition3、condition4 、condition5......这些条件各不相同,可能同时配置其中几个,这几个条件有一个交集,交集内部就是我们需要的。

  给一个实例吧。用户在系统中配置了一个时间条件集合,用户可以按照年、月、周或者日来配置,按照其中一种来配置,下面有很多条件可以选择,其中开始日期和时间是必须配置的,最后会形成一个xml信息存储在数据库里面,我们会用当前时间判断每个用户的配置条件,如何符合,我们把他的邮箱拿出放到一个字符串尾部,不符合则不管,最后这个字符串就是所有符合用户配置条件的邮箱集合,我们可以把我们的信息推送给这些用户。其中xml按照月配置的如下:

Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)  View Code

StartDateTime是开始时间,它是一个基本条件,每个配置信息里面都必须存在。MonthlyDOWRecurrence是代表是按照月来配置的(也可能是WeeklyRecurrence周、DailyRecurrence日等)这个xml节点下面存在很多条件,例如WhichWeek是代表哪一周,DaysOfWeek表示每周的哪一天,又或者MonthsOfYear是每年的哪几个月。

     问题就是这样,我从数据库MatchData得到所有用户的配置的数据集 Id、Email、MatchData(sql :SELECT [Id],[Email],[MatchData]FROM [MatchData].[dbo].[EmailInTime]),MatchData就是我们的配置xml信息,从集合中找出所有符合条件的用户Email。

2:分析问题,解决问题

     如果你遇上了啦,该如何处理呢?下面是我的处理方法,如果你有更好的办法,请给我留意,不吝赐教,谢谢!

     有人可以会总结一下有多少个条件,例如10个condition,然后写成是个if语句从上到下去判断是否存在这个条件,如果存在,判断是否当前时间符合这个条件。

Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)  View Code

这是一个伪代码,简单模拟了一下,大家都会发现很多问题,

1:判断是否存在这个条件在xml里面,耗性能;

2:有很多条件是不用去判断的,最好是xml配置文件里面有什么条件,我们就去判断什么条件;

3:扩展性差,当我们需要条件更多的条件的时候,需要修改已有的代码。

如何解决这三个问题呢?我们可以去遍历xml配置信息,我们遇到什么条件的时候,我们反射到封装好的的条件方法里面。遍历xml,我们就可以解决第1个问题;遇上什么条件我们判断什么条件,这个可以解决第2个问题;利用每个条件都封装成方法,遇到条件的时候反射到对应的方法,就解决了第3个问题。

3:演示解决方案

      下面我把解决方案的数据库和代码展现给大家,希望能看到你的宝贵意见!

数据库结构如下:

Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)

数据库在代码的App_Data文件夹下,下载代码可以得到。

定义好数据库后,我们看一下解决方案:

Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)

      工程下面包含三个类SqlHelper、AnalyseMatchData和MatchWithMatchData:SqlHelper类是网上下载的帮助工具类,用于操作数据库;AnalyseMatchData类只包含一个公共的方法GetMailAddressByMatchData,作为对外提供的API接口;MatchWithMatchData类是程序集内部类internal class ,因为我们一个会分层,把这下代码放在一个类库里面,这里面包含的是每个条件反射的方法。然后就是一个web.config配置文件和一个Index页面,Index页面用于测试效果,web.config配置文件里面当然就是连接字符串了啦,如果要用本人的代码就需要附加数据库和修改配置的连接字符串了,相信对大家都是小KS了。

Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)
 1         private readonly string connectionStr;
 2         private readonly string matchDataSqlStr;
 3         MatchWithMatchData matchFunction;//use to reflect the xml node to the corresponding function
 4         Type matchType;//the MatchWithMatchData class 's type
 5         XmlDocument xmldoc;
 6         DateTime nowDt;
 7 
 8         public AnalyseMatchData()
 9         {
10             connectionStr = System.Configuration.ConfigurationManager.ConnectionStrings["MatchData"].ConnectionString;
11             matchDataSqlStr = "SELECT [Id],[Email],[MatchData]FROM [MatchData].[dbo].[EmailInTime]";
12             matchFunction = new MatchWithMatchData();
13             matchType = matchFunction.GetType();
14             xmldoc = new XmlDocument();
15         }
Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)

首先是定义需要的使用的全局变量,然后再构造函数里面初始化变量,细心的朋友会看到当前时间nowDt变量没有初始化,因为应该放在后面的api接口里面,当前时间应该是用户调用接口的时间。

Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)
 1         /// <summary>
 2         /// the api to get the email address of  match all conditions 
 3         /// </summary>
 4         /// <returns></returns>
 5         public string GetMailAddressByMatchData()
 6         {
 7             StringBuilder emailAddresses = new StringBuilder();
 8             nowDt = Convert.ToDateTime("2013-06-26T22:15:00.000+08:00");//DateTime.Now;//
 9             using (SqlDataReader dr = SqlHelper.ExecuteReader(connectionStr, CommandType.Text, matchDataSqlStr))
10             {
11                 while (dr.Read())
12                 {
13                     string matchData = dr.GetString(dr.GetOrdinal("MatchData"));
14                     if (IsMatchWithMatchData(matchData, nowDt))
15                     {
16                         string emails = dr.GetString(dr.GetOrdinal("Email"));
17                         emailAddresses.Append(string.IsNullOrWhiteSpace(emails) ? string.Empty : ";" + emails);
18                     }
19                 }
20             }
21             return emailAddresses.Length > 0 ? emailAddresses.Remove(0, 1).ToString() : string.Empty;
22         }
23         /// <summary>
24         /// judge whether the current time match all conditions in the xml string 'matchData'
25         /// </summary>
26         /// <param name="matchData"></param>
27         /// <param name="nowDt"></param>
28         /// <returns></returns>
29         private bool IsMatchWithMatchData(string matchData, DateTime nowDt)
30         {
31             bool result = true;
32             xmldoc.LoadXml(matchData);
33             try
34             {
35                 XmlNodeList xmllist = xmldoc.SelectSingleNode("ScheduleDefinition").ChildNodes;
36                 DateTime startDt = Convert.ToDateTime(xmllist[0].InnerText).ToLocalTime();
37                 matchFunction.InitData(startDt, nowDt);
38                 foreach (XmlNode xmlnode in xmllist)
39                 {
40                     MethodInfo method = matchType.GetMethod(xmlnode.Name + "Function", BindingFlags.Instance | BindingFlags.Public);
41                     object oj = method.Invoke(matchFunction, new object[] { xmlnode.OuterXml });
42                     if (result)
43                         result = result && Convert.ToBoolean(oj);
44                     else
45                         break;
46                 }
47                 return result;
48             }
49             catch
50             {
51                 return false;
52             }
53         }
Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)
    GetMailAddressByMatchData方法是对应的 api接口他需要遍历从数据库里面得到的数据集,最后把符合条件的email地址返回给调用方。其中比较关键的是IsMatchWithMatchData方法用于判断xml配置条件和当前的时间nowDt的比较,如何前期时间在配置的时间条件内返回为真true否则为假false。遍历xml节点,反射到对应的方法,方法的名称是很有讲究的,是节点名称后面加一个后缀xmlnode.Name + "Function"。
这样我们达到了业务的分离,每一个方法表示一个条件业务,需要扩展的时候,新增一种xml节点类型,然后新添加一个方法,其他的一概不用管,是不是很方便!
有人说反射消耗性能,何不使用switch想简单工厂一样,遇到什么节点调用什么方法。其实我不太提倡这种方式:
1:扩张性没有反射好,扩展的时候需要修改代码,不符合开闭原则;
2:xml现在是两层条件,如果用switch,需要嵌套地使用switch了,如果以后变成三层的呢?所有灵活度也不如反射。
3:其实现在反射的性能愈来愈好啦,几乎和一般的方式调用很接近了。我曾使用一万六千多条在3秒内完成,相信我们的员工没有这么多吧!也可以做一些其他的优化,例如利用缓冲,还有及时条件中只要一个条件为false,就直接返回为假,不用检测所有的XML里面的条件,相信你有更多的方式优化它。
反射的方法类MatchWithMatchData里面:
Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)  View Code

 

   比较有意思的是WhichWeekFunction方法,判断当前日期是哪一周,MonthlyDOWRecurrenceFunction等方法下面,还有条件,又使用了反射的方式调用。整体是如此简单,如此容易理解。测试的时候,把DateTime.Now;//Convert.ToDateTime("2013-06-26T22:15:00.000+08:00");//改成Convert.ToDateTime("2013-06-26T22:15:00.000+08:00");//DateTime.Now;//,这是一个小技巧。这个Convert.ToDateTime("2013-06-26T22:15:00.000+08:00");测试用例下面有数据,总体跟踪一遍,就能理解这个代码
   最后祝大家好运,希望在能够帮助大家,也希望大家能多提意见,觉得有收获的帮助顶一下。
代码下载:http://files.cnblogs.com/zhangxl/MatchData.zip
文章出处:http://www.cnblogs.com/zhangxl/p/reflection.html 

 
 
   
 
  
 
 
 

 

 

 

 
标签:  源码剖析Django