Home Blog

  1. DBA
  2. DBA
  3. 【Redis】

DBA : 乐信Redis开发规范

Created by  lanceli(李志财), last modified on 四月 08, 2021

简介: 本文介绍了在使用乐信Redis的开发规范,从键值设计、命令使用、客户端使用、相关工具等方面进行说明,

通过本文的介绍可以减少使用Redis过程带来的问题。

乐信Redis规范

简介

Redis 是基于单线程模型实现的,也就是 Redis 是使用一个线程来处理所有的客户端请求的,尽管 Redis 使用了非阻塞式 IO,并且对各种命令都做了优化(大部分命令操作时间复杂度都是 O(1)),但由于 Redis 是单线程执行的特点,因此它对性能的要求更加苛刻,再快的系统,也经不住疯狂的滥用,本文介绍了在使用乐信Redis的开发规范,从键值设计、命令使用、优化建议、运营平台等方面进行说明,通过本文的介绍可以减少使用Redis过程带来的问题。

一、键值设计

1. key名设计

(1)【建议】: 可读性和可管理性

命令规范:以英文冒号分隔key,前缀概念的范围的返回从大到小,从不变到可变,从变化幅度小到变化幅度大。业务名:key用途:变量

例如:yoga:user:1,表示 yoga:user:{userID},即瑜伽子系统ID=1的用户信息

(2)【建议】:简洁性

保证语义的前提下,控制key的长度,不超64个字符。

当key较多时,内存占用也不容忽视,例如:

user:friends:messages:{mid} 简化为 u:fr:msg:{mid}

(3)【强制】:不要包含特殊字符

反例:包含空格、换行、单双引号以及其他转义字符

Redis 的 Key 一定要规范,这样在遇到问题时,能够进行方便的定位。Redis 属于无 scheme 的 KV 数据库,所以,我们靠约定来建立其 scheme 语义。其好处:

1、能够根据某类 key 进行数据清理

2、能够根据某类 key 进行数据更新

3、能够方面了解到某类 key 的归属方和应用场景

4、为统一化、平台化做准备,减少技术变更

2. value设计

(1)【强制】:拒绝bigkey

字符串类型:它的big体现在单个value值很大,一般认为超过10KB就是bigkey

非字符串类型:哈希、列表、集合、有序集合,它们的big体现在元素个数太多,元素个数不要超过5000

bigkey的危害

1、Redis集群的内存空间不均匀 由于Redis单线程的特性

2、操作bigkey的通常比较耗时,也就意味着阻塞Redis可能性越大

3、bigkey也就意味着每次获取要产生的网络流量较大,容易打满带宽

4、过期删除的时候会阻塞Redis

以下是压测结果,可直观看出bigkey对性能的影响:

(2)【推荐】:选择适合的数据类型

例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)

反例:

set user:1:name tom
set user:1:age 19
set user:1:favor football

正例:

hmset user:1 name tom age 19 favor football

3.【强制】:控制key的生命周期,redis不是垃圾桶。

如果应用将Redis定位为缓存Cache使用,对于存放的Key一定要设置超时时间!因为若不设置,这些Key会一直占用内存不释放,造成极大的浪费,而且随着时间的推移会导致内存占用越来越大,直到达到服务器内存上限!另外Key的超时长短要根据业务综合评估,而不是越长越好!(某些业务要求key长期有效。可以在每次写入时,都设置超时时间,让超时时间顺延。)

二、Redis命令使用规范

1、【强制】严禁不设置范围的批量操作

  • 例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值,有遍历的需求可以使用hscan、sscan、zscan代替。
  • zrange、 zrangebyscore等多个操作 zset 的函数,严禁使用 zrange myzset 0 -1 等这种不设置范围的操作。请指定范围,如 zrange myzset 0 100,如不确定长度,可使用 zcard 判断长度。
  • hgetall会取出相关 hash 的所有数据,如果数据条数过大,同样会引起阻塞,请确保业务可控。如不确定长度,可使用 hlen 先判断长度。
  • 严禁使用 sunion, sinter, sdiff等一些聚合操作。

