它是如何工作的?
您的应用程序将使用包含LiteSync代码的SQLite库的修改版本来访问数据库。
SQLite库上的修改仅仅是内部的,接口还是相同的。
LiteSync库将相互通信,交换数据。

复制
首次打开该应用程序时,它将连接到其他节点并下载数据库的新副本。
在集中式网络中,主节点将数据库副本发送到辅助节点。
一旦下载,该节点即开始同步。
同步
一旦节点具有相同的基本数据库,它们便交换离线时执行的事务。
此后,它们进入在线模式,并且一旦在节点中执行了新事务,该事务便被转移以在连接的节点中执行。
如果该节点处于离线状态,则该事务将存储在本地日志中,以便以后进行交换。
我是否需要更改我的应用代码?
这里有几个步骤但是基本上我们必须更改在数据库中打开的URI代码:
"file:/path/to/app.db"
改成这样:
"file:/path/to/app.db?node=secondary&connect=tcp://server.ip:1234"
好消息是LiteSync使用本机SQLite3接口。这意味着我们不需要使用其他API。
连接
每个节点有两个选项
bind连接到一个地址
connect连接到一个对等地址
因此,您可以从一个设备连接到另一个设备。可以便于连接到路由器或防火墙后面的设备。
支持的拓扑网络
集中式网络, 星形网络

在拓扑网络中,我们必须要有一个主节点,其他节点将连接主节点,因此主节点必须在线才能进行同步。
以下是一些示例配置:
主节点可以绑定到地址,辅节点可以连接到该地址。
主节点:
"file:/home/user/app.db?node=primary&bind=tcp://0.0.0.0:1234"
副节点(其他设备):
"file:/home/user/app.db?node=secondary&connect=tcp://server:1234"
主节点也可以连接到副节点。
主节点:
"file:/home/user/app.db?node=primary&connect=tcp://address1:port1,tcp://address2:port2"
副节点(其他分开的设备):
"file:/home/user/app.db?node=secondary&bind=tcp://0.0.0.0:1234"
我们甚至可以同时使用这两个选项。
主节点:
"file:/home/user/app.db?node=primary&bind=tcp://0.0.0.0:1234&connect=tcp://address1:port1"
副节点1:
"file:/home/user/app.db?node=secondary&connect=tcp://server:1234"
副节点2:
"file:/home/user/app.db?node=secondary&bind=tcp://0.0.0.0:1234"
对等拓扑

完全连接的对等网络是在主节点之间建立的。
我们需要在每个节点上手动通知网络上的节点总数(目前)
还必须告知连接方向(哪些节点将连接到哪个节点)
这是一个具有3个节点的网络的示例:
节点1:
"file:db1.db?node=primary&total_primary_nodes=3&bind=tcp://0.0.0.0:1201"
节点2:
"file:db2.db?node=primary&total_primary_nodes=3&bind=tcp://0.0.0.0:1202& connect=tcp://127.0.0.1:1201"
节点3:
"file:db3.db?node=primary&total_primary_nodes=3&bind=tcp://0.0.0.0:1203& connect=tcp://127.0.0.1:1201,tcp://127.0.0.1:1202"
混合拓扑

在此拓扑中,我们有多个作为对等节点连接的主节点,并且有许多与它们连接的辅助节点。
主节点的配置与对等拓扑中的配置相同(如上所述)。
每个辅助节点将在给定时间连接到单个主节点。 我们可以告知许多主要节点的地址,以便它们随机选择一个。 如果到主节点的连接断开,它将连接到另一主节点。
这是辅助节点的示例URI:
"file:db4.db?node=secondary&connect=tcp://127.0.0.1:1201,tcp://127.0.0.1:1202,tcp://127.0.0.1:1203"
同步状态
我们可以使用以下命令检查同步状态:
PRAGMA sync_status
它会返回一个JSON字符串。
同步通知
当由于与远程节点同步而更新本地数据库时,可以通知您的应用程序。 通知是通过用户定义的功能完成的。
选择编程语言 -->
static void on_db_update(sqlite3_context *context, int argc, sqlite3_value **argv){ char* changes = sqlite3_value_text(argv[0]); printf("收到更新: %s\n", changes); } sqlite3_create_function(db, "update_notification", 1, SQLITE_UTF8, NULL, &on_db_update, NULL, NULL);
db.define("update_notification", [](std::string changes) { std::cout << "收到更新: " << changes << std::endl; });
def on_db_update(changes): print("收到更新:", changes) con.create_function("update_notification", 1, on_db_update)
// using better-sqlite3: db.on('sync', function(changes) { console.log('收到更新: ' + changes); });
Function.create(conn, "update_notification", new Function() { protected void xFunc(changes) { System.out.println("收到更新: " + changes); } });
// using SQLite.NET: db.OnSync((changes) => { // the db received an update. update the screen with new data UpdateScreen(db); }); // using Microsoft.Data.SQLite: db.CreateFunction("update_notification", (changes) => { // notification received on the worker thread // do not access the db connection here // transfer the notification to the main thread Console.WriteLine("收到更新: " + changes); return 0; });
' Using SQLite.NET: db.OnSync(Function(changes) As Integer ' the db received an update. update the screen with new data UpdateScreen(db) End Function) ' Using Microsoft.Data.SQLite: db.CreateFunction("update_notification", Function(changes) As Integer ' notification received on the worker thread ' do not access the db connection here ' transfer the notification to the main thread Console.WriteLine("收到更新: " & changes) End Function)
function on_db_update($changes) { echo '收到更新: $changes'; } // with sqlite3: $db->createFunction('update_notification', 'on_db_update'); // with pdo_sqlite: $db->sqliteCreateFunction('update_notification', 'on_db_update', 1);
$dbh->sqlite_create_function( 'update_notification', 1, sub { my $changes = shift; # retrieve the argument passed to the function print "收到更新: $changes"; });
db.create_function "update_notification", 1 do |func, changes| puts "收到更新: #{changes}" func.result = null end
db.create(function: "update_notification", argc: 1) { args in let changes = args.first as! String println("收到更新: \(changes)") return nil }
db:create_function('update_notification',1,function(ctx,changes) print('收到更新: ' .. changes) ctx:result_null() end)
func on_db_update(changes string) int64 { print('收到更新: ' + changes) return null } sql.Register("sqlite3_custom", &sqlite.SQLiteDriver{ ConnectHook: func(conn *sqlite.SQLiteConn) error { if err := conn.RegisterFunc("update_notification", on_db_update, true); err != nil { return err } return nil }, }) db, err := sql.Open("sqlite3_custom", "file:data.db?node=...")
重要提示: 通知函数由工作线程调用。 该应用程序不应在通知功能内使用数据库连接,并且必须尽快返回! 应用程序可以在返回之前将通知转移到主线程。
检查数据库是否准备就绪
如果应用程序是首次在设备上打开,它可以从另一个节点下载数据库的新副本。在完成之前,我们无法访问数据库。
我们可以检索同步状态并检查db_is_ready变量。
在下面查看基本的应用示例。
如何在我的应用程序中使用它?
共有3个步骤:
1 将SQLite库替换为包含LiteSync的库
2 更改URI连接字符串
3 检查数据库准备就绪状态
编译C和C ++应用程序时,必须将应用程序链接到LiteSync库。
对于其他语言,您必须安装正确的包装器
主节点示例
主节点可以是普通应用程序,与副节点完全相同,但使用不同的URI。
或者我们可以使用专用于主要节点的应用程序。
仅用于保持集中式网络节点的基本独立应用程序如下所示:
选择编程语言 -->
#include <sqlite3.h> char *uri = "file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234"; int main() { sqlite3 *db; sqlite3_open(uri, &db); /* open the database */ while(1) sleep(1); /* keep the app open */ }
#include <sqlite_modern_cpp.h> #include <thread> #include <chrono> #include <iostream> using namespace sqlite; int main() { try { // open the database database db("file:app.db?node=primary&bind=tcp://0.0.0.0:1234"); // keep the app open while(1) { std::this_thread::sleep_for(std::chrono::seconds(1)); } } catch (std::exception& e) { std::cerr << e.what() << std::endl; } }
import litesync as sqlite3 conn = sqlite3.connect('file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234') # keep the app open import time while True: time.sleep(60) # in seconds
const uri = 'file:app.db?node=primary&bind=tcp://0.0.0.0:1234'; const options = { verbose: console.log }; const db = require('better-sqlite3-litesync')(uri, options); // keep the app open setInterval(function(){}, 5000);
import java.sql.Connection; import java.sql.DriverManager; public class Sample { public static void main(String[] args) { String uri = "file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234"; Connection connection = DriverManager.getConnection("jdbc:sqlite:" + uri); // keep the app open while (true) { Thread.sleep(5000); } } }
using SQLite; public class Program { public static void Main() { // open the database var uri = "file:app.db?node=primary&bind=tcp://0.0.0.0:1234"; var db = new SQLiteConnection(uri); // keep the app open while(true) { System.Threading.Thread.Sleep(5000); } } }
Imports SQLite Public Class Program Public Shared Sub Main() ' open the database Dim db As New SQLiteConnection("file:app.db?node=primary&bind=tcp://0.0.0.0:1234") ' keep the app open Do System.Threading.Thread.Sleep(5000) Loop End Sub End Class
Option Explicit Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long) Public Sub Main() Dim URI As String Dim Conn As New ADODB.Connection ' Open the connection URI = "file:C:\app\mydb.db?node=primary&bind=tcp://0.0.0.0:1234" Conn.Open "DRIVER=SQLite3 ODBC Driver;Database=" & URI ' Keep the app open Do: Sleep(5000): Loop End Sub
<?php // with sqlite3: $db = new SQLite3("file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234"); // with pdo_sqlite: $pdo = new PDO("sqlite:file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234"); // keep the app open - it should not be used with apache while(1) sleep(5); ?>
use DBI; my $dbh = DBI->connect("dbi:SQLite:uri=file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234"); // keep the app open - it should not be used with apache sleep;
require 'sqlite3' db = SQLite3::Database.new "file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234" # keep the app open loop do sleep(1) end
local sqlite3 = require("lsqlite3") local db = sqlite3.open('file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234') -- keep the app open local lsocket = require("lsocket") while true do lsocket.select(5000) end
package main import ( "database/sql" _ "github.com/litesync/go-sqlite3" "time" ) func main() { db, err := sql.Open("sqlite3", "file:/path/to/app.db?node=primary&bind=tcp://0.0.0.0:1234") // keep the app open for { time.Sleep(1000 * time.Millisecond) } }
基本应用示例
写入本地数据库的基本应用程序如下所示:
选择编程语言 -->
#include <sqlite3.h> char *uri = "file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234"; int main() { sqlite3 *db; /* open the database */ sqlite3_open(&db, uri); /* check if the db is ready */ while(1){ char *json_str = sqlite3_query_value_str(db, "PRAGMA sync_status", NULL); bool db_is_ready = strstr(json_str, "\"db_is_ready\": true") > 0; sqlite3_free(json_str); if (db_is_ready) break; sleep_ms(250); } /* access the database */ start_access(db); } char * sqlite3_query_value_str(sqlite3 *db, char *sql, char **ppErrMsg) { char *ptr = NULL; sqlite3_stmt *stmt; int rc; if (ppErrMsg) *ppErrMsg = NULL; rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { if (ppErrMsg) *ppErrMsg = sqlite3_strdup(sqlite3_errmsg(db)); return NULL; } if (sqlite3_step(stmt) == SQLITE_ROW) { char *text = (char *)sqlite3_column_text(stmt, 0); if (text) { ptr = sqlite3_strdup(text); } } sqlite3_finalize(stmt); return ptr; }
#include <iostream> #include <sqlite_modern_cpp.h> #include <unistd.h> using namespace sqlite; using namespace std; int main() { try { // open the database database db("file:app.db?node=secondary&connect=tcp://myserver.ddns.net:1234"); // wait until the database is ready while(1) { string status; db << "pragma sync_status" >> status; cout << "status : " << status << endl; if (status.find("\"db_is_ready\": true") != string::npos) break; sleep(1); } // now the application can access the database // check examples here: // https://github.com/SqliteModernCpp/sqlite_modern_cpp ... } catch (exception& e) { cout << e.what() << endl; } }
import litesync as sqlite3 import json import time conn = sqlite3.connect('file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234') # check if the db is ready while not conn.is_ready(): time.sleep(0.250) start_access(conn)
const uri = 'file:test.db?node=secondary&connect=tcp://127.0.0.1:1234'; const options = { verbose: console.log }; const db = require('better-sqlite3-litesync')(uri, options); db.on('ready', function() { // the database is ready to be accessed db.exec('CREATE TABLE IF NOT EXISTS users (name, email)'); ... });
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import org.json.*; public class Sample { public static void main(String[] args) { String uri = "file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234"; Connection connection = DriverManager.getConnection("jdbc:sqlite:" + uri); Statement statement = connection.createStatement(); // check if the db is ready while (true) { ResultSet rs = statement.executeQuery("PRAGMA sync_status"); rs.next(); JSONObject obj = new JSONObject(rs.getString(1)); if (obj.getBoolean("db_is_ready")) break; Thread.sleep(250); } // now we can access the db start_access(connection); } }
using SQLite; public class Program { public static void Main() { // open the database var uri = "file:app.db?node=secondary&connect=tcp://server:port"; var db = new SQLiteConnection(uri); // wait until the db is ready while (!db.IsReady()) { System.Threading.Thread.Sleep(250); } // now we can use the database db.CreateTable<TodoItem>(CreateFlags.AutoIncPK); ... } }
Imports SQLite Public Class Program Public Shared Sub Main() ' open the database Dim db As New SQLiteConnection("file:app.db?node=secondary&connect=tcp://server:port") ' wait until the db is ready While Not db.IsReady() System.Threading.Thread.Sleep(250) End While ' now we can use the database db.CreateTable(Of TodoItem)(CreateFlags.AutoIncPK) ' ... End Sub End Class
Option Explicit Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long) Public Sub Main() Dim Conn As New ADODB.Connection Dim Rst As ADODB.Recordset Dim URI As String URI = "file:C:\app\mydb.db?node=secondary&connect=tcp://myserver.ddns.net:1234" Conn.Open "DRIVER=SQLite3 ODBC Driver;Database=" & URI ' Check if the database is ready Do Set Rst = New ADODB.Recordset Rst.Open "PRAGMA sync_status", Conn, , , adCmdText If InStr(Rst!sync_status, """db_is_ready"": true") > 0 Then Exit Do Sleep 200 Loop ' Now we can access the db StartDbAccess(Conn) End Sub
<?php // with sqlite3: $db = new SQLite3("file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234"); // with pdo_sqlite: $pdo = new PDO("sqlite:file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234"); // check if the db is ready while(1) { $results = $db->query('PRAGMA sync_status'); $row = $results->fetchArray(); $status = json_decode($row[0], true); if ($status['db_is_ready'] == true) break; sleep(0.25); } // now we can access the db start_access($db); ?>
use DBI; use JSON qw( decode_json ); my $dbh = DBI->connect("dbi:SQLite:uri=file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234"); // check if the db is ready - it should not be used with apache while (1) { my ($result) = $dbh->selectrow_array("PRAGMA sync_status"); my $status = decode_json($result); if ($status->{'db_is_ready'}) last; sleep; } // now we can access the db ...
require 'sqlite3' require 'json' db = SQLite3::Database.new "file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234" # check if the db is ready loop do result = db.get_first_value "PRAGMA sync_status" status = JSON.parse(result) break if status["db_is_ready"] == true sleep 0.25 end # now we can access the db start_access(db)
local sqlite3 = require "lsqlite3" local json = require "json" local db = sqlite3.open('file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234') -- check if the db is ready local lsocket = require("lsocket") while true do local result = db:rows("PRAGMA sync_status") local status = json:decode(result[0]) if status["db_is_ready"] == true then break end lsocket.select(250) end -- now we can access the db start_access(db)
package main import ( "database/sql" _ "github.com/litesync/go-sqlite3" "time" ) func main() { db, err := sql.Open("sqlite3", "file:/path/to/app.db?node=secondary&connect=tcp://myserver.ddns.net:1234") // wait until the db is ready for !db.IsReady() { time.Sleep(1000 * time.Millisecond) } // now we can access the db start_access(db) }
安全性
LiteSync使用"共享密钥"方法通过加密密钥来控制哪些节点可以成为网络的一部分
建议在数据库和节点之间的通信中启用加密
查看关于加密的说明
目前已知问题
1 不确定性函数(每次调用时返回不同的值)被阻止,例如 random() 和 date('now')。请在您的应用程序中使用显式值
2 AUTOINCREMENT 关键字不支持 - 但您不需要它!(查看视频了解详情)
3 同一时间只有一个应用程序可以访问数据库。每个实例必须使用自己的数据库,然后通过 LiteSync 进行复制和同步