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

news2024/9/22 4:40:45

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

    • 引言 thinkphp* 大道至简
    • 一、分库分表
      • 分表
        • php 分库分表hash算法
        • 0、分表的方法(thinkphp)
        • 1、ThinkPHP6 业务分表之一:UID 发号器
        • 2、ThinkPHP6 业务分表之二:用户
    • 其他杂项
      • 1亿条数据在PHP中实现Mysql数据库分表100张

引言 thinkphp* 大道至简

一、分库分表

分表

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

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、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

2、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 '注册成功';
    }
}

其他杂项

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数据了。

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

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

相关文章

希尔排序(C语言实现)

目录 1.希尔排序( 缩小增量排序 ) 2.动图 ​编辑 3.代码实现 预排序实现 子序列排列实现 单趟排序实现 对整组数进行子排序 希尔排序代码 代码测试 时间复杂度分析 希尔排序的特性总结&#xff1a; 1.希尔排序( 缩小增量排序 ) 基本思想&#xff1a; 1.先选定一个…

QTCreator 调试:unknown debugger type “No engine“

QTCreator 调试&#xff1a;unknown debugger type "No engine" - kaizenly - 博客园 (cnblogs.com) 一开始Debuggers---Auto-detected这里第一row第一个项是标红的&#xff0c;然后没改东西&#xff0c;点完应用Apply以后&#xff0c;就可以调试了...&#xff08;不…

在python爬虫中xpath方式提取lxml.etree._ElementUnicodeResult转化为字符串str类型

简单提取网页中的数据时发现的 当通过xpath方式提取出需要的数据的text文本后想要转为字符串&#xff0c;但出现lxml.etree._ElementUnicodeResult的数据类型不能序列化&#xff0c;在网上查找到很多说是编码问题Unicode编码然后解码什么的&#xff1b;有些是(导入的xml库而不…

深度学习之概率论预备知识点(3)

在深度学习中&#xff0c;概率论和数理统计是理解许多算法背后的理论基础。这些知识在处理不确定性、估计模型参数、理解数据分布等方面非常关键 1、概率 一种用来描述随机事件发生的可能性的数字度量&#xff0c;表示某一事件发生的可能性。 概率并不客观存在&#xff0c;是…

Android Choreographer 监控应用 FPS

Choreographer 是 Android 提供的一个强大的工具类&#xff0c;用于协调动画、绘制和视图更新的时间。它的主要作用是协调应用的绘制过程&#xff0c;以确保流畅的用户体验。Choreographer 也可以帮助我们获取帧时间信息&#xff0c;从而为性能监测和优化提供重要的数据支持。 …

IDEA中Quarkus框架(3.13版本)开发、调试、部署、打包等

code-with-quarkus code-with-quarkus 是使用官网生成的demo项目 这个项目使用Quarkus&#xff08;使用3.13.0版本&#xff0c;该版本支持JDK21&#xff09;&#xff0c;超音速亚原子Java框架。官网地址: https://quarkus.io/. 环境要求 OS: Windows 10.0 jdk 11 maven 3.9…

淘宝扭蛋机小程序,扭蛋机文化下的新体验

在数字化时代中&#xff0c;扭蛋机逐渐从传统的线下机器转移到了线上互联网中&#xff0c;市场得到了创新发展。扭蛋机小程序具有便捷、多样化、个性化的特点&#xff0c;迎合了当下消费者的线上消费习惯&#xff0c;又能够让扭蛋机玩家体验到新鲜有趣的扭蛋。 扭蛋机是一种热…

python简单的小项目-关于央行储蓄占比情况的数据可视化

该数据来源于锐思数据库&#xff0c;如果数据有偏差&#xff0c;可能是本人搜索的问题&#xff0c;希望大家谅解。 数据大纲&#xff1a; 其中我们制作折现统计图需要用到的是截止日期&#xff0c;表达数据最后获取的日期&#xff0c;而更新时间则是数据时效性的表示&#xff…

django项目添加测试数据的三种方式

文章目录 自定义终端命令Faker添加模拟数据基于终端脚本来完成数据的添加编写python脚本编写shell脚本执行脚本需要权限使用shell命令来完成测试数据的添加 添加测试数据在工作中一共有三种方式&#xff1a; 可以根据django的manage.py指令进行[自定义终端命令]可以采用第三方…

pthread_cond_signal 和pthread_cond_wait

