浏览器与服务器交互原理以及用java模拟浏览器操作

* 1,在HTTP的WEB应用中, 应用客户端和服务器之间的状态是通过Session来维持的, 而Session的本质就是Cookie,
* 简单的讲,当浏览器向服务器发送Http请求的时候, HTTP服务器会产生一个SessionID,这个SessionID就唯一的标识了一个客户端到服务器的请求会话过程.
* 就如同一次会议开始时,主办方给每位到场的嘉宾一个临时的编号胸牌一样, 可以通过这个编号记录每个嘉宾(客户端)的活动(请求状态).
* 为了保持这个状态, 当服务端向客户端回应的时候,会附带Cookie信息,当然,Cookie里面就包含了SessionID
* 客户端在执行一系列操作时向服务端发送请求时,也会带上这个SessionID, 一般来说,Session也是一个URL QueryParameter ,就是说,session可以以Key-Value的形式通过URL传递
* 比如,http://www.51etest.com/dede/login.php?PHPSESSIONID=7dg3dsf19SDf73wqc32fdsf
* 一般而言,浏览器会自动把此Session信息放入Header报文体中进行传递.
* 如果浏览器不支持Cookie,那么,浏览器会自动把SessionID附加到URL中去.
*
* 2,在这个例子中,以登陆这个功能点进行讲解.
* 首先,我们登陆的页面是http://www.51etest.com/dede, 我们第一次访问这个页面后,可以从服务器过来的Http Response报文中的Header中找出服务器与浏览器向关联的数据 — Cookie,
* 而且Session的值也在Cookie中. 于是,我们可以通过分析Set-Cookie这个Header中的参数的值,找到Seesion的Key-Value段.
* 然后,我们再向服务器发送请求,请求URL为:post@@http://www.51etest.com/dede/login.php@@userid=admin&pwd=tidus2005&gotopage=/dede/&dopost=login
* 服务器验证登陆成功了, 并且在此次会话变量中增加了我们登陆成功的标识.
*
* 3,增加一个广告定义
* 增加一个广告定义其实就是一个添加数据的过程,无非是我们把我们要添加的数据通过参数的形式告诉指定url页面,页面获取后添加到数据库去而已.
* 此url地址为:
* post@@http://www.51etest.com/dede/ad_add.php@@dopost=save&tagname=test&typeid=0&adname=test&starttime=2008-05-29
* 因为这个页面会先判断我是否登陆
* 而判断的依据,前面讲了,就是根据我请求时的SessionID找到指定的Session数据区中是否存在我的登陆信息,
* 所以我当然要把访问登陆页面时获取的SessionID原封不动的再发回去
* 相当于对服务器说,这是我刚刚来时,你发我的临时身份证,我现在可以形势我的权利。
*
* 这就是整个Java后台登陆网站,然后添加数据的过程。

 
/** 
 *  
 */ 
package sky.dong.test; 
 
import java.io.BufferedReader; 
import java.io.InputStreamReader; 
 
import org.apache.commons.httpclient.Cookie; 
import org.apache.commons.httpclient.Header; 
import org.apache.commons.httpclient.HttpClient; 
import org.apache.commons.httpclient.NameValuePair; 
import org.apache.commons.httpclient.cookie.CookiePolicy; 
import org.apache.commons.httpclient.methods.PostMethod; 
import org.apache.commons.httpclient.params.HttpMethodParams; 
 
/** 
 * @author 核弹头 
 * Email:happyman_dong@sina.com 版权所有 盗版必究 
 * @since 2009-8-11 
 * @version 1.0 
 */ 
public class HttpLoginTest { 
 
    public static void main(String[] args) { 
        String url = "http://discuzdemo.c88.53dns.com/logging.php?action=login&loginsubmit=yes&floatlogin=yes";//论坛的登陆页面 
        String url2="http://discuzdemo.c88.53dns.com/post.php?infloat=yes&action=newthread&fid=2&extra=&topicsubmit=yes&inajax=1";//论坛的发贴页面 
        HttpClient httpClient = new HttpClient(); 
        //httpClient.getHostConfiguration().setProxy("222.247.62.195", 8080); 
        httpClient.getParams().setCookiePolicy( 
                CookiePolicy.BROWSER_COMPATIBILITY); 
        PostMethod postMethod = new PostMethod(url); 
        PostMethod postMethod2 = new PostMethod(url2); 
        NameValuePair[] data = { 
                new NameValuePair("username", "123"), 
                new NameValuePair("referer", 
                        "http://discuzdemo.c88.53dns.com/index.php"), 
                new NameValuePair("password", "123"), 
                new NameValuePair("loginfield", "username"), 
                new NameValuePair("questionid", "0"), 
                new NameValuePair("formhash", "fc922ca7") }; 
        postMethod.setRequestHeader("Referer", 
                "http://discuzdemo.c88.53dns.com/index.php"); 
        postMethod.setRequestHeader("Host", "discuzdemo.c88.53dns.com"); 
        // postMethod.setRequestHeader("Connection", "keep-alive"); 
        // postMethod.setRequestHeader("Cookie", "jbu_oldtopics=D123D; 
        // jbu_fid2=1249912623; smile=1D1; jbu_onlineusernum=2; 
        // jbu_sid=amveZM"); 
        postMethod 
                .setRequestHeader( 
                        "User-Agent", 
                        "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2"); 
        postMethod 
                .setRequestHeader("Accept", 
                        "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); 
        // postMethod.setRequestHeader("Accept-Encoding", "gzip,deflate"); 
        // postMethod.setRequestHeader("Accept-Language", "zh-cn"); 
        // postMethod.setRequestHeader("Accept-Charset", 
        // "GB2312,utf-8;q=0.7,*;q=0.7"); 
        postMethod.setRequestBody(data); 
        try { 
            httpClient.executeMethod(postMethod); 
            StringBuffer response = new StringBuffer(); 
            BufferedReader reader = new BufferedReader(new InputStreamReader( 
                    postMethod.getResponseBodyAsStream(), "gb2312"));//以gb2312编码方式打印从服务器端返回的请求 
            String line; 
            while ((line = reader.readLine()) != null) { 
                response.append(line).append( 
                        System.getProperty("line.separator")); 
            } 
            reader.close(); 
            Header header = postMethod.getResponseHeader("Set-Cookie"); 
            Cookie[] cookies=httpClient.getState().getCookies();//取出登陆成功后,服务器返回的cookies信息,里面保存了服务器端给的“临时证” 
            String tmpcookies=""; 
            for(Cookie c:cookies){ 
                tmpcookies=tmpcookies+c.toString()+";"; 
                System.out.println(c); 
            } 
            System.out.println(tmpcookies); 
//            System.out.println(header.getValue()); 
            System.out.println(response); 
            NameValuePair[] data2 = { 
                    new NameValuePair("subject", "测试自动发贴"), 
                    new NameValuePair("message", 
                            "能否发贴成功呢?测试一下就知道了"), 
                    new NameValuePair("updateswfattach", "0"), 
                    new NameValuePair("wysiwyg", "0"), 
                    new NameValuePair("checkbox", "0"), 
                    new NameValuePair("handlekey", "newthread"), 
                    new NameValuePair("formhash", "885493ec") }; 
            postMethod2.setRequestHeader("cookie",tmpcookies);//将“临时证明”放入下一次的发贴请求操作中 
            postMethod2.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "gbk");//因为发贴时候有中文,设置一下请求编码 
            postMethod2.setRequestHeader("Referer", 
                    "http://discuzdemo.c88.53dns.com/forumdisplay.php?fid=4"); 
            postMethod2.setRequestHeader("Host", "discuzdemo.c88.53dns.com"); 
            // postMethod.setRequestHeader("Connection", "keep-alive"); 
            // postMethod.setRequestHeader("Cookie", "jbu_oldtopics=D123D; 
            // jbu_fid2=1249912623; smile=1D1; jbu_onlineusernum=2; 
            // jbu_sid=amveZM"); 
            postMethod2 
                    .setRequestHeader( 
                            "User-Agent", 
                            "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2"); 
            postMethod2 
                    .setRequestHeader("Accept", 
                            "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");//以上操作是模拟浏览器的操作,使用服务器混淆 
             
            postMethod2.setRequestBody(data2); 
            httpClient.executeMethod(postMethod2); 
            StringBuffer response1 = new StringBuffer(); 
            BufferedReader reader1 = new BufferedReader(new InputStreamReader( 
                    postMethod2.getResponseBodyAsStream(), "gb2312")); 
            String line1; 
            while ((line1 = reader1.readLine()) != null) { 
                response1.append(line1).append( 
                        System.getProperty("line.separator")); 
            } 
            reader1.close(); 
            System.out.println(response1); 
        } catch (Exception e) { 
            System.out.println(e.getMessage()); 
            // TODO: handle exception 
        } finally { 
            postMethod.releaseConnection(); 
            postMethod2.releaseConnection(); 
        } 
 
    } 
 
} 
 

