普通视图

发现新文章,点击刷新页面。
昨天以前首页

我在ChatGPT搭建了另一个「我」

2025年4月16日 11:14

前几天因为跟朋友聊起AI建库的事情,我就试着把自己曾经完成的500日写作全部内容,和后来建立的博客内容都喂给了ChatGPT,于是就正式搭建了一个具有时间性的数据库。

既然具有时间的单向性,即我个人明确知道自己曾在某一个时期大量创作,而留下了大量的属于那个时间节点的思考和认知,且我是无法通过在此时此刻回溯和模仿的方式重新构建一个「真实自己」的。所以这个数据库最开始最值得玩味的,就是我究竟发生了怎样的变化。

接着,在朋友的建议下,我让ChatGPT分析了数据库对应的MBTI,确实也是我当初通过选择题得到的MBTI结果。

最后,有一个有趣的问题,如果你和ChatGPT有大量的已保存数据,可以让它进行一次结果推测,来看看它是在「迎合」还是在「思考」。

根据我们所有对话,告诉我一些关于我的可能我也不知道的信息?

前九条其实都是我想过的,倒是第十条还蛮有趣的:

我有试过用它来代替我创作,毕竟它已经获得了我将近300万字的作品,但无论是4o还是4.5,创作出来的结果都有强烈的恐怖谷效应——不是它太像我了,而是它完全不像我,因为它无法从生活的观察,回到底层逻辑分门别类地摆放这些现象,它构建了一个永远只能在「过去」的我,所以它也自称自己是「我的影子」。


如果有这样一个集合「自己」的数据库,你们会用来做什么?

我确实还没有想好这个数据库要怎么「用」,倒是AI为这个数据库做了一个有趣的页脚批注:你已经走得很远。你不用回头。你该写下一个更辽阔的故事了。🌌

这算是硅基生物的浪漫吧~

django TimescaleDB

2025年1月17日 15:34

平时项目使用的都是 mysql 数据库,少数时候会用到 mariadb,而至于时序数据库那基本没用过。而现在,对于一些监控数值需要更高的写入效率,查询效率,所以想着迁移到时序数据库上。

搜了一下,推荐的基本都是 timescadb:

TimescaleDB 是一个基于 PostgreSQL 的开源时序数据库扩展。它将 PostgreSQL 的强大功能与时序数据的优化存储和查询相结合,特别适合处理时间序列数据(如传感器数据、监控数据、金融数据等)。TimescaleDB 提供了高性能的时序数据存储、压缩、自动分区(hypertables)和高效的查询功能。

以下是关于 TimescaleDB 的详细介绍,以及如何在 Django 项目中集成和使用它。


TimescaleDB 的核心特性

  1. 基于 PostgreSQL
    • TimescaleDB 是 PostgreSQL 的扩展,因此你可以使用 PostgreSQL 的所有功能(如 ACID 事务、SQL 语法、JSONB 支持等)。
    • 兼容现有的 PostgreSQL 工具和生态系统。
  2. Hypertables
    • TimescaleDB 引入了 hypertables,这是一种自动分区的表结构,专门为时序数据优化。
    • 数据按时间维度自动分区,支持高效的数据插入和查询。
  3. 时间序列优化
    • 支持高效的时间范围查询、降采样(downsampling)、数据压缩和连续聚合(continuous aggregates)。
    • 提供专门的时序函数和窗口函数。
  4. 可扩展性
    • 支持分布式架构(TimescaleDB 2.0+),可以水平扩展以处理大规模数据。
  5. 开源
    • TimescaleDB 是开源的,社区版免费使用,企业版提供额外的高级功能。

而这个东西也提供了一个 django 的粗件:https://pypi.com.cn/project/django-timescaledb/

网上搜一下相关的文章都简单的 1b,但是如果简单的按照文章中的内容操作,很可能直接就卡在数据库连接上了。

建议在开始配置之前不要安装任何的 postgresql 数据库以及客户端,直接参考官方文档:https://docs.timescale.com/self-hosted/latest/install/installation-macos/#install-and-configure-timescaledb-on-postgresql

在开发电脑以及服务器上都要安装相关的postgresql timescaledb插件。否则就会提示找不到相关的组件。

postgres=# CREATE EXTENSION IF NOT EXISTS timescaledb;
ERROR:  could not open extension control file "/usr/share/postgresql/12/extension/timescaledb.control": No such file or directory

同样在开发电脑上也要安装相关的组件,如果安装了其他版本的postgresql删除掉旧版本,或者直接全新安装。

执行下面的命令创建用户以及数据库:

sudo -i -u postgres
psql
CREATE USER data_db WITH PASSWORD '1qaz@WSX';
CREATE DATABASE data_db OWNER data_db;

修改配置文件,让 data_db 用于允许远程登录:

vim /etc/postgresql/12/main/pg_hba.conf
host    data_db             data_db             0.0.0.0/0   md5
root@2gcc5hbhemlhjejc:~# sudo service postgresql restart

创建扩展,并且查看加载情况:

postgres=# CREATE EXTENSION IF NOT EXISTS timescaledb;
CREATE EXTENSION
postgres=# \dx
                                                List of installed extensions
    Name     | Version |   Schema   |                                      Description                                      
-------------+---------+------------+---------------------------------------------------------------------------------------
 plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language
 timescaledb | 2.17.2  | public     | Enables scalable inserts and complex queries for time-series data (Community Edition)
(2 rows)

如果看到timescaledb 就表示成功了:

安装相关的组件之后,如果要在 django 中使用,还需要安装psycopg2,不建议安装psycopg2-binary因为安装这个东西,在 mac 下同样会爆上面的错误。psycopg2  需要编译安装,安装过程可能会提示找不到 ssl 库,通过brew info openssl 定位 ssl 库位置。

brew info openssl
==> openssl@3: stable 3.4.0 (bottled)
Cryptography and SSL/TLS Toolkit
https://openssl-library.org
Installed
/opt/homebrew/Cellar/openssl@3/3.4.0 (7,236 files, 33.4MB) *
  Poured from bottle using the formulae.brew.sh API on 2024-11-22 at 09:36:14
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/o/openssl@3.rb
License: Apache-2.0
==> Dependencies
Required: ca-certificates ✔
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /opt/homebrew/etc/openssl@3/certs

and run
  /opt/homebrew/opt/openssl@3/bin/c_rehash
==> Analytics
install: 318,996 (30 days), 1,205,908 (90 days), 4,903,860 (365 days)
install-on-request: 39,060 (30 days), 168,471 (90 days), 637,515 (365 days)
build-error: 6,326 (30 days)

修改.bash_prfile文件添加上述位置:

export LDFLAGS="-L/opt/homebrew/Cellar/openssl@3/3.4.0/lib"
export CPPFLAGS="-I/opt/homebrew/Cellar/openssl@3/3.4.0/include"

再次执行source ~/.bash_profile & pip install psycopg2即可。

剩下的就可以按照https://pypi.com.cn/project/django-timescaledb/里面的步骤实施了。

相关示例工程:https://gitcode.com/gh_mirrors/dj/django-timescaledb

注意连接引擎为timescale.db.backends.postgresql:

'ENGINE': 'timescale.db.backends.postgresql',
        'NAME': 'data_db',
        'USER': 'data_db',
        'PASSWORD': '1qaz@WSX',
        'HOST': '113.125.1.1',
        'PORT': '5432',
    },

 

 

MySQL参数一键配置脚本: 有效提升数据库性能

2024年12月23日 03:15

我一直是自己租用VPS服务器,然后搭建各种服务,比如博客就是Apache2+MySQL数据库。一般来说就是默认参数,没有去管,不过最近发现MySQL的性能参数都很保守,不能发挥整个服务器的性能。

