解决mongo的tickets被耗尽导致卡顿问题

目录
  • tickets是什么
  • 思考历程
  • 总结

近一年来,项目线上环境的mongo数据库出现多次tickets被耗尽,导致数据库卡顿,并且都是突然出现,等待一段时间后又能自动恢复。

为了解决这个问题,我们进行了长期的探索和研究,先后从多个角度进行优化,于此记录和分享一下这一路的历程。

tickets是什么

为了解决这个问题,我们首先要明白ticktes是什么,其实网上基本都说的一知半解,没有一个能说明白的,但是有一个查询tieckts消耗情况的mongo命令:

db.serverStatus().wiredTiger.concurrentTransactions

查询结果:

{
	"write" : {
		"out" : 0,
		"available" : 128,
		"totalTickets" : 128
	},
	"read" : {
		"out" : 1,
		"available" : 127,
		"totalTickets" : 128
	}
}

可以看到tickets分为读写两种,那ticktets到底是什么呢,我们根据这个查询命令,其实大致可以猜测认为是当前同时存在的事务数量。

也就是mongo限制了同时进行的事务数。

早期因为不知道tickets到底是什么意思,尝试过很多思路错误的优化,所以解决问题,最好还是能弄明白问题本身,才能对症下药。

思考历程

在众多数据库卡顿的经历中,曾有一次因为rabbitmq导致的数据库卡顿,原因是一小伙伴在请求的过滤层加了一个发送mq的逻辑,但是没有进行限制,导致每次只有有接口被调,都会去发布一个mq消息,由于过高的并发导致rabbitmq不堪重负,倒是让人想不明到的是mq卡的同时,数据库也卡住了。

一开始以为是因为消息过多,导致消费者疯狂消费,压垮了数据库,其实不存在这个问题,因为我们的mq配置单个消费者机器是串行的,也就是同一台机器同一时间只会消费同一个消息队列的一条消息,所以并不会因为消息的多给数据库带来压力,只会堆积在mq集群里。所以这次其实没有找到mq卡顿导致mongo卡顿的原因。

我们接入的几家第三方服务,比如给我们提供IM消息服务的融云,每次他们出现问题的时候,我们也会出现数据库卡顿,并且每次时间出奇的一直,但也始终找不到原因。

起初经过对他们调用我们接口情况进行分析,发现每次他们出问题时,我们收到的请求会倍增,以为是这个原因导致的数据库压力过大,并且我们基于redis和他们回调的流水号进行了拦截,拦截方式如下:

  • 当请求过来时从redis中查询该笔流水号状态,如果状态为已完结,则直接成功返回
  • 如果查询到状态是进行中,则抛异常给第三方,从而让他继续重试
  • 如果查询不到状态,则尝试设置状态为进行中并设置10秒左右的过期时间,如果设置成功,则放到数据库层面进行数据处理;如果设置失败,也抛异常给第三方,等待下次重试
  • 等数据库曾处理完成后,将redis中的流水号状态改为已完结。

避免重复请求给我们带来的数据库的压力。这其实也算是一部分原因但还是不算主要原因。

引起mongo卡顿的还有发布版本,有一段时间隔三差五发布版本,就会出现卡顿,但是查看更新的代码也都是一些无关痛痒理论上不会引起问题的内容。

后来发现是发布版本时每次同时关闭和启动的机器从原来的一台改成了两台(一台一台发布太慢,所以运维改成了两台两台一起发),感觉原因应该就在这里,后来想到会不会和优雅关闭有关,当机器关闭时仍然有mq消费者以及内置循环脚本在执行,当进程杀死时,会产生大量需要立马回滚的事务,从而导致mongo卡顿。

后来经过和运维小伙伴的沟通发现,在优雅关闭方面确实存在问题,他们关闭容器时会小容器内的主进程发一个容器即将关闭的信号,然后等待几十秒后,如果主进程没有自己关闭,则会直接杀死进程。