以上代码完成一个登陆论坛后在指定的版块自动发贴的功能

 

android 如何保存簡單的配置信息(SharedPreferences、File和Properties)

我們知道在android的開發中,保存項目私有數據的存儲方式我們可以使用:SharedPreferences,File,SQLite,Network.四種方式,而要用到應
用程序之間數據的共享要使用ContentProvider 。那今天我們只敘述一下僅僅保存一些我們登錄等的一些配置信息的數據,也就是說用到的數
據量都不是很大,那麼我們就可以選擇SharedPreferences和File的方式。這裡只針對性的結合File和Properties進行敘述。
一。SharedPreferences

1. 它可以保存上一次用戶所做的修改或者自定義參數的設定,當再次啟動程序後依然可以保持原有的設置。這裡只說明一下使用方式。比如下
面的代碼在OnCreate中使用:

SharedPreferences mSharedPreferences = getSharedPreferences(“list”,MODE_PRIVATE);

String mTempString = mSharedPreferences.getString(“config”,”default”);

其中”list”是SharedPreferences的文件的名字,SharedPreferences是以鍵值映射的關係存放數據。不過多解釋,你也可以這樣用:

SharedPreferences mSharedPreferences = getPreferences(MODE_PRIVATE);

這樣默認的文件名是activity的名字。

2. 退出activity的時候保存數據,在OnPause中使用:

SharedPreferences mSharedPreferences = getSharedPreferences(“list”,    MODE_PRIVATE);

mSharedPreferences.edit().putString(“config”,”data” ).commit();

3. SharedPreferences 是以xml文件的方式自動保存的,在DDMS中的FileExplorer中展開/data/data/包名/shared-prefs下面就是SharedPreferences文件。

4. SharedPreferences文件只可以用來存放基本的數據類型。
二。結合File和Properties進行保存。

A Properties object is a Hashtable where the keys and values must be Strings. Each property can have a default Properties list which specifies the default values to be used when a given key is not found in this Properties instance.

1.所以,Properties對象也是一個哈希表,也是一個鍵值對應的關係,因此和上面的操作相似。下面看具體的程序。

public class File_ByProperties extends Activity {

private boolean mStatus;

private TextView mShowStatus;
/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

mShowStatus = (TextView) findViewById(R.id.show);

load();

}
private void load() {

// TODO Auto-generated method stub

Properties mProperties = new Properties();

try {

FileInputStream mInputStream = openFileInput(“configuration”);

mProperties.load(mInputStream);

mStatus = Boolean.valueOf(mProperties.get(“status”).toString());

mShowStatus.setText(“the status is : ” + mStatus);

} catch (FileNotFoundException e) {

// TODO Auto-generated catch block

System.out.println(e.toString());

} catch (IOException e) {

System.out.println(e.toString());

}

}
@Override

public boolean onKeyDown(int keyCode, KeyEvent event) {

// TODO Auto-generated method stub

if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {

mStatus = !mStatus;

mShowStatus.setText(“the status is : ” + mStatus);

}

return super.onKeyDown(keyCode, event);

}
@Override

protected void onPause() {

// TODO Auto-generated method stub

super.onPause();

Properties mProperties = new Properties();

if (mProperties.containsKey(“status”)) {

mProperties.remove(“status”);

}

mProperties.put(“status”, String.valueOf(mStatus));

try {

FileOutputStream mOutputStream = openFileOutput(“configuration”,     MODE_WORLD_WRITEABLE);

mProperties.store(mOutputStream, null);

} catch (FileNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}
}

2. 在DDMS中的FileExplorer中展開/data/data/包名/files,可以查看到該文件。
三。你還可以將一個靜態的文件放到res/raw/下面,然後通過getResources().openRawResource(R.raw.文件);來得到一個InputStream對象,然後讀取文件的內容。

开发 Eclipse 插件

简介: 在本文中,David Gallardo 向您展示了如何使用 Plug-in Development Environment 的代码生成向导来创建 Eclipse 插件。您将学到如何在运行时工作台中运行和调试插件,并且在 Eclipse 中安装完成的插件。David 还研究了与打包插件相关的问题 ― 包括维护版本信息、以插件片段的形式更新功能,以及组合插件来创建完整的功能部件。

 

基于插件的体系结构

Eclipse 平台是 IBM 向开发源码社区捐赠的开发框架,它之所以出名并不是因为 IBM 宣称投入开发的资金总数 ― 4 千万美元 ― 而是因为如此巨大的投入所带来的成果:一个成熟的、精心设计的以及可扩展的体系结构。Eclipse 的价值是它为创建可扩展的集成开发环境提供了一个开放源码平台。这个平台允许任何人构建与环境和其它工具无缝集成的工具。

工具与 Eclipse 无缝集成的关键是插件。除了小型的运行时内核之外,Eclipse 中的所有东西都是插件。从这个角度来讲,所有功能部件都是以同等的方式创建的。从这个角度来讲,所有功能部件都是以同等的方式创建的。

但是,某些插件比其它插件更重要些。Workbench 和 Workspace 是 Eclipse 平台的两个必备的插件 ― 它们提供了大多数插件使用的扩展点,如图 1 所示。插件需要扩展点才可以插入,这样它才能运行。
图 1. Eclipse Workbench 和 Workspace:必备的插件支持
Eclipse Workbench

