6.系统日志架构设计
日志在日常的系统运维、产品运营、公司决策中具有至关重要的作用,它可以协助保护账号安全、查证系统问题、分析用户行为、刻画用户特征、辅助战略决策等。
例如,可以根据用户的登录和退出时间分析其作息时间,根据用户的位置分析其活动区域,根据用户的功能使用情况分析其兴趣和偏好等。
日志具有数据量大、结构性不强等特点,因此对于日志的收集、存储、查询、分析都会存在一定的性能问题。对于程序日志、操作日志、接口日志等不同的日志类型,要怎样采集、如何合理地存储以便于后续分析使用、如何利用这些日志扩展系统功能,这都是本章要讨论的内容。
6.1 日志的分类和用途
日志可以细分为登录日志、退出日志、操作日志、接口日志、程序日志、埋点日志等,每种日志都有其独特的用途。
(1)登录日志:用于记录用户登录的轨迹,从而可以检测用户的账号安全性,统计用户的活跃度,以及分析用户对系统的依赖程度等。
(2)退出日志:用于记录用户退出的轨迹,从而可以统计出用户的使用系统时长、作息时间等。
(3)操作日期:用于记录用户在登录系统后的操作行为,便于问题追责和工作效率的统计等。
(4)接口日志:用于记录系统的所有接口交互日志,便于跟踪分析系统调用链路关系,以及接口使用频率、接口性能等。
(5)程序日志:用于记录程序运行过程中的详细日志,便于查证业务处理逻辑及系统bug等。
(6)埋点日志:用于记录用户的特定行为,从而统计出对于产品运营更具有价值的分析数据。例如,用户最喜欢系统中的哪些功能,更倾向于哪种交互方式,等等。
不同种类的日志,由于产生方式、产生时间、频率、数据量、应用目的都不同,因此需要采用不同的设计方法。
6.2 3种登录日志设计
登录日志是系统中的必要设计,不可以缺失,否则一旦发生纠纷则无法查证。例如,用户说自己并没有登录系统,而系统中却发生了一些购买、转账等行为。由于无法提供用户详细的登录行为信息,所以平台要承担管理责任。
用户的登录行为信息应该包含登录人、登录时间、登录地点、登录系统、登录方式等,从而完整记录用户在什么时间、什么地点、采用哪种方式、登录了哪个系统。例如,用户尹洪亮,在2020年12月31日22点33分44秒,于北京西城区,采用手机短信验证码的方式登录了资金管理系统。
登录日志记录表的结构如表6-1所示,可以采用关系型数据库、文档型数据库进行存储。
6.2.1 利用登录日志进行安全检测
利用用户的登录日志,可以对账户的安全程度做自动检测,从而达到自动保护账号安全的目的。
通过对用户登录行为的分析,判定账户异常,从而触发账户冻结和短信通知操作。如图6-1所示,如果一个账号同一天之内在多个城市登录是违反常识的。例如,上午10点在北京登录,11点在上海登录,12点又在长春登录,经过分析就可以判定用户账号异常,从而触发账号冻结操作,将当前登录用户全部踢出,同时给用户发送短信、邮件、微信等消息通知,告知账号由于在多地登录,存在安全隐患而被冻结,以及如何申请解冻。
6.2.2 利用登录日志刻画用户
用户的登录日志经过指标化分析,就可以分析出用户的很多特征,利于建立用户画像,俗称给用户打标签,如图6-3所示。
如果一个人经常在晚上8点之后登录某业务系统,则基本可以判定他属于经常加班的人群,甚至可以根据登录系统的时间找到规律,他经常在星期几加班,是月初加班多还是月末加班多,甚至可以统计出其加班趋势图。
如果一个人每天都是上午9点~9点30分登录系统,则基本可以判定他是9点上班,属于朝九晚五的工作特点。
如果一个人经常变换登录地点,则基本可以判定他属于经常出差的人群。通过登录系统的机型甚至可以判断出一个人的基本消费能力。例如,使用什么品牌型号的手机、什么品牌型号的计算机,而这些设备的价格又分布在不同的区间内,因此可以对这个人的消费观有一个大致的判断。
通过不同的登录方式还可以分析出用户的使用习惯。例如,喜欢使用PC端还是移动端,喜欢扫码登录还是手机验证码登录,进而对功能优化提供数据支撑。
对于用户画像的刻画是一个长期分析的过程,除了登录日志,还可以利用操作日志、退出日志、埋点日志、业务数据等。
6.2.3 登录日志客户化
登录日志除了在安全和用户刻画方面具有重大的价值,对于用户本身也具有重要作用。对于一个相对完善和严谨的系统,一般都具有登录轨迹查看和登录情况统计分析功能。这些功能是由用户自己使用的,与系统运营方无关。
如果用户可以查看自己近1个月、近3个月、近1年的系统登录情况(登录日志由于数据量较大,因此一般仅需要提供近1个月的登录情况,最长不超过1年),并形成完整的时间轴轨迹,甚至可以看到自己的本年度统计信息,如总计登录系统次数、最早和最晚登录时间等,这会给用户带来一种成就感,提升用户体验。用户登录轨迹信息如图6-4所示。
如果用户发现有异常的、非本人操作的登录情况,则也可以主动修改密码,或者提升账号的安全级别设置。
这还可以很好地规避用户与服务提供商的责任风险,用户的账号是否为本人登录,一目了然,用户可以截图留存作为证据。服务提供商也可以清晰地解释清楚,是否由本人登录。如果是本人账号登录的情况下造成的损失,则一般由用户自己负责,相当于用户对个人的账号和密码保管不善,服务提供商无责任。
6.3 退出日志设计
退出日志与登录日志是相互对应的,如果是主动退出,则本身不具有安全性问题,退出系统本质上是对账号的一种保护行为,因此不需要考虑安全设计问题,也不需要给用户发送消息提醒。
用户的退出行为,很多时候并不需要完整的记录,用户可能直接关闭了浏览器,或者由于Session到期导致被动退出,这种情况下往往是不记录日志的。甚至有些系统设计是不保存用户退出日志的,如用户保持了登录状态,只要客户端还留存有相应的Cookie或登录令牌,则始终保持登录状态。
如果用户主动退出,则可以利用退出日志统计出用户的系统使用时长和作息时间,以及分析用户对系统的依赖程度等。
在退出日志存储时一定要注意,不要与登录日志存储在一起。很多人这样设计存储结构:日志ID、登录人、登录时间、登录地点、登录方式、登录设备、退出时间、退出地点、退出方式、使用时长。这样看似只用一条数据就完整记录了用户从登录到退出的全流程,实际上登录次数一定比退出多,而不是一一对应的。另外,需要不断地去更新登录日志,而登录日志的数量级较大,查找的性能并不高。
还有一种情况是被动退出。例如,用户正在使用系统,而这个账号又被其他人登录了,这时往往要踢出当前用户,并通知当前用户“您已经被迫下线”,同时进行短信通知提醒,让用户核实是否安全。这种情况下的退出日志是必须要记录的,这往往具有一定的安全风险。
6.4 4种操作轨迹设计
日志的记录顺序为首先记录登录日志,然后记录操作日志,最后记录退出日志,这样才能形成完整的用户行为时间轴,如图6-5所示。操作日志属于数据量最大、可分析利用的空间也最大的一部分内容,经常与埋点分析相互结合使用。
用户在登录进入系统后,所产生的所有行为都可以称为操作日志。例如,进入了哪个菜单、使用了哪个功能、点击了哪个按钮,甚至是操作了哪条数据、数据前后发生了什么变化。
系统、菜单、功能和页面的关系如图6-6所示。一个系统中包含多个菜单,每个菜单中包含多个功能,每个功能又包含多个页面。同时,一个页面可以被多个功能共用,一个功能也可以放入多个菜单中。在这样复杂的关系下,可以将操作日志分为菜单操作日志、功能操作日志、流程操作日志和业务操作日志。对于不同的操作日志,也要采用不同的设计方式。
6.4.1 菜单操作日志设计
菜单操作日志就是记录用户对于系统菜单(包括PC端系统、移动端系统)的使用轨迹,以便于进行指标分析和问题查证。要记录的内容包括当前用户在什么时候、进入了哪个菜单,停留了多长时间。
用户姓名和菜单名称字段看似是冗余的,其实对于追查问题的意义很大,能够准确地还原当时的用户操作状态。
停留时长可以反映出用户使用某个功能菜单的耗时情况,当用户进行菜单切换时,使用当前的系统时间减去上一次的操作时间即可计算出停留时长。例如,当前时间为11点,而用户上一次切换菜单的时间为10点30分,则可以用11点减去10点30分, 从而得到用户在上一个菜单中停留了30分钟。
通过菜单操作日志可以掌握用户的工作习惯和重点关注内容,以便针对用户的痛点去设计新功能和解决系统问题。
一个系统中往往包括大量的功能菜单,有些功能会经常被使用,而有些功能则很少被使用。这些经常被使用的菜单称为热点菜单,是做性能优化、提升体验的重点。有些企业投入巨大的资源在一些用户并不常用的功能上,这是典型的错误做法。
6.4.2 功能操作日志设计
功能操作日志描述了当前用户在什么时间、哪个页面、使用了哪个功能。
例如,用户A在2020年9月30日9点30分20秒,在某宝App的个人余额菜单中使用了余额提现功能。用户B在2020年10月30日9点30分20秒,在公司财务系统的科目管理菜单中使用了新增财务科目的功能。
一个菜单中往往包含多个功能,因此功能操作日志属于菜单操作日志的下一层。PC端和移动端的区别在于PC端能够承载的内容更多,所以同一个菜单中的功能也就更多。移动端由于显示限制,因此同一个菜单中的功能往往较少,甚至一个菜单项只包含一个功能。
如果想要记录每个功能的操作日志,则必须要先将功能定义出来(包含功能编码、功能名称、功能描述等),这一般是在权限设计时就确定的,可参见5.7节。
功能与菜单为多对多关系,一个菜单中可以包含多个功能,而一个功能也同样可以在不同的菜单中使用。因此,记录功能操作日志时,必须要记录对应的菜单编码和名称,这样才可以知道,是在哪个菜单中使用了哪个功能。对于同样的功能,在哪个菜单中被使用得最为频繁。
通过功能操作日志也可以统计出用户使用菜单的情况,但是并不能很好地反映出用户进入菜单的时间、停留的时长这些系统关注的内容。所以,设计上不可以用功能操作日志替代菜单操作日志。
6.4.3 流程操作日志设计
对于一些复杂的业务场景,具有较长的操作过程,在这个过程中会包含大量的页面跳转、前进和后退的动作,以及在页面中包含了大量的功能操作。在这种极为复杂的操作环境下可能产生各种意想不到和测试无法覆盖的问题。
如果可以记录用户的完整操作过程,就可以更好地帮助系统重现用户的操作过程。
用户一次完整的支付操作流程,可以将日志转化为流程图展示,如图6-7所示。用户首先进入支付页面,点击“支付”按钮,发现没有绑定银行卡,所以跳转到银行卡绑定页面,在页面中点击“保存”按钮,完成了银行卡绑定,又跳转到支付页面,在支付页面点击“支付”按钮,最后支付成功并跳转到支付成功页面。这样就可以很轻松地重现用户操作,排查系统问题了。
由于流程操作日志涉及大量的页面跳转及元素操作(点击按钮、滑动、点选等),如果所有的页面动作都实时地调用后端服务接口,就会造成请求量过大,不仅服务端性能无法保障,前端操作也会受到影响。
注意事项如下。
(1)区分业务重要程度,并不需要在所有页面、所有元素动作上记录日志,而只需专注于重点流程、重点业务场景,这样可以减少日志的存储量。
(2)对于重点监控内容可以采用前端实时异步上报日志的方式。对于非重点监控内容可以采用定时异步调用,先将用户的操作轨迹信息缓存在本地,再定时异步批量上报给后端服务,每次上报后清空本地缓存即可,从而减少交互次数,提高传输效率。
(3)采用人工上报日志的方式,在前端设计一个上报日志的功能,在用户登录系统后将所有的操作轨迹缓存在前端,当用户使用发生问题时,由用户主动上报操作日志供开发人员排查问题。这种方式主要用在2B系统中使用。
(4)流程操作日志的数据量巨大,可以在用户操作过程中进行轨迹的缓存,在发生异常时自动上报,而业务正常操作完毕时进行清除,无须上报,从而能够达到自动收集问题数据的目的。
6.4.4 业务操作日志设计
业务操作日志往往记录到某一类或某一笔具体业务的数据级别。例如,变更前的数据和变更后的数据,形成数据的对比,也可以用于用户数据的回滚和追查。
由于不同业务的数据结构千差万别,所以很难做到统一的系统设计,往往由某个业务功能独自设计实现。但是,设计思路可以统一为按照事件的记录方式。
任何数据的变化都是由某个事件的触发而改变的。
可以将事件的粒度控制到不同的级别,如果需要更为详细的日志粒度,则只需要把事件定义得更加详细即可。但是,这样也只是知道了事件的发生时间点、顺序对固定状态字段的影响而已,并不知道每个数据内容的变化。
可以建立对应的业务快照表,与具体的操作日志相关联,用于查找每个事件发生时的数据快照。
6.5 接口日志设计
当前系统大多采用前后端分离架构,后端采用微服务等分布式系统架构,同时与众多的第三方平台又存在大量的接口交互,如图6-9所示。当系统出现问题时,80%以上的情况需要先排查接口的请求和应答是否正常,因此对于接口日志的记录就显得至关重要。
1.切面记录接口日志
接口日志的存储要充分利用面向切面编程(Aspect Oriented Programming,AOP)和动态代理的思想,在所有系统的交互链路上加入切面,然后在切面上进行日志存储,当接收到请求后记录请求日志,在程序应答前记录应答日志,接口日志的切面位置如图6-10所示。
以Java项目为例,对于后端应用程序,可以开发出统一的日志切面JAR包,其他项目只需要依赖该JAR包即可。但是,要求所有服务必须遵守方法的命名规则。例如,所有Controller方法必须以Handler结尾,否则无法经过日志切面记录日志。这种方式的缺点是所有项目必须与切面程序保持紧密的依赖关系,耦合性较高。
2.网关记录接口日志
如图6-11所示,所有的前后端交互都需要通过网关再请求到后端服务,如果后端服务之间也存在接口调用,如服务1调用服务2,则不可以直接调用,而是必须通过网关的代理转发,因此可以在网关上完成接口日志的记录。
这种方式的好处是所有的后端服务无须做任何类库的集成和修改,通过服务的分层,解决了耦合性过高的问题。但是,它使得一个去中心化的微服务架构变为了集中化的ESB架构(参见3.3节),使得服务端之间的接口调用必须经过网关(或ESB)的转发,从而降低了接口响应速度。
因此,也可以将两种设计方案进行融合和取舍。对于前后端的交互通过网关记录日志,而微服务之间的调用则通过面向切面编程的方式记录日志。
3.接口日志存储结构
接口日志存储的内容要多一些,因为除要记录接口的请求和应答报文外,还要记录它们之间的调用关系,到底是哪个系统调用了哪个系统。因此,需要分别定义应用和接口的存储结构。
应用的构成和相互调用如图6-12所示。一个应用包含多个接口,定义应用和接口的目的是将各个客户端、后端服务、第三方服务及它们所包含的接口全部实例化为具体的数据(如应用A和应用B),用于应用之间的访问授权,以及交互日志的记录。
最终记录的交易日志要能够明确地表达出,在什么时间、哪个系统调用了哪个系统的什么接口,在什么时间收到了应答,双方交互的耗时有多长,以及交互的内容是什么。
授权方式用于接口交互的安全控制,各自具有不同的用途,具体如下。
(1)开放式调用:对接口不进行任何校验和拦截,可以直接调用上游服务。这种接口一般是对外公开的,如官网的新闻资讯、公开的信息查询等。
(2)需验证密钥:这种一般为服务之间的调用,或者提供给第三方的服务接口,需要验证应用所携带的SecretID和SecretKey是否存在并且匹配。
(3)需要登录:一般为用户级别的私密接口,必须先验证登录状态,才可以调用。
4.接口日志的积极作用
通过接口日志,按照不同的维度进行统计分析,可以达到以下5个目的。
(1)可以清晰地观察系统的实时交易量,制作动态交易监控仪表盘,实时监控系统接口调用链路是否正常,是否存在服务节点故障,服务性能是否下降。
(2)如果某个接口突然频繁超时,则其对应的应用一定出现了问题,可以快速定位问题服务进行排查。如果某个接口的交易耗时过长,或者只有请求数据,没有应答数据,则可以断定接口或服务出现了问题。
(3)可以轻松统计出交易趋势曲线,查看系统在每天的交易高峰与低谷,以及在每周、每月、每年的交易量变化。
(4)可以清晰地判断出哪些服务被频繁调用,找到热点服务,避免其成为系统瓶颈,从而有针对性地提高其可靠性和服务性能,使资源更加有目的性地投放。
(5)可以纵观全局,看到整个系统的交易链路,哪些应用访问量高,哪些应用访问量低,哪些服务应该被整合,哪些服务应该被拆分。
接口日志是对系统进行跟踪、检测、评估的数据基础,也为架构设计的调整提供了重要的数据支撑。
6.6 程序日志设计
程序日志是最为详细的日志,最常见的方式就是输出到文件和控制台。如果通过查询接口日志无法判断问题,那么最直接的手段就是分析程序日志。然而,程序日志的数据量过于庞大,对日志的查看和分析也带来了巨大的挑战。
1.日志级别的划分和设定
程序日志一定要设定日志级别。日志级别通常从低到高分为8种,分别是ALL、TRACE、DEBUG、INFO、WARN、ERROR、FATAL和OFF。
(1)ALL:即所有,显示所有级别的日志。
(2)TRACE:即追踪,打印程序的运行轨迹,比DEBUG的粒度更细。
(3)DEBUG:即调试,程序运行的事件明细信息,是调试程序时使用最多的日志级别。
(4)INFO:即信息,较粗的粒度描述程序的运行过程,通常为开发人员主动输出的提示性信息。
(5)WARN:即警告,提示可能存在潜在危险的信息。
(6)ERROR:即错误,明确的错误信息,但是程序可以正常执行。
(7)FATAL:即致命,极其严重的错误,可能会导致程序终止或崩溃。
(8)OFF:即关闭,不显示任何日志。
不同的日志级别输出的内容范围不同,具有一定的包含关系,如表6-12所示。
2.如何有效地输出程序日志
(1)日志输出对于程序的性能是有比较严重影响的,所以在开发阶段增加的非必要性日志输出语句应该在上线前全部删除。在程序优化时,只是删除了一些无关紧要的日志输出,就可能提高十余倍的接口吞吐量,所以不要小看这个问题。
(2)在开发和测试环境中使用较低的日志级别,而在生产环境中使用较高的日志级别。例如,一般在开发和测试环境中使用DEBUG级别,而在生产环境中使用WARN或ERROR级别。
(3)在上线初期使用较低的日志级别,如DEBUG级别,避免在上线初期程序不稳定,便于快速查证问题。待程序稳定后,修改到较高的日志级别,如ERROR级别,减少日志的输出。
(4)对于SQL语句的日志输出,大多数定义在DEBUG级别。由于90%的问题都需要排查SQL的执行是否正确,因此除非确保程序十分稳定,一般可以不关闭SQL日志,以便于快速查证问题。
3.程序日志的输出方式
程序日志的输出主要分为同步输出和异步输出两种方式。
(1)同步输出日志:可以保持日志输出顺序与程序执行、用户操作顺序一致,但是对于异步执行的程序,日志依然无法保证其顺序性。同步输出日志会占用程序运行时间,造成用户响应时间加长。
(2)异步输出日志:可以加快程序响应速度,但是日志顺序性被破坏,不便于排查问题。
4.通用程序日志存储设计
由于程序日志量较大,如果将所有日志都写入一个文件中,就会导致文件过大,打开文件都会存在问题,查看日志就变得更加困难,因此通用日志文件存储设计如下。
(1)按照日期滚动存储。每日生成一个日志文件,保留固定的天数。这样可以保持数据只保留最近N天的文件。例如,只保留最近30天的日志,则第31天的日志会覆盖第1个日志文件,滚动存储。日志文件名通常为xxxx.log.xxxx-xx-xx,如xxx.log.2020-12-30、xxx.log.2020-12-31。
(2)按照文件大小滚动存储。设定单个文件的大小,如一个日志文件最大为500MB,超过500MB则生成新文件,再设定最大保留的文件个数如100个,则第101个文件会覆盖最初的日志文件,滚动存储。日志文件名通常为xxx.log.sequnce_num,如xxx.log.1、xxxlog.2。
(3)先按照日期,再按照文件大小滚动存储。这种方式结合了以上两种方法的优势,也是设计中推荐的方式。每天按照日期生成日志文件夹,然后在文件夹下存放当天的日志文件。当单个日志文件大小超过限定时,则按照序号生成日志。最终形成的日志为xxx.log.xxxx-xx-xx.seqnum形式,如xxx.log.2020-12-30.1、xxx.log.2020-12-30.2。
(4)存放实时日志。一般会在程序日志中存放一个最新的无后缀日志,用来保留当前的实时日志。日志文件名为xxx.log。
(5)独立存放错误日志。为了更快速地排查问题,也可以将错误日志拆分出去,对于所有的程序异常单独形成日志。日志文件名为xxx_error.log。
结合上述设计思路,可以得到一个分层滚动日志文件存储结构,清晰直观,便于查看。
5.按照业务属性做个性化的日志存储
对于一些企业内部的管理系统,用户数量有限,并且人员按照组织机构、岗位进行了明确的划分,这时就可以依靠这种结构建立直达用户的日志文件结构,做到一个用户对应一个日志文件。
如图6-14所示,可以按照日期、分公司/部门/岗位等维度建立文件夹,然后在文件夹下按照员工的编号生成日志,这样可以保证每个员工每天生成一个独立的日志文件。当某个员工反馈系统问题时,就可以直接找到该员工的日志文件进行排查。
只要记住程序日志的宗旨是完整记录程序的执行过程,应当结合系统和业务特点,设计出便于查证问题的日志存储结构即可。
6.分布式架构下的日志存储
通常将日志存放在程序运行的项目服务器上,对于独立的单体服务还是比较容易查看的,因为文件位置固定,甚至只有一个日志文件。但是,对于集群架构和分布式架构(图6-15),可能有几个、几十个,甚至上百个服务,每个服务都输出日志到自己的服务器上,当要排查问题时,就需要登录到不同的服务器上获取文件。有时开发或运维人员并不知道哪个日志文件能够定位问题,可能要将所有的日志文件查看一遍,这是十分不方便的。
如图6-16所示,可以利用NFS共享存储来存储日志文件。在各应用服务器上挂载相同的存储目录,各应用均将自己的日志文件写入共享目录中。不同的应用采用不同的文件名进行区分即可。
运维或开发人员登录任意一台服务器,就可以在共享目录中查看到所有服务的日志文件,可极大地提高工作效率。当磁盘空间不足时,只需要扩展共享存储的磁盘空间即可。
任何架构设计都是有利有弊的,需要进行不同程度的权衡,因此需要注意以下事项。
(1)使用NFS由于涉及网络传输,因此势必对日志的写入速度造成影响,但是因为在内网中使用,所以对性能影响并不大。
(2)对于各个应用服务器一定要确保服务器重启时自动挂载共享存储,如果忘记挂载,则会造成日志文件直接输出到本地,不但容易造成磁盘爆满,而且后续再挂载共享存储时需要做很多的额外操作。
(3)要对NFS共享服务搭建高可用,并且做好服务监控,否则一旦共享存储故障,就会导致关联服务全部故障。
无论是采用集群架构,还是采用分布式架构,都会将应用进行水平扩展,这样相同的应用就会部署到多个服务器上,即使用共享存储,查看起来依然不方便。
水平扩展下将日志输出到不同文件中,如图6-17所示,订单服务水平部署3个服务,它们各自输出日志到共享存储中,当需要排查某一笔订单数据时,就要去3个日志文件中搜索,效率较低。
如图6-18所示,可以利用NFS共享存储,将相同应用的日志全部写入同一个文件中,此时需要排查问题时只需要查看一个文件即可。
由于文件属于共享资源,多个应用写入时会进行资源加锁和释放,因此不用担心文件的内容会彼此覆盖。但是,对于同一个共享文件的写入势必会造成资源的相互争抢,对日志的写入性能会有较大影响。
不存在绝对完美的架构,有优点就会有缺点,这就是架构师应该权衡之处。
6.7 日志存储设计
日志具有格式松散、难以结构化的特点,同时日志的输出位置随机,数据量较大。因此,对于日志的存储方式要进行单独设计。
1.采用异步化的独立事务记录日志
日志异步存储设计如图6-19所示,日志的记录可能穿插于不同的业务和程序流程之间。日志虽然可以帮助技术人员做很多事情,但是所有的日志记录都不应该影响到用户的正常使用。因此,日志记录必须是独立的事务。
推荐采用异步方式处理,提高程序的响应速度。如果是小型系统,则可以直接采用异步线程池或内存队列的方式实现。如果是大型系统,则应该借助消息中间件(Kafka、ActiveMQ等)完成。
2.日志的存储设计
程序日志由于输出的内容不确定,因此具有非结构化的特点,通常采用分层滚动的文件存储方式。而登录日志、退出日志、操作日志和接口日志具有明确的数据结构,可以很方便地按照不同维度进行统计分析。
如果只采用关系型数据库存储,则可以很方便地按照各种维度进行分组、排序、聚合运算。但是,对于接口日志这种请求报文、应答报文就难以存储,因为这种数据需要占用很大的存储空间,使用CLOB等类型的大文本字段,会导致数据查询性能急剧降低。
因此,可以采用关系型数据库和文档型数据库联合使用的方式进行存储,如图6-20所示。使用关系型数据库存储主交易内容,使用MongoDB存储请求和应答报文等大文本内容,或者使用磁盘文件存储请求和应答日志。然后将关系型数据库与文档型数据库、文件按照交易流水号进行关联即可。
6.8 日志收集架构
将程序日志存储到文件中并不能很好地解决查看问题,尤其对于互联网应用,开发运维人员需要打开巨大的日志文件,在海量的日志中寻找他们需要的那一点点内容,犹如大海捞针。因为在日志文件中有需要关注的内容,也有大量并不需要关注的内容,怎样以可视化的方式,简单快速地查找到关注的内容,这就是日志收集设计需要解决的问题。
系统希望实现以下4个目标。
(1)具有海量存储能力:能够存储海量数据,并且保证数据可靠性(不丢失)和检索能力。
(2)具有全文检索能力:能够像百度一样搜索某个关键词就找到对应的内容。
(3)具有链路追踪能力:能够一次性查询到单笔交易中的所有日志。
(4)具有统计分析能力:可以根据各种指标进行汇总查询。
海量存储能力和全文检索能力很容易实现,只要选用支持海量存储,并且具有全文检索能力的数据库引擎即可,目前主流采用的就是Elasticsearch数据库。
链路追踪能力可以参见3.10节,只需要在日志中记录TraceID和SpanID就可以达到目的。
统计分析能力是最难的,因为这是关系型数据库的特长,并不是搜索引擎数据库做不到,但前提是必须先将数据结构化。下面将详细阐述日志收集的设计思路。
6.8.1 日志收集架构的设计
日志收集流程如图6-23所示,几乎所有的日志收集架构都可以抽象为4个步骤:日志采集、日志清洗、日志存储和日志分析。
(1)日志采集:可以从不同的数据源采集日志,可以是应用程序、数据库、中间件、网络、操作系统等。
(2)日志清洗:将采集到的日志进行清洗、过滤、分析,转化为结构化的数据形式。
(3)日志存储:将清洗后已经结构化的数据存储到存储引擎中。
(4)日志分析:通过存储引擎对日志进行可视化的查询和统计分析。
日志收集架构设计如图6-24所示,日志采集程序要能够从不同的软件、设备、操作系统中主动采集日志,而不是由各个程序主动上送日志。例如,可以收集MySQL、Oracle等不同数据库的日志, Nginx、Tomcat等不同Web服务器的日志,等等。
由于采集的日志来源不同,因此日志输出的格式也各不相同,大多数只是纯文本内容的流式输出。因此,这些日志需要输入日志清洗程序中进行解析和转换。日志清洗程序按照过滤器进行设计,一条日志经过层层清洗和转化,最终得到便于查询和分析的结构化数据。例如,将日志转化为JSON结构、XML结构等。
6.8.2 Elastic Stack架构组件介绍
当前主流的开源日志收集与分析架构之一就是ELastic Stack架构。ELastic Stack以前称为ELK Stack,其中E是指Elasticsearch,L是指Logstash,K是指Kibana,使用这3个组件可以构建一套完整的日志采集、清洗、存储和分析的平台。
(1)Elasticsearch:分布式搜索和分析引擎,主要用于结构化的日志存储和查询分析。
(2)Logstash:用户日志清洗,将非结构化的流式日志转化为结构化日志。
(3)Kibana:提供可视化的日志查询和统计分析、权限管理等。
ELK Stack日志收集架构如图6-26所示。Elasticsearch的作用是进行结构化的日志存储。Logstash的作用是日志采集和清洗,将非结构化的流式日志转化为结构化日志。Kibana提供可视化的日志查询和统计分析。开发人员直接使用Kibana进行日志查询,而不再需要接触服务器日志文件,只需要分配Kibana的系统账号即可,从而还可以控制不同的人员可查看的日志范围。
随着ELK的发展,又有新成员Beats的加入,所以就形成了ELastic Stack。
1. Elasticsearch
Elasticsearch在日志收集中的作用是存储结构化的日志数据,用于查询和分析,以下内容引用自百度百科。
Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful Web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。官方客户端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和许多其他语言中都是可用的。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎。
2. Logstash
Logstash是一个开源实时的管道式日志收集引擎,可以动态地将不同来源的数据进行归一,并且将格式化的数据存储到所选择的位置。
Logstash流水线原理如图6-27所示。Logstash包含3个组件,分别是输入组件(INPUTS)、输出组件(OUTPUTS)和过滤器组件(FILTERS)。其中输入组件和输出组件是必选组件。输入组件的目的是从不同的数据源接收日志数据,经过过滤器组件进行过滤清洗和结构转换,最后经过输出组件进行输出,可以直接输出到Elasticsearch数据库中。如果不使用过滤器组件,则接收数据后直接输出到存储引擎中。
Logstash输入输出设计如图6-28所示。Logstash提供了50多种输入组件、50多种输出组件、40多种过滤器组件,可以从诸如文件、数据库、Web应用、操作系统、网络、防火墙、消息队列等多种数据源输入数据;也可以将数据输出到文件、数据库、控制台、操作系统、网络、消息队列等终端中,几乎涵盖了所有需要的业务场景。
3. Kibana
Kibana是一个十分强大的可视化工具,可以使用柱状图、线状图、饼图、热力图等形式按照不同维度统计日志信息,也可以自己定制化各种仪表盘,如图6-29所示。
4. Beats
Beats是一个日志采集工具,可以将采集的日志直接输出到Elasticsearch中,也可以输出到Logstash中,可以极大地简化日志采集的过程(否则每个数据源都要主动推送数据到Logstash中),如图6-31所示。
Beats包含6种工具,具体如下。
(1)Filebeat:轻量型日志采集器,可以从安全设备、云、容器、主机进行数据收集。采集的来源为文件,可以通过tail -f实时读取日志变化,将其输出到Logstash或Elasticsearch中。
(2)Packetbeat:轻量型网络数据采集器,可用于监测HTTP 等网络协议,随时掌握应用程序延迟和错误、响应时间、流量、SLA性能、用户访问量和趋势等。
(3)Metricbeat:轻量型指标采集器,用于从系统和服务中收集指标。将Metricbeat部署到Linux、Windows和Mac主机,Metricbeat 就能够以一种轻量型的方式输送各种统计数据,如系统级的CPU使用率、内存、文件系统、磁盘 I/O和网络I/O统计数据等。
(4)Winlogbeat:轻量型Windows事件日志采集器,用于密切监控基于Windows的基础设施上发生的事件。可以将Windows事件日志流式传输至Elasticsearch和Logstash。
(5)Auditbeat:轻量型审计日志采集器,可以收集Linux审计框架的数据,监控文件完整性。可监控用户的行为和系统进程,分析用户事件数据。Auditbeat与Linux审计框架直接通信,收集与Auditd相同的数据,并实时发送这些事件消息到Elastic Stack。
(6)Heartbeat:面向运行状态监测的轻量型采集器,通过主动探测来监测服务的可用性。通过给定URL列表,通过心跳机制询问系统是否运行正常。
6.8.3 Elastic Stack架构模式
对于不同的应用场景,可以采用不同的Elastic Stack架构模式进行日志收集。
1. 3种无Filebeat架构
如图6-32所示,如果应用日志可以结构化为JSON结构,则可以直接将结构化数据存储到Elasticsearch中,借助Kibana从Elasticsearch中进行查询。这样的架构模式最简单,节省了Logstash的过滤和传输流程,可以极大地提高日志采集的性能,适用于日志量较小的小型项目。
如图6-33所示,如果日志数据无法结构化,则可以将非结构化的日志直接输入Logstash中,在Logstash中进行格式化处理,形成JSON结构,再存储到Elasticsearch中,最后借助Kibana进行查询。这样的架构模式适用于大多数的中型项目。
如图6-34所示,为了防止Logstash的压力过大,可以借助Kafka这种高吞吐量的消息队列,将日志直接输出到Kafka中,再输入Logstash中,在Logstash中进行格式化处理,形成JSON结构,再存储到Elasticsearch中,最后借助Kibana进行查询。这样的架构模式适用于日志量较大的集群和分布式项目。
2. 3种有Filebeat架构
以上3种架构对程序都具有一定的侵入性,应用程序必须主动输出日志。如果只是想上线一套日志收集系统,而不想改变任何原有程序,就可以如图6-35所示,使用Filebeat主动监控应用程序的日志文件。Filebeat可以将日志文件中的内容直接输出到Elasticsearch、Logstash或Kafka中,因此在前3种架构之前加入Filebeat,就可以形成3种新的架构模式。
Filebeat有以下两种部署方式。
第一种部署方式如图6-36所示,将Filebeat应用与每个应用部署在一起,作为一个代理服务去采集日志,然后再输出到Logstash中。这种部署方式的优点是多个Filebeat服务之间互不影响,采集性能较好,但是部署相对麻烦;缺点是由于Filebeat需要实时监控和采集日志,对资源也会有一定的损耗,尤其是当日志量变化较快时,这样就会抢占服务器资源,影响应用程序运行。
第二种部署方式如图6-37所示,将所有的日志通过NFS共享存储在一起,然后部署一台或多台独立的Filebeat服务器。这种部署方式的优点是可以独立部署Filebeat服务,与应用程序分离,即使Filebeat对资源消耗较大,也不会影响到业务应用的运行。
6.9 章节练习
1.日志都有哪些类型?
登录日志、退出日志、操作日志、接口日志、程序日志、埋点日志等。其中操作日志又包括菜单操作日志、功能操作日志、流程操作日志和业务操作日志。
2.如何利用用户登录日志来判定账户的安全程度?
(1)根据用户的登录地点判定,用户是否短时间内在多个地点登录。
(2)根据用户的登录IP判定,是否短时间内IP发生频繁变动。
(3)用户是否频繁尝试登录并且登录失败。
(4)用户是否在同一时间段内在多个设备反复登录。
以上几种情况都可以作为账户安全性的判断依据。
3.如何详细地记录用户在系统内的行为轨迹?
可通过菜单操作日志、功能操作日志、流程操作日志和业务操作日志来记录用户的各种行为。基于这4种日志可以详细地描绘出用户在登录系统后的所有行为轨迹,包括点击了哪个菜单、使用了哪个功能、经过了哪些页面、点击了哪些按钮,以及做了什么样的业务。
通过这些日志还可以分析出系统功能的热度、用户的偏好和行为习惯等信息。
4.记录各类日志需要注意哪些内容?
(1)日志记录一定不能影响正常的业务流程,所以对于登录日志、退出日志、操作日志、程序日志大多采用异步线程池或消息队列的方式记录。
(2)日志中都必须包含明确的时间戳,避免因为异步引发数据顺序混乱问题。
(3)日志的数据量往往巨大,必须做好转储或清理计划,否则必将拖累系统。
5.日志收集架构的通用策略是什么?
日志收集架构的通用策略分为4个步骤:日志采集、日志清洗、日志存储和日志分析。
(1)日志采集:可以从不同的数据源采集日志,可以是应用程序、数据库、中间件、网络、操作系统等。
(2)日志清洗:将采集到的日志进行清洗、过滤、分析,转化为结构化的数据形式。
(3)日志存储:将清洗后已经结构化的数据存储到存储引擎中。
(4)日志分析:通过存储引擎对日志进行可视化的查询和统计分析。
6.10 案例设计
1.场景设计题:用户操作轨迹回放案例设计
为了解决用户在系统使用中遇到的各种问题,希望有这样一套系统,可以对用户的操作过程进行回放,构建一个用户行为轨迹回放系统,需要满足如下需求。
(1)可以完整地还原用户从登录到退出的整个行为轨迹。
(2)用户操作了哪些页面,点击了哪些按钮,录入了哪些数据,发生了什么问题。
(3)可以快速地帮助测试人员回放整个过程,从而定位问题。
(4)不要对用户的使用造成影响,同时最大程度地减轻系统压力。如果您作为企业的系统架构师,会怎样进行系统设计,需要考虑哪些内容?
2.设计思路指引
(1)应主要考虑以行为日志的方式进行数据记录。以登录日志和退出日志记录用户的登录和退出行为,以操作日志记录行为轨迹,以交易日志记录用户提交的数据。
(2)为了记录用户的操作行为,经过了哪些页面,点击了哪些按钮,首先需要对页面、功能按钮、功能区域等进行字典定义,将这些内容进行编码。
(3)不应该在用户点击或跳转页面时向服务端发送日志记录的请求,这样请求会过于频繁,在高并发场景下将对系统造成巨大压力。可以考虑采用客户端先进行日志缓存,再以用户操作过程是否正常完成作为依据,决定是否上传行为日志。操作日志的上传应该进行压缩,批量上传,以提高上传的效率。
(4)只有不稳定的系统才会产生大量的异常操作轨迹数据,所以数据量不会过大。同时,轨迹日志应该具有定期清理功能,以节省存储空间,同时减轻系统压力。
(5)对日志进行分析和加载,从而形成可视化流程页面,以动态化的方式回放用户的操作流程。
(6)记录用户的操作轨迹只是第一步,同时要记录用户操作过程中提交的数据,此时需要记录的是用户操作触发的交易流水号,通过交易流水号再获取接口日志,从而获得用户提交的数据和服务端的应答结果,而不应该将所有内容都记录在操作日志中,这样会导致数据量暴增。