精华内容
下载资源
问答
  • 维度数据

    2021-03-07 11:24:13
    为了后续将订单、订单明细等数据进行实时ETL拉宽,需要提前将一些维度数据加载一个高性能存储中。此处,选择Redis作为商品维度、商品分类维度、门店维度、运营组织机构维度存储。先一次性将所有MySQL中的维度数据...

    维度数据全量装载

    为了后续将订单、订单明细等数据进行实时ETL拉宽,需要提前将一些维度数据加载一个高性能存储中。此处,选择Redis作为商品维度、商品分类维度、门店维度、运营组织机构维度存储。先一次性将所有MySQL中的维度数据全量装载到Redis中,后续只要MySQL中的维度数据更新,马上使用Flink更新Redis中的维度数据

    创建样例类

    • 在 com.chb.shop.realtime.bean 的 DimEntity 类中创建以下样例类

    • DimGoodsDBEntity 商品维度样例类

    列名描述
    goodsName商品名称
    shopId店铺id
    goodsCatId商品分类id
    shopPrice商品价格
    goodsId商品id
    • DimGoodsCatDBEntity 商品分类维度样例类
    列名描述
    catId商品分类id
    parentId商品分类父id
    catName商品分类名称
    cat_level商品分类级别
    • DimShopsDBEntity 店铺维度样例类
    列名描述
    shopId店铺id
    areaId区域id
    shopName店铺名称
    shopCompany店铺公司名称
    • DimOrgDBEntity 机构维度样例表
    列名描述
    orgId机构id
    parentId父机构id
    orgName机构名称
    orgLevel机构级别
    • DimShopCatsDBEntity门店商品分类维度样例表
    列名描述
    catId门店商品分类id
    parentId门店商品分类父id
    catName门店商品分类名称
    catSort门店商品分类级别

    参考代码:

    /**
     * 定义维度表的样例类
     *
     * @BeanProperty:生成set和get方法
     */
    // 商品维度样例类
    case class DimGoodsDBEntity(@BeanProperty goodsId:Long = 0,		// 商品id
                                @BeanProperty goodsName:String = "",	// 商品名称
                                @BeanProperty shopId:Long = 0,			// 店铺id
                                @BeanProperty goodsCatId:Int = 0,   // 商品分类id
                                @BeanProperty shopPrice:Double = 0) // 商品价格
    /**
     * 商品的伴生对象
     */
    object DimGoodsDBEntity{
      def apply(json:String): DimGoodsDBEntity = {
        //正常的话,订单明细表的商品id会存在与商品表中,假如商品id不存在商品表,这里解析的时候就会抛出异常
        if(json != null){
          val jsonObject: JSONObject = JSON.parseObject(json)
          new DimGoodsDBEntity(
            jsonObject.getLong("goodsId"),
            jsonObject.getString("goodsName"),
            jsonObject.getLong("shopId"),
            jsonObject.getInteger("goodsCatId"),
            jsonObject.getDouble("shopPrice"))
        }else{
          new DimGoodsDBEntity
        }
      }
    }
    
    
    // 商品分类维度样例类
    case class DimGoodsCatDBEntity(@BeanProperty catId:String = "",	    // 商品分类id
                                   @BeanProperty parentId:String = "",	// 商品分类父id
                                   @BeanProperty catName:String = "",	  // 商品分类名称
                                   @BeanProperty cat_level:String = "")	// 商品分类级别
    
    object DimGoodsCatDBEntity {
      def apply(json:String): DimGoodsCatDBEntity = {
        if(json != null) {
          val jsonObj = JSON.parseObject(json)
    
          val catId = jsonObj.getString("catId")
          val catName = jsonObj.getString("catName")
          val cat_level = jsonObj.getString("cat_level")
          val parentId = jsonObj.getString("parentId")
          DimGoodsCatDBEntity(catId, parentId, catName, cat_level)
        }else{
          new DimGoodsCatDBEntity
        }
      }
    }
    
    // 店铺维度样例类
    case class DimShopsDBEntity(@BeanProperty shopId:Int  = 0,		      // 店铺id
                                @BeanProperty areaId:Int  = 0,		      // 店铺所属区域id
                                @BeanProperty shopName:String  = "",	  // 店铺名称
                                @BeanProperty shopCompany:String  = "")	// 公司名称
    
    object DimShopsDBEntity {
      def apply(json:String): DimShopsDBEntity = {
        if(json != null) {
          val jsonObject = JSON.parseObject(json)
          val areaId = jsonObject.getString("areaId")
          val shopCompany = jsonObject.getString("shopCompany")
          val shopId = jsonObject.getString("shopId")
          val shopName = jsonObject.getString("shopName")
    
          DimShopsDBEntity(shopId.toInt, areaId.toInt, shopName, shopCompany)
        }else{
          new DimShopsDBEntity
        }
      }
    }
    
    // 组织结构维度样例类
    case class DimOrgDBEntity(@BeanProperty orgId:Int = 0,			  // 机构id
                              @BeanProperty parentId:Int = 0,		  // 机构父id
                              @BeanProperty orgName:String = "",	// 组织机构名称
                              @BeanProperty orgLevel:Int = 0)		  // 组织机构级别
    
    object DimOrgDBEntity {
      def apply(json:String): DimOrgDBEntity = {
        if(json != null) {
          val jsonObject = JSON.parseObject(json)
          val orgId = jsonObject.getString("orgId")
          val orgLevel = jsonObject.getString("orgLevel")
          val orgName = jsonObject.getString("orgName")
          val parentId = jsonObject.getString("parentId")
    
          DimOrgDBEntity(orgId.toInt, parentId.toInt, orgName, orgLevel.toInt)
        }else{
          new DimOrgDBEntity()
        }
      }
    }
    
    // 门店商品分类维度样例类
    case class DimShopCatDBEntity(@BeanProperty catId:String = "",	      // 商品分类id
                                  @BeanProperty parentId:String = "",	  // 商品分类父id
                                  @BeanProperty catName:String = "", 	  // 商品分类名称
                                  @BeanProperty catSort:String = "")	    // 商品分类级别
    
    object DimShopCatDBEntity {
      def apply(json:String): DimShopCatDBEntity = {
        if(json != null) {
          val jsonObj = JSON.parseObject(json)
          val catId = jsonObj.getString("catId")
          val catName = jsonObj.getString("catName")
          val catSort = jsonObj.getString("catSort")
          val parentId = jsonObj.getString("parentId")
          DimShopCatDBEntity(catId, parentId, catName, catSort)
        }else{
          new DimShopCatDBEntity()
        }
      }
    }
    

    在配置文件中添加Redis配置、MySQL配置

    # Redis配置
    redis.server.ip="node2"
    redis.server.port=6379
    
    # MySQL配置
    mysql.server.ip="node1"
    mysql.server.port=3306
    mysql.server.database="chb_shop"
    mysql.server.username="root"
    mysql.server.password="123456"
    

    编写配置工具类

    val `redis.server.ip` = config.getString("redis.server.ip")
    val `redis.server.port` = config.getString("redis.server.port")
    val `mysql.server.ip` = config.getString("mysql.server.ip")
    val `mysql.server.port` = config.getString("mysql.server.port")
    val `mysql.server.database` = config.getString("mysql.server.database")
    val `mysql.server.username` = config.getString("mysql.server.username")
    val `mysql.server.password` = config.getString("mysql.server.password")
    

    编写Redis操作工具类

    在utils类中添加RedisUtils类,使用Redis连接池操作Redis

    object RedisUtil {
      val config = new JedisPoolConfig()
    
      //是否启用后进先出, 默认true
      config.setLifo(true)
      //最大空闲连接数, 默认8个
      config.setMaxIdle(8)
      //最大连接数, 默认8个
      config.setMaxTotal(1000)
      //获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,  默认-1
      config.setMaxWaitMillis(-1)
      //逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
      config.setMinEvictableIdleTimeMillis(1800000)
      //最小空闲连接数, 默认0
      config.setMinIdle(0)
      //每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
      config.setNumTestsPerEvictionRun(3)
      //对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断  (默认逐出策略)
      config.setSoftMinEvictableIdleTimeMillis(1800000)
      //在获取连接的时候检查有效性, 默认false
      config.setTestOnBorrow(false)
      //在空闲时检查有效性, 默认false
      config.setTestWhileIdle(false)
    
      var jedisPool: JedisPool = new JedisPool(config, GlobalConfigUtil.`redis.server.ip`, GlobalConfigUtil.`redis.server.port`.toInt)
    
      /**
        * 获取Redis连接
        * @return
        */
      def getResouce() = {
        jedisPool.getResource
      }
    }
    
    读取MySQL商品维度数据到Redis

    在 cn.chb.shop.realtime.etl.dataloader 包中创建 DimensionDataLoader 单例对象,实现装载商品维度数据

    实现步骤:

    1、先从MySQL的 chb_goods 表中加载数据

    2、将数据保存到键为 chb_goods:dim_goods 的 HASH 结构中

    3、关闭资源

    参考代码:

      def main(args: Array[String]): Unit = {
        Class.forName("com.mysql.jdbc.Driver")
    
        val connection = DriverManager.getConnection("jdbc:mysql://node1:3306/chb_shop", "root", "123456")
        val jedis = RedisUtil.getJedis()
    
        loadDimGoods(connection, jedis)
      }
    
      // 加载商品维度数据到Redis
      def loadDimGoods(connection: Connection, jedis: Jedis) = {
        val querySql: String =
          """
            |SELECT
            |	t1.`goodsId`,
            |	t1.`goodsName`,
            |	t1.`goodsCatId`,
            | t1.`shopPrice`,
            |	t1.`shopId`
            |FROM
            |	chb_goods t1
          """.stripMargin
        val statement: Statement = connection.createStatement
        val resultSet: ResultSet = statement.executeQuery(querySql)
    
        while (resultSet.next) {
          val goodsId: Long = resultSet.getLong("goodsId")
          val goodsName: String = resultSet.getString("goodsName")
          val goodsCatId: Long = resultSet.getLong("goodsCatId")
          val shopPrice:Double = resultSet.getDouble("shopPrice")
          val shopId: Long = resultSet.getLong("shopId")
    
          val entity = DimGoodsDBEntity(goodsId, goodsName, shopId, goodsCatId.toInt, shopPrice)
          println(entity)
    
          jedis.hset("chb_shop:dim_goods", goodsId + "", JSON.toJSONString(entity, SerializerFeature.DisableCircularReferenceDetect))
        }
        resultSet.close()
        statement.close()
      }
    
    
    读取MySQL店铺维度数据到Redis

    实现步骤:

    1、先从MySQL的 chb_shops 表中加载数据

    2、将数据保存到键为 chb_goods:dim_shops 的 HASH 结构中

    3、关闭资源

    参考代码:

    // 加载商铺维度数据到Redis
      // 加载商铺维度数据到Redis
      def loadDimShops(connection: Connection, jedis: Jedis) = {
        val sql =
          """
            |SELECT
            |	t1.`shopId`,
            |	t1.`areaId`,
            |	t1.`shopName`,
            |	t1.`shopCompany`
            |FROM
            |	chb_shops t1
          """.stripMargin
    
        val statement = connection.createStatement()
        val resultSet = statement.executeQuery(sql)
    
        while (resultSet.next()) {
          val shopId = resultSet.getInt("shopId")
          val areaId = resultSet.getInt("areaId")
          val shopName = resultSet.getString("shopName")
          val shopCompany = resultSet.getString("shopCompany")
    
          val dimShop = DimShopsDBEntity(shopId, areaId, shopName, shopCompany)
          println(dimShop)
          jedis.hset("chb_shop:dim_shops", shopId + "", JSON.toJSONString(dimShop, SerializerFeature.DisableCircularReferenceDetect))
        }
    
        resultSet.close()
        statement.close()
      }
    
    读取MySQL商品分类维度数据到Redis

    实现步骤:

    1、先从MySQL的 chb_goods_cats 表中加载数据

    2、将数据保存到键为 chb_shop:dim_goods_cats 的 HASH 结构中

    3、关闭资源

    参考代码:

      def loadDimGoodsCats(connection: Connection, jedis: Jedis) = {
        val sql = """
                    |SELECT
                    |	t1.`catId`,
                    |	t1.`parentId`,
                    |	t1.`catName`,
                    |	t1.`cat_level`
                    |FROM
                    |	chb_goods_cats t1
                  """.stripMargin
    
        val statement = connection.createStatement()
        val resultSet = statement.executeQuery(sql)
    
        while(resultSet.next()) {
          val catId = resultSet.getString("catId")
          val parentId = resultSet.getString("parentId")
          val catName = resultSet.getString("catName")
          val cat_level = resultSet.getString("cat_level")
    
          val entity = DimGoodsCatDBEntity(catId, parentId, catName, cat_level)
          println(entity)
    
          jedis.hset("chb_shop:dim_goods_cats", catId, JSON.toJSONString(entity, SerializerFeature.DisableCircularReferenceDetect))
        }
    
        resultSet.close()
        statement.close()
      }
    
    读取MySQL组织结构数据到Redis

    实现步骤:

    1、先从MySQL的 chb_org 表中加载数据

    2、将数据保存到键为 chb_shop:dim_org 的 HASH 结构中

    3、关闭资源

    参考代码:

      // 加载组织结构维度数据
      def loadDimOrg(connection: Connection, jedis: Jedis) = {
        val sql = """
                    |SELECT
                    |	orgid,
                    |	parentid,
                    |	orgName,
                    |	orgLevel
                    |FROM
                    |	chb_org
                  """.stripMargin
    
        val statement = connection.createStatement()
        val resultSet = statement.executeQuery(sql)
    
        while(resultSet.next()) {
          val orgId = resultSet.getInt("orgId")
          val parentId = resultSet.getInt("parentId")
          val orgName = resultSet.getString("orgName")
          val orgLevel = resultSet.getInt("orgLevel")
    
          val entity = DimOrgDBEntity(orgId, parentId, orgName, orgLevel)
          println(entity)
          jedis.hset("chb_shop:dim_org", orgId + "", JSON.toJSONString(entity, SerializerFeature.DisableCircularReferenceDetect))
        }
      }
    
    读取MySQL门店商品分类维度数据到Redis

    实现步骤:

    1、先从MySQL的 chb_shop_cats表中加载数据

    2、将数据保存到键为 chb_shop:dim_shop_cats 的 HASH 结构中

    3、关闭资源

    参考代码:

    // 加载门店商品分类维度数据到Redis
      def LoadDimShopCats(connection: Connection, jedis: Jedis): Unit ={
        val sql = """
                    |SELECT
                    |	t1.`catId`,
                    |	t1.`parentId`,
                    |	t1.`catName`,
                    |	t1.`catSort`
                    |FROM
                    |	chb_shop_cats t1
                  """.stripMargin
    
        val statement = connection.createStatement()
        val resultSet = statement.executeQuery(sql)
    
        while(resultSet.next()) {
          val catId = resultSet.getString("catId")
          val parentId = resultSet.getString("parentId")
          val catName = resultSet.getString("catName")
          val cat_level = resultSet.getString("catSort")
    
          val entity = DimShopCatDBEntity(catId, parentId, catName, cat_level)
          println(entity)
    
          jedis.hset("chb_shop:dim_shop_cats", catId, JSON.toJSONString(entity, SerializerFeature.DisableCircularReferenceDetect))
        }
    
        resultSet.close()
        statement.close()
      }
    

    维度数据增量更新

    创建同步Redis中维度数据ETL处理类

    在etl包下创建SyncDimDataETL类,继承MySqlBaseETL特质,实现process方法

    /**
      * Redis维度数据同步业务
      */
    class SyncDimDataETL(env: StreamExecutionEnvironment) extends MySqlBaseETL(env) {
      /**
        * 业务处理接口
        */
      override def process(): Unit = {
        
      }
    }
    
    过滤维度表消息

    只过滤出来维度表的数据流

    // 过滤维度表消息
    val dimRowDataDS = canalRowDataDS.filter {
        rowData =>
        rowData.getTableName match {
            case "chb_goods" => true
            case "chb_shops" => true
            case "chb_goods_cats" => true
            case "chb_org" => true
            case _ => false
        }
    }
    
    处理新增/更新维度消息

    使用Redis处理增量同步,insert和update消息统一处理,直接将Redis中的Hash结构更新

    /**
      * Redis维度数据同步业务
      */
    class SyncDimDataETL(env: StreamExecutionEnvironment) extends MySqlBaseETL(env) {
        ........
    
        // 更新Redis
        dimRowDataDS.addSink(new RichSinkFunction[RowData] {
          var jedis: Jedis = _
    
          override def open(parameters: Configuration): Unit = {
            jedis = RedisUtil.getResouce()
            jedis.select(1)
          }
    
          override def close(): Unit = {
            if (jedis != null) {
              jedis.close()
            }
          }
    
          override def invoke(rowData: RowData, context: SinkFunction.Context[_]): Unit = {
            rowData.getEventType match {
              case eventType if(eventType == "insert" || eventType == "update") => updateDimData(rowData)
            }
          }
    
          private def updateDimData(rowData:RowData) = {
            rowData.getTableName match {
              case "chb_goods" => {
                val goodsId = rowData.getColumns.get("goodsId")
                val goodsName = rowData.getColumns.get("goodsName")
                val goodsCatId = rowData.getColumns.get("goodsCatId")
                val shopId = rowData.getColumns.get("shopId")
                val shopPrice = rowData.getColumns.get("shopPrice")
    
                val entity = DimGoodsDBEntity(goodsId.toLong, goodsName, shopId.toLong, goodsCatId.toInt, shopPrice.toDouble)
                println("增量更新:" + entity)
    
                jedis.hset("chb_shop:dim_goods", goodsId, JSON.toJSONString(entity, SerializerFeature.DisableCircularReferenceDetect))
              }
              case "chb_shops" => {
                val shopId = rowData.getColumns.get("shopId")
                val areaId = rowData.getColumns.get("areaId")
                val shopName = rowData.getColumns.get("shopName")
                val shopCompany = rowData.getColumns.get("shopCompany")
    
                val dimShop = DimShopsDBEntity(shopId.toInt, areaId.toInt, shopName, shopCompany)
                println("增量更新:" + dimShop)
                jedis.hset("chb_shop:dim_shops", shopId + "", JSON.toJSONString(dimShop, SerializerFeature.DisableCircularReferenceDetect))
              }
              case "chb_goods_cats" => {
                val catId = rowData.getColumns.get("catId")
                val parentId = rowData.getColumns.get("parentId")
                val catName = rowData.getColumns.get("catName")
                val cat_level = rowData.getColumns.get("cat_level")
    
                val entity = DimGoodsCatDBEntity(catId, parentId, catName, cat_level)
                println("增量更新:" + entity)
    
                jedis.hset("chb_shop:dim_goods_cats", catId, JSON.toJSONString(entity, SerializerFeature.DisableCircularReferenceDetect))
              }
              case "chb_org" => {
                val orgId = rowData.getColumns.get("orgId")
                val parentId = rowData.getColumns.get("parentId")
                val orgName = rowData.getColumns.get("orgName")
                val orgLevel = rowData.getColumns.get("orgLevel")
    
                val entity = DimOrgDBEntity(orgId.toInt, parentId.toInt, orgName, orgLevel.toInt)
                println("增量更新:" + entity)
                jedis.hset("chb_shop:dim_org", orgId + "", JSON.toJSONString(entity, SerializerFeature.DisableCircularReferenceDetect))
    
              }
            }
          }
        })
      }
    }
    
    处理删除维度消息

    当接收到Kafka中的binlog消息是delete时,需要将Redis中的维度数据删除

    /**
      * Redis维度数据同步业务
      */
    class SyncDimDataETL(env: StreamExecutionEnvironment) extends MySqlBaseETL(env) {
      /**
        * 业务处理接口
        */
      override def process(): Unit = {
        ....
    
        // 更新Redis
        dimRowDataDS.addSink(new RichSinkFunction[RowData] {
          .......
    
          override def invoke(rowData: RowData, context: SinkFunction.Context[_]): Unit = {
            rowData.getEventType match {
              case eventType if(eventType == "insert" || eventType == "update") => updateDimData(rowData)
              case "delete" => deleteDimData(rowData)
            }
          }
    
          private def deleteDimData(rowData: RowData) = {
            rowData.getTableName match {
              case "chb_goods" => {
                jedis.hdel("chb_shop:dim_goods", rowData.getColumns.get("goodsId"))
              }
              case "chb_shops" => {
                jedis.hdel("chb_shop:dim_shops", rowData.getColumns.get("shopId"))
              }
              case "chb_goods_cats" => {
                jedis.hdel("chb_shop:dim_goods_cats", rowData.getColumns.get("catId"))
              }
              case "chb_org" => {
                jedis.hdel("chb_shop:dim_org", rowData.getColumns.get("orgId"))
              }
            }
          }
        })
      }
    }
    
    在App中调用同步ETL

    1、在App中创建实时同步ETL,并调用处理方法

        val syncRedisDimDataETL = new SyncRedisDimDataETL(env)
        syncRedisDimDataETL.process()
    

    2、启动Flink程序

    3、测试

    • 新增MySQL中的一条表数据,测试Redis中的数据是否同步更新

    • 修改MySQL中的一条表数据,测试Redis中的数据是否同步更新

    • 删除MySQL中的一条表数据,测试Redis中的数据是否同步更新

    查看redis中的数据

    192.168.88.121:0>select 1
    "OK"
    192.168.88.121:1>hget chb_shop:dim_goods 115912
    "{"goodsCatId":10361,"goodsId":115912,"goodsName":"机械革命深海泰坦X9Ti-R","shopId":100364,"shopPrice":13999.0}"
    
    展开全文
  • 表的分类 1、实体表 实体表,一般是指一个现实存在的业务对象。 比如用户,商品,商家,销售员等等。 用户表: 用户id 姓名 生日 性别 ... 2

    表的分类

    1、实体表

    实体表,一般是指一个现实存在的业务对象。

    比如用户,商品,商家,销售员等等。

    用户表:

    用户id

    姓名

    生日

    性别

    邮箱

    用户等级

    创建时间

    1

    张三

    2011-11-11

    zs@163.com

    2

    2018-11-11

    2

    李四

    2011-11-11

    ls@163.com

    3

    2018-11-11

    2、维度表

    维度表,一般是指对应一些业务状态,编号的解释表。也可以称之为码表。

    比如地区表,订单状态,支付方式,审批状态,商品分类等等。

    订单状态表:

    订单状态编号

    订单状态名称

    1

    未支付

    2

    支付

    3

    发货中

    4

    已发货

    5

    已完成

           商品分类表:

    商品分类编号

    分类名称

    1

    服装

    2

    保健

    3

    电器

    4

    图书

    3、事务型事实表

    事务型事实表,一般指随着业务发生不断产生的数据。特点是一旦发生不会再变化。

    一般比如,交易流水,操作日志,出库入库记录等等。

    维度建模中一个非常重要的步骤是定义事实表的粒度。定义了事实表的粒度,则事实表能表达数据的详细程度就确定了。定义粒度的例子如下:

    客户的零售单据上的每个条目。

    定义好事实表的粒度有很大的用处。

    第一个用处就是用来确定维度是否与该事实表相关。例如,对于粒度细到医疗单据上条目项的事实表来说,医疗结果是不会作为维度和它进行关联的,因为它们不在同一个粒度上。但是,对于一般的E/R数据模型来说,医疗单据是和医疗结果是进行关联的。通常的规范化建模里没有粒度的概念,它们表示的是实体之间的关系,这也是规范化建模和维度建模中一个较大的不同之处。

    定义成原子的事实表粒度后,可以选择较多的维度来对该事实表进行描述。也就是说,事实表的粒度越细,能记载的信息就会越多。原子粒度的事实表对维度建模来说是至关重要的。

    交易流水表:

    编号

    对外业务编号

    订单编号

    用户编号

    支付宝交易流水编号

    支付金额

    交易内容

    支付类型

    支付时间

    1

    7577697945

    1

    111

    QEyF-63000323

    223.00

    海狗人参丸1

    alipay

    2019-02-10 00:50:02

    2

    0170099522

    2

    222

    qdwV-25111279

    589.00

    海狗人参丸2

    wechatpay

    2019-02-10 00:50:02

    4、周期型事实表

    周期型事实表,一般指随着业务发生不断产生的数据。

    与事务型不同的是,数据会随着业务周期性的推进而变化。

     比如订单,其中订单状态会周期性变化。再比如,请假、贷款申请,随着批复状态在周期性变化。

    订单表:

    订单编号

    订单金额

    订单状态

    用户id

    支付方式

    支付流水号

    创建时间

    操作时间

    1

    223.00

    2

    111

    alipay

    QEyF-63000323

    2019-02-10 00:01:29

    2019-02-10 00:01:29

    2

    589.00

    2

    222

    wechatpay

    qdwV-25111279

    2019-02-10 00:05:02

    2019-02-10 00:05:02

     

    数据仓库中的维度和粒度

    从时间的角度讲:简单说粒度就是事实表里测量值的测量‘频率’。比如说,销售库里的销售额,可以是一 天一个值,也可以是一个月一个值,甚至一年一个值,这就是相对于时间维度表的力度;可以是一个商品一个值,也可以是一类商品一个值,这就是相对于商品的粒度。

     

     

    展开全文
  • 数据仓库维度模型设计

    千次阅读 2020-05-05 16:36:32
    维度建模以分析决策的需求出发构建模型,构建的数据模型为分析需求服务,因此它重点解决用户如何更快速完成分析需求,同时还有较好的大规模复杂查询的响应性能。 维度建模是专门应用于分析型数据库、数据仓库、...

    维度建模基本概念

          维度建模以分析决策的需求出发构建模型,构建的数据模型为分析需求服务,因此它重点解决用户如何更快速完成分析需求,同时还有较好的大规模复杂查询的响应性能。

          维度建模是专门应用于分析型数据库、数据仓库、数据集市建模的方法。数据集市可以理解为是一种"小型数据仓库"

     

    事实表

                                                     

    发生在现实世界中的操作型事件,其所产生的可度量数值,存储在事实表中

    事实表表示对分析主题的度量。比如一次购买行为我们就可以理解为是一个事实

    图中的订单表就是一个事实表,可以理解他就是在现实中发生的一次操作型事件,每完成一个订单,就会在订单中增加一条记录。

    事实表的特征:表里没有存放实际的内容,他是一堆主键的集合,这些ID分别能对应到维度表中的一条记录事实表包含了与各维度表相关联的外键,可与维度表关联。事实表的度量通常是数值类型(条/个/次),且记录数会不断增加,表数据规模迅速增长。

     

    维度表

    维度表示要对数据进行分析时所用的一个量,比如你要分析产品销售情况, 你可以选择类别进行分析,或按区域分析这样的按..分析就构成一个维度。上图中的用户表、商家表、时间表这些都属于维度表。这些表都有一个唯一的主键,然后在表中存放了详细的数据信息。

    每个维度表都包含单一的主键列。维度表的主键可以作为与之关联的任何事实表的外键,当然,维度表行的描述环境应与事实表行完全对应。维度表通常比较宽,是扁平型非规范表,包含大量的低粒度的文本属性。

    总的说来,在数据仓库中不需要严格遵守规范化设计原则。因为数据仓库的主导功能就是面向分析,以查询为主,不涉及数据更新操作

    事实表的设计是以能够正确记录历史信息为准则。

    维度表的设计是以能够以合适的角度来聚合主题内容为准则。

     

    维度建模三种模式

    星型模型

    星形模式(Star Schema)是最常用的维度建模方式。星型模式是以事实表为中心,所有的维度表直接连接在事实表上,像星星一样。

    星形模式的维度建模由一个事实表和一组维表成,且具有以下特点:

    a. 维表只和事实表关联,维表之间没有关联;

    b. 每个维表主键为单列,且该主键放置在事实表中,作为两边连接的外键;

    c. 以事实表为核心,维表围绕核心呈星形分布;

    雪花模式

    雪花模式(Snowflake Schema)是对星形模式的扩展。雪花模式的维度表可以拥有其他维度表的,虽然这种模型相比星型更规范一些,但是由于这种模型不太容易理解,维护成本比较高,而且性能方面需要关联多层维表,性能也比星型模型要低。所以一般不是很常用。

    星座模式

    星座模式是星型模式延伸而来,星型模式是基于一张事实表的,而星座模式是基于多张事实表的,而且共享维度信息。

    前面介绍的两种维度建模方法都是多维表对应单事实表,但在很多时候维度空间内的事实表不止一个,而一个维表也可能被多个事实表用到。在业务发展后期,绝大部分维度建模都采用的是星座模式。

     

    数据仓库分层架构

     

    为什么要分层,分层的好处

    清晰数据结构

    每一个数据分层都有它的作用域,这样我们在使用表的时候能更方便地定位和理解。

    方便数据血缘追踪

    简单来说,我们最终给业务呈现的是一个能直接使用业务表,但是它的来源有很多,如果有一张来源表出问题了,我们希望能够快速准确地定位到问题,并清楚它的危害范围。

    减少重复开发:

    规范数据分层,开发一些通用的中间层数据,能够减少极大的重复计算。

    把复杂问题简单化:

    将一个复杂的任务分解成多个步骤来完成,每一层只处理单一的步骤,比较简单和容易理解。而且便于维护数据的准确性,当数据出现问题之后,可以不用修复所有的数据,只需要从有问题的步骤开始修复。

    屏蔽原始数据的异常:

    屏蔽业务的影响,不必改一次业务就需要重新接入数据

     

    数仓分层思想

    数据分层每个企业根据自己的业务需求可以分成不同的层次,但是最基础的分层思想,理论上数据分为三个层数据运营层数据仓库层数据服务层。基于这个基础分层之上添加新的层次,来满足不同的业务需求。

    数据运营层(ODS)

    Operate data store(操作数据-存储),是最接近数据源中数据的一层,数据源中的数据,经过抽取、洗净、传输,也就说传说中的ETL之后,装入ODS。本层的数据,总体上大多是按照源头业务系统的分类方式而分类的。

    例如:MySQL里面的一张表可以通过sqoop之间抽取到ODS层

    ODS层数据的来源方式:

    • 业务库
    • 经常会使用sqoop来抽取,比如我们每天定时抽取一次。在实时方面,可以考虑用canal监听mysql的binlog,实时接入即可。
    • 埋点日志
    • 线上系统会打入各种日志,这些日志一般以文件的形式保存,我们可以选择用flume定时抽取,也可以用用spark streaming或者Flink来实时接入,当然,kafka也会是一个关键的角色。
    • 消息队列
    • 来自ActiveMQ、Kafka的数据等

    数据仓库层(DW)

    Data warehouse(数据仓库)。在这里,从ODS层中获得的数据按照主题建立各种数据模型。例如以研究人的旅游消费为主题的数据集中,便可以结合航空公司的登机出行信息,以及银联系统的刷卡记录,进行结合分析,产生数据集。在这里,我们需要了解四个概念:维(dimension)、事实(Fact)、指标(Index)和粒度( Granularity)。

    DW数据分层,由下到上为 DWD,DWB,DWS

    DWD:data warehouse detail 细节数据层,是业务层与数据仓库的隔离层。

    DWB:data warehouse base 基础数据层,存储的是客观数据,一般用作中间层,可以认为是大量指标的数据层。

    DWS:data warehouse service 服务数据层,基于DWB上的基础数据,整合汇总成分析某一个主题域的服务数据,一般是宽表。

    数据服务层/应用层(ADS)

    Application Data Service(应用数据服务)。该层主要是提供数据产品和数据分析使用的数据一般会存放在ES、MySQL等系统中供线上系统使用,也可能会存在Hive或者Druid中供数据分析和数据挖掘使用。

    例如:我们经常说的报表数据,或者说那种大宽表,一般就放在这里。

     

     阿里巴巴数据仓库分层架构样例

    ODS 数据准备层

    功能

    ODS层是数据仓库准备区,为DWD层提供基础原始数据,可减少对业务系统的影响 

    建模方式及原则:

    从业务系统增量抽取、保留时间由业务需求决定、可分表进行周期存储、数据不做清洗转换与业务系统数据模型保持一致、按主题逻辑划分

    DWD 数据明细层

    功能:

    为DW层提供来源明细数据,提供业务系统细节数据的长期沉淀,为未来分析类需求的扩展提供历史数据支撑  

    建模方式及原则:

    数据模型与ODS层一致,不做清洗转换处理、为支持数据重跑可额外增加数据业务日期字段、可按年月日进行分表、用增量ODS层数据和前一天DWD相关表进行merge处理

    DW(B/S) 数据汇总层 

    功能:

    为DW、ST层提供细粒度数据,细化成DWB和DWS;

    DWB是根据DWD明细数据进行转换,如维度转代理键、身份证清洗、会员注册来源清晰、字段合并、空值处理、脏数据处理、IP清晰转换、账号余额清洗、资金来源清洗等;

    DWS是根据DWB层数据按各个维度ID进行高粒度汇总聚合,如按交易来源,交易类型进行汇合

    建模方式及原则:

    聚合、汇总增加派生事实;

    关联其它主题的事实表,DW层可能会跨主题域;

    DWB保持低粒度汇总加工数据,DWS保持高粒度汇总数据;

    数据模型可能采用反范式设计,合并信息等。

     Data Market (数据集市)

    功能:

    可以是一些宽表,是根据DW层数据按照各种维度或多种维度组合把需要查询的一些事实字段进行汇总统计并作为单独的列进行存储

    满足一些特定查询、数据挖掘应用;

    应用集市数据存储

    建模方式及原则:

    尽量减少数据访问时计算(优化检索)

    维度建模,星型模型

    事实拉宽,度量预先计算

    分表存储

    ST 数据应用层(ADS层)

    功能:

    ST层面向用户应用和分析需求,包括前端报表、分析图表、KPI、仪表盘、OLAP、专题等分析,面向最终结果用户

    适合作OLAP、报表模型,如ROLAP,MOLAP;

    联机事务处理OLTP、联机分析处理OLAP。

    OLTP是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,例如银行交易。OLAP是数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。

    联机分析处理的用户是企业中的专业分析人员及管理决策人员,他们在分析业务经营的数据时,从不同的角度来审视业务的衡量指标是一种很自然的思考模式。例如分析销售数据,可能会综合时间周期、产品类别、分销渠道、地理分布、客户群类等多种因素来考量。

    根据DW层经过聚合汇总统计后的粗粒度事实表

    建模方式及原则:

    保持数据量小

    维度建模,星形模型

    各位维度代理键+度量;

    增加数据业务日期字段,支持数据重跑;

    不分表存储

    展开全文
  • 大家好,今天为大家分享了解数据仓库维度模型设计分层思想 希望加载能够坚持的学习,送给大家一句话今日事,今日毕 今日目标: 了解什么是事实表 维度表 了解维度表的建模三种方式星性模型 雪花模型 星座模型 了解...

    大家好,今天为大家分享了解数据仓库维度模型设计和分层思想
    希望加载能够坚持的学习,送给大家一句话今日事,今日毕
    今日目标:

    • 了解什么是事实表 维度表
    • 了解维度表的建模三种方式星性模型 雪花模型 星座模型
    • 了解数仓分层ODS DW ADS

    展开全文
  • 阿里中台的概念,可以说是近些年来的颇为火爆的概念。从十余年前的阿里在内部完成这一过程,并...它来告诉用户如何构建数据化服务体系,包括从数据集成、数据建模、数据开发、数据共享到数据质量、数据治理等。用户可以
  • 多维数据模型中维度、度量、层级理解 如何认识(看)对象? 当我们去看、去观测一个事物,一个对象的时候,总是不能回避的是观测方向、观测角度、观测属性、观测维度、观测特征等待。 角度,属性、方向、维度、...
  • 一般常规的数据仓库层级结构可分为:ods、dw(可在细分为dwd...dw层:称为中间层,按主题建模(域->主题)的明细数据层,数据粒度与ods层一致。 dm层:称为数据集市层,集市层是按照业务主题、分主题构建出来的、面向特
  • 保存度量值的详细信息或者事实的表,表中是键值列加上度量列(数值或者号码之类的),最底层级别的明细,包含数据、汇总数据等。 维度维度的每个成员的特定名称(属性),包含事实记录的特性,如产品维度表中包含...
  • Hive数据仓库维度分析

    2021-10-06 15:42:14
    这里个人理解:先有指标后有维度,指标相当于具体一些列数据,维度相当于在这一些数据中的某种条件下的数据. 要进行维度分析需要先理解两个术语:指标和维度。指标是衡量事物发展的标准,也叫度量,如价 格,销量等;...
  • 推荐学习《华为数据之道》《数据仓库工具箱-维度建模权威指南》两本书。 此文档是数据仓库建模的知识点总结文档,在持续更新中(2021-10-13)。 文章目录数据仓库知识点总结1.数据仓库分层理论1.1数仓分层架构的好处...
  • 数据仓库--事实表和维度

    万次阅读 多人点赞 2014-05-11 18:39:00
    最明显的区别是:操作型数据库主要是用来支撑即时操作,对数据库的性能质量要求都比较高,为了防止“garbage in,garbage out”,通常设计操作型数据库的都要遵循几个范式的约束,除非少数情况下为了性能进行妥协...
  • 数据仓库维度建模之事实表设计

    千次阅读 2020-02-18 15:44:22
    一、DWD层明细事实表设计 事实表有粒度大小之分,基于数据仓库...为了提高模型易用性,将常规维度表中的常用属性数据冗余到相应的事实表中,从而在使用的时候避免维表关联的方式,既为数据降维。 事实表的设计主要是...
  • 理解维度数据仓库——事实表、维度表、聚合表

    万次阅读 多人点赞 2019-05-23 21:16:22
    理解维度数据仓库——事实表、维度表、聚合表 一、事实表 在多维数据仓库中,保存度量值的详细值或事实的表称为“事实表”。一个按照州、产品月份划分的销售量销售额存储的事实表有5个列,概念上与下面的示例...
  • 2.1 维度模型 2.1.1 星型模型 2.1.2 雪花模型 2.1.3星座模型 2.2 范式模型 2.3 Data Vault模型 2.4 Anchor模型
  • 数据仓库(基础篇)——基于维度建模思想

    千次阅读 多人点赞 2021-08-26 17:45:05
    作为一名互联网行业的小白,博主写博客一方面是为了记录自己的学习过程,另一方面是总结自己所犯的错误希望能够帮助到很多自己一样处于起步阶段的萌新。但由于水平有限,博客中难免会有一些错误出现,有纰漏之处...
  • 维度建模是数仓的核心,建模的好坏,决定了数仓对于企业支撑的价值。”01基本概念企业可以通过一系列维数相同的数据集市递增地构建数据仓库,通过使用一致的维度,能够共同看到不同数据集市中的...
  • 文章目录1. 建模流程2. 迭代流程3. 维度表4. 事实表 1. 建模流程 确认每个主题域,明确范围,即事实表清单。 根据业务流程(比如投保->...…)拆分相关实体 确认维度维度退化:who?...在事实表中按需添加属性
  • 电信互联网行业数据安全标准体系包括基础共性、关键技术、安全管理、重点领域四大类标准。基础共性标准包括术语定义、数据安全框架、数据分类分级,相关标准为各类标准提供基础性支撑。关键技术标准从数据采集、...
  • 今天大家分享下,在做业务预测时,需要使用到天气方面的数据,这些数据需要从一些网站中进行收集,这是我们就要用到爬虫,收集到一个完整的天气预报数据(用excel保存): 1、爬虫前准备: ① python 3.6已正常...
  • 数据仓库常见建模方法与建模实例演示

    万次阅读 多人点赞 2020-04-14 15:52:09
    大数据的数仓建模是通过建模的方法更好的组织、存储数据,以便在 性能、成本、效率和数据质量之间找到最佳平衡点。一般主要从下面四点考虑 访问性能:能够快速查询所需的数据,减少数据I/O 数据成本:减少不必要的...
  • 1、与几个概念的关系 操作型业务系统 对于这个概念大家都不陌生。企业业务赖以运转的交易系统就属于操作型业务...不管是面向业务的业务系统、经过数据统一后的主数据系统或者基于微服务架构的服务中心的数据,都是作为
  • 数据仓库维度建模总结

    千次阅读 2020-09-08 16:12:52
    统计各个主题对象的当天行为,服务于DWT 层的主题宽表,以及一些业务明细数据, 应对特殊需求(例如,购买行为,统计商品复购率) DWS层表设计原则 通过外键获取相关的度量值,整合多个dwd事实表度量值构成新表。 ...
  • 点击上方 "云祁QI"关注,星标或置顶一起成长如今,随着诸如互联网以及物联网等技术的不断发展,越来越多的数据被生产出来。据统计,每天大约有超过2.5亿亿字节的各种各样数据...
  • 作者:彭锋 宋文欣 孙浩峰来源:大数据DT(ID:hzdashuju)数据仓库是一个面向主题的、集成的、随时间变化但信息本身相对稳定的数据集合,用于支持管理决策过程。数据仓库的主要功能如下...
  • 了解过数据仓库历史的人都知道Bill Inmon、 Ralph Kimball。 Bill Inmon 代表作《Building the Data WareHouse》 , Ralph Kimball代表作为 《The Data Warehouse Toolkit》、《The data Warehouse lifecycle》。两...
  • 随着公司业务不断发展,数据种类存储呈现爆发式增长,繁多的业务数据如何被各业务中心分析使用,如何有效组织管理大量业务数据,减少大数据平台相近逻辑重复计算、相近数据...明细区:采用维度建模方法,整合近源

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,833
精华内容 3,533
关键字:

明细数据和维度数据