Workbench 组件包含了一些扩展点,例如,允许您的插件扩展 Eclipse 用户界面,使这些用户界面带有菜单选择和工具栏按钮;请求不同类型事件的通知;以及创建新视图。Workspace 组件包含了可以让您与资源(包括项目和文件)交互的扩展点。

当然,其它插件可以扩展的 Eclipse 组件并非只有 Workbench 和 Workspace。此外,还有一个 Debug 组件可以让您的插件启动程序、与正在运行的程序交互,以及处理错误 ― 这是构建调试器所必需的。虽然 Debug 组件对于某些类型的应用程序是必需的,但大多数应用程序并不需要它。

还有一个 Team 组件允许 Eclipse 资源与版本控制系统(VCS)交互,但除非您正在构建 VCS 的 Eclipse 客户机,否则 Team 组件,就象 Debug 组件一样,不会扩展或增强它的功能。

最后,还有一个 Help 组件可以让您提供应用程序的联机文档和与上下文敏感的帮助。没有人会否认帮助文档是专业应用程序必备的部分,但它并不是插件功能的必要部分。

上述每个组件提供的扩展点都记录在 Eclipse Platform Help 中,该帮助在 Platform Plug-in Developer 指南的参考部分中。乍一看,尤其是 API 参考大全的 Workbench 部分,一开始会令人望而却步。我们不会深入了解众多可用扩展点的详细信息,而只是粗略地看一个简单插件及其组件。

插件简介

创建插件最简单的方法是使用 Plug-in Development Environment(PDE)。PDE 和 Java Development Tooling(JDT)IDE 是 Eclipse 的标准扩展。PDE 提供了一些向导以帮助创建插件,包括我们将在这里研究的“Hello, world”示例。

从 Eclipse 菜单,选择 File=>New=>Other(或按 Ctrl-N),然后选择 Select 对话框左边的 Plug-in Development 向导。在 Select 对话框的右边,选择 Plug-in Project。按 Next。在下一屏上,输入项目名称;我使用了 com.example.hello。再次按 Next。在下一屏上,请注意,插件标识就与项目名称相同。使用项目名称作为插件标识可以将该插件与另一个插件的名称发生冲突的机会减到最小。再按一次 Next。下一屏让您选择是手工创建初始插件代码,还是运行代码生成向导。保留代码生成向导的缺省选项,选择“Hello, World”,然后按 Next,如图 2 所示。
图 2. 选择“Hello, World”代码生成向导
新插件

下一屏要求一些附加信息。请注意这一屏上的信息:它包含了插件名称、版本号、提供者名称和类名。这些是关于插件的重要信息,我们将在稍后研究。可以接受向导提供的缺省值。按 Next。在下一屏幕上,接受包名、类名和消息文本的缺省值。选择“Add the action set to the resource perspective”复选框。按 Finish。

如果接到通知:向导需要启用某些其它插件才能完成,那么按 OK。

过一会儿,向导将完成,而在您的工作区中将会有一个新的项目,名为 com.example.hello,如图 3 所示。
图 3. PDE 透视图:Welcome to Hello Plug-in
PDE 透视图

在 Package Explorer 中,工作台的左边是向导创建的一些东西的概述。大多数项都不引人关注:包括项目类路径中的许多 .jar 文件(这些包括插件和 Java 运行时所需的 Eclipse 类)、一个图标文件夹(包含了工具栏按钮的图形),以及 build.properties 文件(包含自动构建脚本所使用的变量)。

这里最有意思的东西是 src 文件夹,它包含了插件和 plugin.xml 文件的源代码 ― plug-in.xml 是插件的清单文件。我们将先查看 plugin.xml。

插件清单文件

插件清单文件 plugin.xml 包含了 Eclipse 将插件集成到框架所使用的描述信息。缺省情况下,当第一次创建插件时,会在清单编辑器区域中打开 plugin.xml。编辑器底部的选项卡让您可以选择关于插件的不同信息集合。Welcome 选项卡显示了消息“Welcome to Hello Plug-In”,并且简要讨论了所使用的模板和关于使用 Eclipse 实现插件的提示。选择“Source”选项卡可以让您查看 plugin.xml 文件的完整源代码。

让我们看看插件清单文件的各个部分。首先是关于插件的常规信息,包括它的名称、版本号、实现它的类文件的名称和 .jar文件名。
清单 1. 插件清单文件 ― 常规信息

<?xmlversion="1.0" encoding="UTF-8"?>
<plugin
   id="com.example.hello"
   name="Hello Plug-in"
   version="1.0.0"
   provider-name="EXAMPLE"
  >

   <runtime>
      <library name="hello.jar"/>
   </runtime>

 

接着,列出了我们的插件所需的插件:
清单 2. 插件清单文件 ― 必需的插件

   <requires>
      <import plugin="org.eclipse.core.resources"/>
      <import plugin="org.eclipse.ui"/>
   </requires>

 

列出的第一个插件 org.eclipse.core.resources 是工作区插件,但实际上我们的插件并不需要它。第二个插件 org.eclipse.ui 是工作台。我们需要工作台插件,因为我们将扩展它的两个扩展点,正如后面的 extension 标记所指出的。

第一个 extension 标记拥有点属性 org.eclipse.ui.actionSets。操作集合是插件添加到工作台用户界面的一组基值 ― 即,菜单、菜单项和工具栏。操作集合分组了基值,这样用户可以更方便地管理它们。例如,我们的 Hello 插件的菜单和工具栏项将出现在 Resource 透视图中,因为当在运行代码生成向导时,我们做了这样的选择。如果用户要更改它,可以使用 Window=>Customize Perspective 菜单选项从要在 Resource 透视图中显示的项中除去“Sample Action Set”。
图 4. 定制 Resource 透视图
Resource 透视图

操作集合包含了两个标记:menu 标记(描述菜单项应该出现在工作台菜单的什么位置,以及如何出现)和action 标记(描述它应该做什么)― 尤其是 action 标记标识了执行操作的类。注:这个类不是上面列出的插件类。
清单 3. 操作集合

   <extension
         point="org.eclipse.ui.actionSets">
      <actionSet
            label="Sample Action Set"
            visible="true"
            id="com.example.hello.actionSet">
         <menu
               label="Sample &Menu"
               id="sampleMenu">
            <separator
                  name="sampleGroup">
            </separator>
         </menu>
         <action
               label="&Sample Action"
               icon="icons/sample.gif"

               tooltip="Hello, Eclipse world"
               menubarPath="sampleMenu/sampleGroup"
               toolbarPath="sampleGroup"
               id="com.example.hello.actions.SampleAction">
         </action>
      </actionSet>
   </extension>

 

许多菜单和操作属性的目的相当明显 ― 例如,提供工具提示文本和标识工具栏项的图形。但还要注意 action 标记中的menubarPath:这个属性标识了 menu 标记中定义的哪个菜单项调用 action 标记中定义的操作。有关这个和其它工作台扩展点的详细信息,请参考 Platform Plug-in Developer Guide,尤其是“Plugging into the workbench”章节(可以从 Eclipse 的帮助菜单中获取该指南)。