2、【强制】禁用 select 函数

select函数用来切换 database,对于使用方来说,这是很容易发生问题的地方,cluster 模式也不支持多个 database,且没有任何收益,dba已通过配置禁用。

3、【强制】禁用命令

禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。dba已通过配置禁用这些命令。

4、【强制】不推荐使用事务

Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上。

三、使用优化建议

1、缩短键值对的存储长度

键值对的长度是和性能成反比的,比如我们来做一组写入数据的性能测试,执行结果如下:

从以上数据可以看出,在 key 不变的情况下,value 值越大操作效率越慢,因为 Redis 对于同一种数据类型会使用不同的内部编码进行存储,比如字符串的内部编码就有三种:int(整数编码)、raw(优化内存分配的字符串编码)、embstr(动态字符串编码),这是因为 Redis 的作者是想通过不同编码实现效率和空间的平衡,然而数据量越大使用的内部编码就越复杂,而越是复杂的内部编码存储的性能就越低。

2、冷热数据分离

不要将所有数据全部都放到Redis中,建议根据业务只将高频热数据存储到Redis中【QPS大于5000】,重要性级别较低的但却需求容量较大的场景可申请完全兼容Redis操作的Pika,对于低频冷数据可以使用MySQL/ElasticSearch等基于磁盘的存储方式。

3、业务数据分离

不要将不相关的数据业务都放到一个 Redis中。一方面避免业务相互影响,另一方面避免单实例膨胀,并能在故障时降低影响面,快速恢复。

4、大文本数据要压缩

对于大文本【超过500 byte】写入到Redis时,一定要压缩后存储!大文本数据存入Redis,除了带来极大的内存占用外,在访问量高时,很容易就会将网卡流量占满,进而造成整个服务器上的所有服务不可用,并引发雪崩效应,造成各个系统瘫痪。

5、使用连接池

使用带有连接池的客户端,可以有效控制连接,同时提高效率

6、缓存 Key 设置失效时间的建议

如果在大型系统中有大量缓存在同一时间同时过期,那么会导致 Redis 循环多次持续扫描删除过期字典,直到过期字典中过期键值被删除的比较稀疏为止,而在整个执行过程会导致 Redis 的读写出现明显的卡顿,卡顿的另一种原因是内存管理器需要频繁回收内存页,因此也会消耗一定的 CPU。

为了避免这种卡顿现象的产生,我们需要预防大量的缓存在同一时刻一起过期,就简单的解决方案就是在过期时间的基础上添加一个指定范围的随机数。

7、限制Redis 分片大小

乐信标准,Redis都有分片,最少一个分片,单分片的大小是4G ,为何不建议一直往上加内存?

  • 单台服务器内存资源有限,不利于扩展
  • 单个分片内存过大,服务器出故障时,影响面大
  • Redis持久化时,虽然操作是异步化,但会有fork进程的操作,这一步是由主进程来完成的,分片内存越大,页表就越大,fork执行时间就越长,就会给主线程带来阻塞风险
  • 不利于平台标准化,后期迁移困难
  • Redis是单进程模型,只能利用一个CPU核心,多分片有利于提高Redis集群能力,充分利用多核性能。

8、认真对待最大内存淘汰策略

根据自身业务类型选择好最大内存淘汰策略,可保证有用数据不被删除,也可避免Redis实例出现OOM,让Redis持续高效。

  • noeviction:不淘汰任何数据,当内存不足时,新增操作会报错,Redis 默认内存淘汰策略
  • allkeys-lru:淘汰整个键值中最久未使用的键值
  • allkeys-random:随机淘汰任意键值
  • volatile-lru:淘汰所有设置了过期时间的键值中最久未使用的键值(乐信默认)
  • volatile-random:随机淘汰设置了过期时间的任意键值
  • volatile-ttl:优先淘汰更早过期的键值

在 Redis 4.0 版本中又新增了 2 种淘汰策略:

  • volatile-lfu:淘汰所有设置了过期时间的键值中,最少使用的键值
  • allkeys-lfu:淘汰整个键值中最少使用的键值

