【用户画像】功能实现值写入ClickHouse人群包、预估和更新分群人数,NoSQL数据库介绍

一 写入ClickHouse人群包

1 组合查询Bitmap表SQL代码实现

第一步插入数据工作已经完成,现进行第二步,需要根据筛选条件进行计算,具体工作如下图:

在这里插入图片描述

(1)SQL语句分析

select
bitmapToArray(
bitmapAnd( 
  bitmapAnd(
    (select groupBitmapMergeState(us) from user_tag_value_string where tag_code='TG_BASE_PERSONA_GENDER' and tag_value='男' and dt='2022-10-16')
      ,
    (select groupBitmapMergeState(us) from user_tag_value_string where tag_code='TG_BASE_PERSONA_AGEGROUP' and tag_value in ('90后','80后','70后') and dt='2022-10-16')
  ),
  (select groupBitmapMergeState(us) from user_tag_value_long where tag_code='TG_TG_BEHAVIOR_ORDER_LAST30d_CT' and tag_value>=1 and dt='2022-10-16')
)
    )

在页面端,创建分群中想要选择条件,点击保存就能将大概人数估计出来,需要将以上SQL拼接出来,传递进去,然后执行。

其中子查询中的变量为

  • 表名
    • 需要知道标签的值类型:去找标签编码,然后去查询标签的定义,最后得到标签的值类型 TagCondition.tag_code。
  • 标签编码 TagCondition.tag_code。
  • 标签值
    • 值 TagCondition.tag_values。
    • 是否存在单引号 TagCondition.tag_code。
    • 是否有多值,( , , , ) TagCondition.tag_values.size。
  • 操作符(=,<)
    • 把英文转为操作符,eq ==》 =,it ==》 < TagCondition.operator。
  • 业务日期
    • 业务日期从页面提取 没有现成的,需要从外部传进来。

完整SQL查询的拼接为bitmapAnd( bitmapAnd( bitmapAnd(sql1,sql2),sql3),sql4)

(2)实现思路

将所有的标签定义全部查找出来,然后放入到堆内存(变量)map中,如果是List则需要根据tag_code依次去List中进行遍历,时间复杂度为O(N)。map中的K存储tag_code,V存储tag_info。

(3)实现过程

controller层

增加方法

/**
 * 生成分群列表
 * @param userGroup
 * @return
 */
@PostMapping("/user-group")
public String genUserGroup(@RequestBody UserGroup userGroup){
    userGroupService.genUserGroup(userGroup);
    return "success";
}
service层

在接口中增加方法

public interface UserGroupService  extends IService<UserGroup> {

    public void genUserGroup(UserGroup userGroup);
}

在实现类中编写

@Service
@Slf4j
@DS("mysql")
public class UserGroupServiceImpl extends ServiceImpl<UserGroupMapper, UserGroup> implements UserGroupService {

    // 以获取全部tagInfo标签的map
    @Autowired
    TagInfoService tagInfoService;

    @Override
    public void genUserGroup(UserGroup userGroup){

        // 1 写入mysql人群的基本定义
        // (1)补充condition_json_str空白字段
        List<TagCondition> tagConditions = userGroup.getTagConditions();
        // 转成json串
        String conditionJson = JSON.toJSONString(tagConditions);
        // 再放回数据库中
        userGroup.setConditionJsonStr(conditionJson);
        // (2)补充condition_commen空白字段,将json串转成一个json说明
        userGroup.setConditionComment(userGroup.conditionJsonToComment());
        // (3)补充create_time空白字段
        userGroup.setCreateTime(new Date());
        // (4)user_group_num和update_time先不处理
        // 调用父类方法
        super.saveOrUpdate(userGroup);
        // 2 写入ClickHouse人群包
        // 全部条件都存储在列表中,一个条件对应list中的一个
        // 所以想生成一个子查询,依据就是集合中的一个condition
        // 获取全部tagInfo标签的map,在TaskInfoServiceImpl中实现
        // K为TagCode,V为TagInfo
        // (1)组合查询Bitmap表完整SQL
        Map<String, TagInfo> tagInfoMapWithCode = tagInfoService.getTagInfoMapWithCode();
        String bitAndSQL = getBitAndSQL(userGroup.getTagConditions(),tagInfoMapWithCode,userGroup.getBusiDate());
        System.out.println(bitAndSQL);

        // 3 人群包(包含所有uid)以应对高QPS访问
        // redis(bitmap/set)
    }

    // 获取完整bitAndSQL语句
    public String getBitAndSQL(List<TagCondition> tagConditionList,Map<String,TagInfo> tagInfoMap,String busiDate){
        String sql="";
        for (TagCondition tagCondition : tagConditionList) {
            String conditionSQL = getConditionSQL(tagCondition,tagInfoMap,busiDate);
            if(sql.length() == 0){
                sql = conditionSQL;
            }else{
                sql = " bitmapAnd(" + sql + "," + conditionSQL + ")";
            }
        }
        return "select " + sql;
    }

    // 2.1 对应2 获取单个condition,生成子查询
    public String getConditionSQL(TagCondition tagCondition,Map<String,TagInfo> tagInfoMap,String busiDate){
        // (1) 获取K,根据K找V
        TagInfo tagInfo = tagInfoMap.get(tagCondition.getTagCode());
        String tagValueType = tagInfo.getTagValueType();
        // (2) 获取表名
        String tableName = null;
        if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_STRING)){
            tableName = "user_tag_value_string";
        } else if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_LONG)){
            tableName = "user_tag_value_long";
        } else if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_DECIMAL)){
            tableName = "user_tag_value_decimal";
        } else if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_DATE)){
            tableName = "user_tag_value_date";
        }
        // (3)获取值类型
        String tagValueSQL = getTagValueSQL(tagCondition.getTagValues(), tagValueType);
        // (4)将操作符转换
        String operatorSQL = getConditionOperator(tagCondition.getOperator());
        // (5)拼接SQL
        String conditionSQL = "(select groupBitmapMergeState(us) from " + tableName +
                " where tag_code='" + tagCondition.getTagCode() +
                "' and tag_value " + operatorSQL + " " + tagValueSQL + " " +
                "and dt = '" + busiDate + "')";

        return conditionSQL;
    }

    // 2.2 对应2.1 中(3)
    // 获取SQL中的值部分
    public String getTagValueSQL(List<String> valueList,String tagValueType){
        // 判断是否要单引,是否为多值
        // ('90后','80后','70后')
        boolean needQuote = needStringQuote(tagValueType);
        String tagValueSQL = null;
        if(needQuote){
            tagValueSQL =  " '" + StringUtils.join(valueList,"','") + "' ";
        }else{
            tagValueSQL =  StringUtils.join(valueList,",");
        }
        if(valueList.size() > 1){
            tagValueSQL = "(" + tagValueSQL + ")";
        }
        return tagValueSQL;
    }

    // 2.3 对应2.2
    // 判断是否需要加单引
    public boolean needStringQuote(String tagValueType){
        if(tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_LONG) || tagValueType.equals(ConstCodes.TAG_VALUE_TYPE_DECIMAL)){
            return false;
        } else {
            return true;
        }
    }

    // 对应2.1中(4) 把中文的操作代码转换为判断符号
    private  String getConditionOperator(String operator){
        switch (operator){
            case "eq":
                return "=";
            case "lte":
                return "<=";
            case "gte":
                return ">=";
            case "lt":
                return "<";
            case "gt":
                return ">";
            case "neq":
                return "<>";
            case "in":
                return "in";
            case "nin":
                return "not in";
        }
        throw  new RuntimeException("操作符不正确");
    }
}
Taginfo实现类

使用下面方法得到service中的map

public Map<String,TagInfo> getTagInfoMapWithCode(){
    // 获取全部list
    List<TagInfo> tagInfoList =  super.list();
    Map<String,TagInfo> tagInfoMap=new HashMap<>();
    // 用循环将list转成map
    for (TagInfo tagInfo : tagInfoList) {
        tagInfoMap.put(tagInfo.getTagCode(),tagInfo);
    }
    return tagInfoMap;
}
mapper层
@DS("mysql")
@Mapper
public interface UserGroupMapper extends BaseMapper<UserGroup> {
}

2 人群包代码实现

目的是查出此分群包中包含什么人,人群包的唯一标识是分群ID,所以人群包建表需要内容如下:

  • 包含字段
    • 分群id
    • 分群uid集合,可以直接存储为bitmap
  • 引擎:AggregatingMergeTree
  • 分区:使用分群id进行分区,这样一个分群一个分区,目的是当某个分群的集合发生了变化,造成分区的重建时,不会影响其他的分群
  • 排序:分群id

建表语句如下:

CREATE TABLE  user_group
(
    `user_group_id` String,
    `us` AggregateFunction(groupBitmap, UInt64)
)
ENGINE = AggregatingMergeTree
PARTITION BY user_group_id
ORDER BY user_group_id;

(1)配置文件

spring.datasource.dynamic.datasource.clickhouse.url=jdbc:clickhouse://hadoop101:8123/user_profile1009
spring.datasource.dynamic.datasource.clickhouse.driver-class-name=ru.yandex.clickhouse.ClickHouseDriver

(2)UserGroupMapper

@Insert("${sql}")
@DS("clickhouse")
public void insertSQL(String insertBitSQL);

@Select("select bitmapCardinality(us) ct from user_group where user_group_id=#{userGroupId}")
@DS("clickhouse")
public Long userGroupCount(@Param("userGroupId") String userGroupId);

(3)UserGroupServiceImpl实现类

添加方法

// 从已有人群包中,查询人群个数 对应2 (4) a)
public Long getUserGroupCount(String userGroupId){
    Long userGroupCount = super.baseMapper.userGroupCount(userGroupId);
    return userGroupCount;
}

// 获得插入的SQL语句 对应2 (3)
public  String getInsertBitSQL(String userGroupId,String bitAndSQL){
    String insertSQL="insert into user_group  select '"+userGroupId+"' ,"+bitAndSQL+" as  us";
    return insertSQL;
}

进行调用

//(3)insert into  + 查SQL
String insertBitSQL = getInsertBitSQL(userGroup.getId().toString(), bitAndSQL);

// 执行语句
// 父类已经装配好了mapper 直接使用即可
super.baseMapper.insertSQL(insertBitSQL);

// (4)更新人群包的人数
// a)从已有人群包中,查询人群个数
Long userGroupCount = getUserGroupCount(userGroup.getId().toString());

// b)更新MySQL分群基本信息中的人数,使用mabatis-plus方法
userGroup.setUserGroupNum(userGroupCount);
super.saveOrUpdate(userGroup);

二 预估分群人数

预估分群人群思路如下图:

在这里插入图片描述

三 更新分群人数

更新分群人数思路如下图:

在这里插入图片描述

四 NoSQL数据库

1 NoSQL数据库简介

(1)概述

NoSQL(NoSQL = Not Only SQL ),意即 ”不仅仅是SQL“,泛指非关系型的数据库

NoSQL 不拘泥于关系型数据库的设计范式,放弃了通用的技术标准,为某一领域特定场景而设计,从而使性能、容量或者扩展性都打到了一定程度的突破。

  • 不遵循SQL标准。

    是一个巨大的牺牲,学习成本,人力成本很高,所有的语法都需要学习。

  • 不支持ACID(事务)。

  • 远超于SQL的性能。

(2)NoSQL适用场景

性能快,容量大,扩展性高。

(3)NoSQL不适用场景

需要事务支持、基于sql的结构化查询存储,处理复杂的关系,需要即席查询、用不着sql的和用了sql也不行的情况,请考虑用NoSql。

2 NoSQL家族

(1)Memcache(内存数据库)

很早出现的NoSql数据库。

数据都在内存中,一般不持久化。

持久化:即备份写磁盘。

支持简单的key-value模式,支持类型单一。

KV模式中的K一定是String,类型单一指的是V,Memcache只有String,即一个String对应一个String,一个K只能存储一个值。

不过通过一些转换操作,一个K也可以存储多个值,如将集合变为字符串(JSON)。

一般是作为缓存数据库辅助持久化的数据库。

(2)Redis(内存数据库)

几乎覆盖了Memcached的绝大部分功能。

数据都在内存中,支持持久化,主要用作备份恢复。

MySQL和Hbase都支持写磁盘且使用内存,那么Redis和这两者的区别是什么?

当内存和磁盘中的数据一致时,分不清谁是内存数据库谁是磁盘数据库,当内存和磁盘中的数据不一致时,磁盘数据库以磁盘中的数据为主,这种数据库称为标准的持久化数据库,而内存数据库则以内存中的数据为主,即使是内存数据少,磁盘数据多的情况,如刚刚执行删除操作,删除操作还没有刷到磁盘,磁盘多是作为备份恢复的作用,只有当内存不能使用时,如宕机,才会使用磁盘。

内存数据库中内存的数据是最准确的,任何数据的访问请求都不会第一时间去查磁盘或者写磁盘,会进行周期型的备份。

如果在内存中找不到相应的数据,MySQL和Hbase会查磁盘,而Redis不会去查磁盘,直接返回异常信息。

除了支持简单的key-value模式,还支持多种数据结构的存储,比如 list、set、hash、zset等。

一个K可以存储多个值。

一般是作为缓存数据库辅助持久化的数据库。

一般都是将数据存放到MySQL中,使用Redis作为缓存,其中新浪微博将所有的数据全部存放到Redis中,读写性能大大提高,代价就是成本较高。

(3)Mongodb(文档数据库)

对大数据领域来说,此数据库大多作为一个数据源。

存储的数据结构一般为K-JSON结构,存储结构相对简单,三范式的数据就相对复杂了,表会非常多,Mongodb中的表很少去和其他表进行关联,数据量非常大,不强调关系,可能存储用户与客户的聊天记录、社交中的问答,评论等。

速度介于MySQL和Redis之间。

高性能、开源、模式自由(schema free)的文档型数据库。

数据都在内存中, 如果内存不足,把不常用的数据保存到硬盘。

虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能。

支持二进制数据及大型对象。

可以根据数据的特点替代RDBMS ,成为独立的数据库。或者配合RDBMS,存储特定的数据。

(4)Hbase(列式数据库)

HBase是Hadoop项目中的数据库。它用于需要对大量的数据进行随机、实时的读写操作的场景中。

HBase的目标就是处理数据量非常庞大的表,可以用普通的计算机处理超过10****亿行数据,还可处理有数百万**列元素的数据表。

(5)Cassandra(列式数据库)

Apache Cassandra是一款免费的开源NoSQL数据库,其设计目的在于管理由大量商用服务器构建起来的庞大集群上的海量数据集(数据量通常达到PB级别)。在众多显著特性当中,Cassandra最为卓越的长处是对写入及读取操作进行规模调整,而且其不强调主集群的设计思路能够以相对直观的方式简化各集群的创建与扩展流程。

3 DB-Engines数据库排名

国外数据库排行榜

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
用户画像作为大数据的根基,它抽象出一个用户的信息全貌,为进一步精准、快速地分析用户行为习惯、消费习惯等重要信息,提供了足够的数据基础,奠定了大数据时代的基石。 用户画像,即用户信息标签化,就是企业通过收集与分析消费者社会属性、生活习惯、消费行为等主要信息的数据之后,抽象出一个用户的商业全貌作是企业应用大数据技术的基本方式。用户画像为企业提供了足够的信息基础,能够帮助企业快速找到精准用户群体以及用户需求等更为广泛的反馈信息。 用户画像系统能很好地帮助企业分析用户的行为与消费习惯,可以预测商品的发展的趋势,提高产品质量,同时提高用户满意度。构建一个用户画像括数据源端数据收集、数据预处理、行为建模、构建用户画像。有些标签是可以直接获取到的,有些标签需要通过数据挖掘分析到!本套课程会带着你一步一步的实现用户画像案例,掌握了本套课程内容,可以让你感受到Flink+ClickHouse技术架构的强大和大数据应用的广泛性。 在这个数据爆发的时代,像大型电商的数据量达到百亿级别,我们往往无法对海量的明细数据做进一步层次的预聚合,大量的业务数据都是好几亿数据关联,并且我们需要聚合结果能在秒级返回。 括我们的画像数据,也是有这方便的需求,那怎么才能达到秒级返回呢?ClickHouse正好满足我们的需求,它是非常的强大的。 本课程采用Flink+ClickHouse技术架构实现我们的画像系统,通过学习完本课程可以节省你摸索的时间,节省企业成本,提高企业开发效率。希望本课程对一些企业开发人员和对新技术栈有兴趣的伙伴有所帮助,如对我录制的教程内容有建议请及时交流。项目中采用到的算法含Logistic Regression、Kmeans、TF-IDF等,Flink暂时支持的算法比较少,对于以上算法,本课程将带大家用Flink实现,并且结合真实场景,学完即用。系统含所有终端的数据(移动端、PC端、小程序端),支持亿级数据量的分析和查询,并且是实时和近实时的对用户进行画像计算。本课程含的画像指标含:概况趋势,基础属性,行为特征,兴趣爱好,风险特征,消费特征,营销敏感度,用户标签信息,用户群里,商品关键字等几大指标模块,每个指标都会带大家实现。课程所涵盖的知识点括:开发工具为:IDEA FlinkClickhouseHadoopHbaseKafkaCanalbinlogSpringBootSpringCloudHDFSVue.jsNode.jsElemntUIEcharts等等 课程亮点: 1.企业级实战、真实工业界产品 2.ClickHouse高性能列式存储数据库 3.提供原始日志数据进行效果检测 4.Flink join企业级实战演练 5.第四代计算引擎Flink+ClickHouse技术架构6.微服务架构技术SpringBoot+SpringCloud技术架构7.算法处理含Logistic Regression、Kmeans、TF-IDF等8.数据库实时同步落地方案实操9.统计终端的数据(移动端、PC端、小程序端) 10.支撑亿级海量数据的用户画像平台11.实时和近实时的对用户进行画像计算12.后端+大数据技术栈+前端可视化13.提供技术落地指导支持 14.课程凝聚讲师多年实战经验,经验直接复制15.掌握全部内容能独立进行大数据用户平台的设计和实操企业一线架构师讲授,代码在老师的指导下企业可以复用,提供企业解决方案。  版权归作者所有,盗版将进行法律维权。 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OneTenTwo76

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值