首页 文章 精选 留言 我的

精选列表

搜索[权限与责任],共10000篇文章
优秀的个人博客,低调大师

JS原型链与Objective-C内存布局不能说的秘密

本篇文章并不是新知识的探索,文章没啥含量,就是看到某2个不同领域的知识,被串联起来,或者语言的设计思想被印证了感到开心而记录下来。 假期看了本关于 JS 的书。很多地方就是已知知识。但是看到2个地方感觉和现有知识体系有关联,蛮有意思。拍照记录了下 Javascript 借鉴了 Self 语言中关于原型的继承,可能会比较陌生,什么是“Self”? 看看 JS 的表现:假设从对象 O 中查询属性 X 如果 O 没有 X 这个自有属性,则会从 O 的原型对象查询属性 X 如果原型对象也没有叫做 X 的自有属性,但它有自己的原型,则继续查询这个原型的原型 这个过程会不断继续,直至找到属性 X 或者查询到一个原型为 null 的对象 可见,对象通过其 prototype 属性创建了一个用于继承属性的链条或链表。 其实,Self 语言由派生自 Smalltalk,也就是 Objective-C 的借鉴语言。访问 JS 对象属性、方法(找不到则从原型链上不断回溯,直到找到或者找到元类对象 null),这个特性和 OC 一模一样。 关于 Self 语言有2个中文 PDF可供翻阅。SELF:简洁的威力、克隆的反攻

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

Taro 正式发布 3.3 版本:支持使用 H5 标签与框架 DevTools

