lxyfirst

C++博客 首页 新随笔 联系 聚合 管理
  33 Posts :: 3 Stories :: 27 Comments :: 0 Trackbacks

#

服务端/后台开发中如何生成id是每个开发者都会遇到的问题,在电商、游戏领域尤其突出。
如何保证生成id的唯一性、可靠性、高可用性,如何组织id的格式,在不同的应用场景和限制下实现方式也不尽相同。

我们的应用场景类似电商,在一个订单的生命周期内,有多个逻辑需要生成各自的id,还要考虑到可读性和灵活性,我们决定实现一个独立的id服务。
首先,id服务必须具有高可用性,业务逻辑处理中创建id失败是不可接受的,所以id服务必须分布式部署,有多个节点同时对外服务,一个节点失败则重试其他节点,保证成功创建id。
在分布式系统中保证数据的一致性成本是很高的,为了简化设计和实现,每个节点都设计成对等的、独立的,不需要保持数据同步。
其次,id服务必须可靠,数据不能丢失,因此数据的存储放在独立的mysql数据库中,使用replace方式更新数据,id服务本身记录更新日志。
最后,id服务必须灵活,可以自定义id格式,可以高效灵活的实现客户端,因此通讯协议使用json over udp方式,在id服务端使用lua实现id格式的灵活定义。
ID规则
    具体规则有lua脚本定义,修改脚本后需要reload生效,需要实现4个函数
    min_counter :   计数器最小值
    max_counter :   计数器最大值
    reset_seconds : 计数器重置周期
    create_id : 根据计数器、自定义参数和时间参数创建ID。
    例如:
    function min_counter()
        return 0
    end
    function max_counter()
        return 9999
    end
    function reset_seconds()
        return 86400
    end
    function create_id(counter,now,salt)
        local seq = counter:generate_counter()
        local new_id = string.format("%01d%02d%02d%04d",now:year()%10 ,now:month(),now:day(),seq)
        return new_id
    end
接口
    采用udp协议,数据格式为json ,字段定义:
    action: 请求类型 get: 创建ID ,  monitor:监控
    rule_name: 规则名字, 由服务端定义
    app_name : 应用名或命名空间 , 客户端自定义,rule_name和app_name一起决定生成ID的唯一性
    salt :  自定义参数 ,可选项 ,
    seq : 自定义参数,可选项,原样返回
    例如:
    创建ID请求:  {"action":"get","rule_name":"o2o","app_name":"test"}
    响应:{"code":0,"message":"success","data":"505140001"}
    监控请求:{"action":"monitor","rule_name":"o2o","app_name":"test"}
    响应:{"code":0,"message":"ok","data":{"counter":3,"node_offset":1}}
性能
    id服务器使用c++实现,性能测试做的比较简单,因为性能不是id服务的主要关注点, 简单以php为客户端进行测试。
    4个php并发进程,每个进程不停发送20万个请求,测试结果:
    total:200000 fail:0 min:0.000214 max:0.087330 avg:0.000393
    total:200000 fail:0 min:0.000215 max:0.087129 avg:0.000391
    total:200000 fail:0 min:0.000221 max:0.087252 avg:0.000391
    total:200000 fail:0 min:0.000218 max:0.087484 avg:0.000391
    说明  min : 最小耗时(秒) max : 最大耗时(秒) avg : 平均耗时(秒)
    服务器TPS达到近1万/秒时,平均延迟在0.3毫秒。

经过在生产环境使用,运行稳定,现在将整个系统开源出来,欢迎试用,有任何意见和建议欢迎反馈到lxyfirst@163.com 。
项目源代码位置 : https://github.com/lxyfirst/id_server

版本更新9.19
1.增加数据落地的预保存和批量保存机制,一方面减少数据库压力,一方面增加异步保存的可靠性。
2.由于主线程和数据库线程只需要传递sql语句,将线程间通信由pipe方式改为eventfd + lockfree queue方式。
posted @ 2015-09-17 14:09 star 阅读(18461) | 评论 (4)编辑 收藏

svn的账号和权限管理是基于文件的,修改时需要更新到服务器,多有不便,可利用svn管理账号和权限,利用svn的pos-commit 钩子监测账号和权限文件变化,多个库可共享同一账号和权限文件。

/home/svn/conf/目录下存放了多个库共用的passwd和authz文件,用来控制这些库的账号和访问权限,独立的svn_admin库中存放对应的passwd和authz文件,有更新时自动同步到/home/svn/conf/下。
svn_admin库的post-commit 脚本如下:
REPOS="$1"
REV="$2"
FILE_DIR="/home/svn/conf"
UPDATE_FILE_LIST="passwd authz"


