[转载]分享插件化平台规范——OSGi R4规范的中文翻译 - 道法自然 - 博客园

mikel阅读(853)

[转载]分享插件化平台规范——OSGi R4规范的中文翻译 – 道法自然 – 博客园.

我从本科开始一直专注于.NET,不过2007年毕业后却到一个外企先做Java开发,后来又做Flex + Java的开发。在工作之余,我一直在关注和学习.NET,并把在Java领域学习的优秀思想应用到.NET平台。第一次听说OSGi是2008年底,我 们产品开始换更新换代时,一个美国工程师提出来的。他推荐了OSGi技术。我对它一无所知,于是就上网搜索了一下OSGi并初步学习一下。OSGi提倡的 两大特性——模块化和动态化以及支持的三大功能——插件化、面向服务和插件扩展,让我兴奋不已。因为这与我想要实现的SaaS应用商店的理念完全一致。我 决定将OSGi移植到.NET平台(这就是我们现在的OSGi.NET平台产品UIOSP)。在没有接触到OSGi之前,我翻译了CAB和SCSF的文档 并查看了它们的源码,考虑将SCSF进行简化后作为底层框架,但一看到OSGi,我就毅然决然的将其抛弃了。

从2009年开始,我着手翻译了OSGi R4规范,这个分享的OSGi规范包括了OSGi最重要的模块层、声明周期层和服务层。OSGi体现了一种更为灵活的软件体系结构,我把它归纳为“横向切 割 + 纵向分层”。一个应用系统由若干功能组成,而每一个功能是代码的纵向组织而成的。功能间的交互一般很少,而功能内部的实现则非常的耦合,OSGi非常吻合 这种行为。基于OSGi,每一个应用系统按照功能被横向切割成不同模块,而每一个模块又根据不同的复杂度进行纵向分层架构,这种思想与软件工程的“高内 聚、低耦合”非常的吻合。因此,我非常喜欢OSGi提倡的软件体系结构。

不过,在Java总应用OSGi并没有基于.NET的由我们自己开发的尤埃开放服务平台(XAUI Open Service Platform,UIOSP)开发容易,UIOSP能够无缝兼容控制台、桌面、Web和移动设备等环境,并且提供了简单易用的项目模板、完善的帮助文档 和安装包。UIOSP内核设计思想基本和OSGi R4规范一致,不过,我们做了一些简化并添加了一些辅助设计工具。附件就是我翻译的OSGi R4规范的模块层、生命周期层和服务层等章节。Enjoy youself!

要运行示例,你需要下载UIOSP安装包,尤埃开放服务平台试用版下载

[转载]使用PowerDesigner 设计SQL Server 数据库

mikel阅读(876)

[转载]使用PowerDesigner 设计SQL Server 数据库 – xugang – 博客园.

工具:

Sybase PowerDesigner 12.5

Microsoft  SQL Server 2005

第一步:概念数据模型

打开PowerDesigner 软件,设计“概念数据模型(Conceptual Data Model),它通常用在概要设计阶段。

设计表结构

注意:如果Palette(工具面板)没有显示,可以在工具栏 –> 右键 –> 选择“Palette”(打勾选中)

设计结果如下:


在设计属性(字段)的时候,三个字母(MPD)分别表示:

M:是否为空;(√表示不允许为空)

P:是否为主键;

D:是否在该软件的视图中显示;

添加 “Relationship” 关系时,请注意:

1、在所有实体对象中,属性的取名不能重复。

2、 当添加引用完整性时,外键表不必添加主键表的主键字段作为外键。因为,在添加“Relationship”关系 之后,再转换为物理模型以后,外键表将自动添加外键字段列。

3、在添加 “Relationship” 关系时,起始点实体默认为主键表,结束点实体默认为外键表当然,可以在 “Relationship” 中改变。

第二步:物理数据模型

将“概念数据模型”的相关设计转换为“物理数据模型(Physical Data Model),相当于从“概要设计”转换为“详细设 计”,即对表和关系的具体实现。

选择“Tools–>Generate Physical Data Model . . .”即可。


然后选择 SQL Server 2005 数据库管理软件:


点击“确定”按钮以后,就产生了对 应“SQL Server 2005数据库的表、属性、关系的具体实现。

在PowerDesigner 中添加约束

1> 查看或设置属性(字段)的相关约束

2> 设置“标识列”(自动增长)

3> 设置“默认值

4> 设置“Check 约束

5> 设置唯一索引

概念数据模型与物理数据模型中的一些细节区别:

1> 在概念模型中,不要添加表的相关约束,应该在物理模型中添加表的相关约束;

2> 只有在物理模型中,PowerDesigner 才会显示“Database”菜单项。也只有在“Database” 菜单项中才有“Database Generation”选项(用于生成SQL 语句);

第三步:在SQL Server 2005中手动创建数据库

比如:我在SQL Server 2005中手动创建数据库“Students”示例。

第四步:在数据源ODBC中配置用户数据源

比如:我在用户数据源中,添加数据库“Students”的数据源。


第五步:在SQL Server 2005中自动创建表结构

首先:根据“物理数据模型”(Physical Data Model)自动建立相应的SQL语句:


其次:执行其创建的SQL语句文件,将数据表结构自动创建到SQL Server 2005 数据库中:


完成以上步骤后,刷新SQL Server 2005数据,将会看到你所创建的相关表结构。

当然,以上两个步骤 可以一起完成:

只要在“Database–>Database Generation”中,选择“Direct generation”即可:


这样,使用PowerDesigner 设计SQL Server 数据库的基本步骤就全部完成!

[转载]ASP.NET MVC 使用Jquery实现AJax

mikel阅读(990)

[转载]MVC 使用Jquery实现AJax – CELERY – 博客园.

在上一篇介绍MVC中的Ajax实现方法的时候,曾经提到了除了使用Ajax HTML Helper方式来实现之外,JQuery也是实现Ajax的另外一种方案。

通过get方法实现AJax请求

View

<script type="text/javascript">
    function GetTime() {
        $.get("Home/GetTime", function (response) {
            $("#myPnl").html(response);
        });

        return false;
    }
</script>
<div id="myPnl" style="width: 300px; height: 30px; border: 1px dotted silver;">
</div>
<a href="#" onclick="return GetTime();">Click Me</a>

Controller

public ActionResult GetTime()
{
    return Content(DateTime.Now.ToString());
}

通过post方法实现Form的Ajax提交

View

@model MvcAjax.Models.UserModel
@{
    ViewBag.Title = "AjaxForm";
}
<script type="text/javascript">
    $(document).ready(function () {
        $("form[action$='SaveUser']").submit(function () {
            $.post($(this).attr("action"), $(this).serialize(), function (response) {
                $("#myPnl").html(response);
            });

            return false;
        });
    });
    
</script>
<div id="myPnl" style="width: 300px; height: 30px; border: 1px dotted silver;">
</div>
@using (Html.BeginForm("SaveUser", "Home"))
{
    <table>
        <tr>
            <td>
                @Html.LabelFor(m => m.UserName)
            </td>
            <td>
                @Html.TextBoxFor(m => m.UserName)
            </td>
        </tr>
        <tr>
            <td>
                @Html.LabelFor(m => m.Email)
            </td>
            <td>
                @Html.TextBoxFor(m => m.Email)
            </td>
        </tr>
        <tr>
            <td>
                @Html.LabelFor(m => m.Desc)
            </td>
            <td>
                @Html.TextBoxFor(m => m.Desc)
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Submit" />
            </td>
        </tr>
    </table>
}

Model

using System.ComponentModel.DataAnnotations;

namespace MvcAjax.Models
{
    public class UserModel
    {
        [Display(Name = "Username")]
        public string UserName { get; set; }

        [Display(Name = "Email")]
        public string Email { get; set; }

        [Display(Name = "Description")]
        public string Desc { get; set; }
    }
}

Controller

[HttpPost]
public ActionResult SaveUser(UserModel userModel)
{
    //Save User Code Here
    //......

    return Content("Save Complete!");
}

以上代码实现了JQuery POST提交数据的方法。

通过查看以上两种JQuery方式的Ajax实现方法,和之前AJax HTML Helper比较可以发现其实Controller都是相同的。

采用jQuery方式提交数据的的主要实现方案就是通过jQuery的get或者post方法,发送请求到MVC的controller中,然后处理获取的response,更新到页面中。

注意点:

无论是使用超链接和form方式来提交请求,JavaScript的方法始终都有一个返回值false,用来防止超链接的跳转或者是form的实际提交。

这个地方就会出现一个疑问:

