首页 文章 精选 留言 我的

精选列表

搜索[字节级],共10000篇文章
优秀的个人博客,低调大师

基于SpirngBoot的企业级后台管理框架Guns完整解析

小Hub领读: guns这个项目相信很多人都知道,不知道你们有没完整读过呢,今天一起跟着小Hub来学习下哈。 一共几个主要模块比较重要: map + warpper模式 Api数据传输安全 数据范围限定 视频讲解:https://www.bilibili.com/video/BV1P5411j7yA/ 简介 Guns基于SpringBoot 2,致力于做更简洁的后台管理系统。Guns项目代码简洁,注释丰富,上手容易,同时Guns包含许多基础模块(用户管理,角色管理,部门管理,字典管理等10个模块),可以直接作为一个后台管理系统的脚手架! 官网:https://www.stylefeng.cn 视频讲解:https://www.bilibili.com/video/BV1P5411j7yA/ 本次解读版本:tag-v4.2版本,因为5.0后的项目都是maven单项目,核心类都封装到jar中了,所以学习的话最好使用v4.2的最后一版本maven多模块项目学习。 https://gitee.com/stylefeng/guns/tree/v4.2/ 项目特点 基于SpringBoot,简化了大量项目配置和maven依赖,让您更专注于业务开发,独特的分包方式,代码多而不乱。 完善的日志记录体系,可记录登录日志,业务操作日志(可记录操作前和操作后的数据),异常日志到数据库,通过@BussinessLog注解和LogObjectHolder.me().set()方法,业务操作日志可具体记录哪个用户,执行了哪些业务,修改了哪些数据,并且日志记录为异步执行,详情请见@BussinessLog注解和LogObjectHolder,LogManager,LogAop类。 利用beetl模板引擎对前台页面进行封装和拆分,使臃肿的html代码变得简洁,更加易维护。 对常用js插件进行二次封装,使js代码变得简洁,更加易维护,具体请见webapp/static/js/common文件夹内js代码。 利用ehcache框架对经常调用的查询进行缓存,提升运行速度,具体请见ConstantFactory类中@Cacheable标记的方法。 controller层采用map + warpper方式的返回结果,返回给前端更为灵活的数据,具体参见com.stylefeng.guns.modular.system.warpper包中具体类。 简单可用的代码生成体系,通过SimpleTemplateEngine可生成带有主页跳转和增删改查的通用控制器、html页面以及相关的js,还可以生成Service和Dao,并且这些生成项都为可选的,通过ContextConfig下的一些列xxxSwitch开关,可灵活控制生成模板代码,让您把时间放在真正的业务上。 控制器层统一的异常拦截机制,利用@ControllerAdvice统一对异常拦截,具体见com.stylefeng.guns.core.aop.GlobalExceptionHandler类。 技术选型 springboot mybatis plus shiro beetl ehcache jwt 模块分析 学习一个项目就是学习项目的亮点地方,在分析guns的过程中,有些地方值得我们学习,下面我们一一来分析: map + warpper模式 访问后台的用户列表时候,我们通常需要去查询用户表,但是用户表里面有些外键,比如角色信息、部门信息等。因此有时候我们查询列表时候一般在mapper中关联查询,然后得到记录。 官网介绍: map+warpper方式即为把controller层的返回结果使用BeanKit工具类把原有bean转化为Map的的形式(或者原有bean直接是map的形式),再用单独写的一个包装类再包装一次这个map,使里面的参数更加具体,更加有含义,下面举一个例子,例如,在返回给前台一个性别时,数据库查出来1是男2是女,假如直接返回给前台,那么前台显示的时候还需要增加一次判断,并且前后端分离开发时又增加了一次交流和文档的成本,但是采用warpper包装的形式,可以直接把返回结果包装一下,例如动态增加一个字段sexName直接返回给前台性别的中文名称即可。 guns项目中,作者说独创了一种map+warpper模式。我们来看下是如何实现的。 看看下UserController的代码: /** * 查询管理员列表 */ @RequestMapping("/list") @Permission @ResponseBody public Object list(@RequestParam(required = false) String name , @RequestParam(required = false) String beginTime , @RequestParam(required = false) String endTime , @RequestParam(required = false) Integer deptid) { if (ShiroKit.isAdmin()) { List<Map<String, Object>> users = userService.selectUsers(null, name, beginTime, endTime, deptid); return new UserWarpper(users).warp(); } else { DataScope dataScope = new DataScope(ShiroKit.getDeptDataScope()); List<Map<String, Object>> users = userService.selectUsers(dataScope, name, beginTime, endTime, deptid); return new UserWarpper(users).warp(); } } userService.selectUsers中只是一个单表的查询操作,没有关联其他表,因此查询出来的结果中有些字段需要手动转换,比如sex、roleId等,因此作者定义了一个UserWarpper,用来转换这些特殊字段,比如sex存的0转成男,roleId查库之后转成角色名称等。 /** * 用户管理的包装类 * * @author fengshuonan * @date 2017年2月13日 下午10:47:03 */ public class UserWarpper extends BaseControllerWarpper { public UserWarpper(List<Map<String, Object>> list) { super(list); } @Override public void warpTheMap(Map<String, Object> map) { map.put("sexName", ConstantFactory.me().getSexName((Integer) map.get("sex"))); map.put("roleName", ConstantFactory.me().getRoleName((String) map.get("roleid"))); map.put("deptName", ConstantFactory.me().getDeptName((Integer) map.get("deptid"))); map.put("statusName", ConstantFactory.me().getStatusName((Integer) map.get("status"))); } } 因为mybatis plus支持查询返回map的形式,所以只需要把map传进来,就可以转换成功,如果查询结果是一个实体的bean,那就先转成map,然后再用warpTheMap。其中BaseControllerWarpper也是一个关键抽象类,提供转换结果。 日志模块 日志记录采用aop(LogAop类)方式对所有包含@BussinessLog注解的方法进行aop切入,会记录下当前用户执行了哪些操作(即@BussinessLog value属性的内容)。 如果涉及到数据修改,会取当前http请求的所有requestParameters与LogObjectHolder类中缓存的Object对象的所有字段作比较(所以在编辑之前的获取详情接口中需要缓存被修改对象之前的字段信息),日志内容会异步存入数据库中(通过ScheduledThreadPoolExecutor类)。 日志注解标识:com.stylefeng.guns.core.common.annotion.BussinessLog 日志处理切面:com.stylefeng.guns.core.aop.LogAop 日志记录字段字典:com.stylefeng.guns.core.common.constant.dictmap.base.AbstractDictMap 任务式保存记录:LogManager.me().executeLog(TimerTask task) jwt校验 在之前的课程中,我们已经说过了很多次jwt的形式作为用户的token,在这项目中,jwt讲到了与Api的数据传输安全结合起来一起运用。首先我们看下guns-rest项目,打开com.stylefeng.guns.rest.modular.auth.controller.AuthController,这个类是客户端调用登录生成Jwt的地方。 @RestController public class AuthController { @Autowired private JwtTokenUtil jwtTokenUtil; @Resource(name = "simpleValidator") private IReqValidator reqValidator; /** * 请求生成jwt * * @param authRequest * @return */ @RequestMapping(value = "${jwt.auth-path}") public ResponseEntity<?> createAuthenticationToken(AuthRequest authRequest) { boolean validate = reqValidator.validate(authRequest); if (validate) { final String randomKey = jwtTokenUtil.getRandomKey(); final String token = jwtTokenUtil.generateToken(authRequest.getUserName(), randomKey); return ResponseEntity.ok(new AuthResponse(token, randomKey)); } else { throw new GunsException(BizExceptionEnum.AUTH_REQUEST_ERROR); } } } 来说明一下上面的代码: IReqValidator :账号密码校验器 DbValidator SimpleValidator randomKey :随机生成的key,用于数据安全校验 token:生成保护用户id的jwt 所以app登录调用这接口生成的值如下: { "randomKey": "1jim2v", "token": "eyJhbGciOiJIUzUxMiJ9.eyJyYW5kb21LZXkiOiIxamltMnYiLCJzdWIiOiJhZG1pbiIsImV4cCI6MTU2MjM5NjgwNCwiaWF0IjoxNTYxNzkyMDA0fQ.vr3HwhV_e8MrpNZY0rxbqs1cOzHIBdon4cQT-Gs9wvmv8UZEBbc4QNSMxTh_ulcVpkaw2uwZY4_8zJ7I2G-36Q" } 好了,客户端拿到token之后,每次请求需要在header中把token带上,然后服务过滤器校验 AuthFilter:校验jwt是否过期和是否正确 //验证token是否过期,包含了验证jwt是否正确 boolean flag = jwtTokenUtil.isTokenExpired(authToken); ok,jwt的生成和校验逻辑都很简单,下面我们来说说接口传输安全是怎么做到的。 Api数据传输安全 上面我们说到客户端登录之后拿到了一个token和randomKey,token是用来校验用户身份的,那么这个randomKey是用来干嘛的呢,其实是用来做数据安全加密的。 当开启传输安全模式时候,客户端发送数据给服务器的时候会进行加密传输,具体的加密过程,guns中有一个com.stylefeng.guns.jwt.DecryptTest: public static void main(String[] args) { String salt = "1jim2v"; SimpleObject simpleObject = new SimpleObject(); simpleObject.setUser("stylefeng"); simpleObject.setAge(12); simpleObject.setName("ffff"); simpleObject.setTips("code"); String jsonString = JSON.toJSONString(simpleObject); String encode = new Base64SecurityAction().doAction(jsonString); String md5 = MD5Util.encrypt(encode + salt); BaseTransferEntity baseTransferEntity = new BaseTransferEntity(); baseTransferEntity.setObject(encode); baseTransferEntity.setSign(md5); System.out.println(JSON.toJSONString(baseTransferEntity)); } 上面的过程就是把simpleObject 对象进行new Base64SecurityAction().doAction自定义加密(可自定义,项目只是简单Base64编码),然后加把加密后的值和salt进行Md5计算,得出来的md5就是签名,那么这个salt是哪里来的呢,其实这个salt的值就是randomKey的值。 DataSecurityAction:加密、解密的抽象类 Base64SecurityAction:其中一种实现,简单的Base64编码完成加密解密 如果其他方式的直接实现DataSecurityAction即可 上面的main方法运行之后得到的值如下: {"object":"eyJhZ2UiOjEyLCJuYW1lIjoiZmZmZiIsInRpcHMiOiJjb2RlIiwidXNlciI6InN0eWxlZmVuZyJ9", "sign":"34bdd49a0838b1ef69cca928d71e885d"} 因此,客户端就是把这串数据传送到服务器: 注意要填请求头:Authorization的值是:Bearer+空格+token,这个可以从AuthFilter中知道 好了,上面发送给hello接口,那么我们看下是如何接收和解密的,首先来看下接口: @Controller @RequestMapping("/hello") public class ExampleController { @RequestMapping("") public ResponseEntity hello(@RequestBody SimpleObject simpleObject) { System.out.println(simpleObject.getUser()); return ResponseEntity.ok("请求成功!"); } } 貌似没啥特殊的,参数SimpleObject应该是解析之后得到的值得,我们都知道,我们把参数写到控制器中时候,spring会自动帮我们完成参数注入到实体bean的过程,我们传过来的是一个加密的json,spring是帮不了我们自动解析的,因此,这里我们要做个手动转换json(解密)的过程,再完成注入; 先来分析一下spring的过程:在springboot项目里当我们在控制器类上加上@RestController注解或者其内的方法上加入@ResponseBody注解后,默认会使用jackson插件来返回json数据。 因此我们需要实现手动转成json与bean,只需要继承FastJsonHttpMessageConverter,重写read的过程。 guns项目中有WithSignMessageConverter 这样一个类: /** * 带签名的http信息转化器 * * @author fengshuonan * @date 2017-08-25 15:42 */ public class WithSignMessageConverter extends FastJsonHttpMessageConverter { @Autowired JwtProperties jwtProperties; @Autowired JwtTokenUtil jwtTokenUtil; @Autowired DataSecurityAction dataSecurityAction; @Override public Object read(Type type, Class<?> contextClass , HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { InputStream in = inputMessage.getBody(); Object o = JSON.parseObject(in, super.getFastJsonConfig().getCharset(), BaseTransferEntity.class, super.getFastJsonConfig().getFeatures()); //先转化成原始的对象 BaseTransferEntity baseTransferEntity = (BaseTransferEntity) o; //校验签名 String token = HttpKit.getRequest().getHeader(jwtProperties.getHeader()).substring(7); String md5KeyFromToken = jwtTokenUtil.getMd5KeyFromToken(token); String object = baseTransferEntity.getObject(); String json = dataSecurityAction.unlock(object); String encrypt = MD5Util.encrypt(object + md5KeyFromToken); if (encrypt.equals(baseTransferEntity.getSign())) { System.out.println("签名校验成功!"); } else { System.out.println("签名校验失败,数据被改动过!"); throw new GunsException(BizExceptionEnum.SIGN_ERROR); } //校验签名后再转化成应该的对象 return JSON.parseObject(json, type); } } 分析:首先从body中获取到json数据,然后从header中获取到jwt的token(为了拿到randomKey),然后再Md5计算,比较传过来的sign,一致代表数据是没被串改过的,然后dataSecurityAction.unlock解密得到原始的json数据,最后调用JSON.parseObject(json, type);把json转成SimpleObject,所以整过过程就是这样,perfect。 数据范围限定 关于数据范围限定的概念很多人不知道,我们先来看下效果: 超级用户:admin登录查看用户列表 运营主管(运营部):test登录查看用户列表 从上面的两个登录账号中可以很直观看到,admin作为超级管理员,可以看到所有的数据,而test作为运营部的运营主管角色只能看到自己部门下的用户。 因此数据范围限定的意思就是根据用户的角色决定用户能查看的数据范围。 要完成这个功能有两个关键类: DataScope: public class DataScope { /** * 限制范围的字段名称 */ private String scopeName = "deptid"; /** * 具体的数据范围 */ private List<Integer> deptIds; ... } DataScopeInterceptor @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) public class DataScopeInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler) PluginUtils.realTarget(invocation.getTarget()); MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler); MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement"); if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) { return invocation.proceed(); } BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql"); String originalSql = boundSql.getSql(); Object parameterObject = boundSql.getParameterObject(); //查找参数中包含DataScope类型的参数 DataScope dataScope = findDataScopeObject(parameterObject); if (dataScope == null) { return invocation.proceed(); } else { String scopeName = dataScope.getScopeName(); List<Integer> deptIds = dataScope.getDeptIds(); String join = CollectionKit.join(deptIds, ","); originalSql = "select * from (" + originalSql + ") temp_data_scope where temp_data_scope." + scopeName + " in (" + join + ")"; metaStatementHandler.setValue("delegate.boundSql.sql", originalSql); return invocation.proceed(); } } ... } 可以看出,其实就是一个mybatis的拦截器,拦截StatementHandler的prepare方法,然后在需要执行的sql外包装一层select * from(...)别名 where 别名.字段 in (范围)。 看起来逻辑还是挺清晰的。回头看下用户的list代码, DataScope dataScope = new DataScope(ShiroKit.getDeptDataScope()); List<Map<String, Object>> users = userService.selectUsers(dataScope, name, beginTime, endTime, deptid); 因此在需要数据范围限定的地方加上DataScope dataScope参数,拦截器会扫描参数中是否有 DataScope 类型,有的话就在sql外套上一层select * from,然后加上定义的字段限定范围。perfect~ 结束语 好了,这里是MarkerHub,我是小Hub吕一明。就解读到这里,更多开源项目解读可以上 httts://markerhub.com 。 推荐阅读: B站50K播放量,SpringBoot+Vue前后端分离完整入门教程! 分享一套SpringBoot开发博客系统源码,以及完整开发文档!速度保存! Github上最值得学习的100个Java开源项目,涵盖各种技术栈! 2020年最新的常问企业面试题大全以及答案

优秀的个人博客,低调大师

涂鸦智能 dubbo-go 亿级流量的实践与探索

作者 |潘天颖,Github ID @pantianying,开源爱好者,就职于涂鸦智能 dubbo 是一个基于 Java 开发的高性能的轻量级 RPC 框架,dubbo 提供了丰富的服务治理功能和优秀的扩展能力。而 dubbo-go 在 java 与 golang 之间提供统一的服务化能力与标准,是涂鸦智能目前最需要解决的主要问题。本文分为实践和快速接入两部分,分享在涂鸦智能的 dubbo-go 实战经验,意在帮助用户快速接入 dubbo-go RPC 框架,希望能让大家少走些弯路。另外,文中的测试代码基于 dubbo-go版本 v1.4.0。 dubbo-go 网关实践 dubbo-go 在涂鸦智能的使用情况如上图,接下来会为大家详细介绍落地细节,希望这些在生产环境中总结的经验能够帮助到大家。 1. 背景 在涂鸦智能,dubbo-go

优秀的个人博客,低调大师

勇攀监控高峰,饿了么 EMonitor 秒级定位实战

云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 背景 阿里集团针对故障处理提出了“1/5/10”的目标— 1 分钟发现、5 分钟定位、10 分钟恢复,这对我们的定位能力提出了更高的要求。 EMonitor 是一款集成 Tracing 和 Metrics,服务于饿了么所有技术部门的一站式监控系统,其覆盖了: 前端监控、接入层监控; 业务 Trace 和 Metric 监控; 所有的中间件监控; 容器监控、物理机监控、机房网络监控。 每日处理总数据量近 PB,每日写入指标数据量上百 T,日均几千万的指标查询量,内含上万个图表、数千个指标看板,并且已经将所有层的监控数据打通并串联了起来。但是在故障来临时,用户仍然需要花费大量时间来查看和分析 EMonitor 上的数据。 比如阿里本地生活的下单业务,涉及到诸多应用,每个应用诸多 SOA 服务之间错综复杂的调用关系,每个应用还依赖 DB、Redis、MQ 等等资源,在下单成功率下跌时,这些应用的负责人都要在 EMonitor 上查看指标曲线以及链路信息来进行人肉排障以自证清白,耗时耗力,所以自动化的根因分析必不可少。 根因分析建模 业界已经有好多在做根因分析的了,但是大都准确率不高,大部分还在 40% 到 70% 之间,从侧面说明根因分析确实是一个难啃的骨头。根因分析看起来很庞大,很抽象,无从下手,从不同的角度(可能是表象)去看它,就可能走向不同的路。那如何才能透过表象来看到本质呢? 我这里并不会一开始就给你列出高大上的算法解决方案,而是要带你重新认知根因分析要解决的问题到底是什么。其实好多人对要解决的问题都模糊不清,你只有对问题理解清楚了,才能有更好的方案来解决它。 要解决什么样的问题 举个例子:现有一个应用,拥有一堆容器资源,对外提供了诸多 SOA 服务或者 Http 服务,同时它也依赖了其他应用提供的服务,以及 DB 资源、Redis 资源、MQ 资源等等,那我们如何才能够全面的掌控这个应用的健康状况呢? 我们需要掌控: 掌控这个应用的所有入口服务的「耗时」和「状态」 掌控每个入口服务之后每种操作的「耗时」和「状态」 一个应用都有哪些入口? SOA 服务入口; Http 服务入口; MQ 消费消息入口; 定时 job 入口; 其他的一些入口。 进入每个入口之后,都可能会有一系列的如下 5 种操作和 1 种行为(下面的操作属性都是以阿里本地生活的实际情况为例,并且都包含所在机房的属性): DB 远程操作:有 dal group、table、operation(比如select、update、insert等)、sql 的操作属性; Redis 远程操作:有 command 的操作属性; MQ 远程操作(向MQ中写消息):有 exchange、routingKey、vhost 的操作属性; RPC 远程操作:有 远程依赖的 appId、远程 method 的操作属性; Local 操作(即除了上述4种远程操作之外的本地操作): 暂无属性; 抛出异常的行为:有异常 name 的属性。 那我们其实就是要去统计每个入口之后的 5 种操作的耗时情况以及状态情况,以及抛出异常的统计情况。 针对远程操作其实还要明细化,上述远程操作的每一次耗时是包含如下 3 大部分: 客户端建立连接、发送请求和接收响应的耗时; 网络的耗时; 服务器端执行的耗时。 有了上述三要素,才能去确定远程操作到底是哪出了问题,不过实际情况可能并没有上述三要素。 故障的结论 有了上述数据的全面掌控,当一个故障来临的时候,我们可以给出什么样的结论? 哪些入口受到影响了? 受影响的入口的本地操作受到影响了? 受影响的入口的哪些远程操作受到影响了? 具体是哪些远程操作属性受到影响了? 是客户端到服务器端的网络受到影响了? 是服务器端出了问题吗? 受影响的入口都抛出了哪些异常? 上述的这些结论是可以做到非常高的准确性的,因为他们都是可以用数据来证明的。 然而第二类根因,却是无法证明的: GC 问题; 容器问题。 他们一旦出问题,更多的表现是让上述 5 种操作耗时变长,并且是没法给出数据来明确证明他们和 5 种操作之间的关联,只是以一种推测的方式来怀疑,从理论上来说这里就会存在误定位的情况。 还有第三类更加无法证明的根因—变更问题 昨天的变更或者当前的变更到底和当前的故障之间有没有关联,更是无法给出一个证明的,所以也只能是瞎推测。 我们可以明确的是 5 种操作的根因,还需要进一步去查看是否是上述第二类根因或者第三类根因,他们只能作为一些辅助结论,因为没法给出严谨的数据证明。 根因分析实现 在明确了我们要解决的问题以及要求的故障结论之后,我们要采取什么样的方案来解决呢?下面首先会介绍一个基本功能「指标下钻分析」。 指标下钻分析 一个指标,有多个 tag,当该指标总体波动时,指标下钻分析能够帮你分析出是哪些 tag 组合导致的波动。 比如客户端的 DB 操作抖动了,利用指标下钻分析就可以分析出: 哪个机房抖动了? 哪个 dal group 抖动了? 哪张表抖动了? 哪种操作抖动了? 哪个 sql 抖动了? 再比如远程 RPC 操作抖动了,利用指标下钻分析就可以分析出: 哪个机房抖动了? 哪个远程 appId 抖动了? 哪个远程 method 抖动了? 其实这个就是去年 AIOPS 竞赛的题目,详细见: http://iops.ai/competition_detail/?competition_id=8&flag=1 通常的方案: 对每个时序曲线都要进行数据预测,拿到预测值; 针对每个方案根据实际值和预测值的差异算出一个方案分数; 遍历所有可能性方案,挑选出分数最高的方案(通过蒙特卡洛树搜索进行剪枝优化)。 我们的方案: 确定整体的波动范围; 确定计算范围 = 该波动范围 + 前面一段正常范围,在计算范围内算出每根时间线的波动值(比如波动的方差,实际肯定不会这么简单,有很多的优化点); 对所有时间曲线进行一些过滤(比如和整体抖动方法相反,整体都向上抖动,它向下抖动); 对过滤后的时间曲线按照波动值进行 KMeans 聚类; 从排名靠前的分类中挑选出方案,为每个方案计算方案分数(这个方案个数就非常之少了); 得出分数最高的方案。 针对去年的决赛题目,我们的这个方案的跑分达到了 0.95 以上,应该就在前三名了。 根因分析流程 有了指标下钻分析功能之后,我们来看如何进行根因分析: 1、针对入口服务的响应时间和成功率判断是否异常,若无异常则无根因可分析;2、针对入口服务的 5 种操作进行指标下钻分析,得出异常波动的操作类型有哪些;3、然后到对应操作类型的指标中再次进行指标下钻分析,得出该操作下: 哪些入口受到该操作的波动影响了? 哪些操作属性异常波动了? 假如该操作是远程操作,还要继续深入服务器端进行分析: 假如你有远程操作数据的 3 要素的话,那么是可以分析出: 客户端建立连接、发送请求和接收响应耗时问题; 网络耗时问题; 服务器端耗时问题。 假如没有相关数据的话,那就只能相对来说进行推测了(准确率也会受到影响): 假如服务器端耗时正常的话,那就相对划分为客户端发送请求或接收响应耗时是根因; 假如服务器端耗时异常抖动的话,那么就需要到服务器端进行深入分析; 比如 DB 操作来说,可以继续深入到 DAL 层面分析。 DAL 是我们的分库分表的数据库代理层,同时起到一些熔断限流的作用,所以一条 SQL 执行时间会包含在 DAL 代理层的停留时间、以及在 DB 层的执行时间,利用指标下钻分析,可以分析出如下的一些根因: 某个 table 的所有操作耗时抖动? 某条 SQL 操作耗时抖动? 某台具体 DB 实例抖动? SQL 的停留时间 or 执行时间抖动? 比如客户端的 RPC 操作来说,可以继续递归到远程 appId 的根因分析流程: 4、针对受影响的这些入口使用指标下钻分析,哪些异常也抖动了(有些异常一直在抛,但是跟本次故障无关);5、再次查看上述抖动的操作是否是由 GC 问题、容器问题、变更问题等引起的。 落地情况 阿里本地生活的根因分析能力,1 个月内从产生根因分析的想法到实现方案上线到生产(不包括指标下钻分析的实现,这个是之前就已经实现好的了),1 个月内在生产上实验和优化并推广开来,总共 2 个月内实现了从 0 到 1 并取得了如下成绩: 50 个详细记载的案例中准确定位 48 次,准确率 96%; 最高一天执行定位 500 多次; 平均定位耗时 1 秒; 详细的定位结果展示。 我们对定位准确性的要求如下: 要明确给出受到影响的入口服务有哪些; 每个受到影响的入口服务抖动的操作根因以及操作属性都要正确,每个入口服务抖动的根因很可能不一样的,比如当前应用的 SOA1 是由于 Redis 操作抖动,当前应用的 SOA2 是由于远程 RPC 依赖的其他 SOA3 抖动导致,SOA3 是由于 Redis 操作抖动导致。 客户端操作耗时抖动到底是客户端原因还是服务器端原因要保证正确; 容器问题时,要保证不能定位到错误的容器上。 准确率为什么这么高? 我认为主要是以下 3 个方面: 1、数据的完整度 假如是基于采样链路去分析,必然会存在因为漏采导致误判的问题。 我们分析的数据是全量链路数据转化成的指标数据,不存在采样的问题,同时在分析时可以直接基于指标进行快速分析,临时查采样的方式分析速度也是跟不上的。 2、建模的准确性 你的建模方案能回答出每个 SOA 服务抖动的根因分别是什么吗? 绝大部分的建模方案可能都给不出严谨的数据证明,以 DB 是根因为例,他们的建模可能是 SOA 服务是一个指标,DB 操作耗时是一个指标,2 者之间是没有任何关联的,没有数据关联你就给不出严谨的证明,即没法证明 DB 的这点抖动跟那个 SOA 服务之间到底有没有关联,然后就只能处于瞎推测的状态,这里就必然存在误判的情况。 而我们上述的建模是建立了相关的关联,我们会统计每个入口后的每种操作的耗时,是可以给到严谨的数据证明。 3、异常判定的自适应性 比如 1 次 SOA 服务整体耗时 1s,远程调用 RPC1 耗时 200ms,远程调用 RPC2 耗时 500ms,到底是哪个 RPC 调用耗时抖动呢?耗时最长的吗?超过一定阈值的 RPC 吗? 假如你有阈值、同环比的限制,那么准确率一定不高的。我们的指标下钻分析在解决此类问题的时候,是通过当前情况下的波动贡献度的方式来计算,即使你耗时比较高,但是和之前正常情况波动不大,那么就不是波动的根因。 速度为什么这么快? 我认为主要是以下 2 方面的原因: 业内领先的时序数据库 LinDB 根因分析需要对诸多指标的全量维度数据进行 group by 查询,因此背后就需要依靠一个强大的分布式时序数据库来提供实时的数据查询能力。 LinDB 时序数据库是我们阿里本地生活监控系统 E-Monitor 上一阶段的自研产物,在查询方面: 强悍的数据压缩:时序数据原始数据量和实际存储量之比达到 58:1,相同 PageCache 的内存可以比别的系统容纳更多的数据; 高效的索引设计:索引的预过滤,改造版的 RoaringBitmap 之间的 and or 操作来进行高效的索引过滤; 单机内充分并行化的查询机制:利用 akka 框架对整个查询流程异步化。 整体查询效率是 InfluxDB 的几倍到几百倍,详细见文章—分布式时序数据库 - LinDB :https://zhuanlan.zhihu.com/p/35998778 指标下钻分析算法的高效 我们不需要每个时间线都进行预测; 实际要计算的方案个数非常之少; 方案算分上可以适应于任何加减乘除之类的指标计算上,比如根因定位中的平均响应时间 = 总时间 / 总次数 SOA1 的平均响应时间 t1 和 SOA2 的平均响应时间 t2,SOA1 和 SOA2 的总体平均响应时间并不是 t1 和 t2 的算术平均而是加权平均,如果是加权平均,那么久需要多存储一些额外的信息,并且需要进行额外的加权计算。 实际案例 案例一 故障现场如下,某个应用的 SOA 服务抖动上升: 直接点击根因分析,就可以分析到如下结果: AppId1 的 SOA 服务在某个机房下抖动了: 依赖的 AppId2 的 SOA 服务抖动。 依赖的 AppId3 的 SOA 服务抖动。 依赖的 AppId4 的本地处理和 Redis 操作耗时抖动。 依赖的 AppId5 的本地处理和 Redis 操作耗时抖动。 依赖的 AppId6 的本地处理和 Redis 操作耗时抖动。 这里的本地处理包含获取 Redis 连接对象 Jedis 的耗时,这一块没有耗时打点就导致划分到本地处理上了,后续会进行优化。这里没有给出详细的 Redis 集群或者机器跟我们的实际情况有关,可以暂时忽略这一点。 点击上面的每个应用,下面的表格都会列出所点击应用的详细故障信息: 1、受影响的 SOA 服务有哪些,比如 AppId1 的 3 个 SOA 服务受到影响;2、每个 SOA 服务抖动的根因,比如 AppId1 的 3 个 SOA 服务都受到 AppId2 的 1 个 SOA 服务的抖动影响;3、表格中每一个链接都可以跳转到对应的指标曲线监控面板上。 再比如点击 AppId4,显示如下: AppId4 的 1 个 SOA 方法抖动: 1、该方法的本地处理抖动(实际是获取 Redis 连接对象 Jedis 的耗时抖动);2、该方法依赖的 Redis 操作抖动;3、该方法抛出 Redis 连接异常; 案例二 故障现场如下,某个应用的 SOA 服务抖动上升。 点击根因分析,就可以帮你分析到如下结果: AppId1 的 SOA 服务在某个机房下抖动了: 1、依赖的 AppId2 的 SOA 服务抖动,依赖的 DB 服务抖动。2、依赖的 AppId3 的 SOA 服务抖动,依赖的 AppId2 的 SOA 服务抖动,依赖的 DB 服务抖动。 点击 AppId2,可以看下详细情况,如下所示: 从表格中就可以看到,根因是 DB 的一台实例抖动导致这个 dal group 所有操作抖动。 【云栖号在线课堂】每天都有产品技术专家分享!课程地址:https://yqh.aliyun.com/live 立即加入社群,与专家面对面,及时了解课程最新动态!【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK 原文发布时间:2020-05-26本文作者:李刚,予谱本文来自:“高效运维公众号”,了解相关信息可以关注“高效运维”

优秀的个人博客,低调大师

直播转点播,秒级上线!详解优酷直转点系统

作者| 阿里文娱算法专家 静斋、阿里文娱算法专家 邑尘 一、系统设计背景 互联网视频平台以内容为王,如何帮助内容发挥更大的价值,是技术团队一直努力的方向。 优酷媒体生产团队服务于优酷全站的内容生产,并与媒资运营一起保障着 OGC/PGC 内容的上 线时效与质量。为配合媒资运营提供高时效,高画质,易操作的在线剪辑服务,技术团队启动 了媒资中心全方位升级项目,从基础提效、供需分发匹配、标签体系和生态供给等多个维度, 实现媒资的智能化生产和有效供给,优酷直(播)转点(播)在线剪辑系统应运而生。直转点在线剪辑系统是一个基于 Web 页面的在线生产剪辑系统,前端页面用于用户操作剪 辑并预览效果,后端剪辑服务接受前端指令来做视频的裁剪、遮标、整档上线,拆条等功能, 可支撑不同的生产业务方流水线工作,极大的提升生产效率。本文将介绍其中最常用的直播

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册