文章目录
- 一 在clickhouse中将宽表转换为Bitmap表
- 1 任务目标
- 2 步骤分析
- 3 代码实现
- (1)搭建模块
- (2)pom文件
- (3)创建数据库
- (4)主程序
- (5)查看数据
- (6)打包发布
- 二 用户分群
- 1 功能需求
- 2 结构分析
- 3 SpringBoot概述
- (1)Web服务概况
- (2)SpringBoot简介
- (3)Springboot和SSM的关系
- (4)没有xml,去哪配置
- (5)Springboot 的优势
- 4 测试模块搭建
- (1)新建工程
- (2)应用开发的分层
- 5 各层涉及到的标签
- (1)controller层
- @RestController 和 @RequestMapping 标识类和方法
- @RequestParam获取路径
- @PathVariable获取主键
- @RequestBody获取请求体中的参数
- (2)service层
- @Service
- @Autowired
一 在clickhouse中将宽表转换为Bitmap表
1 任务目标
为了能够更快的进行条件查询群体,把clickhouse中的宽表,通过行转列、聚合能操作转入Bitmap表。
2 步骤分析
- 读取标签定义表,用于获得标签名称和标签类型。
- 建立四种不同数据类型的标签值表
- 根据标签类型的不同要写入到四种不同的Bitmap表中。
3 代码实现
(1)搭建模块
task-bitmap,添加scala目录,标记为源码,创建scala类com.hzy.userprofile.app.TaskBitmapApp。
(2)pom文件
添加如下配置信息
<dependencies><dependency><groupId>com.hzy.userprofile</groupId><artifactId>task-common</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies><build><plugins><!-- 该插件用于将Scala代码编译成class文件 --><plugin><groupId>net.alchim31.maven</groupId><artifactId>scala-maven-plugin</artifactId><version>3.4.6</version><executions><execution><!-- 声明绑定到maven的compile阶段 --><goals><goal>compile</goal><goal>testCompile</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.0.0</version><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin></plugins></build>
(3)创建数据库
在ClickHouse中userProfile1009库中创建以下四个表。
create table user_tag_value_string( tag_code String,tag_value String,us AggregateFunction(groupBitmap,UInt64),dt String )engine=AggregatingMergeTreepartition by (dt,tag_code)order by tag_value;create table user_tag_value_decimal( tag_code String,tag_value Decimal64(2),us AggregateFunction(groupBitmap,UInt64),dt String )engine=AggregatingMergeTreepartition by (dt,tag_code)order by tag_value;create table user_tag_value_long( tag_code String,tag_value UInt64,us AggregateFunction(groupBitmap,UInt64),dt String )engine=AggregatingMergeTreepartition by (dt,tag_code)order by tag_value;create table user_tag_value_date( tag_code String,tag_value Date,us AggregateFunction(groupBitmap,UInt64),dt String )engine=AggregatingMergeTreepartition by (dt,tag_code)order by tag_value;
(4)主程序
(5)查看数据
select tag_code,tag_value,bitmapToArray(us) from user_tag_value_string;
此时表中的数据存在重复现象,需要进行幂等性处理
(6)打包发布
把程序打包。
在【流程任务管理】中增加任务,任务级别400。
执行【手动调度任务】,选择数仓的业务日期。
任务执行完成后查看结果。
SELECTtag_code,tag_value,bitmapToArray(us)
FROM user_tag_value_string
至此夜间处理的部分已经结束,即将数仓中的数据,一步一步的导入到ClickHouse中。
接下来需要进行即席查询部分,即根据用户的需求立刻产生结果,用户根据页面操作,所见即所得,主要涉及web开发的技术。

数据存储到ClickHouse还不能被外部的应用使用,还需要进行一些人工干预,其中两个重点是
- 用户标签明细及分析(洞察):根据一些标签进行统计分析,如这个标签涉及多少人,各个标签的分布比例,有点类似于数仓最后的分析,如饼状图等,只不过数仓不只是以用户这一个维度去分析。
- 用户分群:下游应用都不是直接使用这写标签,需要将标签再进行一步组合,如根据运行,决策,广告等各种需求,将不同的标签进行归类,归类之后,将涉及到这些标签的人群集中起来,打成一个包,称为“人群包”,即一个一个的用户id。
二 用户分群
1 功能需求
点击分区【创建分群】
填写分群信息,需要填写的内容
| 字段 | 描述 |
|---|---|
| 分群名称 | 用户分群的信息 |
| 业务日期(调试) | 实际生产环境应该是当前日期的前一日 |
| 筛选条件 | 根据三级标签进行筛选, |
| 分群人数 | 用于圈选的用户一边调整筛选条件一边了解人群数 |
| 更新类型 | 自动就是每日定时更新,手动更新就是通过界面手动触发。 |
2 结构分析