如果针对该页面禁止了JavaScript脚本,那么该页面就是跳转或者是form就是提交,返回的ActionResult处理就会出现问题了。

这个时候就需要在Controller中判断该请求是否是Ajax请求,根据不同的情况返回不同的ActionResult:

[HttpPost]
public ActionResult SaveUser(UserModel userModel)
{
    //Save User Code Here
    //......

    if (Request.IsAjaxRequest())
        return Content("Save Complete!");
    else
        return View();
}
Celery 标签:

[转载]SWF文件格式和ABC代码混淆工具的开发(二) – SWF和ABC文件格式

mikel阅读(1337)

[转载]SWF文件格式和ABC代码混淆工具的开发(二) – SWF和ABC文件格式 – Tony Huang 的编程园地 – 博客园.

在2010年11月28日,我在盛大举办的WeDoSwf会议上,我做了一个演讲,主题就是关于Swf的文件格式和Abc文件的混淆。

最近一个多月的时间里面,各种零零总总的事情特别多,一直没有时间把这个东西整理好放到博客中。

首先呢,把我的演讲的视频的ppt放上来:
视频:
http://v.ku6.com/show/MwjMgfhEgUFoiZhx.html

PPT:
http://www.slideshare.net/swfsh/swfabc

然后呢,经过我们的努力,我们已经将这个东西做成了一个产品,有兴趣的朋友可以去我们的公司主页上下载试用:
http://www.orandea.com/product?lang=cn

回到正题。在上一篇博客中,我已经说明了为什么需要对Swf文件进行加密,那接下来说说Swf文件和Swf内嵌的Abc文件的文件格式:

Swf文件格式

Swf文件格式呢,是Flash Player所使用的文件格式。Swf文件格式具有一下几个特点:

  • 基于Tag的流媒体技术
  • 内建压缩支持

这两个技术,尤其是第一个技术对于Flash的发展起到了至关重要的作用。

基于Tag的流媒体技术

Swf文件是由文件头和一系列的Tag组成的,下面这张图很好的说明了Flash的文件结构:

我们可以将Swf文件看成一个串流,然后顺序的从头读到尾来进行播放。
那么有那些种类的Tag呢?
这里说几个经典的:

  • SetBackgroundColor这个顾名思义就是设定swf的背景颜色的,我们在编译Flash的时候,设定的背景颜色最终就会生成这个Tag
  • ShowFrame接触Flash比较早的朋友应该都知道,在很久很久以前(我是慢羊羊村长),网络上的大部分Flash是可以一边下载一边播放的,这在当初 56kbps的小猫时代可谓是惊天地泣鬼神的功能啊,当时互联网上涌现出了一大批的优秀的Flash动画作品;哎呀,扯远了,回来回来。说说 ShowFrame的用途吧:在Flash里面,你可以通过其他的Tag定义位图,定义图形,定义补间动画,然后再放到舞台上去,而这个 ShowFrame的作用就是把这一帧的画面显示出来,意义重大吧:)
  • DoAbc我们都知道,从Flash 9开始,Flash引入了Avm2虚拟机、Abc文件格式和AS3语言,大大提高了Flash中脚本的执行效率。所以Flash9应该算是划时代的一个版本啊。DoAbc类型的Tag的作用就是把Abc文件嵌入到Flash中

我们先来用Swfspy看看,我们的Swf文件到底包含些什么东西:

具体Tag的定义,大家可以参考一下参考文件中的“swf_file_format_spec_v10.pdf”。

内建压缩支持

从Flash6开始Swf文件格式开始支持压缩功能,主要的压缩算法采用的是zlib中的deflate算法。
通过Swf文件的第一个字节我们就可以分辨出这是压缩的Swf文件还是平铺的Swf文件。
第一个字节是Signature byte,如果是’F’表示该Swf文件没有压缩过,如果是’C’表示该Swf文件采用了deflate算法压缩。
这里需要注意的是,Swf文件的压缩从容是从SWF File Header的第9个字节开始的,也就是从FrameSize字段开始,就全是压缩的内容了。
这里同时给大家提供一个建议就是,对于纯位图的资源文件,建议不要采用压缩的文件。因为本身所有的位图数据不是采用JPEG格式压缩的就是采用zlib格式压缩的,如果再采用压缩格式的Swf,那就会导致用户的Flash Player需要解码两次才能访问图片数据。

Abc文件格式

Adobe在设计Avm2和Abc文件格式的时候,大量参考了Java的设计。Abc文件格式几乎是Java中的class文件格式的翻版。
此外Adobe在Swf文件格式和Abc文件格式使用了大量的相似但是不同的基本数据格式,最典型的例子就是可变长无符号整形的存储,在Swf文件格式中 是U30格式,最多使用4个字节,但是到了Abc文件里面就变成了AbcU30,最多使用5个字节。给开发工作带来了很多麻烦。
Abc文件格式是一种结构化的文件格式,主要由以下几个部分组成:

  • 版本号Abc文件格式的版本号,现在只有一种可能,就是16.20
  • 常量池常量池是Flash为了减小Swf的体积,加快Swf的运行速度设计的一个基础设施,主要包括了“整形常量”、“无符号整形常量”、“双精度浮点常 量”、“字符串常量”、“命名空间常量”、“命名空间集常量”、“Multiname常量”,非常多,但是对于咱们程序员而言并不复杂。
  • 元数据主要是各种类、方法、函数、常量、属性等等等等的声明信息
  • 方法体方法具体包含的的指令都在这个部分保存。

Abc文件格式的具体内容,限于篇幅就放到下篇文章里面吧!

参考资料

快速连接

[转载]android开发我的新浪微博客户端-登录页面功能篇(4.2) – 遇见未知的自己 – 博客园

mikel阅读(910)

[转载]android开发我的新浪微博客户端-登录页面功能篇(4.2) – 遇见未知的自己 – 博客园.

上一篇中完成了如上图的UI部分的实现,现在继续来讲功能的实现,用户登录操作主要就是账号列表显示和选择账号登录两个功能其他的都是些简单的辅助功能,首先是点击id为iconSelectBtn的ImageButton时显示用户选择窗口,这个时候去数据库中获取账号记录然后在选择窗口中以列表方式显示出来,通过上一篇已经知道Id为list的ListView控件来显示账号列表,首先是从数据库中获取所有的账户记录然后设置默认选中的用户账号代码如下:

代码

private void initUser(){
//获取账号列表
dbHelper=new DataHelper(this);
userList
= dbHelper.GetUserList(false);
if(userList.isEmpty())
{
Intent intent
= new Intent();
intent.setClass(LoginActivity.
this, AuthorizeActivity.class);
startActivity(intent);
}
else
{
SharedPreferences preferences
= getSharedPreferences(Select_Name, Activity.MODE_PRIVATE);
String str
= preferences.getString(name, “”);
UserInfo user
=null;
if(str!=“”)
{
user
=GetUserByName(str);
}
if(user==null)
{
user
=userList.get(0);
}
icon.setImageDrawable(user.getUserIcon());
iconSelect.setText(user.getUserName());
}
}

这个initUser() 初始账号的方法在LoginActivity的onCreate中调用,主要完成两件事情,第一件获取通过userList = dbHelper.GetUserList(false);获取所有的账户记录,关于DataHelper前面已经有说过了,如果获取的用户记录为空那么就跳转到用户授权功能页面让用户添加账号,如果不为空那么通过SharedPreferences去读取用户上一次选择的账号名称,如果没有或者数据库里账号记录不包括这个账户名称那么默认显示记录的第一个账号和头像,如果有那么显示这个账户的名称和头像。关于SharedPreferences,是Android提供给开发者用来存储一些简单的数据用的,非常方便类似于网站Cookie,在这里我就是用这个来保存上一次用户选择的是哪个账号,非常实用。

接下类首先为Id为list的ListView控件准备数据Adapter,这个Adapter非常简单就是普通的adapter继承BaseAdapter即可,代码如下:代码

public class UserAdapater extends BaseAdapter{

@Override
public int getCount() {
return userList.size();
}

@Override
public Object getItem(int position) {
return userList.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView
= LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_user, null);

ImageView iv = (ImageView) convertView.findViewById(R.id.iconImg);
TextView tv
= (TextView) convertView.findViewById(R.id.showName);
UserInfo user
= userList.get(position);
try {
//设置图片显示
iv.setImageDrawable(user.getUserIcon());
//设置信息
tv.setText(user.getUserName());

} catch (Exception e) {
e.printStackTrace();
}
return convertView;
}

接下就是为这个ListView设定数据源Adapter,在账号选择窗口显示的时候进行设置,添加到id为iconSelectBtn的ImageButton的OnClickListener中代码如下:代码

