2015年9月8日星期二

memcached 学习笔记 2 - yweihainan

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
memcached 学习笔记 2 - yweihainan  阅读原文»

  • 原理

1 核心组件

Memcached有两个核心组件组成:服务端(ms)和客户端(mc)。

首先mc拿到ms列表,并对key做hash转化,根据hash值确定kv对所存的ms位置。

然后在一个memcached的查询中,mc先通过计算key的hash值来确定kv对所处在的ms位置。

当ms确定后,客户端就会发送一个查询请求给对应的ms,让它来查找确切的数据。

因为ms之间并没有护卫备份,也就不需要互相通信,所以效率较高。

Memcached Client目前有一下四种:

Memcached Client for Java,比 SpyMemcached更稳定、更早、更广泛;

SpyMemcached,比 Memcached Client for Java更高效;

XMemcached,比 SpyMemcache并发效果更好。

alisoft-xplatform-asf-cache阿里软件的架构师岑文初进行封装的。里面的注释都是中文的,比较好

2 memcached是怎么工作的?

Memcached的神奇来自两阶段哈希(two-stage hash)。

Memcached就像一个巨大的、存储了很多<key,value>对的哈希表。通过key,可以存储或查询任意的数据。

客户端可以把数据存储在多台memcached上。

当查询数据时,客户端首先参考节点列表计算出key的哈希值(阶段一哈希),进而选中一个节点;

客户端将请求发送给选中的节点,然后memcached节点通过一个内部的哈希算法(阶段二哈希),查找真正的数据(item)。

举个列子,假设有3个客户端1, 2, 3,3台memcached A, B, C:
Client 1想把数据"barbaz"以key "foo"存储。

Client 1首先参考节点列表(A, B, C),计算key "foo"的哈希值,假设memcached B被选中。

接着,Client 1直接connect到memcached B,通过key "foo"把数据"barbaz"存储进去。  

Client 2使用与Client 1相同的客户端库(意味着阶段一的哈希算法相同),也拥有同样的memcached列表(A, B, C)。
于是,经过相同的哈希计算(阶段一),Client 2计算出key "foo"在memcached B上,然后它直接请求memcached B,得到数据"barbaz"。

各种客户端在memcached中数据的存储形式是不同的(perl Storable, php serialize, java hibernate, JSON等)。

一些客户端实现的哈希算法也不一样。但是,memcached服务器端的行为总是一致的。

最后,从实现的角度看,memcached是一个非阻塞的、基于事件的服务器程序。这种架构可以很好地解决C10K problem ,并具有极佳的可扩展性。

3 内存分配原理

Memcached有一个很有特色的内存管理方式,为了提高效率,它使用预申请和分组的方式管理内存空间,

而并不是每次需要写入数据的时候去malloc,删除数据的时候free一个指针。

Memcached使用slab->chunk的组织方式管理内存。

3.1 Slab Allocation 机制(板 分配):

整理内存以便重复使用最近的 memcached 默认情况下采用了名为 Slab Allocator 的机制分配、 管理内存。

在该机制出现以前,内存的分配是通过对所有记录简单地进行 malloc 和 free 来进行的。

但是,这种方式会导致内存碎片,加重操作系统内存管理器的负担,最坏的情况下,会导致操作系统比 memcached 进程本身还慢。

Slab Allocator 就是为解决该问题而诞生的。

下面来看看 Slab Allocator 的原理。

下面是 memcached 文档中的 slab allocator 的目 标:

the primary goal of the slabs subsystem in memcached was to eliminate memory fragmentation issues

totally by using fixed­size memory chunks coming from a few predetermined size classes.

也就是说, Slab Allocator 的基本原理是按照预先规定的大小, 将分配的内存分割成特定长度的块,以完全解决内存碎片问题。

Slab Allocation 的原理相当简单。

将分配的内存分割成各种尺寸的块( chunk), 并把尺寸相同的块分成组( chunk 的集合 slab class)(图 2.1)。

memcached给Slab分配内存空间,默认是1MB。

分配给Slab之后 把slab的切分成大小相同的chunk。

Chunk是用于缓存记录的内存空间。

Slab Class特定大小的chunk的组。

而且, slab allocator 还有重复使用已分配的内存的目的。

也就是说,分配到的内存不会释放,而是重复利用。

2 在 Slab 中缓存记录的原理

memcached 根据收到的数据的大小, 选择最适合数据大小的 slab(图 )。

memcached 中保存着slab 内空闲 chunk 的列表,根据该列表选择 chunk,然后将数据缓存于其中。

实际上, Slab Allocator 也是有利也有弊。 下面介绍一下它的缺点。

Slab Allocator 解决了当初的内存碎片问题, 但新的机制也给 memcached 带来了新的问题。
这个问题就是, 由于分配的是特定长度的内存,因此无法有效利用分配的内存。

