I used to shut my door while my mother screamed in the kitchen
I'd turn the music up, get high and try not to listen
To every little fight, 'cause neither one was right
I swore I'd never be like them
But I was just a kid back then
The older I get the more that I see
My parents aren't heroes, they're just like me
And loving is hard, it don't always work
You just try your best not to get hurt
I used to be mad but now I know
Sometimes it's better to let someone go
It just hadn't hit me yet
The older I get
I used to wonder why, why they could never be happy
I used to close my eyes and pray for a whole 'nother family
Where everything was fine, one that felt like mine
I swore I'd never be like them
But I was just a kid back then
The older I get the more that I see
My parents aren't heroes, they're just like me
And loving is hard, it don't always work
You just try your best not to get hurt
I used to be mad but now I know
Sometimes it's better to let someone go
It just hadn't hit me yet
The older I get
The older I get the more that I see
这天运维找我反馈说zgyh这个客户的redis内存越来越高,让我们先自查下是否有程序不合理的一直往redis写key。
在排查了java go python 和php 的代码后,我信心满满的给了他一个key的列表和统计计数的脚本
#!/bin/bash
A=$0
B=${A##*/}
C=${B%.*}
running_file_name=$C
running_flag="run.$running_file_name"
REDIS_CLIENT='redis-cli -a xxxx -x'
function process {
echo $0
index=-1
count=0
step=100000
while ((index!=0))
do
if [ $index -le 0 ];then
index=0
fi
echo "scan $index match $1 count $step" | $REDIS_CLIENT > $running_file_name.cache
read index <<< `head -1 $running_file_name.cache`
read inc <<< `cat $running_file_name.cache | wc -l`
inc=$((inc - 1))
if [ $? -ne 0 ];then
break
fi
count=$((count + inc))
done
echo "$1 count:"$count
}
#
if [ $# -ne 1 ];then
echo "$0 <pars>"
exit 0
fi
#
if [ -f "$running_flag" ] ; then
echo "is running..."
exit 0
fi
#
touch $running_flag
#
echo "processing...."
echo $*
process $*
#
rm -rf $running_flag
#
echo "ok!"
和查bigkey的方法
redis-cli -c -p 6379 -a 密码 --bigkeys
运维反馈bigkeys因为版本问题访问不了,根据我提供的业务key查的数量最大的也就W。但是现在生产的key已经达到了几百W。
过了几天,运维反馈说找到了那些幽灵的key,是32位的随机字幕+数字,没有赋值,最要命的是没有设置过期时间且确定是从我们的服务机器发出的请求。
再次走查代码,依旧没有头绪,测试环境的redis环境也没有这些奇怪的key。殚思竭虑两天后,猛地想起这个数据格式和php的session_id很类似。于是检查php的入口方法,果然找到问题。
$time = $this->isSession();
if ($this->skipCheck) return true;
这个isSession方法
protected function isSession() {
if ($this->AccessField == 'FILE') {
return $this->isFSession();
} else {
return $this->isRSession();
}
}
在类型不是FILE的情况下会走isRSession
private function isRSession() {
$PHPSESSID = $_COOKIE['PHPSESSID'];
if (empty($PHPSESSID)) {
return;
}
$data = S($PHPSESSID);
if (empty($data)) {
return;
}
/*从缓存服务器同步到本地*/
if ($data['SYNCH']) {
$data = array(
"DATA" => $data['DATA'],
"EXPIRE" => time(),
"REGIST" => time()
);
$data['SYNCH'] = false;
/*更新缓存服务器状态*/
}
if (!empty($_SESSION[$this->AccessName])) {
/*已同步到现在的服务器上*/
return $_SESSION[$this->AccessName]['EXPIRE'];
}
}
问题就出在S($PHPSESSID)这句,S方法内容如下
function S($name,$value='',$options=null) {
static $cache = '';
if(strtoupper(C('DATA_CACHE_TYPE')) == 'REDISMS'){
$cache = Think\Cache\Driver\Redisms::getInstance();
if(''=== $value){ // 获取缓存
return $cache->get($name);
}elseif(is_null($value)) { // 删除缓存
return $cache->rm($name);
}else{
if(is_array($options)) {
$expire = isset($options['expire'])?$options['expire']:NULL;
}else{
$expire = is_numeric($options)?$options:NULL;
}
return $cache->set($name, $value, $expire);
}
}
if(is_array($options)){
// 缓存操作的同时初始化
$type = isset($options['type'])?$options['type']:'';
$cache = Think\Cache::getInstance($type,$options);
}elseif(is_array($name)) { // 缓存初始化
$type = isset($name['type'])?$name['type']:'';
$cache = Think\Cache::getInstance($type,$name);
return $cache;
}elseif(empty($cache)) { // 自动初始化
$cache = Think\Cache::getInstance();
}
if(''=== $value){ // 获取缓存
return $cache->get($name);
}elseif(is_null($value)) { // 删除缓存
return $cache->rm($name);
}else { // 缓存数据
if(is_array($options)) {
$expire = isset($options['expire'])?$options['expire']:NULL;
}else{
$expire = is_numeric($options)?$options:NULL;
}
return $cache->set($name, $value, $expire);
}
}
坏就坏在这边没有缓存会去set Memcache.class.php创建缓存的代码;
public function write($sessID, $sessData) {
return $this->handle->set($this->sessionName.$sessID, $sessData, 0, $this->lifeTime);
}
$this->lifeTime取得
protected $lifeTime = 3600;
protected $sessionName = '';
protected $handle = null;
/**
* 打开Session
* @access public
* @param string $savePath
* @param mixed $sessName
*/
public function open($savePath, $sessName) {
$this->lifeTime = C('SESSION_EXPIRE') ? C('SESSION_EXPIRE') : $this->lifeTime;
好死不死,配置中SESSION_EXPIRE是0。所以改配置中SESSION_EXPIRE,确保后续数据的及时清理。
但是之前redis中key怎么清理呢。找了下轮子没有现成的,写了一个python 的脚本
import redis
# 匹配test:开头[0-9]结尾的key
prefix = '*'
conn = redis.Redis(host = '172.20.199.5', port=6379, password="***")
i = 0
s = 0
for key in conn.scan_iter(match=prefix, count=1000):
ttltime = conn.ttl(key)
i = i + 1
if( (ttltime is None or ttltime == -1) and len(key) == 32 and conn.type(key).decode() == 'string' and not conn.get(key)):
# 删除匹配到的没有过期时间的key
print(key, ' - value =', conn.get(key))
print(key, ' - key =', conn.type(key))
conn.delete(key)
s = s + 1
print(key, ' - del number=', s)
print('total key', i,' -> del ',s)
完美。今天小雪 晚来天欲雪,能饮一杯无?