前文如下:
11.【clickhouse】ClickHouse从入门到放弃-概述
12.【clickhouse】ClickHouse从入门到放弃-环境搭建
13.【clickhouse】ClickHouse从入门到放弃-引擎
4.clickhouse实战
基于上一篇的总结,做一个入门的demo,场景基于业务的spm场景:
首先先回顾一下clickhouse的优缺点。
优点:
1,为了高效的使用CPU,数据不仅仅按列存储,同时还按向量进行处理;
2,数据压缩空间大,减少IO;处理单查询高吞吐量每台服务器每秒最多数十亿行;
3,索引非B树结构,不需要满足最左原则;只要过滤条件在索引列中包含即可;即使在使用的数据不在索引中,由于各种并行处理机制ClickHouse全表扫描的速度也很快;
4,写入速度非常快,50-200M/s ,对于大量的数据更新非常适用。
缺点:
1,不支持事务,不支持真正的删除/更新;
2,不支持高并发,官方建议qps为100,可以通过修改配置文件增加连接数,但是在服务器足够好的情况下;
3,SQL满足日常使用80%以上的语法,join写法比较特殊;最新版已支持类似SQL的join,但性能不好;
4,尽量做1000条以上批量的写入,避免逐行insert或小批量的insert,update,delete操作,因为ClickHouse底层会不断的做异步的数据合并,会影响查询性能,这个在做实时数据写入的时候要尽量避开;
5,Clickhouse快是因为采用了并行处理机制,即使一个查询,也会用服务器一半的CPU去执行,所以ClickHouse不能支持高并发的使用场景,默认单查询使用CPU核数为服务器核数的一半,安装时会自动识别服务器核数,可以通过配置文件修改该参数。
全量数据导入:数据导入临时表 -> 导入完成后,将原表改名为tmp1 -> 将临时表改名为正式表 -> 删除原表
增量数据导入:增量数据导入临时表 -> 将原数据除增量外的也导入临时表 -> 导入完成后,将原表改名为tmp1-> 将临时表改成正式表-> 删除原数据表
4.1 应用参考
基于Clickhouse的日志体系
参考文档
cloud.tencent.com/developer/a…
4.2 clickhouse+mybatis plus集成
参考文档
https://blog.csdn.net/douglas8287/article/details/84705750 https://blog.csdn.net/xhaimail/article/details/122084999 https://www.jianshu.com/p/953ba54d434c https://blog.csdn.net/fx9590/article/details/105163804
创建表结构
-- 本地创建SPM表:业务:用户行为分析 CREATE TABLE default.trade_spm ( `id` Int64, `user_id` Int64 COMMENT '用户id 未登录为0', `from_type` String COMMENT '来源:ios, \r\nandroid, \r\napplet', `client_id` String COMMENT '客户端ID', `spm_platfrom` String COMMENT '访问平台固定;10=ios,11=小程序,12=H5 , \r\n13=android', `spm_page` String COMMENT '页面', `spm_model` String COMMENT '模块', `spm_position` String COMMENT '位置', `param_content` Nullable(String) COMMENT '内容(商品ID或专题模板ID或链接)', `ip` String COMMENT 'IP', `ua` String COMMENT 'User-Agent', `create_time` DateTime DEFAULT toDateTime(now(), 'Asia/Shanghai') COMMENT '行为发生时间', `update_time` DateTime DEFAULT toDateTime(now(), 'Asia/Shanghai') COMMENT '修改时间', `project_code` String COMMENT '项目编码', `_sign` Int8 DEFAULT 1, `_version` UInt64 DEFAULT 1 ) ENGINE = ReplacingMergeTree(_version) PARTITION BY intDiv(id, 18446744073709551) ORDER BY tuple(id) SETTINGS index_granularity = 8192
工程配置
环境dubbo+spring boot +mybatisplus +nacos+swagger ui;使用nacos作为服务注册中心和配置中心
bootstrap.yml配置文件,配置nacos配置中心,使用远程配置,配置测试环境mall-log测试环境配置文件 profiles: active为 test.
# nacos配置 server: port: 8090 spring: application: name: mall-log profiles: active: test cloud: config: override-none: true allow-override: true override-system-properties: false nacos: discovery: server-addr: 127.0.0.1:8848 #localhost:8848 #Nacos服务注册中心地址 config: server-addr: 127.0.0.1:8848 #Nacos作为配置中心地址 file-extension: yml #指定yaml格式的配置 group: DEFAULT_GROUP namespace: 803931a7-6d1b-44be-a8ee-8732822722bf #指定配置中心命名空间
登录 127.0.0.1:8848/nacos ,用户名密码nacos
nacos 点击命名空间:看到之前以及创建了一个locolhost本地测试的命名空间,id和bootstrap.yml的 namespace配置一致:
nacos点击配置列表,选择localhost命名空间,导入配置,或者克隆配置,文件名称和bootstrap.yml和application:name: mall-log 加上profiles:active: test一致,profiles:active区分是生产还是测试环境配置
编辑配置如下,文件类型选择yaml
dubbo: application: name: mall-log protocol: name: dubbo port: 20881 registry: address: nacos://127.0.0.1:8848 #使用本地nacos作为服务注册中心 check: false timeout: 5000 scan: base-packages: com.anchu.log.web.service config-center: check: consumer: check: false timeout: 5000 server: port: 8090 spring: cloud: config: override-none: true datasource: driver-class-name: com.clickhouse.jdbc.ClickHouseDriver url: jdbc:clickhouse://192.168.120.110:8123/default userName: default password: clickhouse druid: # 按照自己连接的 clickhouse 数据库来 filters: none #clickhouse不支持wall及监控 #配置初始化大小/最小/最大 initial-size: 5 min-idle: 5 max-active: 20 #获取连接等待超时时间 max-wait: 60000 #间隔多久进行一次检测,检测需要关闭的空闲连接 time-between-eviction-runs-millis: 60000 #一个连接在池中最小生存的时间 min-evictable-idle-time-millis: 30000 validation-query: SELECT 1 #mybatis mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) logging: config: classpath:log4j2.xml
代码编写(部分逻辑,其实实现和操作关系型数据库类似)
启动类,@EnableDiscoveryClient注解,服务发现
/** * @author hh * @date 2022/4/6 16:26 */ @SpringBootApplication(scanBasePackages = {"com.anchu.log"}) @MapperScan(basePackages = {"com.anchu.log.dao"}) @EnableConfigurationProperties @EnableDiscoveryClient @EnableAsync public class WsnbLogApplication { public static void main(String[] args) { SpringApplication.run(WsnbLogApplication.class, args); } }
伪代码如下:
//controller @RestController @RequestMapping("/spm") @Api(tags = "用户端SPM埋点") public class TradeSpmAppController { @Reference private JwtService jwtService; @Autowired private ITradeSpmService iTradeSpmService; @ApiOperation(value = "增加埋点点击事件", httpMethod = "POST") @PostMapping("add") public ResultEntity add(HttpServletRequest request, @RequestBody TradeSpmDto dto){ if(Objects.isNull(dto)){ throw new CustomException(ResultEnum.PARAMS_IS_EMPTY); } String token = request.getHeader(ShiroConstants.AUTHORIZATION); String userAgent = request.getHeader("User-Agent"); // if(!Objects.isNull(token) && !("".equals(token))){ // TradeUserDto userDto =jwtService.parseToken(token); // dto.setUserId(userDto.getUserId()); // } dto.setUserId(1L); dto.setIp(IPUtil.getIp(request)); dto.setUa(Objects.isNull(userAgent) ? "unknow" : userAgent); iTradeSpmService.add(dto); return ResultTemplate.success(); } } //service public interface ITradeSpmService { void add(TradeSpmDto dto); } @Service public class TradeSpmLogServiceImpl extends ServiceImpl<TradeSpmLogMapper, TradeSpmLog> implements ITradeSpmLogService { @Override public void add(TradeSpmLogDto dto) { if(Objects.isNull(dto)){ throw new CustomException(ResultEnum.PARAMS_IS_EMPTY); } TradeSpmLog entity = dto.cloneToDomain(TradeSpmLog.class); //clickhouse 不支持自增主键,雪花算法生成 entity.setId(SnowflakeIdWorker.generateId()); this.baseMapper.insert(entity); } } //Mapper public interface TradeSpmLogMapper extends BaseMapper<TradeSpmLog> { } //dao,dto,entity省略 import lombok.Data; import lombok.EqualsAndHashCode; import java.util.Date; @Data @EqualsAndHashCode(callSuper = false) public class TradeSpm extends BaseObject { private static final long serialVersionUID = 1L; //@TableId(value = "id", type = IdType.AUTO) 不支持自增 private Long id; private Long userId; /** * 来源:ios,android,applet */ private String fromType; /** * 客户端ID */ private String clientId; /** * 访问平台固定;10=APP,11=小程序,12=H5 */ private String spmPlatfrom; /** * 页面 */ private String spmPage; /** * 模块 */ private String spmModel; /** * 位置 */ private String spmPosition; /** * 内容 */ private String paramContent; /** * IP */ private String ip; /** * User-Agent */ private String ua; /** * 创建时间 */ private Date createTime; /** * 修改时间 */ private Date updateTime; }
swagger ui测试:
http://127.0.0.1:8090/doc.html
//请求体 { "clientId": "1", "fromType": "1", "id": 0, "ip": "127.0.0.1", "paramContent": "{"goodId":1}", "spmModel": "10", "spmPage": "11", "spmPlatfrom": "11", "spmPosition": "111", "ua": "microsoft edge", "userId": 1 }
问题
(1)主键不支持自增,可以业务代码自己生成,比如雪花算法id
(2)时区问题
# 确保机器时间正确 时区确保正确,PDT是太平洋夏季时间。 [root@localhost anchu]# date -s 2022-05-18 Wed May 18 00:00:00 PDT 2022 [root@localhost anchu]# date -s 11:14:20 Wed May 18 11:14:20 PDT 2022 [root@localhost anchu]# hwclock --systohc [root@localhost anchu]# date Wed May 18 11:14:48 PDT 2022 [root@localhost anchu]# #修改时区 [root@localhost anchu]# ll /etc/localtime lrwxrwxrwx. 1 root root 41 Apr 22 12:36 /etc/localtime -> ../usr/share/zoneinfo/America/Los_Angeles [root@localhost anchu]# ll /etc/localtime lrwxrwxrwx. 1 root root 41 Apr 22 12:36 /etc/localtime -> ../usr/share/zoneinfo/America/Los_Angeles [root@localhost anchu]# ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime [root@localhost anchu]# ll /etc/localtime lrwxrwxrwx. 1 root root 33 May 19 03:39 /etc/localtime -> /usr/share/zoneinfo/Asia/Shanghai [root@localhost anchu]# date Thu May 19 03:39:22 CST 2022 [root@localhost anchu]# date -s 2022-05-18 Wed May 18 00:00:00 CST 2022 [root@localhost anchu]# date -s 12:48:30 Wed May 18 12:48:30 CST 2022 #修改clickhouse配置文件,并重启 [root@localhost anchu]# vi /etc/clickhouse-server/config.xml <timezone>Asia/Shanghai</timezone> [root@localhost anchu]# clickhouse restart #查看clickhouse时区配置 localhost :) show SETTINGS like '%time_zone%'; SHOW SETTINGS LIKE '%time_zone%' Query id: a31192e8-b011-4117-91ce-2abc86bbbeef ┌─name─────────────────┬─type─┬─value─┐ │ use_client_time_zone │ Bool │ 0 │ └──────────────────────┴──────┴───────┘ localhost :) show SETTINGS like '%time_zone%'; SET use_client_time_zone = 1; localhost :) select toTimeZone(toDateTime(now()), 'Asia/Shanghai'); ┌─toTimeZone(toDateTime(now()), 'Asia/Shanghai')─┐ │ 2022-05-18 12:50:06 │ └────────────────────────────────────────────────┘
(3)性能问题
由于clickhouse对高并发支持不高,其次类似标题5.1说明,避免逐行insert或小批量的insert,update,delete操作,尽量做1000条以上批量的写入,我们可以优化代码,先将spm数据队列缓存起来,后面多并发批处理。
队列及处理相关参考文档: