使用SpringBoot+PostgreSQL物化檢視實現微服務設計模式 - vinsguru

banq發表於2020-11-20

在本教程中,我想演示帶有Spring Boot的Materialized View PostgreSQL,這是微服務設計模式之一,可以提高應用程式的讀取效能。
 

物化檢視:
本質上,大多數基於Web的應用程式都是CRUD,具有簡單的CREATE,READ,UPDATE和DELETE操作。同樣,在大多數應用程式中,與其他INSERT,DELETE和UPDATE事務相比,我們執行更多的READ操作。有時READ操作可能非常繁重,以至於我們需要使用聚合函式將多個表連線在一起。雲會減慢讀取操作的效能。
本文的目的是展示例項化檢視模式,以演示如何在每次都不容易查詢源資料時如何檢索資料的預先設定的檢視,以及如何提高微服務的效能。
 

樣例應用:
讓我們考慮一個簡單的應用程式,其中有3個服務,如下所示。(理想情況下,所有這些服務都應具有不同的資料庫。在本文中,我使用的是同一資料庫)

使用SpringBoot+PostgreSQL物化檢視實現微服務設計模式 - vinsguru

  • 使用者服務:包含與使用者相關的操作
  • 產品服務:包含與產品相關的操作
  • 訂單服務:  這是我們感興趣的內容-與使用者訂單相關的功能。

我們的訂單服務負責為使用者下訂單。它還暴露了提供銷售統計資訊的終點。為了更好地理解這一點,讓我們首先看一下資料庫表結構。

CREATE TABLE users(
   id serial PRIMARY KEY,
   firstname VARCHAR (50),
   lastname VARCHAR (50),
   state VARCHAR(10)
);

CREATE TABLE product(
   id serial PRIMARY KEY,
   description VARCHAR (500),
   price numeric (10,2) NOT NULL
);

CREATE TABLE purchase_order(
    id serial PRIMARY KEY,
    user_id integer references users (id),
    product_id integer references product (id)
);


訂單服務公開了一個端點,該端點按使用者狀態提供了總銷售價值。

select 
    u.state,
    sum(p.price) as total_sale
from 
    users u,
    product p,
    purchase_order po
where 
    u.id = po.user_id
    and p.id = po.product_id
group by u.state
order by u.state

我們可以建立一個檢視以獲取我們感興趣的結果,如下所示。

create view purchase_order_summary
as
select u.state,
       sum(p.price) as total_sale
from users u,
     product p,
     purchase_order po
where u.id = po.user_id
  and p.id = po.product_id
group by u.state
order by u.state


所以,下面的查詢執行提供了total_sale的狀態

select * from purchase_order_summary;
 

Spring Boot應用程式:

  • 首先,讓我們先建立一個簡單的spring boot應用程式,然後再深入物化檢視實現。
  • 我在下面使用依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

原始碼可在此處獲得
 
 

效能測試–資料庫檢視

  • 我在使用者表中插入了10000個使用者
  • 我在產品表中插入了1000個產品
  • 我將500萬個使用者訂單(隨機使用者+產品組合)插入purchase_order表
  • 我使用JMeter和11個併發使用者進行了效能測試
    • 10個使用者,用於傳送讀取請求
    • 1個用於連續建立採購訂單的使用者
  • 銷售摘要的平均響應時間為7.2秒。它正在嘗試按狀態彙總每個GET請求的purchase_order表中的資訊。

  

 資料庫檢視問題:

  • 檢視是資料庫中的虛擬表
  • 即使DB Views可以很好地隱藏一些敏感資訊並在諸如結構之類的更簡單的表中提供資料,但基礎查詢每次都會執行。在某些情況下,如果資料變化非常頻繁,則可能需要這樣做。但是,在大多數情況下,它可能會嚴重影響應用程式的效能!

  

物化檢視PostgreSQL:
物化檢視是資料庫中最有可能的檢視。但是它們不是虛擬表。而是實際上使用查詢來計算/檢索資料,並將結果作為單獨的表儲存在硬碟中。因此,當我們執行以下查詢時,底層查詢不會每次都執行。而是直接從表中獲取資料。這類似於使用快取的資料。因此,它提高了效能。

