thinkphp 做分布式服务+读写分离+分库分表+负载均衡(分区)(后续接着写)

news2024/11/19 13:32:02

thinkphp 做分布式服务+读写分离+分库分表+负载均衡(分区)

    • 引言 thinkphp* 大道至简
    • 负载均衡
    • 分布式服务
    • 一、读写分离
        • 1、读写分离的实现方式
        • 2、主从同步的三种模式
        • 2-1、异步模式(mysql async-mode)
        • 2-2、半同步模式(mysql semi-sync)
        • 2-3、同步模式(mysql semi-sync)
        • 2-4 总结
    • 二、分库分表
      • 分表
        • 1、为什么要分表
        • 2、垂直拆分和水平拆分
      • 分表分库的策略
        • php 分库分表hash算法
        • 0、分表的方法(thinkphp)
        • 1、几种生成id的方式对比:
        • 2、ThinkPHP6 业务分表之一:UID 发号器
        • 3、ThinkPHP6 业务分表之二:用户
        • 简单的分表写入
    • 代码示例
        • 分表写入
    • 其他杂项
      • 1、1亿条数据在PHP中实现Mysql数据库分表100张
        • 2、tp6 Sharding-Proxy企业分库分表最佳实践

引言 thinkphp* 大道至简

负载均衡

分布式服务

一、读写分离

为什么要读写分离
理论上来说读写请求不要超过2000/s,如果加了缓存之后,到数据库请求还是超过2000以上考虑读写分离
使得读请求可以在不同机器并发,用了读写分离之后可以通过动态扩展读服务器增加读效率,这与redis中的主从架构读写分离、copyOnWrite机制的并发容器、以及数据库MVCC机制有点相识,都是通过读请求的数据备份增加读写并发效率。
适用于业务场景中,读请求大于写请求的情况,读写分离使得系统能够更多的容纳读请求并发

1、读写分离的实现方式

一般来说是基于mysql自带的主从复制功能。mysql主从复制的流程图如下:
在这里插入图片描述
总结mysql的主从复制过程大体是主库有一个进程专门是将将记录的Binlog日志发送到从库,从库有一个io线程(mysql5.6.x之后IO线程可以多线程写入relay日志)将收到的数据写入relay日志当中,另外还有一个SQL进程专门读取relay日志,根据relay日志重做命令(mysql5.7版本之后,从可以并行读取relay log重放命令(按库并行,每个库一个线程))。

2、主从同步的三种模式
2-1、异步模式(mysql async-mode)

异步模式如下图所示,这种模式下,主节点不会主动push bin log到从节点,这样有可能导致failover的情况下,也许从节点没有即时地将最新的bin log同步到本地。

2-2、半同步模式(mysql semi-sync)

这种模式下主节点只需要接收到其中一台从节点的返回信息,就会commit;否则需要等待直到超时时间然后切换成异步模式再提交;这样做的目的可以使主从数据库的数据延迟缩小,可以提高数据安全性,确保了事务提交后,binlog至少传输到了一个从节点上,不能保证从节点将此事务更新到db中。性能上会有一定的降低,响应时间会变长

2-3、同步模式(mysql semi-sync)

全同步模式是指主节点和从节点全部执行了commit并确认才会向客户端返回成功。

2-4 总结

在代码中插入之后,又查询这样的操作是不可靠,可能导致插入之后,查出来的时候还没有同步到从库,所以查出来为null。如何应对这种情况了?其实并不能从根本上解决这种情况的方案。只能一定程度通过降低主从延迟来尽量避免。

降低主从延迟的方法有:
拆主库,降低主库并发,降低主库并发,此时主从延迟可以忽略不计,但并不能保证一定不会出现上述情况。
打开并行复制-但这个效果一般不大,因为写入数据可能只针对某个库并发高,而mysql的并行粒度并不小,是以库为粒度的。
但这并不能根本性解决这个问题,其实面对这种情况最好的处理方式是:
重写代码,插入之后不要更新
如果确实是存在先插入,立马就能查询到,然后立马执行一些操作,那么可以对这个查询设置直连主库(通过中间件可以办到)

二、分库分表

分表

分表分库一般分垂直和水平,垂直是指感觉业务来进行库的拆分,比如专门的用户库或者订单库这样子,但垂直还是无法解决单表数据量过大导致的性能会差的问题(这里可能会和上面矛盾,网上多数指的是 2000W 就会影响,但好像没有多少人谈过他们的表结构情况)。水平分指的是某一个表,里面的数据量非常大,我们按照一定的规则来进行一个拆分分流,比如把用户的数据由一直存放在用户表,变为可能这条数据是在用户1号表或者2号表这样子。规则可能是 范围(range)或者哈希(hash),也不知道我喜欢的取模算不算哈希。分了其实会带来一些问题的复杂性,比如分库那如何确保多库事务性的一致性、分布式锁等等很多,然后还有之前我们的 join 查询,现在可能就无法使用呢。

1、为什么要分表

当不使用分库分表的情况下,系统的性能瓶颈主要体现在:
当面临高并发场景的时候,为了避免Mysql崩溃(MySql性能一般的服务器建议2000/s读写并发以下),只能使用消息队列来削峰。
受制于单机限制。数据库磁盘容量吃紧。
数据库单表数据量太大,sql越跑越慢
而分库分表正是为了解决这些问题,提高数据库读写并发量,磁盘容量大大提高,单表数据量降低,提高查询效率。

2、垂直拆分和水平拆分

以表的维度来说:

垂直拆分 指根据表的字段进行拆分,其实很常见,有时候在数据库设计的时候就完成了,属于数据库设计范式,如订单表、订单支付表、商品表。
水平拆分 表结构一样,数据进行拆分。如原本的t_order表变为t_order_0,t_order_1,t_order_3

以库的维度来说:

垂直拆分 指把原本的大库,按业务不同拆到不同的库(微服务一般都是这么设计的,即专库专用)
水平拆分 一个服务对应多个库,每个库有相同的业务表,如库1有t_order表,库2也有t_order表。业务系统通过数据库中间件或中间层操作t_order表,分库操作对于业务代码透明。
所以,我们平常说的分库分表,一般都是指的水平拆分

分表分库的策略

  1. hash分法,按一个键进行hash取模,然后分发到某张表或库。优点是可以平摊每张表的压力,缺点是扩容时会存在数据迁移问题。
  2. range分法,按范围或时间分发,比如按某个键的值区间、或创建时间进行分发,优点是可以很方便的进行扩容,缺点是会造成数据热点问题。从分表上说还好,如果是分库,将导致某一个库节点压力过大,节点间负载不均。
php 分库分表hash算法

app/common.php 公共方法定义
$userid 也可以用下面的uid 发号器定义,通过哈希来就算出表名称

//哈希分表
    function get_hash_table($table, $userid) {
        $str = crc32($userid);
        if ($str < 0) {
            $hash = "0" . substr(abs($str), 0, 1);
        } else {
            $hash = substr($str, 0, 2);
        }
        return $table . "_" . $hash;
    }

控制器调用计算表名

public function index() {
     echo $table=get_hash_table('message', '18991').'<br>';
     echo $table=get_hash_table('message', '18993').'<br>';
     echo $table=get_hash_table('message', '18994').'<br>';
}
0、分表的方法(thinkphp)
public function getPartitionTableName($data=array()) {
        // 对数据表进行分区
        if(isset($data[$this->partition['field']])) {
            $field   =   $data[$this->partition['field']];
            switch($this->partition['type']) {
                case 'id':
                    // 按照id范围分表
                    $step    =   $this->partition['expr'];
                    $seq    =   floor($field / $step)+1;
                    break;
                case 'year':
                    // 按照年份分表
                    if(!is_numeric($field)) {
                        $field   =   strtotime($field);
                    }
                    $seq    =   date('Y',$field)-$this->partition['expr']+1;
                    break;
                case 'mod':
                    // 按照id的模数分表
                    $seq    =   ($field % $this->partition['num'])+1;
                    break;
                case 'md5':
                    // 按照md5的序列分表
                    $seq    =   (ord(substr(md5($field),0,1)) % $this->partition['num'])+1;
                    break;
                default :
                    if(function_exists($this->partition['type'])) {
                        // 支持指定函数哈希
                        $fun    =   $this->partition['type'];
                        $seq    =   (ord(substr($fun($field),0,1)) % $this->partition['num'])+1;
                    }else{
                        // 按照字段的首字母的值分表
                        $seq    =   (ord($field{0}) % $this->partition['num'])+1;
                    }
            }
            return $this->getTableName().'_'.$seq;
        }else{
            // 当设置的分表字段不在查询条件或者数据中
            // 进行联合查询,必须设定 partition['num']
            $tableName  =   array();
            for($i=0;$i<$this->partition['num'];$i++)
                $tableName[] = 'SELECT * FROM '.$this->getTableName().'_'.($i+1);
            $tableName = '( '.implode(" UNION ",$tableName).') AS '.$this->name;
            return $tableName;
        }
}
 
1、几种生成id的方式对比:

1、通过数据库自增
往公用的一张表(这张表是自增主键)插入一条数据,获取id的返回值,用这个id再去插入中间件当中去。oracle可以通过自增序列。
缺点:不适合并发高的场景,毕竟不管是自增序列还是采取自增键的方式来生成,会并发竞争写锁,效率太低。
2、UUID
缺点:uuid太长了,不规则
3、时间戳
一般联合其他业务字段拼接作为一个Id,如时间戳+用户id+业务含义编码
缺点:并发高容易重复
4、雪花算法 demo

<?php
class SnowFlake
{
    const TWEPOCH = 1288834974657; // 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)

    const WORKER_ID_BITS     = 5; // 机器标识位数
    const DATACENTER_ID_BITS = 5; // 数据中心标识位数
    const SEQUENCE_BITS      = 12; // 毫秒内自增位

    private $workerId; // 工作机器ID
    private $datacenterId; // 数据中心ID
    private $sequence; // 毫秒内序列

    private $maxWorkerId     = -1 ^ (-1 << self::WORKER_ID_BITS); // 机器ID最大值
    private $maxDatacenterId = -1 ^ (-1 << self::DATACENTER_ID_BITS); // 数据中心ID最大值

    private $workerIdShift      = self::SEQUENCE_BITS; // 机器ID偏左移位数
    private $datacenterIdShift  = self::SEQUENCE_BITS + self::WORKER_ID_BITS; // 数据中心ID左移位数
    private $timestampLeftShift = self::SEQUENCE_BITS + self::WORKER_ID_BITS + self::DATACENTER_ID_BITS; // 时间毫秒左移位数
    private $sequenceMask       = -1 ^ (-1 << self::SEQUENCE_BITS); // 生成序列的掩码

    private $lastTimestamp = -1; // 上次生产id时间戳

    public function __construct($workerId, $datacenterId, $sequence = 0)
    {
        if ($workerId > $this->maxWorkerId || $workerId < 0) {
            throw new Exception("worker Id can't be greater than {$this->maxWorkerId} or less than 0");
        }

        if ($datacenterId > $this->maxDatacenterId || $datacenterId < 0) {
            throw new Exception("datacenter Id can't be greater than {$this->maxDatacenterId} or less than 0");
        }

        $this->workerId     = $workerId;
        $this->datacenterId = $datacenterId;
        $this->sequence     = $sequence;
    }