由于我们选择了将插件添加到 Resource 透视图,于是生成了第二个 extension 标记。这个标记会导致当 Eclipse 第一次启动并装入我们的插件时,将插件添加到 Resource 透视图。
清单 4. extension 标记

   <extension
         point="org.eclipse.ui.perspectiveExtensions">
      <perspectiveExtension
            targetID="org.eclipse.ui.resourcePerspective">
         <actionSet
               id="com.example.hello.actionSet">
         </actionSet>
      </perspectiveExtension>
   </extension>
</plugin>

 

如果忽略这最后一个 extension,用户就需要使用 Window=>Customize Perspective 将插件添加到 Resource(或其它)透视图。

插件源代码

代码生成向导生成了两个 Java 源文件,打开 PDE Package Explorer 中的 src 文件夹就可以看到它们。第一个文件HelloPlugin.java 是插件类,它继承了 AbstractUIPlugin 抽象类。HelloPlugin 负责管理插件的生命周期,在更为扩展的应用程序中,它负责维护诸如对话框设置和用户首选项等内容。HelloPlugin 要做的事就这么多:
清单 5. HelloPlugin

packagecom.example.hello.actions;

import org.eclipse.ui.plugin.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.resources.*;
import java.util.*;

/**
 * The main plugin class to be used in the desktop.
 */
public class HelloPlugin extends AbstractUIPlugin {
      //The shared instance.
      private static HelloPlugin plugin;
      //Resource bundle.
      private ResourceBundle resourceBundle;

      /**
       * The constructor.
       */
      public HelloPlugin(IPluginDescriptor descriptor) {
            super(descriptor);
            plugin = this;
            try {
                  resourceBundle= ResourceBundle.getBundle(
                       "com.example.hello.HelloPluginResources");
            } catch (MissingResourceException x) {
                  resourceBundle = null;
            }
      }

      /**
       * Returns the shared instance.
       */
      public static HelloPlugin getDefault() {
            return plugin;
      }

      /**
       * Returns the workspace instance.
       */
      public static IWorkspace getWorkspace() {
            return ResourcesPlugin.getWorkspace();
      }

      /**
       * Returns the string from the plugin's resource bundle,
       * or 'key' if not found.
       */
      public static String getResourceString(String key) {
            ResourceBundle bundle= HelloPlugin.getDefault().getResourceBundle();
            try {
                  return bundle.getString(key);
            } catch (MissingResourceException e) {
                  return key;
            }
      }

      /**
      * Returns the plugin's resource bundle,
      */
      public ResourceBundle getResourceBundle() {
          return resourceBundle;
      }
}

 

第二个源文件 SampleAction.java 包含的类将执行在清单文件的操作集合中指定的操作。SampleAction 实现了IWorkbenchWindowActionDelegate 接口,它允许 Eclipse 使用插件的代理,这样不是在万不得已的情况下,Eclipse 就无需装入插件(这项优化工作使在装入插件时发生内存和性能方面的问题降到最低)。IWorkbenchWindowActionDelegate 接口方法使插件可以与代理进行交互:
清单 6. IWorkbenchWindowActionDelegate 接口方法

package com.example.hello.actions;

import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import org.eclipse.jface.dialogs.MessageDialog;

/**
 * Our sample action implements workbench action delegate.
 * The action proxy will be created by the workbench and
 * shown in the UI. When the user tries to use the action,
 * this delegate will be created and execution will be
 * delegated to it.
 * @see IWorkbenchWindowActionDelegate
 */
public class SampleAction implements IWorkbenchWindowActionDelegate {
      private IWorkbenchWindow window;
      /**
       * The constructor.
       */
      public SampleAction() {
      }

      /**
       * The action has been activated. The argument of the
       * method represents the 'real' action sitting
       * in the workbench UI.
       * @see IWorkbenchWindowActionDelegate#run
       */
      public void run(IAction action) {
            MessageDialog.openInformation(
                  window.getShell(),
                  "Hello Plug-in",
                  "Hello, Eclipse world");
      }

      /**
       * Selection in the workbench has been changed. We
       * can change the state of the 'real' action here
       * if we want, but this can only happen after
       * the delegate has been created.
       * @see IWorkbenchWindowActionDelegate#selectionChanged
       */
      public void selectionChanged(IAction action, ISelection selection) {
      }

      /**
       * We can use this method to dispose of any system
       * resources we previously allocated.
       * @see IWorkbenchWindowActionDelegate#dispose
       */
      public void dispose() {
      }

      /**
       * We will cache window object in order to
       * be able to provide parent shell for the message dialog.
       * @see IWorkbenchWindowActionDelegate#init
       */
      public void init(IWorkbenchWindow window) {
            this.window = window;
      }
}

 

运行和调试插件

当开发 Eclipse 的插件时,必须停止 Eclipse 并用新的插件重新启动它以便进行测试和调试,这很笨拙。幸好,Eclipse PDE 提供了一个自托管(self-hosted)的开发环境,它让您无需将插件安装在工作台的单独实例中即可运行。

要运行 Hello 插件,选择 Run=>Run As=>Run-time Workbench 来启动另一个 Workbench 实例,而该实例添加了插件的菜单选项和工具栏,如图 5 所示。
图 5. 在运行时工作台中运行的 Hello 插件
Hello 插件

我们可以通过单击工具栏按钮或从“Sample Menu”菜单激活插件。任何一种方法都会生成一个框,其标题是“Hello Plug-in”,内容是“Hello, Eclipse world”,以及一个 OK 按钮,按该按钮可以关闭这个框。

通过选择 Run=>Debug As=>Run-time Workbench,按类似的方法调试插件。这次,当插件在第二个工作台实例中运行时,我们可以在最初的工作台中单步执行源代码,以及检查变量等。

一旦插件经过测试并准备发布,我们就需要将它适当打包,以便在 Eclipse 中安装。

打包插件

Eclipse 在启动时会查看其插件目录来确定要装入哪些插件。要安装插件,我们需要在插件目录中创建一个子目录,并将程序文件和清单文件复制到那里。建议目录名称能表示插件的标识,并且后面跟下划线和版本号,但是这种做法不是必需的。假设 Eclipse 安装在 C:\eclipse 中;我们要创建一个目录:

C:\eclipse\plugins\com.example.hello_1.0.0.

按照 Java 程序的标准,我们的程序文件需要归档到 .jar文件中 — 我们的插件清单文件,您也许记得它包含这个项:

   <runtime>
      <library name="hello.jar"/>
   </runtime>

 

要创建 hello.jar 文件,我们可以通过突出显示项目名称,并从 Eclipse 菜单选择 File=>Export,以导出插件文件。选择 JAR 文件作为导出方式,按 Next,然后浏览到我们为它创建的目录。下一步,我们还需要将 plugin.xml 文件复制到这个目录。也可以使用File=>Export 菜单选项(但请要记住选择 File System 作为导出目的地)。

这就是安装插件所需的全部操作,但您将需要停止并重新启动 Eclipse,以便能识别这个新的插件。从帮助菜单中选择“About Eclipse Platform”,可以找到关于已安装插件的信息,包括版本号。在出现的屏幕上有一个按钮是 Plug-in Details;向下滚动列表来寻找 Hello 插件及其版本号。