距离 Taro 3.3 第一个 alpha 版本的发布,已过去三个月的时间。期间我们不断地对 3.3 版本进行打磨,除了优化 H5 同构方案外,还引入了一些提升开发体验的新特性和修复了若干重要问题。 支持使用 H5 标签 Taro 3.3 最重要的特性就是支持使用 H5 标签进行开发,让开发者在小程序环境中复用部分的 Web 生态。 例如在 React 中可以这样使用: functionIndex(){ return( <div>HelloWorld!</div> ) } 相关背景及实现思路在 《Taro 3.3 alpha 发布》 一文中有详细说明。 用法 首先需要安装插件 @tarojs/plugin-html: $npmi@tarojs/plugin-html 然后配置使用即可: //config/index.js constconfig={ //... plugins:[ '@tarojs/plugin-html' ] } 插件的详细用法请见文档。 示例项目 为了验证同构功能的可用性和效果,我们对 CSS 样式库 WEUI、React 组件库 Antd Design Mobile、Vue2 组件库 VantUI 的所有组件进行了测试。结果显示有相当一部分的组件能直接运行在小程序环境中。 示例项目仓库地址: 兼容 WeUI 的例子 兼容 Antd Design Mobile 的例子 兼容 VantUI 的例子 What's news 在开发 @tarojs/plugin-html 插件的时候,我们发现需要为 Taro 模拟的 DOM APIs 新增一些方法,如 getBoundingClientRect 等。但是对于不使用这个插件的开发者来说这些 API 是冗余的,会增大包体积。因此我们对 @tarojs/runtime 进行了重构,把 DOM APIs 做成可插拔式,让开发者可以选择在最终的编译结果里只存在哪些 DOM APIs。 本次对运行时的重构也是为了 Taro 3.4 版本作铺垫。React、Vue2 和 Vue3 这些框架运行时需要的 DOM APIs 是不尽相同的,但是目前假设开发者使用了 React,编译结果里仍然会存在着只有 Vue2 或 Vue3 依赖的 DOM APIs。Taro 3.4 将会把对各 Web 开发框架的支持逻辑抽离成为一个个插件,如:@tarojs/plugin-framework-react 等,目的是剔除掉冗余代码。 例如,假设开发者没有使用渲染 HTML 文本的 API(如 React 的 dangerouslySetInnerHTML)。这时可以修改 Taro 编译配置的 mini.runtime.enableInnerHTML 选项,令最终的打包结果剔除掉相关代码: //config/index.js config={ //... mini:{ runtime:{ enableInnerHTML:false } } } 小程序支持使用框架的 DevTools 自 Taro 3.x 改造为运行时框架后,有不少同学抱怨小程序开发者工具的 AppData 面板看到的都是 Taro 虚拟 DOM 的序列化结果,而不是页面的 data 数据。为了缓解这个问题,并进一步地提升开发者的开发体验,Taro 对 React DevTools 和 Vue DevTools 进行了适配。只需要简单地配置,就能够和开发 Web 应用时一样使用框架的 DevTools。 React DevTools 安装并配置使用 @tarojs/plugin-react-devtools 即可,详细用法请见文档。 效果图: Vue DevTools 安装并配置使用 @tarojs/plugin-vue-devtools 即可,详细用法请见文档。 效果图: 修复百度小程序 flex 布局的问题 在过去很长一段时间里,Taro 3 在百度小程序里是不支持使用 flex 布局的。原因是逻辑层的每个 Taro DOM 节点都在渲染层使用一个对应的 <template> 进行渲染,而百度小程序会把 <template> 渲染成一个真实节点。 非常感谢 @SyMind 的 PR,帮助 Taro 绕过了此问题。从 Taro 3.2 开始,部分组件在百度小程序中也能够使用 flex 布局。 相关的讨论和提交可以浏览 #6015 #9529 升级指南 从 v2.x 升级的同学需要先按 迁移指南 进行操作。 从 v3.x 升级的同学,首先需要安装 v3.3 的 CLI 工具: npmi-g@tarojs/cli 然后手工修改 package.json 文件中 Taro 相关依赖的版本,再重新安装依赖;或者使用 taro update project 更新依赖。 如果安装失败或打开项目失败,可以删除 node_modules、yarn.lock、package-lock.json 后重新安装依赖再尝试。 The End Taro 3.4 下一个 minor 版本,我们会考虑把对各个 Web 开发框架的支持逻辑拆分为对应的插件。让开发者可以以插件的形式为 Taro 拓展支持一门新的开发框架,我们会运用此能力进行对 PReact 的支持,希望能进一步减少用户的包体积。 鸿蒙应用 && OpenHarmony 近期我们也一直在探索对鸿蒙应用 && OpenHarmony的适配,目前已有一系列的讨论和开发计划,也有可以运行的 Demo,详情请见 鸿蒙 && OpenHarmony 适配小组。 RoadMap: 欢迎各位开发者参与讨论和共建~ 快应用 此外,社区大佬 @bgfist 正在为 Taro 开发支持快应用的功能。也欢迎各位开发者参与讨论和共建,相关 PR。 感恩 时光飞逝,1 年前 Taro 3 正式发布,Taro 逐渐成为被社区喜欢、信赖的开源项目。未来我们也将继续做好 Taro 的开源工作,同时也希望社区有更多的开发者参与进来,无论是提出 Issues、在论坛发帖、提交 PR 还是帮助建设周边生态等。让我们一起把 Taro 做好,做强。 最后,衷心感谢为 Taro 从 v3.2 走到 v3.3 贡献过代码的各位同学!

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

十亿级流量下,我与Redis时延小突刺的战斗史