ImageButton iconSelectBtn=(ImageButton)findViewById(R.id.iconSelectBtn);
iconSelectBtn.setOnClickListener(
new OnClickListener(){
@Override
public void onClick(View v) {
……
dialog.show();

UserAdapater adapater = new UserAdapater();
ListView listview
=(ListView)diaView.findViewById(R.id.list);
listview.setVerticalScrollBarEnabled(
false);// ListView去掉下拉条
listview.setAdapter(adapater);
listview.setOnItemClickListener(
new OnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> arg0, View view,int arg2, long arg3) {
TextView tv
=(TextView)view.findViewById(R.id.showName);
iconSelect.setText(tv.getText());
ImageView iv
=(ImageView)view.findViewById(R.id.iconImg);
icon.setImageDrawable(iv.getDrawable());
dialog.dismiss();
}

});
}

});

通过上面代码完成了账号选择的功能,接下来给id为login的ImageButton添加OnClickListener,使得点击后以当前选择账号进入微博首页,代码如下:

代码

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login);
……
ImageButton login
=(ImageButton)findViewById(R.id.login);
login.setOnClickListener(
new OnClickListener(){
@Override
public void onClick(View v) {
GoHome();
}

});
}

//进入用户首页
private void GoHome(){
if(userList!=null)
{
String name
=iconSelect.getText().toString();
UserInfo u
=GetUserByName(name);
if(u!=null)
{
ConfigHelper.nowUser
=u;//获取当前选择的用户并且保存
}
}
if(ConfigHelper.nowUser!=null)
{
//进入用户首页
Intent intent = new Intent();
intent.setClass(LoginActivity.
this, HomeActivity.class);
startActivity(intent);
}
}

在上面的GoHome方法中ConfigHelper.nowUser是类型为UserInfo的static类型用来保存当前登录账号的信息,替代web中session使用。

最后添加如下方法,用来当这个登录LoginActivity结束的时候保存当前选择的账户名称到SharedPreferences中,以便帮用户记住登录账号的功能,就是前面的initUser() 初始账号的方法中会获取保存在SharedPreferences中的账户名称,代码如下:

代码

@Override
protected void onStop() {
//获得SharedPreferences对象
SharedPreferences MyPreferences = getSharedPreferences(Select_Name, Activity.MODE_PRIVATE);
//获得SharedPreferences.Editor对象
SharedPreferences.Editor editor = MyPreferences.edit();
//保存组件中的值
editor.putString(name, iconSelect.getText().toString());
editor.commit();
super.onStop();
}

至此登录页面功能篇结束,请继续关注下一篇。

[转载]缓存应用--Memcached分布式缓存简介

mikel阅读(818)

[转载]缓存应用–Memcached分布式缓存简介 – 情缘 – 博客园.

一. 什么是Memcached

Memcached 是一个高性能的分布式内存 对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象 来减少读取数据库的次数,从而提供动态、数据库驱动网站的速度。

相信很多人都用过缓存,在 .net 中也有内置的缓存机制,还有很多第三方工具如apachenginx等可以做静态资源的缓存,同时我们也可 以制定自己的缓存机制,缓存数据库查询的数据以减少对数据库的频繁操作。但是很多时候我们总是感觉这些缓存总不尽人意, Memcached可以解决你不少的烦恼问题。 最少在我的学习中解决了我不少问题,所以决定记录下来分享。