分群服务提供主要几个功能:
- 接收页面传来的分群信息
- 把分群基本信息保存在MySQL作为管理数据。
- 利用clickhouse计算出分群的人群结果(Bitmap)保存起来,用于后续对该人群进行统计分析。
- 将人群uid写入redis,用于营销、广告应用的高频访问数据。
3 SpringBoot概述
(1)Web服务概况
Web服务(http服务),接收网页或者App应用发起的http请求,然后通过程序进行处理、计算、查询、存储等,再将结果返回给页面或App应用。

基本架构如下图:

- 平台化管理:越来越多的企业认为使用脚本管理不方便,容易出错。无论是离线还是实时数仓,更多的是将脚本变为页面,越来越多脚本中的SQL会被放到页面中。
- 数据治理:需要大量的元数据管理,血缘管理等,虽然有许多的开源软件,但每个软件都是处理一部分功能,是彼此独立的,从整体掌控来说,对企业十分不方便,企业倾向的是一个统一的管理平台,也称为数据中台,无论是血缘,质量,指标,标准化管理,将所有的数据都放到一个页面中,对web开发有一定的需求。
- 数据接口:上图的模式为web应用接收页面的请求,返回给页面,除了这种模式,还存在其他的web应用去调用这个web应用的情况,希望这个web应用对外提供一个接口,达到解耦合的目的,第二个目的是不希望其他应用访问我的数据库,可以发布一个对外的接口,让外部应用去访问这个数据接口,达到读取数据的目的。
(2)SpringBoot简介
Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。
该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。
(3)Springboot和SSM的关系
Springboot整合了SpringMVC,Spring等核心功能。也就是说本质上实现功能的还是原有的SpringMVC,Spring的包,但是Springboot单独包装了一层,这样用户就不必直接对SpringMVC,Spring等,在xml中配置。
(4)没有xml,去哪配置
Springboot实际上就是把以前需要用户手工配置的部分,全部作为默认项。除非用户需要额外更改,不然不用配置。这就是所谓的:“约定大于配置”
如果需要特别配置的时候,去修改application.properties (application.yml)
(5)Springboot 的优势
- 不再需要那些千篇一律,繁琐的xml文件。
- 内嵌Tomcat,不再需要外部的Tomcat。
- 更方便的和各个第三方工具(mysql,redis,elasticsearch,dubbo,kafka等整合),而只要维护一个配置文件即可。
4 测试模块搭建
(1)新建工程
在user_profile_manage_0224新建工程【Spring initializr】,点击【next】
具体信息如下:

点击next选择依赖,最终会形成一个pom文件,暂时选择最基本的配置,如下图

标红窗口内最好选择不带SNAPSHOT的。
创建完成后可以在DemoSpbtApplication中运行应用程序,运行之后,可在127.0.0.1:8080进行访问。
运行结果如下图:

(2)应用开发的分层
| 分层 | 职责 | 涉及的标签 |
|---|---|---|
| controller | 主要用于对接http请求,返回http响应。 | @RestController @RequestMapping @RequestParam @RequestBody |
| service | 用于处理业务请求 | @Service @Autowired |
| mapper | 用于处理数据库操作请求 | @Select @Insert |
三层之间的关系