四、运营平台

了解Redis这么多使用规范和优化建议,日常中如何去查看自己应用中使用的Redis实例是否合理呢,通过宙斯Redis健康分析系统(http://zeus.oa.fenqile.com/Redis/redis/show_redis/)即可快速查看

主要功能如下:

1、查找内存占用最高的key

2、查找bigkey

3、查找未设置过期时间的key

Document generated by Confluence on 四月 07, 2023 17:43

补充:Redis的bigKey的危害

Redis中的bigKey(大键值)指的是那些包含大量元素或占用大量内存空间的键。以下是bigKey可能带来的几个显著的危害:

  1. 内存瓶颈
    • 单个bigKey可能占用大量内存,导致Redis实例内存迅速增长,接近或超过最大内存限制(maxmemory)设置,如果没有适当的内存淘汰策略,可能导致其他重要数据无法缓存,甚至发生内存溢出(OOM),从而致使Redis服务不稳定或崩溃。
  2. 性能瓶颈
    • Redis基于单线程模型运行,对bigKey的操作(如读取、修改或删除)会消耗较长时间,这可能导致Redis服务器暂时停止处理其他客户端请求,因为Redis必须先完成当前操作才能继续处理队列中的下一个命令。这种情况下,bigKey操作很容易引起阻塞,增加客户端请求的延迟,产生所谓的“慢查询”。
  3. 阻塞和客户端超时
    • 处理bigKey时,Redis可能长时间处于阻塞状态,客户端在此期间可能因等待响应超时而终止连接,严重影响服务可用性。
  4. 网络拥塞
    • bigKey的传输可能会导致网络带宽占用过大。例如,当bigKey的内容被客户端频繁地获取时,网络流量剧增,特别是在低带宽环境下,可能导致网络拥堵,降低整体系统的性能。
  5. 集群不平衡
    • 在Redis集群环境中,bigKey可能导致集群中某个节点的内存使用率远高于其他节点,破坏数据分片的内存资源均衡,进一步影响整个集群的稳定性与性能。
  6. 扩容困难
    • 当需要迁移bigKey时,由于其大小和复杂性,迁移操作可能会非常耗时,给在线扩容或数据迁移带来挑战。

因此,针对bigKey问题,通常建议采取以下措施:

  • 设计合理的数据结构和编码方式,避免单个键存储过多数据。
  • 定期检查和监控Redis中的大键值,及时优化或合理拆分bigKey。
  • 使用合适的内存淘汰策略,防止bigKey影响其他重要数据的缓存。
  • 对于删除操作,尽量使用异步删除命令(如unlink)减少阻塞时间。
  • 在集群环境中,合理分配数据,避免bigKey集中在单个节点上。

起因:有一个功能,需要调用pop-order的PRC接口,我在Dubbo中进行了声明

<!-- pop订单 -->
<dubbo:reference id="popOrderService"
             interface="com.fenqile.pop.order.service.PopOrderService"
             timeout="3000" protocol="fsof" group="default" version="1.0.0" check="false"/>

一般的接口都是这样声明就可以了

出参定义如下:

@Setter
@Getter
@NoArgsConstructor
@ToString
public class PopOrderDetailResp {

    private Integer uid;
    @JSONField(name = "order_id")
    private String orderId;
    @JSONField(name = "create_time")
    private String createTime;
    @JSONField(name = "modify_time")
    private Date modifyTime;
    @JSONField(name = "merch_sale_state")
    private Integer merchSaleState;
    @JSONField(name = "merch_sale_state_desc")
    private String merchSaleStateDesc;
    @JSONField(name = "sale_type")
    private Integer saleType;
    @JSONField(name = "sale_type_desc")
    private String saleTypeDesc;
    @JSONField(name = "sku_id")
    private String skuId;
    @JSONField(name = "product_name")
    private String productName;
    @JSONField(name = "sku_key_1")
    private String skuKey1;
    @JSONField(name = "sku_key_2")
    private String skuKey2;
    @JSONField(name = "sku_key_3")
    private String skuKey3;
    @JSONField(name = "delivery_mode")
    private Integer deliveryMode;
    @JSONField(name = "delivery_mode_desc")
    private String deliveryModeDesc;
    @JSONField(name = "spec")
    private String spec;
    @JSONField(name = "pic")
    private String pic;
    @JSONField(name = "quantity")
    private Integer quantity = 1;
    @JSONField(name = "amount")
    private String amount;
    @JSONField(name = "used_discount")
    private Integer usedDiscount;
    @JSONField(name = "price")
    private String price;
    @JSONField(name = "discount_info")
    private List<String> discountInfo;
    @JSONField(name = "memo_flag")
    private Integer memoFlag;
    @JSONField(name = "channel_desc")
    private String channelDesc;
    @JSONField(name = "company_name")
    private String companyName = "";
    @JSONField(name = "express_id")
    private String expressId = "";
    @JSONField(name = "express_flag")
    private Integer expressFlag = 0;
    @JSONField(name = "invoice_id")
    private String invoiceId = "";
    @JSONField(name = "receipt_mobile")
    private String receiptMobile = "";
    @JSONField(name = "receiver_credit_id")
    private String receiverCreditId = "";
    @JSONField(name = "group_flag")
    private Integer groupFlag = 0;
    @JSONField(name = "apply_flag")
    private Integer applyFlag = 0;
    @JSONField(name = "pre_sell_flag")
    private Integer preSellFlag = 0;
    @JSONField(name = "pre_sell_desc")
    private String preSellDesc;
    @JSONField(name = "pre_sell_amount")
    private String preSellAmount;
    @JSONField(name = "pre_sell_state_desc")
    private String preSellStateDesc;
    @JSONField(name = "final_pay_desc")
    private String finalPayDesc;
    @JSONField(name = "final_pay_amount")
    private String finalPayAmount;
    @JSONField(name = "final_pay_state_desc")
    private String finalPayStateDesc;
    @JSONField(name = "imei")
    private String imei;
    @JSONField(name = "port")
    private String port;
    @JSONField(name = "port_name")
    private String portName;
    @JSONField(name = "taxes")
    private String taxes;
    @JSONField(name = "refund_taxes_flag")
    private Integer refundTaxesFlag = 0;
    @JSONField(name = "shop_coupon")
    private String shopCoupon;
    @JSONField(name = "plat_coupon")
    private String platCoupon;
    @JSONField(name = "refund_flag")
    private List<Integer> refundFlag = new ArrayList<>();
    @JSONField(name = "freight")
    private String freight;
    @JSONField(name = "lease_term")
    private String leaseTerm = "";
    @JSONField(name = "lease_period")
    private String leasePeriod = "";
    @JSONField(name = "lease_price")
    private String leasePrice = "";
    @JSONField(name = "guarantee_service")
    private String guaranteeService = "";
    @JSONField(name = "associated_order")
    private String associatedOrder = "";
    @JSONField(name = "deposit_turnover")
    private List<DeposoitTurnOver> depositTurnover = new ArrayList<>();
    @JSONField(name = "pay_back_amount")
    private String payBackAmount = "";
    @JSONField(name = "deduction_amount")
    private String deductionAmount = "";
    @JSONField(name = "deduction_reason")
    private String deductionReason = "";
    @JSONField(name = "pay_time")
    private String payTime;
    @JSONField(name = "associated_amount")
    private String associatedAmount;
    @JSONField(name = "check_flag")
    private Integer checkFlag = 0;
    @JSONField(name = "checkPassFlag")
    private Integer checkPassFlag = 0;
    //买断金额
    @JSONField(name = "buy_out_amout")
    private String buyOutAmount;
    //押金
    @JSONField(name = "deposite_amount")
    private String depositeAmount;
    //用户授信免押额度
    @JSONField(name = "shop_credit_amount")
    private String shopCreditAmout;
    //首付金额
    @JSONField(name = "after_pay_flag")
    private Integer afterPayFlag = 0;
    @JSONField(name = "first_pay")
    private String firstPay = "";
    @JSONField(name = "first_pay_way")
    private String firstPayWay = "";
    @JSONField(name = "pay_way")
    private String payWay = "";
    @JSONField(name = "month_pay")
    private String monthPay = "";
    @JSONField(name = "goods_business_type")
    private Integer goodsBusinessType;
    @JSONField(name = "goods_business_type_desc")
    private String goodsBusinessTypeDesc;


    @Setter
    @Getter
    @ToString
    public static class DeposoitTurnOver {
        private String time;
        private String remark;
        private String amount;
        @JSONField(name = "associated_amount")
        private String associatedAmount;
    }
}
然后我调用RPC接口拿到的参数为:
[{"after_pay_flag":0,"amount":"0.90","apply_flag":0,"associated_order":"","checkPassFlag":0,"check_flag":0,"company_name":"","deduction_amount":"","deduction_reason":"","deposit_turnover":[],"express_flag":0,"express_id":"","first_pay":"","first_pay_way":"","freight":"0","group_flag":0,"guarantee_service":"","invoice_id":"","lease_period":"","lease_price":"","lease_term":"","month_pay":"","pay_back_amount":"","pay_way":"","pic":"https://coss-ec.fenqile.com/ecproduct201/M00/ex/20231030142247-ee1e3b73-100f-483a-ba7b-2c14b6d7fedb.png","pre_sell_flag":0,"price":"0.99","quantity":1,"receipt_mobile":"","receiver_credit_id":"","refund_flag":[],"refund_taxes_flag":0,"spec":"满折互斥平台券","taxes":"0","uid":4063946}

仔细观察可以发现,除了有默认值的参数,和无需下划线的参数都能拿到,其他的参数都拿不到!

刚开始定位方向错了,以为是包版本没有对应上,更新包版本后还是不行(耽误了大半天)

后面,我注意到关键信息【除了有默认值的参数,和无需下划线的参数都能拿到,其他的参数都拿不到!】
于是我重新看了下dubbo的RPC接口配置
在提供方是这样写的

    <!--提供的service-->
    <dubbo:service interface="com.fenqile.pop.order.service.PopOrderService"
                   ref="popOrderService" protocol="fsof" group="default" version="1.0.0" timeout="8000"
                   openfastjsonsupport="true">
        <dubbo:method name="shipX" timeout="8000"/>
    </dubbo:service>

注意,这里提供方用了openfastjsonsupport="true"!
然后我问了下Ai,这个参数是干什么的
得到如下回复:
在Apache Dubbo中,OpenFastJsonSupport并非Dubbo官方提供的组件,但在实际开发中,有可能是指开发者基于开源JSON处理库Fastjson定制的一个适配器或扩展,用于整合Dubbo与Fastjson。

Fastjson是由阿里巴巴公司开发的一款高性能的JSON处理器,用于Java对象与JSON之间的转换。在Dubbo框架中,序列化和反序列化是通信过程中的重要环节,一般会使用某种序列化工具将Java对象转换成可在网络上传输的格式,如JSON格式。

OpenFastJsonSupport可能是一个扩展点,它使Dubbo能够使用Fastjson作为其默认的序列化方案,替代或补充Dubbo自带的序列化方式(如Hessian2、Java原生序列化等)。通过集成Fastjson,Dubbo服务之间交互的数据可以高效地进行JSON格式的序列化和反序列化,从而提升服务调用的性能。具体实现上,它可能提供了一些注册Fastjson为Dubbo的序列化器和反序列化器的配置方法或类。

就是这里,使用OpenFastJsonSupport 指定了该接口的出参使用Fastjson作为其默认的序列化方案,和出参里面使用  @JSONField注解呼应上了,到这里 答案呼之欲出了!

解密:由于服务提供方指定了该接口的出参序列化方式为Fastjson,而我在配置调用方的RPC时,没有使用openfastjsonsupport="true",导致调用方拿到出参后,使用dubbo默认的序列化方式进行反序列化,而Dubbo默认的序列化方式是Hessian2,Hessian2本身并不直接支持Fastjson的@JSONField注解,因为这两个是不同的序列化框架。
Hessian2有自己的序列化规则,并且不会解析或识别Fastjson的注解。

然而,在Dubbo中,可以通过配置将序列化方式改为Fastjson,这样在序列化和反序列化时,Fastjson就会生效,并且支持@JSONField注解。当你将Dubbo的序列化方式配置为fastjson时,Fastjson将会被用来处理对象与JSON之间的转换,此时@JSONField注解的功能才会得到体现。

默认的Hessian2不能处理@JSONField注解,所以拿不到参数信息!那怎样才能拿到呢?很简单,调用方和提供方保持一致的序列化框架就可以了

于是在dubbo配置中写上:
	<!-- pop订单 -->
	<dubbo:reference id="popOrderService"
					 interface="com.fenqile.pop.order.service.PopOrderService"
					 timeout="3000" protocol="fsof" group="default" version="1.0.0" check="false" openfastjsonsupport="true"/>

OK,这样就可以正常拿到了~

一、背景

【tapd】:https://www.tapd.cn/20060141/prong/stories/view/1120060141001316092

    1. 往期直播有收到用户在直播间评论区反馈想定向听某些商品讲解,评论区无法实时采集用户意愿数据,只能事后分析用于改善下一场直播,无法用于优化当前直播间讲解策略,需有功能承载该场景诉求;

2. 直播间商品初始排序无法人为干预管控,直播开播前运营需手动调整直播间商品展示排序,目前调整排序交互是拖动,效率比较低,严重影响操作效率,需优化;

二、需求拆分

工程:afterpay_offline_marking_server

分支:rel_liveAsk_1316092

2.1 直播间商品求讲解功能

2.1.1 用户端

实现解析

模块设计内容备注
用户点击商品求讲解网关新增【商品求讲解】功能接口网关:/route0002/ecLive/incrProductAskCount.json入参:liveId + skuId + 登录态 
用户明细记录异步将明细写入Kakfa,上报给大数据 
统计计数Redis实现Key: AskCountNum : liveId  + skuIdValue: askCount有效期:7s(正常情况5S就会删除一次) 
数据落库延时队列每5s将Redis中的数据同步到数据表(直播商品属性统计表)t_live_sku_attribute_statis_record

2.1.2 管理端

实现解析

模块设计内容备注
直播选品统计页展示网关新增【查询直播选品统计详情】功能接口网关:oa/mix/live/queryLiveSelectStatis.json入参:liveId + skuId + 登录态 
查询选品信息(已有)rc_oa_gateway/mix/selection/selectGoodsAdminQuery.json 
查询已讲解次数 + 求讲解人数查询统计表t_live_sku_attribute_statis_record
商品求讲解趋势图接入大数据实现通过大数据侧明细实现 
直播选品讲解/取消讲解更新库表【讲解中的商品】更新直播间管理表 t_live_event_record
更新【已讲解次数】、【求讲解次数】更新统计表t_live_sku_attribute_statis_record
更新缓存【讲解中商品】、【求讲解次数】Key见上述缓存设计 

2.1.3 缓存设计

  • 商品求讲解统计

Key: AskCountNum_liveId + skuId

Value: askCount

2.1.4 延时队列设计

  • RocketMQ

Topic: LIVE_SKU_ASK_COUNT_STATIS_TOPIC

Group: LIVE_SKU_ASK_COUNT_STATIS_TOPIC_GROUP

2.1.5 库表设计

  • 直播商品属性统计表

CREATE TABLE `t_live_sku_attribute_statis_record` (

  `Fid` int(11) NOT NULL AUTO_INCREMENT COMMENT ‘自增主键’,

  `Flive_activity_id` varchar(32) NOT NULL DEFAULT ” COMMENT ‘直播间ID’,

  `Fsku_id` varchar(32) NOT NULL DEFAULT ” COMMENT ‘商品ID’,

  `Fextend_info` varchar(128) NOT NULL DEFAULT ” COMMENT ‘扩展信息’,

  `Fversion` int(11) unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘版本号’,

  `Fcreate_time` datetime NOT NULL DEFAULT ‘1970-01-01 00:00:00’ COMMENT ‘创建时间’,

  `Fmodify_time` datetime NOT NULL DEFAULT ‘1970-01-01 00:00:00’ COMMENT ‘修改时间’,

  `Fask_count` int(20) NOT NULL DEFAULT ‘0’ COMMENT ‘求讲解人数’,

  `Fexplained_count` int(20) NOT NULL DEFAULT ‘0’ COMMENT ‘已讲解次数’,

  `Fjg_auto_test_id` varchar(256) NOT NULL DEFAULT ”,

  PRIMARY KEY (`Fid`) USING BTREE,

  UNIQUE KEY `idx_unique_live_sku` (`Flive_activity_id`,`Fsku_id`),

  KEY `idx_modify_time` (`Fmodify_time`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=’直播|直播商品属性统计表|breakyang|20240116′;

  • 直播商品属性用户明细表(在大数据侧建表)

CREATE TABLE ec_live_db.t_live_sku_use_record(

  `Fid` int(20) NOT NULL AUTO_INCREMENT COMMENT ‘自增主键’,

  `Flive_activity_id` varchar(32) NOT NULL DEFAULT ‘0’ COMMENT ‘直播ID’,

  `Fsku_id` varchar(32) NOT NULL DEFAULT ” COMMENT ‘商品ID’,

  `Fuid` int(20) NOT NULL DEFAULT ‘0’ COMMENT ‘用户Uid’,

  `Fextend` varchar(256) NOT NULL DEFAULT ” COMMENT ‘扩展信息’,

  `Fmodify_time` datetime NOT NULL DEFAULT ‘1970-01-01 00:00:00’ COMMENT ‘修改时间’,

  `Fcreate_time` datetime NOT NULL DEFAULT ‘1970-01-01 00:00:00’ COMMENT ‘创建时间’,

  `Fversion` int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘版本号’,

  PRIMARY KEY (`Fid`) USING BTREE,

  UNIQUE KEY `idx_unique_live_sku` (`Flive_activity_id`,`Fsku_id`),

  KEY `idx_modify_time` (`Fmodify_time`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8  COMMENT=’直播|直播商品属性统计流水表

|breakyang|20240116′;

2.1.5 接口文档

轮询讲解中商品接口:

接口名称直播间ID获取直播间信息接口
gateway/route0002/ecLive/queryLiveInfo.json
接口 
方法queryLiveInfo
group*
版本号1.0.0
超时时间3000ms
是否登录
入参字段名字段类型是否必传描述
 live_activity_idString直播间ID
出参    
     
resultint0-成功
res_infoStringok
result_rows  
 live_activity_idString直播间ID
 live_activity_nameString直播间名称
 event_base_idString促销ID
 select_idString选品池ID
 live_stateint1-未开播,2-直播中,3-已结束 
 explaining_sku_infoString讲解中的商品信息
 update_timeString直播间商品列表更新时间
 couponActivityEventInfoJSONObject直播活动信息
     

直播间商品接口:(无改动)

https://m.fenqile.com/route0002/galaxy/recommendSku.json

新增【商品求讲解】功能接口

接口名称商品求讲解接口
gateway/route0002/ecLive/incrProductAskCount.json
接口com.fenqile.afterpay.offline.marketing.cgi.live.CgiLiveSkuAskService 
方法increaseProductAskCount 
group*
版本号1.0.0
超时时间3000ms
是否登录
入参字段名字段类型是否必传描述
 live_activity_idString直播间ID
sku_idString商品ID
登录态   
出参   

新增【查询直播选品统计详情】接口

接口名称查询直播选品统计详情功能接口
gatewayrc_oa_gateway/mix/live/queryLiveSelectStatis.json
接口com.fenqile.afterpay.offline.marketing.cgi.live.CgiLiveEventRecordQueryService
方法queryLiveSelectorStatis
group*
版本号1.0.0
超时时间3000ms
是否登录
入参字段名字段类型是否必传描述
 登录态   
live_activity_idString直播间ID
select_idString选品ID
query_sceneInteger查询场景2 – smart选品明细
出参字段名字段类型  
 sort int  序号 
sku_id String  skuId 
sku_pic List<String>  商品图片 
mart_amount String  商品市场价 
stock String  可销售库存 
is_product_up_down String  状态 
is_product_up_down_desc String  状态描述 
short_product_name   
ask_countint  求讲解人数 
explained_countint  已讲解次数 

                {

// 已讲解次数

“explained_count”: 10,

// 求讲解次数

“ask_count”: 52,

                    “mart_amount”: “10”,

                    “category_concat_name”: “测试类目-测试类目-测试类目01”,

                    “category_id_2”: 2380,

                    “category_id_1”: 80,

                    “order_cnt_30_new”: 0,

                    “sku_pic”: [

                        “https://coss-ec.fenqile.com/ecproduct201/M00/ex/20230920135058-fcd2ee07-c50b-456d-aab9-572fcded1a26.png”

                    ],

                    “operate_list”: [

                        1,

                        2

                    ],

                    “self_pop”: 2,

                    “category_name_2”: “测试类目”,

                    “category_name_1”: “测试类目”,

                    “product_id”: “P202309207375402”,

                    “stock”: 999999,

                    “category_id_3”: 2134,

                    “is_product_up_down”: 0,

                    “amount”: “9”,

                    “discounted_amount”: 0.0,

                    “self_pop_desc”: “POP”,

                    “brand_name”: “阿玛尼(ARMANI)”,

                    “sku_id”: “MES202309207855022”,

                    “category_name_3”: “测试类目01”,

                    “sku_key_1”: “3”,

                    “sort”: 1,

                    “product_name”: “单品促销测试0”,

                    “sku_key_concat_name”: “3,d,a”,

                    “brand_id”: 53,

                    “extend”: {},

                    “merch_name”: “测试一号店铺”,

                    “is_product_up_down_desc”: “下架”,

                    “sku_key_2”: “d”,

                    “sku_key_3”: “a”,

                    “merch_id”: “MC201601210278193”

                }

改动【商品讲解/取消讲解】接口

接口名称商品讲解/取消讲解接口
gatewayoa/mix/live/explaining_goods.json
接口com.fenqile.afterpay.offline.marketing.oa.cgi.live.CgiLiveEventRecordApplyService
方法explainGoods
group*
版本号1.0.0
超时时间3000ms
是否登录
入参字段名字段类型是否必传描述
 登录态   
live_activity_idString直播间ID
explain_actionInteger1 – 讲解2 – 取消讲解
explaining_sku_infoString操作的商品
出参字段名字段类型  

Kafka生产者分区策略 – 掘金 (juejin.cn)

kafka key的作用一探究竟,详解Kafka生产者和消费者的工作原理!-腾讯云开发者社区-腾讯云 (tencent.com)

kafka 生产调优-生产常用参数配置 – 知乎 (zhihu.com)

彻底理解kafka中partition和消费者对应关系_kafka partition consumer 对应关系_慕城南风的博客-CSDN博客

更换group.id时kafka从哪开始消费-CSDN博客

【精选】kafka部分partition消息堆积问题解决记录01_kafka lag产生的原因-CSDN博客

大概如下:

Bean 的生命周期

如上图所示,Bean 的生命周期还是比较复杂的,下面来对上图每一个步骤做文字描述:

Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化

Bean实例化后对将Bean的引入和值注入到Bean的属性中

如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法

如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入

如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。

如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。

如果bean有被@PostConstruct注解的方法,会执行该方法;如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用

如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。

此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。

如果bean有被@PreDestroy注解的方法,执行该方法;如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。
————————————————
版权声明:本文为CSDN博主「秃头小魔王」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_32780741/article/details/106317438