百万级的微服务基础架构

其实这篇文章应该是五月份就已经进入了draft模式,年底梳理发现还有未完成的,所以应该今年事,今年毕。很多互联网的初创公司,一版融完了A轮或者进入B轮的时期,系统大多都是千疮百孔的,顶着快速增长的压力,码农们各种开始深夜修服务器。从万般紧凑的进度计划中,排列出治理的重构方案。发现痛点最多还是基础的用户和认证,其次是基础的接入层服务,之后再是运维管理平台。恰好也帮忙梳理过一些初创公司的痛,在此纪录下个人的一些心得体会。

初创的公司,场景大多要求,app,web,mobile都提供不同程度的服务,一般雄厚些的公司大多上来就以APP为主,android和ios并行前进,辅助以简单的web页面版本和mobile页面版本,再接入微信的导流,基本上就是一个初创公司所有的用户入口,偶然有一些特殊的B端定制化的pad之类要求,基础层不动,基本可以完全覆盖。

基础的服务设计,离不开原始的概念设计图,所有的设计,也都离不开业务的需求场景。所以任何抛开了基础业务场景的设计,都是在耍流氓,以下是以如上的概念场景,来进行的设计。基本的思路很简单,拆离基础的公有服务,做成微服务,以RESTFUL的接口互相交互,并且所有服务之间无状态,可以几乎各自独立的进行水平扩展。

原来玩MS体系技术的时候,画图基本上都是要求五图并出的,并且需要各自呼应,不能有缺失和矛盾,从不同的层面去阐述系统的整体设计思路,便于后期的细化。可惜创业公司大多飞奔前进,先于设计出代码,或者说完全由主负责通过自己的代码和经验去控制设计,高手固然可行,可惜高手少之又少,团队协作需要沟通理解的东西多之又多,于是各种先上,再补,再说的情况不绝。资本推动固然快准狠,不过欠下的债始终是要还的,至于什么时候还,呵呵,ceo大多说等这轮完了。

快速成长的互联网公司基本是不会留出独立的时间给技术团队进行架构的梳理和重构的,于是时间只能从各个feature当中来挤,码农基本通过加班来搞定,最为头疼的问题就是挤的过程当中的前后兼容,不过路总是人走出来的,大量的兼容代码在迭代的过程当中不绝于眼。再恶劣的环境一般也都能解,所以高手一般都是留有注释的,方便以后修,代码治理最好的套路也就是如此了。
简要的One Page Design可以快速的协调开发组对整体技术套路的理解,让大家朝着统一的方向前进,其实可以不用写的很复杂,能够让大家快速的的保持一致,才是关键。

arch1

系统概念设计图说明
 核心内容将通过triple store进行数据存储,并且依据自定义的规则生成对应的内容,进入业务数据库,供平台使用
 数据来源主要通过传感设备以及移动设备采集,经过数据筛选和数据加工,进入临时的内容数据库,平台依据规则有选择的进行MQTT标准化,存储至triple Store
 核心业务层平台主要负责处理复杂的业务逻辑,并且提供对应的流程和job的调度处理。
 业务平台将采用NODEJS进行构建,并且采用三端合一的Restful进行实现,方便终端各级的接入。

arch2

系统层次图设计说明
 终端用户目前需要支持移动端,微信,移动站点和PC站点的全面覆盖。
 核心的API层主要由RESTFUL框架进行封装,核心语言采用NODEJS,框架采用目前比较成熟的Express
 认证采用目前BEARER TOKEN的OAuth认证,并且采用统一的中间件在各个系统里面通过middleware进行快速集成
 业务流程和队列系统采用云平台进行设计和实现,对复杂业务的加工,统一经由队列消费worker进行处理。
 中间系统需要接入互联网基础的运维平台,持续集成平台,运营数据分析平台
 后部triple store的数据规则控制,数据分析采用标准的JENA协议。

arch3

物理结构图设计说明
 系统目前提供互联网接入的口径为pc端,移动端,以及微信客户端,后期会陆续加入app端的内容。
 接入主要提供主站和移动站。
 内部系统避免多个单点故障出现,所有的服务节点均采用负载多点方式
 后部业务数据库采用k/v和关系型数据库混合的模式,k/v采用目前较为成熟的MONGODB产品,主从心跳线三机热备切换模式,关系型采用MYSQL一主三从读写分离模式。
 Triple Store 产品采用你想要的任意

arch4
系统依赖图设计说明
 对于数据统计和分析,在app上线依赖目前主要的UMENG平台,
 对于微信端的账号绑定验证,需要依赖短信网关供应商
 平台部分的需要依赖cdn对js,css,图片等资源做加速
 对用户页面停留时间,pv,uv的统计,以及后期的搜索引擎优化,需要接入百度以及google的API
 内容处理队列采用云端产品。

互联网入门三套车

话说央妈都开始推互联网了,于是资本,人员等各种资源开始疯狂的涌入。但从一个技术民工的角度来看,涌入进来的弄局者,讨论到最后的结果基本都是,我们只差一个码农写码了。但是真正的互联网是怎么玩儿的,局外人们也只是看着互联网企业的惊人成长速度而管窥狸豹而已。其实真正能认识,并且相信套体系的boss应该没几个,尤其是想转型的传统企业,尤为困难,并不是简单的买一堆服务器,养几个死贵的码农那么简单的事情。于是有着各种大互联网公司的从业人员们一时洛阳纸贵。原来的各种行业的人才也开始涌入这个领域开始折腾,于是各种bat的墙角变成了市场上的香饽饽。阿猫阿狗齐上阵的时代开始到来。

互联网企业,所依赖的快速迭代,快速响应,一切以用户需求为主旨的背后,不可离开的线上三大体系,目前的巨头们,基本都是体系成熟,并且开始玩四个体系的细分领域和外延部分了。
1,运维监控体系
2,内容运营体系
3,数据化营销体系
4,持续迭代发布体系

所以衡量一个互联网公司估值价值的指标,从技术数据层面,依赖于这三个体系的正常运转,不过许多大boss可能第一次见投资人还基本对一些衡量的指标保持懵逼的状态。或者说保持一个不关注的状态,不过不同的领域也许依赖和衡量的指标貌似不尽相同,从技术角度来说,基本如下:
1,日活,用户量
2,次日留存,月留存
3,qps,tps
4,交易量,流水量

那对于各个体系,是如何去影响这些指标和辅助强化这些指标的呢?大致粗浅的理解如下:

运维监控体系:这个体系实际上是从前到后的一套完整的对线上服务器硬件,以及业务关键程序运行的健康度的监控,所以说得从两个维度来看这个东西。

系统角度:对于每天看着服务器的运维工程师来说,大规模的集群,基本上都采用了统一化的管理,linux上面对于集成部署和监控,有很多开源的组件,目前我们用的比较多的是salt和ansible,基本可以conver大部分的集成化运作指令,设想,一百台级别的服务器,需要随时发布,更新,以及监控各个服务器的cpu,内存,硬盘的占用情况。shell利器不可不用。现在云化的时代,对服务器的cpu,等硬件设备的监控大多云平台都带,几乎也都好使,不够的可以用云平台的接口做一个嘛。

业务程序角度:对于业务这块的运维监控,会比系统的要复杂得多,各个服务集群之间,所关注的业务点都不一样,为服务器化的集群,最主要的还是看http的响应数量和响应时间,这块我们大致会采用开源falcon来进行定制,分服务,分api,分页面来进行关键性的监控。例如,我们比较关注用户的注册量或者报名量,那么我们必然监控注册和报名的页面或者api的的请求量。单独出一个较为独立的图表。

内容运营体系:这个体系实际上决定了很多大多数终端用户看到的东西,比如说今天做个活动,明天做个活动,今天花式发个卷什么的,所以大多的内容运营平台几乎也都是按照系统的场景来定制化开发的,内容需要能够每天秒更新自己的活动和创意,而系统也必须秒支持。我记得有个典故,我们pm说这是一个cms,然后又加需求说这个东西其实是一个带crm的cms,然后这又是一个运维管理后台还要有审核等流程的机制,然后开发组就狂暴了,你们要的到底是尖椒炒肉,还是鱼香肉丝。所以从一个侧面说明这块东西的复杂程度。

数据化营销体系:这就是大家传说中的大数据了,不过大多数公司的数据都不能称为大数据吧,不过这块由于数据量巨大,还是需要一些大数据的基础基础来支持的。很多时候pm上了一个新feature,市场做了一个新活动,想要知道效果,比如和交易量相关的指标上升了,pm也得说出个理由来。或者说,用来说服码农全天秒出新feature的理由,也是由这些数据来支撑的。
例如,pm和dev leader死磕,说这个东西上了没什么卵用,于是乎pm说到时候我拿数据和你说话,dev leader于是带着项目组周日加班干出来这个新feature,上线了,然后跑了一个星期,然后告诉开发组,你看,我们的这个新feature导致我们的app访问时常增长了10%,dev于是哑口无言,开发们也乐得屁颠屁颠的,俺们写的东西终于是有用的啊,于是乎下一个feature继续接踵而至。当然,也有pm被打脸的时候,基本上就是pm买瓜子买水,捏背,请吃饭的节奏。
这些报表的基础数据,就来源于数据化营销的基础平台数据。初期落地到系统当中的大多就是日志和关键点的埋点数据。重要页面的action几乎都需要埋点,日志请求基本也都是全记录的。一天动辄上几十个个T的数据,大多都用hadoop家族系列产品。到了后期的数据分析组,技术就会用得比较复杂,大多都是混合型的技术架构,而且还有复杂的数据专家坐镇,直接影响领导层的战略调整。记得有个女同学做这个的叫什么来着,大家都叫她“统计学之母”。

持续迭代体系:很多人一看这个就会说,不就是一个jenkins和gitlab-ci这样一些产品嘛,但是貌似具体落地起来并不是一个持续化集成体系能搞定的事情,快速的迭代,需要PM(项目经理和产品经理目前互联网公司是复合型的,如果在你的公司的这两个角色不是复合型的,那么只能呵呵了)非常清晰的了解每个阶段需要快速实现的功能,不要问为疯狂的pm为啥老加班,他们需要控制整个产品的内容,feature上线的时间,开发资源的调配,市场的反馈,数据分析模型的建立,以达到最快的速度响应客户的需求,做到快速的调整。这个内容之所以是一套系统,在于它就是一个企业快速更新的基础。几乎涉及到整条业务线的调整优化。所以牛x的pm一个迭代周期基本是1-2周,而落地到具体的内容,就是码农的代码写完,测试,发布到线上,并且周知相关的各种人员。jenkins在具体的落地的过程当中可以扮演很多关键的角色,如包的自动化测试,编译,灰度的发布。极大的简化运维人员的工作负担。但是对于超大的交叉型互相依赖的线上项目,一个jk估计就没法满足需求了,具体的可以看下阿里发布的云端持续化发布平台,感觉屌屌的,几乎也是一套复合型的框架,不过不一定能够满足你们公司的场景。

以上就是把原来一些粗浅的理解和内容写个心得记录下来,仔细琢磨其实很多点如果深入进去去研究,估计都可以写成一小本小册子了,配点图在配点实战的操作步骤,几乎就可以出一本书了。不过大部分的文字,几乎很难传达个人理解的真实的内容,因为每个人的理解可能都是不一样的,因为大脑的思维方式不一样,不一定能get到你的点。所以相对而言,还是落地的代码和搭建好的系统相对便于容易沟通一些。

是马是骡子,自己搭建一套跑起来,并且让大家都用起来,应该就知道了。

Alisoft阿里软件淘宝开发接口研究

最近这两天打算开个淘宝店铺玩下,研究了三天的淘宝接口,写个一个半成品的.net类库,然后开始测试,发现了一些很不稳定的问题,让人十分崩溃,发现阿里给出来的rest方式,在他们那边的服务器解析,的确问题多多,总之就是不成熟,接口和说明文档不友好,还有待改进.

以下是我post过去的两个数据的对比,一个有效,一个无效,真的让人十分纳闷,

有效
sip_appkey=123456&
sip_apiname=alisoft.validateUser&
sip_timestamp=2009-02-24 06:08:31&
sip_sign=432034AE0536681E0438364E57062B67&
userId=16503352&
appId=123456&
appInstanceId=USERE1D043BE6633E0D32CEE8CA1BBEE6D9A&
token=D0BCF33CB0D6680667017336B894F1B6D20386650FA6912003D188993F29621D5D5C4395CB3B39A9658E39C48A497592