    public function nextId()
    {
        $timestamp = $this->timeGen();

        if ($timestamp < $this->lastTimestamp) {
            $diffTimestamp = bcsub($this->lastTimestamp, $timestamp);
            throw new Exception("Clock moved backwards.  Refusing to generate id for {$diffTimestamp} milliseconds");
        }

        if ($this->lastTimestamp == $timestamp) {
            $this->sequence = ($this->sequence + 1) & $this->sequenceMask;

            if (0 == $this->sequence) {
                $timestamp = $this->tilNextMillis($this->lastTimestamp);
            }
        } else {
            $this->sequence = 0;
        }

        $this->lastTimestamp = $timestamp;

        /*$gmpTimestamp    = gmp_init($this->leftShift(bcsub($timestamp, self::TWEPOCH), $this->timestampLeftShift));
        $gmpDatacenterId = gmp_init($this->leftShift($this->datacenterId, $this->datacenterIdShift));
        $gmpWorkerId     = gmp_init($this->leftShift($this->workerId, $this->workerIdShift));
        $gmpSequence     = gmp_init($this->sequence);
        return gmp_strval(gmp_or(gmp_or(gmp_or($gmpTimestamp, $gmpDatacenterId), $gmpWorkerId), $gmpSequence));*/

        return (($timestamp - self::TWEPOCH) << $this->timestampLeftShift) |
            ($this->datacenterId << $this->datacenterIdShift) |
            ($this->workerId << $this->workerIdShift) |
            $this->sequence;
    }

    protected function tilNextMillis($lastTimestamp)
    {
        $timestamp = $this->timeGen();
        while ($timestamp <= $lastTimestamp) {
            $timestamp = $this->timeGen();
        }

        return $timestamp;
    }

    protected function timeGen()
    {
        return floor(microtime(true) * 1000);
    }

    // 左移 <<
    protected function leftShift($a, $b)
    {
        return bcmul($a, bcpow(2, $b));
    }
}

5、uid 发号器 redis中间件生成
原理:利用redis单线程工作线程属性去维护一个自增变量。

2、ThinkPHP6 业务分表之一:UID 发号器

我们现在假设项目是新成立的,暂时没有一个技术债。目前我们要先规划一个用户表,打算划分 16 个表,按照取模的方式来查询。最先可能我们要考虑如何定义 UID 的问题,不过对我们 PHPer 来说不是什么难事,毕竟我们基本都不用 UUID 来做 UID 的,占用的空间会比较多,不利于索引。

但是不用 UUID ,选择了 INT 来做 UID,那我们要如何确保它的连续性和唯一性呢?业内常用的可能是雪花算法,但我选择自己写一个简易的发号器。

发号器需要加东西,然后取东西,我们可以利用 Redis List 来很好的实现我们需要的先进先出功能。通过一个命令或者说脚本,我们定时往列表中填上自增的 ID,然后在注册流程中来取最前面的。
在这里插入图片描述
代码层面
app/common.php 公共方法定义获取redis 的值

if(!function_exists('get_redis')) {
    function get_redis() {
        return new \Predis\Client('tcp://IP:端口', [
            'parameters' => [
                'password' => '密码',
            ],
        ]);
    }
}

定义一个发号器函数
app/command/GenerateUID.php

<?php
declare (strict_types = 1);

namespace app\command;

use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;

class GenerateUID extends Command
{
    protected $redis;

    /*
     * 发号器列表 键
     *
     * @var string
     */
    protected $cache_key = 'generate:uid';

    /*
     * 锁 键
     *
     * @var string
     */
    protected $lock_key = 'generate:uid_lock';

    /*
     * 发号器列表最大容量,默认为 500000
     *
     * @var int
     */
    protected $max_capacity = 5E5;

    public function __construct()
    {
        $this->redis = get_redis();

        parent::__construct();
    }

    protected function configure()
    {
        // 指令配置
        $this->setName('generate:uid')
            ->setDescription('生成 UID');
    }