Memcached基于一 个存储键/值对的hashmap。其守护进程是用C写的,但是客户端可以用任何语言来编写(本文使用C#作为例子),并通过memcached协议与守护进程通信。可           能这些东西都太高深了,我们暂不做研究。

二. 分布式缓存

其实 Memcached作为一个分布式缓存数据服务,但是每个服务之间根本没有进行相互通信,这里可能与 我理解的分布式有点区别,可能是我才疏学浅,也可能是每个人思考问题的角度不同。Memcached 客户端就是通过一种分布式算法将数据保存到不同的Memcached服务器上,将数据进行缓存。分布 式缓存,可以而知memcached可以进行大数据量的缓存。这点可以弥补我们之前很多人都遇到的将 数据缓存到应用服务器上,而且只能缓存少量数据,否则对应用服务器的影响非常大。
Memcached应用机制图:

这个图是有点简陋了,但是问题还是能够描述的清楚的,缓存机制的基本原理就是先查询数据保存到memcached中,地址在此请求就直接从Memcached缓存中取数据,这样就可以减少对服务器请求压力。

. Memcached 特征

(1) 协议简单: 不使用复杂的xml格式,而是使用文本格式

(2) 基于libevent的事件处理机制 (不懂)

(3) 内置内存存储方式: 数据存在在内存中,所以重启机器会导致数据丢失

(4)Memcached相互不通信的分布式: Memcached 服务器之间不会进行通信,数据都是通过客户端的分布式算法存储到各个服务器中

四. Memcached的安装

首先这里是在windows系统上做测试,Memcachedlinux等非windows平台上性能会更高。

下载Memcached 服务端: http://memcached.org/ (官网寻找适用的版本)

1 解压缩文件到D:\Program Files\Memcached
2
命令行输入 D:\Program Files\Memcached\memcached.exe -d install
3
命令行输入 D:\Program Files\Memcached\memcached\memcached.exe -d start,该命令启动 Memcached ,默认监听端口为 11211

当然我们可以在windows 服务中查看到此此服务:

.NET 平台下使用Memcached

因为个人首先接触的客户端是memcacheddotnet ,所以习惯了适用这个核心库作为客户端。下载的工作都非常简单,因为客户端都帮我们实现了分布式程序算法,我们关心的只要怎样去存在获取这些数据。下面简单介绍一下:

1 static void Main(string[] args)
2 {
3 SockIOPool pool = SockIOPool.GetInstance();
4 string[] servers = { 127.0.0.1:11211 };
5 pool.SetServers(servers);
6 pool.MinConnections = 3;
7 pool.MaxConnections = 5;
8 pool.InitConnections = 3;
9 pool.SocketConnectTimeout = 5000;
10 pool.Initialize();
11
12 MemcachedClient client = new MemcachedClient();
13 client.EnableCompression = false;
14 Console.WriteLine(———————–Memcached Set 设置值————————–);
15 client.Set(key1,value1);
16 Console.WriteLine(client.Get(key1));
17 Console.WriteLine(———————–Memcached Add 设置值————————–);
18 client.Add(key2,value2);
19 Console.WriteLine(client.Get(key2));
20 client.Set(key2,value1 value2);
21 Console.WriteLine(client.Get(key2));
22 Console.WriteLine(———————–Memcached Replace 设置值————————–);
23 client.Replace(key2, value3);
24 Console.WriteLine(client.Get(key2));
25
26 Console.WriteLine(———————–Memcached 键值是否存在————————–);
27 if (client.KeyExists(key2))
28 {
29 Console.WriteLine(键key2 存在);
30 }
31 if (client.KeyExists(hechen)==false)
32 {
33 Console.WriteLine(键hechen 不存在);
34 }
35
36 Console.WriteLine(———————–Memcached 删除数据————————–);
37 client.Add(key4,value4);
38 Console.WriteLine(key4==> + client.Get(key4));
39 client.Delete(key4);
40 if (!client.KeyExists(key4))
41 {
42 Console.WriteLine(key4 已将删除);
43 }
44
45 Console.WriteLine(———————–Memcached 数据过期————————–);
46 client.Add(key5,value5,DateTime.Now.AddMilliseconds(5000));
47 Console.WriteLine(client.Get(key5));
48 System.Threading.Thread.Sleep(6000);
49 Console.WriteLine(过期: + client.Get(key5));
50 }

上面的例子虽然简单,包括一些基本的用法,在一般使用Memcached缓存数据的时候都用得到。

Memcached 初始化数据

1 SockIOPool pool = SockIOPool.GetInstance();
2 string[] servers = { 127.0.0.1:11211 };
3 pool.SetServers(servers);
4 pool.MinConnections = 3;
5 pool.MaxConnections = 5;
6 pool.InitConnections = 3;
7 pool.SocketConnectTimeout = 5000;
8 pool.Initialize();

SockIOPool 用于初始化分布式缓存池的对象,上面设置了多种属性,我相信这些属性的意思大家都懂。这里要注意的是Initialize () ,只有调用这个方式之后,才能初始化数据。才能使用连接缓存池。

Memcached 设置缓存值的三种方式

1 MemcachedClient client = new MemcachedClient();
2 client.EnableCompression = false;
3 Console.WriteLine(———————–Memcached Set 设置值————————–);
4 client.Set(key1,value1);
5 Console.WriteLine(client.Get(key1));
6 Console.WriteLine(———————–Memcached Add 设置值————————–);
7 client.Add(key2,value2);
8 Console.WriteLine(client.Get(key2));
9 client.Set(key2,value1 value2);
10 Console.WriteLine(client.Get(key2));
11 Console.WriteLine(———————–Memcached Replace 设置值————————–);
12 client.Replace(key2, value3);
13 Console.WriteLine(client.Get(key2));

有心的可以测试一下这三个方法: Set () 如果缓存中存在相同的键值这替换原有的,Add() 只是添加数据,如果存在相同键的不再添加。 Replace() 则用于替换已有的相同的键值。

Memcached 缓存数据过期

1 Console.WriteLine(———————–Memcached 数据过期————————–);
2 client.Add(key5,value5,DateTime.Now.AddMilliseconds(5000));
3 Console.WriteLine(client.Get(key5));
4 System.Threading.Thread.Sleep(6000);
5 Console.WriteLine(过期: + client.Get(key5));

很多时候我们并不希望数据永久缓存,一般都会有一个过期时间。上面的添加缓存数据就设置了缓存时间,可以达到过期缓存的效果。

案例下载地址 点击下载

此篇文章到此结束,内容比较简单,而且很多都是别人写过的东西。不过为了个人的学习还是积累起来,对于缓存部分后续文章继续更新。

Memcached 分布式缓存也不是如此简单,这里记录希望对自己和各位都有一定的帮助。有意见多多拍砖头。。。个人不介意的

[转载]memcache源码分析之items

mikel阅读(1271)

[转载]memcache源码分析之items – 先贝夜话 – 博客园.

items是memcache用来管理item的封装,采用的hash表和LRU链的形式,关于hash表的操作见我前几天的文章  memcache源码分析之assoc

关于item内容的存储机制简介

item的内容存储是在slab中管理的,为了对内存进行有效的管理,slab采用的是分桶的大小来存储item的内容的,简单举例解释一下,初始化时会有不同块大小的桶,比如桶1里面的

内存块都是80b的,专门用来存储item内容大小接近80b的。桶2的内存块是100b的,专门用来存储内容大小接近100b的item,桶3是 120b的,用来存储大小接近120b的item,等等。所以,如果有一个item的内容大小是90b,那它只能存储在100b的桶内,不能存储在其他里 面的,120b的也不可以。具体详细介绍请见我后续关于slab的文章。

问题:当100b的桶存储满的时候,memcache怎么办呢?

这个问题的答案就在本文介绍的内容里面。

为一个item分配存储空间的时候,具体的操作是这样的:

1、首先,计算该item占用的空间大小,只有知道了它的大小,才能知道它需要存储在哪个桶中。一个item的大小包括它的item结构体大小 部分、名字长度部分、状态标识部分、内容大小部分等的总和。具体计算方法请看下面的代码分析中 item_make_header 函数。

2、然后寻找合适的slab用于存储,这一部分主要是比较item 和各slab桶的大小,寻找最合适的slab,此部分代码是文件  slabs.c 中的  slabs_clsid 函数,具体内容我后续关于slab的文章会详细分析。

3、从对应slab的tail队列中寻找是否存在过期的item,如果有,清除掉,此处操作最多尝试50次。

4、如果第3步操作失败,并且在对应slab中分配空间失败,那么从slab对应的tail队列中删除没有被引用的item,且最多也是尝试50次。

5、尝试从slab中分配空间。

6、如果第5步失败,会从slab对应的tail队列中删除3个小时(默认)之前的正在引用的item。

7、然后尝试从slab中分配空间。如果失败,返回NULL,成功则会设置item对应的一些信息,返回成功标识。

item的删除过程:

1、设置已被删除状态。并从hash表中删除,次部分代码调用的是  memcache源码分析之assoc 中介绍到的函数assoc_delete

2、从LRU链中删除。函数item_unlink_q。

3、如果要清除item占用的资源,则调用函数do_item_remove和item_free,释放占用内存空间。


另外还提供了一些其他操作,分别包括,获取某个item(会判断是否过期),获取某个item(不判断是否过期),客户端通过flush_all操作清空所有过期item,item的新值替换,访问时间更新等。

当然,有item的删除操作,就要有相应的加入hash表和LRU链的操作。

另外,还提供了一些item和slab状态函数。


想了解详细代码的同学可以看一下下面的简要分析。有错误之处请指正。


items.h

01 /* See items.c */
02 uint64_t get_cas_id(void);
03
04 /*@null@*/
05 item *do_item_alloc(char *key, const size_t nkey, const int flags, const rel_time_t exptime, const int nbytes);
06 void item_free(item *it);
07 bool item_size_ok(const size_t nkey, const int flags, const int nbytes);
08
09 int do_item_link(item *it); /** may fail if transgresses limits */
10 void do_item_unlink(item *it);
11 void do_item_remove(item *it);
12 void do_item_update(item *it); /** update LRU time to current and reposition */
13 int do_item_replace(item *it, item *new_it);
14
15 /*@null@*/
16 char *do_item_cachedump(const unsigned int slabs_clsid, const unsigned int limit, unsigned int *bytes);
17 void do_item_stats(ADD_STAT add_stats, void *c);
18 /*@null@*/
19 void do_item_stats_sizes(ADD_STAT add_stats, void *c);
20 void do_item_flush_expired(void);
21
22 item *do_item_get(const char *key, const size_t nkey);
23 item *do_item_get_nocheck(const char *key, const size_t nkey);
24 void item_stats_reset(void);
25 extern pthread_mutex_t cache_lock;

items.c

001 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
002 #include "memcached.h"
003 #include <sys/stat.h>
004 #include <sys/socket.h>
005 #include <sys/signal.h>
006 #include <sys/resource.h>
007 #include <fcntl.h>
008 #include <netinet/in.h>
009 #include <errno.h>
010 #include <stdlib.h>
011 #include <stdio.h>
012 #include <string.h>
013 #include <time.h>
014 #include <assert.h>
015
016 /* Forward Declarations */
017 static void item_link_q(item *it);
018 static void item_unlink_q(item *it);
019
020 /*
021 * We only reposition items in the LRU queue if they haven't been repositioned
022 * in this many seconds. That saves us from churning on frequently-accessed
023 * items.
024 */
025 #define ITEM_UPDATE_INTERVAL 60
026
027 #define LARGEST_ID POWER_LARGEST
028
029 //item状态信息结构体
030 typedef struct {
031 unsigned int evicted;
032 unsigned int evicted_nonzero;
033 rel_time_t evicted_time;
034 unsigned int reclaimed;
035 unsigned int outofmemory;
036 unsigned int tailrepairs;
037 } itemstats_t;
038
039 static item *heads[LARGEST_ID];
040 static item *tails[LARGEST_ID];
041 static itemstats_t itemstats[LARGEST_ID];
042 static unsigned int sizes[LARGEST_ID];//记录每个slab的元素个数
043
044 void item_stats_reset(void) {
045 pthread_mutex_lock(&cache_lock);
046 memset(itemstats, 0, sizeof(itemstats));
047 pthread_mutex_unlock(&cache_lock);
048 }
049
050
051 //获取新的CAS值
052 uint64_t get_cas_id(void) {
053 static uint64_t cas_id = 0;
054 return ++cas_id;
055 }
056
057 /* Enable this for reference-count Debugging. */
058 #if 0
059 # define Debug_REFCNT(it,op) \
060 fprintf(stderr, "item %x refcnt(%c) %d %c%c%c\n", \
061 it, op, it->refcount, \
062 (it->it_flags & ITEM_LINKED) ? 'L' : ' ', \
063 (it->it_flags & ITEM_SLABBED) ? 'S' : ' ')
064 #else
065 # define Debug_REFCNT(it,op) while(0)
066 #endif
067
068 /**
069 * Generates the variable-sized part of the header for an object.
070 *
071 * key     - The key
072 * nkey    - The length of the key
073 * flags   - key flags
074 * nbytes  - Number of bytes to hold value and addition CRLF terminator
075 * suffix  - Buffer for the "VALUE" line suffix (flags, size).
076 * nsuffix - The length of the suffix is stored here.
077 *
078 * Returns the total size of the header.
079 */
080 //计算item占用空间大小
081 static size_t item_make_header(const uint8_t nkey, const int flags, const int nbytes,char *suffix, uint8_t *nsuffix) {
082 /* suffix is defined at 40 chars elsewhere.. */
083 *nsuffix = (uint8_t) snprintf(suffix, 40, " %d %d\r\n", flags, nbytes - 2);
084 return sizeof(item) + nkey + *nsuffix + nbytes;
085 }
086
087
088 //分配一个item空间
089 item *do_item_alloc(char *key, const size_t nkey, const int flags, const rel_time_t exptime, const int nbytes) {
090 uint8_t nsuffix;
091 item *it = NULL;
092 char suffix[40];
093 size_t ntotal = item_make_header(nkey + 1, flags, nbytes, suffix, &nsuffix);//获取item占用空间大小
094 if (settings.use_cas) {
095 ntotal += sizeof(uint64_t);
096 }
097
098 unsigned int id = slabs_clsid(ntotal);//寻找合适的slab
099 if (id == 0)
100 return 0;
101
102 /* do a quick check if we have any expired items in the tail.. */
103 int tries = 50;
104 item *search;
105
106 for (search = tails[id];tries > 0 && search != NULL;tries--, search=search->prev) {
107 if (search->refcount == 0 && (search->exptime != 0 && search->exptime < current_time)) {//过期
108 it = search;
109 /* I don't want to actually free the object, just steal
110 * the item to avoid to grab the slab mutex twice ;-)
111 */
112 STATS_LOCK();
113 stats.reclaimed++;
114 STATS_UNLOCK();
115 itemstats[id].reclaimed++;
116 it->refcount = 1;
117 do_item_unlink(it);//从hash表删除
118 /* Initialize the item block: */
119 it->slabs_clsid = 0;
120 it->refcount = 0;
121 break;
122 }
123 }
124
125 if (it == NULL && (it = slabs_alloc(ntotal, id)) == NULL) {//没有过期元素且加入相应slab失败
126
127 tries = 50;
128
129 /* If requested to not push old items out of cache when memory runs out,
130 * we're out of luck at this point...
131 */
132
133 if (settings.evict_to_free == 0) {
134 itemstats[id].outofmemory++;
135 return NULL;
136 }
137
138 /*
139 * try to get one off the right LRU
140 * don't necessariuly unlink the tail because it may be locked: refcount>0
141 * search up from tail an item with refcount==0 and unlink it; give up after 50
142 * tries
143 */
144
145 if (tails[id] == 0) {
146 itemstats[id].outofmemory++;
147 return NULL;
148 }
149
150 for (search = tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) {
151 if (search->refcount == 0) {//没有被引用的情况下删除之
152 if (search->exptime == 0 || search->exptime > current_time) {
153 itemstats[id].evicted++;
154 itemstats[id].evicted_time = current_time - search->time;
155 if (search->exptime != 0)
156 itemstats[id].evicted_nonzero++;
157 STATS_LOCK();
158 stats.evictions++;
159 STATS_UNLOCK();
160 } else {
161 itemstats[id].reclaimed++;
162 STATS_LOCK();
163 stats.reclaimed++;
164 STATS_UNLOCK();
165 }
166 do_item_unlink(search);
167 break;
168 }
169 }
170 it = slabs_alloc(ntotal, id);
171 if (it == 0) {
172 itemstats[id].outofmemory++;
173 /* Last ditch effort. There is a very rare bug which causes
174 * refcount leaks. We've fixed most of them, but it still happens,
175 * and it may happen in the future.
176 * We can reasonably assume no item can stay locked for more than
177 * three hours, so if we find one in the tail which is that old,
178 * free it anyway.
179 */
180 tries = 50;
181 for (search = tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) {
182 if (search->refcount != 0 && search->time + TAIL_REPAIR_TIME < current_time) {//没有被引用并且是3小时之前的item
183 itemstats[id].tailrepairs++;
184 search->refcount = 0;
185 do_item_unlink(search);
186 break;
187 }
188 }
189 it = slabs_alloc(ntotal, id);
190 if (it == 0) {
191 return NULL;
192 }
193 }
194 }
195
196 assert(it->slabs_clsid == 0);
197
198 it->slabs_clsid = id;
199
200 assert(it != heads[it->slabs_clsid]);
201
202 it->next = it->prev = it->h_next = 0;
203 it->refcount = 1; /* the caller will have a reference */
204 DEBUG_REFCNT(it, '*');
205 it->it_flags = settings.use_cas ? ITEM_CAS : 0;
206 it->nkey = nkey;
207 it->nbytes = nbytes;
208 memcpy(ITEM_key(it), key, nkey);
209 it->exptime = exptime;
210 memcpy(ITEM_suffix(it), suffix, (size_t)nsuffix);
211 it->nsuffix = nsuffix;
212 return it;
213 }
214
215
216 //释放item
217 void item_free(item *it) {
218 size_t ntotal = ITEM_ntotal(it);
219 unsigned int clsid;
220 assert((it->it_flags & ITEM_LINKED) == 0);//没有在hash表和LRU链中
221 assert(it != heads[it->slabs_clsid]);
222 assert(it != tails[it->slabs_clsid]);
223 assert(it->refcount == 0);
224
225 /* so slab size changer can tell later if item is already free or not */
226 clsid = it->slabs_clsid;
227 it->slabs_clsid = 0;
228 it->it_flags |= ITEM_SLABBED;//内存空闲交给slab
229 DEBUG_REFCNT(it, 'F');
230 slabs_free(it, ntotal, clsid);
231 }
232
233
234 //检验某item是否有适合的slab来存储
235 bool item_size_ok(const size_t nkey, const int flags, const int nbytes) {
236 char prefix[40];
237 uint8_t nsuffix;
238
239 return slabs_clsid(item_make_header(nkey + 1, flags, nbytes,prefix, &nsuffix)) != 0;
240 }
241
242
243 //加入LRU队列,成为新的head
244 static void item_link_q(item *it) { /* item is the new head */
245 item **head, **tail;
246 assert(it->slabs_clsid < LARGEST_ID);//判断所设置slab是否有效
247 assert((it->it_flags & ITEM_SLABBED) == 0);//判断状态
248
249 head = &heads[it->slabs_clsid];
250 tail = &tails[it->slabs_clsid];
251 assert(it != *head);
252 assert((*head && *tail) || (*head == 0 && *tail == 0));
253 it->prev = 0;
254 it->next = *head;
255 if (it->next) it->next->prev = it;
256 *head = it;
257 if (*tail == 0) *tail = it;//只有tail为空时才加入?
258 sizes[it->slabs_clsid]++;
259 return;
260 }
261
262
263 //从对应的slab的LRU链上删除
264 static void item_unlink_q(item *it) {
265 item **head, **tail;
266 assert(it->slabs_clsid < LARGEST_ID);
267 head = &heads[it->slabs_clsid];
268 tail = &tails[it->slabs_clsid];
269
270 if (*head == it) {
271 assert(it->prev == 0);
272 *head = it->next;
273 }
274 if (*tail == it) {
275 assert(it->next == 0);
276 *tail = it->prev;
277 }
278 assert(it->next != it);
279 assert(it->prev != it);
280
281 if (it->next) it->next->prev = it->prev;
282 if (it->prev) it->prev->next = it->next;
283 sizes[it->slabs_clsid]--;
284 return;
285 }
286
287
288 //将item加入到hashtable和LRU链中
289 int do_item_link(item *it) {
290 MEMCACHED_ITEM_LINK(ITEM_key(it), it->nkey, it->nbytes);//ITEM_key在memcached.h中定义
291 assert((it->it_flags & (ITEM_LINKED|ITEM_SLABBED)) == 0);//判断状态,既没有在hash表LRU链中或被释放
292 it->it_flags |= ITEM_LINKED;//设置linked状态
293 it->time = current_time;//设置最近访问时间
294 assoc_insert(it);//插入hashtable   assoc.c
295
296 STATS_LOCK();
297 stats.curr_bytes += ITEM_ntotal(it);//增加每个item所需要的字节大小,包括item结构体和item内容大小
298 stats.curr_items += 1;
299 stats.total_items += 1;
300 STATS_UNLOCK();
301
302 /* Allocate a new CAS ID on link. */
303 ITEM_set_cas(it, (settings.use_cas) ? get_cas_id() : 0);//设置新CAS,CAS是memcache用来处理并发请求的一种机制
304
305 item_link_q(it);//加入LRU链
306
307 return 1;
308 }
309
310
311 //从hash表和LRU链中删除item
312 void do_item_unlink(item *it) {
313 MEMCACHED_ITEM_UNLINK(ITEM_key(it), it->nkey, it->nbytes);
314 if ((it->it_flags & ITEM_LINKED) != 0) {
315 it->it_flags &= ~ITEM_LINKED;//设置为非linked
316 STATS_LOCK();
317 stats.curr_bytes -= ITEM_ntotal(it);
318 stats.curr_items -= 1;
319 STATS_UNLOCK();
320 assoc_delete(ITEM_key(it), it->nkey);//从hash表中删除
321 item_unlink_q(it);//从LRU链中删除
322 if (it->refcount == 0) item_free(it);
323 }
324 }
325
326
327 //remove item
328 void do_item_remove(item *it) {
329 MEMCACHED_ITEM_REMOVE(ITEM_key(it), it->nkey, it->nbytes);
330 assert((it->it_flags & ITEM_SLABBED) == 0);
331 if (it->refcount != 0) {
332 it->refcount--;
333 DEBUG_REFCNT(it, '-');
334 }
335 if (it->refcount == 0 && (it->it_flags & ITEM_LINKED) == 0) {//没有人在引用并且没有在hash表和LEU链中
336 item_free(it);
337 }
338 }
339
340
341 //更新item最后访问时间
342 void do_item_update(item *it) {
343 MEMCACHED_ITEM_UPDATE(ITEM_key(it), it->nkey, it->nbytes);
344 if (it->time < current_time - ITEM_UPDATE_INTERVAL) {
345 assert((it->it_flags & ITEM_SLABBED) == 0);//没有被释放
346
347 if ((it->it_flags & ITEM_LINKED) != 0) {
348 item_unlink_q(it);
349 it->time = current_time;
350 item_link_q(it);
351 }
352 }
353 }
354
355
356 //item替换
357 int do_item_replace(item *it, item *new_it) {
358 MEMCACHED_ITEM_REPLACE(ITEM_key(it), it->nkey, it->nbytes,ITEM_key(new_it), new_it->nkey, new_it->nbytes);
359 assert((it->it_flags & ITEM_SLABBED) == 0);//确保没有被释放
360
361 do_item_unlink(it);
362 return do_item_link(new_it);
363 }
364
365
366 /*@null@*/
367 char *do_item_cachedump(const unsigned int slabs_clsid, const unsigned int limit, unsigned int *bytes) {
368 unsigned int memlimit = 2 * 1024 * 1024; /* 2MB max response size */
369 char *buffer;
370 unsigned int bufcurr;
371 item *it;
372 unsigned int len;
373 unsigned int shown = 0;
374 char key_temp[KEY_MAX_LENGTH + 1];
375 char temp[512];
376
377 it = heads[slabs_clsid];
378
379 buffer = malloc((size_t)memlimit);
380 if (buffer == 0) return NULL;
381 bufcurr = 0;
382
383 while (it != NULL && (limit == 0 || shown < limit)) {
384 assert(it->nkey <= KEY_MAX_LENGTH);
385 /* Copy the key since it may not be null-terminated in the struct */
386 strncpy(key_temp, ITEM_key(it), it->nkey);
387 key_temp[it->nkey] = 0x00; /* terminate */
388 len = snprintf(temp, sizeof(temp), "ITEM %s [%d b; %lu s]\r\n",key_temp, it->nbytes - 2,(unsigned long)it->exptime + process_started);
389 if (bufcurr + len + 6 > memlimit) /* 6 is END\r\n\0 */
390 break;
391 memcpy(buffer + bufcurr, temp, len);
392 bufcurr += len;
393 shown++;
394 it = it->next;
395 }
396
397 memcpy(buffer + bufcurr, "END\r\n", 6);
398 bufcurr += 5;
399
400 *bytes = bufcurr;
401 return buffer;
402 }
403
404
405 //slab状态信息
406 void do_item_stats(ADD_STAT add_stats, void *c) {
407 int i;
408 for (i = 0; i < LARGEST_ID; i++) {
409 if (tails[i] != NULL) {
410 const char *fmt = "items:%d:%s";
411 char key_str[STAT_KEY_LEN];
412 char val_str[STAT_VAL_LEN];
413 int klen = 0, vlen = 0;
414
415 APPEND_NUM_FMT_STAT(fmt, i, "number", "%u", sizes[i]);
416
417 APPEND_NUM_FMT_STAT(fmt, i, "age", "%u", tails[i]->time);
418
419 APPEND_NUM_FMT_STAT(fmt, i, "evicted","%u", itemstats[i].evicted);
420
421 APPEND_NUM_FMT_STAT(fmt, i, "evicted_nonzero","%u", itemstats[i].evicted_nonzero);
422
423 APPEND_NUM_FMT_STAT(fmt, i, "evicted_time","%u", itemstats[i].evicted_time);
424
425 APPEND_NUM_FMT_STAT(fmt, i, "outofmemory","%u", itemstats[i].outofmemory);
426
427 APPEND_NUM_FMT_STAT(fmt, i, "tailrepairs","%u", itemstats[i].tailrepairs);;
428
429 APPEND_NUM_FMT_STAT(fmt, i, "reclaimed","%u", itemstats[i].reclaimed);;
430 }
431 }
432
433 /* getting here means both ascii and binary terminators fit */
434 add_stats(NULL, 0, NULL, 0, c);
435 }
436
437
438 /** dumps out a list of objects of each size, with granularity of 32 bytes */
439 /*@null@*/
440 void do_item_stats_sizes(ADD_STAT add_stats, void *c) {
441
442 /* max 1MB object, divided into 32 bytes size buckets */
443 const int num_buckets = 32768;
444 unsigned int *histogram = calloc(num_buckets, sizeof(int));
445
446 if (histogram != NULL) {
447 int i;
448
449 /* build the histogram */
450 for (i = 0; i < LARGEST_ID; i++) {
451 item *iter = heads[i];
452 while (iter) {
453 int ntotal = ITEM_ntotal(iter);
454 int bucket = ntotal / 32;
455 if ((ntotal % 32) != 0) bucket++;
456 if (bucket < num_buckets) histogram[bucket]++;
457 iter = iter->next;
458 }
459 }
460
461 /* write the buffer */
462 for (i = 0; i < num_buckets; i++) {
463 if (histogram[i] != 0) {
464 char key[8];
465 int klen = 0;
466 klen = snprintf(key, sizeof(key), "%d", i * 32);
467 assert(klen < sizeof(key));
468 APPEND_STAT(key, "%u", histogram[i]);
469 }
470 }
471 free(histogram);
472 }
473 add_stats(NULL, 0, NULL, 0, c);
474 }
475
476
477 //获取item
478 item *do_item_get(const char *key, const size_t nkey) {
479 item *it = assoc_find(key, nkey);
480 int was_found = 0;
481
482 if (settings.verbose > 2) {//输出调试信息
483 if (it == NULL) {
484 fprintf(stderr, "> NOT FOUND %s", key);
485 } else {
486 fprintf(stderr, "> FOUND KEY %s", ITEM_key(it));
487 was_found++;
488 }
489 }
490
491 //忽略比设置日期早的item
492 if (it != NULL && settings.oldest_live != 0 && settings.oldest_live <= current_time && it->time <= settings.oldest_live) {
493 do_item_unlink(it); /* MTSAFE - cache_lock held */
494 it = NULL;
495 }
496
497 if (it == NULL && was_found) {
498 fprintf(stderr, " -nuked by flush");//被忽略错误信息
499 was_found--;
500 }
501
502 if (it != NULL && it->exptime != 0 && it->exptime <= current_time) {//过期
503 do_item_unlink(it); /* MTSAFE - cache_lock held */
504 it = NULL;
505 }
506
507 if (it == NULL && was_found) {
508 fprintf(stderr, " -nuked by expire");//过期错误
509 was_found--;
510 }
511
512 if (it != NULL) {
513 it->refcount++;
514 DEBUG_REFCNT(it, '+');
515 }
516
517 if (settings.verbose > 2)
518 fprintf(stderr, "\n");
519
520 return it;
521 }
522
523
524 //获取一个item,不论过期与否
525 item *do_item_get_nocheck(const char *key, const size_t nkey) {
526 item *it = assoc_find(key, nkey);
527 if (it) {
528 it->refcount++;
529 DEBUG_REFCNT(it, '+');
530 }
531 return it;
532 }
533
534
535 //flush all items
536 void do_item_flush_expired(void) {
537 int i;
538 item *iter, *next;
539 if (settings.oldest_live == 0)
540 return;
541 for (i = 0; i < LARGEST_ID; i++) {
542 /* The LRU is sorted in decreasing time order, and an item's timestamp
543 * is never newer than its last access time, so we only need to walk
544 * back until we hit an item older than the oldest_live time.
545 * The oldest_live checking will auto-expire the remaining items.
546 */
547 for (iter = heads[i]; iter != NULL; iter = next) {
548 if (iter->time >= settings.oldest_live) {
549 next = iter->next;
550 if ((iter->it_flags & ITEM_SLABBED) == 0) {//没有被释放,unlink
551 do_item_unlink(iter);
552 }
553 } else {
554 break;
555 }
556 }
557 }
558 }

[转载]SQL Server Assembly (SQL CLR) 还原数据库后的问题

mikel阅读(1116)

[转载]SQL Server Assembly (SQL CLR) 还原数据库后的问题 – Sai~ – 博客园.

前端时间给别人做迁移数据库时候,遇到一些问题.大致是,如果备份的数据库存在EXTERNAL_ACCESS 和UNSAFE的程序集,那么在还原的时候程序集会出现一些奇怪的错误:

消息 10314,级别 16,状态 11,第 1 行
在 尝试加载程序集 ID 65536 时 Microsoft .NET Framework 出错。服务器可能资源不足,或者不信任该程序集,因为它的 PERMISSION_SET 设置为 EXTERNAL_ACCESS 或 UNSAFE。请重新运行查询,或检查有关的文档了解如何解决程序集信任问题。有关此错误的详细信息:
System.IO.FileLoadException: 未能加载文件或程序集“testclr, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null”或它的某一个依赖项。异常来自 HRESULT:0x80FC80F1
System.IO.FileLoadException:
在 System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
在 System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
在 System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
在 System.Reflection.Assembly.Load(String assemblyString)

至 于产生的原因大概是,在备份数据库的时候,在机器A,那么数据库的拥有者是A\Administrator(如果用windows登录创建),那么但是我 们还原到服务器B,那么拥有者可能是B\Administrator,那么SQL CLR的安全性会认为该程序集不可靠.

例如:

我首先创建一个简单的SQL CLR 存储过程:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;


public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void Test()
    {
        // 在此处放置代码
    }
};
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas,”Courier New”,courier,monospace; background-color: rgb(255, 255, 255); }.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }编译创建程序集.

机器A上的数据库上执行:

CREATE DATABASE test;

USE test;

ALTER DATABASE test SET TRUSTWORTHY ON;

CREATE ASSEMBLY [TestCLR] FROM 'E:\Documents\Visual Studio 2010\Projects\TestCLR\TestCLR\bin\Release\TestCLR.dll' WITH PERMISSION_SET = EXTERNAL_ACCESS;
--SAFE;
CREATE PROC dbo.usp_test
AS
EXTERNAL NAME [TestCLR].StoredProcedures.Test;

EXEC dbo.usp_test;

USE master;

BACKUP DATABASE test TO DISK = 'c:\test.bak' WITH FORMAT;

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas,”Courier New”,courier,monospace; background-color: rgb(255, 255, 255); }.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }将c:\test.bak copy 到机器B上,然后执行:

USE [master];
--还原数据库
RESTORE DATABASE test FROM DISK = 'c:\test.bak' WITH RECOVERY,
MOVE 'test' TO 'E:\data\test.mdf',
MOVE 'test_log' TO 'E:\data\test.ldf',REPLACE;

--如果没有启用CLR,开启
EXEC sp_configure 'clr enabled',1
RECONFIGURE WITH OVERRIDE;

USE test;
--查看程序集,是存在的.
SELECT * FROM sys.assemblies;
SELECT * FROM sys.assembly_files;
--还原之后的数据库TRUSTWORTHY 都是OFF的,需要重新设置
ALTER DATABASE test SET TRUSTWORTHY ON;

USE test;
--执行存储过程
EXEC dbo.usp_test;

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas,”Courier New”,courier,monospace; background-color: rgb(255, 255, 255); }.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }但是一执行就报错了.

解决方案:

在还原数据库之后,我们可以将数据库的OWNER设置成SA.

exec sp_changedbowner ‘sa’

再调用存储过程就是成功的.

可以查看:KB http://support.microsoft.com/kb/918040

后来经过一些整理,发现当SQL CLR 存在EXTERNAL_ACCESS或者是UNSAFE的程序集的时候,SQL Server会检查DBO的SID在sys.databases 和sys.server_principals是否一致.

因此我们可能未必一定要修改成sa 的,只要所有者的SID在sys.databases和sys.server_principals 是一致的,就不出问题.

我们在SSMS里面右键数据库属性->找到文件选项卡->发现在所有者(是空的,还原以后原来的SID,数据库所有者在当前的 sys.server_principals不匹配的),我们可以在 […] 里面选择一个,具有创建CREATE ASSEMLY 权限的所有者就好,我选择了B\Administrator,然后测试 CLR 存储过程,没问题,

引深:

在SQL Server 复制里面也存在类似的问题,就是我们做 “对等复制” 的时候,会出现DBO不存在,以及sp_replcmd 不存在类似的错误.其实也是因为对等复制初始化订阅是通过 RESTORE 来实现的,因此只要简单的修改数据库所有者 就好了….那么对等复制的问题也就解决了!!

[转载]使用 SQL的 for xml path来进行字符串拼接

mikel阅读(1129)

[转载]使用 SQL的 for xml path来进行字符串拼接 – Repository – 博客园.

本篇主要讲怎么利用SQLFOR XML PATH 参数来进行字符串拼接,FOR XML PATH的用法很简单,它会以xml文件的形式来返回数据。

我的讲解步骤:

1:构造初始数据

2:提出问题

3:简单介绍FOR XML PATH

4:解答问题

1.构造初始数据

举出一个经典的学生课程例子,共有学生、课程与学生课程三张表。

表1:Student

student_id student_name
1 张三
2 李四
3 王五

表2:Course

course_id course_name
1 语言
2 数学
3 英语

表3:Student_Course

student_id course_id
1 2
1 3
2 1
2 3
3 3

脚本:

create table student
(
student_id
int primary key,
student_name
nvarchar(50) not null
)

create table course
(
course_id
int primary key,
course_name
nvarchar(50) not null
)

create table student_course
(
student_id
int not null,
course_id
int not null,
primary key(student_id,course_id)
)

2.提出问题

写一条SQL语句,查询显示出下列结果:

student_name course_name
张三 数学,英语
李四 语言,英语
王五 英语

3.简单介绍 FOR XML PATH

FOR XML PATH 语句能够把查询的数据生成XML数据,举个例子,针对student表,以前SQL语句的查询结果为:
select str(student_id) + , + student_name from student for xml path(student)

查询结果:

<student> 1,张三</student>
<student> 2,李四</student>
<student> 3,王五</student>

student已成为一个xml文件中的结点了,再看看FOR XML PATH(”)的效果,针对上述SQL作出修改,

select str(student_id) + , + student_name from student for xml path()

查询结果:

1,张三 2,李四 3,王五

看得出来,这个参数自动把我们的查询结果串接在一起了,这下子,要做字符串拼接就很简单了!

4. 解答问题

要查询想要的结果,我们首先用一般的SQL语句,连接三个表之后的结果为:

select a.student_name,b.course_name from student_course c,student a,course b where
c.student_id
=a.student_id and c.course_id=b.course_id

查询结果:

student_name course_name
张三 数学
张三 英语
李四 语文
李四 英语
王五 英语

我们把这个查询结果看作为一个临时表,与自身进行一次连接,再得用FOR XML PATH(”)参数来对课程course_name列进行拼接,再得用子查询功能。这样就得到一个每一个学生的所选的所有课程,由于上表会存在同一学生的多条记录,所以需要对最后的结果按学生进行分组,先看看查询语句:

select student_name,
(select course_name+',' from
(
select student_name,course_name from
(
select a.student_name,b.course_name from stud_course c,student a,course b where c.student_id=a.student_id and c.course_id=b.course_id
) as a
) as b where c.student_name=b.student_name for xml path('')
) as course_name
from
(
select a.student_name,b.course_name from student_course c,student a,course b where c.student_id=a.student_id and c.course_id=b.course_id
) as c  group by student_name

查询结果:

student_name course_name
张三 数学,英语,
李四 语言,英语,
王五 英语,

还有个小问题, course_name后面多出一个,号,最后做一次裁剪,假设上面的SQL语句作为一个子查询 subquery

select student_name,left(course_name,len(course_name)1) from (……..) as subquery

这样,就可以得出最终的结果!可以看得出来FOR XML PATH(”) 参数非常强大!

PS:有很多人把这个叫行转列,我个人并不这么认为,虽然这和行转列有点像,但是这更像是字符串拼接!就把它这么叫好了!

PS:我的测试环境:SQL server 2008

[转载]jQuery1.5的改进细节

mikel阅读(1270)

[转载]jQuery1.5的改进细节 – Novice Doodle from Gray Zhang – 博客园.

JQuery 1.5 beta1出来了,从学习跟进上来说,这一次已经比较晚了(我竟然不知道1.5什么时候出的alpha,就这么beta了)。