无效
sip_appkey=123456&
sip_apiname=alisoft.validateUser&
sip_timestamp=2009-02-24 18:20:00&
sip_sign=0A24256909430ED81FE63C0362A00681&
userId=16503352&
appId=123456&
appInstanceId=USERE1D043BE6633E0D32CEE8CA1BBEE6D9A&
token=D0BCF33CB0D6680667017336B894F1B6D20386650FA6912003D188993F29621D5D5C4395CB3B39A9DB5F27660BAA2610

这里面不同的就是token,timeStamp和sign,下面我来说下我对整个请求构造的理解吧,

对于http协议,由于我原来就是做网络协议开发的,所以没啥子难度,一般用得最多的两种,post和get方式,http协议是应用层的,在传输层用的还是tcp来跑,我们所关心的是,发个http请求过去,然后等返回,对返回过来的东西进行解析,ok,就那么简单.下面说一下关键的请求串的构造,

阿里给的地址有两个
测试地址是:http://sipdev.alisoft.com/sip/rest,软件测试开发调用
正式环境地址是:http://sip.alisoft.com/sip/rest,软件发布上线后调用。
目前一般开发的话用的都是用第一个地址,有了地址,就要知道怎么构造请求串了,

阿里的请求串分为两段,一段是sip的数据段,一段是api数据段,
sip的数据段,包括了在系统级别的参数和变量,最后会跟一个sign,这里的sign是关键,下面说下sign的生成方法

待签名串= 分配给你的软件的编码code+系统级别参数变量名+值+应用级参数变量名+值

然后对这个串用utf8编码打成二进制流,然后取这个二进制流的md5值,然后替换md5以后得到的加密串,里面的’-‘为空.经历了这样一个漫长的过程,你的宝贵的sign就得到了,然后就可以构造你的串了,

请求串=系统级参数变量名+”=”+值+”&”+应用级参数变量名的变量+”=”+值;

然后吧这段数据用ascii编码为二进制流,然后填充到http的请求的数据区,然后post出去.
具体代码如下:

string sipsign = code + "appId" + appId + "appInstanceId" + aepInstanceId + "sip_apinamealisoft.validateUser" + "sip_appkey" + appId + "sip_timestamp" + timestamp + "token" + token + "userId" + aepUserId;

MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();

sipsign = BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes(sipsign))).Replace("-", "");

string SIPdate = "sip_appkey=" + appId + "&sip_apiname=alisoft.validateUser&sip_timestamp=" + timestamp +"&sip_sign=" + sipsign;

string apidate = "&userId=" + aepUserId + "&appId=" + appId + "&appInstanceId=" + aepInstanceId + "&token=" + token;

ASCIIEncoding encoding = new ASCIIEncoding();

byte[] postdata = encoding.GetBytes(SIPdate + apidate);

这样的话一个请求就算完全搞定了,就等返回的数据,然后读出字段就可以了,可惜的是,我发过去的请求,返回的为空,在测试环境下是这样的,不知道生产环境下如何,崩溃,相当的崩溃.

写了一半的类库,就此闲置,这里给两个关键函数,就等阿里稳定了再继续写。

///
/// 发送请求数据
///
/// 拼装好的数据
/// http的请求方式,POST 或者 GET
///
public static string SendRequest(string inputData,string httpSendWay)
{
byte[] postdata = System.Text.Encoding.UTF8.GetBytes(inputData);//所有要传参数拼装

// Prepare web request...
//目前阿里软件的服务集成平台(SIP)的接口测试地址是:http://sipdev.alisoft.com/sip/rest,生产环境地址是:http://sip.alisoft.com/sip/rest,
//这里使用测试接口先,到正式上线时需要做切换
string url = System.Configuration.ConfigurationManager.AppSettings["APPURL"];
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(url);
//myRequest.Method = "POST";
myRequest.Method = httpSendWay;
myRequest.ContentType = "application/x-www-form-urlencoded";
myRequest.ContentLength = postdata.Length;

// Send the data.
Stream newStream = myRequest.GetRequestStream();
newStream.Write(postdata, 0, postdata.Length);
newStream.Close();

// Get response
HttpWebResponse myResponse = (HttpWebResponse)myRequest.GetResponse();
StreamReader reader = new StreamReader(myResponse.GetResponseStream(), System.Text.Encoding.UTF8);

string returnInfo=reader.ReadToEnd();

XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(returnInfo);
XmlNode node = xmlDoc.SelectSingleNode("/error_rsp/code");

if (node != null && node.InnerText != string.Empty)
{
throw new TBException(node.InnerText, xmlDoc.SelectSingleNode("/error_rsp/msg").InnerText);
return "";
}
else
{
return returnInfo;
}
}

public class ParamsHelper
{
string _code;

public string Code
{
get { return _code; }
set { _code = value; }
}

SortedList _mySL = new SortedList();

public ParamsHelper(string code)
{
_code = code;
}

//public ParamsHelper(string appID,string apiName,string timeStamp,string sessionID)
// : this(apiName)
//{

//}

public ParamsHelper(string sessionId, string apiName)
: this(GetAppCode)
{
AddParam("sip_appkey", GetAppID);
AddParam("sip_apiname", apiName);
AddParam("sip_timestamp", System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
AddParam("sip_sessionid", sessionId);
}

public void AddParam(string name, string value)
{
_mySL.Add(name, value);
}

public void AddParam(string name, int value)
{
_mySL.Add(name, value);
}

public string GetURL()
{
StringBuilder orgin = new StringBuilder();
orgin.Append(_code); //将安全编码放字符串首位

//对list里的参数进行拼装,参数名+参数值,按自然排序,即所有参数字母排序
StringBuilder _url = new StringBuilder();
foreach (DictionaryEntry Item in _mySL)
{
// ListItem newListItem = new ListItem();
orgin.Append(Item.Key.ToString());
if (Item.Value != null)
{
orgin.Append(Item.Value.ToString());
}
_url.AppendFormat("{0}={1}&", Item.Key, Item.Value);

}
_url.AppendFormat("sip_sign={0}", SignatureHelper.MD5hash(orgin.ToString()));
return _url.ToString();
}

public static string GetAppID
{
get
{
return System.Configuration.ConfigurationManager.AppSettings["APPID"];
}
}
public static string GetAppCode
{
get
{
//软件注册时获得
return System.Configuration.ConfigurationManager.AppSettings["APPcode"];
}
}
}

另类MDX学习记要

  本文作者:donegal 转载请注明出处:murdercdh.tianya.cn
  有错误的地方请给我E-Mail:murdercdh@126.com
  
开篇
  对于数据挖掘与商业智能这一块内容,Sql Server提供了一整套的解决方案,本着易用,快速的原则,将大部分东西都集成到了SSAS当中,作为中小企业实施BI首选。
 在这里我想重要讲解的是在SSAS当中非常重要的一个环节,MDX语言。
  学习新的技术,就好像拿到了一本新的武功图谱,过程总是充满激情,思维的互相碰撞,使整个过程充满悬念,当然可能长时间修炼可能造成枯燥,厌恶,但是为了成为一代大侠,这个过程是原始的积累和学习过程,不可跳跃,除非你是自创武功,独成一派,这样的功力至少需要差不多几十年的积累,张三疯不是一百岁才创太极么?所以各位安身修炼,切不可急躁。
  
总诀式
  首先给出定义:多维表达式 (MDX) 是一种功能完备、基于语句的脚本语言,用于定义、使用以及从 Microsoft SQL Server 2005 Analysis Services (SSAS) 中的多维对象中检索数据。
  MDX 提供以下几种语言功能:
  1. 用于创建、删除以及使用多维对象的数据定义语言 (DDL) 语句。
  2. 用于从多维对象中检索操作数据的数据操作语言 (DML) 语句。
  3. 用于管理作用域、上下文以及 MDX 脚本内的流控制的脚本语言语句。
  4. 用于操作从多维对象中检索的数据的大量运算符和函数。
  要想深入的了解这种语言所带来的巨大优势和遍历,首先需要对整个SSAS的框架体系做一个大致的了解,这样才能更加深入的了解MDX的强大功能以及复杂程度。
  Microsoft SQL Server 2005 Analysis Services (SSAS) 使用服务器组件和客户端组件为商业智能应用程序提供联机分析处理 (OLAP) 和数据挖掘功能:
  
六大心法秘诀:练功者必须仔细体会,待小有所成,亦可不断体会,必有所得。
  
特性1:Analysis Services 的服务器组件作为 Microsoft Windows 服务来实现。SQL Server 2005 Analysis Services 支持同一台计算机中的多个实例,每个 Analysis Services 实例作为单独的 Windows 服务实例来实现。
  
