说明: 正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使是纯拼音命名方式也要避免采用。
正例: alibaba / tabao / youku / hangzhou / duiba / tuia 等国际通用的名称,可视同英文
反例: DaZhePromotion [打折] / getPinFenByName() [评分] / int 某变量 = 3
说明: JAVA本身是支持对象中同名且大小不一致的字段,但是第三方工具对这种形式的命名不友好,甚至异常报错。例如fastjson、jackson、lombok等
正例: name & otherName / age & otherAge / sex & otherSex
反例: name / NAME
正例: MarcoPolo / UserDO / XMLService / TcpUdpDeal / TaPromotion / Qrcode
反例: macroPolo / UserDo / XmlService / TCPUDPDeal / TAPromotion / QRcodeg
案例: DTO ==> DO 入库,不用严格执行,对于下游系统直接可以DTO入库(松懈)
强制: DTO作为RPC的载体,必须实现序列化,此外类属性杜绝使用 BigDecimal (原因自行百度)
中台部门ext包支持启动检索,在启动类Application的main方法第一行加入代码扫描指定文件夹:
cn.com.duiba.wolf.utils.ClassUtils.checkDto("cn.tuia.activity.center.api.dto");
1. 兑吧yapi地址:http://yapi.dui88.com/
2. 上传swagger==>【数据管理】==>开启url导入==>本地启动web系统
3. 导入swagger地址为本地ip如,http://172.16.61.240:17785/v2/api-docs
4. 运行可以sso_ticket:test和csrf_off:true绕过权限不要adminId信息
5. yapi对于后端可以用来模拟请求,类似POSTMAN,对于前端有个Mock接口
正例:
@Getter
public enum OrientPeopleTagTypeEnum {
NONE(0,"无"),
CROWDINTEREST(1,"商业兴趣标签")
;
private Integer type;
private String desc;
OrientPeopleTagTypeEnum(Integer type, String desc) {
this.type = type;
this.desc = desc;
}
。。。。
}
注意:类属性要注释,类属性要生成getter方法
说明: getObject() 与 get0bject() 的问题。一个是字母0, 一个是数字 0,加@Override可以准确判断是否覆盖成功。
另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错;类似的常量赋值时要用大写的L,反例如Long a = 2l和数字Long a = 21
正例: "test".equals(object)
反例: object.equals("test")
说明: 推荐使用java.util.Objects#equals(JDK7 引入的工具类)
Objects.equals("test", "testNo");
说明: 对于 Integer var = ? 在 -128~127范围内的赋值,Integer 对象是在 IntegerCache.cache
中产生的,会复用已有对象,这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象。
这是一个大坑,推荐使用 equals 方法进行判断
反例:
Integer a = 122;
Integer b = 122;
// 输出 true
System.out.println(a == b);
Integer c= 129;
Integer d = 129;
// 输出 false
System.out.println(c ==d);
1. 所有的POJO类属性必须使用包装类型数据
2. RPC 方法的返回值和参数必须使用包装数据类型
3. 所有局部变量使用基本数据类型
说明: POJO类属性没有初值,是要提醒使用者在需要使用时,必须自己显式地进行赋值,
任何NPE问题,或者入库检查,都由使用者来保证
正例: 数据库的查询结果可能是null,因为自动拆箱,所以用基本数据类型接收有NPE风险。采用Integer i
反例: 比如显示成交总额涨跌情况,即正负x%,x为基本数据类型,rpc失败,默认返回为0%不合理,
应该是中画线。所以包装数据类型的 null 值,可以表示其他信息,比如rpc远程调用失败,异常退出
说明: 在方法抛出异常时,可以直接调用 POJO 的 toString() 方法打印其属性值,便于排查问题;当然lombok也可以自己选择使用
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
说明: 框架在调用属性 xxx 的提取方法时,并不能确定哪种方法一定是被优先调用到
说明: 除注释外,方法签名、左右大括号、方法内代码、空行、回车及任何不可见字符的总行数不超过80行
正例: 代码逻辑分清红花和绿叶,个性和共性,绿叶逻辑单独成为额外方法,使主干代码更加清晰;
共性逻辑抽取成共性方法,便于复用和维护
import org.apache.commons.collections.CollectionUtils;
正例: if (CollectionUtils.isEmpty(map)){
。。。
正例: if (CollectionUtils.isNotEmpty(map)){
反例: if (null==list||list.size()==0){
。。。
源码:
public static boolean isEmpty(Collection coll) {
return (coll == null || coll.isEmpty());
}
import org.apache.commons.collections.CollectionUtils;
正例: if (MapUtils.isEmpty(map)){
。。。
正例: if (MapUtils.isNotEmpty(map)){
。。。
反例: if (null==map||map.size()==0){
。。。
源码:
public static boolean isEmpty(Map map) {
return (map == null || map.isEmpty());
}
Map<String, String> demoMap = Collections.emptyMap();
// 不报错
String test = demoMap.get("test");
if (null != demoMap && StringUtils.isEmpty(test)) {
// 报错
demoMap.put("test", "test");
}
报错:UnsupportedOperationException
List<String> list= Collections.EMPTY_LIST;
// 报错
list.add("1");
import org.apache.commons.lang.StringUtils;
正例: if (StringUtils.isBlank(sthStr)){
。。。 比isEmpty多了全空格的处理判断
正例: if (StringUtils.isEmpty(sthStr)){
。。。
正例: if (StringUtils.isNotEmpty(sthStr)){
。。。
反例: if (null==str||str.length()==0){
。。。
源码:
public static boolean isEmpty(String str) {
return str == null || str.length() == 0;
}
说明:
(1)月份比实际数字少1即(0-11,5月获取到的数字是4)
(2)当按年与周一起用时需注意年底与年初的周数
(3)java默认周的第一天是sunday,在中国Calendar获取的weekday(周一获取是2,周日获取是1)
(4)时区问题,检查系统的默认时区
(5)@RefreshScope 使用cglib代理,注意默认构造
(6)禁止线程间传递HttpServletRequest,HttpServletRequest在请求调用时由tomcat创建,在响应返回时会被tomcat销毁;
若异步子线程传递使用,会出现主线程执行完成销毁HttpServletRequest,子线程报错
https://www.jb51.net/article/158192.htm
https://blog.csdn.net/m0_38039437/article/details/76229486
(7)RPC/restful接口的出入参对象一定要实现序列化
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
正例:最好使用两个对象,然后进行removeAll操作,以下正例需要自行确认
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if ("3".equals(next)) {
iterator.remove();
}
}
正例:list.removeIf("3"::equals);
反例:
for (String item : list) {
if ("2".equals(item)) {
list.add("4");
}
}
报错:java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
说明:参考《码出高效 Java开发手册》,在for循环中对expectedModCount采用modCount进行赋值。
在进行for循环时每次都会有判定条件modCount == expectedModCount,当执行完list.add("4")之后,
该判定条件返回false退出循环,然后执行if语句,结果同样抛出java.util.ConcurrentModificationException 异常
注意:这里list对象发生多线程竞争时,也会报错,要考虑线程安全性,比如guava缓存取出的也会产生竞争
说明: list.stream().collect(Collectors.toMap.....)方法,在key重复或value为null,会抛出RuntimeException
例子:只是解决重复key的问题,IllegalStateException: Duplicate key
List<Student> arrayList = new ArrayList<>();
arrayList.add(new Student(1, "zhangsan1"));
arrayList.add(new Student(1, "zhangsan2"));
arrayList.add(new Student(3, "lisi"));
//arrayList.add(new Student(3, null));
Map<Integer, String> studentMap =
arrayList.stream().filter(dto -> null != dto && null != dto.getName())
.collect(Collectors.toMap(Student::getAge, Student::getName, (oldVal, newVal) -> newVal));
打印:{
1:"zhangsan2",3:"lisi"}
注意:打开注释Student(3, null),依旧会报错NullPointerException ,value为null了
Student::getName 改成 a->a 是对象本身的话应该会减少出错的概率,返回类型不为空了
说明:三个条件如下
1)x,y的比较结果和y,x的比较结果相反
2)x>y,y>z,则x>z。
3)x=y,则 x,z比较结果和 y,z比较结果相同
List<Student> list = new ArrayList<>();
list.add(s1);
list.add(s2);
list.add(s3);
list.sort((o1, o2) -> o1.getId() > o2.getId() ? 1 : -1);
反例: if(condition) statements;
正例:
if ( condition ){
...
return obj;
}
// 接着写else的业务逻辑代码
说明: 如果不得不使用,请不要超过3层
正例: 超过3层的if-else逻辑判断代码可以使用
卫语句、策略模式、状态模式等来实现,其中卫语句示例如下
public void today(){
if(isBusy()){
sout("change time.");
return;
}
if(isFree()){
sout("go to travel.");
return;
}
sout("you are the best!");
return;
}
说明: 卫语句就是把复杂的表达式拆成多个条件的表达式,条件为真时,立马返回给调用方。
卫语句的好处是条件表达式之间相互独立,不会干扰。
说明: 反逻辑不利于快速理解,并且取反逻辑必然存在对应的正向逻辑写法
正例: 使用 if ( x < 628 ) 来表达 x 小于 628
反例: 使用 if ( ! ( x >= 628 )) 来表达小于628
说明:由动态代理实现,主要两种:jdk动态代理和cglib动态代理,核心是新的代理类执行方法, 这里本类调用的public方法无法切面,除非自己再把自己注入再调用(太low,不推荐)或者使用AopContext .currentProxy()获取当前类代理对象(存在内存泄漏的可能,不推荐)
1. jdk动态代理是由java内部的反射机制来实现的(spring默认),cglib动态代理底层则是借助asm来实现的但是需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理(面向接口编程没有该问题)
2. jdk反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效
3. jdk动态代理的应用前提,必须是目标类基于统一的接口(局限了,但我们一般用它)
/**
* 定制简介:在元业务逻辑0积分兑换不发送扣积分请求的基础上,
* 增加白名单,给白名单中开发者额外发送0积分扣积分请求
* 需求链接:http://cf.dui88.com/pages/viewpage.action?pageId=22296558
* 负责人:刘凯
* 代码上线时间:2022年05月30日
* 代码下线时间:20XX年XX月
*/
if (order.getCredits() == 0 && !zeroCreditsSendDingzhi(order)) {
return true;
}
(1)调用频次低的方法,如B端财务这种
(2)执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数操作导致中间执行回退,或者错误,那得不偿失。
(3)需要极高稳定性和可用性的方法
(4)对外提供的开放接口,不管是否为RPC/API/HTTP 接口
(5)敏感权限入口
(1)极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求
(2)底层调用频度比较高的方法。毕竟像纯净水过滤的最后一道,参数错误不太可能到底层才暴露问题。
一般DAO层与Service层同一个应用中,并部署在同一台服务器中,所以DAO的参数校验可以忽略
(3)类中的private方法,自家兄弟,老爹public肯定校验过了
1.【推荐】SSO日志:对于B端管理关键业务操作,特别是修改和删除,增加sso操作日志,方便追踪回溯
说明:可以在SSO查看日志:https://sso.duiba.com.cn/ui#/log/logList
1. 共有方法上: @LogWrite(modelName = "用户模块", option = "管理员登录", ignoreParams = {
"password" })
2. 或者干脆定制上报sso
@SsoLoggerMethod(value = "开关策略,设置${type}为${op}", group = "风控组")
public void doSth(){
log.put("op", op);
log.put("type", type);
SsoLogger.logForJson(log);
2.【推荐】阿里云日志:实时日志信息聚合可以通过阿里云查看,如输入广告订单。阿里云地址:https://sls.console.aliyun.com/lognext/project/credits/logsearch/tuia_launch_log
(1)二次加工会议确认:读写分离的原则上,最好一次回流到位
回流数据涉及业务查询再次计算整合加工的,需要会议提前确认并告知@张攀@永坤
(2)表结构业务研发负责,数据只回流生产:
数据回流表结构DDL,业务研发负责,生产、非生产环境,字段修改
(3)业务部:单行数据可适当业务研发计算:
数据回流提前确定字段,但如果后期发现需要CTR(最好回流好),表中已有点击C和曝光S,业务研发可自己计算CTR=C/S
(4)数据部:多行数据加工回流,工程业务不加工:如3日日均,7日统计(每行都有个字段如 per_uv)
用例设计方法:逐级细分法、域测试法、输出域分析法、正交试验法、业务流分析法、状态迁移法、判定表法、因果图法、错误猜测法
逐级细分法是对被测对象进行细分,当被测对象有多种特性或参数,各特性或参数之间可能有依赖关系并且需要选择不同参数取值的组合时适用,由于考虑的因素较多使用相对复杂
正交试验法适合被测对象输入多组合多,无法全部测试所有组合时使用,与其他方法相比能够有效合理的减少测试用例数量。当各输入之间有特殊关系,需要选择是否组合时可以用逐级细分法
因果图和判定表两种方法充分考虑了输入条件间的组合和约束关系,避免了无效用例,并且同时得出了预期输出。因果图可理解为判定表的前置过程。简单的或输入输出非常明确的系统,可单独使用判定表;但是当被测对象输入较多时,用例规模会非常庞大。输入之间的关系不能明确是否确实需要进行组合时,容易产生冗余
错误猜测法依靠直觉和经验设计用例,经验越丰富使用该方法的效率越高
正例: logger.info("LandPage.addAdvert落地页审核报错id为: {}。。。", id);
查看日志级别:http://10.104.12.3:17789/loggers
QPS高的系统,一定要做好info日志收敛
import org.apache.commons.lang.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Objects;
@Component
@RefreshScope
public class ApolloConfig {
private static final Logger logger = LoggerFactory.getLogger(ApolloConfig.class);
/**
* 环境变量deploy_type=fortress灰度 deploy_type=normal正式
*/
private static String deploy_type = System.getenv("deploy_type");
@Value("#{'${tuia.pdd.advert.account.id.list:0}'.split(',')}")
private Set<String> pddAdvertAccountIdList;
/**
* 打印日志信息,默认是不打印日志的
*/
@Value("${sys.log.info.flag.on:0}")
public String sysLogInfoFlag;
/**
* 打印日志信息
*/
public void info(String format, Object... arguments) {
if (infoPrintFlag) {
logger.info(format, arguments);
}
}
public Boolean infoPrintFlag() {
if (ObjectUtils.equals(sysLogInfoFlag, "1") || ObjectUtils.notEqual(deploy_type,"normal") ) {
return true;
}
return false;
}
}
@Autowired
private ApolloConfig apolloConfig;
apolloConfig.info("竞价请求成功{}_1,bid", bid);
LoggerSizeFilter获取 key为 duiba.logger.size-limit.error-threshold (默认32K) 和 duiba.logger.size-limit.error-threshold (默认16K) 的appollo配置值
超过error长度限制的日志信息不允许打印,并且打印一个error级别的告警日志,超过warn长度限制的允许打印日志信息 但会同时打印一条warn日志,可在Apollo配置
正例:无占位符, logger.error("LandPageAuditController.addAdvertRiskRecord ", ex);
说明: Spring针对反射提供的工具类:ReflectionUtils,这些方法一般在Spring框架内部使用,
Spring框架处理异常,void handleReflectionException(Exception ex),查看源码
public static void handleReflectionException(Exception ex) {
if (ex instanceof NoSuchMethodException) {
throw new IllegalStateException("Method not found: " + ex.getMessage());
}
if (ex instanceof IllegalAccessException) {
throw new IllegalStateException("Could not access method: " + ex.getMessage());
}
if (ex instanceof InvocationTargetException) {
handleInvocationTargetException((InvocationTargetException) ex);
}
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw new UndeclaredThrowableException(ex);
}
说明: 个人手机号码会显示为132****0813,隐藏中间4位,防止泄露个人隐私
(1)page size 过大导致内存溢出
(2)恶意order by 导致数据库慢查询
(3)任意重定向
(4)SQL注入
(5)反序列化注入
(6)正则输入源串拒绝服务ReDos
说明: 如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,
应使用大于或小于的区间判断条件来代替。
反例: 判断剩余奖品数量等于 0 时,终止发放奖品, 但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止。
1. 动态资源和静态资源分离;CDN;客户端承载计算(如抢红包可客户端先随机)
2. 网关限流、负载均衡
3. 本地缓存(过期刷新)、二级缓存、redis缓存(防止热点数据问题)
3. 分布式缓存:缓存雪崩、穿透、缓存更新、预热、降级、双写数据一致性
4. 分布式部署,方便横向扩容(单机调整权重,压测瓶颈性能)、分布式全局ID、分布式Session
5. 数据读写分离或数据切分(垂直或水平)
6. 高并发最主要通过QPS压测、容器线程调优、火焰图CPU、GC日志等分析解决单机瓶颈问题;工具(阿里云或脚本)压测,结合95线监控,曲线监控等解决集群的瓶颈问题(中间件、数据库、高时间复杂度如list.contains改成set.contains等)
正例:
(1)hashMap.contians(key)
(2)hashSet.contains(key)
(3)将计算结果进行缓存,不要取出缓存后再计算
反例: list.contains(key)
1. 安装:curl -O https://arthas.gitee.io/arthas-boot.jar
2. 启动:java -jar arthas-boot.jar 1
3. 启动profiler (可以监听cpu、lock、itimer等):profiler start --event cpu
4. 采样,过一分钟,停止profiler: profiler stop
上述已经执行过,进入arths界面了
tt -t cn.com.duiba.tuia.service.impl.EngineServiceImpl queryAdvert
tt -l
tt -i 1001
查看入参:
watch cn.com.duiba.tuia.ssp.center.api.remote.RemoteProduceDayBillService selectNotUploadedSlot "{params,returnObj}" -x 5
此外,也可以这么进入arths,但是慎用,先测试环境自己确认:
curl -L https://alibaba.github.io/arthas/install.sh | sh
./as.sh pid
说明:阿里云已经封装好了trace的方法,1代表进程1,可以jps查看
(1)curl -sLk http://ompc.oss.aliyuncs.com/greys/install.sh|sh
(2) ./greys.sh 1
(3)查看方法耗时,小心,一定要用完立刻退出,会阻塞线程,吃掉CPU,万一吃掉记得控制【禁用容器】
trace cn.com.duiba.tuia.service.impl.AdvertQueryServiceImpl copyAndPutItem
(4)查看方法返回参数(f是返回,b):watch -bf com.duiba.tuia.activity.web.bo.impl.JoinActivityBOImpl doJoin returnObj -x 5
(5)params[0](或者1表示第2个) 代替 returnObj也可以查看入参,可以当成object执行如objet.getName()方法
查看出入参:
(1)【生产】环境慎用greys命令,也可以记录1次条用: tt -t -n 2 com.duiba.tuia.activity.web.bo.impl.JoinActivityBOImpl doJoin
(2)控制台得到index为1001后,查看入参和返回结果:tt -i 1001 -w params[0] -x 2
=====> 或者:tt -i 1001 -w returnObj -x 2
说明:循环调用这类方法可能会同时阻塞,占用资源,造成资源浪费,严重时导致程序挂起。
循环中禁止调用dubbo服务,循环调用改成一次批量调用。
循环中禁止多次操作数据库,循环操作数据库改成一次批量操作数据库。
严禁循环中调用redis操作,修改成批量查询。
说明: 任何字段如果为非负,则必须是unsigned。
注意: POJO 类型中的任何布尔类型的变量,都不要加 is 前缀。
所以需要在设置从is_xxx到XxxFlag的映射关系。数据库表示是与否的值,使用tinyint类型。
正例: 表达逻辑删除字段名 is_deleted ,1表示删除,0表示未删除,默认为0
说明: 数据库字段名修改代价很大,无法进行预发布,会锁表的!!!!
正例: aliyun_admin , rdc_config , level3_name
反例: AliyunAdmin , rdcConfig , level_3_name
说明: 表名应该仅仅表示类里面的实体内容,不应该表示实体数量,对应DO类名也是单数形式,符合表达习惯。
说明: pk_即 primary key; uk_即 unique key; idx_即index的简称
说明: 在存储的时候 float 和 double 存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。
如果存储的范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储
说明: 其中id为主键,类型为unsigned bigint、单表时自增、步长为1。可以适当增加 is_deleted 字段
gmt_create和gmt_modified的类型均为date_time类型,前者现在时表示主动创建,后者被动更新
CREATE TABLE `tb_what_info` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted ` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除0否,1是'
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tag_id` (`tag_id`),
KEY `idx_gmt` (`gmt_create`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='表的名称';
说明: 即使双表join也要注意表索引、SQL性能。
说明: 索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用 count(distinct left(列名, 索引长度) / count(*)) 区分度确定
说明: 索引文件具有B-Tree 的最左前匹配特性,如果左边的值未确定,那么无法使用此索引
正例: where a=? and b=? order by c; 索引 : a_b_c。
反例: 索引中有范围查找,那么索引有序性无法利用,如 WHERE a>10 ORDER BY b; 索引 a_b 无法排序
正例: 如果 where a=? and b=? , a列几乎接近唯一值(不重复),
那么只需要单建 idx_a 索引即可。
说明: 如存在非等号和等号混合判断条件,在建索引时,请把等号条件的列前置。
where a>? and b=?,那么即使a的区分度更高,也必须把b放在索引的最前列。
SELECT group_concat(id separator ',')
FROM(SELECT id FROM `tb_material` WHERE `ms_id`= -1
ORDER BY `gmt_create` DESC LIMIT 20) a
url参数解析: default.get_url_para(a.url_query, 'dpm') = '4.1.99'
referer解析: parse_url(referer, 'QUERY','slotId') = 324312
json解析: get_json_object(json,'$.advert.advertId')='63120'
接口access查询: and url_query = '/activity/index'
常用BI分析会建小表:
create table tmp.wzj_20200922_land_a as
select * from (。。。
@ApiModel("广告主秘钥分页查询参数")
public class AdvertiserSecretKeyQueryParam extends BaseQueryReq {
@ApiModelProperty("广告主id")
@NotNull(message = "不能空!!!!")
private Long advertiserId;
@ApiModelProperty("广告主名称")
private String advertiserName;
。。。
/**
* 分页查询广告列表
*/
@RequestMapping(value = "/pageQuery")
@ApiOperation(value = "分页查询广告列表",notes = "分页查询广告列表", response = AdvertsInfo.class)
public void pageQuery(@Valid @ModelAttribute PageQueryAdvertsReq req,
BindingResult result,HttpServletResponse response) {
try {
checkParam(result);
exceptionSuccess(response, advertsService.pageQuery(req), "查询广告列表成功");
} catch (Exception e) {
logger.error("pageQuery error! the req=[{。。。]", req);
exceptionFailure(response, e);
}
}
说明: 统一代码样式,已经造好的轮子就不要继续造轮子了
public class ByDateQueryReq extends BaseQueryReq {
private static final long serialVersionUID = -4050540694496810908L;
/** 开始时间. */
@DateTimeFormat(pattern = "yyyy-MM-dd")
@ApiModelProperty(value="开始时间,yyyy-MM-dd")
private String startDate;
。。。。。
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
5.【参考】子线程复用Htttp请求的参数BUG。Controller中的HttpRequest对象,如果异步处理需要将对象中的数据取出,否则会在主线程结束的时候,destroy掉,这样就会导致系统NPE
【强制】RPC调用,对象属性不准使用BigDecimal,hessian序列化会将其变为0,需要升级hessian版本到4.0.63以上,还要很多其他操作,而且DTO一定要实现序列化
【强制】RPC调用必须用包装类,Query对象采用中台提供的BeanUtils进行copy
正例: import cn.com.duiba.wolf.utils.BeanUtils;
// req构造rpc从dao取数据的对象,dto
LandPageDiagCenterDto centerReqDto = BeanUtils.copy(req, LandPageDiagCenterDto.class);
centerReqDto.setStartDate(DateUtils.getDayStartTime(req.getStartDate()));
centerReqDto.setEndDate(DateUtils.getDayStartTime(req.getEndDate()));
diagnosisResultDtoList = remoteLandPageQueryService.listDiagnosisResult(centerReqDto)
【参考】本地启动服务A,调用本地服务B,启动的服务可在Eureka查看,需要配置A服务:
idea添加参数 Program arguments中: --tuia-advert-center.ribbon.listOfServers=127.0.0.1:17787
【参考】RPC提供的接口是可以直接手动调用的
说明:
(1) 系统直接调用remote,然后第一个参数_p0
(2) 参数对象需要json然后URL编码
(3) curl 'http://127.0.0.1:17787/remoteAdvertCallV2Service/queryLog?_p0=%7B%22advertKey%22:%22663D5CFBF76FF408A160311F6613C1D2%22,%22startDate%22:%222019-08-29%2014:00%22,%22endDate%22:%222019-08-29%2016:00%22%7D'
(4)rpc入参是变量Long id 这种,就只处理第几个参数:curl 'xxx?_p0=1&_p1=something'
(5)对象中如果还有Map之类的,可以采用:{
"reqData":{
'curDate':'2018-06-28'},"reqCode":"appId"}
【强制】服务端要有上下级概念,不能出现循环调用的情况,更不能for循环调用rpc或者mysql
【强制】rpc调用的dto:必须按中台实现hessian2的序列化接口,提供序列化Id,内部类必须提供构造方法
配置:duiba.feign.serialization=hessian2
举例:
class SuccBidInfo implements Serializable {
private static final long serialVersionUID = 6996300130162900166L;
。。。
生产环境topic是不会自动生成的,看MQ生成的topic等信息
可以在米莉亚==>监控平台==>MQ监控==>主题,也可以看具体的消费信息
说明:以下配置需要在Apollo进行配置
duiba.rocketmq.producer.enable true
duiba.rocketmq.producer.group PID-tuia-ssp-center-daily
duiba.rocketmq.topic.refreshcache tuiaRefreshCacheDaily
duiba.rocketmq.topic.crmSign duibaCrmSignTest
duiba.rocketmq.consumer.enable true
duiba.rocketmq.consumer.group CID-tuia-ssp-center-daily
duiba.rocketmq.consumer.message-model BROADCASTING
duiba.rocketmq.consumer.topics tuiaRefreshCacheDaily
多配置,如同时配置集群和广播,看中台文档:
duiba.rocketmq.extra-consumer[0].message-model BROADCASTING
(1)禁用key:scan、flushdb、hgetall、hkeys、hvals、smembers、scan
(2)热点key问题:本来可以单商品缓存的,但是如果用了hget缓存了所有商品,就GG了。。。
(3)分布式锁:
try(RedisLock lock = redisAtomicClient.getLock(key, 5)) {
if (lock != null) {
//lock succeed, do something
}
}
正例:
import java.util.concurrent.ExecutorService;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.TimeUnit;
@Resource
private ExecutorService executorService;
private final LoadingCache<String, AdvItem> TRADE_SIMILAR_ADVERT_CACHE = CacheBuilder.newBuilder().initialCapacity(100).maximumSize(5000).
recordStats().refreshAfterWrite(5, TimeUnit.MINUTES).expireAfterWrite(1, TimeUnit.HOURS).build(
new CacheLoader<String, AdvItem>() {
@Override
public Optional<AdvItem> load(String key) {
return doSomething(key);
}
@Override
public ListenableFuture<Optional<AdvItem>> reload(String key, Optional<AdvItem> oldValue) throws Exception {
ListenableFutureTask<Optional<AdvItem>> task = ListenableFutureTask.create(() -> {
Optional<AdvItem> loadRes;
try {
loadRes= load(key);
} catch (Exception e) {
log.error("XXXXX reload e", e);
return oldValue;
}
return loadRes;
});
executorService.submit(task);
return task;
}
});
}
注意:配置文件配置或Apollo配置,查看中台文档,自构线程池,配置在 application.properties
duiba.threadpool.core-size=50
duiba.threadpool.enabled=true
#然后,根据系统中所要使用线程池个数增加相应数目的线程池配置
#每个线程池有四个参数,意义分别如下:
##线程池核心大小.默认2,建议按应用使用情况改为合适的值
duiba.threadpool.extra.[线程名称].core-size=50
##线程池最大线程数,默认20,建议按应用使用情况改为合适的值(实际上maxSize获取的算法为maxSize=Math.max(maxSize,coreSize))
duiba.threadpool.extra.[线程名称].max-size=100
参考:
String[] split = advertIdAndAcgId.split("_");
if (split.length != 2) {
throw new TuiaException(ErrorCode.E0100002);
}
Long advertId = Long.parseLong(split[0]);
Long acgId = Long.parseLong(split[1]);
说明:
(1)如广告线广告的全量过滤逻辑,就是循环list[A、B](举例)然后浅拷贝对象成list2[C、D],
注意A和C是不同对象,hash地址是不同的;但是A的属性 idsSet 比如一个Set和C有相同的地址;
所以C的idsSet属性只能进行替换操作setIdsSet(新的Set),而不能取出来属性后进行修改(会修改源数据)
(2)还有流量线之前有一个guava缓存中取list,然后对list进行了remove操作,
采用了正规的迭代器删除,还是报错 ConcurrentModificationException ,就是因为共享变量问题。这里只要list.addAll()就能解决
【强制】DB数据变更MQ通知:配置修改后,需要广播通知所有机器缓存,否则会引发侧漏问题,可以在缓存所在类中暴露一个public方法提供刷新refresh使用。本地缓存刷新的时候,如果刷新的数据量大,如List,要小心缓存击穿引起的【MYSQL崩溃】,要做好二级缓存;也可以类似先A生产,集群消费1台B进行本地缓存、分布式缓存更新,完成后B广播消费进行更新数据
【参考】本地缓存,非生产环境提供了清除缓存功能:curl http://localhost:17792/monitor/cache/clearAll
【参考】集群中大量使用本地缓存,DB修改后,如果通过MQ进行广播refresh实现数据的一致性,要小心缓存击穿引起的DB压力过大。常见的优化通过二级缓存进行处理,二级缓存也要防止击穿。可以通过如先集群消费确保数据更新,后广播同步刷新;或者广播时,针对如批量List数据刷新,在消费端可以进行List数据重排,更新二级缓存,减轻DB压力
链路化思维要具备traceId的关联,可以在日志平台查看info日志:
比如1,2,3,4,5行为关联,缺失了4就知道是4的问题:dpaTes* and traceId: c2b2822351af75ac
1. 米莉亚搜索日志:exportable: true;然后查看:
https://console.dui88.com/zipkin/#/service/zipkin/tracePage
2. id的名词意思:
traceId:用来确定一个追踪链的16字符长度的字符串,在某个追踪链中保持不变。
spanId:区域Id,在一个追踪链中spanId可能存在多个,每个spanId用于表明在某个服务中的身份,也是16字符长度的字符串。
parentId:在跨服务调用者的spanId会传递给被调用者,被调用者会将调用者的spanId作为自己的parentId,然后自己再生成spanId。
3. 同类产品:pinpoint、skywalking、HTrace、Tracing 阿里鹰眼、Hydra 京东、Watchman 新浪
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
@RequestMapping(value = "/bidTest", method = RequestMethod.GET)
@ResponseBody
@HystrixCommand(
fallbackMethod = "fallbackBidTest",
commandProperties = {
@HystrixProperty(
name = "execution.isolation.thread.timeoutInMilliseconds", value = "100")})
public String bidTest(@RequestParam String isOne, HttpServletResponse response) {
if (StringUtils.isNotEmpty(isOne) && isOne.equals("1")) {
try {
Thread.sleep(5000);
} catch (Exception e) {
logger.error("AiQiYiAdxController e", e);
}
return "1";
}
return "2";
}
public String fallbackBidTest(String isOne, HttpServletResponse response) {
response.setStatus(HttpStatus.NO_CONTENT.value());
System.out.println("wzjReq-----------------" + isOne);
return "3";
}
注意:hystrix是线程处理的!!!小心线程池线程数不够引起的性能问题
hystrix.threadpool.default.coreSize = 30
hystrix.threadpool.default.maximumSize = 70
hystrix.threadpool.default.maxQueueSize = 1000
1. 棱镜后台,服务治理,选择 a-web -> 【配置管理】,在自己应用的namespace中,增加一条配置 b-center.ribbon.ReadTimeout = 10000(具体数值自行设置)
2. 棱镜后台,服务治理,选择 a-web -> 【超时配置】,进去后,找到 b-center,点击左边的 【+】 展开,找到 remoteService.method1 ,修改后边的【超时时间】为10150(具体数值自行设置),然后点击右上角的【保存】,之后会自动刷新 a-web 的所有实例的配置
3. 通常 hystrix 的超时 需要略大于 ribbon的超时;1操作不会自动刷新配置(要点击 服务治理中的【刷新配置】进行配置的生效),2操作会自动刷新配置
3.【推荐】Hytrix熔断关闭,部分系统可能测试和生产要求熔断不一致,可以在Apollo配置刷新即可(不推荐application.properties)
关闭超时(ADX用此):hystrix.command.default.execution.timeout.enabled=false
关闭hystrix异常后熔断短路:hystrix.command.default.circuitBreaker.enabled=false
健康检查:curl http://localhost:17793/monitor/check
配置中心的访问,如:http://configserver.dui88.com/tuia-activity-web/dev
(1)eureka地址:http://eureka.duibatest.com.cn/
(2)健康检查:curl http://localhost:17793/monitor/check
【参考】容器中调用指定接口: curl localhost:17779/test/monitor/v2 --cookie “_duibaServiceGroupKey=miria-704”
【参考】 Linux中对CPU占用排查
ps -mp 1 -o THREAD,tid,time|grep -v 00:00
jstack 1 | grep 0x$(printf "%x\n" 26) -A 10 --color
说明:
(1)变量表达不要怕长一定要分类清晰、且注释明确,不要忘记RefreshScope,最好附带默认值
@Service
@RefreshScope
public class AdvertQueryServiceImpl implements AdvertQueryService {
@Value("${advert.launch.parallel.flag:-1}")
private String parallelFlag;
(2)变量在服务治理上修改时,需要发布==>点击刷新某台示例==>选择配置信息==>单台刷新,查看日志监控==>刷新所有实例
(3)如果apollo中配置的属性,比较复杂,需要代码加工的,可以用@PostConstruct注解。
但是有个问题当配置刷新时不会重新回调@PostConstruct方法。解决方案
@Service
@RefreshScope
public class AdvertQueryServiceImpl implements AdvertQueryService {
@Value("${advert.launch.parallel.flag:-1}")
private String parallelFlag;
// 添加这个@EventListener 空壳方法后,当配置刷新时,Spring就会帮我们回调@PostConstruct方法
@EventListener
public void onRefreshScopeRefreshed(final RefreshScopeRefreshedEvent event) {
getClass();
}
@PostConstruct
private void postConstruct(){
// 当parallelFlag比较复杂,例如切割字符串,转化json等等。
//......
}
找中台哨兵、刘瑶进行检索(后期会功能化,直接Apollo就可以了),不要以为就几个系统ABC用到了该数据库;如果迁移完发现还有系统F,那就是【生产事故】了;apollo_prod库
SELECT DISTINCT namespace.AppId FROM item,namespace WHERE item.Value like "%advert_statistics%" AND namespace.Id = item.NamespaceId
说明:
(1)下列方法中: obtainAdvertType 是文件夹区分,后者是文件中的子类,抛出 Throwable 异常
(2)在miliya==>监控平台==>应用监控,查看对应系统
import cn.com.duibaboot.ext.autoconfigure.core.utils.CatUtils;
举例1(处理返回值方法):
List<AdvItem> listAdvItemFinal1 = listAdvItem;
listAdvItem = CatUtils.executeInCatTransaction(() ->
getParallelStreamFilterList(advQueryParam, listAdvItemFinal1),"obtainAdvertType", "method1");
举例2(处理void方法):
CatUtils.executeInCatTransaction(() -> {
advertSupportService.support(finalOrientationPackages1, slotId);
return null;
},"obtainAdvertType", "method2");
举例3(public上面注解)
@CatTransaction(type="MessageQueue", name="RocketMQ.send")
说明:
(1)添加代码,发券异常监控:CatUtil.catLog("obtainTime/中文也行");
(2)然后在cat3中进行配置(侯文): http://console.dui88.com/cat/s/config
(3)最终可以在miliya==>监控平台==>业务监控==>查看对应系统===>报警等配置
重点: Cat监控进行了分组6位数字(如100101),前3位是组号100,后3位是组里面的个项101
@Override
public ObtainAdvertRsp obtainAdvert(ObtainAdvertReq req) {
try{
DBTimeProfile.enter("EngineServiceImpl.obtainAdvert");
ObtainAdvertRsp rsp = new ObtainAdvertRsp();
。。。
} finally {
DBTimeProfile.release();
}
注意:要最终释放,DBTimeProfile.release()
(1) DBTimeProfile默认阈值为500ms,可以属性修改,duiba.profile.threshold=500,甚至可以手动设置阈值,查看【中台文档==>其它公共属性】
(2)也可以直接注解实现( @DBTimeProfiler),但是注解只能在public方法上,打印的是:类名+方法名
@Override
@DBTimeProfiler
public ObtainAdvertRsp obtainAdvert(ObtainAdvertReq req) {
。。。
(3)也可以手动设置本次超过多少毫秒打印日志(400ms),放在enter之后:DBTimeProfile.setCurrentThreshold(400);
(4)千万要慎重使用全局配置,DBTimeProfile.setThreshold(100ms)
(5)熔断HystrixCommand等处理的时候就无法捕获DBTimeProfile
说明: 可以查看调用的链路与耗时情况: 比如可以查看: tuia-activity-web的activity/dojoin
==>redis 1ms ==> geo-server 1ms ==> activity-center /remoteActivityService/selectByActAndUserType 2ms ...
注意:(1)每次都要选择服务名才能使用,名称过长有可能检索不到,可以适当删除后面的文字
(2)也可以通过traceId检索,米莉亚日志==>检索,但是要采样到才可以使用==>过滤条件exportable: true
@SuppressWarnings("squid:S3776")
public void catAccessLogAndFixParamMap(HttpServletRequest request) {
// 很多if..else等,无法避免的
}
注意:sonar是常规各公司使用的代码检查工具,主要是帮助大家写代码的时候:(1)抓住核心;(2)代码逻辑不会太复杂,行数不能太多
大家写代码的时候有些人喜欢:如果怎么样。。。然后怎么样。。。如果怎么样。。。然后怎么样。。。,这样没有重点。
应该是:一、怎么样A;二、怎么样B;三、怎么样C;可能二和三之间会加入,如果怎么样。。。然后怎么样;这是卫语句写法(guard clause)
说明: (1)阻塞: 并行流使用要计算QPS,比如执行耗时20ms,系统单线程理论支持50qps,
如果系统qps增加为60qps,将有10个qps阻塞
(2)严禁公用 ForkJoinPool,容易造成死锁和超长队列等待
(3)耗时<20ms,或共享变量修改过多,禁止使用 ParallelStream
(4)线程池大小不能超过CPU核数,公式
private static ForkJoinPool forkJoinPoolSimpleFilter = new ForkJoinPool(4);
// 并行流执行过滤
try {
ForkJoinTask> submit = forkJoinPoolSimpleFilter.submit(() ->
advOrientationItemList.parallelStream().filter(adv ->isBooleanFilterCheck(advQueryParam, filterTypeSets, adv))
.collect(Collectors.toList()));
advOrientationItems = submit.get();
} catch (Exception e) {
logger.error("AdvertQueryServiceImpl.getSimpleFilterList e:{。。。",e);
advOrientationItems = new ArrayList<>();
}
配置环境变量,限制JNI问题:env |grep MALLOC_ARENA_MAX
显示:MALLOC_ARENA_MAX=4,文献:https://cloud.tencent.com/developer/article/1054839
dump日志分析(dump日志先处理容器权重为0):自己dump日志,放到容器:root/logs/xxx/ip/123.zip
(1) cd /root/logs
(2)命令,其中1是进程pid的意思:jmap -dump:format=b,file=a.heap 1
(3)yum install zip
(4)zip -r 123.zip a.heap
(5)mv 过去,然后放在某个目录可以在控制台==>应用发布==>下载日志
(6)优化后可以在米莉亚直接下载了,DUMP是重量级命令,注意容器预留内存(如DUMP一个8G的JVM,需要先提高容器配置到16G)
(1) jstat -gcutil ,关键点YGC从25155次到25156次发生了一次YGC,查看各个指标
(1) 实际执行语法:jstat -gcutil ${
vmid} 1000 10
S0 S1 E O M YGC YGCT FGC FGCT GCT
0.00 100.00 91.57 44.38 94.20 25155 885.923 0 0.000 885.923
0.00 100.00 1.53 43.43 94.20 25156 885.963 0 0.000 885.963
(2)jstat -gc ,类似,具体还有很多命令,可以自行检索
重点关注搜索:死锁,Deadlock、等待资源,Waiting on condition、等待获取监视器,Waiting on monitor entry、阻塞,Blocked
其次关注:执行中,Runnable、暂停,Suspended、对象等待中,Object.wait() 或 TIMED_WAITING
停止,Parked
【说明】 如Waiting on condition,可以根据线程pid,检索到什么线程阻塞了
1. 【应用监控-JVM信息】GC Info信息: G1 Young GenerationCount 15次/min以下最好;G1 Young GenerationTime 0.5s/min以下最好;Tomcat-ActiveCount 1000以内都可
2. 看CPU抖动很厉害,登录容器查看规律
3. 查看jps程序,1为Java的进程:top -p 1 -H
4. ps -mp 1 -o THREAD,tid,time|grep -v 00:00
5. jstack 1 | grep 0x$(printf "%x\n" 26) -A 10 --color
6. 个人经验:PID30以内的都是JVM线程,主要如G1的线程 26~29
(1)修改项目下 .idea\workspace.xml,
(2)找到标签,然后添加属性: component name="PropertiesComponent"
说明:可从以下问题排查
(1)jar包 or 插件安装lombok
(2)设置setting==>搜索Annotation 勾选, Enable Annotation Processing
(3)Java Compile 配置错误,配置成了 Eclipse,改成Javac
说明:子类实体的equals/hasCode方法无法继承父类属性
(1)实体类添加:@EqualsAndHashCode(callSuper = true)
(2)或者lombok.config配置文件添加配置
config.stopBubbling=true
lombok.equalsAndHashCode.callSuper=call
Lombok的坑点:@Getter用在类上,不要在类属性上
(1)传染性,胁迫同事不得不用
(2)getter/setter没有办法区分找到使用的上下文(equals也被坑了)
(3)慎用(不用):@data、@Build、@EqualsAndHashCode(callSuper = true),使用的时候自己先百度后使用
(4)属性不区分大小写,比如userName和username是一样的(getter/setter)导致问题
(5)编译期特别吃内存,100个类用了@Getter占用100M,但是如果大家@Getter使用到属性上(1个类10个属性),就会吃掉1G的内存
优化导包:ctrl+alt+o
格式代码:ctrl+alt+l, 这个新类使用,修改的已有的类就不要使用了,可以局部使用
代码环绕:ctrl+alt+T, 通常6try,catch
插入代码:Alt+Insert, 比如构造,tostring,Getter与Setter,但是这个一般lombok的@Data
实现方法:Ctrl+I, Implement methods
覆盖方法:Ctrl+O ,Override methods
自动返回:ctrl+alt+v, 自动返回类型与变量
闭环方法:------------------------------------------------------------------
Ctrl+shift+Enter 句末分号,if的补全,很强大
Alt+Enter,自动生成比如私有方法
Shift+Enter 新建一行
优化导包:⌘ + ⌥ + O
提取变量:⌘ + ⌥ + V
返回至上次:⌘ + ⌥ + left/right
(1)采用插件RestfulToolkit,ctrl+\,直接可以获取到拼接好的url
(2)lombok
【参考】idea远程remote为单进程,debug注意默认是All的,debug红点右键选择Thread选型,只阻塞当前线程(否则K8S健康检查会重启容器)
【参考】git不常用,但是要记住的命令
本地已经commit,还没有push,想撤销(1或者2表示回滚commit之前几个版本):git reset --soft HEAD~1
git config --global user.name “你的用户名
底层架构(基础中间件):
1. DevOps(Development和Operations的组合词)透过自动化“软件交付”和“架构变更”的流程
2. 大数据埋点系统:根据埋点对接标准可以任务自动处理形成报表
3. 容器化基础应用K8S:服务发现与调度、负载均衡、服务自愈、服务弹性扩容、横向扩容、存储卷挂载等
4. SpringCloud生态:网关GateWay、负载均衡Ribbon客户端、断路器Hystrix、服务发现Eureka等
5. 分布式系统调用链zipkin、基础应用监测Cat监控等
6. 流量回流模拟压测
7. 容器监控:资源、宿主机CPU/内存/网络IO等、K8S事件、公网信息等监控
8. 应用监控:集群访问QPS95线等、服务调用依赖看板、问题排查(异常调用记录)、JVM信息(GC/线程)等
9. 业务监控:应用节点功能方法实时状态曲线表达、曲线异常波动告警
10. 流水线发布系统:自动贯穿开发、测试、预发、生产一键发布
基础运维(基础设施):
1. 资源采买配置:ECS、域名、带宽等
2. 运维基础:Mysql、Hbase、RabbitMQ、Redis、ZK、ES、LocalCache等功能和监控,以及资源池服务(不需要业务应用了解中间件的配置信息,并提供简单连接使用方法)
3. 域名与证书:域名(包含CDN)的申请、配置、投放使用、预警、切换、续费、监测等
4. 系统健康状态通知能力建设:如钉钉机器人、电话报警通知
中台提供将对象属性自动填充:
import cn.com.duiba.wolf.utils.TestUtils;
TestUtils.createRandomBean()
生成随机数:
int random = new Random().nextInt( 10 ) + 1;
主要抽象的业务中台服务如:
1. geo服务:根据ip、经纬度、geohash,转换成中国省市区编码和全球编码
2. 短信服务:抽象短信源,对接如阿里短信等不同厂商,实现高可用,区分验证码和高风险如货到验证码短信源,保障短信服务高可用
3. OSS服务:图片视频文件等资源上传下载、图片鉴黄、图片OCR识别、CDN刷新等处理
4. 权限管理:以多租户、组织、角色、个人等方式进行权限处理
5. 开发套件与框架(EXT基础包)、功能组件、消息推送等
6. 商品中心、支付中心、订单中心
抽象公司业务场景使用的中台能力,助效开发与迭代
1. 素材微服务:素材的自动生成、渲染、相似素材查询
2. 活动组装服务:活动的基础模块,可视化组装
3. 过滤推荐引擎服务,提供通用业务解决方案
4. 域名监测替换服务
5. 短链服务、短链多区域重定向服务
核心指标:请求、返回、获胜、曝光、点击 (站外)===>(站内) 活动、复参、券获胜、券点击、券转化、转化回收归因
消耗 = 请求数*返回率*获胜率*曝光率*广告CTR *活动唤起率*复参率*券获胜率*券CTR*券CPC
目标成本 = (站外ECPM*ROI)/(目标CVR*券CTR*复参率*广告CTR
核心指标:请求、返回、获胜、曝光、点击 、转化
消耗 = 请求数*返回率*获胜率*曝光率*广告CTR *券CPC
目标成本 = =(站外ECPM*ROI)/(广告CTR *目标CVR)
OCPX:OCPM、OCPC、OCPA,O意味着Obtain优化的意思,如目标广告主愿意花100元买1个转化
假设链路漏斗效率:1000个曝光==>(CTR10%)100个点击==>(CVR10%)10个转化
(1)OCPC=目标(100元/1转化)*效率(1转化/10个点击)=10元/点击=目标*10%效率
当前流量质量好,或超适配当前广告A,转化效率有可能达到2倍即20%(算法可以学习到),意味着CPC=10元不变时,广告主A花100元可以买到2个转化;但是由于当前流量是无数个可投广告中,算法竞价挑优出来的实际投放广告;所以广告A不一定竞价胜出,适当的提高单价能帮助广告获胜曝光,基于广告主原先目标,在不超过20元的情况下,广告主肯定乐意提高价格抢优质量的。比如出价1次15元获取一个20%转化效率的流量,肯定比之前出价2次10元一共20元,拿取之前效率10%的2个流量要效果好;同理,如果流量效率差只有5%,那么广告主只愿意花5元,这样10元就能买下2个点击(竞争不过就放弃,也不浪费预算),合起来效率还是10%,这样帮助广告主控制成本或者提价拿优质量,这就是OCPC,有利于广告主
(2)OCPM=(100元/1转化)*(1转化/10个点击)*(1点击/10个点击)=1元/曝光=目标*1%效率
当前流量质量差只有0.5%的效率,那么广告主只愿意花0.5元买本次曝光,能帮助广告主控制成本;但是就入口而言,当前入口素材还没有曝光,媒体的流量还没有售卖,媒体肯定存在内部竞价,如果有合适本次流量的其它广告主,他们肯定会出高价,这样就非常有利于媒体了;所以计费方式越靠前,越有利于媒体
PDB、PD:保价保量(退量比)Programmatic Direct Buying,优选购买 (保价不保量)Preferred Deal,都需要排期单,在流量金字塔中上游,下游还有RTB
ROI : Return On Investment , 投资回报率=(单均额X转化量)/ (CPAX转化量)
RTA:Real-Time API,api对接方式(对接平台为DSP,一般是提供人群画像等选择的流量)
RTB : Real Time Bidding,,实时竞价(对接平台为ADX,一般是广告主自己抉择流量)
RPM :RevenuePerMille,千次竞价收入 = ((广告消耗 / 1.15) - adx消耗) / 竞价返回次数 * 1000
SPM : send per mille , 广告位每展示1000次,可以产生的发券量(计算公式=发券量/(广告位曝光量/1000))
SSP : Supply Side Platform 供应方平台,服务于流量主的平台
TA:Target Audience,目标受众,简称TA浓度,某品牌投放计划中的TA为25~34岁女性,整个投放计划触达100万受众,投放后50万受众属于TA,则TA浓度为50%
PV:Page View;pv分流常见有随机数
pv分流:用户每一次行为决策都不受上一次行为的影响,比如抛硬币
uv分流:一般用户hash后处理,分层一般会添加唯一的不同盐值,合并hash离散用户后处理
public static Boolean randomTrueFlag(int ratio) {
if (ratio >= 100) {
return true;
}
// 0到99的随机数
int i = (new Random()).nextInt(1)*100;
// 通过率是1%,那么i只有随机到0,才能返回true
return ratio > i;
}
uv分流常见应用,Unique Visitor
private boolean coverUvSplit(Long userId, int ratio) {
if (ratio >= 100) {
return true;
}
int hash = HashAlgorithm.dekHash(userId.toString() + "唯一的关键盐值");
final int value = (hash < 0 ? -hash : hash) % 100;
return value >= 0 && value < ratio;
}
【参考】推啊起家:2014年5月起家帮助互联网企业提升运营效率,达到锦上添花作用的用户运营服务平台,兑吧集团成立。以免费积分商城的方式合作了4000+媒体APP。2016年末孵化的推啊,2017年基于四大核心要素,迅速起家,同年完成了月消耗1亿的小目标,四大核心点:移动互联网浪潮、兑吧积累的商务优势、媒体的非标位置(无同行竞争、CPS分成合作)、互动广告(沉浸式体验、决策促进酶,让素材展现能力弱的非标也能实现转化)
【参考】推啊主链路:入口素材推荐,点击5%>引擎推荐活动>活动访问,如大转盘加载==>40%活动参与,发券==>弹层出券,ad券曝光==>30%券点击,CPC计费==>落地页曝光/下载(ad曝光上报)==>10%落地页转化(ad回传,达标开启OCPC)
【参考】推啊定义的皮肤与活动:皮肤是素材展现的一种互动形式,如大转盘、套猫等模板;围绕皮肤配置的投放周期、参与次数、标签、奖品等属性构建的信息称为活动,如某个大转盘活动;常见的互动形式也区分轻互动和深度互动
常见的轻互动:刮刮卡、大转盘、割绳子、砸金蛋、摇奖机、翻牌子、大海捞金、扭蛋机、扯红包、拆快递、套娃娃、扭蛋机、
摇塞子、摇签、射箭、挖金矿、套兔子、卡包、吹气球、摇钱树、答题、炮击拿好礼、幸运大抽奖、聚合页、幸运翻牌、
福利天天送、集字摇奖机、幸运八连抽、集字摇奖机
常见的深度互动:种红包、天天果园、大富翁、挖矿,有用户沉淀,复参深度玩法的游戏
(1)统一动态参数的宏定义,便于第三方和媒体对接,以及监测排期。参数全大写,前后加双下划线__,统一宏定义如下:
__OS__, __IMEI__, __MAC__, __MAC1__, __IDFA__, __AAID__,
__OAID__,__DUID__, __IP__, __UA__, __TS__,__LBS,__GEO__
(2)参数优先级
对于 Android 系统,用户唯一标识的优先级顺序从高到低依次为: IMEI、OAID、MAC、AAID。
对于 iOS 系统,用户唯一标识的优先级顺序从高到低依次为: IDFA、MAC
推荐书籍:《计算广告》、《程序化广告》、《浪潮之巅》、《推荐系统实践》、《消费者行为学》、《参与感》、《体验经济》、《跨界》、《九败一胜:王兴创业十年》
1. DSP :需求方平台(Demand-side Platform),腾讯、今日头条
2. SSP/AdX :供应方平台(Supply-side Platform/Ad Exchange),腾讯、今日头条
3. DMP : 数据管理平台(Data-management Platform), 达摩盘、同盾科技
4. PCP : 程序创意平台(Programatic Creative Platform), 筷子科技、Sizmek
5. MAP :监测分析平台(Measure&Analytics Platform),百度监测、Admaster
6. 我司追随者:豆萌(HK01917)、互动推、章鱼、变现猫(我司裂变)
1. 开屏广告:又称闪屏广告。在 APP 启动时展示的一个占满全屏的广告。
2. 插屏广告:在内容与内容的切换间隙展示一个广告。
3. 焦点图:在首页或频道首页头部可以手动或自动循环翻页的一组图片或图文组中展示一个图片或图文广告。
4. 信息流广告(feeds,也称流内广告或原生广告):在信息列表中,以与相邻信息样式完全相同的形式展示一个广告。
5. 条幅广告(banner):又称矩形广告。以长方形形式展示的广告。通常出现在页面内,或者离开一个应用时。
1. 前贴片广告:在视频播放前展示的广告, 广告展示结束后自动播放视频。
2. 中插广告:在视频播放过程中,强行中止视频播放并展示广告,广告展示结束后自动播放视频。
3. 后贴片广告:在播放列表的最后一个视频结束后展示广告。
4. 暂停广告:在视频播放过程中,用户暂停时展示广告。
5. 视频角标广告:在视频播放中,在视频播放框中任意位置出现的不影响视频观看的小面积广告。
1. 广告曝光:曝光数 Impression,独立访客数UV(UniqueVisitor),频次 Frequency,广告可见的TA浓度 Viewable TA%,广告可见的TA到达率
2. 广告可视度:落地页/网站/APP访问,浏览数,停留时间,独立访客数,跳出率,二跳率
3. 用户互动:回搜率,点击率CTR,点击数Click,点击转化率CVR,点击到达率,互动率
1. 头部流量:头条系、腾讯系、阿里汇川等
2. 长尾流量利用:穿山甲、优量汇、软告、科大讯飞