3. 行情 API
• 5 秒内整个财经系统所有实时数据自动刷新
• 利用 H TML 标签轻松定义数据格式
• 支持单只股票行情和排行榜等多种展现方式
• 接口里整合了行情里 6 个品种的实时行情、排
行榜以及统计数据
07/09/12
4. 基于 To kyo C a b ine t 的 ng inx c 模块
数据更新 数据更新
Me mc a c he
S e rve r
数据更新 数据更新
H as h .48 H as h .1 8
A p is e rver A p is e rver
A p ic lie nt A p ic lie nt A p ic lie nt A p ic lie nt A p ic lie nt
TokyoCabinet TokyoCabinet TokyoCabinet TokyoCabinet TokyoCabinet
Ng inx Ng inx Ng inx Ng inx Ng inx
( C mo d ule ) ( C mo dule ) ( C mo d ule ) ( C mo dule ) ( C mo dule )
Lo ad B alan c e 1 63 1 68 1 71 23 1 37 1 85
H TTP Re q ue s t
07/09/12
5. 行情 A P I 架构优化
nginx + ngx_lua + redis
nginx + tmpfs
nodejs
基于共享内存的 key-value 存储( shm_map )
基于 shm_map 的 nginx c 模块
07/09/12
6. ng inx + ng x_lua + re d is
Ng inx
( ng x_lua )
pipeline read
Ng inx
( ng x_lua )
subscribe A P I C lie nt write
( ja va p ro c e s s ) re d is
Ng inx
( ng x_lua )
Ng inx
( ng x_lua )
在并发高时, redis 吃内存,并且一直不释放。如果内存不够, redis 会使用
swap
07/09/12
7. ng inx + tmp fs
Ng inx
read file
Ng inx
subscribe A P I C lie nt write file
( ja va p ro c e s s ) tmp fs
Ng inx
0000001
1399001
0000010
……
……
Ng inx
多进程并发读写问题,以及打开的文件描述符过多
07/09/12
8. no d e js
a. master 进程订阅,然后分发给 worker 进程,数据以 map 形式存储在 worker
进程内。
nodejs
(worker)
dispatch
nodejs
(worker)
subscribe no d e js
( mas te r)
nodejs
(worker)
分发: nodejs 使用管道实现。
会有两次内核态到用户态的数据复制。
nodejs
(worker)
数据延迟,各个 worker 的数据可能不一致;上下文切换频繁
; cpu 负载高
07/09/12
9. no d e js
b. master 进程订阅,然后通过 redis 分发给 worker 进程,数据以 map 形式存储
在 worker 进程内。
nodejs
(worker)
subscribe
nodejs
subscribe (worker)
no d e js publish
re d is
( mas te r)
q ue ue
nodejs
(worker)
nodejs
(worker)
数据延迟的现象没有了,但是 cpu 负载高和上下文切换频繁的
情况依然存在
07/09/12
10. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
共享内存的实现原理:
将多个进程的地址空间的一部分映射到同一块物理内存
进程 A 进程 B
0XFFFFFFFF
虚拟地址空间 虚拟地址空间
页 物理内存空间 页
虚拟地址 表 物理地址 表
共享内存
0X00000000
07/09/12
11. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
设计目标:
内存操作
一个进程写,多个进程读,无锁
错误日志,便于定位 bug
持久化
模块划分:
内存池
HashMap
07/09/12
12. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
内存池:
以 8n 字节进行分配,比如:申请 5 字节,内存池调整为 8 字节
共享内存空间布局
初始时 运行一段时间后
未分配空间 已分配空间 未分配空间
已分配空间布局
占用块 空闲块
将空闲内存组织成多条空闲块链
07/09/12
13. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
空闲块列表:
NULL
索引表 0 1 2 3 …… n
8 字节 24 字节 8n 字节
header data header data header data
header data header data
header data
空闲块链
NULL
struct header{
free_block *next;
free_block *prev;
int index;
};
07/09/12
14. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
m_alloc :
对应大小的空闲块链不存在,直接从未分配空间分配
以分配 32 字节为例, m_alloc(m_pool, 30)
已分配空间 未分配空间
unalloc_p
已分配空间 header data 未分配空间
data unalloc_p
07/09/12
15. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
m_alloc :
存在对应大小的空闲块链,返回空闲块链的尾部空闲块,并从链中删除
以分配 8 字节为例, m_alloc(m_pool, 5)
NULL
0 1 2 3 …… n
8 字节 24 字节 8n 字节
header data header data header data
header data header data
tail header data
NULL
07/09/12
16. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
m_alloc :
存在对应大小的空闲块链,返回空闲块链的尾部空闲块,并从链中删除
以分配 8 字节为例, m_alloc(m_pool, 5)
NULL
0 1 2 3 …… n
8 字节 24 字节 8n 字节
header data header data header data
header data header data
tail header data
NULL
07/09/12
17. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
m_alloc :
存在对应大小的空闲块链,返回空闲块链的尾部空闲块,并从链中删除
以分配 8 字节为例, m_alloc(m_pool, 5)
NULL
0 1 2 3 …… n
8 字节 24 字节 8n 字节
header data header data header data
tail header data header data
NULL
07/09/12
18. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
m_free :
将空闲块从头部插入到对应的空闲块链中
以释放 32 字节为例, m_free(m_pool, ptr)
NULL
0 1 2 3 …… n
8 字节 24 字节 8n 字节
header data header data header data
tail header data header data
NULL
07/09/12
19. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
m_free :
将空闲块从头部插入到对应的空闲块链中
以释放 32 字节为例, m_free(m_pool, ptr)
NULL
0 1 2 3 …… n
8 字节 24 字节 8n 字节
header data header data header data header data
tail header data header data
NULL
07/09/12
20. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
HashMap :
支持 put 、 get 、 remove 、 keys 、 iter 、 size 、 contains 操作
通过红黑树解决碰撞
NULL
Bulk List 0 1 2 3 …… n
entry entry
entry entry entry entry
entry entry entry entry struct entry{
entry *left;
entry *right;
entry *parent;
NULL rb_color_t color;
const char *key;
val_t value;
};
07/09/12
21. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
Benchmark
分别向 tc 和 shm_map put 100000 条大小分别为 2KB 、 20KB 、 200KB
机器配置:虚拟机, 4 核 E5640 2.67GHz , 4G
254.924s 254.991s
24.785s 25.211s
2.539s 2.590s
07/09/12
22. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
a. node binding
nodejs
(worker)
get
nodejs
subscribe (worker)
no d e js put
shm map
( mas te r)
nodejs
(worker)
nodejs
(worker)
上下文切换的次数明显降低了 40-50% , cpu 负载还是很高
07/09/12
23. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
b. python binding
tornado
(worker)
get
tornado
subscribe (worker)
no d e js put
shm map
( mas te r)
tornado
(worker)
tornado
(worker)
cpu 负载的确降低了,但是在高并发、大数据量的情况下, tornado 抛出
一堆 socket 错误
07/09/12
24. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
c. Nginx C module
Ng inx
( C mo d ule )
get
Ng inx
( C mo d ule )
subscribe put
nodejs s hm map
Ng inx
( C mo d ule )
Ng inx
( C mo d ule )
07/09/12
25. 基于共享内存的 ke y- va lue 存储( s hm_ma p )
各种方案的性能测试
响应内容大小是 150KB+ , 20000 并发下,请求 20000 次
机器配置:虚拟机, 4 核 E5640 2.67GHz , 4G
方案 QPS
TC + nginx C module 1300+
Redis + lua + nginx 900+
nodejs 1100+
Shm_map + nginx C module 1400+
07/09/12