开发一个Ejabberd HTTP模块

概述

如何开发一个Ejabberd模块, 本文基于一个实例演示了如何开发一个Ejabberd模块, 以及模块的基本结构.

HTTP模块的核心函数是process(Paths,Requst)处理函数, 接受一个路径列表, 和一个请求作为参数. 返回值为一个三元组{ResponseCode, ResponseHeaders, ResponseBody}, 分别表示响应码, 响应头和响应体. 其中:

  1. ResponseCode为一个整数值, 例如200
  2. ResponseHeaders为一个二元组列表, 例如[{<<"Content-Type">>, <<"text/html; charset=utf-8">>}],表示要设置的HTTP响应头信息.
  3. ResponseBody为一个类似<<"Content">>二进制字符串, 返回给客户端的实际内容.

具体细节可参考ejabberd_http.erl模块的make_xhtml_output/4函数.

下面是一个process/4函数的例子, 该函数用于获取当前在线用户数, 支持跨域资源共享(CORS):

1
2
3
4
5
6
7
8
9
10
process(_LocalPath, _Request) ->
ConnectedUsersNumber = ejabberd_sm:connected_users_number(),
Json = jiffy:encode({[
{connected_users_number, ConnectedUsersNumber}
]}),

{200, [
{<<"Access-Control-Allow-Origin">>, <<"http://www.example.com">>},
{<<"Access-Control-Allow-Headers">>, <<"Content-Type,X-Requested-With">>},
{<<"Content-Type">>, <<"application/json; charset=utf-8">>}
], Json}.

在线用户数也可通过ejabberdctl connected_users_number获取.

故障转移和接管

2015-11-02 更新

Elixir 可以运行在主/从, 故障转移/接管模式下. 要使Elixir应用程序能够执行故障转移/接管, Elixir应用程序必须是一个OTP应用程序.

下面来创建一个包含Supervisor的Elixir项目

1
mix new distro --sup

修改distro.ex添加logger模块. 以记录当触发故障转移/接管操作时的日志记录.

1
2
3
4
5
6
7
8
9
10
11
12
13
defmodule Distro do
use Application
require Logger
def start(type, _args) do
import Supervisor.Spec, warn: false
Logger.info("Distro application in #{inspect type} mode")
children = [
worker(Distro.Worker, [])
]
opts = [strategy: :one_for_one, name: Distro.Supervisor]
Supervisor.start_link(children, opts)
end
end

Distro.Worker是一个GenServer: 它使用全局名称注册, 假设其运行在集群中的一个节点上, 全局注册让我们不用考虑其实际的运行位置, 只需要提供注册名称就可以访问.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
defmodule Distro.Worker do
use GenServer
require Logger
def start_link do
GenServer.start_link(__MODULE__, [], name: {:global, __MODULE__})
end
def init([]) do
{:ok, [], 1000}
end
def handle_info(:timeout, state) do
Logger.debug "timeout"
{:noreply, state, 1000}
end
end

编译

1
$ mix compile

应用程序分布

本节阐述了如何把一个应用程序分布到多个节点上

假设应用程序运行在3个节点上, 名称分别为abc, bcd, def. 创建三个配置文件如下:

1
touch config/abc.config
touch config/bcd.config
touch config/def.config

配置文件中有3个重要的键值对. distributed, sync_nodes_mandatorysync_nodes_timeout, 其中:

  1. distributed 定义了分布式应用的启动延迟, 备用节点.
  2. sync_nodes_mandatory 定义强制要求的节点
  3. sync_nodes_timeout 定义了distributed中所有节点都启动完成需要等待的时间, 如果在此时间内要求的节点没有全部启动, 那么所有节点上的应用启动失败.

abc.config

1
[
  {logger,[{console,[{format,<<"$date $time $metadata[$level] $message\n">>}]}]},
  {kernel,
    [{distributed, [
        {'distro', 5000, ['abc@192.168.8.104', {'bcd@192.168.8.104', 'def@192.168.8.104'}]}]},
        {sync_nodes_mandatory, ['bcd@192.168.8.104', 'def@192.168.8.104']},
        {sync_nodes_timeout, 30000}
]}].

bcd.config

1
[
  {logger,[{console,[{format,<<"$date $time $metadata[$level] $message\n">>}]}]},
  {kernel,
    [{distributed, [
        {distro,5000, ['abc@192.168.8.104', {'bcd@192.168.8.104', 'def@192.168.8.104'}]}]},
        {sync_nodes_mandatory, ['abc@192.168.8.104', 'def@192.168.8.104']},
        {sync_nodes_timeout, 30000}
]}].

