文章851
标签121
分类10

Redis 高效删除大key

一、大key的删除问题

大key(bigkey)是指 key 的 value 是个庞然大物,例如 Hashes, Sorted Sets, Lists, Sets,日积月累之后,会变得非常大,可能几十上百MB,甚至到GB。

如果对这类大key直接使用 del 命令进行删除,会导致长时间阻塞,甚至崩溃。

因为 del 命令在删除集合类型数据时,时间复杂度为 O(M),M 是集合中元素的个数。

Redis 是单线程的,单个命令执行时间过长就会阻塞其他命令,容易引起雪崩。

二、解决方案

可靠方案:

渐进式删除
UNLINK (4.0版本以后)

1. 渐进式删除

思路:

分批删除,通过 scan 命令遍历大key,每次取得少部分元素,对其删除,然后再获取和删除下一批元素。

示例:

删除大 Hashes

步骤:

(1)key改名,相当于逻辑上把这个key删除了,任何redis命令都访问不到这个key了
(2)小步多批次的删除

伪代码:

# key改名
newkey = "gc:hashes:" + redis.INCR( "gc:index" )
redis.RENAME("my.hash.key", newkey)

# 每次取出100个元素删除
cursor = 0
loop
  cursor, hash_keys = redis.HSCAN(newkey, cursor, "COUNT", 100)
  if hash_keys count > 0
    redis.HDEL(newkey, hash_keys)
  end
  if cursor == 0
    break
  end
en
删除大 Lists

伪代码:

# key改名
newkey = "gc:hashes:" + redis.INCR("gc:index")
redis.RENAME("my.list.key", newkey)

# 删除
while redis.LLEN(newkey) > 0
  redis.LTRIM(newkey, 0, -99)
end
删除大 Sets

伪代码:

# key改名
newkey = "gc:hashes:" + redis.INCR("gc:index")
redis.RENAME("my.set.key", newkey)

# 每次删除100个成员
cursor = 0
loop
  cursor, members = redis.SSCAN(newkey, cursor, "COUNT", 100)
  if size of members > 0
    redis.SREM(newkey, members)
  end
  if cursor == 0
    break
  end
end
删除大 Sorted Sets

伪代码:

# key改名
newkey = "gc:hashes:" + redis.INCR("gc:index")
redis.RENAME("my.zset.key", newkey)

# 删除
while redis.ZCARD(newkey) > 0
  redis.ZREMRANGEBYRANK(newkey, 0, 99)
end

2. UNLINK

Redis 4.0 推出了一个重要命令 UNLINK,用来拯救 del 删大key的困境。

UNLINK 工作思路:

(1)在所有命名空间中把 key 删掉,立即返回,不阻塞。

(2)后台线程执行真正的释放空间的操作。

UNLINK 基本可以替代 del,但个别场景还是需要 del 的,例如在空间占用积累速度特别快的时候就不适合使用UNLINK,因为 UNLINK 不是立即释放空间。

三、总结

使用 del 删除大key可能会造成长时间阻塞,甚至崩溃。
可以使用渐进式删除,对 Hashes, Sorted Sets, Lists, Sets 分别处理,思路相同,先逻辑删除,对key改名,使客户端无法使用原key,然后使用批量小步删除。
4.0版本以后可以使用 UNLINK 命令,后台线程释放空间。

原文地址 : https://cloud.tencent.com/developer/article/1530197

Mysql 使用全文索引(FullText index)

简单介绍

MySQL全文索引是在基于文本的列(char、varchar或text列)上创建的,以提升对这些列中包含的数据的查询和DML操作效率。全文索引被定义为create table语句的一部分,或者使用alter table或create index追加到现有表中。搜索语法为:match(field1,field2,…) against(’keyword‘),更多使用方法及详细信息参考 MySQL全文索引官方文档

版本说明

MySQL 5.6 以前的版本,只有 MyISAM 存储引擎支持全文索引

MySQL 5.6 及以后的版本,MyISAMInnoDB 存储引擎均支持全文索引

只有字段的数据类型为 charvarchartext 及其系列才可以建全文索引

创建全文索引

说明:搜索时,默认对中文类分词支持不友好,如果有中日韩文分词需求,在创建索引时,需要声明该分词插件ngram,语法可选【with parser ngram】

建表时创建