CREATE MATERIALIZED VIEW purchase_order_summary
AS
select 
    u.state,
    sum(p.price) as total_sale
from 
    users u,
    product p,
    purchase_order po
where 
    u.id = po.user_id
    and p.id = po.product_id
group by u.state
order by u.state
WITH NO DATA;
CREATE UNIQUE INDEX state_category ON purchase_order_summary (state);

-- to load into the purchase_order_summary
REFRESH MATERIALIZED VIEW CONCURRENTLY purchase_order_summary;


查詢:

select * from purchase_order_summary;


顯而易見的問題是,如果源資料被更新,該怎麼辦。也就是說,如果我們在purchase_order表中輸入新條目,那麼將如何更新purchase_order_summary表!它不會自動更新。我們需要採取一些行動來做到這一點。
 
物化檢視PostgreSQL –帶觸發器的自動更新:
  • 我們需要更新purchase_order_summary只有當我們做專案到PURCHASE_ORDER。(到目前為止,我忽略了刪除/更新操作)。因此,讓我們建立一個觸發器,以便在我們向purchase_order表中輸入條目時更新例項化檢視。
  • 因此,讓我們首先建立一個函式來更新例項化檢視。

CREATE OR REPLACE FUNCTION refresh_mat_view()
  RETURNS TRIGGER LANGUAGE plpgsql
  AS $$
  BEGIN
  REFRESH MATERIALIZED VIEW CONCURRENTLY purchase_order_summary;
  RETURN NULL;
  END $$;

  • 每當我們在purchase_order表中輸入條目時,都應呼叫上述函式。所以我建立了一個插入後觸發器。

CREATE TRIGGER refresh_mat_view_after_po_insert
  AFTER INSERT 
  ON purchase_order
  FOR EACH STATEMENT
  EXECUTE PROCEDURE refresh_mat_view();


 

物化檢視–效能測試:

  • 我重新執行相同的效能測試。
  • 這次,我的銷售摘要取得了非常出色的成績。由於不是針對每個GET請求都執行基礎查詢,因此效能非常好!吞吐量超過3000個請求/秒。
  • 但是,新的purchase_order請求的效能會受到影響,因為它負責更新例項化檢視。
  • 在某些情況下,如果我們非同步進行新的訂單放置,那可能沒問題。但是,我們真的需要為每個訂單更新摘要嗎?取而代之的是,我們可以將物化檢視更新為一定的間隔,例如5秒。幾秒鐘內資料可能不是很準確。最終將在5秒鐘內重新整理。對於避免我們在上面看到的新訂單效能問題,這可能是一個不錯的解決方案。
  • 讓我們刪除觸發器和我們建立的函式。
  • 讓我們建立一個簡單的過程來重新整理檢視。該過程將透過SpringBoot定期呼叫。

-- drop trigger

drop trigger refresh_mat_view_after_po_insert ON purchase_order;

-- drop function
drop function refresh_mat_view();

-- create procedure
CREATE OR REPLACE PROCEDURE refresh_mat_view()
LANGUAGE plpgsql    
AS $$
BEGIN
  REFRESH MATERIALIZED VIEW CONCURRENTLY purchase_order_summary;
END;
$$;

使用Spring Boot的物化檢視:
  • 我新增了新元件,它將負責定期呼叫該過程。

@Component
public class MaterializedViewRefresher {

    @Autowired
    private EntityManager entityManager;

    @Transactional
    @Scheduled(fixedRate = 5000L)
    public void refresh(){
        this.entityManager.createNativeQuery("call refresh_mat_view();").executeUpdate();
    }

}

  • 我重新執行相同的效能測試,以獲得以下結果。
  • 我的讀寫操作都獲得了極高的吞吐量。
  • 兩種情況下的平均響應時間均為6毫秒。

 

總結
我們演示了將Materialized View PostgreSQL與Spring Boot配合使用,  以提高微服務體系結構的讀取繁重操作的效能。實施此模式還將使我們能夠實施CQRS模式,以進一步提高微服務的效能。
原始碼可在此處獲得

相關文章