def.config

1
[
  {logger,[{console,[{format,<<"$date $time $metadata[$level] $message\n">>}]}]},
  {kernel,
    [{distributed, [
        {distro,5000, ['abc@192.168.8.104', {'bcd@192.168.8.104', 'def@192.168.8.104'}]}]},
        {sync_nodes_mandatory, ['abc@192.168.8.104', 'bcd@192.168.8.104']},
        {sync_nodes_timeout, 30000}
]}].

在不同的终端自动全部3个节点

1
iex --name abc@192.168.8.104 -pa _build/dev/lib/distro/ebin/ --app distro \
--erl "-config config/abc"

iex --name bcd@192.168.8.104 -pa _build/dev/lib/distro/ebin/ --app distro \
--erl "-config config/bcd"

iex --name def@192.168.8.104 -pa _build/dev/lib/distro/ebin/ --app distro \
--erl "-config config/def"

验证步骤

bcd, def 是 abc 的备用节点. bcd 优先于def, 如果abc死掉, 应用程序会在bcd上重启, 如果bcd死掉, 应用程序会转移到def, 这时候abc, bcd 修复启动后, 应用会被abc接管.

  1. 终止(Ctrl+C两次)节点abc@192.168.8.104后,5秒内会在节点bcd@192.168.8.104上重启应用
  2. 再次启动节点abc@192.168.8.104后,应用在bcd@192.168.8.104上停止, 应用被恢复后的abc@192.168.8.104节点接管(Takeover)

参考资料

  1. Elixir Application Failover/Takeover
  2. Distributed OTP Applications
  3. 源码仓库 (求加星)

用Elixir Mix构建Ejabberd发行包

Github上Ejabberd最新版15.07支持通过Elixir Mix构建工具创建Ejabberd的发行(部署)包.

详细信息参考这篇BLog: https://blog.process-one.net/building-an-otp-release-for-ejabberd-with-elixir-mix/

其使用了Elixir的exrm打包工具

构建过程和步骤

1. 创建目录和克隆仓库

1
mkdir ejabberd-demo1
mkdir ejabberd-demo1/deploy
cd ejabberd-demo1
git clone https://github.com/processone/ejabberd.git

2. 编译

1
cd ejabberd-demo1/ejabberd
mix do deps.get, compile

3. 创建发布包

1
MIX_ENV=prod mix release

4. 部署

1
mkdir -p deploy/ejabberd
tar zxvf rel/ejabberd/releases/15.07.0/ejabberd.tar.gz  -C deploy/ejabberd

5. 创建配置文件

1
mkdir deploy/ejabberd/config
cd deploy/ejabberd/config
wget https://gist.githubusercontent.com/mremond/383666d563025e86adfe/raw/723dfa50c955c112777f3361b4f2067b76a55d7b/ejabberd.yml

6. 启动

前台运行(调试)

1
deploy/ejabberd/bin/ejabberd console

后台运行(产品环境)

1
deploy/ejabberd/bin/ejabberd start

7. 注册一个测试用户

1
:ejabberd_auth.try_register("user1", "localhost", "mypass")

8. 停止Ejabberd

调试

1
:init.stop()

热代码替换步骤

假定在15.07.0中发现了一个BUG, 修改该BUG后, 更新线上系统.

1. 修改Ejabberd源码

比如修改一个线上的程序BUG

2. 修改Mix项目文件

修改Ejabberd源码根目录下的mix.exs文件, 提升版本号

3. 编译

1
mix compile

4. 重新生产新的发行包

1
MIX_ENV=prod mix release

5. 实时升级线上系统

  • 创建一个新目录
1
mkdir -p deploy/ejabberd/releases/15.07.1
  • 创建新发行文件
1
tar zxvf rel/ejabberd/releases/15.07.1/ejabberd.tar.gz  -C deploy/ejabberd
  • 执行升级
1
deploy/ejabberd/bin/ejabberd upgrade "15.07.1"

结语

上述只是一个基本过程, 实际项目往往比这个过程复杂, 这个简单的过程只是说明如何使用.

UFTP - 基于UDP的加密文件多播传输协议

UFTP - Encrypted UDP based FTP with multicast

应用场景

  • 软件包分发
  • 视频流分发

下载和编译

1
wget http://sourceforge.net/projects/uftp-multicast/files/source-tar/uftp-4.7.tar
tar xf uftp-4.7.tar
cd uftp-4.7
make
make install

测试和使用

查看manpage

1
man uftpd
man uftp

uftpd是客户端守护进程, 用于一直监听服务端发送的多播包
uftp是服务端, 用于发送文件

客户端监听在本地接口en0的50002端口上, 并接受多播地址为224.0.0.100的多播包

1
./uftpd -d -I en0 -D /tmp/uftp/done -T /tmp/uftp/receiving -M 224.0.0.100

服务器端发送文件

1
./uftp -R 500 -p 50002 -I en0 -x 5 -M 224.0.0.100 -P 224.0.0.100 ../update.zip

常用命令行选项解释

UFTP命令行参数

参考资料

  1. http://en.wikipedia.org/wiki/UFTP
  2. http://uftp-multicast.sourceforge.net

Web加密API - 散列(Hashing)

本文说明了如何使用Web加密API生成数据的HASH值.

转换字符串为ArrayBuffer,并且把ArrayBuffer转换为16进制字符串

要生成字符串文本或二进制数据的Hash值, 必须先把它转换为ArrayBuffer类型.

