阿斯旺与驼峰




hongjiang 2011.7.27
OfferDetail 访问统计
► 1)大数据量:每天 2000 万 PV 左右,每周 pv 超过
 1 亿。去重后 offer 量在 5000~7000 万之间。

► 2)离散性:统计连续的 5 周,对访问去重后将近
 1.5 亿 ( 总 offer 为 2.4 亿 ) 。每天有 80% 的 offer
 只访问了 1 次,放大到一周,仍有 30% 多的 offer
 只被访问 1 次。

► 3)
  重叠率不高:以周为单位,第 1 周访问过 offer
 在第 2 周又被访问到的约 40~55% 之间。
解决方法

►   1) 缓存全部没有意义,找热点

►   2) 即使只缓存热点,想要达到较高的命中
    率,也依然要缓存很大量的数据。千万级的
    数据量,必须采用分布式
思路:冷热交换
       出队          入队


Head


   权
   重
   大
   的
   排
   在
   队
   首
            Head



       入队          出队
Aswan 的架构
Client    Client       Client               Client       Client    Client




              Index                                        Index


                                Scheduler




    Storage           Storage    Storage             Storage       Storage
服务器端的角色
►   1) Scheduler: 调度 / 配置 / 注册中心。事件的中心。
    并通过一致性哈希来将数据分布在各台 Storage 上
    。

►   2) Index: 索引服务器,用于定位某个 key 存储在
    哪个 Storage 上。 Index 可配置单台或多台,上面
    的数据是一致的。多台主要起到负载均衡的作用。

►   3) Storage: 存储服务器,根据数据权重的变化自
    动调节其级别。
客户端
►     与 memcached 的 client 不同,“轻”客户
    端, client 知道的很少,服务器端的变化对
    client 来说是透明的,比如新增 / 减少
    Storage ,对 client 来说是无需了解的。

►   client 定时从配置中心获取可用的 Index 列
    表。实现方式同 napoli
Client 的一次请求过程
1) 未命中的情况:
   Client       2 未命中,
                返回 null


   1 Query                       6 更新 Index
                       Index


                           3 发送事件         Scheduler


                          4 定位并通知


             Storage
                               5 更新后反馈
Client 的一次请求过程
2) Index 命中 , Storage 未命中的情况:
                      1 Query
        Client
                                  Index
                    2 命中,返回存储节点

5 Set

        4 返回        3 Get
        location
        Val 为空

          Storage
数据更新的解决
►    使用 napoli 监听变更

                           remove
              Scheduler             Task   napoli




                  remove



    Storage   Storage         Storage
存储结构
► 借鉴 Redis 的做法,提供   K-V, K-Hash, K-
 List
 等结构
应用场景:
 1) 页面的异步加载,同一个 id 对应几个
 value ,可采用 K-Hash

 2) 交易订单数, A 页面显示 20 条, B 页面
 显示 10 条,可采用 K-List
通讯框架的选择
1, 序列化
 1) protocolBuffers: 不适宜在现有业务上采用。
 2) json/hessian: 性价比未必高
 3) Thrift : 想采用,但不熟悉
 4) 现在采用 java serialize + gzip 以后再优化

这里有一个详细的对比:
https://github.com/eishay/jvm-serializers
反馈给温少后,他很快对 fastjson 做了优化,后又根
  据江航的建议采用 asm 优化,最 1.1.1 版 fastjson
  已经超过了 protoBuffers
通讯框架的选择
2, 通讯协议
  1) mina/netty : 缺乏 NIO 网络编程的经验,其中的
  陷阱也不少。
 http://www.slideshare.net/killme2008/nio-trick-and-trap-8464155


 2) tb-remoting: dubbo 最初采用的。基于 mina ,
 后来 dubbo 开发了自己 dubbo-remoting 通讯框架
 ,基于 netty3 ,但在写 Aswan 时还没有发布。

 3) http: 采用嵌入式 jetty 用于 http 连接管理。简
 单,但 http 在并发较高时衰减的较快,性能不佳。
Jetty 的调优
►   1) 增加 jetty.acceptors    以应对高并发
         使用 apache ab 50 并发测试, acceptors=8 比 2 有
        明显提高

       http 在并发情况下的衰减 (acceptors=2) :
       1 个线程 http 耗时平均 1.7ms 左右,
       10 个线程并发将衰减到 5ms 左右,
       20 个线程并发将衰减到 10ms ,
       50 个线程降到 20ms
Jetty 的调优
►   2) Http-KeepAlive 是否起了作用?




