千万级数据并发解决方案(理论+实战)

课程地址

项目地址

场景

秒杀 高并发

新闻系统 超大数据量

一般的网站 写入的少 读取的次数多

模糊查询
数据量少的时候可以用 like

数据量多的时候用 Elasticsearch搜索引擎 占用磁盘空间比较大

生成数据

SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `article_tmp`;
CREATE TABLE `article_tmp` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `url` varchar(255) NOT NULL,
  `descs` varchar(255) NOT NULL,
  `author` varchar(255) NOT NULL,
  `add_time` varchar(255) NOT NULL,
  `from` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=201 DEFAULT CHARSET=utf8;

INSERT INTO `article_tmp` VALUES ('1', '模型驱动设计的构造块(下)——DDD', 'https://www.cnblogs.com/afei-24/p/16985996.html', '3. 领域对象的生命周期 每个对象都有生命周期,如下图所示。对象自创建后,可能会经历各种不同的状态,直至最终消亡——要么存档,要么删除。当然很多对象是简单的临时对象,仅通过调用构造函数来创建,用来做一些计算,然后由垃圾收集器回收。这类对象没必要搞得那么复杂。但有些对象具有更长的生命周期,其中一部分时 ...', 'Ruby_Lu', '2023-01-06 07:22', 'https://www.cnblogs.com');
INSERT INTO `article_tmp` VALUES ('2', 'IT编程相关内容汇总 - 进阶者系列 - 学习者系列文章', 'https://www.cnblogs.com/lzhdim/p/17028789.html', '笔者工作了十多年了,对于技术也有一定的经验,但是IT编程技术的更新是挺快的,特别是各种框架,各种中间件啥的都涌现出来了。这篇博文笔者打算将IT编程的前端、后端、数据库和移动端做一个博文知识汇总,让阅读笔者博客的读者能够有一个系统化学习编程技术的博文。前面已经有一个博文进行过相关的介绍,但是那个比较普 ...', 'lzhdim', '2023-01-06 00:00', 'https://www.cnblogs.com');
INSERT INTO `article_tmp` VALUES ('3', '小白致力于成为前后端开发程序员', 'https://www.cnblogs.com/pocn/p/16857245.html', '小白有个烦恼,做前端项目的时候,遇到两种情况一种是在vue框架下,使用HTML写页面,script部分代码里面的方法基本上使用JS来写;一种同样在vue框架下,通过安装的框架来构建页面,script中使用的方法也多是安装的框架中封装好的方法。小白是个倒霉催的孩子,负责的项目比较多,常常在两种情况下切 ...', 'BOBO~', '2023-01-05 22:51', 'https://www.cnblogs.com');
INSERT INTO `article_tmp` VALUES ('4', 'BST查找结构与折半查找方法的实现与实验比较', 'https://www.cnblogs.com/Az1r/p/17028980.html', '简介 作业:查找结构与排序方法 作业题目: BST 查找结构与折半查找方法的实现与实验比较 要求编写程序实现 BST 存储结构的建立(插入)、删除、查找和排序算法; 实现折半查找算法;比较 BST 查找与折半查找方法的时间性能。 作业要求: 1. 设计 BST 的左右链存储结构,并实现 BST 插入 ...', '江水为竭', '2023-01-05 22:12', 'https://www.cnblogs.com');
INSERT INTO `article_tmp` VALUES ('5', 'Java开发学习(五十)----MyBatisPlus快速开发之代码生成器解析', 'https://www.cnblogs.com/xiaoyh/p/16468217.html', '1、代码生成器原理分析 造句: 我们可以往空白内容进行填词造句,比如: 在比如: 观察我们之前写的代码,会发现其中也会有很多重复内容,比如: 那我们就想,如果我想做一个Book模块的开发,是不是只需要将红色部分的内容全部更换成Book即可,如: 所以我们会发现,做任何模块的开发,对于这段代码,基本上 ...', '|旧市拾荒|', '2023-01-05 21:57', 'https://www.cnblogs.com');
INSERT INTO `article_tmp` VALUES ('6', 'VMware搭建内网渗透环境', 'https://www.cnblogs.com/LeslieSec/p/17028722.html', '网络结构: 攻击机:kali 192.168.1.103 DMZ区域:防火墙 WAN:192.168.1.104 LAN:192.168.10.10 winserver03 LAN:192.168.10.11 用户办公区域:路由器 WAN:192.168.10.20 LAN:192.168.20.1 ...', '林烬', '2023-01-05 21:21', 'https://www.cnblogs.com');
INSERT INTO `article_tmp` VALUES ('7', '自动增长配置不合理导致的性能抖动', 'https://www.cnblogs.com/zhuancloud/p/17028870.html', '背景 客户收到了SQL专家云告警邮件,在凌晨2点到3点之间带有资源等待的会话数暴增,请我们协助分析。 现象 登录SQL专家云,进入活动会话的趋势分析页面,下钻到2点钟一个小时内的数据,看到每分钟的等待数都在100左右,2点15分时达到200。 转到活动会话原始数据页面,看到大量会话都在等待,等待类型 ...', '格瑞趋势技术团队', '2023-01-05 21:07', 'https://www.cnblogs.com');
INSERT INTO `article_tmp` VALUES ('8', 'C | 指针', 'https://www.cnblogs.com/lijiuliang/p/17028849.html', '1.什么是指针 指针是一种变量,也称指针变量,它的值不是整数、浮点数和字符,而是内存地址。指针的值就是变量的地址,而变量有拥有一个具体值。因此,可以理解为变量直接引用了一个值,指针间接地引用了一个值。一个存放变量地址的类型称为该变量的“指针”。 指针变量的大小? 以32位系统为例,每个字节(即一个内 ...', '就良同学', '2023-01-05 20:54', 'https://www.cnblogs.com');
INSERT INTO `article_tmp` VALUES ('9', '07.synchronized都问啥?', 'https://www.cnblogs.com/wyz1994/p/17028834.html', '大家好,我是王有志。关注王有志,一起聊技术,聊游戏,从北漂生活谈到国际风云。最近搞了个抽奖送书的活动,欢迎点击链接参与。 如果Java面试有什么是必问的,synchronized必定占据一席之地。初出茅庐时synchronized的用法,成长后synchronized的原理,可谓是Java工程师的“ ...', '王有志', '2023-01-05 20:48', 'https://www.cnblogs.com');
INSERT INTO `article_tmp` VALUES ('10', 'JavaScript 中如何拦截全局 Fetch API 的请求和响应?', 'https://www.cnblogs.com/wenruo/p/17028832.html', '本文翻译自 Intercepting JavaScript Fetch API requests and responses 拦截器是可用于预处理或后处理 HTTP 请求的代码块,有助于全局错误处理、身份验证、日志记录等。在本文中,你将学习如何拦截 JavaScript Fetch API 请求。  ...', '我不吃饼干呀', '2023-01-05 20:47', 'https://www.cnblogs.com');
INSERT INTO `article_tmp` VALUES ('11', '.gitignore文件配置以及gitee提交报Push rejected...错误解决', 'https://www.cnblogs.com/wren/p/17028830.html', '.gitignore文件配置 .gitignore 文件可以用来忽略被指定的文件或文件夹的改动。记录在.gitignore文件里的文件或文件夹是不会被 git 跟踪到,也就是被忽略的文件是不会被上传到远程仓库的,如果文件已经存在于远程仓库中就无法通过.gitignore文件来忽略。 下面总结了一些可 ...', '请叫我阿杰', '2023-01-05 20:46', 'https://www.cnblogs.com');
INSERT INTO `article_tmp` VALUES ('12', 'Ventoy制作启动盘和使用VMware测试启动盘(论文版)', 'https://www.cnblogs.com/alittlemc/p/17027815.html', 'Ventoy是可用于制作启动U盘的开源工具,在占用少量引导分区容量后,其他空间依旧可以正常当一般的U盘读写文件。它的最大特点是只要将iso、win、img、efi等之类的镜像文件和引导文件移动到U盘中。比如导出微PE、杏雨梨云的可启动iso镜像文件,移动到U盘中,在启动时选择微PE、杏雨梨云任意一个... ...', 'alittlemc', '2023-01-05 20:45', 'https://www.cnblogs.com');
INSERT INTO `article_tmp` VALUES ('13', 'JUC并发编程详解(通俗易懂)', 'https://www.cnblogs.com/ZhangHao-Study/p/16994667.html', '一、JUC简介 在Java5.0提供了java.util.concurrent包,简称JUC,即Java并发编程工具包。JUC更好的支持高并发任务。 具体的有以下三个包: java.util.concurrent java.util.concurrent.atomic java.util.concu ...', '九八十', '2023-01-05 20:39', 'https://www.cnblogs.com');

