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

这篇文章主要介绍了mongo的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))
}		

关于“mongo的tickets被耗尽导致卡顿问题怎么解决”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“mongo的tickets被耗尽导致卡顿问题怎么解决”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程之家行业资讯频道。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


类型转换 1、int转string 2、string转int 3、string转float 4、用户结构类型转换
package main import s &quot;strings&quot; import &quot;fmt&quot; var p = fmt.Println func main() { p(&quot;Contains: &quot;, s.Contains(&quot;test&quo
类使用:实现一个people中有一个sayhi的方法调用功能,代码如下: 接口使用:实现上面功能,代码如下:
html代码: beego代码:
1、读取文件信息: 2、读取文件夹下的所有文件: 3、写入文件信息 4、删除文件,成功返回true,失败返回false
配置环境:Windows7+推荐IDE:LiteIDEGO下载地址:http://www.golangtc.com/downloadBeego开发文档地址:http://beego.me/docs/intro/ 安装步骤: 一、GO环境安装 二、配置系统变量 三、Beego安装 一、GO环境安装 根
golang获取程序运行路径:
Golang的文档和社区资源:为什么它可以帮助开发人员快速上手?
Golang:AI 开发者的实用工具
Golang的标准库:为什么它可以大幅度提高开发效率?
Golang的部署和运维:如何将应用程序部署到生产环境中?
高性能AI开发:Golang的优势所在
本篇文章和大家了解一下go语言开发优雅得关闭协程的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1.简介本文将介绍首先为什么需要主...
这篇文章主要介绍了Go关闭goroutine协程的方法,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。1.简介本文将介绍首先为什么需要主动关闭gor...
本篇文章和大家了解一下go关闭GracefulShutdown服务的几种方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。目录Shutdown方法Regi...
这篇文章主要介绍了Go语言如何实现LRU算法的核心思想和实现过程,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。GO实现Redis的LRU例子常
今天小编给大家分享的是Go简单实现多租户数据库隔离的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会...
这篇“Linux系统中怎么安装NSQ的Go语言客户端”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希
本文小编为大家详细介绍“怎么在Go语言中实现锁机制”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么在Go语言中实现锁机制”文章能帮助大家解决疑惑,下面...
今天小编给大家分享一下Go语言中interface类型怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考