开启 keepAlive , SelectChannelEndPoint 的实
 例数应该是较稳定的。
事件过程 : 上 -> 下
                                       Storage


IndexServer
EventQueue          Scheduler

                    BlockingQueue

EventQueue                      消费端
              多线程
                                单线程串
                                行通知
事件过程 : 上 -> 下

IndexServer              Scheduler
  EventQueue             BlockingQueue     消费者:
                                           单线程



               Storage: ChangeRecorder



        消费者:
                消费者                  生产者
        单线程     缓冲区                  缓冲区

                 Exchanger :达到一定量再消费
事件过程 : 下 -> 上
Storage

                        Scheduler
Exchanger     多线程


Exchanger




                    异
                    步
                              Exchanger
                                          单线程串
Exchanger                                 行通知



                    Index



                     Index
事件队列
► 借鉴         AWT 中的 EventQueue
    com.alibaba.aswan.server.index.WeightingChangeEventQueue.java


►    事件的合并: coalesceEvent ,对于离散事
    件意义不大。

►     锁分离: 两把锁, putlock 和 tacklock
      参考 LinkedBlockingQueue
缓冲区的设计
Exchanger : 简单好用。需考虑缓冲区的大小。生产
 者的节奏与消费者的节奏是否匹配的上。

Queue: 需考虑批量消费的需求。自实现批量出列,
 或者单个出列后累计一定量再一并处理。如果是
 BlockingQueue 注意初始容量,太小会导致高并发
 情况下生产者阻塞。进而可能引起网络阻塞等连锁
 反应。 Aswan 在第一个测试版本遇到过。
一致性哈希的选择
► Ketama  通过 MD5 实现
 last.fm 最早采用, spy/xmemcached 等
 client 也采用。
 测试中发现 240 个虚拟节点最佳。
 see: com.alibaba.aswan.server.schedule.locator.KetamaLocator.java



► MurmurHash ,更高效的 Hash , Redis 的
 java-client 中采用
权重因素
►   1) 访问次数: 越多越好
►   2) 最后访问事件:越近越好

权重公式:权重值 = 访问次数 * 系数
系数与最后访问时间有关,越近系数越高:
系数公式: Math.pow(WEEK_FACTOR, lastAccTime - startTime)

// week_factor 是一个经验值
public static final float WEEK_FACTOR = 1.35F
权重因素

► 3)
  增加优先级属性,某些过大的对象优先
 级设置低一些,比如大于 1m 的只能沦为二
 等公民,不能存储在 L1 中
Storage 的内部结构




                       L2                L1
ColdPool
                  Val 存在磁盘          Val 存在内存




           Swap              Swap
双向队列的实现
      DoubleEndedPriorityQueue
           维持 2 个 heap,minheap 和 maxheap




               2                                 7


       5            4                      6           2



6              7                   5            4



    Min heap                                   Max heap

    http://www.cise.ufl.edu/~sahni/dsaaj/enrich/c13/double.htm
性能的考虑
► 1)千万级对象,需要分成很多个区域
    否则同步问题成了瓶颈,参考
 ConcurrentHashMap 的做法,每 1 万个元素存储在
 一个 Segment 上

► 2)
  避免频繁的堆排序,堆中元素改变超过一个阈值
 再进行重排序。

► 3)
  双向堆注意关联影响,对 referent 做一层包装,
 清除元素时避免引起堆的调整 , 手动排序。
GC 策略与内存调优




对象太大,采用 Gzip 压缩后平均每个线上
 的 OfferDO 对象 5.5k (detail 字段占了大
 头)
吞吐量优先还是应用响应优先
►   UseParallelOldGC or CMS or G1 ?

►   一次 Full GC 的代价?
     旧生代的空间有 3.5G
按代 GC 策略的组合方式




http://blogs.oracle.com/jonthecollector/entry/our_collectors
线上前台应用的 GC 策略

以 Exodus2 为例: -server -Xmx2g -Xms2g -Xmn512m
 -XX:PermSize=128m -XX:MaxPermSize=196m -Xss256k
 -XX:+DisableExplicitGC       // 禁止外部应用程序强制进行垃圾收集 ,
   将 System.gc() 调用转换成一个空操作
 -XX:+UseConcMarkSweepGC      // 启用 CMS
 -XX:+CMSParallelRemarkEnabled // 尽量减少 mark 的时间
 -XX:+UseCMSCompactAtFullCollection // 对 live object 进行整理,使
   memory 碎片减少
 -XX:+UseFastAccessorMethods        // 对 get 、 set 方法转成本地代码
 -XX:+UseCMSInitiatingOccupancyOnly // 只有在 old generation 在达到初
   始化的比例后启动收集
 -XX:+UseCompressedOops          // 压缩普通对象指针,针对 64 位系统
►    默认老年代到 92% 才进行 GC (一些老版
    本的 JVM 是 68% )

► 可通过
-XX:CMSInitiatingOccupancyFraction=80
 指定为 80% 时进行
精简 HashMap
► HashMap 中的 Entry 存在浪费


► 修改 HashMap ,实现一个 MyHashMap
将其初始容量与增长因子设的紧凑一些

► 千万对象,内存节省了约 300m
架构的缺陷
► 1) 角色较多
► 2) 2 次网络访问,比 memcached 等多一次
 网络开销。
► 3) 想要做到 server 端透明的增减服务器,
 但实际这种需求不强烈。
► 4) hump ,一个简化版的热点 cache ,将采
 用 dubbo-remoting/fastjson ,类似
 memcached 方式。包含 K-List, K-Hash 等
 存储结构。
分布式领域的一些技术
►    Gossip 协议 : 去中心化
      又被称为反熵( Anti-Entropy ),反熵就是在杂
    乱无章中寻求一致
    Gossip 的特点:在一个有界网络中,每个节点都随
    机地与其他节点通信,经过一番杂乱无章的通信,
    最终所有节点的状态都会达成一致。每个节点可能
    知道所有其他节点,也可能仅知道几个邻居节点,
    只要这些节可以通过网络连通,最终他们的状态都
    是一致的,这也是疫情传播的特点。
分布式领域的一些技术
Gossip 的案例 :

►    Cassandra

►    Redis 的分布式实现草案。
    http://redis.io/presentation/Redis_Cluster.pdf
分布式领域的一些技术
►   Paxos 算法 : 解决数据一致性。
      算法比较复杂。

Yahoo! 开源的 ZooKeeper 是一个开源的类
  Paxos 实现

Google Chubby 也基于 paxos 算法
参考
►   << 面向模式的软件架构 - 第 4 卷 : 分布式
    计算的模式语言 >>

►   Napoli Client

►   Jedis https://github.com/xetorthio/jedis.git
     Redis 的一个 java 客户端

