Redis 数据库是一个基于内存的 key-value 存储系统,现在 Redis 最常用的使用场景就是存储缓存用的数据,在需要高速读写的场合使用它快速读写,从而缓解应用数据库的压力,进而提升应用处理能力。许多数据库会提供慢查询日志帮助开发和运维人员定位系统存在的慢操作。所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阀值,就将这条命令的相关信息(例如:发生时间、耗时、命令的详细信息)记录下来,Redis 也提供了类似的功能。
本文将基于 Amazon 托管的服务来介绍如何展示和分析大规模 Redis 集群慢日志。
架构说明
通过 Amazon Event Bridge 产生定时任务(1分钟)触发 Amazon Lambda 执行,Lambda 通过 Amazon 的 sdk 找到所有 Redis 的节点信息并一个个连接索取慢日志信息。 通过慢日志自带的时间戳过滤出最近1分钟产生的日志并上传到 RDS 数据库保存,最终通过托管的 Amazon Grafana 展示并统计。
创建 RDS 或者 Amazon Aurora 数据库并初始化表结构
在控制台创建 RDS (过程略),要注意需要开公网访问,因为托管的 Grafana 需要通过公网访问 MySQL 的数据源。这里会有安全的隐患,如果有自建的能力,可以考虑用 VPC 内自建的 Grafana 代替,或者开 case 问 Amazon 后台托管的 Grafana IP 地址范围,通过 RDS 安全组做限制访问并配置复杂的数据库用户名密码。
创建好表结构 slowlog.detail:
MySQL [slowlog]> show create table detail \G
*************************** 1. row ***************************
Table: detail
Create Table: CREATE TABLE `detail` (
`id` bigint(40) NOT NULL AUTO_INCREMENT,
`nodeid` varchar(100) NOT NULL,
`endpoint` varchar(100) NOT NULL,
`slowlog_time` int(20) NOT NULL,
`today` date NOT NULL,
`command` varchar(100) NOT NULL,
`Redis_key` varchar(100) NOT NULL,
`duration` int(10) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_nodeid_time` (`nodeid`,`slowlog_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
左滑查看更多
创建测试用的 Redis 实例
过程略
创建 Lambda 函数
代码如下:
import Redis
import boto3
import os,sys
import json
import time
from datetime import datetime,timedelta
import PyMySQL
import socket
import struct
# get system environment
AWS_REGION=os.environ['AWS_REGION']
DB_HOST=os.environ['db_host']
DB_DataBase=os.environ['db_database']
DB_PW=os.environ['db_pw']
DB_USER=os.environ['db_user']
PORT=int(os.environ['port'])
# get current time from internet
# time.time() 方式的时间不准
def RequestTimefromNtp(addr='0.de.pool.ntp.org'):
REF_TIME_1970 = 2208988800 # Reference time
client = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
data = b'\x1b' + 47 * b'\0'
client.sendto( data, (addr, 123))
data, address = client.recvfrom( 1024 )
if data:
t = struct.unpack( '!12I', data )[10]
t -= REF_TIME_1970
return t
# get Redis client
def aws_client():
client = boto3.client(
'elasticache',
region_name=AWS_REGION,
)
return client
# get all Redis cluster list
def get_Redis_cluster_list(client):
clusters_metas = client.describe_cache_clusters()['CacheClusters']
clusters = []
for item in clusters_metas:
clusters.append(item['ReplicationGroupId'])
cluster_list = set(clusters)
return cluster_list
# get each Redis cluster meta data
def get_Redis_meta(client,groupid):
info={}
response=client.describe_replication_groups(
ReplicationGroupId=groupid)
NodeGroupMembers=response['ReplicationGroups'][0]['NodeGroups'][0]['NodeGroupMembers']
for item in NodeGroupMembers:
k = item['CacheClusterId']
v = item['ReadEndpoint']['Address']
info[k] = v
return info
# get slow log every 1 min
# 本次测试环境密码为空
def get_slow_log_info(endpoint,nodeid,date,port='',pw=''):
now=int(RequestTimefromNtp())
last=int(now - 60)
r=Redis.Redis(host=endpoint)
slow_data = r.slowlog_get()
result=[]
for item in slow_data:
if (item['start_time']>= last and item['start_time']<= now ):
try:
command_type = str(item['command']).split('\'')[1].split()[0]
except Exception as err:
command_type = "null"
try:
command_key = str(item['command']).split('\'')[1].split()[1]
except Exception as err:
command_key = "null"
try:
duration = int(item['duration'])
except Exception as err:
duration = 0
data = {
'nodeid' : nodeid,
'endpoint' : endpoint,
'slowlog_time' : int(item['start_time']) ,
'today' : str(date),
'command' : command_type,
'key' : command_key,
'duration' : duration,
}
result.append(data)
return result
# upload data to RDS
def upload_logs(result,DB_HOST,DB_USER,DB_PW,DB_DataBase,PORT):
db = PyMySQL.connect(host=DB_HOST,
user=DB_USER,
password=DB_PW,
database=DB_DataBase,
port=PORT,
cursorclass=PyMySQL.cursors.DictCursor)
cur = db.cursor()
for item in result:
nodeid=item['nodeid']
endpoint=item['endpoint']
slowlog_time=item['slowlog_time']
today=item['today']
command=item['command']
Redis_key=item['key']
duration=item['duration']
SQL = "insert into slowlog.detail (nodeid,endpoint,slowlog_time,today,command,Redis_key,duration) values('%s','%s',%d,'%s','%s','%s',%d)" % \
(nodeid,endpoint,slowlog_time,today,command,Redis_key,duration)
cur.execute(SQL)
db.commit()
db.close()
def lambda_handler(event, context):
date = datetime.now().strftime("%Y-%m-%d")
client=aws_client()
cluster_lists=get_Redis_cluster_list(client)
for item in cluster_lists:
info = get_Redis_meta(client,item)
for k in info.keys():
result=[]
result=get_slow_log_info(info[k],k,date)
print(result)
upload_logs(result,DB_HOST,DB_USER,DB_PW,DB_DataBase,PORT)
左滑查看更多
Python 代码依赖 Redis 和 PyMySQL 包,操作步骤如下:
1) 在本地 Linux 环境(x86)安装 Redis 和 PyMySQL 两个包,建议通过 virtual env 安装, 如果是 通过 pip 安装则包的参考路径为: /usr/local/lib/python3.7/site-packages;
2) 把 Redis 目录 和 PyMySQL 目录拷贝到临时目录/tmp;
3) 下载上面的代码到/tmp;
4) 打包 zip -r lambda.zip PyMySQL Redis lambda_function.py;
5) 在控制台通过 S3 或者本地上传。
配置 Lambda 函数
1)给 Lambda 一个执行的 Role,Policy 见下图:
2)配置环境变量
演示为了方便,使用 Lambda 环境变量存数据库访问连接串,建议生产环境中使用 Secret Manager 存取;
3) 配置 Lambda 使用 VPC 下的 Private 子网(确保子网配置了 NAT-gw)。
配置 EventBridge
打开控制台,跳转到 EventBridge ,新建一个 Rule;
Target 选中我们的 Lambda 函数。
配置 Grafana
1)首先配置一个新的 workspace;
2)使用托管的 SSO 来配置用户密码;
记录下 URL:
3)到 SSO 创建用户;
4)添加 SSO 用户到 Grafana;
5)配置数据源。
测试方案
1)修改 Redis 参数,确保能记录慢日志
修改 slowlog-log-slower-than = 1;
2)使用 Redis-benchmark 模拟真实 workload
过程可以观察 lambda 的日志输出或者登陆数据库查看是否有数据插入。
./Redis-benchmark -h test-cluster-1-001.t5tzux.0001.apse1.cache.amazonaws.com -n 1000000 -c 20 -t hset,lpush,rpush,sadd,zadd,set,get -q > test1.log &
./Redis-benchmark -h test-cluster-2-001.t5tzux.0001.apse1.cache.amazonaws.com -n 1000000 -c 20 -t hset,lpush,rpush,sadd,zadd,set,get -q > test2.log &
./Redis-benchmark -h test-cluster-3-001.t5tzux.0001.apse1.cache.amazonaws.com -n 1000000 -c 20 -t hset,lpush,rpush,sadd,zadd,set,get -q > test3.log &
./Redis-benchmark -h test-cluster-4-001.t5tzux.0001.apse1.cache.amazonaws.com -n 1000000 -c 20 -t hset,lpush,rpush,sadd,zadd,set,get -q > test4.log &
左滑查看更多
3) 在 Grafana 作图观察分析
第一条 SQL (Slow_log_details) 是看日志明细:
SELECT
slowlog_time AS "time",
nodeid,
endpoint,
command,
Redis_key,
duration as "duration(ms)"
FROM detail
WHERE
$__unixEpochFilter(slowlog_time)
ORDER BY slowlog_time
左滑查看更多
第二条 SQL (slow_log_trend) 查看总体趋势分析:
SELECT
from_unixtime(slowlog_time) as time,
count(*) as cnt,
nodeid
FROM detail
group by time,nodeid
左滑查看更多
4) 查看效果图
参考文档
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/elasticache.html
本篇作者
张振威
亚马逊云科技 APN 解决方案架构师,主要负责合作伙伴架构咨询和方案设计,同时致力于亚马逊云科技云服务在国内的应用及推广。曾就职于互联网公司数据库团队,拥有丰富的平台化和自动化运维经验。
听说,点完下面4个按钮
就不会碰到bug了!