admin管理员组

文章数量:1794759

兑吧开发规范《源Java手册》

兑吧开发规范《源Java手册》

1. 规范: 语法规范 1.1. 命名规范

  • 【推荐】业务属性:对象、方法命名,要使用携带业务属性的名词,比如AdvertVO;反例DeleteVO;除非单纯对该对象操作,而对象具备了名词意义,比如AdvertVO中包含方法,getAccountByVO(AdvertVO advertVO)
  • 【强制】代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式
  • 说明: 正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使是纯拼音命名方式也要避免采用。 正例: alibaba / tabao / youku / hangzhou / duiba / tuia 等国际通用的名称,可视同英文 反例: DaZhePromotion [打折] / getPinFenByName() [评分] / int 某变量 = 3
  • 【强制】对象中禁止使用 同名&大小写不一致 的字段
  • 说明: Java本身是支持对象中同名且大小不一致的字段,但是第三方工具对这种形式的命名不友好,甚至异常报错。例如fastjson、jackson、lombok等 正例: name & otherName / age & otherAge / sex & otherSex 反例: name / NAME
  • 【强制】类名使用UpperCamelCase风格,但 DO/BO/DTO/VO/AO/PO 等情形例外。
  • 正例: MarcoPolo / UserDO / XMLService / TcpUdpDeal / TaPromotion / Qrcode 反例: macroPolo / UserDo / XmlService / TCPUDPDeal / TAPromotion / QRcodeg 案例: DTO ==> DO 入库,不用严格执行,对于下游系统直接可以DTO入库(松懈) 强制: DTO作为RPC的载体,必须实现序列化,此外类属性杜绝使用 BigDecimal (原因自行百度) 中台部门ext包支持启动检索,在启动类Application的main方法第一行加入代码扫描指定文件夹: cn.duiba.wolf.utils.ClassUtils.checkDto("cn.tuia.activity.center.api.dto");
  • 【强制】系统交互单位处理(图片、视频大小),统一用 KB 进行交互和计算,如果涉及前端数据量过大需要转换成 MB 等,只是修改前端显示处理
  • 【推荐】前后端字段命名,接口交互采用swagger生成的接口文档导入yapi使用
  • 1. 兑吧yapi地址:yapi.dui88/ 2. 上传swagger==>【数据管理】==>开启url导入==>本地启动web系统 3. 导入swagger地址为本地ip如,172.16.61.240:17785/v2/api-docs 4. 运行可以sso_ticket:test和csrf_off:true绕过权限不要adminId信 5. yapi对于后端可以用来模拟请求,类似POSTMAN,对于前端有个Mock接口 1.2. 常量变量
  • 【强制】杜绝使用魔法值,尽量使用枚举;每个字段都要进行注释;任何常量、硬编码都要注释,当前意义和剩余的其他意义;字段的命名(尤其是大数据日志type类型)最好使用【元数据管理系统】data-dictionary,注册字段和意义,系统会自动生成枚举Jar包,然后升级工程依赖版本号,全司统一
  • 正例: @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方法 1.3. OOP规约
    • 面向对象6大原则: SRP、OCP、LSP、DIP、ISP、LOD
    • Single Responsibility Principle、Open Close、 Liskov Substitution、 Dependence Inversion、Interface Segregation、 Law of Demeter
  • 【强制】静态变量或静态方法类名直接调用,避免使用实例对象调用,造成无谓的加编译器解析成本,直接用类名访问即可。
  • 【强制】所有覆盖方法,必须加@Override注解
  • 说明: getObject() 与 get0bject() 的问题。一个是字母0, 一个是数字 0,加@Override可以准确判断是否覆盖成功。 另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错;类似的常量赋值时要用大写的L,反例如Long a = 2l和数字Long a = 21
  • 【强制】Object的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals
  • 正例: "test".equals(object) 反例: object.equals("test") 说明: 推荐使用java.util.Objects#equals(JDK7 引入的工具类) Objects.equals("test", "testNo");
  • 【强制】大坑!!!所有相同类型的包装类对象之间的比较,全部使用equals方法。
  • 说明: 对于 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远程调用失败,异常退出
  • 【强制】构造方法禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中
  • 【强制】POJO 类必须写 toString 方法。使用IDE中的工具 source> generate toString 时,如果继承了另一个POJO类,注意在前, 加一下 super.toString。
  • 说明: 在方法抛出异常时,可以直接调用 POJO 的 toString() 方法打印其属性值,便于排查问题;当然lombok也可以自己选择使用 @Override public String toString() { return ToStringBuilder.reflectionToString(this); }
  • 【强制】禁止在 POJO 类中,同时存在对应属性的 xxx 的 isXxx() 和 getXxx() 方法
  • 说明: 框架在调用属性 xxx 的提取方法时,并不能确定哪种方法一定是被优先调用到 1.4. 类中代码
  • 【强制】private方法使用: 只在类中调用的方法,定义为private
  • 【推荐】public方法使用: 方法要用接口继承
  • 【推荐】单个方法总行数不超过80行
  • 说明: 除注释外,方法签名、左右大括号、方法内代码、空行、回车及任何不可见字符的总行数不超过80行 正例: 代码逻辑分清红花和绿叶,个性和共性,绿叶逻辑单独成为额外方法,使主干代码更加清晰; 共性逻辑抽取成共性方法,便于复用和维护 1.5. 常用方法
  • 【推荐】 List 判空,使用 apache 的 CollectionUtils
  • import org.apachemons.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()); }
  • 【推荐】 Map 判空,使用 apache 的 MapUtils
  • import org.apachemons.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");
  • 【推荐】 String 判空,使用 apache 的 StringUtils
  • import org.apachemons.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.6. 代码坑点
  • 【参考】代码的时间处理,为什么我的眼里常含泪水?
  • 说明: (1)月份比实际数字少1即(0-11,5月获取到的数字是4) (2)当按年与周一起用时需注意年底与年初的周数 (3)java默认周的第一天是sunday,在中国Calendar获取的weekday(周一获取是2,周日获取是1) (4)时区问题,检查系统的默认时区 (5)@RefreshScope 使用cglib代理,注意默认构造 (6)禁止线程间传递HttpServletRequest,HttpServletRequest在请求调用时由tomcat创建,在响应返回时会被tomcat销毁; 若异步子线程传递使用,会出现主线程执行完成销毁HttpServletRequest,子线程报错 www.jb51/article/158192.htm blog.csdn/m0_38039437/article/details/76229486 (7)RPC/restful接口的出入参对象一定要实现序列化
  • 【参考】HttpServletRequest千万不要传入到子线程中使用。它是请求调用时由tomcat创建,在响应返回时会被tomcat销毁
  • 【参考】生产使用,尤其是C端投放(高响应场景)很少用深拷贝,BeanUtils.copy或者set基本上都是浅拷贝。浅拷贝注意线程安全问题,其中将取出的属性对象,可以set替换,但不能修改,会污染原数据。
  • 1.7. 集合处理
  • 【强制】不要在foreach循环里进行元素的remove/add 操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁
  • 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缓存取出的也会产生竞争
  • 【强制】 慎用Collectors.toMap,key重复或者value为空会报错;使用toMap时一定要添加(oldVal, newVal) -> newVal)处理重复key;value判空一定要处理好(尽量不要用,value处理很麻烦)!!!一定要空值过滤,单独对象判空:.filter(Objects::nonNull) 注意如果属性为value,属性也要判空
  • 说明: 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 是对象本身的话应该会减少出错的概率,返回类型不为空了
  • 【强制】JDK7以上,Comparator 要满足以下3个条件,不然 Arrays.sort, Collections.sort 会报错 异常。
  • 说明:三个条件如下 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); 1.8. 控制语句
  • 【强制】switch块内,每个case通过break/return来终止。switch必须包含一个default语句并且放在最后,即使什么代码也没有
  • 【强制】控制语句杜绝使用单行代码,必须使用大括号。( if / else / for / while / do )
  • 反例: if(condition) statements;
  • 【推荐】在表达异常分支时,尽量少用if-else方式: 这种方式可以改写成:
  • 正例: 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; } 说明: 卫语句就是把复杂的表达式拆成多个条件的表达式,条件为真时,立马返回给调用方。 卫语句的好处是条件表达式之间相互独立,不会干扰。
  • 【推荐】高性能:循环体内尽量不要用try-catch操作,可以在循环体外进行;具体示例具体分析如B端、定时任务,for循环发送邮件,就需要在循环体内try-catch,不要因为1个失败导致全局失败。
  • 【推荐】避免使用反逻辑
  • 说明: 反逻辑不利于快速理解,并且取反逻辑必然存在对应的正向逻辑写法 正例: 使用 if ( x < 628 ) 来表达 x 小于 628 反例: 使用 if ( ! ( x >= 628 )) 来表达小于628 1.9. AOP思想
  • 【参考】AOP(Aspect Oriented Programming),面向切面编程,核心就是不入侵代码完成相关业务,常见的使用场景如:日志、监控、95线、权限、过滤、拦截等,具体在下文设计模式中;AOP思想应用很广,比如我们正常工作中,之前登记了个人身份和银行卡信,财务可以在不打断我们工作的情况下在每月9号的切点上,自动给我们打款;核心就是在不入侵主链路的基础上,完成一些横向的工作。
  • 说明:由动态代理实现,主要两种:jdk动态代理和cglib动态代理,核心是新的代理类执行方法, 这里本类调用的public方法无法切面,除非自己再把自己注入再调用(太low,不推荐)或者使用AopContext .currentProxy()获取当前类代理对象(存在内存泄漏的可能,不推荐) 1. jdk动态代理是由java内部的反射机制来实现的(spring默认),cglib动态代理底层则是借助asm来实现的但是需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理(面向接口编程没有该问题) 2. jdk反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效 3. jdk动态代理的应用前提,必须是目标类基于统一的接口(局限了,但我们一般用它) 2. 规范: 业务规范 2.1. 代码注释
  • 【推荐】代码注释,可个性化定制好代码注释模板。最好有核心信,如需求文档地址
  • /** * 定制简介:在元业务逻辑0积分兑换不发送扣积分请求的基础上, * 增加白名单,给白名单中开发者额外发送0积分扣积分请求 * 需求链接:cf.dui88/pages/viewpage.action?pageId=22296558 * 负责人:刘凯 * 代码上线时间:2022年05月30日 * 代码下线时间:20XX年XX月 */ if (order.getCredits() == 0 && !zeroCreditsSendDingzhi(order)) { return true; } 2.2. 入参校验
  • 【参考】 下列情形,需要进行参数校验:
  • (1)调用频次低的方法,如B端财务这种 (2)执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数操作导致中间执行回退,或者错误,那得不偿失。 (3)需要极高稳定性和可用性的方法 (4)对外提供的开放接口,不管是否为RPC/API/HTTP 接口 (5)敏感权限入口
  • 【参考】下列情形,不需要进行参数校验:
  • (1)极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求 (2)底层调用频度比较高的方法。毕竟像纯净水过滤的最后一道,参数错误不太可能到底层才暴露问题。 一般DAO层与Service层同一个应用中,并部署在同一台服务器中,所以DAO的参数校验可以忽略 (3)类中的private方法,自家兄弟,老爹public肯定校验过了 2.3. 日志检索

    1.【推荐】SSO日志:对于B端管理关键业务操作,特别是修改和删除,增加sso操作日志,方便追踪回溯

    说明:可以在SSO查看日志:sso.duiba/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.【推荐】阿里云日志:实时日志信聚合可以通过阿里云查看,如输入广告订单。阿里云地址:sls.console.aliyun/lognext/project/credits/logsearch/tuia_launch_log

    2.4. 配置引擎
  • 【参考】米莉亚配置的Apollo,一般需要先发布,后单台刷新,报警监控日志确认,最后全量刷新;配置出错导致的报警可以回滚配置,或者禁用当前机器流量,(存在配置割裂问题)
  • 【参考】B端需求迭代应该基于角色,而不是基于个人;前者如媒体财务、运营;后者会随着人员迭代而积累成平台的痛点问题;平台非角色化普遍性的功能(不是40%+以上的人员或者配置项用到的功能)都通过配置引擎进行迭代,减少理解成本;配置引擎为【盘古Apollo】,支持自定义、账户、广告位、活动、广告、皮肤,(回收所有定制化、个性化配置)
  • 2.5. 广告线常用
  • 【参考】广告查看出券订单详情15日内: manager.tuia/order/queryDetail?tuiaOrderId=taw-399981509100658
  • 2.6. 流量线常用
  • 【强制】域名配置,所有的域名上线必须https,且配置引擎engine和活动activity域名,配置完访问如: activity.tuia
  • 3. 规范:流程规范 3.1. 研发规范
  • 【强制】理解需求本质,研发可以暂时理解不了价值,但是千万不要起争执(怎么妥善处理也是owner能力的体现);研发的底线:你不用管需求价值,干活就好了;就算是工具,我们也是有思想,有自我的工具
  • 3.2. 数据规范
  • 【参考】数据与业务达成一致:
  • (1)二次加工会议确认:读写分离的原则上,最好一次回流到位 回流数据涉及业务查询再次计算整合加工的,需要会议提前确认并告知@张攀@永坤 (2)表结构业务研发负责,数据只回流生产: 数据回流表结构DDL,业务研发负责,生产、非生产环境,字段修改 (3)业务部:单行数据可适当业务研发计算: 数据回流提前确定字段,但如果后期发现需要CTR(最好回流好),表中已有点击C和曝光S,业务研发可自己计算CTR=C/S (4)数据部:多行数据加工回流,工程业务不加工:如3日日均,7日统计(每行都有个字段如 per_uv) 3.3. 测试规范
  • 【参考】测试核心是对产品交付前的验收打磨,是产品生命周期中最重要,也是最后一环;测试重点在常用功能和数据一致性问题上;但边界值、暴力测试、性能测也是重要一环(这是品质的区别);测试用例的设计也是测试能力体现的重要一个环节,测试的本质需要打破砂锅到底的心态,如测试某个位置要求投放90X90的JPG图片,不仅仅是打开能看就可以了,还需要下载到本地,真正打开属性查看图片规格和尺寸
  • 用例设计方法:逐级细分法、域测试法、输出域分析法、正交试验法、业务流分析法、状态迁移法、判定表法、因果图法、错误猜测法 逐级细分法是对被测对象进行细分,当被测对象有多种特性或参数,各特性或参数之间可能有依赖关系并且需要选择不同参数取值的组合时适用,由于考虑的因素较多使用相对复杂 正交试验法适合被测对象输入多组合多,无法全部测试所有组合时使用,与其他方法相比能够有效合理的减少测试用例数量。当各输入之间有特殊关系,需要选择是否组合时可以用逐级细分法 因果图和判定表两种方法充分考虑了输入条件间的组合和约束关系,避免了无效用例,并且同时得出了预期输出。因果图可理解为判定表的前置过程。简单的或输入输出非常明确的系统,可单独使用判定表;但是当被测对象输入较多时,用例规模会非常庞大。输入之间的关系不能明确是否确实需要进行组合时,容易产生冗余 错误猜测法依靠直觉和经验设计用例,经验越丰富使用该方法的效率越高
  • 【参考】自动化测试覆盖主链路。评估产品需求,开发影响面时,人力一定有评估不到的地方,自动化测试是解放全面覆盖的的劳动力问题,是测试提高生产价值的核心力量
  • 3.4. 日志打印
  • 【强制】日志打印尽量精简,尤其是C端投放侧QPS高,如果日志过多会造成磁盘压力;B端核心链路QPS低,尽量保证日志。C端而言,日志采用非生产环境全打(生产环境不打info,真正有问题可以开启全打)
  • 正例: logger.info("LandPage.addAdvert落地页审核报错id为: {}。。。", id); 查看日志级别:10.104.12.3:17789/loggers QPS高的系统,一定要做好info日志收敛 import org.apachemons.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);
  • 【参考】中台底层对日志长度进行了,限制: duiba.logger.size-limit.error-threshold ,可查看中台ext包
  • 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);
  • 【强制】warn、error日志处理,要及时处理,可以平台日志【告警配置】超过一定阈值报警到群里, 重复N次会打电话给系统owner,保障系统的高可用
  • 3.5. 异常处理
  • 【强制】 SpringMvc框架异常大坑!!!自定义异常一定要继承RuntimeException,不要继承Exception!!!捕获不到的,异常抛到 Controller 层的时候,Controller 处理 RequestMaping会被解析成RequestHandller,方法invoke操作的时候,进行以下捕获异常操作了
  • 说明: 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); } 4. 安全与性能 4.1. 用户与权限
  • 【强制】用户敏感数据禁止直接展示,必须对展示数据进行脱敏
  • 说明: 个人手机号码会显示为132****0813,隐藏中间4位,防止泄露个人隐私
  • 【强制】用户请求传入的任何参数必须做有效性验证,忽略可能导致以下问题
  • (1)page size 过大导致内存溢出 (2)恶意order by 导致数据库慢查询 (3)任意重定向 (4)SQL注入 (5)反序列化注入 (6)正则输入源串拒绝服务ReDos
  • 【强制】在使用平台资源时,比如短信,邮件,下单,必须实现正确的重防限制,如数量限制、疲劳度控制、验证码校验,避免被滥刷,资损。
  • 4.2. 高并发处理
  • 【强制】高并发场景,避免使用“等于”作为判断中断或退出的条件
  • 说明: 如果并发控制没有处理好,容易产生等值判断被“击穿”的情况, 应使用大于或小于的区间判断条件来代替。 反例: 判断剩余奖品数量等于 0 时,终止发放奖品, 但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止。
  • 【参考】高并发常用措施
  • 1. 动态资源和静态资源分离;CDN;客户端承载计算(如抢红包可客户端先随机) 2. 网关限流、负载均衡 3. 本地缓存(过期刷新)、二级缓存、redis缓存(防止热点数据问题) 3. 分布式缓存:缓存雪崩、穿透、缓存更新、预热、降级、双写数据一致性 4. 分布式部署,方便横向扩容(单机调整权重,压测瓶颈性能)、分布式全局ID、分布式Session 5. 数据读写分离或数据切分(垂直或水平) 6. 高并发最主要通过QPS压测、容器线程调优、火焰图CPU、GC日志等分析解决单机瓶颈问题;工具(阿里云或脚本)压测,结合95线监控,曲线监控等解决集群的瓶颈问题(中间件、数据库、高时间复杂度如list.contains改成set.contains等) 4.3. 时间复杂度
  • 【推荐】读写问题,计算尽可能发生在写操作,读数据的时候尽可能快且少计算(或者缓存数据与计算)
  • 正例: (1)hashMap.contians(key) (2)hashSet.contains(key) (3)将计算结果进行缓存,不要取出缓存后再计算 反例: list.contains(key) 4.4. 系统与安全
  • 【参考】缓存穿透是一个常见问题,我们需要把 null 也作为一种缓存结果,否则此类情况等于缓存是失效的。如使用redis做二级缓存时,一般设置有key、value、TTL,其中value中会设置refreshTime、trueValue等,这样就有效避免了缓存击穿问题
  • 4.5. 性能分析Arthas
  • 【强制】arthas火焰图,分析性能采用,强大的Arthas请自行了解,如dashboard看
  • 1. 安装:curl -O 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
  • 【参考】优先使用Arthas查看出入参
  • 上述已经执行过,进入arths界面了 tt -t cn.duiba.tuia.service.impl.EngineServiceImpl queryAdvert tt -l tt -i 1001 查看入参: watch cn.duiba.tuia.ssp.center.api.remote.RemoteProduceDayBillService selectNotUploadedSlot "{params,returnObj}" -x 5 此外,也可以这么进入arths,但是慎用,先测试环境自己确认: curl -L alibaba.github.io/arthas/install.sh | sh ./as.sh pid
  • 【参考】对于系统的耗时情况,可以预发环境多次trace,查看主要耗时的方法,生产采用DBTimeProfile,或Cat监控参看分析;
  • 说明:阿里云已经封装好了trace的方法,1代表进程1,可以jps查看 (1)curl -sLk ompc.oss.aliyuncs/greys/install.sh|sh (2) ./greys.sh 1 (3)查看方法耗时,小心,一定要用完立刻退出,会阻塞线程,吃掉CPU,万一吃掉记得控制【禁用容器】 trace cn.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操作,修改成批量查询。 4.6. 线程池多线程
  • 【参考】提高单机响应,可串行改并行,无上下文依赖的逻辑可以并行处理,常见如:数据组装、消通知、关联业务处理
  • 5. MYSQL数据库
    • 底层数据库的规范有助于减少软件实现的复杂度,降低沟通成本,
    • 本章主要说明建表规范、索引优化准则以及ORM层的处理约定
    5.1. 建表规约
  • 【强制】表达是与否概念字段,必须用 is_xxx 的方式命名,数据类型是 unsigned tinyint(1 表示是,0表示否)
  • 说明: 任何字段如果为非负,则必须是unsigned。 注意: POJO 类型中的任何布尔类型的变量,都不要加 is 前缀。 所以需要在<resultMap>设置从is_xxx到XxxFlag的映射关系。数据库表示是与否的值,使用tinyint类型。 正例: 表达逻辑删除字段名 is_deleted ,1表示删除,0表示未删除,默认为0
  • 【强制】表名、字段名必须小写: 【字母打头】【下划线】【数字】【禁止2下划线中间出现数字】
  • 说明: 数据库字段名修改代价很大,无法进行预发布,会锁表的!!!! 正例: aliyun_admin , rdc_config , level3_name 反例: AliyunAdmin , rdcConfig , level_3_name
  • 【强制】 表名不使用复数名词
  • 说明: 表名应该仅仅表示类里面的实体内容,不应该表示实体数量,对应DO类名也是单数形式,符合表达习惯。
  • 【强制】禁用保留字段,如 desc、range、match、delayed、admin等,请参考Mysql官方保留字
  • 【强制】主键索引pk_字段名,唯一健索引名为uk_字段名,普通索引名为idx_字段名
  • 说明: pk_即 primary key; uk_即 unique key; idx_即index的简称
  • 【强制】小数类型为decimal,禁止使用 float 和 double
  • 说明: 在存储的时候 float 和 double 存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。 如果存储的范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储
  • 【强制】varchar可变长字符串,不预先分配存储空间,长度<5000个字符,差不多长度用char。 如果长度大于此值,则应该定义字段类型为text,独立一张表,主键对应,避免影响其他字段索引效率
  • 【强制】表的三剑客: id、gmt_create、gmt_modified
  • 说明: 其中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='表的名称';
  • 【推荐】当单表行数超过 500 万行或单表容量超过 2 GB 时,才推荐进行分库分表
  • 【强制】推啊数据库新增字段,一定要在最后面进行添加,不要为了美观修改位置,会影响数据采集处理,造成可怕的后果
  • 5.2. 索引规约
  • 【强制】超过三个表禁止join。需要join的字段,数据类型必须绝对一致; 当多表关联查询时,保证被关联的字段需要有索引。
  • 说明: 即使双表join也要注意表索引、SQL性能。
  • 【强制】 在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
  • 说明: 索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用 count(distinct left(列名, 索引长度) / count(*)) 区分度确定
  • 【强制】页面搜索严禁左模糊或者全模糊,如果需要请通过搜索引擎来解决,意思是PK产品需求(除非数据量少)
  • 说明: 索引文件具有B-Tree 的最左前匹配特性,如果左边的值未确定,那么无法使用此索引
  • 【推荐】排序问题,尽量不要内存排序;如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
  • 正例: 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放在索引的最前列。 5.3. 常用SQL
  • 【参考】打印Mysql日志,配置application中加: logging.level.duiba.tuia.core.biz.dao=debug
  • 【参考】sql语句获取id进行拼接,比如获取所有的idList
  • SELECT group_concat(id separator ',') FROM(SELECT id FROM `tb_material` WHERE `ms_id`= -1 ORDER BY `gmt_create` DESC LIMIT 20) a 5.4. Hive常用SQL
  • 【参考】hive查询一定要分区,,也就是【dt = ‘2020-10-28’】 这种,生产红线!常用的一些函数,比如like百分号
  • 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 (。。。 5.5. SQL优化
  • 【参考】慢SQL优化拉取:登录DMS->工作台->权限申请->实例-性能;免登录实例找到需要操作的库实例->双击选择性能;跳转到新的页面->慢日志;慢日志分析主要从索引和数据量级维度。
  • 6. 工程结构 6.1. Controller
  • 【强制】新创建项目可以自动创建脚手架,米莉亚上统一处理
  • 【强制】接口入参对象,做好注释方便swagger维护
  • @ApiModel("广告主秘钥分页查询参数") public class AdvertiserSecretKeyQueryParam extends BaseQueryReq { @ApiModelProperty("广告主id") @NotNull(message = "不能空!!!!") private Long advertiserId; @ApiModelProperty("广告主名称") private String advertiserName; 。。。
  • 【强制】list的列表查询,需要按模板样式进行,包括分页和查询条件,除非是单id查询,否则全部采用包装类
  • /** * 分页查询广告列表 */ @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); } }
  • 【强制】list列表查询的参数,要包装类且继承ByDateQueryReq.class,并复写toString方法(固定格式)
  • 说明: 统一代码样式,已经造好的轮子就不要继续造轮子了 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); }
  • 【参考】@CanAccess,管理控制才能访问的接口,浏览器也能,可以避免系统授权拦截请求,但是注意该功能会使得无法获取用户信,adminId之类的
  • 5.【参考】子线程复用Htttp请求的参数BUG。Controller中的HttpRequest对象,如果异步处理需要将对象中的数据取出,否则会在主线程结束的时候,destroy掉,这样就会导致系统NPE

    6.2. Service
  • 【强制】Service一定要且继承实现添加@Override,内部方法调用一定要用private方法,外部方法一定要用public方法,且继承实现
  • 6.3. Dao
  • 【推荐】数据库查询参数使用包装类型,避免使用Map,规范,且方便后续同事的扩展和检索
  • 6.4. RPC
  • 【强制】RPC调用,对象属性不准使用BigDecimal,hessian序列化会将其变为0,需要升级hessian版本到4.0.63以上,还要很多其他操作,而且DTO一定要实现序列化

  • 【强制】RPC调用必须用包装类,Query对象采用中台提供的BeanUtils进行copy

  • 正例: import cn.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 '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; 。。。
  • 【强制】禁止使用枚举在RPC接口!!!扯犊子,增加耗时还,具体就不说明了
  • 6.5. 消RocketMQ
  • 【参考】MQ用于异步、解耦、削峰,常见的如阿里开源RocketMQ(Erlang)和大数据多副本的Kafka(Java/Scala)
  • 【推荐】目前业务所使用的RocketMQ,默认使用的是集群消费CLUSTERING(可以定制广播 BROADCASTING);
  • 生产环境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 6.6. 分布式Redis
  • 【强制】redis使用禁止使用keys,慎用一系列O(n)的操作;记住redis是hash结构,就算集群环境也要小心热点key,热点key进行hash永远在一台机器上,阿里云的集群redis就出现过雪崩,它也是单机代理Proxy组成的集群
  • (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 } }
  • 【参考】采用redis作锁用户秒杀场景,可以通过双重TTL;类似的二级缓存也采用双重TTL理念;如结构3要素中:key、value、TTL,其中value中还包含refreshTime、trueValue,这样也能有效避免value为null的缓存击穿问题
  • 6.7. 本地缓存Guava
  • 【推荐】基本配置:Guava缓存要设置容量与上限;最好 refreshAfterWrite 和 expireAfterWrite 一起用(具体业务场景具体分析),refreshAfterWrite注意是懒加载的;而expireAfterWrite是为了防止懒加载导致低频率缓存过了几天后,第一次取到旧的数据;Guava缓存有load和reload,reload方法采用的线程池是取自共享线程池,要合理设置和使用;注意reload的降级监控报警
  • 正例: import java.util.concurrent.ExecutorService; import com.googlemon.util.concurrent.ListenableFuture; import com.googlemon.util.concurrent.ListenableFutureTask; import com.googlemon.cache.CacheBuilder; import com.googlemon.cache.CacheLoader; import com.googlemon.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
  • 【参考】key与value使用:key可能存在多个,可以使用字符串、下划线拼接;value注意使用不能为null
  • 参考: 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]);
  • 【强制】共享变量:Guava缓存对象取出使用是共享的,使用有可能需要copy:比如new一个list,然后执行list.addAll(),进行过滤增删操作;如果涉及到【改】还要进行深复制操作(生产几乎不用)
  • 说明: (1)如广告线广告的全量过滤逻辑,就是循环list[A、B](举例)然后浅拷贝对象成list2[C、D], 注意A和C是不同对象,hash地址是不同的;但是A的属性 idsSet 比如一个Set<Integer>和C有相同的地址; 所以C的idsSet属性只能进行替换操作setIdsSet(新的Set),而不能取出来属性后进行修改(会修改源数据) (2)还有流量线之前有一个guava缓存中取list,然后对list进行了remove操作, 采用了正规的迭代器删除,还是报错 ConcurrentModificationException ,就是因为共享变量问题。这里只要list.addAll()就能解决
  • 【强制】DB数据变更MQ通知:配置修改后,需要广播通知所有机器缓存,否则会引发侧漏问题,可以在缓存所在类中暴露一个public方法提供刷新refresh使用。本地缓存刷新的时候,如果刷新的数据量大,如List,要小心缓存击穿引起的【MYSQL崩溃】,要做好二级缓存;也可以类似先A生产,集群消费1台B进行本地缓存、分布式缓存更新,完成后B广播消费进行更新数据

  • 【参考】本地缓存,非生产环境提供了清除缓存功能:curl localhost:17792/monitor/cache/clearAll

  • 【参考】集群中大量使用本地缓存,DB修改后,如果通过MQ进行广播refresh实现数据的一致性,要小心缓存击穿引起的DB压力过大。常见的优化通过二级缓存进行处理,二级缓存也要防止击穿。可以通过如先集群消费确保数据更新,后广播同步刷新;或者广播时,针对如批量List数据刷新,在消费端可以进行List数据重排,更新二级缓存,减轻DB压力

  • 6.8. 链路思想zipkin
  • 【参考】info日志链路化,info日志可以通过traceId进行关联
  • 链路化思维要具备traceId的关联,可以在日志平台查看info日志: 比如1,2,3,4,5行为关联,缺失了4就知道是4的问题:dpaTes* and traceId: c2b2822351af75ac
  • 【参考】系统链路上下游RPC,日志通过traceId进行关联,traceId是按照一定采样频率进行采样处理的;Apollo可以配置:spring.sleuth.sampler.percentage,然后查看系统调用关系
  • 1. 米莉亚搜索日志:exportable: true;然后查看: console.dui88/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 新浪 6.9. 降级熔断Hytrix
  • 【参考】熔断、外部调用我司服务的时候要求150ms响应,我司Controller进行熔断处理限定100ms(预估北京到杭州来回的物理距离,网络传输)
  • import comflix.hystrix.contrib.javanica.annotation.HystrixCommand; import comflix.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
  • 【参考】超时熔断配置,用 a-web 调用 b-center 的 remoteService.method1 的客户端超时配置
  • 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用此):hystrixmand.default.execution.timeout.enabled=false 关闭hystrix异常后熔断短路:hystrixmand.default.circuitBreaker.enabled=false 6.10. SpringCloud
  • 【参考】配置中心,其实就是一个静态资源,加载到系统中
  • 健康检查:curl localhost:17793/monitor/check 配置中心的访问,如:configserver.dui88/tuia-activity-web/dev
  • 【参考】容器注册与健康检查,中台配置K8S健康检查180s一次,如果ping不通后续会重启该容器
  • (1)eureka地址:eureka.duibatest/ (2)健康检查:curl localhost:17793/monitor/check
  • 【参考】网关路由:用户==>公网==>域名解析DNS==>阿里云SLB==>NGINX==>网关==>K8S应用;RTB实时竞价中有个案例,爱奇艺【北京】要求我司
  • 6.11. 容器Linux/K8S
  • 【参考】容器中调用指定接口: 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
  • 【参考】容器内的代码是可以查看的,可以jar -xvf 指定jar包;然后javap -verbose XXX.class
  • 【参考】全仓库查代码,可以在代码服务器git或者本地文件夹执行命令:find . -name “*.xml” | xargs grep "drop "
  • 7. 工程辅助 7.1. 米莉亚Apollo
  • 【推荐】 服务治理==>参数配置管理,配置完后可以在项目中实现热刷新
  • 说明: (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等等。 //...... }
  • 【参考】配置的修改如Hbase、数据库迁移、地址处理(能不修改连接信、账号、密码最好),这些信最好配置化,可索引查询
  • 找中台哨兵、刘瑶进行检索(后期会功能化,直接Apollo就可以了),不要以为就几个系统ABC用到了该数据库;如果迁移完发现还有系统F,那就是【生产事故】了;apollo_prod库 SELECT DISTINCT namespace.AppId FROM item,namespace WHERE item.Value like "%advert_statistics%" AND namespace.Id = item.NamespaceId 7.2. CAT监控使用
  • 【推荐】Cat监控日志,查看统计、QPS与耗时;注意入参变量为final类型的(语法糖);监控返回值为空需要return null
  • 说明: (1)下列方法中: obtainAdvertType 是文件夹区分,后者是文件中的子类,抛出 Throwable 异常 (2)在miliya==>监控平台==>应用监控,查看对应系统 import cn.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")
  • 【推荐】业务监控,关键业务要进行监控,可以监控QPS与业务异常(曲线图,每个系统都有自己封装的,比如web的CatUtils.log)
  • 说明: (1)添加代码,发券异常监控:CatUtil.catLog("obtainTime/中文也行"); (2)然后在cat3中进行配置(侯文): console.dui88/cat/s/config (3)最终可以在miliya==>监控平台==>业务监控==>查看对应系统===>报警等配置 重点: Cat监控进行了分组6位数字(如100101),前3位是组号100,后3位是组里面的个项101
  • 【推荐】关键业务要进行耗时监控(中台设置默认500ms打印),可以在miliya==>服务治理==>日志, DBTimeProfile timeout 502ms, 检索条件为,DBTimeProfile and exportable: true
  • @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
  • 【推荐】系统调用链路分析: miliya==>服务治理==>分布式调用链,选择服务名,才可以选择span名称
  • 说明: 可以查看调用的链路与耗时情况: 比如可以查看: tuia-activity-web的activity/dojoin ==>redis 1ms ==> geo-server 1ms ==> activity-center /remoteActivityService/selectByActAndUserType 2ms ... 注意:(1)每次都要选择服务名才能使用,名称过长有可能检索不到,可以适当删除后面的文字 (2)也可以通过traceId检索,米莉亚日志==>检索,但是要采样到才可以使用==>过滤条件exportable: true 7.3. SonarQube
  • 【推荐】代码上传后,SonarQube 可能会commit报错,需要解决报错可以加入忽略规则 @SuppressWarnings(“squid:S3776”)
  • @SuppressWarnings("squid:S3776") public void catAccessLogAndFixParamMap(HttpServletRequest request) { // 很多if..else等,无法避免的 } 注意:sonar是常规各公司使用的代码检查工具,主要是帮助大家写代码的时候:(1)抓住核心;(2)代码逻辑不会太复杂,行数不能太多 大家写代码的时候有些人喜欢:如果怎么样。。。然后怎么样。。。如果怎么样。。。然后怎么样。。。,这样没有重点。 应该是:一、怎么样A;二、怎么样B;三、怎么样C;可能二和三之间会加入,如果怎么样。。。然后怎么样;这是卫语句写法(guard clause) 7.4. ParallelStream
  • 【强制】 ParallelStream 要使用自定义的 ForkJoinPool ,注意共享变量处理要使用线程安全的。
  • 说明: (1)阻塞: 并行流使用要计算QPS,比如执行耗时20ms,系统单线程理论支持50qps, 如果系统qps增加为60qps,将有10个qps阻塞 (2)严禁公用 ForkJoinPool,容易造成死锁和超长队列等待 (3)耗时<20ms,或共享变量修改过多,禁止使用 ParallelStream (4)线程池大小不能超过CPU核数,公式 private static ForkJoinPool forkJoinPoolSimpleFilter = new ForkJoinPool(4); // 并行流执行过滤 try { ForkJoinTask<List<AdvOrientationItem>> 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<>(); } 7.5. tomcat
  • 【参考】tomcat线程数修改:server.tomcat.max-threads 800到1000都是OK的,修改完可以在【应用监控】==>JVM信,查看tomcat;修改完可以配置修改权重为0重启容器查看
  • 7.6. JVM与JNI
  • 【参考】算法涉及TF(tensorflow)模型的问题,如果出现非JVM内存的OOM,或者JVM与物理机交互的JNI问题,需要进行一定的配置,如:
  • 配置环境变量,限制JNI问题:env |grep MALLOC_ARENA_MAX 显示:MALLOC_ARENA_MAX=4,文献:cloud.tencent/developer/article/1054839
  • 【参考】中台Apollo配置==>可以:查看配置信、GC实时分析(下载GC日志,jstat -gcutil)、内存分析(jmap -histo)、线程分析、调整权重
  • 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)
  • 【参考】 jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
  • (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 ,类似,具体还有很多命令,可以自行检索
  • 【参考】 jmap -histo ${vmid} ,查看内存中加载的实例个数和大小等,class name中出现业务对象基本上就是有问题的
  • 【参考】 jstack ${vmid} , 打印Java进程堆栈信
  • 重点关注搜索:死锁,Deadlock、等待资源,Waiting on condition、等待获取监视器,Waiting on monitor entry、阻塞,Blocked 其次关注:执行中,Runnable、暂停,Suspended、对象等待中,Object.wait() 或 TIMED_WAITING 停止,Parked 【说明】 如Waiting on condition,可以根据线程pid,检索到什么线程阻塞了
  • 【参考】Linux服务器,查看Java程序的CPU
  • 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 7.7. idea与git等
  • 【参考】 idea启动报错,Command line is too long
  • (1)修改项目下 .idea\\workspace.xml, (2)找到标签,然后添加属性: component name="PropertiesComponent" <property name="dynamic.classpath" value="true" />
  • 【参考】 lombok问题,无法正常用
  • 说明:可从以下问题排查 (1)jar包 or 插件安装lombok (2)设置setting==>搜索Annotation 勾选, Enable Annotation Processing (3)Java Compile 配置错误,配置成了 Eclipse,改成Javac
  • 【参考】 lombok问题,正确使用@Data,注意告警Generating equals/hashCode
  • 说明:子类实体的equals/hasCode方法无法继承父类属性 (1)实体类添加:@EqualsAndHashCode(callSuper = true) (2)或者lombok.config配置文件添加配置 config.stopBubbling=true lombok.equalsAndHashCode.callSuper=call
  • 【参考】Lombok属于编译期的优化,不影响生产性能,使用请遵循规范
  • 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的内存
  • 【推荐】Windows快捷键Idea使用,常用推荐
  • 优化导包: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 新建一行
  • 【推荐】Mac快捷键Idea使用,常用推荐
  • 优化导包:⌘ + ⌥ + O 提取变量:⌘ + ⌥ + V 返回至上次:⌘ + ⌥ + left/right
  • 【参考】idea常用比较好的插件
  • (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 “你的用户名 8. 中台服务 8.1. 平台架构
  • 【参考】平台架构跟业务完全无关,是保障公司业务运行的基石,主要分底层架构和基础运维
  • 底层架构(基础中间件): 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.duiba.wolf.utils.TestUtils; TestUtils.createRandomBean() 生成随机数: int random = new Random().nextInt( 10 ) + 1; 8.2. 业务中台
    • 可标准化对接,可规模化应用,可降低边际成本,非定制化的需求
    • 中台的基因决定了可以形成组合式创新,如阿里孵化的盒马,但是无法承担颠覆式创新
    • 星盟台,实现的是碎片化的需求,标准化交付,适合于平台型公司,让合作方产生持久化的依赖
  • 【参考】业务中台1:首先包含轻量级别的服务抽象,解决企业级别重复造轮子的问题
  • 主要抽象的业务中台服务如: 1. geo服务:根据ip、经纬度、geohash,转换成中国省市区编码和全球编码 2. 短信服务:抽象短信源,对接如阿里短信等不同厂商,实现高可用,区分验证码和高风险如货到验证码短信源,保障短信服务高可用 3. OSS服务:图片视频文件等资源上传下载、图片鉴黄、图片OCR识别、CDN刷新等处理 4. 权限管理:以多租户、组织、角色、个人等方式进行权限处理 5. 开发套件与框架(EXT基础包)、功能组件、消推送等 6. 商品中心、支付中心、订单中心
  • 【参考】业务中台2:其次业务中台会抽象沉淀公共业务,提供公司或三方使用
  • 抽象公司业务场景使用的中台能力,助效开发与迭代 1. 素材微服务:素材的自动生成、渲染、相似素材查询 2. 活动组装服务:活动的基础模块,可视化组装 3. 过滤推荐引擎服务,提供通用业务解决方案 4. 域名监测替换服务 5. 短链服务、短链多区域重定向服务
  • 【参考】数据中台,承担:大数据计算服务、大数据开发套件、画像分析、数据可视化、数仓规划、数据服务
  • 8.3. 常用地址
  • 中台ext技术文档:gitlab2.dui88/basic_platform/spring-boot-ext/wikis/目录
  • 监控处理cat3(先要在米莉亚点击业务监控):console.dui88/cat
  • 兑吧OA系统:oa.dui88/main.html#/
  • rdc地址: rdc.aliyun
  • 服务治理: console.duibatest
  • 大数据HUE:cdh-hue.dui88/hue
  • 火眼: eye.dui88/#!/
  • 风控钟馗:zhongkui.tuiatest/
  • gitlab: gitlab2.dui88/
  • 阿里云登录: signin.aliyun/1699099544509081/login.htm
  • 阿里云数据库企业: dms.aliyun/
  • 元数据系统:metatool.duiba/#/data-dictionary/codeManage
  • 8.4. 设计模式
  • 【参考】23种设计模式,主要包含5创7结11行
  • 【参考】创建型模式,5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
  • 【参考】结构型模式,7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
  • 【参考】行为型模式,11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
  • 9. 名词解释 9.1. ABCDEFG
    • ADX : Ad Exchange ,互联网广告交易平台
    • ARPU : Average Revenue Per User,每用户平均收入,我司:每发券收益=消费量/发券量
    • Cost : 消费量
    • CPM : Cost Per Mile,千人成本,实际是省略了Impression,每千次展示的成本,对应除1000有ECPM
    • CPC : Cost Per Click, 点击成本,按点击出价计费=消费量/点击量
    • CPA : Cost Per Action, 按转化付费=消费量/转化量
    • CPP : Cost Per Purchase 每购买成本
    • CPT:Cost Per Time:按时间付费,传统是按天结算的CPD,Cost Per Day
    • CPS:Cost Per Sale,分成模式结算
    • CPD:Cost Per DownLoad
    • CTR : Click Through Rate, 点击率=点击量/曝光量
    • CVR : Click Value Rate, 转化率=转化量/点击量
    • 长尾流量:Long Tail Effect,正太曲线中间凸起的叫头,两边平滑的叫尾;大多数需求集中在头部,称之为流行,个性化、差异化、少量的需求在需求曲线上形成一条长长的尾巴。长尾效应在于数量,将所有非流行的市场累加起来将会形成一个比流行市场还大的市场。长尾流量就是细分的、小众的、非标的流量聚合起来的流量市场
    • DSP : Demand Side Platform,需求方平台,为广告主提供服务,方便广告主购买设置策略投放广告
    • DPA : Dynamic Product Ads,DPA广告,中文叫“动态产品广告”,简称“动态广告”
    • DMP:Data Management Platform,简称DMP,DMP能够为广告投放提供人群标签进行受众精准定向
    9.2. HIJKLMN
    • H互动广告:我司轻互动广告一般投放在非标位置,入口素材展现能力较弱,用户参与活动,沉浸式体验,促进用户决策,实现广告投放转化
    核心指标:请求、返回、获胜、曝光、点击 (站外)===>(站内) 活动、复参、券获胜、券点击、券转化、转化回收归因 消耗 = 请求数*返回率*获胜率*曝光率*广告CTR *活动唤起率*复参率*券获胜率*券CTR*券CPC 目标成本 = (站外ECPM*ROI)/(目标CVR*券CTR*复参率*广告CTR
    • 直投广告:直投广告如信流等,一般素材展现能力强,但是相应的流量成本较高
    核心指标:请求、返回、获胜、曝光、点击 、转化 消耗 = 请求数*返回率*获胜率*曝光率*广告CTR *券CPC 目标成本 = =(站外ECPM*ROI)/(广告CTR *目标CVR)
    • MAP:Measure&Analytics Platform监测分析平台,百度监测、Admaster
    9.3. OPQRST
    • OCPC : 区别于CPC,Optimized Cost Per Click,维稳,以目标转化为优化方式的点击出价
    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; } 9.4. UVWXYZ
    • UV:Unique visitor,一般指的是用户个体
    9.5. 推啊互动
  • 【参考】推啊起家:2014年5月起家帮助互联网企业提升运营效率,达到锦上添花作用的用户运营服务平台,兑吧集团成立。以免费积分商城的方式合作了4000+媒体APP。2016年末孵化的推啊,2017年基于四大核心要素,迅速起家,同年完成了月消耗1亿的小目标,四大核心点:移动互联网浪潮、兑吧积累的商务优势、媒体的非标位置(无同行竞争、CPS分成合作)、互动广告(沉浸式体验、决策促进酶,让素材展现能力弱的非标也能实现转化)

  • 【参考】推啊主链路:入口素材推荐,点击5%>引擎推荐活动>活动访问,如大转盘加载==>40%活动参与,发券==>弹层出券,ad券曝光==>30%券点击,CPC计费==>落地页曝光/下载(ad曝光上报)==>10%落地页转化(ad回传,达标开启OCPC)

  • 【参考】推啊定义的皮肤与活动:皮肤是素材展现的一种互动形式,如大转盘、套猫等模板;围绕皮肤配置的投放周期、参与次数、标签、奖品等属性构建的信称为活动,如某个大转盘活动;常见的互动形式也区分轻互动和深度互动

  • 常见的轻互动:刮刮卡、大转盘、割绳子、砸金蛋、摇奖机、翻牌子、大海捞金、扭蛋机、扯红包、拆快递、套娃娃、扭蛋机、 摇塞子、摇签、射箭、挖金矿、套兔子、卡包、吹气球、摇钱树、答题、炮击拿好礼、幸运大抽奖、聚合页、幸运翻牌、 福利天天送、集字摇奖机、幸运八连抽、集字摇奖机 常见的深度互动:种红包、天天果园、大富翁、挖矿,有用户沉淀,复参深度玩法的游戏 9.6. 广告行业
  • 【参考】中国广告行业标准:中国广告协会(CAA)、中国信通信研究院(CAICT)、中国无线营销联盟(MMA)、移动安全联盟(MSA)
  • 【参考】中国无线营销联盟(MMA)规定
  • (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)、互动推、章鱼、变现猫(我司裂变) 9.7. 广告与流量
    • 广告大体分两类:品牌广告和效果广告(信流、短视频、其它/互动)
  • 【参考】展示类广告:开屏、插屏、焦点、信流、条幅
  • 1. 开屏广告:又称闪屏广告。在 APP 启动时展示的一个占满全屏的广告。 2. 插屏广告:在内容与内容的切换间隙展示一个广告。 3. 焦点图:在首页或频道首页头部可以手动或自动循环翻页的一组图片或图文组中展示一个图片或图文广告。 4. 信流广告(feeds,也称流内广告或原生广告):在信列表中,以与相邻信样式完全相同的形式展示一个广告。 5. 条幅广告(banner):又称矩形广告。以长方形形式展示的广告。通常出现在页面内,或者离开一个应用时。
  • 【参考】视频类广告:前贴片、中插、后贴片、暂停、视频角标
  • 1. 前贴片广告:在视频播放前展示的广告, 广告展示结束后自动播放视频。 2. 中插广告:在视频播放过程中,强行中止视频播放并展示广告,广告展示结束后自动播放视频。 3. 后贴片广告:在播放列表的最后一个视频结束后展示广告。 4. 暂停广告:在视频播放过程中,用户暂停时展示广告。 5. 视频角标广告:在视频播放中,在视频播放框中任意位置出现的不影响视频观看的小面积广告。
  • 【参考】广告投放KPI评估指标
  • 1. 广告曝光:曝光数 Impression,独立访客数UV(UniqueVisitor),频次 Frequency,广告可见的TA浓度 Viewable TA%,广告可见的TA到达率 2. 广告可视度:落地页/网站/APP访问,浏览数,停留时间,独立访客数,跳出率,二跳率 3. 用户互动:回搜率,点击率CTR,点击数Click,点击转化率CVR,点击到达率,互动率
  • 【参考】流量结构优化与分级,涉及的市面公司
  • 1. 头部流量:头条系、腾讯系、阿里汇川等 2. 长尾流量利用:穿山甲、优量汇、软告、科大讯飞

    本文标签: 手册java