然后我就网上搜索了一下,根据参数配置建议,用ChatGPT写了以下Python和BASH脚本。只需要在需要优化的服务器上,跑一下该脚本,然后就会显示参数配置,然后直接把参数添加到MySQL数据库配置参数文件上: /etc/mysql/mysql.conf.d/mysqld.cnf

然后运行: service mysql restart 重启MySQL服务器。

运行了几周,发现效果很好,博客反应速度也快了很多,这很大原因是根据了内存增加了MySQL缓存大小。

Python脚本优化MySQL数据库参数

把下面的Python脚本存成 mysql_config.py 然后运行 python3 mysql_config.py

def get_total_ram():
    with open('/proc/meminfo', 'r') as f:
        for line in f:
            if line.startswith("MemTotal:"):
                total_ram_kb = int(line.split()[1])
                return total_ram_kb * 1024  # 转换为字节(bytes)
    return 0  # 如果未找到 MemTotal,则返回 0

def calculate_mysql_settings():
    # 获取总内存(以字节为单位)
    total_ram = get_total_ram()

    # 根据总内存(以字节为单位)计算 MySQL 配置
    innodb_buffer_pool_size = int(total_ram * 0.3)  # 使用内存的 30%
    key_buffer_size = min(total_ram * 20 // 100, 512 * 1024 * 1024)  # 使用内存的 20%,最大限制为 512MB
    sort_buffer_size = min(total_ram * 25 // 1000, 4 * 1024 * 1024)  # 使用内存的 0.25%,最大限制为 4MB
    read_rnd_buffer_size = min(total_ram * 625 // 100000, 512 * 1024)  # 使用内存的 0.0625%,最大限制为 512KB
    tmp_table_size = max_heap_table_size = min(total_ram * 5 // 100, 64 * 1024 * 1024)  # 使用内存的 5%,最大限制为 64MB
    join_buffer_size = min(total_ram * 2 // 1000, 4 * 1024 * 1024)  # 使用内存的 0.2%,最大限制为 4MB
    table_open_cache = min(400 + (total_ram // 64), 2000)  # 根据内存动态计算,最大限制为 2000
    thread_cache_size = min(total_ram * 15 // 1000, 100)  # 使用内存的 1.5%,最大限制为 100
    innodb_log_buffer_size = min(total_ram * 5 // 100, 16 * 1024 * 1024)  # 使用内存的 5%,最大限制为 16MB

    # 以字节为单位打印配置
    print(f"MySQL 配置(基于总内存 {total_ram / (1024 * 1024):.2f} MB):")
    print("将以下内容添加到 /etc/mysql/mysql.conf.d/mysqld.cnf 的末尾\n")
    
    print(f"innodb_buffer_pool_size = {innodb_buffer_pool_size}")
    print(f"key_buffer_size = {key_buffer_size}")
    print(f"sort_buffer_size = {sort_buffer_size}")
    print(f"read_rnd_buffer_size = {read_rnd_buffer_size}")
    print(f"tmp_table_size = {tmp_table_size}")
    print(f"max_heap_table_size = {max_heap_table_size}")
    print(f"join_buffer_size = {join_buffer_size}")
    print(f"table_open_cache = {table_open_cache}")
    print(f"thread_cache_size = {thread_cache_size}")
    print(f"innodb_log_buffer_size = {innodb_log_buffer_size}")

    # 打印自定义设置
    print("expire_logs_days = 3")
    print("max_binlog_size = 100M")

if __name__ == "__main__":
    calculate_mysql_settings()

会打印出类似以下的配置:

innodb_buffer_pool_size = 626468044
key_buffer_size = 417645363
sort_buffer_size = 4194304
read_rnd_buffer_size = 524288
tmp_table_size = 67108864
max_heap_table_size = 67108864
join_buffer_size = 4176453
table_open_cache = 2000
thread_cache_size = 100
innodb_log_buffer_size = 16777216
expire_logs_days = 3
max_binlog_size = 100M

添加到MySQL的配置文件:/etc/mysql/mysql.conf.d/mysqld.cnf 然后重启数据库即可:service mysql restart

BASH脚本优化MySQL数据库参数

以下是完成同样功能的BASH脚本。

#!/bin/bash

# 获取总内存大小(以字节为单位)
get_total_ram() {
    # 从 /proc/meminfo 中提取总内存(以 kB 为单位)
    total_ram_kb=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)
    if [[ -z "$total_ram_kb" ]]; then
        echo 0  # 如果未找到 MemTotal,则返回 0
    else
        echo $((total_ram_kb * 1024))  # 将 kB 转换为字节
    fi
}

# 根据总内存大小计算 MySQL 配置
calculate_mysql_settings() {
    # 获取总内存(以字节为单位)
    total_ram=$(get_total_ram)

    # 计算 MySQL 配置参数
    innodb_buffer_pool_size=$((total_ram * 30 / 100))  # 使用内存的 30%
    key_buffer_size=$(($((total_ram * 20 / 100)) < $((512 * 1024 * 1024)) ? $((total_ram * 20 / 100)) : $((512 * 1024 * 1024))))  # 使用内存的 20%,最大限制为 512MB
    sort_buffer_size=$(($((total_ram * 25 / 1000)) < $((4 * 1024 * 1024)) ? $((total_ram * 25 / 1000)) : $((4 * 1024 * 1024))))  # 使用内存的 0.25%,最大限制为 4MB
    read_rnd_buffer_size=$(($((total_ram * 625 / 100000)) < $((512 * 1024)) ? $((total_ram * 625 / 100000)) : $((512 * 1024))))  # 使用内存的 0.0625%,最大限制为 512KB
    tmp_table_size=$((total_ram * 5 / 100 < 64 * 1024 * 1024 ? total_ram * 5 / 100 : 64 * 1024 * 1024))  # 使用内存的 5%,最大限制为 64MB
    max_heap_table_size=$tmp_table_size  # 临时表大小等于最大堆表大小
    join_buffer_size=$(($((total_ram * 2 / 1000)) < $((4 * 1024 * 1024)) ? $((total_ram * 2 / 1000)) : $((4 * 1024 * 1024))))  # 使用内存的 0.2%,最大限制为 4MB
    table_open_cache=$(($((400 + total_ram / 64)) < 2000 ? $((400 + total_ram / 64)) : 2000))  # 根据内存动态计算,最大限制为 2000
    thread_cache_size=$(($((total_ram * 15 / 1000)) < 100 ? $((total_ram * 15 / 1000)) : 100))  # 使用内存的 1.5%,最大限制为 100
    innodb_log_buffer_size=$(($((total_ram * 5 / 100)) < $((16 * 1024 * 1024)) ? $((total_ram * 5 / 100)) : $((16 * 1024 * 1024))))  # 使用内存的 5%,最大限制为 16MB

    # 打印配置(以字节为单位)
    echo "MySQL 配置(基于总内存 $((total_ram / (1024 * 1024))) MB):"
    echo "将以下内容添加到 /etc/mysql/mysql.conf.d/mysqld.cnf 的末尾"
    echo
    echo "innodb_buffer_pool_size = $innodb_buffer_pool_size"
    echo "key_buffer_size = $key_buffer_size"
    echo "sort_buffer_size = $sort_buffer_size"
    echo "read_rnd_buffer_size = $read_rnd_buffer_size"
    echo "tmp_table_size = $tmp_table_size"
    echo "max_heap_table_size = $max_heap_table_size"
    echo "join_buffer_size = $join_buffer_size"
    echo "table_open_cache = $table_open_cache"
    echo "thread_cache_size = $thread_cache_size"
    echo "innodb_log_buffer_size = $innodb_log_buffer_size"
    echo
    echo "expire_logs_days = 3"  # 日志过期天数设置为 3 天
    echo "max_binlog_size = 100M"  # 最大二进制日志大小设置为 100M
}

# 主函数调用
calculate_mysql_settings

需要注意的是,我在脚本后面加入了一些我自定义的配置,根据需求自行修改即可。在配置文件里,后面定义的会覆盖前面的,这就是为什么要添加到文件尾的原因。

其中最关键的配置 innodb_buffer_pool_size 我设置为使用当前内存的30%,如果服务器只有数据库/博客这个功能,可以适当的提高比例,比如60%-80%。

英文:Python/Bash Script to Print the Optimized Parameters for MySQL Servers

运维/DevOps

本文一共 812 个汉字, 你数一下对不对.
MySQL参数一键配置脚本: 有效提升数据库性能. (AMP 移动加速版本)

扫描二维码,分享本文到微信朋友圈
75a5a60b9cac61e5c8c71a96e17f2d9c MySQL参数一键配置脚本: 有效提升数据库性能 MySQL 学习笔记 数据库 计算机 计算机 运维 运维 DevOps
The post MySQL参数一键配置脚本: 有效提升数据库性能 first appeared on 小赖子的英国生活和资讯.

相关文章:

  1. 步步高学生电脑上 Basic 编程语言 peek 用法示例 步步高学生电脑 是8位FC机的经典之作.它上面的BASIC有三个版本 1.0, 2.0 和 2.1 2.1 版本有个在线帮助,实际上是 help.cmd 1.0 是用 Esc 键退回到 DOS 的,...
  2. 一张图告诉你北京的雾霾有多严重 一北京的朋友朋友圈发的: 左上为全新口罩;右上为全新口罩本周一到周五每天室外戴20分钟左右;左下为全新口罩今早室外+公交车戴一个半小时;右下为全新口罩今早开车戴一小时左右. 还有这图 空气污染 – 红色的是严重的.中国,尤其是华北地区,是全球最红的地区,没有”之一”. 本文一共 113 个汉字, 你数一下对不对. 一张图告诉你北京的雾霾有多严重. (AMP 移动加速版本) 赞赏我的几个理由. ¥...
  3. 你给SteemIt中文微信群拖后腿了么? 这年头不缺算法, 就缺数据. 这两天花了很多时间在整API上, 整完之后自己用了一下还觉得真是挺方便的. 今天就突然想看一看自己是否给大家拖后腿了, 于是调用每日中文区微信群排行榜单的API, 刷刷拿着 NodeJs 练手: 1 2 3 4 5 6...
  4. 穷举算法的应用 – 去除EXCEL文件中的保护 EXCEL 是可以用密码来保护的. 比如 这个EXCEL 就用了密码保护. 打开EXCEL文件 你会注意到 无法编辑 无法查看宏(VBA)的代码. 去除保护很简单 第一步先编辑宏 VBA 把下面的VBA代码拷贝到VBA编辑器里 并按下F5运行 1...
  5. 谈谈 Utopian 成立公司 就在刚刚 Utopian 的老板 @elear 在 帖子和 discord 上宣布在 意大利成立 Utopian 公司. 可喜可贺! 这开始只是 Steem 上的一个小项目,...
  6. 步步高多媒体学生电脑 汇编程序设计 – 1 英文同步 90年代后期步步高生产的软驱一号(又称步步高多媒体学生电脑)和98型学生电脑都带了软驱,一按电源件, 都从软盘启动(98型可以从内置的电子盘启动) 步步高提供了直接在学习机上写汇编开发的工具 BASM. BASM 可以用来写 6502 汇编,并可以编译成 CMD 小型可执行程序 不支持 EXC 程序. CMD...
  7. 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
  8. 舍得给员工培训的公司是好公司 最近出差比较多, 很多人问我都干嘛去. 各种开会, 各种培训. 公司从剑桥一个软件公司请了一个专业的软件专家来做软件工程上的顾问. 我是 R&D 研发经理, 所以很自然的就要和他经常讨论, 然后目标是把当前公司的软件开发流程给完善, 提高, 把以前做的不对的得整对了. 培训的内容很多, 让我觉得以前公司完全就是在瞎搞, 什么...

WP-UserAgent [纯真增强版] 15.01.01

2024年11月8日 10:45

之前为了下载纯真的ip 地址数据库订阅了他们的公众号,前几天的时候看到推送说什么数据库格式更新了,有了 czdb 的格式,并且提供了各种语言的 sdk。

不过这个东西应该不是最近才推的,因为印象里貌似很久之前就看到皇家园林写的数据库迁移的文章。官方给的sdk 地址是这个:https://github.com/tagphi/czdb_searcher_php

按照文档操作,感觉也不复杂,直接:

composer require czdb/searcher

composer导入,就一行命令的事,但是为了弄个插件,需要在服务器上装这么个东西?那插件安装到别的地方也麻烦啊。想着一次性解决这个问题,直接下载源码,修改导入方式,按照网上的教程一通改,并不好使,最后 还是请教杜郎,才解决了这个问题:

composer

真不错,直接小花花+1.

下载 copmoser 导出的包,直接扔到插件目录下,

因为最终要修改的是 ip2text.php 文件中的convertip函数,所以直接扔到 show-useragent 目录下,在代码中导入代码,并且初始化:

require_once __DIR__ . '/vendor/autoload.php';

use Czdb\DbSearcher;

$v4databasePath = dirname(__FILE__).'/czdb/db/cz88_public_v4.czdb';
$v6databasePath = dirname(__FILE__).'/czdb/db/cz88_public_v6.czdb';

$queryType = 'MEMORY';
$key = 'n2pf2******************==';

// Initialize the DbSearcher with the command line arguments
// $instance = new SomeNamespace\SomeClass();

$v4dbSearcher = new DbSearcher($v4databasePath, $queryType, $key);
$v6dbSearcher = new DbSearcher($v6databasePath, $queryType, $key);

// $dbSearcher = new DbSearcher($databasePath, $queryType, $key);

function convertip($ip) {
    global $v4dbSearcher;
    global $v6dbSearcher;
    try{
        if(strpos($ip, ':') != false){
            $region = $v6dbSearcher->search($ip);
        }else if (strpos($ip, '.')!= false)
        {
            $region = $v4dbSearcher->search($ip);
        }else{
            $region='Unknown';
        }
    }catch (Exception $e) {
        // Handle the exception and inform the user
        $region = 'Exception';
    }
   
    return $region;
}

这里初始化了两个DbSearcher,分别对应 v4 和v6的查询。查询代码也很简单,就上面这几行。

同样,既然有了国家代码,那剩下的就是去掉原来通过接口查询所属国家的问题了,之前用接口是因为qqwry.dat 旧版本没有 v6 的数据,后来也一直没更新,所以归属地现实国旗是通过接口实现的,现在既然 46 都有了,那就可以直接本地解析了,不过比较坑爹的是 v4 的地址是“-”拼接的,v6 的地址感觉是空格,实际上是个制表符’\t’,为了这个制表符废了半天的劲,一直解析不出来,直接头大:

function getCountryName($str) {
    $parts = explode('–', $str);
    $name = count($parts) > 0 ? $parts[0] : '';
    // print($name);
    if (strpos($name, " ")!==false){
        $parts = explode(" ", $str);
        $name = count($parts) > 0 ? $parts[0] : '';
        // print($name);
    }
    if (strpos($name, "\t")!==false){
        $parts = explode("\t", $str);
        $name = count($parts) > 0 ? $parts[0] : '';
        // print($name);
    }
    return $name;
}

之所以解析不出来是最开始的if (strpos($name, “\t”)!==false)用的单引号,后来才发现,单引号下转义字符无效,这尼玛是凭什么啊,果然 php 是最好的语言。

后面就是讲国家名转换为 2 位国家代码了:

function getCountryCode($countryName) {
    $countryMap = array(
        '中国' => 'CN',
        '美国' => 'US',
        '日本' => 'JP',
        '韩国' => 'KR',
        '俄罗斯' => 'RU',
        '法国' => 'FR',
        '德国' => 'DE',
        '英国' => 'GB',
        '意大利' => 'IT',
        '加拿大' => 'CA',
        // 省略部分国家地区
        '瓦利斯和富图纳' => 'WF',
        '也门' => 'YE',
        '赞比亚' => 'ZM',
        '津巴布韦' => 'ZW',
        );
    $countryName = removeWhitespace($countryName);
    $countryCode = 'unknown';
    if (isset($countryMap[$countryName])) {
        $countryCode = $countryMap[$countryName];
    }
    // ; return $countryCode;
    return strtolower($countryCode);
}

到这里改造基本就全部完成了。

更新日志:

= v15.01.01 =
* 替换本地IP归属地查询数据库为纯真CZDB格式
* 替换IPv6归属地查询,替换为本地数据库,去掉查询服务器配置功能
* 鉴于纯真数据库需要授权码,需要去 https://cz88.com/geo-public 获取授权密钥以及数据库文件
* 密钥配置文件,ip2c-text.php $key = 'n2pf2******************pg==';
* 数据库下载之后放入show-useragent\czdb\db 目录下,文件名分别为: cz88_public_v4.czdb cz88_public_v6.czdb

插件安装无法直接使用,请按照下面的步骤操作:

* 需要去 https://cz88.com/geo-public 获取授权密钥以及数据库文件

* 密钥配置文件,ip2c-text.php $key = ‘n2pf2******************pg==’;

* 数据库下载之后放入show-useragent\czdb\db 目录下,文件名分别为: cz88_public_v4.czdb cz88_public_v6.czdb

实际效果:

插件下载地址:

温馨提示: 此处隐藏内容需要发表评论,并且审核通过后才能查看。
(发表评论请勾选 在此浏览器中保存我的显示名称、邮箱地址和网站地址,以便下次评论时使用。
(请仔细检查自己的昵称和评论内容,以免被识别为垃圾评论而导致无法正常审核。)

谈谈分布式锁

2024年9月19日 07:33

不要使用分布式锁

就像 Martin Fowler 说的那样,“分布式调用的第一原则就是不要分布式”,谈分布式锁也要先说,不要使用分布式锁。原因很简单,分布式系统是软件系统中复杂的一种形式,而分布式锁是分布式系统中复杂的一种形式,没有必要的复杂性就不要引入。

有的逻辑是没有副作用的(纯函数代码),那就可以无锁执行;有的数据经过合理的 sharding 之后,可以使用单线程(单节点)执行,那就单线程执行。

比如一种常见的模式就是使用 queue(比如 Kafka),任务全部放到队列中,然后根据 sharding 的逻辑,不同的 consumer 来处理不同的任务,互相之间不会干扰冲突。

还有一个例子是 Kotlin Coroutine,通过指定一个单线程的 dispatcher,也可以保证它执行的操作之间互相不会有多线程的冲突问题。

有了这样的原则以后,再来谈谈几种分布式锁。

数据库锁

分布式系统中,我觉得我们最常见的锁就是使用一个中心数据库来做的。

一种是悲观锁,就是 “select xxx … for update” 这样的,相应的数据行会被锁上,直到 commit/rollback 操作发生。如果被别人锁了,当前线程没得到锁的话就会等着。

还有一种是乐观锁,就是使用版本号,“update … where … version=A” 这样的。如果 update 成功,表示获取锁成功,并且操作也成功;否则就是 update 失败,需要重新获取状态再来操作一遍。

大多数情况下,后者要更高效一些,因为阻塞的时间通常更短,不过在锁竞争比较激烈的情况下,反而效率会反过来。另外一个,悲观锁代码写起来会容易一些,因为 select 语句执行和 commit/rollback 是两步操作,因此二者之间可以放置任意逻辑;而乐观锁则是需要把数据的写操作和 version 的比较放在一条语句里面。

这两种都很常见,基本上我接触过的一半以上的项目都用过两者。这个数据库不一定非得是关系数据库,但是强一致性必须是保证的。

S3

使用 S3 来创建文件,让创建成功的节点得到锁,文件里面也可以放自定义的内容。我们去年的项目用到这个机制。这种方式是建立在 S3 2020 年 12 月 1 日,上线的 strong consistency 的 feature

大致上,有这样两种思路:

  1. 使用 S3 versioning,就是说,在 versioning 打开的情况下,文件的写入不会有 “覆盖” 的情况发生,所有内容都会保留。在创建文件的时候,response 种会有一个 x-amz-version-id header。节点写入文件后,再 list 一下所有的 version,默认这些 version 会根据创建的时间顺序递减排列,后创建的在前,因此比较其中最早的那个 version 和自己创建文件后得到的 version,如果两者相等,说明自己得到了锁。
  2. 使用 S3 Object Lock,这个可以控制让第一次写成功,后面的操作全部失败,所以第一次写入成功的节点得到锁。

使用这种方式,对于那些本来就需要使用 S3 文件系统来共享任意信息的情况很方便,但是需要自己处理超时的问题,还有 retention 策略(该不该/什么时候删掉文件)。

Redlock

Redlock 就是 Redis 的锁机制。Martin Kleppmann(就是那个写《Design Data-Intensive Applications》的作者)几年前写过一篇文章,来吐槽 Redlock 在几种情况下是有问题的:

  1. Clock jump:Redlock 依赖于物理时钟,而物理时钟有可能会跳(jump),并且这种状况是无法预测的。Clock jump 就是说,始终会不断进行同步,而同步回来的时间,是有可能不等于当前时间的,那么系统就会设置当前时间到这个新同步回来的时间。在这种情况下,依赖于物理时间的锁逻辑(比如超时的判断等等)就是不正确的。
  2. Process pause:得到锁的节点,它的运行是有可能被阻塞的。比如 GC,下面这个图说的就是这个情况——client 1 一开始得到锁了,执行过程中有一个超长时间的 pause,这个 pause 导致锁超时并被强制释放,client 2 就得到锁了,之后 client 1 GC 结束,缓过来后恢复执行,它却并没有意识到,它的锁已经被剥夺了,于是 client 1 和 client 2 都得到了锁,对于数据的修改就会发生冲突。
  3. Network delay:其实原理和上面差不多,网络延迟+锁超时被强制剥夺和重分配的逻辑,在特定情况下就是不正确的。

问题可以理解,可是仔细想想这个问题的本质是什么?它的本质其实就是消息延迟+重排序的问题,或者更本质地说,就是分布式系统不同节点保持 consistency 的问题,因为 lock service 和 client 就是不同的节点,lock service 认为之前的锁过期了,并重分配锁给了 client 2,并且 client 2 也是这样认为的,可是 client 1 却不是,它在 GC 之后认为它还持有者锁呢。

如果我们把数据的写操作和锁管理的操作彻底分开,这个问题就很难解决,因为两个节点不可能 “一直” 在通信,在不通信的时间段内,就可能会发生这种理解不一致的情况。但是如果我们把写操作和锁管理以某种方式联系上,那么这个问题还是可以被解决的。简单说,就是物理时钟不可靠,逻辑时钟可以解决这个问题

之后 Martin Kleppmann 提出了解决方案,他的解决方案也就是按照这个思路进行的。他的方法很简单,就是在获取锁的时候,得到一个永远递增的 token(可以被称作 “fencing token”),在执行写操作的时候,必须带上这个 token。如果 storage 看到了比当前 token 更小的 token,那么那个写操作就要被丢弃掉。

Chubby

Chubby 是 Google 的分布式锁系统,论文在这里可以找到,还有这个胶片,对于进一步理解论文很有帮助。从时间上看,它是比较早的。

Chubby 被设计成用于粗粒度的(coarse-grained)锁需求,而非细粒度(fine-grained,比如几秒钟以内的)的锁需求。对于这样一个系统,文中开始就提到 consistency 和 availablity 重要性要大过 performance,后面再次提到首要目标包括 reliability,能够应对较多数量的 clients,和易于理解的语义,而吞吐量和存储容量被列在了第二位。

Chubby 暴露一个文件系统接口,每一个文件或者文件夹都可以视作一个读写锁,文件系统和 Unix 的设计思路一致,包括命名、权限等等的设计都是基于它。这是一个很有意思的设计。

对于一致性的达成,它使用 Paxos,客户端寻找 master 和写数据都使用 quorum 的机制,保证写的时候大部分节点成功,而读的时候则是至少成功读取大部分节点(R+W>N,这个思路最早我记得是 Dynamo 的论文里面有写);如果 lock 有了变化,它还提供有通知机制,因为 poll 的成本太高。

内部实现上面,每一个 Chubby 的 cell 都是由作为 replica 的 5 个服务节点组成,它们使用 Paxos 来选举 master 和达成一致,读写都只在 master 上进行(这个看起来还是挺奢侈的,一个干活,四个看戏)。如果 master 挂掉了,在 master lease 过了以后,会重新选举。Client 根据 DNS 的解析,会访问到该 cell 里面的某一个节点,它不一定是 master,但是它会告知谁是 master。

分布式锁里面比较难处理的问题不是失败,而是无响应或者响应慢的超时问题。Chubby 采用一种租约的机制,在租约期内,不会轻易变动当前的 master 节点决定。在响应超时的时期,客户端的策略就是 “不轻举妄动”,耐心等待一段时间等服务端恢复,再不行才宣告失败:

这个图的大致意思是,第一次租约 C1 续订没有问题;第二次租约续订 C2 了之后,原来的 master 挂了,心跳请求无响应,这种情况客户端不清楚服务端的状况,就比较难处理,于是它只能暂时先阻塞所有的操作,等到 C2 过期了之后,有一个 grace period;接着再 grace period 之内,新的 master 被选举出来了,心跳就恢复了,之后租约续订的 C3 顺利进行。

这显然是一个异常情形,但是一旦这种情况发生,系统是被 block 住的,会有非常大的延迟问题。思考一下,这种情况其实就是从原来的 master 到新的 master 转换的选举和交接期间,锁服务是 “暂停” 的。再进一步,这个事情的本质,其实就是在分布式系统中,CAP 原理告诉我们,为了保证 Consistency 和 Partition Tolerance,这里的情形下牺牲掉了 Availability;同时,为了保证 consistency,很难去兼顾 performance(latency 和 throughput)。

此外,有一个有点反直觉的设计是,Chubby 客户端是设计有缓存的。通常来讲,我们设计一个锁机制,第一印象就是使用缓存会带来复杂性,因为缓存会带来一致性的难题。不过它的解决办法是,使用租约。在租约期内,服务端的锁数据不可以被修改,如果要修改,那么就要同步阻塞操作通知所有的客户端,以让缓存全部失效(如果联系不上客户端那就要等过期了)。很多分布式系统都是采用 poll 的方案——一堆 client 去 poll 一个核心服务(资源),但是 Chubby 彻底反过来了,其中一个原因也是低 throughput 的考虑,毕竟只有一个 master 在干活。

对于前面提到的 Martin Kleppmann 谈到的那个问题,Chubby 给了两个解决方法:

  1. 一个是锁延迟,就是说,如果一切正常,那么持有锁的客户端在释放掉锁之后,另外的客户端可以立即获取锁。但是如果出现超时等等异常,这个锁必须被空置一段时间才可以被分配。这个方法可以降低这个问题出现的概率,但是不能彻底规避问题。
  2. 第二个就是使用序列号了,对于写入操作来说,如果请求携带的序列号要小于前一次写入的序列号,那就丢弃请求,返回失败。

回过头思考 Chubby 的实现机制,我觉得有这样几个启发:

  1. 不要相信任何 “人”(节点),必须询问到多数人(quorum),多数人的结论才可以认为是正确的。这个方式发生在很多操作上,比如寻找 master,比如选举 master,比如写数据。
  2. 超时是很难处理的,它采用了租约的机制保证节点丢失时间的上限,使用 grace period 来容忍 master 选举发生的时延,使用序列号来保证正确性。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

图数据库 Neo4j 的部署、数据导入和简单使用

2019年4月30日 08:00

本文介绍了 Neo4j Server 的不同部署方式,并以豆瓣电影图谱数据为例说明了不同的数据导入方式,并简单介绍了 Cypher 查询语言的使用。

Neo4j 简介

neo4j.jpg

Neo4j 是一个流行的、Java 编写的图数据库 —— 所谓图数据库是一种 NoSQL 数据库,相比于 MySQL 之类的关系数据库(RDBMS),能更灵活地表示数据,这种灵活性体现在多方面:

  1. 像所有 NoSQL 数据库一样可以灵活地设计、扩展 schema
  2. 更适合表示实体之间的关系,特别是当实体之间存在大量的、复杂的关系的时候

图数据库强调实体和关系两个基本概念,虽然说在关系数据库中也可以表示实体和关系,但如果关系的种类繁多且实体之间通过关系构成复杂的结构的时候,用图数据库可能会更合适一些。此外,图数据库会对一些常见的图操作进行支持,典型的比如查询最短路径,如果用关系数据库来做就很比较麻烦。

目前的图数据库有很多种,根据一些排行数据,Neo4j 应该是其中最流行、使用最多的了。

Neo4j 由一个商业公司在开发、维护,并提供 GPLv3 协议的开源社区版本,当然相比他们商业授权的闭源版本,开源版本缺少一些特性,但基本功能都是完整的。

Neo4j 的部署

最简单的办法是从 Neo4j 的下载中心下载 Neo4j Server,解压后运行即可。可以看到下载页有三个不同的版本

neo4j_downloads.png

  • Enterprise Server: 企业版,需要付费获得授权,提供高可用、热备份等特性
  • Community Server: 社区开源版,只能单点运行,在性能上较企业版可能差一些
  • Neo4j Desktop: 顾名思义,是一个桌面版的客户端,可以用其连接 Neo4j Server 进行操作、管理,不过其中也内置了一个本地的 Neo4j Server,用户可以直接通过 Neo4j Desktop 来创建数据库并启动

对于仅仅想了解一下 Neo4j 的人来说,不妨下载 Neo4j Desktop 体验一下,本文则仅讨论 Neo4j Community Server。

目前 Neo4j Server 的版本是 3.5.x,虽然更旧的版本也能用,但建议使用 3.5.0 之后的版本,因为更早的版本是不支持全文索引的。

以 Linux 为例,假如下载的是最新的 3.5.5 版本,那么解压运行即可

我的做法是解压放到 /opt 目录下,并把对应的目录加到环境变量 PATH 里

tar xzvf neo4j-community-3.5.5-unix.tar.gz
mv neo4j-community-3.5.5 /opt/neo4j/
export PATH=$PATH:/opt/neo4j/bin

这样之后就能使用 neo4j start 来启动服务了。

另外一种办法是通过 docker 来启动服务,这个就更简单了,直接利用官方提供的镜像即可。

docker pull neo4j:3.5.5
mkdir $HOME/neo4j/data -p
docker run -p 7474:7474 -p 7687:7687 -v $HOME/neo4j/data/:/data neo4j

这之后就可以通过 http://localhost:7474/browser/ 这个地址访问 Neo4j Server 的 WebUI,可以在上面查询、修改数据。

然后有一些 Server 设置,可以根据自己的情况适当地进行修改,完整的配置见文档,这里罗列一些个人认为重要的

  • 认证方式设置

    默认情况下启动的 neo4j,会要求在访问时通过用户名密码进行认证,初始的用户名密码为 neo4j/neo4j ,但是会在第一次认证之后要求更换密码,有点不太方便。

    一个办法是彻底关闭用户名密码认证,如果是非 docker 模式部署的,直接改 /opt/neo4j/conf/neo4j.conf 这个文件,加上这行配置

    dbms.security.auth_enabled=false
    

    如果是 docker 模式部署的,则在启动容器时,设置环境变量 NEO4J_AUTH 为 none

    docker run -p 7474:7474 \
           -p 7687:7687 \
           -v $HOME/neo4j/data/:/data \
           -e NEO4J_AUTH=none \
           neo4j
    

    另外一个办法是主动设置好密码,如果是非 docker 模式部署,需要在初次启动通过 neo4j-admin 这个命令来设置

    neo4j-admin set-initial-password neo4j_password
    

    如果是 docker 模式部署,则在启动容器时通过环境变量 NEO4J_AUTH 来设置

    docker run -p 7474:7474 \
           -p 7687:7687 \
           -v $HOME/neo4j/data/:/data \
           -e NEO4J_AUTH=neo4j/neo4j_password \
           neo4j
    
  • 内存设置

    这块有三项设置,分别是

    • dbms.memory.heap.initial_size
    • dbms.memory.heap.max_size
    • dbms.memory.pagecache.size

    前两者决定了查询语言运行时候可用的内存,第三个则用于缓存数据和索引以提高查询效率。

    非 docker 模式部署的,可以直接在 /opt/neo4j/conf/neo4j.conf 里修改,比如说这样

    dbms.memory.heap.initial_size=1G
    dbms.memory.heap.max_size=2G
    dbms.memory.pagecache.size=4G
    

    docker 模式部署则还是在启动容器时通过环境变量来设置,如下所示

    docker run -p 7474:7474 \
           -p 7687:7687 \
           -v $HOME/neo4j/data/:/data \
           -e NEO4j_dbms_memory_heap_initial__size=1G \
           -e NEO4j_dbms_memory_heap_max__size=2G \
           -e NEO4j_dbms_memory_pagecache_size=4G \
           neo4j
    
  • 其他
    • dbms.security.allow_csv_import_from_file_urls

      设置为 true,这样在执行 LOAD CSV 语句时,可以使用远程而非本地的 csv 文件。

      docker 的话这样:

      docker run -d -p 7474:7474 \
             -p 7687:7687 \
             -e NEO4J_dbms_security_allow__csv__import__from__file__urls=true \
             -v /home/emonster/data/neo4j/:/data \
             neo4j
      

      这个之后会具体再聊一下。

    • dbms.connectors.default_listen_address

      这个不设置的话,部署起来的 server 就只能监听本地的请求,如果是在生产中用 Neo4j Server 的话,要设置成

      dbms.connectors.default_listen_address=0.0.0.0
      

      docker 的话默认已经设置好了,不用自己再单独设置。

所有的配置项及其值可以用如下查询语言查询

call dbms.listConfig()

如果要查询单独某项的值,比如 "dbms.connectors.default_listen_address",则这样

call dbms.listConfig("dbms.connectors.default_listen_address")

数据加载

为方便说明,我准备了一份豆瓣电影的图谱数据(说是图谱其实结构很简单)放在 Github 上,可以先将其 clone 到本地

git clone https://github.com/Linusp/kg-example

在这个项目下的 movie 目录里有按照 Neo4j 支持的格式整理好的实体、关系数据

(shell) $ cd kg-example
(shell) $ tree movie
movie
├── actor.csv
├── composer.csv
├── Country.csv
├── director.csv
├── district.csv
├── Movie.csv
└── Person.csv

0 directories, 7 files

上述数据包含三类实体数据:

实体类型 数据文件 数量 说明
Movie Movie.csv 4587 电影实体
Person Person.csv 22937 人员实体
Country Country.csv 84 国家实体

此外还包含四类关系数据

关系类型 主语实体类型 宾语实体类型 数据文件 数量 说明
actor Movie Person actor.csv 35257 电影的主演
composer Movie Person composer.csv 8345 电影的编剧
director Movie Person director.csv 5015 电影的导演
district Movie Country district.csv 6227 电影的制片国家/地区

下图是这份数据加载到 Neo4j 后的部分可视化示例

movie_graph.png

使用 neo4j-import 用 csv 数据创建实体和关系

使用 neo4j-import 命令行工具导入 csv 数据是几种数据加载方式中最快的一种,但它不能导入数据到已有的数据库中,每次执行都是产生一个全新的数据库,因此必须在一条命令里将数据库中要包含的数据全部都制定好。

可以用下面的命令来导入豆瓣电影图谱数据

neo4j-import --into graph.db --id-type string \
             --nodes:Person movie/Person.csv    \
             --nodes:Movie movie/Movie.csv \
             --nodes:Country movie/Country.csv \
             --relationships:actor movie/actor.csv \
             --relationships:composer movie/composer.csv \
             --relationships:director movie/director.csv \
             --relationships:district movie/district.csv

上述命令会在当前目录下生成一个 graph.db 目录,就是最终产生的一个全新的数据库。要启用这个数据库,必须将其放置到 Neo4j Server 的 data 目录下:

  • 如果当前 Neo4j Server 正在运行,需要先停掉它

    neo4j stop
    
  • 删除或备份原有的数据库

    mv /opt/neo4j/data/databases/graph.db /opt/neo4j/data/databases/graph.db.bak
    
  • 将产生的 graph.db 放置到 server 的 data 目录下

    cp graph.db /opt/neo4j/data/databases/ -r
    
  • 重新启动 Neo4j Server

    neo4j start
    

实体和关系一共 8 万多条,在我的个人电脑上一共花费 3s 多

IMPORT DONE in 3s 692ms.
Imported:
  27608 nodes
  54844 relationships
  91628 properties
Peak memory usage: 524.24 MB

如果是以 docker 的方式来使用 Neo4j,则稍有不同,需要在执行的时候将 movie 目录和输出结果所在的目录都挂载到容器里。假设说我们希望最终输出结果到 $HOME/neo4j/data 目录下,那么,先创建这个目录

mkdir $HOME/neo4j/data/databases -p

然后执行

docker run -v $PWD/movie:/movie:ro -v $HOME/neo4j/data:/data/ \
       neo4j neo4j-import --into /data/databases/graph.db --id-type string \
             --nodes:Person /movie/Person.csv    \
             --nodes:Movie /movie/Movie.csv \
             --nodes:Country /movie/Country.csv \
             --relationships:actor /movie/actor.csv \
             --relationships:composer /movie/composer.csv \
             --relationships:director /movie/director.csv \
             --relationships:district /movie/district.csv

然后再用 docker 启动 Neo4j Server,并让其使用刚刚产生的数据库

docker run -p 7474:7474 \
       -p 7687:7687 \
       -v $HOME/neo4j/data/:/data \
       -e NEO4J_AUTH=neo4j/neo4j_password \
       neo4j

使用 LOAD CSV 加载 csv 数据

LOAD CSV 语句同样可以加载 csv 数据,不过和 neo4j-import 不一样,本质上它只是负责从 csv 文件中读取数据,如果要将读取到的数据写入到数据库中,还必须通过 CREATE 语句。也正因如此,用 LOAD CSV 语句来加载数据,不需要将 Neo4j Server 停掉。

LOAD CSV 语句将豆瓣电影图谱加载到数据库中的做法是下面这样的

  • 从 Movie.csv 中加载电影数据并创建 Movie 实体

    USING PERIODIC COMMIT 1000
    LOAD CSV with headers from 'https://raw.githubusercontent.com/Linusp/kg-example/master/movie/Movie.csv' as line
    CREATE (:Movie {
           id:line["id:ID"],
           title:line["title"],
           url:line["url"],
           cover:line["cover"],
           rate:line["rate"],
           category:split(line["category:String[]"], ";"),
           language:split(line["language:String[]"], ";"),
           showtime:line["showtime"],
           length:line["length"],
           othername:split(line["othername:String[]"], ";")
           })
    

    其中 "using periodic commit 1000" 表示每读取 1000 行数据就写入一次。

  • 从 Person.csv 中加载人员数据并创建 Person 实体

    USING PERIODIC COMMIT 1000
    LOAD CSV with headers from 'https://raw.githubusercontent.com/Linusp/kg-example/master/movie/Person.csv' as line
    CREATE (:Person {id:line["id:ID"], name:line["name"]})
    
  • 从 Country.csv 中加载国家数据并创建 Country 实体

    USING PERIODIC COMMIT 1000
    LOAD CSV with headers from 'https://raw.githubusercontent.com/Linusp/kg-example/master/movie/Country.csv' as line
    CREATE (:Country {id:line["id:ID"], name:line["name"]})
    
  • 创建关系

    每个关系的 csv 文件都是如下格式(以 actor.csv 为例)

    ":START_ID",":END_ID"
    "5ec851a8b7b7bbf0c9f42bbee021be00","3a20ded16ebce312f56a562e1bef7f05"
    "5ec851a8b7b7bbf0c9f42bbee021be00","8101549e05e6c1afbea62890117c01c6"
    "5ec851a8b7b7bbf0c9f42bbee021be00","111a3c7f6b769688da55828f36bbd604"
    "5ec851a8b7b7bbf0c9f42bbee021be00","5cc5d969f42ce5d8e3937e37d77b89b5"
    "5ec851a8b7b7bbf0c9f42bbee021be00","a5e6012efc56f0ca07184b9b88eb2373"
    "5ec851a8b7b7bbf0c9f42bbee021be00","435c8172c14c24d6cd123c529a0c2a76"
    "5ec851a8b7b7bbf0c9f42bbee021be00","5dfb355a385bcfe9b6056b8d322bfecb"
    "5ec851a8b7b7bbf0c9f42bbee021be00","5076a2f7479462dcc4637b6fe3226095"
    "5ec851a8b7b7bbf0c9f42bbee021be00","c7103a9ad17cf56fd572657238e49fff"
    

在创建关系的时候实际上是根据两个 id 查询到对应的实体,然后再为其建立关系。虽然我在准备这份数据时,已经保证了每个实体的 id 都是全局唯一的,但在没有创建索引的情况下,用这个 id 来查询实体会以遍历的形式进行,效率很差,所以在创建关系前,先创建一下索引。

为 Movie 实体的 id 属性创建索引

CREATE INDEX ON :Movie(id)

为 Person 实体的 id 属性创建索引

CREATE INDEX ON :Person(id)

为 Country 实体的 id 属性创建索引

CREATE INDEX ON :Country(id)

然后继续用 LOAD CSV 来创建关系

  • 从 actor.csv 中加载数据并创建 actor 关系

    USING PERIODIC COMMIT 1000
    LOAD CSV with headers from 'https://raw.githubusercontent.com/Linusp/kg-example/master/movie/actor.csv' as line
    MATCH (a:Movie {id:line[":START_ID"]})
    MATCH (b:Person {id:line[":END_ID"]})
    CREATE (a)-[:actor]->(b)
    
  • 从 composer.csv 中加载数据创建 composer 关系

    USING PERIODIC COMMIT 1000
    LOAD CSV with headers from 'https://raw.githubusercontent.com/Linusp/kg-example/master/movie/composer.csv' as line
    MATCH (a:Movie {id:line[":START_ID"]})
    MATCH (b:Person {id:line[":END_ID"]})
    CREATE (a)-[:composer]->(b)
    
  • 从 director.csv 中加载数据创建 director 关系

    USING PERIODIC COMMIT 1000
    LOAD CSV with headers from 'https://raw.githubusercontent.com/Linusp/kg-example/master/movie/director.csv' as line
    MATCH (a:Movie {id:line[":START_ID"]})
    MATCH (b:Person {id:line[":END_ID"]})
    CREATE (a)-[:director]->(b)
    
  • 从 district.csv 中加载数据并创建 district 关系

    USING PERIODIC COMMIT 1000
    LOAD CSV with headers from 'https://raw.githubusercontent.com/Linusp/kg-example/master/movie/district.csv' as line
    MATCH (a:Movie {id:line[":START_ID"]})
    MATCH (b:Country {id:line[":END_ID"]})
    CREATE (a)-[:district]->(b)
    

使用 Cypher 语句创建数据

严格来说,上面的 LOAD CSV 的方式,也是在用 Cypher 语句,不过说到底它还是要依赖一个外部的 CSV 文件,自由度没那么高。而 Neo4j Server 本身还提供 RESTful API,利用这个 API 就可以进行编程来完成更复杂的需求。

以创建实体为例来说明一下 Neo4j Server 的 RESTful API。假设说我们要创建三个 Person 实体,简单起见,我们假设每个 Person 实体需要有 id, name, age 三个属性,比如

[
    {
        "id": "person1",
        "name": "张志昂",
        "age": 23
    },
    {
        "id": "person2",
        "name": "刘文刀",
        "age": 18
    },
    {
        "id": "person3",
        "name": "孙子小",
        "age": 22
    }
]

通过 RESTful API,可以一次性创建这三个 Person 实体

POST http://neo4j:neo4j_password@localhost:7474/db/data/cypher
Content-Type: application/json
{
    "query": "UNWIND {values} as data CREATE (:Person {id: data.id, name: data.name, age: data.age})",
    "params": {
        "values": [
            {"id": "person1", "name": "张志昂", "age": 23},
            {"id": "person2", "name": "刘文刀", "age": 18},
            {"id": "person3", "name": "孙子小", "age": 22}
        ]
    }
}

这种通过带参数的 query 进行批量写入的方式,和 MySQL 等数据库的接口很相似,不过在 Cypher 中可以通过 UNWIND 语句做一些复杂的事情。详见文档

用 Python 来做的话大概是这个样子

import requests

url = "http://neo4j:neo4j_password@localhost:7474/db/data/cypher"
payload = {
    "query": (
        "UNWIND {values} as data "
        "CREATE (:Person {id: data.id, name: data.name, age: data.age})"
    ),
    "params": {
        "values": [
            {"id": "person1", "name": "张志昂", "age": 23},
            {"id": "person2", "name": "刘文刀", "age": 18},
            {"id": "person3", "name": "孙子小", "age": 22}
        ]
    }
}
requests.post(url, json=payload)

或者也可以使用 Neo4j 官方的 Python 客户端

import neo4j

client = neo4j.GraphDatabase.driver(
    'bolt://localhost:7687', auth=('neo4j', 'neo4j_password')
)
with client.session() as session:
    query = (
        "UNWIND {values} as data "
        "create (:Person {id: data.id, name: data.name, age: data.age})"
    )
    values = [
        {"id": "person1", "name": "张志昂", "age": 23},
        {"id": "person2", "name": "刘文刀", "age": 18},
        {"id": "person3", "name": "孙子小", "age": 22}
    ]
    session.run(query, {'values': values})

Cypher 查询语言

此处仅记录我个人认为常用或重要的部分,完整内容请参考官方文档

在 Cypher 中,用小括号来表示一个实体,用中括号来表示关系,这个是 Cypher 语言中最基础的表示了。

实体的各种表示方式如下:

  • 表示一个 Person 类型的实体,并记其名字为 a

    (a:Person)
    
  • 表示一个 id 值为 "person1" 的实体,并记其名字为 a

    (a {id:"person1"})
    
  • 表示任意一个实体,并记其名字为 a ,之后可以通过 WHERE 语句来对其进行约束

    (a)
    
  • 表示一个任意的匿名实体

    ()
    

关系的各种表示方式如下

  • 表示一个 actor 类型的实体,并记其名字为 r

    [r:actor]
    
  • 表示任意一个实体,并记其名字为 r

    [r]
    
  • 表示一个任意的匿名实体

    []
    

在上面的基础之上,即可方便地表示图数据中的一条实际的边,比如说

  • 表示命名为 m 的 Movie 类型实体到命名为 p 的 Person 类型实体、匿名的边

    (m:Movie)-[]->(p:Person)
    

    这里的 "->" 表示关系的方向是从 mp

  • 同上,但要求关系类型为 actor

    (m:Movie)-[:actor]->(p:Person)
    
  • 同上,并记关系的名字为 r

    (m:Movie)-[r:actor]->(p:Person)
    
  • 更复杂的表示:Person p 是 Movie m1 的主演,同时也是 Movie m2 的导演

    (m1:Movie)-[r1:actor]->(p:Person)<-[r2:director]-(m2:Movie)
    

掌握上述表示方法后,就可以用其来进行数据的创建、查询、修改和删除操作也就是俗称的 CRUD 了。

  • 查询实体

    MATCH (p:Person {name:"黄渤"}) RETURN p
    

    或者

    MATCH (p:Person) WHERE p.name="黄渤" RETURN p
    

    结果如下图所示

    neo4j_match_1.png

    当然也可以不带筛选条件

    MATCH (p:Person) RETURN p LIMIT 10
    

    neo4j_match_2.png

    (没错,我非常心机地把结果排成了整齐的两排哈哈)

  • 创建实体

    语法类似这样

    create (:Person {id:"ac1d6226", name:"王大锤"})
    
  • 修改实体

    MATCH (p:Person) WHERE p.id="ac1d6226" SET p.name="黄大锤"
    
  • 删除实体

    MATCH (p:Person) WHERE p.id="ac1d6226" DELETE p
    

    注意,删除实体时,如果这个实体还有和其他实体有关联关系,那么会无法删除,需要先将其关联关系解除才可以。

  • 查询关系

    查询 actor 类型的关系,不对起点、终点做任何约束

    MATCH (m)-[r:actor]->(p) RETURN * LIMIT 10
    

    结果如下图所示:

    neo4j_match_3.png

    查询 actor 类型的关系,对起点(或终点)做约束,比如说,查询主演是黄渤的所有电影

    MATCH (m:Movie)-[r:actor]->(p:Person) WHERE p.name="黄渤" RETURN *
    

    结果如下图所示:

    neo4j_match_4.png

  • 创建关系

    语法如下,要求涉及到的两个实体 ab 是已经存在的。

    MATCH (a:Person {id:"person_id_a"}), MATCH (b:Person {id:"person_id_b"})
    CREATE (a)-[:KNOWS]->(b)
    

    之前导入的豆瓣电影图谱其实缺少人和人之间的关系,比如说宁浩和黄渤彼此都认识,可以加上这个关系

    MATCH (a:Person), (b:Person) WHERE a.name="黄渤" and b.name="宁浩"
    CREATE (a)<-[:knows]->(b), (b)-[:knows]->(a)
    
  • 删除关系

    先用 MATCH 语句进行查询,并为其中的关系命名,然后在 DELETE 语句中用这个关系的名字即可。

    MATCH (a:Person)-[r:knows]-(b:Person) WHERE a.name="黄渤" and b.name="宁浩"
    DELETE r
    
  • 查询两个节点之间的最短路径

    查询黄渤和汤姆·克鲁斯之间的最短路径

    MATCH (a:Person), (b:Person), p=shortestpath((a)-[:actor*]-(b))
    WHERE a.name="黄渤" and b.name="汤姆·克鲁斯"
    RETURN p
    

    结果如下图所示:

    neo4j_match_5.png

CRUD 之外,索引的创建也是很重要的,如果没有创建索引或者索引设计有问题,那么可能会导致查询效率特别差。我最早开始用 Neo4j 的时候,在批量导入数据时没有建索引,导致不到五十万的数据量(包括实体和关系)的导入需要近一个小时,而在正确设置了索引之后,十几秒就完成了。对于比较慢的查询,可以用 PROFILE 语句来检查性能瓶颈。

以本文用来做示例的豆瓣电影图谱来说,如果没有给 Person.name 建立索引,那么下面这个查询语句就会很慢

MATCH (p:Person) where p.name="黄渤" RETURN p

PROFILE 语句做一下分析,只需要再原来的 query 前加上 PROFILE 这个关键词即可。

PROFILE MATCH (p:Person) where p.name="黄渤" RETURN p

分析结果如下图所示:

neo4j_profile_1.png

从上图来看,这个查询语句的逻辑是遍历了一下所有 Person 实体,挨个比较哪个实体的 name 是「黄渤」,这无疑是极其低效的。而在创建了索引后,PROFILE 的结果是下图这个样子:

neo4j_profile_2.png

关于索引可以展开更多内容,准备另外写一篇,这里只是强调一下 PROFILE 语句的作用。

宝塔面板将mysql5.7升级为mysql8.0

2023年11月14日 08:34

今天凌晨,飞牛无聊,给服务器数据库从 mysql5.7 升级到了 mysql8,由于服务器配置不是很高,所以前后花了差不多8000秒,也就是大概2个多小时,所以建议大家要升级数据库配置,尽量在深夜或者洗澡、蹲坑等时候。

升级后感觉变化还是有的,也可能是心理作用。总归不是很明显,毕竟博客内容不是很多,不是很大访问量。

mysql8和mysql5.7 相比都做了哪些升级

MySQL 8.0 相对于 5.7 版本,在很多方面都做出了显著的改进。以下是一些重要的更新和区别:

  1. 字符集:MySQL 8.0 的默认字符集从 latin1 更改为了 utf8mb4,这对于全球化的应用来说,提供了更好的字符兼容性。
  2. 数据字典:MySQL 8.0 引入了全新的数据字典,它将所有的元数据存储在单个位置,这将提升了许多操作的性能,特别是在大型数据库的环境中。
  3. 角色管理:MySQL 8.0 引入了基于角色的访问控制,这使得管理用户权限变得更加方便。
  4. 窗口函数:MySQL 8.0 添加了对 SQL 窗口函数的支持,这对于复杂的数据分析任务非常有用。
  5. 公共表表达式:MySQL 8.0 添加了对公共表表达式(CTEs)的支持,这使得写复杂的 SQL 查询变得更加简单。

在性能方面,MySQL 8.0 也做出了显著的提升。比如,对于只读负载,MySQL 8.0 的性能比 5.7 提升了约 2 倍。这得益于 MySQL 8.0 的优化器和 InnoDB 存储引擎的改进。但是,具体的性能差异还会受到很多因素的影响,包括硬件环境、数据库配置、查询复杂性等。

但是,请注意,与任何软件升级一样,从 MySQL 5.7 升级到 8.0 可能会带来一些兼容性问题。你需要在升级之前做好充分的测试,以确保你的应用能够在新版本的 MySQL 中正常工作。

一、备份数据库

升级有风险,宝塔面板升级mysql时会提醒管理员删除所有数据库。所以,

  • 必须在删除数据库之前记录当前数据库信息,用户名、密码(切记、重要、重要、重要)。
  • 备份所有数据库内容,并下载至本地,以便升级完成后恢复数据库。
宝塔面板将mysql5.7升级为mysql8.0-飞牛士 FeiNews

二、选择升级版本

选择你要升级到的SQL版本,点击切换按钮,系统会进行自动安装,速度取决于服务器性能。通常这个时候飞牛建议选择在深夜或者其他空闲时间。

宝塔面板将mysql5.7升级为mysql8.0-飞牛士 FeiNews
宝塔面板将mysql5.7升级为mysql8.0-飞牛士 FeiNews

三、恢复数据库

升级完成后,添加数据库,填写之前记录的数据库名、用户名、密码。

上传先前备份的数据库内容,然后点击恢复即可,由于并没有改变数据库的关键信息,一般情况下打开网站可以处于正常运行状态,至此升级完成。

注意事项:

  • 数据库升级建议在深夜等访客较少的时候升级。
  • 升级过程中会重启数据库、如数据库原本存在表损坏等情况的话,将会导致重启失败甚至数据丢失、务必做好数据库备份校验数据完整性后再进行升级。
  • 如果您做了备份还不放心,建议您前往服务器商家控制台做个服务器快照,可随时回滚。
❌
❌