5 各层涉及到的标签
| 标签 | 职责 | 工作层级 |
|---|---|---|
| @RestController | 标识请求的入口类 类上 | controller层 |
| @RequestMapping | 标识请求入口方法 | controller层 |
| @GetMapping | 标识请求入口方法 | controller层 |
| @PostMapping | 标识请求入口方法 | controller层 |
| @RequestParam | 接收请求URL路径中的参数 | controller层 |
| @PathVariable | 接收请求URL路径中的参数 | controller层 |
| @RequestBody | 接收Post请求体中的数据 | controller层 |
| @Autowired | 自动将接口实例化 | controller层、service层 |
| @Service | 标识了业务实现类的组件 | service层 |
| @Select | 标识要执行的select语句 | mapper |
| @Insert | 标识要执行的insert语句 | mapper |
(1)controller层
@RestController 和 @RequestMapping 标识类和方法
-
@RestController:标注的类不是普通的类,是所有程序的入口,其中的每个方法都可能成为入口方法。
-
@RequestMapping:在浏览器地址输入映射内容就能进入到对应方法。
Get 和 Post请求都能接收。
新建com.hzy.spbt.demo.controller.CustomerController类。
编写方法。
package com.hzy.spbt.demo.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController //@Controller
public class CustomerController {@RequestMapping("/hello")public String helloCustomer(String hello){return "hello world";}
}
运行程序,可以在浏览器端输入http://127.0.0.1:8080/hello进行访问这个方法,将hello word 输出到页面上。
多数情况下,不仅需要输入/hello,还要在后面加上对应的参数,有如下三种参数:
- 路径:如 ?x=xxxx&y=xxx&c=xxx,格式为name=value键值对形式。
- 主键:如 /customer/3。
- 请求体中的参数:在浏览器地址中不显示出来,往往这些参数比较复杂,内容特别多,如提交订单,填写用户信息等,一般组成一个JSON串传递给后台。
@RequestParam获取路径
实现功能:将参数通过@RequestParam(“name”)中的name,传递给java中的name,以键值对形式呈现。
@RestController //@Controller
public class CustomerController {@RequestMapping("/hello")public String helloCustomer(@RequestParam("name") String name){return "hello world " + name;}
}
在浏览器输入http://127.0.0.1:8080/hello?name=zhangsan可以进行查看。
@PathVariable获取主键
获取路径中的值。
@RequestMapping("/customer/{id}") //@GetMapping("/customer/{id}")
public String getCustomer(@PathVariable("id") int userId){return "user id " + userId;
}
在浏览器输入http://127.0.0.1:8080/customer/32进行查看。
@RequestBody获取请求体中的参数
创建bean包,定义Customer Bean:com.hzy.spbt.demo.bean.Customer。
@Data注解自动添加get,set方法。
customer bean。
package com.hzy.spbt.demo.bean;import lombok.Data;@Data
public class Customer {String id;String name;int age;
}
@PostMapping("/customer")public String saveCustomer(@RequestBody Customer customer){return "save user information: " + customer.toString();}
可以通过postman或者在浏览器中的插件市场中搜索rest,安装插件,完成测试。

输入地址 http://127.0.0.1:8080/customer/,在body中输入{"id":"101","name":"zhangsan","age":22}。
结果如下图:

在idea中添加断点,可以看到数据已经传到后台。

需要注意,bean中对象的格式要和JSON体中的格式高度一致。
根据bean写json,根据json写bean,两种思路都可以。
(2)service层
@Service
新建接口 com.hzy.spbt.demo.service.CustomerService
package com.hzy.spbt.demo.service;import com.hzy.spbt.demo.bean.Customer;public interface CustomerService {public void saveCustomer(Customer customer);
}
新建实现类 com.hzy.spbt.demo.service.impl.CustomerServiceImpl
@Service:将类注册成为组件,为后面的自动装配做准备。在服务启动时,首先会发现自动装配,然后去整个服务的类中去找@Service的组件,查看哪个@Service匹配这个自动装配。容器会将这个对象new出来,是单态的,即一个容器中只有一个,供所有的方法使用。
这个类会常驻在内存中,成为容器的组件,当哪个Controller需要的时候,自动完成装配,也称依赖注入。可以本地装配(接口和实现类在一台机器),也可以远程装配(接口和实现类在不同机器)。
package com.hzy.spbt.demo.service.impl;import com.hzy.spbt.demo.bean.Customer;
import com.hzy.spbt.demo.service.CustomerService;
import org.springframework.stereotype.Service;@Service
public class CustomerServiceImpl implements CustomerService {@Overridepublic void saveCustomer(Customer customer) {System.out.println("service : saveCustomer:" + customer);}
}
@Autowired
如果在方法内实现CustomerService customerService = new CustomerServiceImpl;,这个实现类是一个服务,作为一个web应用的服务,触发的频率是非常高的,那么每次调用这个服务都会创建对象,耗费大量的内存空间。
将以上语句放在外面,先创建好,之后在各个方法中使用,这样也会带来一个问题,即一种接口可能有多个实现,所以一般会让容器对各个接口进行自动装配(注入),使用@Autowired。
package com.hzy.spbt.demo.controller;import com.hzy.spbt.demo.bean.Customer;
import com.hzy.spbt.demo.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController //@Controller
public class CustomerController {@AutowiredCustomerService customerService;@PostMapping("/customer")public String saveCustomer(@RequestBody Customer customer){customerService.saveCustomer(customer);return "save user information: " + customer.toString();}
}


