# 一、背景 某一日收到上游调用方的反馈,提供的某一个Dubbo接口,每天在固定的时间点被短时间熔断,抛出的异常信息为提供方dubbo线程池被耗尽。当前dubbo接口日请求量18亿次,报错请求94W/天,至此开始了优化之旅。 # 二、快速应急 ## 2.1 快速定位 首先进行常规的系统信息监控(机器、JVM内存、GC、线程),发现虽稍有突刺,但都在合理范围内,且跟报错时间点对不上,先暂时忽略。 其次进行流量分析,发现每天固定时间点会有流量突增的情况,流量突增的点跟报错的时间点也吻合,初步判断为短时大流量导致。 流量趋势 ![](https://static001.geekbang.org/infoq/ac/ace49acc5bc2b254ee731364a358ba75.png) **被降级量** ![](https://static001.geekbang.org/infoq/3c/3cc063ebb1b1d762dfaf586260161959.png) **接口99线** ![](https://static001.geekbang.org/infoq/54/547d97366352697cc45228e7ac06df72.png) # 三、寻找性能瓶颈点 ## 3.1 接口流程分析 ### 3.1.1 流程图 ![](https://static001.geekbang.org/infoq/e5/e5e5709e6adcc1bcd25a3a2290d99653.png) ### 3.1.2 流程分析 > 收到请求后调用下游接口,使用hystrix熔断器,熔断时间为500MS; > 根据下游接口返回的数据,进行详情数据的封装,第一步先到本地缓存中获取,如果本地缓存没有,则从Redis进行回源,Redis中无则直接返回,异步线程从数据库进行回源。 > 如果第一步调用下游接口异常,则进行数据兜底,兜底流程为先到本地缓存中获取,如果本地缓存没有,则从Redis进行回源,Redis中无则直接返回,异步线程从数据库进行回源。 ## 3.2 性能瓶颈点排查 ### 3.2.1 下游接口服务耗时比较长 调用链显示,虽然下游接口的P99线在峰值流量时存在突刺,超出1S,但因为熔断超时的设置(熔断时间500MS,coreSize&masSize=50,下游接口平均耗时10MS以下),判断下游接口不是问题的关键点,为进一步排除干扰,在下游服务存在突刺时能快速失败,调整熔断时间为100MS,dubbo超时时间100MS。 ### 3.2.2 获取详情本地缓存无数据,Redis回源 借助调用链平台,第一步分析Redis请求流量,以此来判断本地缓存的命中率,发现Redis的流量是接口流量的2倍,从设计上来说不应该出现这个现象。开始代码Review,发现在有一处逻辑出现了问题。 没有从本地缓存读取,而是直接从Redis中获取了数据,Redis最大响应时间也确实发现了不合理的突刺,继续分析发现Redis响应时间和Dubbo99线突刺情况基本一致,感觉此时已经找到了问题的原因,心中暗喜。 **Redis请求流量** ![](https://static001.geekbang.org/infoq/0d/0d125fec49a6cbced8f8d36ffd34051d.png) **服务接口请求流量** ![](https://static001.geekbang.org/infoq/fc/fcd9bf47452667d3fc34a9eb977d8b09.png) **Dubbo99线** ![](https://static001.geekbang.org/infoq/ff/ffdfc17960caedcf2e1f7a40683c5ef9.png) **Redis最大响应时间** ![](https://static001.geekbang.org/infoq/7a/7a9f3fe6b6a958699c613bc44f88a05b.png) ### 3.2.3 获取兜底数据本地缓存无数据,Redis回源 正常 ### 3.2.4 记录请求结果入Redis 因为当前Redis做了资源隔离,且未在DB后台查询到慢日志,此时分析导致Redis变慢的原因有很多,不过其他的都被主观忽略了,注意力都在请求Redis流量翻倍的问题上了,故优先解决3.2.2中的问题。 # 四、解决方案 ## 4.1 3.3.2中定位的问题上线 **上线前Redis请求量** ![](https://static001.geekbang.org/infoq/0d/0d125fec49a6cbced8f8d36ffd34051d.png) **上线后Redis请求量** ![](https://static001.geekbang.org/infoq/2f/2f0fc2d08c9af47015ad094c5b0343e5.png) 上线后Redis流量翻倍问题得到解决,Redis最大响应时间突刺有所缓解,但依旧没能彻底解决,说明大流量查询不是最根本的原因。 **redis最大响应时间(上线前)** ![](https://static001.geekbang.org/infoq/66/6667b0ca10a3d33453a731b4859676f9.png) **redis最大响应时间(上线后)** ![](https://static001.geekbang.org/infoq/9a/9a6116968b85f1a456e107af0253fd26.png) ## 4.2 Redis扩容 在Redis异常流量问题解决后,问题并未得到彻底解决,此时能做的就是静下心来,仔细去梳理导致Redis慢的原因,思路主要从以下三个方面: - 出现了慢查询 - Redis服务出现性能瓶颈 - 客户端配置不合理 基于以上思路,一个个的进行排查;查询Redis慢查询日志,未发现慢查询。 借用调用链平台详细分析慢的Redis命令,没有了大流量导致的慢查询的干扰,问题定位流程很快,大量的耗时请求在setex方法上,偶尔出现查询的慢请求也都是在setex方法之后,根据Redis单线程的特性判断setex是Redis99线突刺的元凶。找到具体语句,定位到具体业务后,首先申请扩容Redis,由6个master扩到8个master。 ![](https://static001.geekbang.org/infoq/a8/a8ae8caf453f091ec260bd048ab25b9e.png) ![](https://static001.geekbang.org/infoq/b4/b4cf1d4a23b53b06f4443f41ee1f9db7.png) **Redis扩容前** ![](https://static001.geekbang.org/infoq/38/382e030c7a1779af34503278305e2eb6.png) **Redis扩容后** ![](https://static001.geekbang.org/infoq/d5/d5d81475112685699cc27fa20e4ccaaf.png) 从结果上看,扩容基本上没有效果,说明redis服务本身不是性能瓶颈点,此时剩下的一个就是客户端相关配置了。 ## 4.3 客户端参数优化 ### 4.3.1 连接池优化 Redis扩容没有效果,针对客户端可能出现的问题,此时怀疑的点有两个方向。 第一个是客户端在处理Redis集群模式时,对连接的管理上存在BUG,第二个是连接池参数设置不合理,此时源码分析和连接池参数调整同步进行。 #### 4.3.1.1 判断客户端连接管理上是否有BUG 在分析完,客户端处理连接池的源码后,没有问题,跟预想一致,按照槽位缓存连接池,第一个假设被排除,源码如下。 ```java 1、setEx public String setex(final byte[] key, final int seconds, final byte[] value) { return new JedisClusterCommand (connectionHandler, maxAttempts) { @Override public String execute(Jedis connection) { return connection.setex(key, seconds, value); } }.runBinary(key); } 2、runBinary public T runBinary(byte[] key) { if (key == null) { throw new JedisClusterException("No way to dispatch this command to Redis Cluster."); } return runWithRetries(key, this.maxAttempts, false, false); } 3、runWithRetries private T runWithRetries(byte[] key, int attempts, boolean tryRandomNode, boolean asking) { if (attempts <= 0) { throw new JedisClusterMaxRedirectionsException("Too many Cluster redirections?"); } Jedis connection = null; try { if (asking) { // TODO: Pipeline asking with the original command to make it // faster.... connection = askConnection.get(); connection.asking(); // if asking success, reset asking flag asking = false; } else { if (tryRandomNode) { connection = connectionHandler.getConnection(); } else { connection = connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key)); } } return execute(connection); } 4、getConnectionFromSlot public Jedis getConnectionFromSlot(int slot) { JedisPool connectionPool = cache.getSlotPool(slot); if (connectionPool != null) { // It can't guaranteed to get valid connection because of node // assignment return connectionPool.getResource(); } else { renewSlotCache(); //It's abnormal situation for cluster mode, that we have just nothing for slot, try to rediscover state connectionPool = cache.getSlotPool(slot); if (connectionPool != null) { return connectionPool.getResource(); } else { //no choice, fallback to new connection to random node return getConnection(); } } } ``` **4.3.1.2 分析连接池参数** 通过跟中间件团队沟通,以及参考commons-pool2官方文档修改如下; ![](https://static001.geekbang.org/infoq/6c/6c3f477e3ac68b49eab7f189613c8287.png) 参数调整后,1S以上的请求量得到减少,但还是存在,上游反馈降级量由每天90万左右降到每天6W个(关于maxWaitMillis设置为200MS后为什么还会有超过200MS的请求,下文有解释)。 参数优化后Reds最大响应时间 ![](https://static001.geekbang.org/infoq/c8/c86644c5436a2c5d01ebd9bf47d6e1ce.png) 参数优化后接口报错量 ![](https://static001.geekbang.org/infoq/50/50c2fca0770e9bfe0175342f82af12a3.png) ### 4.3.2 持续优化 优化不能停止,如何把Redis的所有写入请求降低到200MS以内,此时的优化思路还是调整客户端配置参数,分析Jedis获取连接相关源码; **Jedis获取连接源码** ```java final AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getRemoveAbandonedOnBorrow() && (getNumIdle() < 2) && (getNumActive() > getMaxTotal() - 3) ) { removeAbandoned(ac); } PooledObject p = null; // Get local copy of current config so it is consistent for entire // method execution final boolean blockWhenExhausted = getBlockWhenExhausted(); boolean create; final long waitTime = System.currentTimeMillis(); while (p == null) { create = false; p = idleObjects.pollFirst(); if (p == null) { p = create(); if (p != null) { create = true; } } if (blockWhenExhausted) { if (p == null) { if (borrowMaxWaitMillis < 0) { p = idleObjects.takeFirst(); } else { p = idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS); } } if (p == null) { throw new NoSuchElementException( "Timeout waiting for idle object"); } } else { if (p == null) { throw new NoSuchElementException("Pool exhausted"); } } if (!p.allocate()) { p = null; } if (p != null) { try { factory.activateObject(p); } catch (final Exception e) { try { destroy(p); } catch (final Exception e1) { // Ignore - activation failure is more important } p = null; if (create) { final NoSuchElementException nsee = new NoSuchElementException( "Unable to activate object"); nsee.initCause(e); throw nsee; } } if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) { boolean validate = false; Throwable validationThrowable = null; try { validate = factory.validateObject(p); } catch (final Throwable t) { PoolUtils.checkRethrow(t); validationThrowable = t; } if (!validate) { try { destroy(p); destroyedByBorrowValidationCount.incrementAndGet(); } catch (final Exception e) { // Ignore - validation failure is more important } p = null; if (create) { final NoSuchElementException nsee = new NoSuchElementException( "Unable to validate object"); nsee.initCause(validationThrowable); throw nsee; } } } } } updateStatsBorrow(p, System.currentTimeMillis() - waitTime); return p.getObject(); ``` 获取连接的大致流程如下: > 是否有空闲连接,有空闲连接就直接返回,没有就创建; > 创建时如果超出最大连接数,则判断是否有其他线程在创建连接,如果没则直接返回,如果有则等待maxWaitMis时间(其他线程可能创建失败),如果未超出最大连接,则执行创建连接操作(此时获取连接等待时间可能会大于maxWaitMs)。 > 如果创建不成功,则判断是否是阻塞获取连接,如果不是则直接抛出异常,连接池不够用,如果是则判断maxWaitMillis是否小于0,如果小于0则阻塞等待,如果大于0则阻塞等待maxWaitMillis。 > 后续就是根据参数来判断是否需要做连接check等。 根据以上流程分析,maxWaitMills目前设置的为200,以上流程加起来最大阻塞时间为400MS,大部分情况为200MS,不应该出现超出400MS的突刺。 此时问题可能出现在创建连接上,因为创建连接比较耗时,且创建时间不定,重点分析是否有这个场景,通过DB后台监控Redis连接情况。 **DB后台监控Redis服务连接** ![](https://static001.geekbang.org/infoq/5a/5a7758cfdc0bc6acfd957975957eb91c.png) 分析上图发现,确实在几个时间点(9:00,12:00,19:00...),redis连接数存在上涨情况,跟Redis突刺时间基本吻合。感觉(之前的各种尝试后,已经不敢用确定了)问题到此定位清晰(在突增流量过来时,连接池可用连接满足不了需求,会创建连接,造成请求等待)。 此时的想法是在服务启动时就进行连接池的创建,尽量减少新连接的创建,修改连接池参数vivo.cache.depend.common.poolConfig.minIdle,结果竟然无效??? 啥都不说了,开始撸源码,jedis底层使用的是commons-poll2来管理连接的,查看项目中使用的commons-pool2-2.6.2.jar部分源码; **CommonPool2源码** ```java public GenericObjectPool(final PooledObjectFactory factory, final GenericObjectPoolConfig config) { super(config, ONAME_BASE, config.getJmxNamePrefix()); if (factory == null) { jmxUnregister(); // tidy up throw new IllegalArgumentException("factory may not be null"); } this.factory = factory; idleObjects = new LinkedBlockingDeque<>(config.getFairness()); setConfig(config); } ``` 竟然发现没有初始化连接的地方,开始咨询中间件团队,中间件团队给出的源码(commons-pool2-2.4.2.jar)如下,方法执行后多了一次startEvictor方法的调用? ```java 1、初始化连接池 public GenericObjectPool(PooledObjectFactory factory, GenericObjectPoolConfig config) { super(config, ONAME_BASE, config.getJmxNamePrefix()); if (factory == null) { jmxUnregister(); // tidy up throw new IllegalArgumentException("factory may not be null"); } this.factory = factory; idleObjects = new LinkedBlockingDeque >(config.getFairness()); setConfig(config); startEvictor(getTimeBetweenEvictionRunsMillis()); } ``` 为啥不一样???开始检查Jar包,版本不一样,中间件给出的版本是在V2.4.2,项目实际使用的是V2.6.2,分析startEvictor有一步逻辑正是处理连接池预热逻辑。 **Jedis连接池预热** ```java 1、final void startEvictor(long delay) { synchronized (evictionLock) { if (null != evictor) { EvictionTimer.cancel(evictor); evictor = null; evictionIterator = null; } if (delay > 0) { evictor = new Evictor(); EvictionTimer.schedule(evictor, delay, delay); } } } 2、class Evictor extends TimerTask { /** * Run pool maintenance. Evict objects qualifying for eviction and then * ensure that the minimum number of idle instances are available. * Since the Timer that invokes Evictors is shared for all Pools but * pools may exist in different class loaders, the Evictor ensures that * any actions taken are under the class loader of the factory * associated with the pool. */ @Override public void run() { ClassLoader savedClassLoader = Thread.currentThread().getContextClassLoader(); try { if (factoryClassLoader != null) { // Set the class loader for the factory ClassLoader cl = factoryClassLoader.get(); if (cl == null) { // The pool has been dereferenced and the class loader // GC'd. Cancel this timer so the pool can be GC'd as // well. cancel(); return; } Thread.currentThread().setContextClassLoader(cl); } // Evict from the pool try { evict(); } catch(Exception e) { swallowException(e); } catch(OutOfMemoryError oome) { // Log problem but give evictor thread a chance to continue // in case error is recoverable oome.printStackTrace(System.err); } // Re-create idle instances. try { ensureMinIdle(); } catch (Exception e) { swallowException(e); } } finally { // Restore the previous CCL Thread.currentThread().setContextClassLoader(savedClassLoader); } } } 3、 void ensureMinIdle() throws Exception { ensureIdle(getMinIdle(), true); } 4、 private void ensureIdle(int idleCount, boolean always) throws Exception { if (idleCount < 1 || isClosed() || (!always && !idleObjects.hasTakeWaiters())) { return; } while (idleObjects.size() < idleCount) { PooledObject p = create(); if (p == null) { // Can't create objects, no reason to think another call to // create will work. Give up. break; } if (getLifo()) { idleObjects.addFirst(p); } else { idleObjects.addLast(p); } } if (isClosed()) { // Pool closed while object was being added to idle objects. // Make sure the returned object is destroyed rather than left // in the idle object pool (which would effectively be a leak) clear(); } } ``` 修改Jar版本,配置中心增加vivo.cache.depend.common.poolConfig.timeBetweenEvictionRunsMillis(检查一次连接池中空闲的连接,把空闲时间超过minEvictableIdleTimeMillis毫秒的连接断开,直到连接池中的连接数到minIdle为止)。 vivo.cache.depend.common.poolConfig.minEvictableIdleTimeMillis(连接池中连接可空闲的时间,毫秒)两个参数,重启服务后,连接池正常预热,最终从Redis层面上解决问题。 优化结果如下,性能问题基本得到解决; **Redis响应时间(优化前)** ![](https://static001.geekbang.org/infoq/c8/c86644c5436a2c5d01ebd9bf47d6e1ce.png) **Redis响应时间(优化后)** ![](https://static001.geekbang.org/infoq/6d/6d48f2593b278d12f1666ffb9179e920.png) **接口99线(优化前)** ![](https://static001.geekbang.org/infoq/ff/ffdfc17960caedcf2e1f7a40683c5ef9.png) **接口99线(优化后)** ![](https://static001.geekbang.org/infoq/f5/f5064391a33cddc219f94fea7191f6c5.png) # 五、总结 出现线上问题时,首先要考虑的还是快速恢复线上业务,将业务的影响度降到最低,所以针对线上的业务,要提前做好限流、熔断、降级等策略,在线上出现问题时能快速找到恢复方案。对公司各监控平台的熟练使用程度,决定了定位问题的速度,每个开发都要把熟练使用监控平台(机器、服务、接口、DB等)作为一个基本能力。 Redis出现响应慢时,可以优先从Redis集群服务端(机器负载、服务是否有慢查询)、业务代码(是否有BUG)、客户端(连接池配置是否合理)三个方面去排查,基本上能排查出大部分Redis慢响应问题。 Redis连接池在系统冷启动时,对连接池的预热,不同commons-pool2的版本,冷启动的策略也不同,但都需要配置minEvictableIdleTimeMillis参数才会生效,可以看下common-pool2官方文档,对常用参数都做到心中有数,在问题出现时能快速定位。 连接池默认参数在解决大流量的业务上稍显乏力,需要针对大流量场景进行调优处理,如果业务上流量不是很大直接使用默认参数即可。 具体问题要具体分析,不能解决问题的时候要变通思路,通过各种方法去尝试解决问题。 > 作者:vivo互联网服务器团队-Wang Shaodong

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

超实用的任务优化与断点执行方案

随着大数据时代的快速发展,企业每天需要存储、计算、分析数以万亿的数据,同时还要确保分析的数据具备及时性、准确性和完整性。面对如此庞大的数据体系,ETL工程师(数据分析师)如何能高效、准确地进行计算并供业务方使用,就成了一个难题。 作为一家数据智能公司,个推在大数据计算领域沉淀了丰富的经验。本篇文章将对大数据离线计算过程中出现的任务缓慢和任务中断这两大痛点问题提出解决思路,期望读者能够有所收获。 一、任务缓慢 “任务执行缓慢”通常是指任务的执行时间超过10个小时,且不能满足数据使用方对数据及时性的要求。比如业务方需早上就能够查看T-1的数据,但是因为任务延时,业务方只能等到下午或者傍晚才能查询、浏览T-1的数据,从而无法及时发现经营问题、进行高效决策。因此,对缓慢任务进行优化成了ETL工程师必不可少的一项工作。 在长期的大数据实践中,我们发现,缓慢任务往往具有一定的共性。只要我们能找到问题所在,并对症下药,就能将任务执行时间大大缩短。个推将任务执行缓慢的常见问题归纳为以下四点:逻辑冗余,数据倾斜、大表复用,慢执行器。接下来会对每个痛点进行详细阐述。 1、逻辑冗余 “逻辑冗余”往往是因为ETL工程师进行数据处理和计算时更关注处理结果是否满足预期,而未深入考虑是否存在更高效的处理方式,导致原本可通过简单逻辑进行处理的任务,在实际中却使用了复杂逻辑来执行。 减少“逻辑冗余”更多地依赖开发者经验的积累和逻辑思维以及代码能力的提升。这里分享一些高级函数,希望能够帮助开发者进一步提升数据处理效率。 Grouping sets分组统计函数。这个函数可以实现在一段SQL中输出不同维度的统计数据,避免出现执行多段SQL的情况,具体写法如下: Lateral view explode() 一行转多行函数。这个函数只能处理array格式数据,需要配合split()函数使用,具体写法如下: 还有其他一些函数、函数名及功能如下,具体用法需要读者自行查询(可登录hive官网查询函数大全): find_in_set() :查找特定字符串在指定字符串中的位置 get_json_object():从json串中抽取指定数据 regexp_extract():抽取符合正则表达的指定字符 regexp_replace() :替换符合正则替换指定字符 reverse():字符串反转 2、数据倾斜 “数据倾斜”是指在MR计算的过程中某些Map job需要处理的数据量太大、耗时太长,从而导致整个进程长时间无法结束,任务处理进度长时间卡在99%的现象。 针对数据倾斜的情况,开发者们可通过代码层面进行修改,具体操作如下: 使用group by方式替换count(distinct id ) 方式进行去重统计 进行大小表关联时使用mapjoin操作或子查询操作,来替换 join操作 group by出现倾斜需要将分组字段值随机切分成随机值+原始值 join操作避免出现笛卡尔积,即关联字段不要出现大量重复 在之前的文章中,个推详细解读了Hive数据倾斜的原因及解决方案,感兴趣的同学可点击了解:深入浅出Hive数据倾斜 3、大表复用 “大表复用”,是指对上亿甚至几十亿的大表数据进行重复遍历之后得到类似的结果。避免大表复用就要求ETL工程师进行系统化的思考,能够通过低频的遍历将几十亿的大表数据瘦身到可重复使用的中间小表,且同时支持后续的计算。 因此,工程师需要在工程开发之初就将整体的工程结构考虑进去,并且坚持“大表仅使用一次”的原则,以提升整个工程的执行效率。 这里介绍一个实战中的例子,供读者参考: geqi_win_tmp表中数据:5000万 4、慢执行器 “慢执行器”是指数据体量过于庞大时,Hive的底层计算逻辑已经无法快速遍历单一分区中的所有数据。 由于在同等资源的情况下,Spark进行数据遍历的效率远高于MapReduce;且Spark任务对资源的抢占程度远大于MapReduce任务,可在短时间内占用大量资源高效完成任务,之后快速释放资源,以提高整个集群任务的执行效率。 因此,针对该情况,开发者可考虑使用pyspark等更为高效的计算引擎进行数据的快速遍历。同时,开发者也需要有意识地加强思维训练,养成良好的开发习惯,在面对海量数据时探索更快、更准、更体系化的计算和处理方式。 二、任务中断 因为各种各样的原因,线上任务经常会出现被kill掉然后重新执行的情况。任务重新执行会严重浪费集群资源,同时使得数据计算结果延迟从而影响到业务方的数据应用。如何避免这种现象的发生呢?个推是这样解决该问题的。 个推的定时任务是基于Azkaban调度系统开发的,个推的数据分析师主要使用shell、HSQL、MySQL、Pypark四种代码进行数据处理,将原始日志清洗、计算,然后生成公共层、报表层数据,最终供业务方使用。 因此个推需要设定四种代码执行器以支持脚本中对不同类型代码的处理。这里主要对其中的三个核心内容进行介绍:代码块输入、执行函数以及循环器。 1、代码块输入 一般情况下,脚本中的shell、HSQL、MySQL、pypark代码会按照顺序直接执行,不能选择性执行。在实践中,我们将代码块以字符串的方式赋值给shell中的变量,并在字符串的开头标记是何种类型的代码,代码执行到具体步骤时只有赋值操作,不会解析执行,具体如下: ✦ 执行HSQL代码块 ✦ 执行shell代码块 ✦ 执行mysql代码块 ✦ 执行pyspark代码块 如此,就实现了将不同的代码放入对应的step_n中。在后续的执行器中这些代码能够直接执行,开发者只需要关心逻辑处理即可。 2、执行函数 执行函数是对shell中变量step_n当中的字符串进行代码解析并执行。不同类型的代码块解析方式不同,因此需要定义不同的执行函数。函数一般单独放在整个工程的配置文件中,通过source的方式调用,具体函数定义如下: Hive、MySQL以及shell的执行函数比较简单,通过hive-e 或者eval的方式就可以直接执行。pyspark需要配置相应的队列、路径、参数等,还需要在工程中增spark.py文件才能执行,此处不做赘述。 3、循环器 循环器是断点执行功能的核心内容,是步骤的控制器。循环器通过判断shell变量名确定需要执行哪一步,通过判断变量中字符串内容确定使用何种函数解析代码并执行。 下图是参考案例,代码如下: 开发者需要在脚本的开始定义好整个代码的结束步骤,以确保循环器正常运行;同时,可将开始步骤当作脚本参数传入,这样就很好地实现了任务的断点执行功能。 总结 ETL工程中的任务缓慢和任务中断问题是每个大数据工程师都需要面对和解决的。本文基于个推大数据实践,针对任务缓慢和任务中断问题提出了相应解决思路和方案,希望能够帮助读者在任务优化以及ETL工程开发方面扩宽思路,提高任务执行效率,同时降低任务维护的人力成本和机器成本。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

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

Nacos

Nacos

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

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册