for FILENAME in $UPDATE_FILE_LIST ; do
    if svnlook changed $REPOS -r $REV |grep $FILENAME >/dev/null ; then
        DST_FILE=$FILE_DIR/$FILENAME
        mv $DST_FILE $DST_FILE.old                       
        svnlook cat $REPOS $FILENAME > $DST_FILE
    fi
done
posted @ 2014-05-23 11:03 star 阅读(2269) | 评论 (0)编辑 收藏

twemproxy(nutcracker)是twitter实现的开源memcached和redis代理,主要功能是根据key分发请求到后端的memcached和redis服务器,简化memcached和redis集群服务的实现。
出于对twemproxy实现机制的好奇,简要阅读了代码,特别是网络处理部分,一般这部分是网络服务器的核心,这里记录下其代码实现逻辑和发现的问题。

twemproxy作为代理服务器,主体逻辑都围绕着数据流转,采用了单线程非阻塞模型,在linux下由epoll驱动整个程序的运行,对于事件驱动模块的封装在event目录下,event_base对象是引擎,conn对象是具体的连接,conn对象中定义一系列事件处理的回调函数,典型的reactor机制,linux下的实现文件是nc_epoll.c 。 
事件引擎模块使用了两层回调机制, event_base上有个基本的回调函数,这个回调函数进一步调用conn对象的相应回调函数  (注:一般直接使用conn的回调也就够了)。
面向客户端的conn回调:
        conn->recv = msg_recv;
        conn->recv_next = req_recv_next;
        conn->recv_done = req_recv_done;
        conn->send = msg_send;
        conn->send_next = rsp_send_next;
        conn->send_done = rsp_send_done;
        conn->close = client_close;
        conn->active = client_active;

        conn->enqueue_outq = req_client_enqueue_omsgq;
        conn->dequeue_outq = req_client_dequeue_omsgq;
面向后端memcached和redis的conn回调:
        conn->recv = msg_recv;
        conn->recv_next = rsp_recv_next;
        conn->recv_done = rsp_recv_done;
        conn->send = msg_send;
        conn->send_next = req_send_next;
        conn->send_done = req_send_done;
        conn->close = server_close;
        conn->active = server_active;
        conn->enqueue_inq = req_server_enqueue_imsgq;
        conn->dequeue_inq = req_server_dequeue_imsgq;
        conn->enqueue_outq = req_server_enqueue_omsgq;
        conn->dequeue_outq = req_server_dequeue_omsgq;
twemproxy面向客户端时,由proxy_accept接收连接,创建客户端conn对象,并将其加入到事件引擎中。
twemproxy面向后端时,由server_pool管理各个到后端的conn对象,同样会加入到事件引擎中。

在请求处理模块有2个主要的概念是 mbuf对象和msg对象,mbuf对象是数据缓冲区,发送和接收的数据都存放在mbuf中, 采用链式管理。msg对象是具体的逻辑请求,采用链式管理,形成请求/响应队列。请求和响应的处理模块分别在nc_request.c和nc_response.c中实现。

客户端连接的处理逻辑:

    core_recv 
        conn->recv        即msg_recv ,read事件处理
            conn->recv_next           即req_recv_next ,获得msg对象,没有则创建
            msg_recv_chain             创建mbuf对象,接收并处理数据
                  [create mbuf]
                  conn_recv       真正的read数据
                  msg_parse      解析 , mbuf->msg
                       msg_parsed   解析完成
                           conn->recv_done   即req_recv_done    
                               req_filter        过滤器,暂无操作
                               req_forward    分发请求
                                   server_pool_conn 根据key获得后端conn对象
                                   将s_conn加入写事件监控,将msg加入转发队列,可写事件被触发后转发队列内请求
                                   s_conn->enqueue_inq req_server_enqueue_imsgq
 
                  conn->recv_next      即req_recv_next,继续下一个

注:从代码实现看回调逻辑的层次性不强,收发数据放入mbuf列表,然后用writev处理,在遇到发送不完时还要拆分mbuf,重新组织iovec,实现上有些复杂。
另外conn对象的数据采用一次读/写完的方式处理,在高压力下可能会产生大量的mbuf对象。

未完待续。
posted @ 2014-03-09 13:42 star 阅读(2171) | 评论 (0)编辑 收藏

近期项目需要一个mysql代理服务器,实现mysql协议代理和路由功能,形成简单的mysql集群服务。现成的开源方案是mysql-proxy , 分析功能和源代码后发现跟我们的应用场景不太匹配,于是决定重新实现一个符合需求的mysql代理服务器,考虑到需要完美支持mysql协议,优先选择了libdrizzle库, libdrizzle是开源项目drizzle中的协议库,而drizzle可以看作mysql的分支版本,目前稳定版本是7.1.36 , 下面主要是记录使用libdrizzle中遇到的一些问题。
1. 关于nonblock模式的问题,现代应用服务器典型架构一般是使用reactor/proactor模式的事件驱动模型,如何把libdrizzle和应用服务器的驱动模型很好的结合起来尤其重要, libdrizzle支持nonblock模式,独立实现了事件驱动机制,使用poll监控网络事件,具体在drizzle_con_wait()中实现,然后通过drizzle_con_ready()遍历产生事件的网络连接,即drizzle_con_st对象,该接口难以与通常的网络事件驱动机制配合使用,性能也不太理想,具体用法可参见其自带的样例程序examples/client.cc , 也就是说libdrizzle的驱动模型需要重新封装成跟应用服务器相匹配,才能真正发挥nonblock模式的性能。

2. drizzle_result_st对象初始时一些内部数据没有初始化,容易造成程序崩溃,因此需要修改构造函数,初始化所有内部数据。涉及文件libdrizzle-2.0/structs.h 
相应字段为field, field_buffer,row 。

3. libdrizzle中运行时产生的内部对象都以双链表形式挂接在其上级对象中,例如drizzle_st对象中有个双链表维护其创建的drizzle_con_st对象,类似地,drizzle_con_st对象中有个双链表维护其创建的drizzle_result_st对象,所有的对象通过这种形式级联管理,并且这些对象中保存着上下文相关的状态,这样的实现方便资源管理,防止资源泄露,但在代理服务器中,请求和结果在不断转发过程中会形成大量的内存拷贝,为了减少转发过程中的内存拷贝,需要把drizzle_result_st显式的从drizzle_con_st中移除,当数据发往客户端完成后再删除,因此增加了drizzle_result_detach()接口,用于从drizzle_con_st对象中移除drizzle_result_st对象 , 涉及文件libdrizzle-2.0/result.h , libdrizzle-2.0/result.cc 

void drizzle_result_detach(drizzle_result_st *result)
{

  if (result->con)
  {
    result->con->result_count--;
    if (result->con->result_list == result)
      result->con->result_list= result->next;
  }

  if (result->prev)
    result->prev->next= result->next;

  if (result->next)
    result->next->prev= result->prev;

  result->con = NULL ;
  result->prev = NULL ;
  result->next = NULL ;
}
posted @ 2014-01-07 10:07 star 阅读(3039) | 评论 (0)编辑 收藏

keepalived是用来进行网络fail-over的强大工具,配置简单灵活,功能强大,实现了vrrp协议和健康检查,配合lvs使用可实现高可用性。
典型双机热备配置方案:
两台机器的keepalived实例都配置成BACKUP状态,priority高的自动作为master , priority低的自动作为slave ,也可以将priority设置为相同,先启动的作为master 。两边都设置nopreempt,防止出现故障->恢复过程中的再切换。
1.在master发生故障->恢复过程中,原backup会替换为master对外服务,当原master恢复后,一般希望原master作为新backup,以避免master的再次切换,可以使用nopreempt参数,防止priority高的发起切换。
2. 当keepalived设置为随系统启动自动启动时,应加上一定的延迟,防止网络或系统未准备好影响keepalived的状态。
3. 当后端的RS有状态(https)时,lvs一般需要使用sh负载算法或使用持久性连接,以便同一来源的请求分发到同一RS,当http和https的请求分发需要一致时,可以使用iptable对报文做fmark,使用fmark配置lvs 。
posted @ 2011-11-16 11:11 star 阅读(933) | 评论 (0)编辑 收藏

1. arp问题
   在DR或者tunnel模式下,RS需要绑定VIP以便直接将报文发回客户端。因此需要在RS上屏蔽网络内对VIP进行arp查询的响应 。
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce

2. mtu问题
   在tunnel模式下 ,LD将客户端报文进行封装(加IPIP头)后发送给RS , 因此RS需要调整MTU大小,预留IPIP头空间,以便客户端正确分包。
ifconfig "$OUT_DEV" mtu 1480

3.报文转发问题
    在DR或者tunnel模式下,报文直接转发到RS。
echo 1 > /proc/sys/net/ipv4/ip_forward

4.LD支持连接数问题
   内核ip_vs模块的参数conn_tab_bits指定了conn表的大小,最大为20 ,支持1M个连接。

5.LD做HA时VIP接管问题
   新LD接管故障LD的VIP时,需要及时做arp广播,keepalived会自动完成,也通过arping命令查看。

6.LD的cpu负载问题
   LD的网卡软中断(ksoftirqd)由一个cpu处理,无法利用多核,调整软中断的smp_affinity可以改变绑定的cpu,但无法做多核负载均衡。
   内核2.6.32之后已经支持软中断的负载均衡。
   使用支持RSS的网卡,有多个队列,或者使用多个网卡做bonding 。
  echo "alias bond0 bonding" >> /etc/modprobe.conf
  修改ifcfg-bond0 , ifcfg-ethX配置文件。