0、pthread_join()函数作用&#xff1a; pthread_join() 函数会一直阻塞调用它的线程&#xff0c;直至目标线程执行结束&#xff08;接收到目标线程的返回值&#xff09;&#xff0c;阻塞状态才会解除。如果 pthread_join() 函数成功等到了目标线程执行结束&#xff08;成功获取…

【C++】list详解及模拟实现

目录 1. list介绍 2. list使用 2.1 修改相关 2.2 遍历 2.3 构造 2.4 迭代器 2.5 容量相关 2.6 元素访问 2.7 操作相关 3. 模拟实现 3.1 节点类 3.1.1 初始结构 3.1.2 节点的构造函数 3.2 迭代器类 3.2.1 初始结构 3.2.2 迭代器 3.2.3 迭代器-- 3.2.4 解引…

1.随机事件与概率

第一章 随机时间与概率 1. 随机事件及其运算 1.1 随机现象 ​ 确定性现象&#xff1a;只有一个结果的现象 ​ 确定性现象&#xff1a;结果不止一个&#xff0c;且哪一个结果出现&#xff0c;人们事先并不知道 1.2 样本空间 ​ 样本空间&#xff1a;随机现象的一切可能基本…

ML 系列:机器学习和深度学习的深层次总结(05)非线性回归

图 1.不同类型的回归 一、说明 非线性回归是指因变量和自变量之间存在非线性关系的模型。该模型比线性模型更准确、更灵活&#xff0c;可以获取两个或多个变量之间复杂关系的各种曲线。 二、关于 当数据之间的关系无法用直线预测并且呈曲线形式时&#xff0c;我们应该使用非线性…

MySQL篇(索引)(持续更新迭代)

目录 一、简介 二、有无索引情况 1. 无索引情况 2. 有索引情况 3. 优劣势 三、索引结构 1. 简介 2. 存储引擎对于索引结构的支持情况 3. 为什么InnoDB默认的索引结构是Btree而不是其它树 3.1. 二叉树&#xff08;BinaryTree&#xff09; 3.2. 红黑树&#xff08;RB&a…

6、等级保护政策内容

数据来源&#xff1a;6.等级保护政策内容_哔哩哔哩_bilibili 信息安全产品管理与响应 等级管理 对信息系统中使用的信息安全产品实行按等级管理&#xff0c;信息安全事件应分等级响应与处置。 预测评服务由测评公司和咨询公司提供预测评服务&#xff0c;根据技术要求和测评要…

高校心理辅导系统:Spring Boot技术实现指南

目 录 摘 要 I ABSTRACT II 1绪 论 1 1.1研究背景 1 1.2设计原则 1 1.3论文的组织结构 2 2 相关技术简介 3 2.1Java技术 3 2.2B/S结构 3 2.3MYSQL数据库 4 2.4Springboot框架 4 3 系统分析 6 3.1可行性分析 6 3.1.1技术可行性 6 3.1.2操作可行性 6 3.1.3经济可行性 6 3.1.4法律…

[OpenGL]使用OpenGL绘制带纹理三角形

一、简介 本文介绍了如何使用使用OpenGL绘制带纹理三角形。 在绘制带纹理的三角形时&#xff0c; 首先使用.h读取准备好的.png格式的图片作为纹理&#xff0c;然后在fragment shader中使用 ... in vec2 textureCoord; uniform sampler2D aTexture1; void main() {FragColor …

嵌入式 开发技巧和经验分享

文章目录 前言嵌入式 开发技巧和经验分享目录1.1嵌入式 系统的 定义1.2 嵌入式 操作系统的介绍1.3 嵌入式 开发环境1.4 编译工具链和优化1.5 嵌入式系统软件开发1.6 嵌入式SDK开发2.1选择移植的系统-FreeRtos2.2FreeRtos 移植步骤2.3 系统移植之中断处理2.4系统移植之内存管理2…

奥比中光深度相机相关使用内容

奥比中光深度相机相关使用内容 Windows平台测试官方软件关于python环境的配置1、安装CMake2、安装Visual Studio3、项目地址下载4、配置Visual Studio5、完成基于Python的SDK配置官网教学视频地址 3D视觉开发者社区 官方文档地址 效果: Windows平台测试官方软件 Window…

《高等代数》线性相关和线性无关(应用)

说明&#xff1a;此文章用于本人复习巩固&#xff0c;如果也能帮到大家那就更加有意义了。 注&#xff1a;1&#xff09;线性相关和线性无关的证明方法中较为常用的方法是利用秩和定义来证明。 2&#xff09;此外&#xff0c;线性相关和线性无关的证明常常也会用到反证法。 3&…