  特性2:客户端使用公用标准 XML for Analysis (XMLA) 与 Analysis Services 进行通信,XMLA 是一个基于 SOAP 的协议,用于发出命令和接收响应,公开为一项 Web 服务。此外,客户端对象模型通过 XMLA(包括托管提供程序 (ADOMD.Net) 和本机 OLE DB 访问接口)进行提供。
  
  特性3:查询命令可使用下列方式发出:SQL;多维表达式 (MDX)(一种面向分析的行业标准查询语言);或数据挖掘扩展插件 (DMX)(一种面向数据挖掘的行业标准查询语言)。还可以使用 Analysis Services 脚本语言 (ASSL) 来管理 Analysis Services 数据库对象。
  
  特性4:Microsoft SQL Server 2005 Analysis Services (SSAS) 支持瘦客户端体系结构。Analysis Services 计算引擎完全基于服务器,因此,所有查询都在服务器上进行解析。因此,每个查询只需在客户端和服务器之间进行一次来回行程,从而使得性能可以随着查询复杂性的增加而伸缩。
  
  特性5:Analysis Services 的本机协议为 XML for Analysis (XML/A)。Analysis Services 为客户端应用程序提供了数个数据访问接口,但是所有这些组件都使用 XML for Analysis 与 Analysis Services 实例进行通信。
  
  特性6:Analysis Services 提供了数个不同的访问接口,以支持不同的编程语言。访问接口借助 Internet 信息服务 (IIS),并通过 TCP/IP 或 HTTP 发送和接收 SOAP 数据包中的 XML for Analysis 来与 Analysis Services 服务器进行通信。HTTP 连接使用由 IIS 实例化的 COM 对象(称为数据抽取),该对象充当 Analysis Services 数据的管道。数据抽取既不会以任何方式检查包含在 HTTP 流中的基础数据,也不会检查可用于数据库本身中任何代码的任何基础数据结构。
  
  对于ssas有了一个大体的了解以后,就可以开始对具体的mdx语言开始进行学习了。对于一些基础的概念,会穿插到里面进行解释,这样大家就比较容易理解。
  
  下面介绍本门武功的基础概念,这样将有住于练习者领会其中内容,切记,基础乃练功之根本,务必正确领会,否则就像当年梅超疯理解偏差,错炼九鹰白骨爪,终究难成正果。
  下面用一个cube例子来解释基础的语言和概念:
  关系数据库以二维平面表的形式组织数据。这些表有一个列维度和一个行维度。在每个行和列的交点处只有一个数据元素。
  而多维数据库则不同,它是基于称为“多维数据集”的结构,如下图所示。多维数据集按“层次结构”组织数据,而不是以表的形式组织数据。
  
  成员:
  成员是维度中的一个项目,表示数据的一次或多次出现。可将维度中的成员看作基础数据库中的一个或多个记录,该记录在此列中的值属于此类别。成员是描述多维数据集中的单元数据时的最低级别的引用。
  可以用成员名称或成员键引用某个成员。在上一示例中,用成员在 Time 维度中的名称 4th quarter 来引用该成员。但是,如果维度不具有非唯一的成员名称,则成员名称可以重复,也可以更改渐变维度中的成员名称。
  引用成员的另一种方法是引用成员键。维度使用成员键明确标识特定成员。在 MDX 中,“与”符号 (&) 用于区分成员键和成员名称。例如,以下引用使用 4th quarter 成员的成员键 Q4:
  [Time].[2nd half].&[Q4]
  
  元组:
  包含在多维数据集中的数据元素称为“单元”。通过对多维数据集中包含的每个属性层次结构指定一个成员可以唯一地标识一个单元。标识一个单元的属性的组合称为“元组”。
  元组标识多维数据集中的单元。一个元组由多维数据集中每个层次结构中的一个成员组成(显式或隐式引用)。如果特定层次结构中的成员没有在元组中显式引用,则该层次结构中的默认成员将隐式包含在元组中。
  在 MDX 中,元组根据其复杂性依照语法进行构造。如果元组只由一个层次结构中的一个成员组成(通常称为“简单元组”),则下列语法是可以接受的:
  Time.[2nd half]
  例如,下面的元组标识了上图中值为 240 的一个单元(因为这里有四个维度,所以四维定义一个元组):
  ( Source.[Eastern Hemisphere].Africa,
  Time.[2nd half].[4th quarter],
   Route.Air,
   Measures.Packages)
  