create table product (
    id int(11) NOT null auto_increment,
    desc text NOT null,
    tag varchar(255),
    primary key (id),
    // 创建复合全文索
    FULLTEXT key fidx_desc_tag(desc,tag) with parser ngram
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci;

create index方式创建

create fulltext index fidx_desc_tag on product(desc,tag) with parser ngram;

alter table方式创建

alter table product add fulltext index fidx_desc_tag(desc,tag) with parser ngram;

删除全文索引

drop index方式删除

drop index fidx_desc_tag on product;

alter table方式删除

alter table product drop index fidx_desc_tag;

完整使用语法

select * from product where match(desc,tag) against('老汉推车到山前必有路');

提示:match() 函数中指定的列务必与索引中指定的列完全一致,否则将无法使用全文索引,全文索引不会记录关键字来自于哪一列。

发现问题

插入9条测试数据

insert into product(desc,tag) values 
('j'),('ja'),('java'),
('g'),('go'),('golang'),
('p'),('py'),('python');

假设搜索关键字为"j",搜索结果如下

select * from test where match(content) against('j');     // 0条
select * from test where match(content) against('ja');    // 0条
select * from test where match(content) against('jav');   // 1条(InnoDB存储引擎下,这条可以查出)
select * from test where match(content) against('java');  // 1条

从结果看出,只有输入“java”才匹配到了一条记录,有了解过ElasticSearchLuceneSolrMeiliSearch等搜索中间件的大佬,对这样的结果属实是不满意的,预期的结果应该查出3条记录才正常,导致这种情况其中非常关键的一个原因是MySQL对应执行引擎下全文索引的【最小搜索长度】,MySQL 中的全文索引,有两个关键的变量,分别是最小搜索长度和最大搜索长度,对于长度小于最小搜索长度和大于最大搜索长度的词,都不会被索引。换言之,这个词的长度必须在以上两个变量的区间内。

通过命令可以查看

show variables like '%ft%';
----------------------------
// InnoDB
innodb_ft_min_token_size = 3;
innodb_ft_max_token_size = 84;
// MyISAM
ft_min_word_len = 4;
ft_max_word_len = 84;

可以看到最小搜索长度InnoDB 引擎下默认是3,MyISAM引擎下默认是4,因此MySQL 的全文索引只会对长度大于等于3或者4的词建立索引,而前面基于InnoDB,搜索的只有“jav”和“java”的长度大于等于 3,而基于MyISAM,搜索也只有“java”的长度大于等于4。

解决问题

既然最小搜索长度不满足要求,就需要修改配置,在/etc/my.cnf下的mysqlId中追加内容如下,当然也可以修改最大搜索长度,但没必要

innodb_ft_min_token_size = 1
ft_min_word_len = 1

完事儿后重启 MySQL 服务器,并修复全文索引,修复全文索语句如下,不过一般建议删除索引,并重建索引

repair table product quick;

到这里为止,也依然没有达到最终的效果,搜索关键字“j”时,“ja”,“jav”,"java"并没有出现结果集中,因为MySQL的全文索引有自然语言的全文索引和布尔全文索引两种选择:

1、自然语言全文索引

默认情况下,或者使用 in natural language mode 修饰符时,match() 函数对文本集合执行自然语言搜索,上面的例子都是基于自然语言的全文索引。自然语言搜索引擎将计算每一个文档对象和查询的相关度。这里,相关度是基于匹配的关键词的个数,以及关键词在文档中出现的次数。在整个索引中出现次数越少的词语,匹配时的相关度就越高。相反,非常常见的单词将不会被搜索,如果一个词语的在超过 50% 的记录中都出现了,那么自然语言的搜索将不会搜索这类词语。这个机制也比较好理解,比如,一个数据表存储的是一篇篇的文章,文章中的常见词、语气词等等,出现的频率非常多,搜索这些词语是没有意义的,搜索具有文章特性的词,这样才能把文章区分开来。

2、布尔全文索引

在布尔搜索中,可以在查询中自定义某个被搜索的词语的相关性,当编写一个布尔搜索查询时,可以通过一些前缀修饰符来定制搜索。MySQL内置的修饰符,上面查询最小搜索长度时,搜索结果ft_boolean_syntax变量的值就是内置的修饰符,相关修饰符具体的作用可以看看手册。

上面的情况,可以通过布尔满足,根据关键字“j”,查到“j”,“ja”,“jav”,“java”,如下

select * product where match(desc,tag) against('j*' in boolean mode);

总结

MySQL全文索引起初仅支持英文,因为英文的词与词之间有空格,使用空格作为分词的分隔符是很方便的。亚洲文字,比如汉语、日语、汉语等,是没有空格的,这就造成了一定的限制。不过 MySQL 5.7.6 开始,引入了一个 ngram 全文分析器来解决这个问题,并且对 MyISAMInnoDB 引擎都有效。
事实上,MyISAM 存储引擎对全文索引的支持有很多的限制,例如表级别锁对性能的影响、数据文件的崩溃、崩溃后的恢复等,这使得 MyISAM 的全文索引对于很多的应用场景并不适合。所以,多数情况下的建议是使用别的解决方案,例如MeiliSearchElasticSearch等等第三方的插件,亦或是使用InnoDB存储引擎的全文索引。

注意

1、使用全文索引前,明确版本支持情况
2、全文索引比like + %快N倍,但是可能存在精度问题
3、如果需要全文索引的是大量数据,建议先添加数据,再创建索引
4、对于中文,可以使用 MySQL 5.7.6之后的版本,或者第三方插件

原文链接:https://blog.csdn.net/weixin_38264394/article/details/116699506

PHP 捕获Fatal error错误与异常处理

php中的错误和异常是两个不同的概念。

错误:是因为脚本的问题,比如少写了分号,调用未定义的函数,除0,等一些编译语法错误。

异常:是因为业务逻辑和流程,不符合预期情况,比如验证请求参数,不通过就用 throw new 抛一个异常。

在php5的版本中,错误是无法被 try {} catch 捕获的,如下所示:

<?php

error_reporting(E_ALL);
ini_set('display_errors', 'on');

try {
    hello();
} catch (\Exception $e) {
    echo $e->getMessage();
}

运行脚本,最终php报出一个Fatal error,并程序中止。

Fatal error: Uncaught Error: Call to undefined function hello() 

有些时候,我们需要捕获这种错误,并做相应的处理。

那就需要用到 register_shutdown_function()error_get_last() 来捕获错误

<?php

error_reporting(E_ALL);
ini_set('display_errors', 'on');


//注册一个会在php中止时执行的函数
register_shutdown_function(function () {
    //获取最后发生的错误
    $error = error_get_last();
    if (!empty($error)) {
        echo $error['message'], '<br>';
        echo $error['file'], ' ', $error['line'];
    }
});

hello();

我们还可以通过 set_error_handler() 把一些DeprecatedNoticeWaning等错误包装成异常,让 try {} catch 能够捕获到。

<?php

error_reporting(E_ALL);
ini_set('display_errors', 'on');

//捕获Deprecated、Notice、Waning级别错误
set_error_handler(function ($errno, $errstr, $errfile) {
    throw new \Exception($errno . ' : ' . $errstr . ' : ' . $errfile);
    //返回true,表示错误处理不会继续调用
    return true;
});

try {
    $data = [];
    echo $data['index'];
} catch (\Exception $e) {
    //捕获Notice: Undefined index
    echo $e->getMessage();
}

对于php7中的错误捕获,因为php7中定义了 Throwable 接口,大部分的 ErrorException 实现了该接口。

所以我们在php7中,可以通过 try {} catch(\Throwable $e) 来捕获php5中无法捕获到的错误。

<?php

error_reporting(E_ALL);
ini_set('display_errors', 'on');

try {
    hello();
} catch (\Throwable $e) {
    echo $e->getMessage();
} 

Redis 抛错 : send of 31 bytes failed with errno=32 Broken pipe

Notice: Redis::get(): send of 31 bytes failed with errno=32 Broken pipe in 

启两个进程 调研 redis::get

测试 自己本机测试 配置最大客户端连接数 CONFIG set maxclients 2

错误消息 "send of 31 bytes failed with errno=32 Broken pipe" 表明在尝试发送数据时,管道 (pipe) 被中断了。这通常是由于以下几个原因导致的:

  1. 网络连接问题:可能是网络连接不稳定或断开导致的。检查网络连接,确保网络正常并且没有断开。
  2. 资源限制:可能是由于系统资源限制而导致的。当系统中的某些资源(如文件描述符、内存等)达到了限制时,可能会出现此错误。可以通过增加系统资源限制或优化代码来解决。
    `
  3. 程序错误:可能是代码中存在错误或逻辑问题导致的。检查代码逻辑,确保没有错误或异常情况。

针对这个错误,你可以尝试以下几个解决方法:

  1. 检查网络连接:确保网络连接正常,并且没有任何网络问题。
  2. 检查资源限制:查看系统资源限制,特别是文件描述符和内存限制。如果达到了限制,可以增加相应的资源限制。
  3. 优化代码:检查代码逻辑,确保没有错误或异常情况。确保代码中没有意外的文件关闭操作或其他可能导致管道中断的问题。
  4. 增加超时处理:如果发送数据的操作耗时较长,可以考虑增加超时处理机制,确保在超时时间内完成发送操作,避免管道中断。
  5. 分段发送数据:如果发送的数据量较大,可以尝试将数据分成多个较小的部分进行发送,以减少一次发送的数据量,降低出错的可能性。

请根据具体情况逐一尝试上述解决方法,以修复 "send of 31 bytes failed with errno=32 Broken pipe" 错误。如果问题仍然存在,请提供更多的上下文和错误信息,以便更准确地定位和解决问题。

Mysql 批量替换数据库中某个字段内容的sql语句

近年来由于我国新广告法的实施,大量企业网站充斥着违法违规敏感广告词,导致企业被市场监查部门罚款,那么我们企业网站中存在的违法违规敏感广告词可不可以一键替换呢?答案是可以的,下面我们以织梦dedecms程序为例,如何利用织梦数据SQL命令语句一键替换。
例如织梦数据库中某个字段包含有违法违规敏感广告词,我们需要把这些广告词进行替换,大兵给大家提供一段SQL命令语句:

UPDATE `表名` SET `字段名` = replace (`字段名`,’需要被替换字符’,’将要替换成的字符’);

例如:我要将body字段中所有“最”,全部替换为“较”,如下图所示:
表名:zmy_addonarticle
字段名称:body

点击SQL命令,输入以下SQL命令语句:

update zmy_addonarticle set body=replace(body,’最’,’较’)

最后点击左下角的“执行”命令,body字段中所有“最”,就全都被替换为“较”了,最后在织梦网站后台更新全站内容,再看看前台页面内容变了没有。

">