1
2
3
4
5
6
7
8
9
10
function convertStringToArrayBufferView(str)
{

var bytes = new Uint8Array(str.length);
for (var iii = 0; iii < str.length; iii++)
{
bytes[iii] = str.charCodeAt(iii);
}

return bytes;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function convertArrayBufferToHexaDecimal(buffer)
{

var data_view = new DataView(buffer)
var iii, len, hex = '', c;
for(iii = 0, len = data_view.byteLength; iii < len; iii += 1)
{
c = data_view.getUint8(iii).toString(16);
if(c.length < 2)
{
c = '0' + c;
}

hex += c;
}
return hex;
}

生成字符串的HASH值

1
var data = "QNimate";

var crypto = window.crypto || window.msCrypto;

if(crypto.subtle)
{
    alert("Cryptography API Supported");

    var promise = crypto.subtle.digest({name: "SHA-256"}, convertStringToArrayBufferView(data));

    promise.then(function(result){
        var hash_value = convertArrayBufferToHexaDecimal(result);
    });
}
else
{
    alert("Cryptography API not Supported");
}

参考资料

  1. http://qnimate.com/hashing-using-web-cryptography-api/

HTTP协议的三种模式

CLOSE

1
[连接1] [请求1] ... [响应1] [关闭1] [连接2] [请求2] ... [响应2] [关闭2] ...

特点:
没一个请求都要建立新的连接, 请求完毕关闭连接

KEEP-ALIVE

KEEP-ALIVE模式是为了解决CLOSE模式频繁建立和关闭连接开销的问题. 它在一个连接中可以执行多次请求

1
[连接] [请求1] ... [响应1] [请求2] ... [响应2] [关闭] ...

特点:

请求是串行的, 当前请求只有在客户端收到响应后才能执行下一次请求, 它是事务性的.

PIPELINING

1
[连接] [请求1] [请求2]... [响应1][响应2] [关闭] ...

Ejabberd 用Elixir开发一个包过滤模块

过滤模块主要用于

创建一个模块骨架

在Ejabberd源码根目录下的lib目录中创建 filter_packet_demo.ex模块文件, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
defmodule FilterPacketDemo do
import Ejabberd.Logger
@behaviour :gen_mod

def start(_host, _opts) do
info('Starting ejabberd module Filter Packet Demo')
Ejabberd.Hooks.add(:filter_packet, :global, __ENV__.module, :on_filter_packet, 50)
:ok
end

def stop(_host) do
info('Stopping ejabberd module Filter Packet Demo')
Ejabberd.Hooks.delete(:filter_packet, :global, __ENV__.module, :on_filter_packet, 50)
:ok
end

def on_filter_packet({from, to, xml={:xmlel, "message", attributes, children}} = packet) do
info("Filtering packet: #{inspect {from, to, xml}}")
# 访问XML元素属性
# attributes 是一个形如 [{"type", "chat"}, ...]的元组列表, 为了方便访问, 使用如下函数转换为关键字列表
convert = fn {k,v} ->
{String.to_atom(k), v}
end
attribute_keywords = attributes |> Enum.map convert
type = attribute_keywords[:type]
cond do
type == "chat" ->
dosomething()
type == "headline" ->
dosomething()
type == "groupchat" ->
dosomething()
true ->
true
end
packet
end

def dosomething do
IO.puts "Do something."
end
end

编译,安装,动态更新

1
# 编译
make
make install

启动

1
ejabberdctl live

进入调试控制台

1
ejabberdctl debug

在调试控制台中运行

1
(ejabberd@localhost)1> ejabberd_update:update().
{ok,[]}

参考资料

  1. Elixir Sips: ejabberd with Elixir – Part 2
    https://blog.process-one.net/ejabberd-with-elixir-packet-filters/

Web加密API指南

Web加密API提供了执行像散列, 对称 & 非对称加密, 生成 & 校验数字签名等操作的Javascript接口.

本文将介绍加密基础和Web Cryptography API

使用Web Cryptography API需要浏览器的支持, 目前集中常用浏览器的支持情况是:

  • Microsoft Edge
  • Google Chrome 37+
  • Mozilla Firefox 37+
  • Apple Safari 8+
  • Opera 27+

其他浏览器可参考参考资料2

使用Web加密API

安全源策略(Secure Origin Policy)

当且仅当Javascript运行在一个安全源Web页面时, 可访问Web加密API.

“安全源” 是至少匹配如下模式的来源:

1
(https, *, *)
(wss, *, *)
(*, localhost, *)
(*, 127/8, *)
(*, ::1/128, *)
(file, *, —)

支持的加密算法

##

参考资料

  1. W3C Web Cryptography API 规范
    http://www.w3.org/TR/WebCryptoAPI/

  2. 浏览器支持情况
    http://caniuse.com/#feat=cryptography

  3. http://qnimate.com/post-series/web-cryptography-api-tutorial/

HTML5获取电池状态

W3C电池状态接口定义

电池状态接口

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html>
<head>
<title>电池状态API示例</title>
<script>
window.onload = function () {
function updateBatteryStatus(battery) {
document.querySelector('#charging').textContent = battery.charging ? '正在使用交流电' : '正在使用电池';
document.querySelector('#level').textContent = battery.level;
document.querySelector('#dischargingTime').textContent = battery.dischargingTime / 60;
}
navigator.getBattery().then(function (battery) {
// Update the battery status initially when the promise resolves ...
updateBatteryStatus(battery);

// .. and for any subsequent updates.
battery.onchargingchange = function () {
updateBatteryStatus(battery);
};
battery.onlevelchange = function () {
updateBatteryStatus(battery);
};
battery.ondischargingtimechange = function () {
updateBatteryStatus(battery);
};
});
};
</script>

</head>
<body>
<div id="charging">(charging state unknown)</div>
<div id="level">(battery level unknown)</div>
<div id="dischargingTime">(discharging time unknown)</div>
</body>
</html>

参考资料

  1. 电池状态W3C规范
    http://www.w3.org/TR/battery-status/

  2. 新的 JavaScript Battery API
    http://cssha.com/battery/

  3. BatteryManager
    https://developer.mozilla.org/en-US/docs/Web/API/BatteryManager

如何使用Ejabberd消息归档模块

这篇文章阐述了如何使用Ejabberd提供的第三方消息归档模块

前置阅读

15.03在加载外部模块时有个BUG:
https://github.com/processone/ejabberd/commit/8b23727cc6db571b9d1dcc6119e39a602fe37279

所以, 请使用最新的15.04版本的Ejabberd

继续下面的步骤之前, 请先阅读如何安装模块

安装模块

1
ejabberdctl modules_update_specs
ejabberdctl modules_available
ejabberdctl module_install mod_mam

重新编译

1
make

修改配置文件

配置文件有两个地方需要修改

  • 增加mod_mam模块配置
1
mod_mam:
  access_max_user_messages: access_max_user_messages
  default_page_size: 25
  max_page_size: 100
  request_activates_archiving: true
  iqdisc: parallel

配置说明,请参考模块的README文件

  • 修改mod_roster: {}
1
mod_roster:
  versioning: true

最后, 重启Ejabberd服务器

服务功能查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
## IQ-get 请求
<iq type='get'
id='id-1431259882548'
to='x.hezhiqiang.info'
from='root@x.hezhiqiang.info'
xml:lang='zh'
xmlns='jabber:client'>

<query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>

# IQ-result 响应
<iq from='x.hezhiqiang.info'
to='root@x.hezhiqiang.info/24790037601431259873977465'
id='id-1431259882548'
type='result'>

<query xmlns='http://jabber.org/protocol/disco#info'>
<identity category='pubsub' type='pep'/>
<identity category='server' type='im' name='ejabberd'/>
<x xmlns='jabber:x:data' type='result'>
<field var='FORM_TYPE' type='hidden'>
<value>http://jabber.org/network/serverinfo</value>
</field>
</x>
...
<feature var='urn:xmpp:mam:0'/>
...
</query>
</iq>

你会看到, 多了一个<feature var='urn:xmpp:mam:0'/>

注意: 目前MAM模块暂时支持Mnesia数据库作为后端存储

参考资料

  1. http://www.lebsanft.org/?p=537
  2. https://github.com/processone/ejabberd-contrib/tree/master/mod_mam
  3. http://xmpp.org/extensions/xep-0279.html

服务器IP检查

该规范定义了一个XMPP扩展, 让客户端能够发现其自己的外部IP地址.

Ejabberd实现模块: mod_sic

首先客户端发送一个IQ-get给服务器:

1
<iq type='get'
    from='root@x.hezhiqiang.info'
    id='id-1431053085377'
    xmlns='jabber:client'>
  <address xmlns='urn:xmpp:sic:0'/>
</iq>

服务器端响应一个IQ-resultXML片段, 包含一个<address>元素, 其中包含客户端的外部IP地址<ip>, 以及一个可选的<port>元素表示客户端外部IP所对应的端口

1
<iq from='root@x.hezhiqiang.info'
    to='root@x.hezhiqiang.info/1964144757143129898889436'
    id='id-1431053085377'
    type='result'>
  <address xmlns='urn:xmpp:sic:0'>192.168.8.104</address>
</iq>

判断XMPP服务器是否支持该协议扩展

1
<!--请求-->
<iq type='get' id='id-1431052837499'
    to='x.hezhiqiang.info'
    from='root@x.hezhiqiang.info'
    xml:lang='zh'
    xmlns='jabber:client'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>
<!--响应-->
<iq from='x.hezhiqiang.info'
    to='root@x.hezhiqiang.info/1964144757143129898889436'
    id='id-1431052837499'
    type='result'>
  <query xmlns='http://jabber.org/protocol/disco#info'>
    <identity category='pubsub' type='pep'/>
    <identity category='server' type='im' name='ejabberd'/>
    <x xmlns='jabber:x:data' type='result'>
      <field var='FORM_TYPE' type='hidden'>
        <value>http://jabber.org/network/serverinfo</value>
      </field>
    </x>
    <feature var='http://jabber.org/protocol/commands'/>
    <feature var='http://jabber.org/protocol/disco#info'/>
    <feature var='http://jabber.org/protocol/disco#items'/>
    <feature var='http://jabber.org/protocol/pubsub'/>
    <feature var='http://jabber.org/protocol/pubsub#access-authorize'/>
    <feature var='http://jabber.org/protocol/pubsub#access-open'/>
    <feature var='http://jabber.org/protocol/pubsub#access-presence'/>
    <feature var='http://jabber.org/protocol/pubsub#access-whitelist'/>
    <feature var='http://jabber.org/protocol/pubsub#auto-create'/>
    <feature var='http://jabber.org/protocol/pubsub#auto-subscribe'/>
    <feature var='http://jabber.org/protocol/pubsub#collections'/>
    <feature var='http://jabber.org/protocol/pubsub#config-node'/>
    <feature var='http://jabber.org/protocol/pubsub#create-and-configure'/>
    <feature var='http://jabber.org/protocol/pubsub#create-nodes'/>
    <feature var='http://jabber.org/protocol/pubsub#delete-items'/>
    <feature var='http://jabber.org/protocol/pubsub#delete-nodes'/>
    <feature var='http://jabber.org/protocol/pubsub#filtered-notifications'/>
    <feature var='http://jabber.org/protocol/pubsub#get-pending'/>
    <feature var='http://jabber.org/protocol/pubsub#instant-nodes'/>
    <feature var='http://jabber.org/protocol/pubsub#item-ids'/>
    <feature var='http://jabber.org/protocol/pubsub#last-published'/>
    <feature var='http://jabber.org/protocol/pubsub#manage-subscriptions'/>
    <feature var='http://jabber.org/protocol/pubsub#member-affiliation'/>
    <feature var='http://jabber.org/protocol/pubsub#modify-affiliations'/>
    <feature var='http://jabber.org/protocol/pubsub#multi-subscribe'/>
    <feature var='http://jabber.org/protocol/pubsub#outcast-affiliation'/>
    <feature var='http://jabber.org/protocol/pubsub#persistent-items'/>
    <feature var='http://jabber.org/protocol/pubsub#presence-notifications'/>
    <feature var='http://jabber.org/protocol/pubsub#presence-subscribe'/>
    <feature var='http://jabber.org/protocol/pubsub#publish'/>
    <feature var='http://jabber.org/protocol/pubsub#publish-only-affiliation'/>
    <feature var='http://jabber.org/protocol/pubsub#publisher-affiliation'/>
    <feature var='http://jabber.org/protocol/pubsub#purge-nodes'/>
    <feature var='http://jabber.org/protocol/pubsub#retract-items'/>
    <feature var='http://jabber.org/protocol/pubsub#retrieve-affiliations'/>
    <feature var='http://jabber.org/protocol/pubsub#retrieve-default'/>
    <feature var='http://jabber.org/protocol/pubsub#retrieve-items'/>
    <feature var='http://jabber.org/protocol/pubsub#retrieve-subscriptions'/>
    <feature var='http://jabber.org/protocol/pubsub#shim'/>
    <feature var='http://jabber.org/protocol/pubsub#subscribe'/>
    <feature var='http://jabber.org/protocol/pubsub#subscription-notifications'/>
    <feature var='http://jabber.org/protocol/pubsub#subscription-options'/>
    <feature var='http://jabber.org/protocol/stats'/>
    <feature var='iq'/>
    <feature var='jabber:iq:last'/>
    <feature var='jabber:iq:privacy'/>
    <feature var='jabber:iq:private'/>
    <feature var='jabber:iq:register'/>
    <feature var='jabber:iq:roster'/>
    <feature var='jabber:iq:time'/>
    <feature var='jabber:iq:version'/>
    <feature var='msgoffline'/>
    <feature var='presence'/>
    <feature var='urn:xmpp:blocking'/>
    <feature var='urn:xmpp:carbons:1'/>
    <feature var='urn:xmpp:carbons:2'/>
    <feature var='urn:xmpp:ping'/>
    <feature var='urn:xmpp:sic:0'/>
    <feature var='urn:xmpp:time'/>
    <feature var='vcard-temp'/>
  </query>
</iq>

上述结果79行包含一个<feature var='urn:xmpp:sic:0'/>, 表示服务器支持该协议扩展, 我们需要解析上述服务器响应来判断是否包含<feature var='urn:xmpp:sic:0'/>元素,如果包含标识服务器支持. 否则不支持.

Thrift: The Missing Guide

Thrift is a software framework for scalable cross-language services development. It combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml.

针对Ejabberd的操作系统C1000K优化

本文基于Ubuntu Server 14.04进行优化, 不同的Linux/Unix系统有不同的细节.

查看系统当前支持的最大打开文件数:

1
root@ci:~# cat /proc/sys/fs/nr_open
1048576

查看硬性限制和软性限制

1
ulimit -Hn
ulimit -Sn

如果该值小于1000K, 请增大如下设置, 否则达不到100W并发连接.

1
fs.file-max = 1024000
net.ipv4.ip_conntrack_max = 1024000
net.ipv4.netfilter.ip_conntrack_max = 1024000

所有进程打开的文件描述符数不能超过/proc/sys/fs/file-max
单个进程打开的文件描述符数不能超过user limit中nofile的soft limit
nofile的soft limit不能超过其hard limit
nofile的hard limit不能超过/proc/sys/fs/nr_open

查看服务器TCP状态:

1
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

参考资料

  1. 构建C1000K的服务器(1) – 基础
  2. Linux Increase The Maximum Number Of Open Files / File Descriptors (FD)
  3. Linux系统优化加固

fs.file-max=65535000

net.nf_conntrack_max = 1000000

net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

net.core.netdev_max_backlog = 3000000

net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1800
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216

net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

#net.ipv4.tcp_congestion_control = HTCP

#net.ipv4.tcp_mtu_probing = 1

net.netfilter.nf_conntrack_max = 1000000
net.netfilter.nf_conntrack_buckets = 32768

#net.netfilter.nf_conntrack_tcp_timeout_established = 432000
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120

解决 nf_conntrack: table full, dropping packet 的几种思路
http://jaseywang.me/2012/08/16/%E8%A7%A3%E5%86%B3-nf_conntrack-table-full-dropping-packet-%E7%9A%84%E5%87%A0%E7%A7%8D%E6%80%9D%E8%B7%AF/

关于Erlang的一些限制
http://youthyblog.com/2014/08/05/erlang%E6%9C%89%E5%85%B3%E6%95%88%E7%8E%87%E7%9A%84%E4%B8%80%E4%BA%9Blimit/

AngularJS中的If..Else语句

  1. ng-switch directive:

can be used something like the following.

1
2
3
4
5
6
7
8
<div ng-switch on="video">
<div ng-switch-when="video.large">
<!-- code to render a large video block-->
</div>
<div ng-switch-default>
<!-- code to render the regular video block -->
</div>
</div>
  1. ng-hide / ng-show directives

Alternatively, you might also use ng-show/ng-hide but using this will actually render both a large video and a small video element and then hide the one that meets the ng-hide condition and shows the one that meets ng-show condition.

So on each page you’ll actually be rendering two different elements.

  1. Another option to consider is ng-class directive.

This can be used as follows.

1
2
3
<div ng-class="{large-video: video.large}">
<!-- video block goes here -->
</div>

The above basically will add a large-video css class to the div element if video.large is truthy.

UPDATE: Angular 1.1.5 introduced the ngIf directive

  1. ng-if directive:

In the versions above 1.1.5 you can use the ng-if directive. This would remove the element if the expression provided returns false and re-inserts the element in the DOM if the expression returns true. Can be used as follows.

1
2
3
4
5
6
<div ng-if="video == video.large">
<!-- code to render a large video block-->
</div>
<div ng-if="video != video.large">
<!-- code to render the regular video block -->
</div>

参考资料

  1. http://stackoverflow.com/questions/15810278/if-else-statement-in-angularjs-templates

Ejabberd模块安装工具

从Ejabberd版本15.02.77 (aa1250a)起,支持通过ejabberdctl命令行工具安装第三方模块

  • 如果你之前从源码便已过Ejabberd,并且是从源码安装的, 请首先重新执行如下命令:
1
./autogen.sh
./configure --enable-mysql \
    --enable-iconv \
    --enable-elixir \
    --enable-tools \
    --enable-nif \
    --enable-odbc \
    --enable-zlib \
    --enable-json
make
make install
  • 更新模块列表
1
ejabberdctl modules_update_specs
  • 列出可用的模块
1
root@xmpp:~# ejabberdctl modules_available
atom_pubsub         Provides access to all PEP nodes via an AtomPub interface
ejabberd_auth_http  Authentication via HTTP request
ircd                IRC server frontend to ejabberd
mod_admin_extra     Additional ejabberd commands
mod_archive         Supports almost all the XEP-0136 version 0.6 except otr
mod_cron            Execute scheduled commands
mod_log_chat        Logging chat messages in text files
mod_logsession      Log session connections to file
mod_logxml          Log XMPP packets to XML file
mod_mam	Message     Archive Management (XEP-0313)
mod_message_log     Log one line per message transmission in text file
mod_muc_admin       Administrative features for MUC
mod_muc_log_http    Serve MUC logs on the web
mod_multicast       Extended Stanza Addressing (XEP-0033) support
mod_openid          Transform the Jabber Server in an openid provider
mod_post_log        Logs messages to an HTTP API
mod_profile         User Profile (XEP-0154) in Mnesia table
mod_rest            HTTP interface to POST stanzas into ejabberd
mod_s2s_log         Log all s2s connections in a file
mod_shcommands      Execute shell commands
mod_statsdx         Calculates and gathers statistics actively
mod_webpresence     Publish user presence information in the web
  • 安装一个模块
1
ejabberdctl module_install mod_cron

该命令从ejabberd-contrib仓库安装mod_cron模块

$HOME/.ejabberd-modules/mod_cron/conf/mod_cron.yml

原文

https://blog.process-one.net/easy-installer-and-structure-for-ejabberd-contributed-modules

Ejabberd Howto 20150312 <TODO LIST>

Ejabberd 性能调优(草稿)

文章目录
  1. 1. 第一阶段
  2. 2. 进程打开文件数限制
  3. 3. 启动参数
  4. 4. 内核参数调整
  5. 5. 排查
  6. 6. Mnesia表过载
  7. 7. Erlang 虚拟机参数
  8. 8. 内核参数
    1. 8.1. TCP
  9. 9. 参考资料

第一阶段

目标10W并发连接

进程打开文件数限制

  1. 编辑 /etc/pam.d/login,取消pam_limits.so的注释
1
# Sets up user limits according to /etc/security/limits.conf
# (Replaces the use of /etc/limits in old login)
session    required   pam_limits.so
  1. /etc/security/limits.conf
1
root    soft    nofile  320000
root    hard    nofile  320000

我们在root账户下测试, 如何验证该设置是否生效?

exit 退出shell, 重新登录执行ulimit -a查看open files是否为320000

1
root@ci:~# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 127413
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 320000
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 127413
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

启动参数

Ejabberd的启动参数可以在配置文件/etc/ejabberd/ejabberdctl.cfg中进行调整

  • ERL_MAX_PORTS

    每个到客户端(s2c)和服务器(s2s)的连接消耗一个port, ERL_MAX_PORTS定义了Ejabberd可以支持的并发连接数. 其可以在配置文件/etc/ejabberd/ejabberdctl.cfg中指定, 默认值为32000.

    大并发连接场景下的应用需要增大该参数的值.

  • ERL_PROCESSES

    Erlang消耗很多轻量级进程, 如果Ejabberd比较繁忙, 可能会达到进程数上限, 这种情况会导致高延迟. 当消息延迟过高时, 需要判断是否是由于该参数引起的.

内核参数调整

1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216
net.ipv4.ip_local_port_range = 1025 65000
fs.file-max = 65535000
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.netdev_max_backlog = 30000
net.ipv4.tcp_mtu_probing = 1

内核网络参数net.ipv4.ip_local_port_range指定了本地程序可以打开的端口范围. 61000-32768=28232可以最多建立28232个到Ejabberd服务器的连接, 一般比这个数字小, 系统还有其他程序需要占用端口.

  • net.ipv4.tcp_tw_recycle = 1 快速回收TCP连接, 避免TIME_WAIT时间过长

修改端口范围

1
echo 1024 65535 > /proc/sys/net/ipv4/ip_local_port_range

排查

  • 查看ulimit
1
ulimit -a
  • 验证进程的最大可打开文件 Max open files
1
root@scm:~# cat /proc/18938/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        0                    unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             127460               127460               processes
Max open files            60000                60000                files
Max locked memory         65536                65536                bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       127460               127460               signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us

Mnesia表过载

编辑配置文件/etc/ejabberd/ejabberdctl.cfg, 设置选项:

1
ERL_OPTIONS="-mnesia dump_log_write_threshold 50000 -mnesia dc_dump_limit 40"

当前版本的SHELL脚本有BUG, 需要使用\转义, 如下:

1
ERL_OPTIONS="-mnesia\ dump_log_write_threshold\ 50000\ -mnesia\ dc_dump_limit\ 40"

Erlang 虚拟机参数

http://www.cnblogs.com/lulu/p/4132278.html

  • beam 启动参数
    • +sbt db
      绑定调度器与CPU的亲缘性
    • +P 2000000
      进程数限制(合适即可)
    • +K true
      启用epoll
    • +sbwt none
      关闭beam 调度器 spinlock,降低CPU
    • +swt low
      提高调度器唤醒灵敏度,避免长时间运行睡死问题

内核参数

TCP

http://blog.csdn.net/russell_tao/article/details/18711023

参考资料

  1. 文件描述符
    http://erlangcentral.org/wiki/index.php?title=Introduction_to_Load_Testing_with_Tsung#Max_open_file_descriptor_limit

  2. 虚拟接口
    http://erlangcentral.org/wiki/index.php?title=Introduction_to_Load_Testing_with_Tsung#Virtual_interfaces

  3. Mnesia过载问题
    http://blog.include.io/archives/106
    http://www.tuicool.com/articles/rIBbqa

  4. Tsung 常见问题
    http://tsung.erlang-projects.org/user_manual/faq.html

  5. http://blog.fnil.net/index.php/archives/276/

  6. 幻灯片-安装过程(需要梯子)
    http://www.slideshare.net/ngocdaothanh/tsung-13985127?related=1

  7. Performance Tuning
    https://www.ejabberd.im/tuning