使用SpringBoot+PostgreSQL物化视图实现微服务设计模式 - vinsguru


在本教程中,我想演示带有Spring Boot的Materialized View PostgreSQL,这是微服务设计模式之一,可以提高应用程序的读取性能。
 
物化视图:
本质上,大多数基于Web的应用程序都是CRUD,具有简单的CREATE,READ,UPDATE和DELETE操作。同样,在大多数应用程序中,与其他INSERT,DELETE和UPDATE事务相比,我们执行更多的READ操作。有时READ操作可能非常繁重,以至于我们需要使用聚合函数将多个表连接在一起。云会减慢读取操作的性能。
本文的目的是展示实例化视图模式,以演示如何在每次都不容易查询源数据时如何检索数据的预先设置的视图,以及如何提高微服务的性能。
 
样例应用:
让我们考虑一个简单的应用程序,其中有3个服务,如下所示。(理想情况下,所有这些服务都应具有不同的数据库。在本文中,我使用的是同一数据库)

  • 用户服务:包含与用户相关的操作
  • 产品服务:包含与产品相关的操作
  • 订单服务:  这是我们感兴趣的内容-与用户订单相关的功能。

我们的订单服务负责为用户下订单。它还暴露了提供销售统计信息的终点。为了更好地理解这一点,让我们首先看一下数据库表结构。

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模式,以进一步提高微服务的性能。
源代码可在此处获得