持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
28.工具使用:ClickHouse从入门到放弃-概述 - 掘金 (juejin.cn)
29.工具使用:ClickHouse从入门到放弃-环境搭建 - 掘金 (juejin.cn)
30.工具使用:ClickHouse从入门到放弃-引擎 - 掘金 (juejin.cn)
基于上一篇的总结,做一个入门的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-> 将临时表改成正式表-> 删除原数据表
5.1 应用参考
基于Clickhouse的日志体系
参考文档
cloud.tencent.com/developer/a…
5.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数据队列缓存起来,后面多并发批处理。
队列及处理相关参考文档: