设为首页 收藏本站
查看: 1629|回复: 0

[经验分享] Flume 实战(2)--Flume-ng-sdk源码分析

[复制链接]

尚未签到

发表于 2015-9-16 13:23:03 | 显示全部楼层 |阅读模式
  具体参考: 官方用户手册和开发指南
  http://flume.apache.org/FlumeDeveloperGuide.html
  *) 定位和简单例子
  1). Flume-ng-sdk是用于编写往flume agent发送数据的client sdk
2). 简单示例



RpcClient client = null;
try {
  client = RpcClientFactory.getDefaultInstance("127.0.0.1", 41414);
  Event event = EventBuilder.withBody("hello flume", Charset.forName("UTF-8"));
  client.append(event);
} catch (EventDeliveryException e) {
  e.printStackTrace();
} finally {
  if ( client != null ) {
    client.close();
  }
}
  *) Event设计和类层次结构
  1. Event类设计
在Flume中Event是个接口类



public interface Event {
  public Map<String, String> getHeaders();
  public void setHeaders(Map<String, String> headers);
  public byte[] getBody();
  public void setBody(byte[] body);
}
  由代码可得, Event由Header集合和消息负载两部分构成.
  2. Builder设计模式
在org.apache.flume.event下, 有两个Event的具体实现类: SimpleEvent, JSonEvent.
EventBuilder类顾名思义, 采用Builder的方式来组装对象的成员, 并产生最终的对象.



public class EventBuilder {
  public static Event withBody(byte[] body, Map<String, String> headers) {
    Event event = new SimpleEvent();
    if(body == null) {
      body = new byte[0];
    }
    event.setBody(body);
    if (headers != null) {
      event.setHeaders(new HashMap<String, String>(headers));
    }
    return event;
  }
  public static Event withBody(byte[] body) {
    return withBody(body, null);
  }
  public static Event withBody(String body, Charset charset,
      Map<String, String> headers) {
    return withBody(body.getBytes(charset), headers);
  }
  public static Event withBody(String body, Charset charset) {
    return withBody(body, charset, null);
  }
}
  java的访问控制符: public/default/protected/private, default表示同package可见
不过另人意外的是, 其对应的SimpleEvent的构造函数的修饰符是public, 即不是default, 也不是protected, 这点让EventBuilder的引入有些失败.把Builder模式, 用到极致的是Google Protocol Buffer(java), 其每个PB对象, 都是用相应的Builder类来组装和生成. 采用这种Builder模式的用途是, 把一个对象元素的修改和读取彻底分离, 使得一个PB对象,从诞生后就是一个immutable对象, 只能读取其属性信息, 而不能修改其属性.
  *) RpcClient设计和类层次结构
  1. RpcClient的接口定义:



public interface RpcClient {
  public int getBatchSize();
public void append(Event event) throws EventDeliveryException;
public void appendBatch(List<Event> events) throws EventDeliveryException;
public boolean isActive();
public void close() throws FlumeException;
}
  2. AbstractRpcClient的抽象类定义:



public abstract class AbstractRpcClient implements RpcClient {
protected int batchSize = RpcClientConfigurationConstants.DEFAULT_BATCH_SIZE;
protected long connectTimeout = RpcClientConfigurationConstants.DEFAULT_CONNECT_TIMEOUT_MILLIS;
protected long requestTimeout = RpcClientConfigurationConstants.DEFAULT_REQUEST_TIMEOUT_MILLIS;
@Override
public int getBatchSize(){
return batchSize;
}
protected abstract void configure(Properties properties) throws FlumeException;
}
  新增了一些常量定义, 和新的抽象函数configure(Properties prop);
  3. RpcClient工厂类的使用
RpcClientFactory的定义



public class RpcClientFactory {
public static RpcClient getInstance(Properties properties) throws FlumeException {
// 1). 获取具体rpcclient的类型信息
properties.getProperty(RpcClientConfigurationConstants.CONFIG_CLIENT_TYPE);
// 2). 利用反射,获取类的class
Class<? extends AbstractRpcClient> clazz = (Class<? extends AbstractRpcClient>) Class.forName(...);
// 3). 产生类对象
RpcClient client = clazz.newInstance();
// 4). 进行具体rpcclient实例的配置初始化
client.configure(properties);
// 5). 返回对象
return client;
}
}
  RpcClientFactory借助静态方法getInstance, 其依据Properties里的相应key/value来, 来产生不同的对象实例, 配置不同的属性. 同时RpcClient的具体实例, 其构造方法的访问限定符都是protected, 这一点做的, 比之前EventBuilder设计和实现要规范和清晰.



clazz = Class.forName(...);
client = class.newInstance();
client.configure(...);
  是种非常好的实践代码, 把面向对象的多态性用到极致
  4. 具体的RpcClient类的实现
其SDK提供了两大类, 具体的实现类ThriftRpcClient和AvroRpcClient
4.1. 对ThriftRpcClient的解读
4.1.1 thrift idl的定义
idl文件(src/main/thrift/flume.thrift)的定义



namespace java org.apache.flume.thrift
struct ThriftFlumeEvent {
  1: required map <string, string> headers,
  2: required binary body,
}
enum Status {
  OK,
  FAILED,
  ERROR,
  UNKNOWN
}
service ThriftSourceProtocol {
  Status append(1: ThriftFlumeEvent event),
  Status appendBatch(1: list<ThriftFlumeEvent> events),
}
  分别对应源码包org.apache.flume.thrift下
  Status, ThriftFlumeEvent, ThriftSourceProtocol类
