LongLong's Blog

分享IT技术,分享生活感悟,热爱摄影,热爱航天。

Julia语言简介

使用Python+numpy做数值计算基本可以达到类似MATLAB的效果,能够快速的进行开发和部署,但由于Python语言本身的限制性能上并不算十分理想。

目前的动态语言向JIT运行方式转型已经是大趋势,而Python的JIT版本pypy虽然性能大幅提升,但对numpy和scipy的支持还存在很多问题。在寻找了很多解决方案后发现了Julia语言,原生支持多维数组及相关的运算,采用JIT的运行方式,主要面型科学计算领域,官方给出的性能测试结果表明其与LuaJIT的性能相当。

1. 基本语法

Julia的基本语法和Python非常类似,同时引入了很多数学中习惯使用的表达方式,如范围的判断可以写成1 < x < 10,函数的定义可以写成f(x) = x^2

#变量赋值
x = 3;

#条件
if 1 < x < 10
    @show x
else
    println(1);
end

#循环
for i = 1 : 100
    @show i;
end

#函数
#标准定义
function add(a::Int, b::Int)
    return a + b;
end
#简化的定义
f(x::Real) = x^2;

2. 多维数组

在数值计算中最为重要的数据类型,一般常用的是二维数组——矩阵,常用的矩阵运算如下

#生成一个4x4的随机矩阵,4x1的随机向量
M = rand(4, 4);
v = rand(4);
#计算矩阵和向量的乘积
v1 = M * v;
#矩阵的转置和逆
M1 = M';
M2 = M^-1;

通过JuliaGPU提供的ClArray和CuArray包可以将数组的运算扩展到GPU设备上,而接口保持和一般的Array一致

3. 自定义类型

Julia中并不支持Python中的面向对象的方式定义类型,而是定义类型和类型的静态方法(可以将静态方法的第一个参数设定为对应类型的引用实现类似成员方法的效果),并可以重载定义的方法,在定义的类型前增加mutable可使得内部的数据是可修改的

#定义一种抽象类型
abstract type A end;
#定义抽象类型的子类型
mutable struct B <: A
    x::Int64;
    #构造函数
    function B(x::Int64)
        return new(x);
    end
end
#定义方法
function add(v1::A, v2::A)
    return v1.x + v2.x;
end
#定义类似其他语言中成员方法
function show(self::A) {
    println(self.x);
}
#对于子类型B重载show方法
function show(self::B) {
    println("B:");
    println(self.x);
}

4. 包管理

Julia和Python的pip类似也有一套自身的包管理系统,在julia控制台中可以进行安装

import Pkg;
#增加和删除包MatrixMarket
Pkg.add("MatrixMarket");
Pkg.rm("MatrixMarket");

5. 性能测试

使用Julia和Python对比性能对比测试——生成一个随机的10x10的矩阵,并求逆,循环1000000次,代码如下

for i = 0 : 1000000 - 1
    A = rand(10, 10);
    A^-1;
end
import numpy;
for i in range(0, 1000000):
    A = numpy.random.rand(10, 10);
    numpy.linalg.inv(A);

运行结果如下,循环的次数越多Julia的优势就越明显,而当循环数少时Python反而会快一些,原因应该是循环数少时JIT编译时产生的消耗会变得特别明显

time python test.py
real    0m15.129s
user    0m14.958s
sys     0m0.027s

time julia test.jl
real    0m7.872s
user    0m6.648s
sys     0m1.139s

使用Nginx和Redis实现消息推送

实现消息的实时推送,基本的思路就是客户端和服务器之间保持一个长连接,当有消息需要推送给客户端时,服务器发送消息的内容给客户端。而很多人会认为保持太多的长连接会严重影响系统的性能,但如今的网络模型都是基于事件驱动的非阻塞模式,对于总连接数N拥有O(1)的性能,所以并没有必要在这方面有所担心,空闲的连接只是会占用一定数量的内存而已。

1. 消息推送原理

客户端和服务端的Nginx保持一个长连接,同时Nginx和后端的Redis通过SUBSCRIBE指令保持一个长连接,CHANNEL的名称为经过加密的字符串,在建立长连接之前通过认证逻辑下发并作为建立长连接的参数(可以为多个),在需要对客户端进行推送时,只需要向对应的CHANNEL中通过PUBLISH指令发送相应的内容即可实现1对1或1对多的内容推送。

2. Nginx和Reids的配置

要实现Nginx与Redis的通信,可以使用Nginx的Lua模块来完成,其中需要用到resty-lua作为与Redis进行通信的API。

location /sub {
    --检测客户端是否断开
    lua_check_client_abort on;
    content_by_lua_block {
        local cjson = require "cjson";
        local redis = require "resty.redis";
        --建立连接
        local r = redis:new();
        r:connect("127.0.0.1", 6379);
        --启动消息订阅
        local res, err = r:subscribe(ngx.unescape_uri(ngx.var.arg_key));
        --如果客户端意外关闭,则断开与Redis的连接并退出运行
        ngx.on_abort(function()
            r:close();
            ngx.exit(499);
        end);
        --循环接收消息
        while not ngx.worker.exiting()
        do
            repeat
                local res, err = r:read_reply();
                if err then
                    break;
                end
                --接收完成后将消息发送给客户端
                local ok, err = ngx.say(cjson.encode(res));
                ngx.flush();
            until true
        end
    }
}

由于需要处理较高的连接数,所以在Nginx和Redis中需要将支持的最大连接数调到合适的值,由于Nginx对于每一个长连接都需要建立一个SUBSCRIBE的长连接到Redis,因此受到系统端口号的限制,一个IP地址只能支持65535个端口,因此最大连接数设置在60000左右即可。

#nginx
worker_processes 65535;
#redis
maxclients 60000

3. 测试效果

与Nginx建立长连接后,向Redis中依次发布两条消息

curl http://localhost:8080/sub?key=abc -v
< HTTP/1.1 200 OK
< Server: openresty/1.13.6.2
< Date: Sun, 02 Dec 2018 03:48:39 GMT
< Content-Type: application/octet-stream
< Transfer-Encoding: chunked
< Connection: keep-alive
<
["message","abc","123"]
["message","abc","666"]

长连接数测试,建立15000个长连接到Nginx

const http = require('http');
options = {
	hostname: '127.0.0.1',
	port: 8080,
	path: '/sub?key=abc'
};
list = [];
for (var i = 0; i < 15000; i++) {
	req = http.request(options);
	req.end();
	list.push(req);
}

15000个长连接建立后,Nginx主进程占用的内存为280M左右

# Clients
connected_clients:15001

  VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
312672 284664    784 S   0.0  28.2   0:59.09 nginx

4. 总结

使用Nginx和Redis实现了简单的消息推送系统,但存在的缺点是一个IP只能处理60000个连接,不过可以通过在一台服务器上启动多个Nginx实例并绑定不同的IP来充分利用系统的资源。