    protected function execute(Input $input, Output $output)
    {
        $current_capacity = $this->get_list_length();

        // 计算发号器需要增加的数量
        $append_capacity = $this->max_capacity - $current_capacity;

        $uid_max = $this->get_uid_max();

        $output->writeln('当前最大UID:' . $uid_max);
        $output->writeln('当前剩余容量:' . $current_capacity);
        $output->writeln('最大存储容量:' . $this->max_capacity);
        $output->writeln('需要追加容量:' . $append_capacity);

        // 如果不需要增加则结束
        if($append_capacity === 0) {
            return;
        }

        $data = [];

        for($i = 1; $i <= $append_capacity; $i++) {
            $data[] = $uid_max + $i;
        }

        // 把需要加的数据进行分块,方便快速追加
        $data = array_chunk($data, 1000);

        // 加锁
        $lock = $this->redis->executeRaw([
            'SET',
            $this->lock_key,
            1,
            'EX',
            10 * 60,
            'NX',
        ]);

        if($lock !== 'OK') {
            $output->writeln('获取锁失败');

            return;
        }

        try {
            foreach ($data as $item) {
                $this->redis->rpush($this->cache_key, $item);
            }
        } catch (\Exception $e) {
            $output->error($e->getMessage());
        } finally {
            // 释放锁
            $this->redis->del($this->lock_key);
        }
    }

    /*
     * 获取发号器列表的长度
     *
     * @return int
     */
    protected function get_list_length() :int
    {
        return $this->redis->llen($this->cache_key);
    }

    /*
     * 获取当前最大的 UID
     *
     * @return int
     */
    protected function get_uid_max() :int
    {
        $value = (int) $this->redis->lindex($this->cache_key, -1);

        if($value) {
            return $value;
        }

        return 0;
    }
}

config/console.php 定义一个打印函数类

<?php
return [
    // 指令定义
    'commands' => [
        ...
        'generate:uid' => 'app\command\GenerateUID',
    ],
];

执行命令

$ php think generate:uid
当前最大UID:0
当前剩余容量:0
最大存储容量:500000
需要追加容量:500000

3、ThinkPHP6 业务分表之二:用户

上一篇我们将了 UID 的发号器,那这一篇我们将要去实现用户的注册入库和简单的查询。

首先我们要实现数据表的识别。获取表名称
app/common.php

if(!function_exists('table_name')) {
    /*
     * 获取表名称
     *
     * @param string $name 表名
     * @param int|null $uid UID
     * @return string
     */
    function table_name(string $name, ?int $uid = null) {
        $support_table = [
            'users' => 16, // 表名 => 分表数
        ];

        if(!isset($support_table[$name])) {
            return $name;
        }

        // 如果没有传递 UID,那则需要调用其他方法来获取 UID
        if(is_null($uid)) {
            $uid = (int) 1;
        }

        if((int) $uid === 0) {
            throw new \Exception('UID 值异常');
        }

        // 取 UID 和 分表数的模,值转化为小写十六进制
        return sprintf('%s_%x', $name, $uid % $support_table[$name]);
    }
}

执行写入操作
app/controller/Auth.php

<?php
namespace app\controller;

use app\BaseController;
use think\facade\Db;
use think\helper\Str;

class Auth extends BaseController
{
    /*
     * 注册接口
     */
    public function register()
    {
        $redis = get_redis();

        // 获取最左的值
        $uid = $redis->lpop('generate:uid');

        // 为空说明列表已经没内容了
        if(is_null($uid)) {
            return '无法获取 UID';
        }

        // 获取表名
        $table_name = table_name('users', $uid);

        // 插入数据
        Db::table($table_name)->insert([
            'id' => $uid,
            'nickname' => '随机生成' . Str::random(),
        ]);

        return '注册成功';
    }
}

简单的分表写入
	
// 用于查询
$data = Db::table('sslq_question_1')->save(['keywords'=>1111]);

代码示例

第一次用数据表创建一个自增id 表,将id 拿出来放在即将插入的数据库中,每次插入先读当前条数拿出id 作为当前添加数据的自增id,结果 跑着跑着就卡了。毕竟每次都要多操作两次数据库,现在改成用redis 创建一个自增id 作为当前添加的数据id 。各有利弊吧,通过数据表获取自增每次执行插入需要查询总数量在插入一条自增数据,多操作2步数据库,用redis 直接读写,效率好,服务器也没有压力。(不知道reids 服务器挂机了,redis 会不会丢,丢的话,只能在写一个redis 备份)

分表写入

控制器

  /**
	 *添加试题
	 **/
    public function add(){
    	// $data = $this->postData();
    	$model = new QuestionModel;
        for ($i=0; $i <10000; $i++) { 
            $data = [
                'keywords'=>$i,
                'option'  =>'option',
                'answer'  =>'answer',
                'type'    =>rand(0,9999),
                'content_path'  =>'content_path',
                'admin_user_id'  =>rand(0,9999)
            ];
            $model->add($data);
            // try {
            //     //获取用户信息
            //     if ($model->add($data)) {
            //         // return $this->renderSuccess('添加成功');
            //     }
            //     return $this->renderError($model->getError() ?:'添加失败');
            // } catch (Exception $e) {
            //     throw new Exception($e->getMessage());
            // }
        }
    }

模型

	use think\cache\driver\Redis;    //需要使用到的类
	use think\facade\Cache;
	use think\facade\Config;
  /**
     * Created by
     * User: runtu
     * Date: 2024/9/23
     * Brief: 插入分表数据
     * docs: 模型初始化,获取表名称。插入到对应的表里面
     */
    public function add($data){
        $redis = new Redis(Config::get('cache.stores.redis'));
        $uuid =  $redis->get('uuid')+1;
        $uuid = $uuid>0?$uuid:1;
        $redis->set('uuid',$uuid);
        $data['question_id'] = $uuid;
        Db::name($this->getTableName($uuid))->insert($data);

        //通过自建id表通过数据表自增来获取uuid
        // $uuid =  Db::name('question_id')->count()+1;
        // $data['question_id'] = $uuid;
        // Db::name($this->getTableName($uuid))->insert($data);
        // //通过自增函数来计算插入的id
        // return Db::name('question_id')->insert(['name'=>1]);
    }

数据库数据效果:
在这里插入图片描述

其他杂项

1、1亿条数据在PHP中实现Mysql数据库分表100张

当数据量猛增的时候,大家都会选择库表散列等等方式去优化数据读写速度。笔者做了一个简单的尝试,1亿条数据,分100张表。具体实现过程如下:
首先创建100张表:

$i=0;
while($i<=99){
echo "$newNumber \r\n";
$sql="CREATE TABLE `code_".$i."` (
 `full_code` char(10) NOT NULL,
 `create_time` int(10) unsigned NOT NULL,
 PRIMARY KEY  (`full_code`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8";
mysql_query($sql);
$i++;

下面说一下我的分表规则,full_code作为主键,我们对full_code做hash
函数如下:

$table_name=get_hash_table('code',$full_code);
function get_hash_table($table,$code,$s=100){
$hash = sprintf("%u", crc32($code));
echo $hash;
$hash1 = intval(fmod($hash, $s));
 return $table."_".$hash1;
}

这样插入数据前通过get_hash_table获取数据存放的表名。
最后我们使用merge存储引擎来实现一张完整的code表

1 CREATE TABLE IF NOT EXISTS code (
2 full_code char(10) NOT NULL,
3 create_time int(10) unsigned NOT NULL,
4 INDEX(full_code)
5 ) TYPE=MERGE UNION=(code_0,code_1,code_2…) INSERT_METHOD=LAST ;

这样我们通过select * from code就可以得到所有的full_code数据了。

2、tp6 Sharding-Proxy企业分库分表最佳实践

tp6 Sharding-Proxy企业分库分表最佳实践

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2161916.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

红帽rhce含金量?考到能拿多少钱工资?

随着目前国内的it职业飞速发展&#xff0c;rhce已经成为it职业的首选认证、高薪亮点。主要是linux人才出现大比例短缺的状况&#xff0c;很多企业对linux人才的需求也继续升温。 这个时候大家如果抓住了机会&#xff0c;那么实力就能得到质的提升&#xff0c;那么小编针对红帽…

OpenAI GPT-3 API: What is the difference between davinci and text-davinci-003?

题意&#xff1a;OpenAI GPT-3 API&#xff1a;davinci 和 text-davinci-003 有什么区别 问题背景&#xff1a; Im testing the different models for OpenAI, and I noticed that not all of them are developed or trained enough to give a reliable response. 我正在测试…

论文阅读【时间序列】ModerTCN (ICLR2024)

【时间序列】ModerTCN (ICLR2024) 原文链接&#xff1a;ModernTCN: A Modern Pure Convolution Structure for General Time Series Analysis 代码仓库&#xff1a;ModerTCN 简易版本实现代码可以参考&#xff1a;&#xff08;2024 ICLR&#xff09;ModernTCN&#xff1a;A Mod…

谁是AI界的老司机?谁最“纯洁”?谁能通过暧昧小短文的终极考验?

AI的能力已经让人们惊叹不已&#xff0c;不管是帮你写文章、答疑解惑&#xff0c;还是生成艺术作品&#xff0c;几乎无所不能。但如果让AI来解读一篇暗藏玄机、暧昧十足的小短文&#xff0c;结果会怎样&#xff1f;今天&#xff0c;我们就把几款顶流AI大模型拉出来&#xff0c;…

Cobalt Strike的下载与基本用法

CobaltStrike4.8 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;tgf3 what Cobalt Strike是一款渗透测试神器&#xff0c;常被业界人称为CS神器。Cobalt Strike已经不再使用MSF而是作为单独的平台使用&#xff0c;它分为客户端与服务端&#xff0c;服务端是一个&…

C++的扩充和封装

作业&#xff1a; 手动封装一个顺序表&#xff08;SeqList&#xff09;,分文件编译实现 有私有成员&#xff1a;顺序表数组的起始地址 ptr、 顺序表的总长度&#xff1a;size、顺序表的实际长度&#xff1a;len 成员函数&#xff1a;初始化 init(int n) 判空&#xff1a;em…

Vue:默认插槽

目录 一.性质 1.内容分发 2.无名称标识 3.作用域 4.使用方式 二.使用 1.父组件 2.子组件 三.代码 1.父组件代码 2.子组件代码 四.效果 一.性质 1.内容分发 默认插槽允许组件的使用者定义一些内容&#xff0c;这些内容会被插入到组件模板中的特定位置。这有助于实…

C++:类中的特殊关键字,运算重载符

1.My_string类中重载以下的运算符&#xff1a; 、[] 、>、<、、>、<、&#xff01;、、输入输出(>>、<<) 主函数&#xff1a; #include <iostream> #include "my_string.h"using namespace std;int main() {My_string s1("cat…

QT客户端发送HTTP请求此时服务器到底收到了哪些数据?

一个Http请求包括 请求行 请求头 空行 请求体 下面是示例&#xff1a; 1,2,3,4分别代表上面的四个部分&#xff0c;我只是做了一些解析&#xff0c;具体可以结合代码 1. post / HTTP/1.1 2.GET请求头包括Host(主机名),user-agent&#xff08;客户端标识符&#xff09;&am…

AI Agent智能应用从0到1定制开发Langchain+LLM全流程解决方案与落地实战

大模型微调实战&#xff1a;精通、指令微调、开源大模型微调、对齐与垂直领域应用29套AI全栈大模型项目实战&#xff0c;人工智能视频课程-多模态大模型&#xff0c;微调技术训练营&#xff0c;大模型多场景实战&#xff0c;AI图像处理&#xff0c;AI量化投资&#xff0c;OPenC…

fiddler抓包11_列表显示服务器IP (配置文件)

请求列表默认不显示服务器IP字段&#xff0c;也无法从定制列窗口添加&#xff0c;可以修改CustomRules.js实现。 ① 菜单栏“Rules”&#xff08;规则&#xff09; - “Customize Rules...”&#xff08;自定义规则&#xff09;&#xff0c;打开CustomRules.js文件。 &#xf…

HarmonyOS NEXT:解密从概念到实践的技术创新与应用前景

HarmonyOS是目前华为手机所搭载的鸿蒙系统&#xff0c;它在Open Harmony的基础上兼容了安卓的AOSP&#xff0c;所以可以使用安卓APK应用&#xff0c;HarmonyOS属于华为在当前阶段过渡使用的系统&#xff0c;原生鸿蒙的应用生态尚未发展起来&#xff0c;兼容安卓应用可以让用户有…

【AI大模型】通义大模型API接口实现

目录 一、基础环境安装 &#xff08;一&#xff09;OpenAI Python SDK安装 &#xff08;二&#xff09;DashScope SDK安装 二、OPENAI接口实现 &#xff08;一&#xff09;文本输入 &#xff08;二&#xff09;流式输出 &#xff08;三&#xff09;图像输入 &#xff0…

Python 字符串的常见方法

Python 字符串的常见方法 字符串是 Python 中非常重要的数据类型之一。在日常编程中&#xff0c;我们经常需要对字符串进行各种操作&#xff0c;比如分割、连接、替换等。Python 提供了丰富的字符串方法&#xff0c;使得这些操作变得简单而高效。本文将详细介绍一些常见的字符…

【Docker】Docker快速入门

Docker学习笔记 一、Docker概述 为什么会出现Docker? 安卓开发流程&#xff1a;apk(java开发的)发布到应用商店&#xff0c;用户安装apk即可使用。 后端开发流程&#xff1a; jar(java开发的)带上环境发布到Docker仓库&#xff0c;用户从Docker仓库拉取镜像并部署。 总结…

关于Python升级以后脚本不能运行的问题

近日将Python从3.11升级到了3.12&#xff0c;然后把几个包例如numpy等也通过pip给upgrade了一下&#xff0c;结果原来运行的好好的脚本&#xff0c;都运行不了了&#xff0c;还出现各种报错。怀疑是自己升级了环境导致的&#xff0c;因此通过搜索引擎检索了一下&#xff0c;有这…

【React】(推荐项目)使用 React、Socket.io、Nodejs、Redux-Toolkit、MongoDB 构建聊天应用程序 (2024)

使用 React、Socket.io、Nodejs、Redux-Toolkit、MongoDB 构建聊天应用程序 (2024) 学习使用 React、Socket.io、Node.js、Redux-Toolkit 和 MongoDB 构建响应式实时消息聊天应用程序。这个项目涵盖了从设置到实施的所有内容&#xff0c;提供了宝贵的见解和实用技能。无论您是…

地平线占用预测 FlashOcc 参考算法-V1.0

1.简介 3D Occupancy Networks 的基本思路是将三维空间划分成体素网格&#xff0c;并对每个网格进行各类感知任务的预测。目前以网格为中心的方法能够预测每个网格单元的占用率、语义类别、未来运动位移和实例信息。3D occupancy 可以对道路障碍物进行更细粒度的划分&#xff…

【Docker】解决Docker Engine stopped

解决Docker Engine stopped 解决Docker Engine stopped1.检查虚拟设置2 安装wslwindows安装wsl 解决Docker Engine stopped 在安装完docker之后不少用户会遇到Docker Engine stopped。下面就下给出解决方法让docker正常运行起来 1.检查虚拟设置 打开任务管理器查看cpu页面&a…

vue-入门速通

setup是最早的生命周期&#xff0c;在vue2里边的data域可以使用this调用setup里面的数据&#xff0c;但是在setup里边不能使用thisvue项目的可执行文件是index&#xff0c;另外运行前端需要npm run vue的三个模块内需要三个不同的结构&#xff0c;里边放置js代码&#xff0c;注…