更新插件版本

在目录名称中包含版本号的目的是允许在同一台机器上共存某个插件的多个版本(每次只装入一个版本)。我们可以通过创建一个 Hello 插件的已更新版本来看看这是如何工作的:例如,将 plugin.xml 文件中的版本号更改成“1.0.1”,然后将 SampleAction.java 中的文本更改成“New and improved Hello, Eclipse world”。从 Eclipse 菜单中选择 Project=> Rebuild All。下一步,将项目文件以 JAR 形式导出到新的插件目录,例如,com.example.hello_1.0.1。将修订过的 plugin.xml 文件复制到同一个目录中。当停止并重新启动 Eclipse 时,只会装入已更新的插件。

插件片段和功能部件

Eclipse 由插件组成,但在开发 Eclipse 的插件时,还要慎重考虑另外两个级别的组件 ― 插件片段和功能部件。

插件片段(如名称所暗示的)是完整插件的组成部分 ― 目标插件。片段提供的功能与目标插件的功能合并。片段可以用于将插件本地化成各种语言;在无需形成一个全新发行版的情况下,以增量形式将功能部件添加到现有插件,或者提供特定于平台的功能。在许多方面,片段与插件一样。主要的区别就是片段没有插件类 ― 片段的生命周期由其目标插件管理。此外,片段的清单文件叫作 fragment.xml,它列出了目标插件的标识和版本号,以及片段的标识和版本号。

另一方面,插件功能部件根本不包含编码。在 Eclipse 体系结构术语中,功能部件是将一组相关插件打包到完整的产品中。例如,JDT 是包含了象 Java 编辑器、调试器和控制台这样的插件的功能部件。名为 feature.xml 的清单文件描述了一个功能部件归档文件。在其中,该清单文件包含了对该功能部件所包含的插件和其它资源的引用、关于如何更新该功能部件的信息、版权信息和许可证信息。

在 Eclipse 中,主功能部件设置了 Eclipse 平台的外观。主功能部件旨在确定诸如给予 Eclipse 其身份的闪屏和其它特征之类的东西。Eclipse 只允许一个主功能部件。用这种方式,通过创建一组插件,将它们打包到功能部件中,并且使这个功能部件成为主功能部件,就可以重新创建 Eclipse 的品牌,并将它用于创建全新且不同的产品。如果从 Eclipse.org 下载,缺省主功能部件是eclipse.org.platform

后续步骤

在插件的介绍里我们只是稍微了解一些插件的必要用法。学习插件的更多知识的最佳参考资料是 Plug-in Developer’s Guide,可以从 Eclipse 中的帮助菜单中获得该指南。该文档包含了编程指南、Eclipse API 和插件扩展点的参考大全、Eclipse.org 上可用的编程示例的指南,以及常见问题列表。另一个优秀参考资料是 Eclipse 本身的源代码。根据您的兴趣,您也许想要查找一些示例,以了解不同工作台功能部件(如视图和编辑器)是如何扩展的,或者如何使用 SWT(Eclipse 图形 API)。此外,下面的参考资料可以帮助您学到更多知识。

 

参考资料

关于作者

David Gallardo 是一名独立软件顾问和作家,他的专长是软件国际化、Java Web 应用程序和数据库开发。他成为专业软件工程师已经有十五年了,他拥有许多操作系统、编程语言和网络协议的经验。他最近在一家企业对企业电子商业公司 TradeAccess, Inc 从事先进的数据库和国际化开发。在这之前,他是 Lotus Development Corporation 的 International Product Development 组中的高级工程师,负责开发为 Lotus 产品(包括 Domino)提供 Unicode 和国际语言支持的跨平台库。可以通过 david@gallardo.org与 David 联系。

 

原文地址:http://www.ibm.com/developerworks/cn/java/os-ecplug/

让代码取代你的配置文件吧

最近, 在编写一个专门压测NameNode的工具(以下简称s4nn), 它有两个难点 :

  1. s4nn需要可以模拟上万个DataNode ;
  2. s4nn 需要灵活的支持对NameNode访问行为的定义.

后者导致了本文的思考.

命令行参数和配置文件是最常用来配置系统的方法, 前者用于配置项较少, 后者则适合配置复杂情况. 这两种方式都有共同令人痛苦的地方:

  1. 编写代码去载入->解析->转换, 通常如同处理协议般无聊(要是有个什么变更, KMN!!);
  2. 对于复杂的配置文件编写而言, 总是没有顺手的编辑器支持, 写起来既累又易错.

要是用代码取代配置文件呢?

呃… 这会很麻烦吧, 像java这样的改了代码那还不要重新编译啊?

嗯, 确实, 但并没有想象中那么麻烦, 一些技巧可以让它变得简单(至少java是这样), 如Btrace.

用代码取代独立的配置文件不是新鲜的做法, 像GuiceGant 等已经以Internal DSL的代码代替了XML. 好处很明显:

  1. 良好的DSL风格, 简洁易懂 ;
  2. 免去对配置文件的解析转换 ;
  3. 最好的编辑器支持, 语法高亮, 一键格式化, 提示补全, 重构 ;
  4. 编译器帮助查错;
  5. 与代码无缝结合, 能够容易在变化中保持一致性.

简言之两个字, “高效”! 这倒是挺适合s4nn的, 不妨一试!

从需求的角度出发, 配置应该能够完成:

  1. 定义一组Client RPC调用行为, 其调用参数, 次数;
  2. 定义DataNode的运行时特征, 个数.

为满足这两点, 代码设计上可能会有:

class ClientDriver {
  void addRpc(times, name, args)
  void setNameNode(address)
}

class DataNodeSimulator {
  void setCapacity(size)
  void setHeartbeatInterval(sec)
  void setBlockReportInterval(sec)
  void setNameNode(address)
}

那么其配置文件会是:

 1 <configuration>
 2   <client>
 3     <rpc times="100000" name="getFileInfo">
 4       <arguments>
 5         <param name="src">/foo</param>
 6       </arguments>
 7     </rpc>
 8   </client>
 9   <namenode>
10     <address>host:port</address>
11   </namenode>
12   <datanode>
13     <simulator num="10000">
14       <capacity unit="GB">200</capacity>
15       <heartbeatInterval unit="SECOND">1</heartbeatInterval>
16       <blockReportInterval unit="HOUR">1</blockReportInterval>
17     </simulator>
18   </datanode>
19 </configuration>

简单吗? 简单, 那是因为这只是最基本的, 实际的配置应考虑到:

  1. 有30种RPC方法, 其中参数个数最多的有6,  参数类型并非都是基本类型, 参数值可能需要按照某种规则随即生成;
  2. DataNode模拟器也会有可能需要支持多种选项组合, 如实际集群种不是所有的机器的容量都一样的等.

这样的XML会臃肿到什么程度…