例如, 将 100 字节的数据缓存到 128 字节的 chunk 中, 剩余的 28 字节就浪费了(图 )。

对于该问题目前还没有完美的解决方案, 但在文档中记载了比较有效的解决方案。

The most efficient way to reduce the waste is to use a list of size classes that closely matches (if that's at all

possible) common sizes of objects that the clients of this particular installation of memcached are likely tostore.

就是说,如果预先知道客户端发送的数据的公用大小, 或者仅缓存大小相同的数据的情况下, 只要使用适合数据大小的组的列表, 就可以减少浪费。

但是很遗憾,现在还不能进行任何调优, 只能期待以后的版本了。

但是, 我们可以调节 slab class的大小的差别。接下来说明 growth factor 选项。

3 使用 Growth Factor 进行调优

memcached 在启动时指定 Growth Factor 因子(通过­f选项), 就可以在某种程度上控制 slab 之间的差异。 默认值为 1.25。

但是,在该选项出现之前,这个因子曾经固定为 2, 称为“ powers of 2”策略。让我们用以前的设置,以 verbose 模式启动 memcached 试试看:

$ memcached ­f 2 ­vv

下面是启动后的 verbose 输出:

slab class 1: chunk size 128 perslab 8192

slab class 2: chunk size 256 perslab 4096

slab class 3: chunk size 512 perslab 2048

slab class 4: chunk size 1024 perslab 1024

[C#]发送HTTP请求 - VictorZhang  阅读原文»

前言

最近一直在做C# winform客户端项目,因为涉及到和服务器交互,所以研究了一下C#HTTP网络编程。

Http通信是通过Http请求报文和Http应答报文来实现的。

由于我开发的是客户端,所以主要工作就在于封装Http请求报文,以及收到应答报文后解析数据。


Http请求报文是什么样的

如果你使用的是chrome浏览器,按F12就可以开启调试模式。现在主流的浏览器应该都支持这个功能。随便打开一个网页,触发一个超链接请求,应该就能抓取到Http请求报文。

举个例子,以下是一个Http请求报文的截取内容。

我们可以看到请求报文中有很多参数,这里不一一详解各个参数的意义。


C#中实现Http请求

我个人认为,发送Http请求报文主要有四个步骤

1. 初始化HttpWebRequest(需要引用System.Net)

2. 封装Http cookie

3. 封装Http报文头

4. 封装请求内容,并将封装好的请求报文用Stream类写入流(需要引用System.IO

5. 接收应答报文

接下来,我们一步一步来讲解如何在C#中完成Http请求报文的封装。

初始化HttpWebRequest

使用请求地址作为参数,初始化一个HttpWebRequest实例。

// 初始化HttpWebRequest
HttpWebRequest httpRequest = (HttpWebRequest)HttpWebRequest.Create(strRequestUri);

封装Http cookie

首先,简单说明一下Cookie。有时也用其复数形式Cookies,指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。本文为了示例,就以比较简单的形式展现。

C#中,使用Cookie类来封装cookie,以键值对的形式保存。

然后,将这个封装好的cookie添加到CookieContainer容器中,最后填入HttpWebRequest。既然是容器,顾名思义,可以添加多个cookie

// 封装Cookie
Uri uri = new Uri(strRequestUri);
Cookie cookie = new Cookie("Name", strCookie); // 设置key、value形式的Cookie
CookieContainer cookies = new CookieContainer();
cookies.Add(uri, cookie);
httpRequest.CookieContainer = cookies;


封装Http Header

根据自己要发送的请求报文类型来填充Http报文头。

以下是一个简单范例:

// 封装Http Header
httpRequest.Method = "Post";
httpRequest.Referer = strReferer;
httpRequest.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36";
httpRequest.Accept = "text/plain, */*; q=0.01";
httpRequest.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
httpRequest.Timeout = 1000 * 30;
httpRequest.KeepAlive = true;

填充Http content,并写入流

首先,将要写入的内容转为byte数组(假设是文本内容)。将数组长度填入HttpWebRequestContentLength字段。

通过GetRequestStream()获取HttpWebRequest的请求流。使用这个流对象写入数组内容。

最后,千万不要忘记关闭流。本人在开发C#客户端和Java服务器交互过程中,就遇到这个问题。由于忘记关闭流,Java服务器在解析请求报文时,总是提示不能识别流的结尾。

// 通过流写入请求数据
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(strContent); // 编码形式按照个人需求来设置
httpRequest.ContentLength = bytes.Length;
Stream requestStream = httpRequest.GetRequestStream();
requestStream.Write(bytes, 0, bytes.Length);
requestStream.Close(); // 不要忘记关闭流

获得应答报文

通过流将请求报文发出去后,可以通过HttpWebRequestGetResponse()方法来获取HttpWebResponse

没有评论:

发表评论