写点什么

Presto 设计与实现(一):开篇

作者:冰心的小屋
  • 2023-08-16
    上海
  • 本文字数:3391 字

    阅读完需:约 11 分钟

Presto 设计与实现(一):开篇

Presto 是一款快速的、稳定的 SQL 查询引擎。


上面的图片来自于 Presto 官网,Presto 将组件划分为三层:

  • 底层:存储结构化数据的数据源,数据源来自于关系型数据库、非关系型数据库、OLAP、分布式数据库、消息中间件、HDFS 以及各种云存储;

  • 中间层:Presto,支持异构数据源的查询以及异构数据源之间的联合查询;

  • 应用层:借助于 Presto 查询引擎,在复杂的异构数据源中操作数据构建上层应用。

统计接口调用次数:未使用 Presto

在之前的项目中为了计算每天用户调用接口的次数,使用 Redis 累加调用次数,用户基本信息存储在 MySQL 中,下面是实际的实现。

MySQL 中的 user 表:

Redis 中的 key 设计:

  • 调用接口总次数:api_tag_count

  • 每天调用接口次数:api_tag_count_20230812

  • 用户调用接口总次数:user_1_api_tag_count

  • 用户每天调用接口次数:user_1_api_tag_count_20230812


使用 Jedis 客户端累加接口调用次数:

/** * 计算接口调用的次数:总次数/每天总次数/用户调用次数/每天用户调用次数 * * @param jedis jedis客户端 * @param api api接口名称 * @param user 用户名 * @param day 时间精确到天:yyyyMMdd */private void incr(Jedis jedis, String api, String user, String day) {    // 声明事务    Transaction transaction = jedis.multi();
   // 接口调用总次数    String apiCount = "api_" + api + "_count";    transaction.incr(apiCount);    // 每天接口调用次数    transaction.incr(apiCount + "_" + day);
   // 用户调用接口总量    String userApiCount = "user_" + user + "_" + apiCount;    transaction.incr(userApiCount);    // 每天用户调用接口总量    transaction.incr(userApiCount + "_" + day);
   // 事务执行    transaction.exec();}
复制代码

实际应用:管理员可以按照时间、用户查询接口的调用次数

@Datapublic class User {    private int id;    private String name;    private String address;    private int age;    private String company;    private Date created;    private Date updated;}
public interface UserDao {    User user(int id);
   List<User> users(User filer);
   int add(int id);
   boolean delete(int id);}
@Data@AllArgsConstructorpublic class ApiStats {    private int userId;    private String api;    private String day;    private int count;}
/** * 按照用户id、接口名称、日期查询调用次数,同时返回用户详情 * * @param userId 用户id * @param api   接口名称 * @param day   时间:yyyyMMdd * @return 用户详情和接口调用次数 */public Pair<User, ApiStats> query(int userId, String api, String day) {    // 1. 获取用户    User user = userDao.user(userId);    if (user == null) {        throw new IllegalArgumentException("Not found userId: " + userId);   }
   // 2. 构建 redis key    String apiKey = new StringBuilder(64)           .append("user_").append(userId)           .append("_api_").append(api)           .append("_").append(day)           .toString();
   // 3. 获取调用次数    int count = 0;    String value = jedis.get(apiKey);    if(StringUtils.isNotEmpty(value)){        try{            count = Integer.parseInt(value);       }catch (Exception e){            throw new IllegalStateException("Parsed redis value error: id=" + userId + ", value=" + value, e);       }
       if(count < 0){            throw new IllegalStateException("Invalid api count: id=" + userId + ", value=" + value);       }   }
   // 4. 构建返回结果    ApiStats apiStats = new ApiStats(userId, api, day, count);    return Pair.of(user, apiStats);}
复制代码

上面实现的代价:

  • 创建 User、ApiStats 实体类;

  • 使用 mybatis 框架构建 userDao;

  • 使用 userDao 获取用户详情;

  • 使用 jedis 客户端获取调用次数。

如果我们使用 Presto 实现会变的简单些吗 ?

使用 Presto 实现

1. 配置 MySQL Catalog

配置后可通过 mysql.ice.user 访问 user 表。

cat $PRESTO_HOME/etc/catalog/mysql.propertiesconnector.name=mysqlconnection-url=jdbc:mysql://192.168.1.108:3306connection-user=iceconnection-password=配置你的密码
复制代码

2. 配置 Redis Catalog

你需要配置虚拟的表名,完整的表名为 redis.schema1.api,Presto 内置了映射的列,_key 对应 Redis 中实际的键,_value 对应键的值,更详细的可参考:https://prestodb.io/docs/current/connector/redis.html

cat $PRESTO_HOME/etc/catalog/redis.properties
connector.name=redisredis.table-names=schema1.apiredis.nodes=192.168.1.108:6379
复制代码

在 Worker 节点验证下配置的是否正确:

3. 编写实现代码

pom.xml 中加入 presto-jdbc:

<dependency>  <groupId>com.facebook.presto</groupId>  <artifactId>presto-jdbc</artifactId>  <version>0.282</version></dependency>
复制代码

编写实现服务:ApiStatsService,该服务可通过用户 id、接口名称和日期查询调用次数。

import lombok.RequiredArgsConstructor;import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.Statement;@RequiredArgsConstructorpublic class ApiStatsService {    private static final String SQL_FORMAT = "SELECT %s, t2._value count \n" +            "  FROM mysql.ice.user t1, redis.schema1.api t2 \n" +            " WHERE t1.id = %s \n" +            "   AND t2._key = concat('user_', cast(t1.id as varchar), '_api_', '%s', '_', '%s')";    private final Connection connection;    public ResultSet query(String column, int userId, String api, String day) throws Exception {        String sql = String.format(SQL_FORMAT, column, userId, api, day);        Statement statement = connection.createStatement();        return statement.executeQuery(sql);    }}
复制代码

ApiStatsService 实现的主要逻辑通过 MySQL 和 Redis 中的表关联,关联条件基于用户 id 和 Redis 键的构建规则。

4. 实际使用

// 连接 presto 信息String prestoUrl = "jdbc:presto://192.168.1.108:8080";String prestoUser = "ice";String prestoPasswd = "";
// user 表查询的列名String userColumns = "id,name,address,age";String[] columnArray = userColumns.split(",");
// 连接 presto try (Connection connection = DriverManager.getConnection(prestoUrl, prestoUser, prestoPasswd)) {    // 创建查询服务    ApiStatsService prestoUserDao = new ApiStatsService(connection);        // 获取用户调用次数及用户详情    ResultSet resultSet = prestoUserDao.query(userColumns, 1, "tag", "20230812");    while (resultSet.next()) {        for (String column : columnArray) {            System.out.println(column + ": " + resultSet.getString(column));       }        System.out.println("count: " + resultSet.getString("count"));   }}
复制代码

输出:


基于 Presto 的实现:面向 SQL 编程,代码实现简单,原本几个阶段的查询现在转变为一次查询,实际上是 Presto 帮你进行了异构数据源的融合,而对于开发者只需要关心 Catalog 配置的是否正确,查询的 SQL 拼装的是否正确。

Presto 为你做了什么

  1. Presto 管理了数据源的元数据信息:数据源类型、连接地址、用户名和密码等信息;

  2. Presto 定义了异构数据源的访问标准:Catalog、Scehma 和 Column;

  3. Presto 定义了 SQL 语句查询的标准:DDL、DML、方法和运算符等;

  4. Presto SQL 的查询变成了分布式查询,理论上加速了查询,同时限制 SQL 查询过程中内存的使用。

发布于: 2023-08-16阅读数: 22
用户头像

分享技术上的点滴收获! 2013-08-06 加入

一杯咖啡,一首老歌,一段代码,欢迎做客冰屋,享受编码和技术带来的快乐!

评论

发布
暂无评论
Presto 设计与实现(一):开篇_数据湖_冰心的小屋_InfoQ写作社区