  正如可以指定从关系数据库的表中检索多组列或行一样,您可以指定从多维数据集中检索一组元组。MDX 中用来指一个有序的元组集合的标识符称为“集”。下面的示例标识了上图所示的多维数据集中的一个元组集:
  
  { (Time.[1st half].[1st quarter]),
   Time.[2nd half].[3rd quarter]) }
  
  集:
  集是零个、一个或多个元组的有序集合。集最常用于定义 MDX 查询中的查询轴和切片器轴,因此可以只有一个元组,在某些情况下,也可以为空。下面的示例显示了具有两个元组的集:
  { (Time.[1st half], Route.nonground.air) , (Time.[ 2nd half], Route.nonground.sea) }
  好了,了解完了这些基本的概念以后就可以正式开始使用mdx语句来获取你想要的数据了,
  
  具体的语句看起来和sql的语句差不多,查询的思路也差不多,但是所有的数据都要以上面的概念去理解,而不是简单的一维度和二维度的,而是多维的,所以要用集合,元组,成员这些概念去理解,一开始接触的人理解起来可能会比较困难,不过慢慢的就可以加深理解了。
  
  下面我们就用几条比较经典的语句来摡略的学习整个mdx的语法,这样的学习方式可能会遗漏许多细节的地方,但是对于快速入门,那是绝对有好处的,在使用熟练度达到一定要求以后,就可以查阅sdk对一些细节的地方进行处理。
  
  内功心法和总诀式这里就介绍完毕了,下面开始具体的招数,每一招分为许多层次,由简单到复杂,切不可急功近利,一定要着重招式的基础部分的领悟,否则很容易走火入魔。
  
  第一式:查询语句
  第一层:
  SELECT
   { Route.nonground.Members } ON COLUMNS,
   { Time.[1st half].Members } ON ROWS
  FROM TestCube
  
  这条语句很简单,就是从Adventure Works里面查询出mesaures维度下的members成员,所查询出来的属性集作为列,而下面的这个Product.Style.CHILDREN属性集作为行。
  
  提示:这里存在三个细节扩展需要注意:
  第一个是成员的限定,可以在 MDX 查询中使用 WITH 关键字
  第二个是成员的的函数,可用于检索其他 MDX 实体(如维度和级别)中的成员
  第三个是查询轴内容和切片器轴内容的查询:
  具体的在大家深入以后就会有一个了解。
  第二层:
  SELECT
   [Measures].[Special Discount] on COLUMNS,
   NON EMPTY [Product].[Product].MEMBERS ON Rows
  FROM [Adventure Works]
  WHERE [Product].[Category].[Bikes]
  
  这里多加了一个where语句,看似和sql语句差不多,但是理念是不一样的,需要了解下面二个概念:
  查询轴:查询轴用于指定由多维表达式 (MDX) SELECT 语句所返回的单元集的范围。通过指定单元集的范围可以限定客户端可以看到的返回数据。
  切片器轴:切片器轴将对多维表达式 (MDX) SELECT 语句返回的数据进行筛选,限定返回的数据,从而只返回与指定成员相关的数据。切片器轴是在 MDX 中 SELECT 语句的 WHERE 子句中定义的
  每个 MDX 查询都在指定的多维数据集上下文中执行。此上下文定义了由该查询中的表达式求值的成员。
  在 SELECT 语句中,FROM 子句用于确定多维数据集上下文。此上下文可以是整个多维数据集,也可以只是该多维数据集的一个子多维数据集。如果通过 FROM 子句指定了多维数据集上下文,就可以使用其他函数来扩展或限制该上下文。
  
  第三层:
  WITH SET [ChardonnayChablis] AS
   ‘Filter([Product].Members, (InStr(1, [Product].CurrentMember.Name, “chardonnay”) <> 0) OR (InStr(1, [Product].CurrentMember.Name, “chablis”) <> 0))’
  SELECT
   [ChardonnayChablis] ON COLUMNS,
   {Measures.[Unit Sales]} ON ROWS
  FROM Sales
  
  第四层:
  create Session set [Store].[SetCities_2_3] as
  {[Data Stores].[ByLocation].[State].&[CA].&[City 02],
  [Data Stores].[ByLocation].[State].&[NH].&[City 03]}
  这里的第三层和第四层属于同一内容的两个不同方面,应该联系起来学习相互对照,这样可以对这个招术有一个比较深刻的记忆和理解。
  查询作用域:若要创建一个命名集,该命名集被定义为 MDX 查询的一部分并且其作用域因此被限制在该查询内,请使用 WITH 关键字。然后,就可以在 MDX SELECT 语句中使用该命名集。通过这种方法,更改用 WITH 关键字创建的命名集时就不会打乱 SELECT 语句。
  会话作用域:若要创建一个命名集,使其作用域比查询上下文更广(即,其作用域为 MDX 会话的生存期),请使用 CREATE SET 语句。使用 CREATE SET 语句定义的命名集对该会话中的所有 MDX 查询均可用。例如,CREATE SET 语句对于需要在多种查询中大量重用某个集的客户端应用程序会非常有用。
  
