小T导读:在生鲜超市的高效运营中,实时数据分析至关重要。万象云鼎的“云鲜生”通过智能秤+网关+软件系统的组合,实现了销售数据的精准管理与优化。而在数据处理方面,TDengine 的流计算能力成为了这一方案的核心支撑。本文详细分享了“云鲜生”如何利用 TDengine 高效存储和分析海量销售数据,在优化超市运营、提升用户体验的同时,解决高基数分组、高并发查询等技术挑战。
在生鲜超市的主营业务里,生鲜产品如新鲜的肉类、蔬菜、水果等,大多只有较短的保质期,需要在特定的温度、湿度和环境条件下储存,以保持其新鲜度和品质。稍有不慎便可能导致产品变质甚至腐烂。此外,这类产品受季节和地域因素影响,供应往往不稳定。因此,一个优秀的生鲜超市虽然可以为消费者提供丰富多样的选择,但是其管理难度同样极高。
由我司万象云鼎研发的“云鲜生”——一套基于“智能秤+网关+软件系统”的生鲜行业智慧门店管理方案,正是为了解决这些管理难题而生。我们的智能秤能够实现自助称重、结算以及产品数据可视化,同时支持将所售商品信息、订单数据等实时上传至云端,通过对这些数据的高效管理和实时分析,助力超市优化运营、提升客户体验。
产品定位
我们希望可以为超市经营者解决以下问题:
1.提高超市的用户购物效率,提升 C 端用户的满意度。
2.降低超市人力成本、通过自动化/数字化设备技术,减少收银、用户沟通等由人力产生的误差。
3.提供动态的定价策略:基于实时的销售数据和生鲜商品的保质期,给商家提供充足信息,实施动态定价策略,比如立刻开展促销/折扣活动。
4.支持各种维度的数据分析,包括特定商品、特定客户的全部门店或分店,以及不同摊位类型的实时与历史销售数据。通过这些信息,超市经营者可以清晰了解畅销与滞销商品的分布,并深入分析为何同款产品在相同时间段内,在 A 店热销,而在 B 店却无人问津等运营难题,使决策更加精准高效。

Why TDengine?
TDengine 作为涛思数据的旗舰产品,其核心是一个高性能、分布式的时序数据库。通过集成的缓存、数据订阅、流计算和数据清洗与转换等功能,TDengine 已经发展成为一个专为物联网、工业互联网、金融和 IT 运维等关键行业量身定制的时序大数据平台。
在这一需求背景下,我们发现 TDengine 产品定位与之高度契合,因此开始了对 TDengine 的深入探索与应用。
我们的产品是一款基于自建服务器的 SaaS 服务,通过 Web/App 提供功能。在产品诸多特性中,最重要的一个核心特性就是实时获取商品当日的销售量/销售额状况。经过深入分析,我们发现 TDengine 的流计算是实现这一需求最合适的选择。
流计算的应用
首先,我们以“一台智能秤一张表”建模方式存储全部的销售记录,如下: billpay 超级表以支付方式的维度进行记录,billsale 表以商品的维度进行记录。
数据经由每家超市部署的网关统一写入到 TDengine 当中。


接下来,基于 billsale 表,我们创建了一个名为 stream_billsale_day 的流计算任务,计算结果将存入另一个库中的 billsale_day 超级表中:
create stream stream_billsale_day
trigger at_once
IGNORE EXPIRED 0
fill_history 1
into possum.billsale_day
TAGS (tenantid int,shopid int,userid int,stalltype int,productid bigint,subproductid bigint)
SUBTABLE(CONCAT('saleday_', tbname))
as
select _wstart as saledate,
sum(cast(case when IsReturn=0 then qty else 0 end as bigint)) as saleqty,
sum(cast(case when IsReturn=0 then money else 0 end as bigint)) as salemoney,
sum(cast(case when IsReturn=1 then 0-qty else 0 end as bigint)) as refundqty,
sum(cast(case when IsReturn=1 then 0-money else 0 end as bigint)) as refundmoney,
max(price) as maxprice,min(price) as minprice,last(saletime) as lastsaletime,first(saletime) as firstsaletime from billsale
PARTITION BY tbname,tenantid,userid,shopid,stalltype,productid,subproductid
interval (1d);
该流计算逻辑如下,通过对以下标签:tbname(智能秤设备名),tenantid(租户ID)、shopid(商店ID)、操作人员编号(userid)、stalltype(摊位类型)、productid(产品ID)、subproductid(子产品ID)执行分组之后,再按照一天的时间窗口来统计如下字段:
- saleqty:日销售量,计算方式为将 IsReturn 为 0 的“销售量”字段求和。
- salemoney:日销售额,计算方式为将 IsReturn 为 0 的“销售额”字段求和。
- refundqty:日退货数量,计算方式为将 IsReturn 为 1 的“销售量”取负值后求和。
- refundmoney:日退货金额,计算方式为将 IsReturn 为 1 的“销售额”取负值后求和。
- maxprice:日最高价格。
- minprice:日最低价格。
- saledate:日窗口开始时间(_wstart)。
- firstsaletime:日该商品第一次销售时间。
- lastsaletime:日该商品最后一次销售时间。
在我们的实际应用中,由于商品种类繁多,商家众多 (覆盖 260+ 客户、700+ 超市),因此衍生出的标签也极为丰富。在对这些多字段全部进行分组之后,产生了典型的高基数问题,一共创建了 2600 万的子表。只有这样的分组,才能满足我们的全部分析需求,也就是说:灵活的背后是全量的覆盖。
为了结合实时数据进行综合分析,我们在创建流计算语句时也指定了 fill_history 1 参数,对如下 5 年的历史数据(共计 24 亿条)进行计算,以确保分析的完整性和连续性。

尽管我们能想象到数据库的压力非常大,但仍希望尝试一下 TDengine 的流计算,验证其是否能够满足我们的业务需求。
在 TDengine 官方团队的支持下,整个调试过程中,我们做了多项优化:
- 和业务侧多方多次沟通讨论,在分组过程中,精简不需要的标签。
- 把流计算产生的数据,写入到另一个库中,这样就可以针对性的配置建库参数。由于我们的数据每天只有一条,我们将 duration 参数调整为 1825天,使得即便存储 5 年的全量数据,也可以只用一个数据文件承载,从而大幅优化存储效率。

- 大胆放开 VGROUPS 增加硬件增加并行度(实际值 400)。
由于我们对所有历史数据都进行了计算,因此我们的用户在任何时间都可以结合历史数据进行查询分析。
应用场景一:
查询 77 号客户全部超市 2 年整的销售量、销售额、退货量、退货额,耗时 5 秒左右。


应用场景二:
查询 77 号客户在 9 个月内,按照分店ID、操作员ID 和产品ID 进行分组后的,总销售量、总销售额、总退货量、总退货额,除了用作常规的业务分析之外,还可以计算操作员的业绩提成。


总结
以上就是我们在使用 TDengine 过程中的一些心得与经验。作为一款出色的国产时序数据库,TDengine 在实际应用中真正地为我们智能生鲜行业提供了巨大帮助。遂整理成文,希望 TDengine 发展得越来越好,早日成为时序数据领域的 Oracle。