为此我们需要在程序中实现对关闭信号的监听,并实现优雅关闭的逻辑,在spring中,我们可以通过spring的时间拿到外部即将关闭的信号:

	@Volatile
	private var consumeSwitch = true

	/**
	* 销毁逻辑
	*/
	@EventListener
	fun close(event: ContextClosedEvent){
		consumeSwitch = false
		logger.info("----------------------rabbitmq停止消费----------------------")
	}

可以通过如上方式,对系统中的mq消费者或者其他内置程序进行优雅关停控制,对优雅关闭问题优化后,服务器关闭重启导致的数据库卡顿确实得到了有效解决。

上面的融云问题优化过后,后来融云再次卡顿的时候,还是会出现mongo卡顿,由此可见,肯定和第三方有关,但上面说的问题肯定不是主要原因。

后来我看到我们调用第三方的逻辑很多都在@Transactional代码块中间,后来去看了第三方sdk里的逻辑,其实就是封装了一个http请求,但是http请求的请求超时时间长达60秒,那就会有一个问题,如果这个时候第三方服务器卡顿了,这个请求就会不断地等,知道60s超时,而由于这个操作是在事务块中,意味着这个事务也不会commit掉,那等于这个事务所占用的tickets也一直不会放掉,至此根本原因似乎找到了,是因为事务本身被卡住了,导致tickets耗尽,从而后面新的事务全部都在等待状态,全部都卡住了。

其实这次找的原因,同样也可以解释前面mq卡顿导致的数据库卡顿,因为同样有大量的发送mq的操作在事务块中,因为短时间疯狂发mq,导致mq服务端卡顿,从而导致发mq的操作出现卡顿,这就会出现整个事务被卡住,接着tickets被消耗殆尽,整个数据库卡顿。

找到确定问题后就好对症下药了,第三方的问题由于我们不能保证第三方的稳定性,所以当第三方出现问题时的思路应该是进行服务降级,允许部分功能不可用,确定核心业务不受影响,我们基于java线程池进行了同步改异步处理,并且由于第三方的工作是给用户推送im消息,所以配置的舍弃策略是当阻塞队列堆积满之后,将最老的进行丢弃。

而如果是mq导致的这种情况,我们这边没有进行额外的处理,因为这种情况是有自身的bug导致的,这需要做好整理分享工作,避免再次出现这样的bug。

//自己实现的runnable
abstract class RongCloudRunnable(
	private val taskDesc: String,
	private val params: Map<String, Any?>
	) : Runnable {

	override fun toString(): String {
		return "任务名称:${taskDesc};任务参数:${params}"
	}
}
//构建线程池
private val rongCloudThreadPool = ThreadPoolExecutor(
	externalProps.rongCloud.threadPoolCoreCnt, externalProps.rongCloud.threadPoolMaxCnt, 5,
	TimeUnit.MINUTES, LinkedBlockingQueue<Runnable>(externalProps.rongCloud.threadPoolQueueLength),
	RejectedExecutionHandler { r, executor ->
		if (!executor.isShutdown) {
			val item = executor.queue.poll()
			logger.warn("当前融云阻塞任务过多,舍弃最老的任务:${item}")
			executor.execute(r)
		}
	}
)

//封装线程池任务处理方法
fun taskExecute(taskDesc: String, params: Map<String,Any?>, handle: ()-> Unit){
	rongCloudThreadPool.execute(object :RongCloudRunnable(taskDesc, params){
		override fun run() {
			handle()
		}
	})
}

//具体使用
taskExecute("发送消息", mapOf(
	"from_id" to fromId,
	"target_ids" to targetIds,
	"data" to data,
	"is_include_sender" to isIncludeSender
)){
	sendMessage(BatchSendData(fromId, targetIds, data, isIncludeSender))
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • MongoDB高可用与分片

    目录 一.复制 二.如何进行选举 三.优先级 四.选举仲裁者 五.同步 六.处理过时数据 七.哈希片键 ​​​​​​八.多热点 九.分片规则 1.分片的限制 2.片键的基数 十.控制数据分发 1.自动分片 2.手动分发 一.复制 在MongoDB中,创建副本集后就可以使用复制功能了,副本集是一组服务器,其中一个用于处理写操作的主节点primary,还有多个用于保存主节点数据副本的从节点secondary.如果主节点崩溃了,则从节点会选取出一个新的主节点. 如果使用复制功能时有一台服务器停止运行了

  • MongoDB查询与游标之分布式文件存储

    目录 一.查询 1.find()基本用法 2.指定要返回的键 3.查询条件 4.or查询 5.$not 二.特定类型的查询 1.null 2.正则表达式 3.查询数组 4.数组与范围查找的相互作用 三.游标 四.游标的生命周期 五.limit.skip.soat 1.常用的查询选项 2.使用skip进行分页 3.不用skip进行分页 一.查询 1.find()基本用法 查询就是返回集合中文档的一个子集,子集的范围从0个文档到整个集合.要返回哪些文档由find的第一个参数决定,该参数是一个用于指定

  • Mongoose find 查询返回json数据处理方式

    目录 前言 需求 处理思路 遇到问题 解决方案 总结 前言 Mongoose find方法,打印看着返回的是json数据,实际返回的是Mongoose实例,为了方便自定义拓展或操作链式操作. 需求 如图复制按钮,点击复制按钮填写信息,复制出有相同属性的数据模型: 处理思路 传参:{id:"", //被复制的数据模型id ...(其他填写参数) };通过id查询被复制数据模型所有数据,删除数据id,删除属性id,其他填写参数覆盖,然后写库. 遇到问题 代码如下,执行时,直接报堆栈溢出,获

  • 解决mongo的tickets被耗尽导致卡顿问题

    目录 tickets是什么 思考历程 总结 近一年来,项目线上环境的mongo数据库出现多次tickets被耗尽,导致数据库卡顿,并且都是突然出现,等待一段时间后又能自动恢复. 为了解决这个问题,我们进行了长期的探索和研究,先后从多个角度进行优化,于此记录和分享一下这一路的历程. tickets是什么 为了解决这个问题,我们首先要明白ticktes是什么,其实网上基本都说的一知半解,没有一个能说明白的,但是有一个查询tieckts消耗情况的mongo命令: db.serverStatus().w

  • 解决angular 使用原生拖拽页面卡顿及表单控件输入延迟问题

    说明 之前有一个angular项目,页面上表单不算多,也就一百来个(这个不固定,有的地方多,有的地方少),但是再输入的时候会造成输入延迟,反应不灵敏,对用户体验极其不好.还有一个功能就是拖拽功能(原生,没有使用官方中的拖拽功能),从左边拖到右边区域,拖拽区域少的时候还挺流畅,但一旦有几百上千的时候反应极其的慢 原因(?) 上面两个问题其实都和angular的机制有关.一个双向绑定一个拖拽归根结底都是因为angular的变化检测 angular的双向绑定主要是脏数据检查,如果大量的检查,效率比较低

  • antd的select下拉框因为数据量太大造成卡顿的解决方式

    相信用过antd的同学基本都用过select下拉框了,这个组件数据量少的时候很好用,但是当数据量大的时候,比如大几百条上千条甚至是几千条的时候就感觉一点都不好用了,卡的我怀疑人生,一点用户体验都没有了. 当然这不是我想去优化它的动力,主要是公司业务人员和后端的同事也无法忍受,于是我只能屈从于他们的淫威.... 想要优化肯定要知道为什么会卡,初步判断就是数据量过大导致渲染option组件的时间过长导致卡顿,于是想要不卡只能限制渲染的数据数量. 我的想法是这样的:任何时候都只渲染前100条数据以保证

  • el-table嵌套el-popover处理卡顿的解决

    目录 罪魁祸首 解决方法 罪魁祸首 一个常见的场景是在表格行内以el-popover的形式对行内信息进行一些业务操作.在表格分页10条.20条的情况下页面运行良好,但是在分页400条的时候会出现肉眼可见的卡顿.原因是表格渲染的popover组件太多了,一行如果至少3个popover组件,那么400行至少就得渲染1200个了.下面就是导致卡顿的通常写法: <el-table-column label="操作"> <template #default="{ ro

  • Android 优化之卡顿优化的实现

    Android 系统每隔 16ms 会发出 VSYNC 信号重绘界面(Activity).之所以是 16ms,是因为 Android 设定的刷新率是 60FPS(Frame Per Second),也就是每秒 60 帧的刷新率,约合 16ms 刷新一次. 这就意味着,我们需要在 16ms 内完成下一次要刷新的界面的相关运算,以便界面刷新更新. 假设我们更新屏幕的背景图片需要 24ms 来做这次运算,当系统在第一个 16ms 时刷新界面,由于运算还没有结束,无法绘出图片.当系统隔 16ms 再发一

  • IOS中判断卡顿的方案总结

    FPS FPS (Frames Per Second) 是图像领域中的定义,表示每秒渲染帧数,通常用于衡量画面的流畅度,每秒帧数越多,则表示画面越流畅,60fps 最佳,一般我们的APP的FPS 只要保持在 50-60之间,用户体验都是比较流畅的. 监测FPS也有好几种,这里只说最常用的方案,我最早是在YYFPSLabel中看到的.实现原理实现原理是向主线程的RunLoop的添加一个commonModes的CADisplayLink,每次屏幕刷新的时候都要执行CADisplayLink的方法,所

  • JS时间分片技术解决长任务导致的页面卡顿

    目录 起因 处理办法 事件循环 浏览器渲染时机 原始代码 代码 效果 函数改造 代码 效果 优化时间分片 代码 效果 对比优化前后 代码 效果 最后 起因 同事遇到一个动画展示的问题,就是下面要执行一个运算量很大的函数,他要加载一个 loading,但他发现把 loading 的元素 display: block; 页面中也不会立刻出现 loading 动画,出现动画的时候是运算函数执行完毕之后. 处理办法 有两种方法去处理这种耗时任务,第一种就是 webWorker,但是一些 dom 的操作做

  • NestedScrollView+Recyclerview下滑卡顿解决方法

    大家在进行安卓开发用到NestedScrollView+Recyclerview的时候,经常出现的情况就是加载下滑的时候没有任何问题,很流畅,但是在下滑以后明显出现了卡顿的情况,小编根绝这个问题,给大家再来的解决方法,一起来学习下. 我们先来看下这个BUG的表现: 1.滑动卡顿, 2.加载下滑时流畅,下滑时明显的卡顿 3.进入页面时直接加载RecyclerView部分的内容(这里我理解为控件惯性,不知道对不对-------尴尬!!!!!!) 下面我们一一来解决这些问题 在开发项目中,涉及到到商品

  • iOS App使用GCD导致的卡顿现象及解决方法

    最近在调研 iOS app 中存在的各种卡顿现象以及解决方法. iOS App 出现卡顿(stall)的概率可能超出大部分人的想象,尤其是对于大公司旗舰型 App.一方面是由于业务功能不停累积,各个产品团队之间缺乏协调,大家都忙着增加功能,系统资源出现瓶颈.另一方面的原因是老设备更新换代太慢,iOS 设备的耐用度极好,现在还有不少 iPhone 4S 在服役,iPhone 6 作为问题设备持有量很高,据估计,现在 iPhone 6s 以前的设备占有比高达 40%. 所以,如果尝试在线上 App

  • RecyclerView嵌套RecyclerView滑动卡顿的解决方法

    Android 利用RecyclerView仿淘宝订单页面实现,解决RecyclerView嵌套RecyclerView滑动卡顿 问题: 最近在项目中碰到一个问题,类似于淘宝订单页面,由于每个订单项中可能会包含多个具体的项目,要想实现这种效果,一般来说需要在RecyclerView中嵌套RecyclerView,这样做会导致,如果订单项中的具体项目过多,超过一屏,展示效果会有卡顿现象,原因就是由于两个RecyclerView的存在,使得滑动的view滑出当前屏幕的释放存在冲突 思路: 参照 An

随机推荐