4.1.2 ThriftRpcClient的实现
ThriftRpcClient并不是简单对ThriftSourceProtocol的客户端的简单封装




public class ThriftRpcClient extends AbstractRpcClient {
  private ConnectionPoolManager connectionManager;
  private final ExecutorService callTimeoutPool;
  private final AtomicLong threadCounter;
  // ......
}
  评注: 粗略观察其类成员, 其借助线程池(ExecutorService)和连接池(ConnectionManager)管理, 来实现RpcClient的发送接口, 这样append(), appendBatch()的接口都是线程安全的, 该客户端的实例能用于多线程并发使用.
  AvroRpcClient代码结构差不多, 先一笔带过.
  5. 两个重要的实现类
FailOverRpcClient的源码解析:
这边采用装饰模式(Decorator Pattern), FailOverRpcClient继承自RpcClient, 同时又拥有实际的RpcClient实例, 只是在实际RpcClient基础上, 添加了失败后重试的能力.
  FailOver是失败后重试的机制, 通常借助带尝试次数的重试来实现
其append(Event e)方法中:



int tries = 0;
while (tries < maxTries) {
  try {
    tries++;
    localClient = getClient();
    localClient.append(event);
    return;
  } catch (EventDeliveryException e) {
    localClient.close();
    localClient = null;
  } catch (Exception e2) {
    throw new EventDeliveryException(
    "Failed to send event. Exception follows: ", e2);
}
}   
  这段代码采用相对简单的.
  getNextClient()的实现如下:



for (int count = lastCheckedhost + 1; count < limit; count++) {
  HostInfo hostInfo = hosts.get(count);
  try {
    setDefaultProperties(hostInfo, props);
    localClient = RpcClientFactory.getInstance(props);
    lastCheckedhost = count;
    return localClient;
  } catch (FlumeException e) {
    logger.info("Could not connect to " + hostInfo, e);
    continue;
  }
}
for(int count = 0; count <= lastCheckedhost; count++) {
  HostInfo hostInfo = hosts.get(count);
  try {
    setDefaultProperties(hostInfo, props);
    localClient = RpcClientFactory.getInstance(props);
    lastCheckedhost = count;
    return localClient;
  } catch (FlumeException e) {
    logger.info("Could not connect to " + hostInfo, e);
    continue;
  }
}
  HostInfo封装了一个远端服务的ip地址
FailOver简单的轮询了各个服务地址.
  LoadBalancingRpcClient的源码解析:
LoadBalancingRpcClient顾名思义, 采用负载均衡的策略来实现, 其还是采用遍历(轮询/随机)+反馈的机制, 来动态的调整服务列表的候选顺序.
在append(Event)方法中:



Iterator<HostInfo> it = selector.createHostIterator();
while (it.hasNext()) {
  HostInfo host = it.next();
  try {
    RpcClient client = getClient(host);
    client.append(event);
    eventSent = true;
    break;
  } catch (Exception ex) {
    selector.informFailure(host);
    LOGGER.warn("Failed to send event to host " + host, ex);
  }
}
if (!eventSent) {
  throw new EventDeliveryException("Unable to send event to any host");
}
  selector.createHostIterator() 创建当前服务候选列表的一个快照, 同时递进一个轮询单元.
selector.informFailure(host) 是对失败的服务进行降级处理
  而HostSelector接口定义如下:



public interface HostSelector {
  void setHosts(List<HostInfo> hosts);
  Iterator<HostInfo> createHostIterator();
  void informFailure(HostInfo failedHost);
}
  其具体实现类
  #). RoundRobinHostSelector, 借助轮询的方式来实现
#). RandomOrderHostSelector, 借助随机的方式来实现
这两个类, 都是借助OrderSelector<T>的实现类来实现, OrderSelector封装了对错误服务机器列表的屏蔽策略
该屏蔽策略如下所示:
失败一次, 设置一个恢复时间点, 未到该恢复时间点, 则不允许获取该机器ip/port
同时为了惩罚多次失败, 减少获取该服务机器的ip/port, 采用1000 * (1 << sequentialFails), 连续失败次数, 其恢复时间的间隔要加大.
  *) Properties的属性配置
基本的属性配置



client.type = default (for avro) or thrift (for thrift)
hosts = h1 # default client accepts only 1 host
hosts.h1 = host1.example.org:41414 # host and port must both be specified
batch-size = 100 # Must be >=1 (default: 100)
connect-timeout = 20000 # Must be >=1000 (default: 20000)
request-timeout = 20000 # Must be >=1000 (default: 20000)
  FailOver支持的配置



client.type = default_failover
hosts = h1 h2 h3 # at least one is required, but 2 or more makes better sense
max-attempts = 3 # Must be >=0 (default: number of hosts
  Balancing支持的配置



client.type = default_loadbalance
hosts = h1 h2 h3 # At least 2 hosts are required
backoff = false # Specifies whether the client should back-off from a failed host
maxBackoff = 0 # Max timeout in millis
host-selector = round_robin # The host selection strategy used
  *) 异常类定义
  EventDeliveryException和FlumeException

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-114524-1-1.html 上篇帖子: Flume 实战(1) 下篇帖子: Flume-NG启动过程源码分析(二)(原创)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表