好吧, 看看用代码的效果:

 1 import com.taobao.s4nn.*;
 2
 3 public class Main extends Bootstrap {
 5
 6   protected void config() {
 7     setConcurrency(16);
 8     setMaxRandomPathDepth(10);
 9
10     /*
11      * DataNode simulator config
12      */
13     simulate(3, new DataNodeSimulatorGenerator() {
14       protected void config() {
15         setCapacity(gibibyte(1));
16         setTickPeriodSecond(1);
17         setHeartbeatInterval(1);
18         setBlockReportInterval(3);
19         setNameNode(proxyOfNameNodeAt("localhost:10001"));
20       }
21     });
22
23     /*
24      * NameNode Status pre-setting config
25      */
26     parallel(1, new StatusPresetterBuilder() {
27       protected void config() {
28         setName(client("InitalStatus"));
29         setNamenode(proxyOfNameNodeAt("localhost:10001"));
30         times(1, create(randomSrc, defaultFsPermission, overwrite(true), replication((short) 3), blocks(1)));
31       }
32     });
33
34     /*
35      * Client RPC driver config
36      */
37     parallel(10, new ClientDriverBuilder() {
38       protected void config() {
39         setName(seqNameWith("client"));
40         setNamenode(proxyOfNameNodeAt("localhost:10001"));
41
42         times(1, getBlockLocations(src("/foo"), randomOffsetIn(0, 1024), randomLengthIn(0, 2048)));
43         times(2, getListing(src("/foo")));
44         times(3, getLocatedListing(src("/foo")));
45         times(4, getStats());
46         times(5, getDatanodeReport(all));
47         times(6, getPreferriedBlockSize(src("/foo")));
48         times(7, getFileInfo(src("/foo")));
49         times(8, getContentSummary(src("/foo")));
50         times(9, setOwner(src("/foo"), username("jushi"), groupname("dwbasis")));
51         times(10, setReplication(src("/foo"), replication((short) 4)));
52         times(11, setPermission(src("/foo"), defaultFsPermission));
53         times(12, rename(src("/foo"), src("/bar")));
54         times(13, mkdirs(src("/bar"), defaultFsPermission));
55         times(14, setQuota(src("/foo"), randomNamespaceQuotaIn(1024, 2048), randomDiskspaceQuotaIn(1024, 4096)));
56         times(15, setTime(src("/foo"), currentTimeMillis, unchanged));
57       }
58     });
59   }
60 }

嗯,  既保持易读性又更为简洁,  重点是不用再写那些额外处理XML的代码了.

这里效仿了Guice中AbstractModule的DSL做法, 提供了setXxx, times等内置方法, 用Java IDE编写起来那是相当轻快啊~~

要是改用Scala或Groovy来实现, 那么还要简化, 使得代码味更少些, 而且直接运行简单到一条命令就搞定了:)

 

用Drupal 创建的8种网站

Drupal没有像WordPress那样普及,或许是因为它稍有难度来学习。但是如果你创建大的网站,使用Drupal,高度灵活的开源CMS,可以得到你想要的更漂亮、更强大的各种类型的网站。

下面是8种类型的网站,使用Drupal来创建,希望帮你对Drupal有更深刻的认识。

1. 文件存储分享站点

使用Drupal创建文件分享,你可以使用 CCK 和 Views ,也包括一些模块,比如Media MoverFilebrowser 或者Web File Manager。看看Box.net,你会非常感兴趣的^_^。

2. 社交网站

在社交网络能力方面,Drupal可能是最好的CMS。Drupal提供了强大的用户管理和权限管理系统。但是如果你想创建强大的社交网站,就需要一些模块,见http://drupal.org/node/206724

你想看一些案例?Imbee 或者 GoingOn

3. Twitter Clone

建议你不要尝试利用Drupal创建Twitter竞争产品,但是,如果你想整合Twitter功能到你的站点,Drupal的微博模块可以帮到你。

4. 新闻News portal

如果你想创建新闻站点或杂志站点,Drupal的完美的选择。使用CCK 和 Views ,你可以创建所有的发布内容类型,并且可灵活列表。这样的新闻站点非常之多,比如New York Observer

5. 博客网络

用Drupal创建博客网站,很轻松,甚至无需额外模块。看看 Wisebread吧。

6. 视频分享站点

这类站点太耗带宽了,如果你决定创建,那么Drupal来帮你实现吧。FlashVideo 模块提供了创建Youtube克隆的强大能力,它整合了CCK,转换视频到FLV,并有分享代码。另外你也可以尝试Media Mover 和 SWF ToolsMTV UK 站点就是Drupal创建的。

7. 图片分享站点

Image module ,这个模块将派上用场,可让你创建类Flickr站点,很好很强大。MyFinePix 就是Drupal创建的照片分享站点。

8. 类Digg-like news site

感谢 Drigg module, 这个模块可帮助你快速建立Digg克隆站点。流行的设计社交新闻网Designbump在使用Drupal。

英文原文:http://www.designer-daily.com/8-types-of-sites-you-can-build-with-drupal-13924

支持配额的共享线程池

用了几个小时动手实现了一个简陋支持配额的共享线程池. 基本思路与放翁相同, 区别在于引入了两种线程分配策略:

悲观策略

简单的共享一个线程池, 最容易出现的问题就是不同类型任务(或事件)在随机争抢线程资源时, 可能出现”饿死”现象(即抢不到线程).

因此, 悲观策略的宗旨是绝对的保证每种任务都会被分配到预留的(reserve)配额, 这种做法本质上和多个线程池的做法一样. 如总共100个线程, A任务可用50个线程, B任务可用30个线程, C任务可用20个, 三者互不占用, 一旦任意谁的任务实例超过配额, 将被迫等待直至先前的任务实例结束释放了线程.