这个1.5版本最大的更新是AJAX的完全重写,提供了更强的可扩展性。但是受制于精力和篇幅,对新的AJAX的分析还是放到下回,本篇先简单介绍一下细节方面的改进。

JQuery._Deferred和jQuery.Deferred

首先不得不说这两个新生事物,因为他们是作为基础设施存在,不把这两个东西讲明白了,有些问题根本没办法解释。

首先,jQuery.Deferred是jQuery._Deferred的增强版,因此对于这个问题,从jQuery._Deferred入手,就能说明一大半的问题。

什么是Deferred?从字面上看,我的第一反应是“延迟加载”,首字母大写的应该是“类型”的定义,所以这大概是一个“透明提供延迟加载功能”的类型吧。然而实际上,虽然确实带有那么一点点“延迟”的意思,这个东西却不是用来实现延迟加载的。

简单来说,jQuery._Deferred是一个函数队列,他的作用有以下几点:

  • 保存若干个函数。
  • 在特定的时刻把保存着的函数全部执行掉。
  • 执行过后,新进来的函数会立刻执行。

感觉是不是和啥东西很像?对,jQuery的ready函数就是这样的逻辑,实际中jQuery 1.5中的ready函数也确实被嫁接到这上面去了。

jQuery._Deferred提供下面的接口:

  • done:function(fn1, fn2, …)的形式,用于把函数添加到队列中。
  • fire:function(context, args)的形式,使用context指定this对象,args指定参数,调用队列中所有函数。fire被调用后,_Deferred会进入 isResolved状态,未来对done的调用不会再保存函数,而是直接调用函数。
  • resolve:相当于调用fire(this, arguments),一个简化的方法。
  • isResolved:用来判断_Deferred是否在isResolved状态,具体参考前面的fire函数的解释。
  • cancel:取消掉整个队列,这样不管未来是不是fire,队列中的函数都不会再被调用。

说明白了jQuery._Deferred,再来看看jQuery.Deferred。这个东西其实就是2个_Deferred组成的,第一个称为 deferred,用于保管“正常”状态下的函数;第二个称为failDeferred,用于保管“出错”状态下的函数。同时 jQuery.Deferred提供了一些新的接口:

  • then:function(done, fail)的形式,把done添加进deferred,把fail添加进failedDeferred。
  • fail:相当于failDeferred的done函数。
  • fireReject:相当于failDeferred的fire函数。
  • reject:相当于failDeferred的resolve函数。
  • isRejected:相当于failDeferred的isResolved函数。

同时jQuery.Deferred取消了cancel函数。

那么这个是啥用的呢?有“正常”和“出错”2个状态,同时又是异步的,很容易就能想到……对,给AJAX用的,在下一篇分析中再详细说明。

jQuery.ready的变化

因为有了jQuery._Deferred这个东西,jQuery.ready函数变成依赖于函数队列,具体的变化有:

原来的readyList变量已经不再是一个数组,而变成了jQuery._Deferred对象。

原本在DOMContentLoaded时,调用readList中所有函数的逻辑,现在也使用了jQuery._Deferred中,原来的代码:

while ( (fn = ready[ i++ ]) ) {
    fn.call( document, jQuery );
}

变成了:

readyList.fire( document , [ jQuery ] );

jQuery.parseXML函数

新增了静态函数jQuery.parseXML,用于提供浏览器兼容的从字符串转为XML文档的功能。

该函数的逻辑网上有很多,jQuery也没有特别的地方,大致分为以下2种:

  • 对于标准浏览器,使用DOMParser对象:
    var parser = new DOMParser();
    var xml = parser.parseFromString(text, 'text/html');
  • 对于IE,使用Microsoft.XMLDOM对象:
    var parser = new ActiveXObject('Microsoft.XMLDOM');
    parser.async = 'false';
    parser.loadXML(text);
    var xml = parser.documentElement;

data部分

添加了jQuery.hasData函数,用于判断一个元素是否有jQuery附加上去的数据。

修改了jQuery.expando的实现,在原来单纯地取当前时间的基础上,添加了一个随机数:

expando = "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" );

这样保证在同一时间,引入多个jQuery副本,这几个副本之间的expando不会相互冲突,导致元素上的data变得错乱。一般来说,是不会引入多个jQuery副本的,但是使用SealJS等的时候,配置不当的话,也是很容易出现此类问题的。

DOM操作部分

原本的hasClass、addClass、removeClass函数都需要将元素的class属性分隔为数组,在1.4.4版本中,通过\n或\t进行分隔,在1.5中增加了一个\r,用于对应Windows平台下的换行符(\r\n)。

jQuery.fn.attr函数,1.4.4版本中拒绝从TextNode和CommentNode上获取属性,在1.5版本中添加了一个AttributeNode(noteType == 2)。

在1.4.4版本中,jQuery会在页面unload的时候清理掉由jQuery维护的所有DOM事件,这是为了避免IE的内存泄露问题。但是在1.5中这一段代码不见了,不知是出于什么考虑。

对于IE下使用cloneNode复制节点,会将事件也一起复制过来的问题,1.4.4中是采取复制innerHTML的方式给予解决,而在1.5中则采纳了mootools团队提供的方法,使用cloneFixAttribute函数修正该问题。

cloneFixAttribute函数们于jQuery 1.5 beta1源码文件的5388-5438行,处理IE的BUG的原理很简单,当然前端里一些看似简单的东西,都是很难发现的:

  1. IE中有个叫clearAttributes的函数,会清除到节点上的所有属性,顺便把和事件相关的onclick之类的属性也去掉了。在复制出来的节点上调用这个函数,就会把属性清得干干净净。
  2. IE中还有一个叫mergeAttributes的函数,把一个节点的属性复制到另一个节点上,但他不会把和事件相关的属性复制过去。所以再把原始节点调用mergeAttributes,把属性重新放回复制出来的节点上,这就相当于起到了去除事件相关属性的作用。

另外cloneFixAttribute函数还处理了非常多IE6-8在cloneNode上的兼容性问题,非常值得详细研究。

AJAX部分

AJAX已经完全重写了,只留下一点边边角角保留着1.4.4版本的风采,这里只抽取一部分进行简单的说明。

原来版本中$.get和$.post的实现非常相似,具体来说仅有一个method配置项不同,因此在1.5版本中被合并起来了:

$.each(['get', 'post'], function(i, method) {
    $[method] = function() { ... };
});

ajaxSetup函数现在加了一行return this;,可以链式调用了。

serializeArray函数现在统一将value中的换行符替换成Windows的风格(\r\n)。

AJAX的回调函数中,作为参数的对象不再是原生的XMLHTTPRequest,而是jQuery自己封装的称为jXHR的对象,这个对象提供了XMLHTTPRequest的常用接口。

原本对于“请求成功”的浏览器状态码,除200-299以及304外,还有一个1223,来自于IE的一个BUG,会将204的状态码变成 1223。现在因为有了jXHR对象,相当于中间多了一层,因此从jXHR对象获取statusCode不会出现1223的情况,已经被变回204了。

jQuery.ajax函数的配置项中多了一个statusCode项,其结构为map,用于指定返回特定状态码时的回调函数,大致形式如下:

jQuery.ajax({
    url: 'xxx',
    statusCode: {
        200: function() { 处理请求成功 },
        404: function() { 处理页面未找到 },
        503: function() { 处理Service Unavailable }
    }
});

再添加了这个回调后,jQuery.ajax函数已经有非常多的回调函数,其触发过程如下:

  1. 根据返回的状态码,触发success或者error回调。
  2. 根据状态码,触发对应的statusCode回调。
  3. 触发complete回调。
  4. 触发全局ajaxComplete回调。
  5. 如果此时没有正在执行的AJAX,触发全局ajaxStop回调。

其他细节

入口函数jQuery.fn.init现在多了一个参数,值始终为rootjQuery,用于加速init函数中对rootjQuery变量的查找速度(减少了一层作用域):

//jQuery 1.5 beta1 源码23行
jQuery = function( selector, context ) {
    // The jQuery object is actually just the init constructor 'enhanced'
    return new jQuery.fn.init( selector, context, rootjQuery );
}

jQuery对象支持继承了,具体的修改是将几处直接调用jQuery的代码改为了对this.constructor的调用:

202行:return this.constructor( context ).find( selector );
253行:var ret = this.constructor();
334行:return this.prevObject || this.constructor(null);

同时还提供了jQuery.subclass函数用于创建一个继承自jQuery的类型,由于不是很常用jQuery,更是从来没有用到过需要继承jQuery的情况,因此也不方便说这个功能的作用有多大。