Thrift 个人实战--Thrift RPC服务框架日志的优化
前言:
Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还是有一定距离, 本系列将对Thrift作代码解读和框架扩充, 使得它更加贴近生产环境. 本文讲述RPC服务框架中, 日志的重要性, 以及logid的引入. 日志不仅包含丰富的数据(就看是否会挖掘), 而且还是线上服务问题追踪和排查错误最好的方式.
日志级别
采用大家喜闻乐见的log4j作为该RPC服务框架首选的日志库. 其对日志的级别有如下几种:
1). TRACE 最细粒度级别日志级别
2). DEBUG 对调试应用程序有帮助的日志级别
3). INFO 粗粒度级别突出强调应用程序的运行
4). WARN 表明潜在错误的情形
5). ERROR 明确发生错误, 但不影响系统继续运行
6). FATAL 严重的错误, 会导致应用工作不正常
日志级别等级顺序如下: TRACE < DEBUG < INFO < WARN < ERROR < FATAL
而应用具体的输出取决于日志级别的设置(包含及以上才会输出), 往往项目该上线采用DEBUG级别(日志量大, 容易写满磁盘), 等系统稳定后采用INFO级别
RPC服务日志需求
上述的日志需求虽然能定位问题, 但往往存在如下问题:
1). 很多日志只是简单了记录该点(代码行)运行过, 或是运行到该点的数据快照.
2). 服务由多种模块(每个模块由有多个节点构成)组成, 之间的日志串联不起来.
而好的日志设计, 必须能满足
1). 以完整的一次RPC调用作为单位(不是某个执行点快照, 而是完整的RPC callback过程), 并输出完整的一行日志记录, 包括(时间点, 来源, 输入参数, 输出参数, 中间经历的子过程, 消耗时间).
2). 引入logid, 作为多个模块之间串联的依据.
RPC级别的日志解决方案
尝试如下navie的方式去实现
public String echo(String msg) {StringBuilder sb = new StringBuilder();// *) 记录输入参数sb.append("[request: {msg: msg}]");// *) 访问缓存服务sb.append("[action: access redis, consume 100ms]");// *). 访问后端数据库sb.append("[action: dao, consume 100ms]");// *). 记录返回结果sb.append("[response: {msg}]"); logger.info(sb.toString()); return msg;}
评注: 这边的echo函数代表了一个rpc服务调用接口, 且简化了各个组件的交互. 同时引入StringBuilder, 记录各个交互的过程和时间消耗, 最后统一由函数出口前使用logger进行日志的统一输入.
但是这种方式弊端非常的明显:
1. 假设该rpc服务的函数, 存在多个出口
2. 函数存在嵌套调用, 需要嵌套子函数的过程信息
如下面的代码片段, 可参考:
public boolean verifySession() {// ***********我要记录日志(*^__^*) ***************
}public String echo(String msg) {StringBuilder sb = new StringBuilder();// *) 调用子过程verifySession();// *) 记录输入参数sb.append("[request: {msg: msg}]");// *) 访问缓存服务if ( KeyValueEngine Access Fail ) {// *********日志记录在那里***********throw new Exception();}sb.append("[action: access redis, consume 100ms]");// *). 访问后端数据库if ( Database Access Fail ) {// *********日志记录在那里***********throw new Exception();}sb.append("[action: dao, consume 100ms]");// *). 记录返回结果sb.append("[response: {msg}]");logger.info(sb.toString());return msg;}
评注: 子函数verifySession的调用, 需要把StringBuilder对象往里传, 才能记录相关的信息, 而多个异常出口, 需要把日志输入往里添加(这个繁琐且容易忘记). 这种方案只能说容易想到, 但不是最佳的方案.
有一点不可否认, rpc调用始终在同一个线程中. 聪明的读者是否猜到了最佳的解决方案.
对, 就是大杀器ThreadLocal,其能解决子函数调用的问题, 那多出口问题呢? 让rpc服务框架去处理, 其作为具体rpc调用的最外层.
采用动态代理类, 去拦截rpc的handler接口调用.
public class LogProxyHandler<T> implements InvocationHandler {private T instance;public LogProxyHandler(T instance) {this.instance = instance;}public Object createProxy() {return Proxy.newProxyInstance(instance.getClass().getClassLoader(), instance.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {// *) 函数调用前, 拦截处理, 作ThreadLocal的初始化工作LoggerUtility.beforeInvoke(); // -----(1)try {Object res = method.invoke(instance, args);// *) 函数成功返回后, 拦截处理, 进行日志的集中输出 LoggerUtility.returnInvoke(); // -----(2)return res;} catch (Throwable e) {// *) 出现异常后, 拦截处理, 进行日志集中输入 // -----(3)LoggerUtility.throwableInvode("[result = exception: {%s}]", e.getMessage());throw e;}}}
代码评注:
(1). 拦截点beforeInvoke用于ThreadLocal的初始话工作, 日志缓存的清空
(2). 拦截点returnInvoke用于函数成功返回后, 进行日志集中输出
(3). 拦截点throwableInvoke用于出现异常后, 进行日志的集中输出
同时在rpc服务调用中, 才用LoggerUtility的noticeLog静态函数(简单缓存中间日志过程)代替之前的StringBuilder.append来记录中间子过程
LoggerUtility的代码如下所示:
public class LoggerUtility {private static final Logger rpcLogger = LoggerFactory.getLogger("rpc");public static final ThreadLocal<StringBuilder> threadLocals = new ThreadLocal<StringBuilder>();public static void beforeInvoke() {StringBuilder sb = threadLocals.get();if ( sb == null ) {sb = new StringBuilder();threadLocals.set(sb);}sb.delete(0, sb.length());}public static void returnInvoke() {StringBuilder sb = threadLocals.get();if ( sb != null ) {rpcLogger.info(sb.toString());}}public static void throwableInvode(String fmt, Object... args) {StringBuilder sb = threadLocals.get();if ( sb != null ) {rpcLogger.info(sb.toString() + " " + String.format(fmt, args));}}public static void noticeLog(String fmt, Object... args) {StringBuilder sb = threadLocals.get();if ( sb != null ) {sb.append(String.format(fmt, args));}}}
两者的结合完美的解决了上述RPC的日志问题, 是不是很赞.
Logid的日志解决方案
Thrift框架本身是没有logid的概念的, 我们很难去改动thrift的rpc协议, 去添加它(比如大百度的做法是把logid作为rpc协议本身一部分). 这边的解决方案是基于约定. 我们采用如下约定, 所有的rpc请求参数都封装为一个具体Request对象, 所有的返回结构都封装为一个具体的Response对象, 而每个Request对象首个属性是logid.
比如如下的结构定义:
struct EchoRequest {1: required i64 logid = 1001,2: required string msg
}
struct EchoResponse {1: required i32 status,2: optional string msg
}service EchoService {EchoResponse echo(1: EchoRequest req);
}
评注: Request结构中logid, 就是约定的需要加到rpc的请求结构里去的.
我一直觉得: 约定优于配置, 约定优于框架.
后续
中间插入日志处理这块, 后续讲述之前计划的服务发布/订阅化, 借助zookeeper来构建一个简单的系统, 敬请期待.
转载于:https://www.cnblogs.com/mumuxinfei/p/3876190.html
Thrift 个人实战--Thrift RPC服务框架日志的优化相关推荐
- NET Core微服务之路:自己动手实现Rpc服务框架,基于DotEasy.Rpc服务框架的介绍和集成...
原文:NET Core微服务之路:自己动手实现Rpc服务框架,基于DotEasy.Rpc服务框架的介绍和集成 本篇内容属于非实用性(拿来即用)介绍,如对框架设计没兴趣的朋友,请略过. 快一个月没有写博 ...
- 视频教程-RPC服务框架(Dubbo)源码分析-Java
RPC服务框架(Dubbo)源码分析 鲁班学院-子路老师曾就职于谷歌.天猫电商等多家互联网公司,历任java架构师.研发经理等职位,参与并主导千万级并发电商网站与后端供应链研发体系搭建,多次参与电商大 ...
- voyage java_GitHub - yezilong9/voyage: 采用Java实现的基于netty轻量的高性能分布式RPC服务框架...
Voyage Overview 采用Java实现的基于netty轻量的高性能分布式RPC服务框架.实现了RPC的基本功能,开发者也可以自定义扩展,简单,易用,高效. Features 服务端支持注解配 ...
- RSF 分布式 RPC 服务框架的分层设计
RSF 是个什么东西? 一个高可用.高性能.轻量级的分布式服务框架.支持容灾.负载均衡.集群.一个典型的应用场景是,将同一个服务部署在多个Server上提供 request.response 消息通知 ...
- 唯品会RPC服务框架与容器化演进--转
原文地址:http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=405781868&idx=1&sn=cbb10d37e25 ...
- 首发:唯品会RPC服务框架与容器化演进
编者按:本文是邱戈川在 3 月 27 日数人云"百万并发"活动的演讲,授权「高可用架构」首发.转载请注明来自高可用架构公众号「ArchNotes」. 邱戈川,唯品会分布式架构平台产 ...
- java高性能rpc,企业级rpc,zk调度,负载均衡,泛化调用一体的rpc服务框架
先放出链接,喜欢的给个star:https://gitee.com/a1234567891/koalas-rpc 一:项目介绍 koalas-RPC 个人作品,提供大家交流学习,有意见请私信,欢迎拍砖 ...
- Spring Cloud与微服务学习总结(2)——Spring Cloud相较于Dubbo等RPC服务框架的优势
摘要: 目前,Spring Cloud在国内的知名度并不高,在前阵子的求职过程中,与一些互联网公司的架构师.技术VP或者CTO在交流时,有些甚至还不知道该项目的存在.可能这也与国内阿里巴巴开源服务治理 ...
- Thrift 个人实战--Thrift 网络服务模型
前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还 ...
- 【Rpc】基于开源Dubbo分布式RPC服务框架的部署整合
一.前言 Dubbo 作为SOA服务化治理方案的核心框架,用于提高业务逻辑的复用.整合.集中管理,具有极高的可靠性(HA)和伸缩性,被应用于阿里巴巴各成员站点,同时在包括JD.当当在内的众多互联网项目 ...
最新文章
- 程序员眼中的UML(2)--克服用例图的恐惧
- 35 线程优先级队列(queue)
- javascript中的链表结构—双向链表
- gen already exists but is not a source folder
- 用python计算2+4+6+…+20的值_计算2*3+(2*(5+6)*3)/2+4*6的值
- 【Lucene4.8教程之一】使用Lucene4.8进行索引及搜索的基本操作
- 平面设计师和ui设计师_平面设计师为什么要享受所有乐趣?
- echart中拆线点的偏移_Real BIM | Rhino+Grasshopper在双曲异形玻璃幕墙中的应用
- 获取指定进程所对应的可执行(EXE)文件全路径(代码)
- RTOS原理与实现12:性能测量
- 软件测试测试工具总结
- Duplicate Finder and Remover for Mac(重复文件查找删除工具)
- 自定义Openstack图标
- 创龙SOM-TL437xF 核心板简介(二)
- MySql union 连接使用
- Linux进程和轻量级进程(LWP)
- 阿里巴巴的零知识证明
- 第一百篇,真实可重现,详细实现昨日剩下的功能
- SSM项目的基本静态资源配置
- CINTA 作业7 CRT
热门文章
- python怎么更新列表_python更新列表的方法
- 用python写的游戏有哪些_想用Python写个小游戏?这个项目里有21个例子
- Day001 20210206
- 怎么看蛋白质编码序列_墨鱼的“墨汁”可以吃吗,它有什么营养?看完就明白,涨知识了...
- 自动驾驶 6-1: 横向车辆控制介绍 Lesson 1: Introduction to Lateral Vehicle Control
- bitlocker正在加密 c盘_如何扩容C盘(扩容卷变灰问题)
- java jsonobject 清空_有没有办法,我可以清空整个JSONObject – java
- mysql 超时异常捕获_Mysql的链接超时异常CommunicationsException
- linux环境Mechanize安装,在linux下安装activepython2.5 setuptools ClientCookie
- 求一个容器的最值的索引_初中几何最值——瓜豆原理模型分析