第五层:
  WITH
   MEMBER [Measures].[Special Discount] AS
   [Measures].[Discount Amount] * 1.5
  SELECT
   [Measures].[Special Discount] on COLUMNS,
   NON EMPTY [Product].[Product].MEMBERS ON Rows
  FROM [Adventure Works]
  WHERE [Product].[Category].[Bikes]
  这里糅合了前面几层的东西,但是增加了表达式的计算元素,修炼者需要掌握前面级别,方可轻松领悟这一层次。
  这里的on colums和on rows的限定,可以用axis函数来代替,他们所表达的意义是相同的,在axis当中,以下数字分别代表不同的维度界定。
  0 Columns
  1 Rows
  2 Pages
  3 Chapters
  4 Sections
  这里需要注意的是如果要采用1,那么必须采用0,如果要采用3,那么必须采用了0,1和2,这里的维度是逐级构造的,不能跳跃。
  
第六层:
  Create Session Member [Store].[Measures].LastFourStores as
  sum(([Stores].[ByLocation].Lag(3) :
  [Stores].[ByLocation].NextMember), [Measures].[Units Sold])
  采用内部成员属性:定义内部成员数次女冠以供使用.
  SELECT
   CROSSJOIN([Ship Date].[Calendar].[Calendar Year].Members,
   [Measures].[Sales Amount]) ON COLUMNS,
   NON EMPTY Product.Product.MEMBERS
   DIMENSION PROPERTIES
   Product.Product.[List Price],
  
   Product.Product.[Dealer Price] ON ROWS
  FROM [Adventure Works]
  WHERE ([Date].[Month of Year].[January])
  这里写了二条语句,第一条的特殊之处在于使用了功能函数对数据成员进行了操作,而后面的一条带cross join的语句记忆后面的DIMENSION PROPERTIES
  ,涉及到了高级招式的一些细节,在这里不推荐大家练习,待练到后面的招式,这一招自然会融会贯通,切不可操之过急。
  
  小结:
  从以上的几个语句,大家可以很好的看到mdx语句以及他的特性,很多细节的东西都在里面了,整个过程是一个由简单到复杂,由主题到细节的过程,具体的还得大家在实际运用中慢慢体会。
  Ps:第一式里面的层级就只有六层,一次修炼,不断演练,可达到熟练运用的境界,对于本心法,实战当中用的最多的可能就是第一式里面的东西,所以各位修练者务必做到能构熟练运用。
   
  第二式:功能函数
  这一层的描述,包含了许多细节的功能函数,这些函数对于在聚合,整合数据的时候起到很大的作用。
  第一层:
  SELECT Measures.[Internet Sales Amount] ON COLUMNS,
   CrossJoin ( {Product.[Product Line].[Product Line].MEMBERS},
   {[Customer].[Country].MEMBERS}) on ROWS
   FROM [Adventure Works]
  这里添加了一个特殊的CrossJoin,其实是对里面的两个成员组的做笛卡尔集,之后再聚合数据。做笛卡尔集的时候有些单元可能是空的,所以有了后面的招数,负责清空空的]] >