在laravel中 创建
要提前配置好数据库

//创建新的命令类
php artisan make:command Tests
//创建后使用命令
php artisan app:tests

在Tests文件中写入

use DB;
public function handle()
{   
    echo Date('Y-m-d H-i-s')."\r\n";
    $lists = DB::table('article_tmp')->get()->toArray();

    for ($i = 0; $i < 10000000; $i++) {
        $randindexArr = array_rand($lists,2);
        $data['title'] = $data['descs'] = '';
        foreach($randindexArr as $val){
            $data['url']=$lists[$val]->url;
            $data['cid']=rand(1,50);
            $data['title'] .=mb_substr($lists[$val]->title,0,rand(2,16));
            $data['descs'] .=mb_substr($lists[$val]->descs,rand(0,16),rand(32,64));
            $data['author']=$lists[$val]->author;
            $data['add_time']=$lists[$val]->add_time;
            $data['from']=$lists[$val]->from;
        }
        DB::table('article')->insert($data);
    };
    echo Date('Y-m-d H-i-s')."\r\n";
}

运行命令 php artisan app:tests 生成数据

mysql查询原则

数据多 并发高

Alt text

字段越多 体积越大

  • 程序用到哪几个字段就查询哪几个字段,尽量避免select * 查询
  • 一次用多少数据,就返回多少数据:加limit限制
  • 尽量用 = ,and 不要用 >,< ,<>,or
  • 字段设计尽量 不为null

添加索引

  • 针对热点字段添加索引
  • 索引不一定是越多越好 因为会影响 insert,update,delete 的效率 (修改表数据的时候 ,mysql需要同步更新索引)
  • 索引最好在开发的时候创建好,尽量避免在生产环境中创建索引(数据量大的时候,创建耗时较长,会影响业务)

一旦发生 扫表 效率是最低的

EXPLAIN 查看执行计划
EXPLAIN SELECT * FROM article WHERE author = "就良同学" and cid = 50;

最重要的两个字段 possible_keys key 如果为空 说明发生扫表

possible_keys 可能会用都到的索引
key 实际会用到的索引
rows 预估值
select_type (SIMPLE) 简单查询

id select_type possible_keys key rows
1 SIMPLE cid,author author 700

大数据量下进行的查询 最好先查看执行计划

EXPLAIN SELECT * FROM article WHERE author = "就良同学" and cid = 50;

使用缓存

一个简单使用本地文件来当缓存

public function index()
{
    $data= '';
    $cache_file = 'article_cid.txt';
    if(!file_exists($cache_file)){
        //创建文件
        file_put_contents($cache_file,'');
    }
    //读取数据
    $data = file_get_contents($cache_file);
    if($data){
        $data= json_decode($data);
        dump($data);
        return ;
    }
    //没有数据
    $data=DB::table('article')->where('cid',21)->paginate(20);
    file_put_contents($cache_file,json_encode($data));
    dump($data);
}

缓存策略

旁路缓存策略

Cache-Aside

  • 读:
    • 先到缓存里面读数据,如果读到了就直接返回数据
    • 如果读不到数据,从数据库中读数据,然后存到 cache中
  • 写:
    • 方案一. 更新完数据后 ,删除缓存
    • 方案一. 更新完数据后,不删除缓存,直接更新它

缺陷1:首次请求 数据一定不在 cache里面 ,需要到数据库中查询一下.

解决方方案 : 可以提前把数据放到 cache里面 , '预热数据'

缺陷2:对于写操作比较频繁的数据,直接造成缓存命中率较低。这时候缓存的意义就不大了。

解决方案:

  1. 要么不缓存这种数据,要么换缓存策略
  2. 数据库数据和Cache中的数据不要求强一致:异步更新DB和Cache,
  3. 数据库数据和Cache中的数据必须强一致:

比如说 点赞量 浏览量 这些不是非常重要的数据,用异步来处理

读写穿透策略

Read/Write-Through

  • 读:
    • 从Cache读数据,如果读到数据就直接返回;如果读不到数据的话,从数据库中加载,然后写入到Cache里
  • 写:
    • 先查一下Cache,如果Cache中不存在的话,直接更新数据库;如果Cache中存在,先更新Cache,然后Cache自己更新数据库(相当于同步更新数据库和Cache)

缺点:参考【Cache-Aside(旁路缓存策略)】

异步缓存写入策略

Write-Behind

  • 写:只操作Cache,不操作数据库。什么时候写到数据库?异步写入数据库(可以写一个命令行程序定时把Cache中的数据更新到数据库)

应用场景:文章或商品的浏览量、点赞量、关注量等对数据的实时性要求不高的场景。

什么样的数据不适合放到Cache里?

  • 体积太大的数据不适合。(1、消耗内存。2、网络负载比较高(web和redis不在同一台机器上))
  • 频繁变动的数据,考虑一下是否需要放到缓存中

ab压力测试

apachebench的缩写

原理:ab程序会创建多个并发的访问线程,模拟多个访问者同时对某一个url进行访问。所以,它可以测试的服务器类型可以是apache,也可以是nginx、tomcat服务器,也可以是IIS服务器

注意:ab程序对发出负载的机器要求很低,也就是说它占用的cpu和内存非常低。但是因为它模拟大量的用户请求,所以会给目标服务器造成巨大的负载。比较类似CC攻击。所以我们自己测试的时候,注意控制好请求量

ab的安装

  • windows:apache的bin目录里的ab.exe
  • linux:apache的/usr/bin/ab
  • 只想用ab工具,不想安装Apache,怎么办?
1、服务器是windows server:在开发机上,到apache的bin目录下,拷贝ab.exe到服务器上就行。
2、服务器是centos:安装httpd-tools,yum -y install httpd-tools

ab的版本:ab -V

ab程序的参数说明

-n在测试会话中所执行的请求个数。默认时,仅执行一个请求。
-c一次产生的请求个数。默认是一次一个。
-t测试所进行的最大秒数。其内部隐含值是-n 50000,它可以使对服务器的测试限制在一个固定的总时间以内。默认时,没有时间限制。
-p包含了需要POST的数据的文件。
-P对一个中转代理提供BASIC认证信任。用户名和密码由一个:隔开,并以base64编码形式发送。无论服务器是否需要(即, 是否发送了401认证需求代码),此字符串都会被发送。
-T POST数据所使用的Content-type头信息。
-v设置显示信息的详细程度-4或更大值会显示头信息,3或更大值可以显示响应代码(404,200等),2或更大值可以显示警告和其他信息。
-V显示版本号并退出。
-w以HTML表的格式输出结果。默认时,它是白色背景的两列宽度的一张表。
-i执行HEAD请求,而不是GET。
-x设置<table>属性的字符串。
-X对请求使用代理服务器。
-y设置<tr>属性的字符串。
-z设置<td>属性的字符串。
-C对请求附加一个Cookie:行。其典型形式是name=value的一个参数对,此参数可以重复。
-H对请求附加额外的头信息。此参数的典型形式是一个有效的头信息行,其中包含了以冒号分隔的字段和值的对(如,"Accept-Encoding:zip/zop;8bit")。
-A对服务器提供BASIC认证信任。用户名和密码由一个:隔开,并以base64编码形式发送。无论服务器是否需要(即,是否发送了401认证需求代码),此字符串都会被发送。
-h显示使用方法。
-d不显示"percentage served within XX [ms] table"的消息(为以前的版本提供支持)。
-e产生一个以逗号分隔的(CSV)文件,其中包含了处理每个相应百分比的请求所需要(从1%到100%)的相应百分比的(以微妙为单位)时间。由于这种格式已经“二进制化”,所以比'gnuplot'格式更有用。
-g把所有测试结果写入一个'gnuplot'或者TSV(以Tab分隔的)文件。此文件可以方便地导入到Gnuplot,IDL,Mathematica,Igor甚至Excel中。其中的第一行为标题。
-i执行HEAD请求,而不是GET。
-k启用HTTP KeepAlive功能,即在一个HTTP会话中执行多个请求。默认时,不启用KeepAlive功能。
-q如果处理的请求数大于150,ab每处理大约10%或者100个请求时,会在stderr输出一个进度计数。此-q标记可以抑制这些信息。

ab测试结果

如:ab -c 10 -n 1000 http://lv.php.com/index/index

Concurrency Level:  10
模拟的客户端的数量(10个客户端)
Time taken for tests:   19.113 seconds
处理完1000个请求所话费的总时间
Complete requests:      1000
总共完成了多少个请求
Failed requests:        0
失败的请求数
Total transferred:      896000 bytes
所有请求的响应数据长度的总长度,包含每个http请求的头信息(header)和正文数据(body)
HTML transferred:       673000 bytes
所有请求数据的正文的长度
Requests per second:    52.32 [#/sec] (mean)
每秒钟执行的请求数---吞吐率。值越大越好
(第一个)Time per request:       191.131 [ms] (mean)
客户端平均每个请求的等待时间
(第二个)Time per request:       19.113 [ms] (mean, across all concurrent requests)
服务器端平均每个请求的等待时间
Transfer rate:          45.78 [Kbytes/sec] received
数据传输速率。值越大越好

Connection Times (ms)
	    	          min  mean[+/-sd] median   max
(连接时间)Connect:        0    0   0.4      0       1
(处理时间)Processing:    98  189 103.5    179    1342
(等待时间)Waiting:       98  189 103.5    179    1342
(总时间 )Total:         98  190 103.5    179    1342


Percentage of the requests served within a certain time (ms)
  50%    179
  66%    182
  75%    184
  80%    186
  90%    194
  95%    200
  98%    245
  99%    284
 100%   1342 (longest request)
 完成本次测试的时间分布,一般关注90%的处理时间

es扩展 (elasticsearch )

官网: https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html

文档:
https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/getting-started-php.html

软件下载: https://elasticsearch.cn/download/

扩展安装
composer require elasticsearch/elasticsearch

ElasticSearch重置密码步骤(忘记密码的情况)

1.停止Elasticsearch服务
2.编辑elasticsearch.yml文件,设置以下两项为false;
	xpack.security.enabled: false
	xpack.security.transport.ssl.enabled: false
3.重启es服务,删除.security-7索引
	curl -XDELETE -u elastic:changeme http://localhost:9200/.security-7
3.关闭ES服务设置以下两项为true;
	xpack.security.enabled: true
	xpack.security.transport.ssl.enabled: true
4.重启es服务,进入es的bin目录下
	./elasticsearch-setup-passwords interactive
	依次设置每个账号密码即可

获取密码:
进入es安装目录的bin文件中执行一下命令会返回最新的密码
命令 : elasticsearch-reset-password -u elastic
默认用户名: elastic
密码: QhW4NJSQGzP13o2kVI6o
修改密码的其他命令
https://devpress.csdn.net/cloud-native/65195642aae4e179c0b77cc4.html

开启各种安全验证后需要ssl证书才能正常登录

xpack.security.enabled: true
xpack.security.enrollment.enabled: true
xpack.security.http.ssl:
  enabled: true
  keystore.path: certs/http.p12
xpack.security.transport.ssl:
  enabled: true
  verification_mode: certificate
  keystore.path: certs/transport.p12
  truststore.path: certs/transport.p12

证书位置 config\certs\http_ca.crt

config/es.php文件配置

return [
    'host'=>['https://127.0.0.1:9200'],
    'user'=>'elastic',
    'pwd'=>'VX_IuVXQ6_WcfmBO8Mxc',
];
//初始化
use Elastic\Elasticsearch\ClientBuilder;
use Elastic\Elasticsearch\Exception\ClientResponseException;
use Elastic\Elasticsearch\Exception\ServerResponseException;
$client = ClientBuilder::create()
    ->setHosts(config('es.host'))
    ->setBasicAuthentication(config('es.user'), config('es.pwd'))
    ->setCABundle('cacrt/http_ca.crt')
    ->build();

分词器要下载对应的版本
https://github.com/medcl/elasticsearch-analysis-ik
下载地址
https://github.com/medcl/elasticsearch-analysis-ik/releases

下载后解压到 plugins/ik 目录下面 重启 es即可

//创建映射
curl -XPOST http://localhost:9200/index/_mapping -H 'Content-Type:application/json' -d'
{
        "properties": {
            "content": {
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_smart"
            }
        }
}'

案例 增删查改

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Elastic\Elasticsearch\ClientBuilder;
use Elastic\Elasticsearch\Exception\ClientResponseException;
use Elastic\Elasticsearch\Exception\ServerResponseException;
use Illuminate\Support\Facades\DB;


class MyesController extends Controller
{
    protected $client;

    public function __construct()
    {
        //初始化es
        $this->client = $this->_init_es();
    }

    public function index()
    {
        // 创建索引
        // $res = $this->_create_index();
        //   dd($res);
        // $item = DB::table('article')->first();
        // dd($item);
        // $article_list = DB::table('article')->where('id', '<', 300)->get()->toArray();
        // foreach ($article_list as $item) {
        //     $this->insert_update($item);
        // }

        //把数据写入搜索引擎
        // $res=$this->insert_update($item);
        // dd($res);
        //删除文档
        // $this->del_doc();
        //删除索引
        $this->del_index();
        //全文搜索
        // $res = $this->dosearch('C');
        // echo "<pre>";
        // print_r($res);
    }

      //初始化es
    private function _init_es()
    {
        $es_config = [
            'host' => ['https://127.0.0.1:9200'],
            'user' => 'elastic',
            'pwd' => 'QhW4NJSQGzP13o2kVI6o',
            'cacrt' => base_path() . '\private\certs\http_ca.crt',
        ];

        $client = ClientBuilder::create()
            ->setHosts($es_config['host'])
            ->setBasicAuthentication($es_config['user'], $es_config['pwd'])
            ->setCABundle($es_config['cacrt'])
            ->build();
        return $client;
    }
    //创建索引
    private function _create_index()
    {
        $params = [
            'index' => 'article',  //索引的名称(相当于mysql中的表明)
            'body' => [
                'settings' => [
                    'number_of_shards' => 5, //设置数据的分片数,默认为5
                    'number_of_replicas' => 0, // 数据的备份数(如果只有一台机器,设置为0)
                ],
                'mappings' => [
                    'properties' => [
                        'id' => [
                            'type' => 'integer'
                        ],
                        'cid' => [
                            'type' => 'integer'
                        ],
                        'title' => [
                            'type' => 'text',  // text类型,做模糊搜索用
                            "analyzer" => "ik_max_word",   //较细粒度分词,写入数据用
                            "search_analyzer" => "ik_smart", // 较粗粒度分词,查询数据用
                        ],
                        'descs' => [
                            'type' => 'text',
                        ],
                        'author' => [
                            'type' => 'keyword',  // keyword类型,做精确搜索用
                        ],
                    ],
                ],
            ],
        ];
        $res = $this->client->indices()->create($params);
        // 注意:索引不存在,就创建它。如果已存在,会报异常
        return $res;
    }

  
    //写入文档数据(_doc 相当于 mysql表中的一条记录)
    private function insert_update($item)
    {
        $params = [
            'index' => 'article',
            'type' => '_doc',
            'id' => $item->id,
            'body' => [
                'id' => $item->id,
                'cid' => $item->cid,
                'title' => $item->title,
                'descs' => $item->descs,
                'author' => $item->author,
            ]
        ];
        $res = $this->client->index($params);
        // 注意:该文档不存在就创建一个,存在的话,就更新它
        return  $res;
    }
    //全文搜索

    private function dosearch($keyword)
    {    
        $eswhere = [];
        $eswhere['bool']['must'][] = ['match' => ['title' => $keyword]];
        $eswhere['bool']['must'][] = ['match' => ['descs' => $keyword]];
        $esorder = ['id' => 'DESC'];
        $where = [
             		// size和from组合可以用于分页
            'size' => 10, //  取10条数据
            'from' => 0, // 从第几条开始取
            'index' => 'article',  // 从哪个索引中查
            'type' => '_doc',
            'body' => [
                'query' => $eswhere, //查询条件
                // 'sort' => $esorder,   //排序条件 ,加上以后,_score 没有值
                //高亮显示
                'highlight' => [
                    'fields' => [
                        'title' => [
                            'pre_tags' => ['<span style="color:red">'],
                            'post_tags' => ['</span>']
                        ],
                        'descs' => [
                            'pre_tags' => ['<span style="color:red">'],
                            'post_tags' => ['</span>']
                        ],
                    ],
                ],
            ]
        ];
        $results = $this->client->search($where);
        // $milliseconds = $results['took'];
        // $maxScore     = $results['hits']['max_score'];
        // $score = $results['hits']['hits'][0]['_score'];
        // $doc   = $results['hits']['hits'][0]['_source'];
        return  $results->asArray();
    }
    //删除文档
    private function del_doc()
    {
        for ($i = 1; $i < 1000; $i++) {
            $this->client->delete(['index' => 'article', 'id' => $i]);
        }
        // 注意:如果该文档已经删除了,重复删除会报异常
        exit('删除完成');
    }
    //删除索引
    private function del_index()
    {
        $this->client->indices()->delete(['index' => 'article']);
        // 注意:如果该索引已经删除了,重复删除会报异常
    }
}

es扩展 linux(centos7)中安装

  • centos7系统设置
注意:visualbox中,要先在“设置”中把网卡的连接方式改为“桥接网卡”(默认是网络地址转换NAT模式)
1、网卡设置为“桥接模式”
2、cd /etc/sysconfig/network-scripts
3、vi ifcfg-enp0s3
修改两个参数:
ONBOOT=no 改为:ONBOOT=yes
BOOTPROTO=dhcp 改为:BOOTPROTO=static
增加以下IP配置
IPADDR=192.168.31.34
NETMASK=255.255.255.0
GATEWAY=192.168.31.1
DNS1=114.114.114.114
保存退出后,重启网络(service network restart)
验证一下网络:ping www.baidu.com。如果能ping通,说明网络配置OK了

  • 下载elasticsearch
如果centos7是纯净系统(minimal版本的)
1、安装wget下载工具:yum -y install wget
2、进入一个目录里,如:cd /home 注意:最好不要在系统目录里下载文件
3、wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.16.1-linux-x86_64.tar.gz
  • 安装elasticsearch
注意:先把系统的防火墙关掉
service firewalld stop

1、解压压缩包
tar -xzvf elasticsearch-7.16.1-linux-x86_64.tar.gz
cd elasticsearch-7.16.1/config
2、修改配置文件
vi elasticsearch.yml
network.host:0.0.0.0
http.port:9200
node.name:node-1
cluster.initial_master_nodes:["node-1"]
3、cd /home/elasticsearch-7.16.1/bin
4、创建一个新的用户
useradd es
5、切换到es帐号下
su es
6、启动elasticsearch
./elasticsearch
==========================可能会报异常========================
一般需要设置一下centos的系统文件
vi /etc/security/limits.conf
直接在文件的最后增加以下几行参数
* soft nofile 65536
* hard nofile 65536
* soft nproc 65536
* hard nproc 65536
保存后退出
vi /etc/sysctl.conf
直接在文件的最后增加下面的参数
vm.max_map_count=655360
保存后退出

执行sysctl -p
然后再次到elasticsearch的bin目录下执行./elasticsearch
在局域网(当前的windows电脑)的浏览器中输入 192.168.31.34:9200,能访问,说明安装配置OK了
  • 安装ik分词插件
1、进入到/home/elasticsearch-7.16.1/plugins目录
2、创建一个目录,如:mkdir ik
3、进入ik目录中。cd ik
4、wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.16.1/elasticsearch-analysis-ik-7.16.1.zip --no-check-certificate
5、解压
unzip elasticsearch-analysis-ik-7.16.1.zip
6、重启elasticsearch
注意:不要在plugins目录里留无关的目录或文件

高并发/大数据量下,代码编写原则

  • 尽量少用或不用join查询,如果确实要用的话,注意时间开销
  • 尽量不在循环里操作数据库

开启OPCache缓存

Alt text

修改 php.ini 文件 添加这部分内容

zend_extension=php_opcache
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.max_wasted_percentage=5
opcache.revalidate_freq=3
opcache.use_cwd=1
opcache.validate_timestamps=1
opcache.save_comments=1
opcache.enable_file_override=Off
opcache.fast_shutdown=1
opcache.mmap_base=0x20000000

查询案例

不使用join查询

// 关联数据查询
public function article_4(): array
{
    //查询 文章表 关联字段为 cid
    $pages = DB::table('article')->orderBy('id', 'desc')->paginate(2000);
    $list = $pages->items();
    // 将所有的 cid 合并
    foreach ($list as $item) {
        $cidList[] = $item->cid;
    }
    // cid去重
    $cidList = array_unique($cidList);
    // 用whereIn 查出所需要的cid对应的 分类数据 
    $cate_list = $cate = DB::table('article_cate')->whereIn('id', $cidList)->get()->toArray();
    $cates = [];
    // 修改 cate_list 分类 索引为 数据的 id
    foreach ($cate_list as $val) {
        $cates[$val->id] = $val;
    }
    return compact('list', 'cates');
}

view视图

<table class="layui-table">
    <tr>
        <th>id</th>
        <th>文章分类</th>
        <th>文章标题</th>
        <th>添加时间</th>
    </tr>
    @foreach($list as $v)
    <tr>
        <td>{{$v->id}}</td>
        <td>{{$cates[$v->cid]->title}}</td>
        <td>{{$v->title}}</td>
        <td>{{$v->add_time }}</td>
    </tr>
    @endforeach
</table>

给laravel DB操作添加扩展

php artisan make:provider DBServiceProvider
文件路径在 app/provider

use Illuminate\Database\Query\Builder  as QueryBuilder;
public function boot(): void
{
    // 将数据的索引设置 $index
    QueryBuilder::macro('abbbc', function ($index) {
        // echo '扩展的方法';
        $res =   $this->get()->toArray();
        $result = [];
        foreach($res as $val){
            $result[$val->$index] = $val;
        }
        return $result;
    });
}

注册到 config\app里面

'providers' => ServiceProvider::defaultProviders()->merge([
//   ...
    App\Providers\DBServiceProvider::class,
])->toArray(),

使用示例

 $cates=DB::table('article_cate')->abbbc('id');
//  将数据的索引设置为 id ;