PostgreSQL中利用驅動程式實現故障轉移

banq發表於2024-07-03

假設我們已經建立了PostgreSQL雙向複製 ,最好檢查一下中斷的情況,以及如何利用 PostgreSQL 驅動程式的本機故障轉移功能。

我們將衝突解決策略更改為last_update_wins。這樣,在每個資料庫中的兩個同時更新之間,具有最大提交時間戳的更新將被選中。

listen_addresses = '*'
port = 5432
max_connections = 20
shared_buffers = 128MB
temp_buffers = 8MB
work_mem = 4MB
wal_level = logical
max_wal_senders = 3
track_commit_timestamp = on
shared_preload_libraries = 'pglogical'
pglogical.conflict_resolution = 'last_update_wins'

我們需要透過新的變化來啟動組合服務:

docker compose up

請注意,根據程式語言和驅動程式的不同,此功能可能並不總是可用。概念是,當您配置連線池以建立與資料庫的連線時,您可以配置兩個主機。第一個主機將是主主機,而輔助主機將是主主機離線後進行故障轉移的主機。故障轉移可以互換,本質上驅動程式會嘗試找到第一個可用的主機。

1、Python
Python 和驅動程式psycopg2提供此功能。我們將使用 flask api 實現一個應用程式。

該應用程式將提供兩個端點:
一個用於獲取員工的工資,一個用於將工資增加 :

from flask import Flask 
from psycopg2.pool import SimpleConnectionPool
 
app = Flask(__name__)
 
postgreSQL_pool = SimpleConnectionPool(1, 20, user="postgres",
                                       password="postgres",
                                       host="localhost,localhost",
                                       port="5432,5431",
                                       database="postgres",
                                       options="-c search_path=test_schema")
 
 
@app.route('/employee/<employee_id>/salary/increment', methods=['POST'])
def increment_salary(employee_id):
    conn = postgreSQL_pool.getconn()
    cur = conn.cursor()
    cur.execute("""
        UPDATE employee SET salary=salary + %s WHERE id = %s;
        """, (1, employee_id))
    conn.commit()
    cur.close()
    postgreSQL_pool.putconn(conn)
    return '', 204
 
 
@app.route('/employee/<employee_id>/salary')
def index(employee_id):
    conn = postgreSQL_pool.getconn()
    cur = conn.cursor()
    cur.execute("""
        SELECT salary FROM employee WHERE id=%s;
        """, employee_id)
    salary = cur.fetchone()[0]
    cur.close()
    postgreSQL_pool.putconn(conn)
    return str(salary), 200

讓我們以 SimpleConnectionPool 為例,我們可以看到兩個用逗號分隔的主機(它是 localhost,因為它是我們正在執行的本地 docker compose),並且在埠部分,相應的主機埠用逗號分隔。

我們可以執行應用程式

flash run
在另一個終端上發出使用 curl 的呼叫

$ curl -X POST http://localhost:5000/employee/1/salary/increment
$ curl http://localhost:5000/employee/1/salary

總體來說,工資會增加,我們應該在獲取請求中看到這一點。

現在讓我們關閉一個資料庫

docker compose stop postgres-b

此操作後的第一次呼叫將會失敗,但連線將重新初始化並指向輔助主機。

% curl http://localhost:5000/employee/1/salary
<!doctype html>
<html lang=en>
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
%  curl http://localhost:5000/employee/1/salary
1254.23        
                     

2、 Spring Boot
相同的功能適用於其他驅動程式。以 Spring Boot 應用程式上的 Java 驅動程式配置為例。


spring.datasource.url=jdbc:postgresql://localhost:5432,localhost:5431/postgres?currentSchema=test_schema
spring.datasource.username=postgres
spring.datasource.password=postgres

在 jdbc url 上我們新增兩個用逗號分隔的主機localhost:5432,localhost:5431

然後我們可以實現具有相同功能的應用程式。

package com.egkatzioura.psqlfailover.repository;
 
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
 
 
@Repository
public class EmployeeRepository {
 
    private final JdbcTemplate jdbcTemplate;
 
    public EmployeeRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
 
 
    public void incrementSalary(Long employeeId, float increment) {
        jdbcTemplate.update("UPDATE employee SET salary=salary+? WHERE id=?",increment, employeeId);
    }
 
    public Float fetchSalary(Long employeeId) {
        return jdbcTemplate.queryForObject("SELECT salary FROM employee WHERE id=?",new Object[]{employeeId},Float.class);
    }
}

 
import com.egkatzioura.psqlfailover.repository.EmployeeRepository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class EmployeeController {
 
    private final EmployeeRepository employeeRepository;
 
    public EmployeeController(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }
 
    @PostMapping("/employee/{id}/salary/increment")
    public void incrementSalary(@PathVariable Long id) {
        employeeRepository.incrementSalary(id,1f);
    }
 
    @GetMapping("/employee/{id}/salary")
    public Float fetchSalary(@PathVariable Long id) {
        return employeeRepository.fetchSalary(id);
    }
}

由於複製,更改應該已經到達另一個資料庫。您可以以迴圈方式啟動和重新啟動組合服務。更改將被複制,因此每次發生故障轉移時資料都會在那裡。

當我們啟動和停止資料庫時docker compose stop postgres-b,我們可以使用 curl 發出請求:

$ curl -X POST http://localhost:8080/employee/1/salary/increment
$ curl http://localhost:8080/employee/1/salary

最終,Java 驅動程式可以更優雅地處理故障轉移。在故障轉移期間,它不會在第一個請求時失敗,而是會先連線到另一臺主機並返回結果。

就是這樣。您在 PostgreSQL 上設定了雙向複製,並設法利用驅動程式功能將故障轉移到不同的主機。

希望您玩得開心!

相關文章