Aswan&hump

  • 1.
  • 2.
    OfferDetail 访问统计 ► 1)大数据量:每天2000 万 PV 左右,每周 pv 超过 1 亿。去重后 offer 量在 5000~7000 万之间。 ► 2)离散性:统计连续的 5 周,对访问去重后将近 1.5 亿 ( 总 offer 为 2.4 亿 ) 。每天有 80% 的 offer 只访问了 1 次,放大到一周,仍有 30% 多的 offer 只被访问 1 次。 ► 3) 重叠率不高:以周为单位,第 1 周访问过 offer 在第 2 周又被访问到的约 40~55% 之间。
  • 3.
    解决方法 ► 1) 缓存全部没有意义,找热点 ► 2) 即使只缓存热点,想要达到较高的命中 率,也依然要缓存很大量的数据。千万级的 数据量,必须采用分布式
  • 4.
    思路:冷热交换 出队 入队 Head 权 重 大 的 排 在 队 首 Head 入队 出队
  • 5.
    Aswan 的架构 Client Client Client Client Client Client Index Index Scheduler Storage Storage Storage Storage Storage
  • 6.
    服务器端的角色 ► 1) Scheduler: 调度 / 配置 / 注册中心。事件的中心。 并通过一致性哈希来将数据分布在各台 Storage 上 。 ► 2) Index: 索引服务器,用于定位某个 key 存储在 哪个 Storage 上。 Index 可配置单台或多台,上面 的数据是一致的。多台主要起到负载均衡的作用。 ► 3) Storage: 存储服务器,根据数据权重的变化自 动调节其级别。
  • 7.
    客户端 ► 与 memcached 的 client 不同,“轻”客户 端, client 知道的很少,服务器端的变化对 client 来说是透明的,比如新增 / 减少 Storage ,对 client 来说是无需了解的。 ► client 定时从配置中心获取可用的 Index 列 表。实现方式同 napoli
  • 8.
    Client 的一次请求过程 1) 未命中的情况: Client 2 未命中, 返回 null 1 Query 6 更新 Index Index 3 发送事件 Scheduler 4 定位并通知 Storage 5 更新后反馈
  • 9.
    Client 的一次请求过程 2) Index命中 , Storage 未命中的情况: 1 Query Client Index 2 命中,返回存储节点 5 Set 4 返回 3 Get location Val 为空 Storage
  • 10.
    数据更新的解决 ► 使用 napoli 监听变更 remove Scheduler Task napoli remove Storage Storage Storage
  • 11.
    存储结构 ► 借鉴 Redis的做法,提供 K-V, K-Hash, K- List 等结构 应用场景: 1) 页面的异步加载,同一个 id 对应几个 value ,可采用 K-Hash 2) 交易订单数, A 页面显示 20 条, B 页面 显示 10 条,可采用 K-List
  • 12.
    通讯框架的选择 1, 序列化 1)protocolBuffers: 不适宜在现有业务上采用。 2) json/hessian: 性价比未必高 3) Thrift : 想采用,但不熟悉 4) 现在采用 java serialize + gzip 以后再优化 这里有一个详细的对比: https://github.com/eishay/jvm-serializers 反馈给温少后,他很快对 fastjson 做了优化,后又根 据江航的建议采用 asm 优化,最 1.1.1 版 fastjson 已经超过了 protoBuffers
  • 13.
    通讯框架的选择 2, 通讯协议 1) mina/netty : 缺乏 NIO 网络编程的经验,其中的 陷阱也不少。 http://www.slideshare.net/killme2008/nio-trick-and-trap-8464155 2) tb-remoting: dubbo 最初采用的。基于 mina , 后来 dubbo 开发了自己 dubbo-remoting 通讯框架 ,基于 netty3 ,但在写 Aswan 时还没有发布。 3) http: 采用嵌入式 jetty 用于 http 连接管理。简 单,但 http 在并发较高时衰减的较快,性能不佳。
  • 14.
    Jetty 的调优 ► 1) 增加 jetty.acceptors 以应对高并发 使用 apache ab 50 并发测试, acceptors=8 比 2 有 明显提高  http 在并发情况下的衰减 (acceptors=2) :  1 个线程 http 耗时平均 1.7ms 左右,  10 个线程并发将衰减到 5ms 左右,  20 个线程并发将衰减到 10ms ,  50 个线程降到 20ms
  • 15.
    Jetty 的调优 ► 2) Http-KeepAlive 是否起了作用? 开启 keepAlive , SelectChannelEndPoint 的实 例数应该是较稳定的。
  • 16.
    事件过程 : 上-> 下 Storage IndexServer EventQueue Scheduler BlockingQueue EventQueue 消费端 多线程 单线程串 行通知
  • 17.
    事件过程 : 上-> 下 IndexServer Scheduler EventQueue BlockingQueue 消费者: 单线程 Storage: ChangeRecorder 消费者: 消费者 生产者 单线程 缓冲区 缓冲区 Exchanger :达到一定量再消费
  • 18.
    事件过程 : 下-> 上 Storage Scheduler Exchanger 多线程 Exchanger 异 步 Exchanger 单线程串 Exchanger 行通知 Index Index
  • 19.
    事件队列 ► 借鉴 AWT 中的 EventQueue com.alibaba.aswan.server.index.WeightingChangeEventQueue.java ► 事件的合并: coalesceEvent ,对于离散事 件意义不大。 ► 锁分离: 两把锁, putlock 和 tacklock 参考 LinkedBlockingQueue
  • 20.
    缓冲区的设计 Exchanger : 简单好用。需考虑缓冲区的大小。生产 者的节奏与消费者的节奏是否匹配的上。 Queue: 需考虑批量消费的需求。自实现批量出列, 或者单个出列后累计一定量再一并处理。如果是 BlockingQueue 注意初始容量,太小会导致高并发 情况下生产者阻塞。进而可能引起网络阻塞等连锁 反应。 Aswan 在第一个测试版本遇到过。
  • 21.
    一致性哈希的选择 ► Ketama 通过 MD5 实现 last.fm 最早采用, spy/xmemcached 等 client 也采用。 测试中发现 240 个虚拟节点最佳。 see: com.alibaba.aswan.server.schedule.locator.KetamaLocator.java ► MurmurHash ,更高效的 Hash , Redis 的 java-client 中采用
  • 22.
    权重因素 ► 1) 访问次数: 越多越好 ► 2) 最后访问事件:越近越好 权重公式:权重值 = 访问次数 * 系数 系数与最后访问时间有关,越近系数越高: 系数公式: Math.pow(WEEK_FACTOR, lastAccTime - startTime) // week_factor 是一个经验值 public static final float WEEK_FACTOR = 1.35F
  • 23.
    权重因素 ► 3) 增加优先级属性,某些过大的对象优先 级设置低一些,比如大于 1m 的只能沦为二 等公民,不能存储在 L1 中
  • 24.
    Storage 的内部结构 L2 L1 ColdPool Val 存在磁盘 Val 存在内存 Swap Swap
  • 25.
    双向队列的实现 DoubleEndedPriorityQueue 维持 2 个 heap,minheap 和 maxheap 2 7 5 4 6 2 6 7 5 4 Min heap Max heap http://www.cise.ufl.edu/~sahni/dsaaj/enrich/c13/double.htm
  • 26.
    性能的考虑 ► 1)千万级对象,需要分成很多个区域 否则同步问题成了瓶颈,参考 ConcurrentHashMap 的做法,每 1 万个元素存储在 一个 Segment 上 ► 2) 避免频繁的堆排序,堆中元素改变超过一个阈值 再进行重排序。 ► 3) 双向堆注意关联影响,对 referent 做一层包装, 清除元素时避免引起堆的调整 , 手动排序。
  • 27.
    GC 策略与内存调优 对象太大,采用 Gzip压缩后平均每个线上 的 OfferDO 对象 5.5k (detail 字段占了大 头)
  • 28.
    吞吐量优先还是应用响应优先 ► UseParallelOldGC or CMS or G1 ? ► 一次 Full GC 的代价?  旧生代的空间有 3.5G
  • 29.
  • 30.
    线上前台应用的 GC 策略 以Exodus2 为例: -server -Xmx2g -Xms2g -Xmn512m -XX:PermSize=128m -XX:MaxPermSize=196m -Xss256k -XX:+DisableExplicitGC // 禁止外部应用程序强制进行垃圾收集 , 将 System.gc() 调用转换成一个空操作 -XX:+UseConcMarkSweepGC // 启用 CMS -XX:+CMSParallelRemarkEnabled // 尽量减少 mark 的时间 -XX:+UseCMSCompactAtFullCollection // 对 live object 进行整理,使 memory 碎片减少 -XX:+UseFastAccessorMethods // 对 get 、 set 方法转成本地代码 -XX:+UseCMSInitiatingOccupancyOnly // 只有在 old generation 在达到初 始化的比例后启动收集 -XX:+UseCompressedOops // 压缩普通对象指针,针对 64 位系统
  • 31.
    默认老年代到 92% 才进行 GC (一些老版 本的 JVM 是 68% ) ► 可通过 -XX:CMSInitiatingOccupancyFraction=80 指定为 80% 时进行
  • 32.
    精简 HashMap ► HashMap中的 Entry 存在浪费 ► 修改 HashMap ,实现一个 MyHashMap 将其初始容量与增长因子设的紧凑一些 ► 千万对象,内存节省了约 300m
  • 33.
    架构的缺陷 ► 1) 角色较多 ►2) 2 次网络访问,比 memcached 等多一次 网络开销。 ► 3) 想要做到 server 端透明的增减服务器, 但实际这种需求不强烈。 ► 4) hump ,一个简化版的热点 cache ,将采 用 dubbo-remoting/fastjson ,类似 memcached 方式。包含 K-List, K-Hash 等 存储结构。
  • 34.
    分布式领域的一些技术 ► Gossip 协议 : 去中心化 又被称为反熵( Anti-Entropy ),反熵就是在杂 乱无章中寻求一致 Gossip 的特点:在一个有界网络中,每个节点都随 机地与其他节点通信,经过一番杂乱无章的通信, 最终所有节点的状态都会达成一致。每个节点可能 知道所有其他节点,也可能仅知道几个邻居节点, 只要这些节可以通过网络连通,最终他们的状态都 是一致的,这也是疫情传播的特点。
  • 35.
    分布式领域的一些技术 Gossip 的案例 : ► Cassandra ► Redis 的分布式实现草案。 http://redis.io/presentation/Redis_Cluster.pdf
  • 36.
    分布式领域的一些技术 ► Paxos 算法 : 解决数据一致性。 算法比较复杂。 Yahoo! 开源的 ZooKeeper 是一个开源的类 Paxos 实现 Google Chubby 也基于 paxos 算法
  • 37.
    参考 ► << 面向模式的软件架构 - 第 4 卷 : 分布式 计算的模式语言 >> ► Napoli Client ► Jedis https://github.com/xetorthio/jedis.git  Redis 的一个 java 客户端

Editor's Notes

  • #6 红色箭头代表事件。 Scheduler 是中心。