统一到一个共享的池中, 好处自然是归一化管理, 容易从全局上比较不同任务的优先级, 做出合理的资源分配; 坏处可能就是需要去实现这样一个支持配额的共享线程池. 当然, 若不觉得多个线程池有什么不好, 悲观策略其实意义不大:(.

乐观策略

无论是使用悲观策略的共享线程池, 还是精心规划多个线程池, 由于都是预定义, 难免在环境变化过程中出现线程资源不足或闲置的情况. 要是可以这样, 某个时段当A任务较少时,  它所闲置的线程能协调给负载较高的B任务, 那就完美了!

故,  共享线程池的乐观策略就是在保证每种任务预留最低资源的情况下, 允许任务依据一个弹性(elastic)配额去争抢线程资源, 达到线程利用率的最大化. 如有100个线程的池, A任务大部分的时候负载较高, 则给予50个的预留配额, 30个的弹性配额; 而B任务是偶尔某个时段复杂较高, 则给予20个线程的预留配额, 30个的弹性配额, 这样留了一个30个线程的资源空间, 让AB去合理竞争.

很多实现的细节, 还请参见源代码.

源代码

CentralExecutor.java

CentralExecutorTest.java

 

逻辑划分线程池

现在很多系统中,特别是事件驱动的系统中,对于线程池的维护很多时候根据业务处理类型的不同做划分和管理,但分开维护会带来下面两个问题:

1. 到处线程池,每个线程池都有上限设置,但是所有线程池到达上限的时候也许系统已经无法承受了,所以局部设计和限制无法达到全局限制的目标。

2. 合理的利用线程池的资源,当线程池逻辑上真实隔离后,就无法将空闲的线程资源借调给繁忙的任务处理使用。

设计中关注的:

虚拟隔离线程池需要有模型可以保证对于一些处理的保护,对于一些处理的降级。

设计思路:

简单的两种配置模式:保留,限制。

举个例子:

默认线程池大小设置为100。

A类任务设置为保留10,B类任务设置为限制50。

假设有A,B,C三种任务进入。

A最大可以使用100个线程,其中10个是它独占的(通过配置可以选择优先使用公有的还是私有的)

B最大可以使用50个线程,当公有线程(100-10=90)被消耗后剩余总数小于50,那么B消耗的数量就会小于50,假如公有90个线程都没有被消耗,此时B最多也只能消耗50个线程。总结来说,B消耗公有的线程资源,同时最多只能消耗他的设置(当然他设置如果超过公有线程,则以公有线程池最大作为上限)

C最大可以使用90个线程,也就是所有的公有线程。

当任何一种请求没有线程资源可以被使用的时候,将会被放入队列,等待线程可用,队列不区分任务类型。

第一版简单的Java代码参看:http://www.rayfile.com/zh-cn/files/66a89e61-4357-11e0-9ad5-0015c55db73d/
这里只是探讨一种简单的设计思路,以最小代价来全局化管理维护线程池或者资源池。

 

分布式文件系统Ceph调研1 – RADOS

Ceph是加州大学Santa Cruz分校的Sage Weil(DreamHost的联合创始人)专为博士论文设计的新一代自由软件分布式文件系统。自2007年毕业之后,Sage开始全职投入到Ceph开 发之中,使其能适用于生产环境。Ceph的主要目标是设计成基于POSIX的没有单点故障的分布式文件系统,使数据能容错和无缝的复制。2010年3 月,Linus Torvalds将Ceph client合并到内 核2.6.34中。

Ceph中有很多在分布式系统领域非常新颖的技术点,对解决分布式文件系统中一些常见的问题的研究非常有指导意义。所以值得研究。

RADOS简介

1 RADOS概述

RADOS (Reliable, Autonomic Distributed Object Store) 是Ceph的核心之一,作为Ceph分布式文件系统的一个子项目,特别为Ceph的需求设计,能够在动态变化和异质结构的存储设备机群之上提供一种稳定、可扩展、高性能的单一逻辑对象(Object)存储接口和能够实现节点的自适应和自管理的存储系统。事实上,RADOS也可以单独作为一种分布式数据存储系统,给适合相应需求的分布式文件系统提供数据存储服务。

2 RADOS架构简介

RADOS系统主要由两个部分组成(如图1所示):

1.由数目可变的大规模OSDs(Object Storage Devices)组成的机群,负责存储所有的Objects数据;

2.由少量Monitors组成的强耦合、小规模机群,负责管理Cluster Map,其中Cluster Map是整个RADOS系统的关键数据结构,管理机群中的所有成员、关系、属性等信息以及数据的分发。

图1 RADOS系统架构图示

对于RADOS系统,节点组织管理和数据分发策略均有内部的Monitors全权负责,所以,从Clients角度设计相对比较简单,它给应用提供的仅为简单的存储接口。

3 RADOS详细介绍

3.1 扩展机群

1.Cluster Map

存储机群的管理,唯一的途径是Cluster Map通过对Monitor Cluster操作完成。Cluster Map是整个RADOS系统的核心数据结构,其中指定了机群中的OSDs信息和所有数据的分布情况。所有涉及到RADOS系统的Storage节点和Clients都有最新epoch的Cluster Map副本。因为Cluster Map的特殊性,Client向上提供了非常简单的接口实现将整个存储机群抽象为单一的逻辑对象存储结构。

Cluster Map的更新由OSD的状态变化或者其他事件造成数据层的变化驱动,每一次Cluster Map更新都需要将map epoch增加,map epoch使Cluster Map在所有节点上的副本都保持同步,同时,map epoch可以使一些过期的Cluster Map能够通过通信对等节点及时更新。

在大规模的分布式系统中,OSDs的failures/recoveries是常见的,所以,Cluster Map的更新就比较频繁,如果将整个Cluster Map进行分发或广播显然会造成资源的浪费,RADOS采用分发incremental map的策略避免资源浪费,其中incremental map仅包含了两个连续epoch之间Cluster Map的增量信息。

2.Data Placement

数据迁移:当有新的储存设备加入时,机群上的数据会随机的选出一部分迁移到新的设备上,维持现有存储结构的平衡。

数据分发:通过两个阶段的计算得到合适的Object的存储位置。如图2所示。

图2 数据分发图示

1.Object到PG的映射。PG (Placement Group)是Objects的逻辑集合。相同PG里的Object会被系统分发到相同的OSDs集合中。由Object的名称通过Hash算法得到的结果结合其他一些修正参数可以得到Object所对应的PG。

2.RADOS系统根据根据Cluster Map将PGs分配到相应的OSDs。这组OSDs正是PG中的Objects数据的存储位置。RADOS采用CRUSH算法实现了一种稳定、伪随机的hash算法。CRUSH实现了平衡的和与容量相关的数据分配策略。CRUSH得到的一组OSDs还不是最终的数据存储目标,需要经过初步的filter,因为对于大规模的分布式机群,宕机等原因使得部分节点可能失效,filter就是为过滤这些节点,如果过滤后存储目标不能满足使用则阻塞当前操作。

3.Device State

Cluster Map中关于Device State的描述见下表所示。

表1 Device State描述

in out
assigned PGs not assigned PGs
up online & reachable active online & idle
down unreachable unreachable & not remapped failed

4.Map propagate

Cluster Map在OSD之间的更新是通过一种抢占式的方法进行。Cluster Map epoch的差异只有在两个通信实体之间有意义,两个通信实体在进行信息交换之前都需要交换epoch,保证Cluster Map的同步。这一属性使得Cluster Map在通信实体内部之间的更新分担了全局的Cluster Map分发压力。

每一个OSD都会缓存最近Cluster Map和到当前时刻的所有incremental map信息,OSD的所有message都会嵌入incremental map,同时侦听与其通信的peer的Cluster Map epoch。当从peer收到的message中发现其epoch是过期的,OSD share相对peer来说的incremental map,使通信的peers都保持同步;同样的,当从peer收到message中发现本地epoch过期,从其嵌入到message中的incremental map中分析得到相对本地的incremental map然后更新,保持同步。

不是同一个通信对等方的两个OSD之间的epoch差异,不影响同步。

3.2 智能存储

1Replication

RADOS实现了三种不同的Replication方案,见下图3示:

图3 RADOS实现的三种replication方案

Primary-copy:读写操作均在primary OSD上进行,并行更新replicas;

Chain:链式读写,读写分离;

Spaly:Primary-copy和Chain的折中方案:并行更新replicas和读写分离。

2Consistency

一致性问题主要有两个方面,分别是Update和Read:

  1. Update:在RADOS系统中所有Message都嵌入了发送端的map epoch协调机群的一致性。
  2. Read:为避免部分OSD失效导致数据不能从该OSD读需要转向新的OSD,但是read operation的发起方还没有该OSD的失效信息的问题,同一个PG所在的OSDs需要实时交换Heartbeat。

3Failure Detection

错误检测:RADOS采取异步、有序的点对点Heartbeat。(此处的错误检测是OSDs自身检测)

4Data Migration & Failure Recovery

由于设备失效、机群扩展、错误恢复造成的Cluster Map更新使得PG到OSDs的对应关系发生了变化,一旦Cluster Map发生变化,相应的OSDs上的数据也需要做相应的调整。

数据的移植和数据恢复都是由Primary OSD负责统一完成。

(Data Migration & Failure Recovery具体方法待续)

3.3 Monitors

Monitors是Cluster Map主备份存储目标,所有其他位置上的Cluster Map最初都是从Monitors请求得到。Monitors通过对Cluster Map的周期更新升级实现存储机群的管理。

Monitor的工作分两个阶段:

1.首先在多个Monitors中选举Leader,之后Leader向所有Monitors请求Map Epoch,Monitors周期性向Leader汇报结果并告知其活跃(Active Monitor),Leader统计Quorum。这阶段的意义是保证所有的Monitors的Map Epoch都是最新的,通过Incremental updates对已失效的Cluster Map进行更新。

2.Leader周期向每一个Active Monitor授权许可提供分发Cluster Map副本给OSDs和Clients的服务。当授权失效但Leader仍没有重新分发认为Leader died,此时重回第一阶段进行Leader重选;当Active Monitor没有周期向Leader反馈ACK则认为有Monitor died,重回第一阶段进行Leader选举并更新Quorum。Leader周期分发Lease和Active Monitor周期反馈ACK的另外一个作用是同步Monitors的Cluster Map。Active Monitor收到Update请求时,首先验证当前的Epoch是否为最新,如果不是,更新后向上汇报到Leader,Leader分发给所有的Monitors,同时回收授权,重新开始新一轮的Leader选举到Cluster Map服务。

通常Monitor的负载比较小:OSDs上的Cluster Map更新通过OSDs之间的机制实现;OSDs的状态变化比较罕见不会对Monitors的负载造成影响。但是一些特殊情况可能会对Monitors负载带来影响,比如:同时有n OSDs failed,每一个OSD store m个PGs,此时会形成m×n个failure report到达Monitors,对于规模较大的机群这样的数据量比较大。为避免这种情况给Monitor带来的负载压力,OSDs采用伪随机的时间间隔交错安排failure检测(此处是从OSDs到Monitor的检测)向上汇报,另外根据Monitors的并行化和负载均衡分配的特点,扩展Monitors是解决Monitors的负载压力的另一措施。

4 总结

与传统的分布式数据存储不同,RADOS最大的特点是:

1.将文件映射到Objects后利用Cluster Map通过CRUSH计算而不是查找表方式定位文件数据在存储设备中的位置。省去了传统的File到Block的映射和BlockMap管理。

2.RADOS充分利用了OSDs的智能特点,将部分任务授权给OSDs,最大程度的实现可扩展。

5 参考文献

[1]     RADOS: A Scalable, Reliable Storage Service for Petabyte-scale Storage Clusters.

[2]     Ceph: A Scalable, High-Performance Distributed File System.

 

Linux 2.6.38 User-space interface for Crypto API

Linux内核里面自带非常多的加密模块,这是模块经过调优性能非常高, 而且现在又很多硬件本身支持加密功能,比如intel的CPU支持AES加密指令,那些内核的那帮人知道更好如何利用这些硬件更快的完成加密功能的, 他们写的这些硬件的驱动在drivers/crypto目录里. 所以如果我们能在用户空间的应用程序中用到这些加密库有二个好处: 1. 无须再造轮子. 2. 性能高.

幸运的是2.6.38的内核给我们带来了这些功能. 这些功能是通过socket方式暴露的,思路非常独特优雅,同时由于支持gather write, scatter read, 无须拷贝数据,性能应该非常高.

具体可以参考底下材料:
User-space interface for Crypto API : 这里这里

在ubuntu10.10下安装新的内核2.6.38, 参考这里

安装完了系统,我们可以演示下如何使用新的API调用:

$ uname -r
2.6.38-yufeng
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=10.10
DISTRIB_CODENAME=maverick
DISTRIB_DESCRIPTION="Ubuntu 10.10"
$ cat > example.c
#include <stdio.h>
#include <sys/socket.h>
#include <linux/if_alg.h>
#ifndef AF_ALG
#define AF_ALG 38
#define SOL_ALG 279
#endif
int main(void)
{
int opfd;
int tfmfd;
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "hash",
.salg_name = "sha1"
};
char buf[20];
int i;
tfmfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(tfmfd, (struct sockaddr *)&sa, sizeof(sa));
opfd = accept(tfmfd, NULL, 0);
write(opfd, "abc", 3);
read(opfd, buf, 20);
for (i = 0; i < 20; i++) {
printf("%02x", (unsigned char)buf[i]);
}
printf("\n");
close(opfd);
close(tfmfd);
return 0;
}
CTRL+D
$ mkdir -p linux && cp /usr/src/linux-2.6.38/include/linux/if_alg.h linux/
$ gcc -I linux example.c
$ ./a.out
687b37ba3c7f0000100940000000000000000000

 