7. 系统内核参数调整参考
net.ipv4.tcp_tw_recyle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_syn_backlog = 40960
net.ipv4.tcp_keepalive_time = 1800
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_syn_retries = 1
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_max_tw_buckets = 8192
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_orphans = 40960
#net.ipv4.tcp_timestamps = 0

net.ipv4.tcp_rmem = 4194304 8388608 16777216
net.ipv4.tcp_wmem = 4194304 8388608 16777216
net.ipv4.udp_mem = 4194304 8388608 16777216
net.ipv4.udp_rmem_min = 1048576
net.ipv4.udp_wmem_min = 1048576

net.core.somaxconn = 40960
net.core.netdev_max_backlog = 40960
net.core.rmem_default = 1048576
net.core.wmem_default = 1048576
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
 参考:http://www.austintek.com/LVS/
posted @ 2011-10-10 15:55 star 阅读(636) | 评论 (0)编辑 收藏

1.服务器网卡软中断的cpu负载均衡。 1.网卡支持RSS(Receive Side Scaling,接收方扩展)。 2.内核支持RSS。
2.网卡bonding 。多块网卡绑定同一IP地址对外提供服务,通过bonding,虚拟一块网卡对外提供服务。
http://t.chinaunix.net/archiver/tid-1927269.html
posted @ 2011-08-04 10:44 star 阅读(469) | 评论 (0)编辑 收藏

多线程系统中通知用哪种方式效率更好,在一台4核Xeon 3.00GHZ的机器上对比了linux下mq和socketpair通信性能,一写线程,一读线程,初步结论是mq胜出,mq 46w/s ,socketpair 40w/s 。
posted @ 2011-08-04 10:36 star 阅读(1487) | 评论 (2)编辑 收藏

使用几个经典网络模型实现非阻塞简单http服务,部署在一台4Xeon 3.00GHZ的机器上进行压力测试。

 

短连接

长连接

客户端

1个线程

97%cpu,多核分担

60%cpu网卡中断

1.6w/s

平均响应时间10ms

100%cpu

15%cpu网卡软中断

2.8w/s

平均响应时间7ms

100并发/客户端

100w请求/客户端

2个客户端

 

4个线程

每个线程70%cpu

99%cpu网卡中断

2.1w/s

平均响应时间9ms

每个线程100%cpu

40%cpu网卡软中断

6.5w/s

平均响应时间3ms

100并发/客户端

100w请求/客户端

2个客户端

 

1leader线程,接受连接

4worker线程,处理请求

leader线程90%cpu

worker线程40%cpu

75%网卡中断

1.8w/s

平均响应时间10ms

leader线程1%cpu

worker线程100%cpu

40%网卡中断

6.0w/s

平均响应时间3ms

100并发/客户端

100w请求/客户端

2个客户端

 

 

结论:

1.       短连接中,建立连接是很耗费资源的。

2.       长连接中,多线程在提高处理能力方面是很有价值的,尤其是运算量多的请求。

3.       多个线程同时接受连接会造成cpu软中断的overhead

posted @ 2011-07-07 13:24 star 阅读(356) | 评论 (0)编辑 收藏

beanstalkd源于fackbook,是一个快速、简单的内存消息队列,也可以开启binlog,消息将被写入日志文件,用于重启时恢复数据。
1.消息被称作job,在服务器端储存在内存队列中,具有DELAYED,READY,RESERVED,BURIED状态,状态转换图如下
Here is a picture of the typical job lifecycle:


   put            reserve               delete
  
-----> [READY] ---------> [RESERVED] --------> *poof*



Here 
is a picture with more possibilities:



   put with delay               release with delay
  
----------------> [DELAYED] <------------.
                        
|                   |
                        
| (time passes)     |
                        
|                   |
   put                  v     reserve       
|       delete
  
-----------------> [READY] ---------> [RESERVED] --------> *poof*
                       
^  ^                |  |
                       
|   \  release      |  |
                       
|    `-------------'   |
                       |                      |
                       
| kick                 |
                       
|                      |
                       
|       bury           |
                    [BURIED] 
<---------------'
                       |
                       
|  delete
                        `
--------> *poof*

消息支持优先级,生存时间的设置。不同状态的消息分别处于相应状态的队列中。
2. 消息属于某个tube,tube类似于namespace或者消息主题的概念,消费者可以订阅一个或多个tube ,从而接收这些tube的消息 。
3. beanstalkd的代码实现和协议定义很类似memcached的风格。
posted @ 2011-06-29 14:43 star 阅读(1689) | 评论 (0)编辑 收藏

仅列出标题
共4页: 1 2 3 4