Antecedentes: en un sistema a gran escala, las excepciones en línea son un resultado inevitable, entonces, ¿cómo podemos encontrar problemas en el funcionamiento de la aplicación lo antes posible y tratarlos a tiempo para evitar expandir el alcance de la influencia?
La respuesta es monitorear la aplicación y alertar sobre mensajes de error.
Este artículo usa SpringBoot + DingTalk para lograr el monitoreo y la alarma de la aplicación, por supuesto, también puede usar WeChat empresarial, notificación por SMS, etc.
Plataforma de desarrollo DingTalk: documentación de acceso de robot personalizado
Después de mirar la documentación de DingTalk, podemos encontrar que la función de notificación de mensajes se puede realizar llamando a la dirección de Webhook para enviar el mensaje de alarma al chat grupal.
Pasos de implementación
Obtener la dirección de llamada
- Primero cree un grupo DingTalk y cree un robot personalizado
Elija una de las configuraciones de seguridad para fortalecer la seguridad y evitar que la dirección de Webhook filtrada se envíe indiscriminadamente
- Creado con éxito, copie la dirección del Webhook, deberá usarla más tarde
Crear una aplicación SpringBoot
- Estructura del proyecto
servicio de advertencia
Definir la interfaz para reportar información de error
Los mensajes de error se agregarán a la cola MonitorMessageQueue
interface WarnService {
fun reportErrorMsg(moduleName: String, msg: String)
}
@Service
class WarnServiceImpl : WarnService {
@Autowired
private lateinit var monitorMessageQueue: MonitorMessageQueue
override fun reportErrorMsg(moduleName: String, msg: String) {
monitorMessageQueue.add(MessageDto().apply {
this.moduleName = moduleName
this.content = msg
this.timestamp = System.currentTimeMillis()
})
}
复制代码
MonitorMessageQueue cola
Métodos proporcionados por la cola
- start: inicia el subproceso del demonio
- drenaje: espere el tiempo de espera para volver al elemento de la cola, aquí establezca un tiempo de espera de 30 segundos para volver
- add: agrega un elemento a la cola
@Component
@Scope("singleton")
class MonitorMessageQueue {
private val queue: BlockingQueue<MessageDto> = LinkedBlockingQueue()
private val logger = LoggerFactory.getLogger(MonitorMessageQueue::class.java)
@Autowired
private lateinit var sendService: DataSendService
@PostConstruct
private fun start() {
logger.info("MonitorMessageQueue start")
val thread = Thread(DataSendThread(this, sendService), "monitor_thread_0")
thread.isDaemon = true
thread.start()
}
//每个机器人每分钟最多发送20条消息到群里,如果超过20条,会限流10分钟。
fun drain(): ArrayList<MessageDto> {
val bulkData = ArrayList<MessageDto>()
Queues.drain(queue, bulkData, Int.MAX_VALUE, 30, TimeUnit.SECONDS)
return bulkData
}
fun add(message: MessageDto) {
queue.add(message)
}
}
复制代码
Hilo de alerta
Supervise la cola MonitorMessageQueue, agrupe y resuma los mensajes, y llame al servicio de envío para enviar mensajes
class DataSendThread(private val queue: MonitorMessageQueue, private val sendService: DataSendService) : Runnable {
private val sendCount = AtomicLong(0)
private val stop = false
private val logger = LoggerFactory.getLogger(DataSendThread::class.java)
override fun run() {
while (!stop) {
val list = queue.drain()
if (list.isNullOrEmpty()) {
logger.info("queue isEmpty")
return
}
val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val mid = UUID.randomUUID().toString().replace("-", "")
val stringBuilder = StringBuilder("[${format.format(System.currentTimeMillis())}][APP监控报警]")
stringBuilder.append("\n");
list.groupBy { it.moduleName }.map {
stringBuilder.append("${it.key}(${it.value.size}次)")
stringBuilder.append("\n")
stringBuilder.append(it.value.firstOrNull()?.content ?: "")
stringBuilder.append("\n")
}
stringBuilder.append("http://127.0.0.1/monitor/detail?mid=${mid}")
sendService.send(stringBuilder.toString())
logger.info("send success:${sendCount.addAndGet(1)}")
}
}
}
复制代码
Servicio de envío de datos
Procese la firma y llame al Webhook para enviar la información de la alarma al chat grupal de DingTalk
interface DataSendService {
fun send(content: String)
}
@Service
class DataSendServiceImpl : DataSendService {
@Autowired
private lateinit var restTemplate: RestTemplate
private fun url(timestamp: Long, sign: String): String {
return "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxx×tamp=${timestamp}&sign=$sign"
}
override fun send(content: String) {
val timestamp = System.currentTimeMillis()
println(
restTemplate.postForObject(
url(timestamp, calcSign(timestamp)), mapOf(
"msgtype" to "text",
"text" to mapOf(
"content" to content
)
),
String::class.java
)
)
}
private fun calcSign(timestamp: Long): String {
val secret = "xxxxxxx"
val stringToSign = """
$timestamp
$secret
""".trimIndent()
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(secret.toByteArray(charset("UTF-8")), "HmacSHA256"))
val signData = mac.doFinal(stringToSign.toByteArray(charset("UTF-8")))
return URLEncoder.encode(String(Base64.getEncoder().encode(signData)), "UTF-8")
}
}
复制代码
ejecutar casos de prueba
@SpringBootTest
internal class WarnServiceImplTest {
@Autowired
private lateinit var warnService: WarnService
@Test
fun reportErrorMsg() {
while (true) {
for (i in 1..((1000 * Math.random()).toInt())) {
warnService.reportErrorMsg("app-test1", "too many error")
}
for (i in (1..((1000 * Math.random()).toInt()))) {
warnService.reportErrorMsg("app-test2", "too many error")
}
Thread.sleep(1000 * 30)
}
}
}
复制代码