To solve the problem between JAXB2.1 and JDK1.6/6.0

Scenario 1
1.8. Using JAX-WS 2.1 with JavaSE6
JavaSE6 ships with JAX-WS 2.0 API in rt.jar, which causes some trouble when you try to run applications that use JAX-WS 2.1 API. This document collects information about how to solve this issue.

1.8.1. Endorsed directory
One way to fix this is to copy jaxws-api.jar and jaxb-api.jar into JRE endorsed directory, which is $JAVA_HOME/lib/endorsed (or $JDK_HOME/jre/lib/endorsed)

Some application containers, such as Glassfish, modifies the location of the endorsed directory to a different place. From inside the JVM, you can check the current location by doing System.out.println(System.getProperty(“java.endorsed.dirs”));

Obviously you still need other JAX-WS jars in your classpath.

Please do not put all the jars to the endorsed directory. This makes it impossible for JAX-WS RI to see other classes that it needs for its operation, such as servlet classes on the server-side, or Ant classes in the tool time. As those are not loaded by the bootstrap classloader, you’ll get NoClassDefError on servlet/Ant classes.

 

Scenario 2

Support for JDK 1.6
Jbossws 2.0.1.GA is based on the jax-ws and jaxb versions 2.1. But JDK 1.6 ships jaxb 2.0 classes as part of the core distribution (rt.jar). So in order for the jboss jars to take precedent over the jdk supplied jars, we have to use the endorsed directory mechanism, as described here. The above link is for using jaxws ri 2.1, but we need jbossws 2.0.1. For that purpose copy the following 3 jars in to the jboss/lib/endorsed directory.

So assuming your jboss is setup in a directory /jboss and you have already installed jbossws 2.0.1 on top of it, Copy the following 3 files to /jboss/lib/endorsed.

/jboss/server/default/lib/jboss-saaj.jar
/jboss/server/default/lib/jboss-jaxws.jar
/jboss/client/jaxb-api.jar
The jboss run script is already configured to add the /jboss/lib/endoresed directory to the list of endorsed directory. So jars in this directory will take precedence over rt.jar.