qiubo's life

Stay Hungry, Stay Foolish


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

请回答2019

发表于 2019-12-28 | 分类于 review

年年岁岁花相似,岁岁年年人不同

以往说这句话时,其实大多数还是不变,但是为了显得有进步,每次年终回顾时,总是把这句话搬出来。2019比较特别,人生经历了很重要的变化,总结过去,畅想未来。

2019

生活

离开

16年夏,滴滴某位中间件大神通过github找到我,问我要不要考虑去北京发展,我当时很自傲的回答『我在重庆啥都有了,怎么会选择离开呢』。

16年冬,克星推荐我去华为做serverless,同样也以自己身体需要调理为理由拒绝离开,一直感觉对不起克星,今年和克星聊几次,慢慢释怀了。

没想到经过几年的辛苦折腾,终于把自己弄离开了。现在回过头来,不离开的理由非常幼稚,不想离开是因为没吃够生活的苦,稍微尝到了一点乐趣就忘乎所以,同时眼界还不够,把自己的选择局限在一个非常狭窄的圈子里,不知道真正的乐。

种一棵树最好的时间是十年前,其次是现在,虽然离开得有点晚,但也胜于执迷其中,自我安慰下,心智的成长需要一些磨练,世人皆苦,你不算最苦的。

感谢陪伴的家人和朋友。

适应

之前来杭州多次,每次过来只待几天,玩得还是很开心的,每次都恋恋不舍的离开。但真正在杭州生活似乎就不轻松了,吃住行都遇到了挑战。

杭州是一个美食荒漠,没啥好吃的,娜娜我和都想吃辣,刚到杭州时,用小火锅底料抗住了很长一段时间,偶尔去吃吃火锅,弥补下感官刺激。

杭州的房价比较高,不少高价位的盘,周边的配套设施几乎没有,加上我们要首付6层,对于买房的时机一直没有拿定。两次参与万人摇的盘,如娜娜所说,不是咱们运气差,只是证明咱们是普通人。

杭州的公共设施非常差,就一个大工地,常年施工中;刚开始几个月住得非常远,每天7天起床,坚持了半年,实在忍受不了了,就在公司附近租了一个房子,优点是不用早起,缺点是晚上似乎睡得更晚了,娜娜有几次对我说,要不我们还是搬到原来的地方去吧,至少可以早睡😄。

吃住行都受到挑战的时候,多次在知乎上看『离开杭州』相关的话题,看来世人皆苦,你仍然不算最苦的。

难受的过程正是成长的过程。

行万里路

认同『读万卷书不如行万里路』,因为读者理解的内容和作者书里表达的内容有偏差,需要读者亲身体验才能真切感受。

日本之行感受特别深,惊叹他们对文化的传承,对人性的关怀。公交车到站停稳后会侧偏向乘客,方便老年人上下车;有次遇到一位坐轮椅的老奶奶在公交车司机的帮助下上公交车,这样的际遇在国内几乎看不到。

南京之行特别仓促,周五去周天回。第一次体验了排队一个多小时吃南京大牌档,虽然这一个小时等的过程特别急躁,但享受美食的过程印象特别深刻,回到杭州后也去了几次南京大牌档,满足感不复存在,不容易得到的才会有深刻的记忆。

两次上海之行,一次是和三娘全家去迪斯尼过跨年,一次是和三妹全家去迪斯尼海洋馆。迪斯尼玩着太累啦,排队花的时间太长,印象中就玩了两个项目;海洋馆的体验还挺好,全程娜娜非常开心。

之前信誓旦旦的说,花两年时间把杭州周边城市都玩个遍,结果现在还没真正开始。

路还没走够,书也还没读够,继续。

家庭

某律师朋友的观点『生命的意义在于延续』,我和娜娜的观点是『生命的意义在于体验』,答案没有对与错,酸甜苦辣都是味道,都需要慢慢品尝,可能先苦后甜,也可能先苦后更苦。有个小朋友的体验如何,慢慢品尝。

娜娜怀孕的过程非常艰辛,前三个月反应特别大,吃了就吐,而且特别没精神。刚怀上去检查宝宝还不是很稳,医生让保胎试试,妈妈和三妹都过来照顾,我俩安慰自己,这是上天的选择,如果胚胎质量不好,自然选择淘汰了它。保胎一段时间后去听胎心,心跳扑通扑通~,医生说你们的宝宝非常有活力,娜娜高兴得流眼泪了。

保胎完了面临最大的挑战是睡眠不好,怀孕后晚上会起床几次,醒了后就很难睡着,睡眠不足导致经常头痛,头痛时疼得哭时为了宝宝又不能吃药,没有其他好的招,只能按摩,好几次都不想再让坚持下去。比较神奇的是,到了后期,头痛症状消失了,也许是文曲星下凡,不忍心她妈妈受苦,感谢上天的安排。

孕晚期挺着大肚子上班,同事几次催她赶快休假,12月19号开始休产假,晚上就发动了。送到省妇保急诊,已经开两指,医生让直接送到产房待产,在产房待了一会儿,实在是太疼了,求护士给打了无痛,睡觉醒来开十指,宝宝顺产成功。从19号23点去医院,到20号10点29小朋友出生,在这十个小时里,坐在家属区等待的我,和躺在产床上痛苦的娜娜,之前脑子里演练的场景都不复存在,一直在反思,我的参与感在哪里,我又能做什么,直到听到母女平安的消息,喜极而泣。。。

虽然你只是一个过客,但有你的生命更精彩,Nemo。

工作

从零开始

之前的职业生涯只做技术,完全不懂业务,也不懂业务背后的商业价值,所以在选择新的工作时,偏向于金融相关的业务。幸运的是遇到了博一老大,经过一下午的面试,面试通过,顺利拿到offer。

入职之前那段时间,在家挺无聊,白天娜娜上班,我就把sofa开源的中间件看了个遍,结果发现sofaboot所做的事情,就是几年前我做的工作,没花多少经历就看完了,幻想着入职后就给sofaboot提不少pull request,踏上人生巅峰。

入职后幻想破灭了,完全听不懂大家在干啥,晨会一脸蒙逼,技术栈也和之前看的不一样,突然发现十年工作经验在这个领域的经验为零,有些恐慌,也很迷茫,蚂蚁对人的要求非常高,既要熟悉你负责的模块,又要负责你熟悉的上下游,还要熟悉中间件,多亏师兄们给了很详细的学习计划,开始学习之旅。

花了将近半年时间,把所接触的系统代码主流程看了一遍,看完后晕晕乎乎的,有些概念不是特别熟悉,特别是会计里面的概念,基本名词的理解都需要花不少的精力。这个过程身心俱疲,非常沮丧,5天看7个系统代码,看完能串出主流程,由于缺少业务需求输入,不知道为什么要这样设计,也不知道看这些代码的意义何在。直到负责第一个大项目,这个项目是网商系统链路最长的项目,在这个项目中把很多细节都串起来了,在业务灰度过程中,核算缺配置,在脑海中第一时间蹦出核算组,精确到代码行的给业务同学分析遗漏所导致的问题。

业务的学习非常有意思,了解了技术带来的商业价值,不过还不够体系,还得继续串。

As Steve Jobs say:You can’t connect the dots looking forward; you can only connect them looking backwards.

一些活动

百阿认识了一帮来至五湖四海的兄弟,同时也是了解蚂蚁的业务,价值观的机会。关于价值观,我一直很好奇,阿里这么大,是什么支撑了他持续进步,而之前我所在的公司,价值观只是口号,大家把价值观作为言的一部分,管理层很多人都没有遵守,而且还有言行互相矛盾的地方。而阿里的价值观不太一样,首先阿里的价值观具有普世价值,拿着这个价值观去其他公司也可以适用,他要求的是一个正能量满满的人;其次师兄们的言行举止里实践着价值观,且身体力行的传承价值观。阿里很多土话,用土话来传承文化,用制度来维护文化,让新人融入阿里,文化就是言行举止!百阿最后的团队表演是『老子今天不上班』,来自天南海北的兄弟们都用成都话唱,轰动全场。

阿里自己创造的双十一节练人,练系统,练团队,内部有句话叫没有经历双十一的同学不算是真正的蚂蚁人。双十一提前开始全链路压测,应急预案演练,系统优化改造,整体性能超过宇宙第一行,双十一当天如丝般顺滑。唯一的遗憾是今年小组参与的同学太少了,明年带其他新同学去感受。

还有一些活动,包括培训中间件知识的精武门,拿了第一,赢取抱枕一个;程序员节的超级MA力大赛,止于十二名,微贷第一,错失与合伙人进餐的机会。还有其他阿里特有的活动,比如527,1218攻防演练,完善了知识体系也挺有意思,明年继续。

一起拼,一起嗨!

团队

和以往带团队不一样,这次带团队挑战比较大,原因有几点:第一这是一个不太熟悉领域的团队😓;第二大家都很优秀,也很专业,还很努力;第三业务挑战巨大巨大,为网商所有业务提供资产交换服务,tps网商第一;第四团队成员大多数是新人。团队经过几个月的磨合,慢慢发展到7个人,这要特别感谢涛哥及时帮助纠偏误区,认识到了需求和资源之间的鸿沟,建立了良性的持续改进氛围。

持续改进是小团队进化的基石,每周都能看到进步的团队很可怕。

总结

2019是人生最充实的一年,也是人生最拼搏的一年,也是人生变化最大的一年,继续加油,你是最胖的!

感谢陪伴中的同事,朋友,家人!

2020

先立flag,明年年底在来review:

认真生活:

  • 深入学习养娃知识,努力成为此领域专家
  • 读2-3本书
  • 一家人去周边1-2个城市闲逛
  • 每月至少锻炼2次

快乐工作:

  • 摸透本领域
  • 熟悉其他领域
  • 努力努力

2018年11月Reading Notes

发表于 2018-11-13 | 分类于 java

AOP Benchmark

一直觉得spring proxy aop性能比较差,看了AOP Benchmark没想当差别这么大。曾今在一个项目中改用aspectj,成本有点高,也不便于团队协作。先埋个思路在这里:

  1. 在应用启动时,编程式加载java agent(VirtualMachine#loadAgent),spring 使用aspectj ltw(开发时运行)
  2. 使用maven注解实现编译时植入(线上运行)

参考 spring-boot-aspectj

在spring.io上看到了不一样的声音: Debunking myths: proxies impact performance

  1. 这点性能比起长时间运行的任务来说太短了
  2. 一个请求最多也不会经过10个proxy,10 proxy operations * 500 ns per proxy operation = 5 microseconds任然可以忽略不记

这个说法在当年还是有说服力的,现在就不怎么明显了,微服务、SOA大行其道,涉及到的aop会比较多,如果在基础设施上做一些改造,方便无感知的使用byte code weaving意义还是很大的。

sentinel

Sentinel提供流量控制、熔断降级、系统负载保护等功能。相对于Hystrix使用起来倾入性比较小。大致撸了一遍代码:

  1. SlotChainBuilder构建每个资源对应的ProcessorSlotChain。
  2. ProcessorSlot组成责任链来分离功能。(前面几个Slot用来做资源路由、统计,后面几个完成系统功能)。
  3. SystemSlot实现系统负载保护功能。有趣的是参考BBR算法,当负载过高时,判断当前请求容量来减少对请求的拒绝。
  4. AuthoritySlot黑白名单控制
  5. FlowSlot流控
  6. DegradeSlot降级

DubboCache

发表于 2018-07-23 | 分类于 java

@DubboCache

提供dubbo消费者直接使用缓存的能力,当缓存不存在时,再访问远程dubbo服务。

相对于dubbo默认的缓存机制,此项目具有如下优点:

  1. 原生dubbo cache机制只能缓存结果到消费者jvm中,并且cache key不能选择。
  2. 缓存key生成灵活,和spring 声明式缓存一致,采用spring el表达式
  3. 可以扩展CacheKeyValidator接口,实现键缓存策略
  4. 可以扩展CacheValueValidator接口,实现值缓存策略
  5. 由于采用分布式缓存,服务提供端可以灵活控制缓存

目前仅提供redis实现,依赖spring-data-redis.

使用@DubboCache

在服务接口模块中依赖dubbo-cache-common

<dependency>
    <groupId>com.github.bohrqiu.dubbo</groupId>
    <artifactId>dubbo-cache-common</artifactId>
    <version>1.2</version>
</dependency>

此依赖仅定义了@DubboCache注解。

在服务实现模块中依赖dubbo-cache-core

<dependency>
    <groupId>com.github.bohrqiu.dubbo</groupId>
    <artifactId>dubbo-cache-core</artifactId>
    <version>1.2</version>
</dependency>

在服务接口上添加@DubboCache注解

1
2
3
4
public interface CacheableService {
@DubboCache(cacheName = "test",key = "#order.playload")
SingleValueResult<String> echo(SingleValueOrder<String> order);
}

如上所示:cacheName=test,key为第一个参数的playload字段,缓存有效期默认5分钟。

上面的注解和`@org.springframework.cache.annotation.Cacheable(value = “test”, key = “#order.playload”)`生成的key一致。

对于dubbo服务消费者,只需要更新jar包即可,由服务提供者来觉得接口是否需要缓存,和缓存的控制。

注意: 参数名读取依赖java8编译参数-parameters,上面的例子也可以通过p0来引用第一个接口入参。

key生成

key生成策略和Cacheable一致,上面的例子中cache key由两部分组成:cacheName,spring el表达式结果,用:分隔.

如果请求参数order中playload属性值为dubbo,最终key为:test:dubbo

如果服务有多个版本或者group,需要对多个版本和group分别设置缓存,可以设置参数:

cachePrefixContainGroup=true
cachePrefixContainVersion=true

如果两个参数都设置,key由四部分组成:cacheName,group,version,spring el表达式结果,用:分隔,建议在provider端设置此配置。

控制缓存

@DubboCache提供了消费者可优先使用缓存,缓存的一致性由服务提供方负责,当服务提供方使用此注解后,所有的服务消费者都会使用此缓存。

控制缓存分为两种情况:

  1. 缓存一致性要求不高,可以通过DubboCache#expire设置过期时间,默认为5分钟。
  2. 缓存一致性要求高,服务提供方通过redisTemplate或者org.springframework.cache.annotation.CacheEvict控制缓存。

如何扩展

扩展CacheKeyValidator

CacheKeyValidator可以实现对特定的key、url缓存或者不缓存。默认策略为:key不为null可以缓存。

实现CacheKeyValidator

1
2
3
4
5
6
7
8
package com.github.bohrqiu.dubbo.cache.validator;
public class TestCacheKeyValidator implements CacheKeyValidator {
@Override
public boolean isValid(URL url, Invocation invocation, CacheMeta cacheMeta, Object elEvaluatedKey) {
System.out.println("in TestCacheKeyValidator");
return true;
}
}

配置扩展文件

在classpath下创建META-INF/dubbo/com.github.bohrqiu.dubbo.cache.CacheKeyValidator文件,内容为:

test=com.github.bohrqiu.dubbo.cache.validator.TestCacheValueValidator

设置服务url使自定义扩展生效

可以通过在provider配置application时指定(更多配置方式,参考dubbo相关文档).

1
2
3
<dubbo:application name="dubbo-cache-test">
<dubbo:parameter key="cacheKeyValidator" value="test"/>
</dubbo:application>

扩展CacheKeyValidator

扩展方式和上面的类似,更多参考dubbo扩展机制。 默认策略为:value不为null可以缓存。

github源代码

https://github.com/bohrqiu/dubbo-cache

2018年06月Reading Notes

发表于 2018-06-01 | 分类于 java

spring InjectionPoint

InjectionPoint是spring 4.3引入的特性,顾名思义,注入点,通过此类能获取到注入点上下文。可以通过这个特性做一些有意思的事情。

1. 获取被注入组件的类

@Bean
@Scope("prototype")
Logger logger(InjectionPoint ip) {
    return Logger.getLogger(ip.getMember().getDeclaringClass().getName());
}

@Component
public static class LoggingComponent {
    private final Logger logger ;
    public LoggingComponent(Logger logger) {
        this.logger = logger;
    }
}

2. 获取被注入组件上的注解

  @Bean
  @Scope("prototype")
  public String greeting(InjectionPoint ip) {
    Greeting greeting = findAnnotation(ip.getAnnotatedElement(),
                        Greeting.class);
    return (Language.DA == greeting.language()) ? "Hej Verden" : "Hello World";
  }

@Service
public class GreeterService {
  @Autowired @Greeting(language = Language.EN)
  private String greeting;
}

spring jdbc

spring jdbc做了很多有意思的扩展,比如大家熟悉的JdbcTemplate,还有最近才推出的spring-data-jdbc,参考spring-tips,了解spring 对jdbc的能力支持。

Asynchronous Database Access API

ADBA访问数据库的异步api,让io线程的事件机制去等待数据库响应,把这块填补上,java程序中基本上都可以完全异步了。scalikejdbc-async的做法实现了jdbc api,但是由于异步编程模型的改变,使用起来会有点别扭。(比如基于请求的结果做后续操作)

目前就oracle数据库驱动实现了ADBA,它通过nio实现了完全的异步。

Minio

Minio是GlusterFS创始人之一Anand Babu Periasamy发布的兼容Amason S3分布式对象存储项目,可以做为对象存储的解决方案用来保存海量的图片,视频,文档.

分布式minio,有以下特性:

  • 数据保护:采用erasure code来防范多个节点宕机和位衰减bit rot
  • 高可用:如果是一个N节点的分布式Minio,只要有N/2节点在线,你的数据就是安全的
  • 一致性:Minio在分布式和单机模式下,所有读写操作都严格遵守read-after-write一致性模型。

以下几点需要注意:

  • minio不支持动态扩容,如果要扩容只能停机迁移
  • 按照默认的storage class in erasure coding mode策略,比如8个节点,4个数据块,4个校验块,最大允许4个节点挂掉,最大磁盘利用率50%

准实时同步应用日志到数仓

发表于 2018-05-30

传统的etl工具很难做到准实时的数据同步,我们以前使用kettle来做etl,对于日志同步的场景有几个问题:

  1. 做不到实时,kettle只能定时触发
  2. 比较难做到日志文件增量处理,需要记录上次读取的文件位置

鉴于以上情况,实践了下logstash的日志output jdbc插件。

1. 日志模型定义

定义日志通用模型如下:

{
  "appName": "",
  "body": {

  },
  "businessName": "",
  "env": "",
  "hostName": "",
  "timestamp": ""
}

body节点由数据埋点应用根据数据模型自定义。

比如线上某业务埋点数据如下:

{
  "appName": "openapi",
  "body": {
    "app_version": "3.1.18",
    "device_id": "123456",
    "phoneType": "HUAWEI MATE10",
    "userid": "201702057568"
  },
  "businessName": "userRegister",
  "env": "online",
  "hostName": "172.16.0.158",
  "timestamp": "2018-05-30 00:00:41"
}

2. logstash配置

output {
    #不同的业务日志模型输出到不同的表
    if [businessName] == "userRegister"{
      jdbc {
          driver_class => "com.mysql.jdbc.Driver"
          connection_string => "jdbc:mysql://127.0.0.1:3306/log?user=root&password=123456"
          enable_event_as_json_keyword => true
          #解析body中的数据到指定的日志表
          statement => [ "INSERT INTO log_user_register (host, time, message,app_version,channel) VALUES(?, ?, ?,?,?)", "host", "@timestamp", "message","%{[body][app_version]}","%{[body][channel]}" ]
      }
    }
    if [businessName] == "userLogin"{
      jdbc {
          driver_class => "com.mysql.jdbc.Driver"
          connection_string => "jdbc:mysql://127.0.0.1:3306/log?user=root&password=123456"
          enable_event_as_json_keyword => true
          statement => [ "INSERT INTO log_user_login (host, time, message,app_version,channel) VALUES(?, ?, ?,?,?)", "host", "@timestamp", "message","%{[body][app_version]}","%{[body][channel]}" ]
      }
    }
  }

3. 改进之处

  1. 通过不同的业务类型创建了多个jdbc output plugin,jdbc.rb中创建了数据库连接池,比如上面的配置创建了两个数据库连接池。这里需要优化成支持不同的业务类型使用不同的statement.

开发规范(一):日志

发表于 2018-05-20 | 分类于 java

几年前写的东西,已经离职,留作纪念

易极付日志命名格式标准

版本 时间 作者 修订章节 修订内容
1.0 2013-08-10 培根 全部 init
1.1 2015-07-27 秋波 全部 修改摘要日志规范
1.2 2015-07-27 秋波 全部 修改格式和完善内容

1.日志文件存放地址

由运维指定,目前统一放到/var/log/webapps/${appName}下

${appName}为应用名 如:账务(accounttrans)、易生活(elife)

2. 格式构成

2.1 日志文件名称格式

日志全名由小写字母构成,连接线使用”-“

2.2 日志级别

日志类别主要有debug、info、warn、error

2.2.1 debug

debug日志主要是用于调试、运行轨迹跟踪。默认我们的日志级别为info,当我们在测试环境想输出更多轨迹信息时,日志日志级别为debug,输出此日志。

2.2.2 info

info日志为打印基本业务完整性日志,比如对外服务入口,出口,关键业务流程等。

2.2.3 warn

warn日志为可能存在的潜在问题的提示,以及重要的提示信息。

2.2.4 error

error为业务处理出错或致命错误。

2.3 日志类型

可以把某些类型的操作记录到单独的日志文件中。

2.3.1 查询日志(非必须)

业务性能日志,日志名前缀为 {appName}-query

可以把查询相关的请求日志写入到一个文件中,方便日志定位。

2.2.2 摘要日志(必须)

业务摘要日志,记录业务执行轨迹。

摘要日志请使用com.yjf.common.log.DL工具类输出日志。

2.2.3 性能日志(非必须)

业务性能日志,日志名前缀为 {appName}-perf

性能日志主要标识了从两个时间点的执行时间来测量某业务的性能情况

业务性能日志建议使用perf4j

2.3 日志保存策略

日志保持策略定义在文件名种,由两部分组成 天数+保存形式,信息中心按照文件名来处理日志

  • 最长天数为60天
  • 保存形式:

    • 定期清理日志关键字[dt],如:14dt.log
    • 定期备份日志关键字[de],如:14de.log

2.4 日志保存策略

日志滚动规则

  • 按天滚动

    隔天日志格式在当天日志的最后加上日期.如:elife-xxx.log.2013-08-14

  • 按大小滚动

    日志文件大小超过1G后,滚动为新的日志.如:elife-xxx.log.2013-08-14.0、elife-xxx.log.2013-08-14.1

2.5 日志内容格式

logback配置如下:

%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{0}:%L- %msg%n

3. 最佳实践

3.1 善用MDC

MDC用于记录请求执行轨迹,我们可以把某请求参数(比如gid)记录到日志中。当我们在分析日志时,搜索此请求参数,就能看到此请求完整的执行轨迹。

参考:

以openapi-arch中的代码为例,我们在logback.xml中配置MDC,当用户请求到达时,会生成唯一的gid.此时把gid存入MDC中,后续一系列的处理日志输出中都会有此gid,当请求处理完毕后,清理MDC。

3.2 日志调优

参考 日志优化

4.配置样例

下面以cs系统为例,日志配置如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!-- 日志组件启动时,打印调试信息,并监控此文件变化,周期60秒 -->
<configuration debug="true" scan="true" scanPeriod="60 seconds">

<!--针对jul的性能优化  -->
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
    <resetJUL>true</resetJUL>
</contextListener>

<!-- 定义变量 -->
<property name="appName" value="cs"/>
<property name="logPath" value="/var/log/webapps/${appName}"/>
<property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{0}:%L- %msg%n"/>

<!-- contextName主要是为了区分在一个web容器下部署多个应用启用jmx时,不会出现混乱 -->
<contextName>${appName}</contextName>

<!-- ***************************************************************** -->
<!-- 配置输出到控制台,仅在开发测试时启用输出到控制台 ,下面的语句在window环境下生效,使用mac或者ubuntu的同学,请自己构造下-->
<!-- ***************************************************************** -->

<if condition='property("os.name").toUpperCase().contains("WINDOWS")||property("os.name").toUpperCase().contains("MAC")'>
    <then>
        <appender class="ch.qos.logback.core.ConsoleAppender" name="STDOUT">
            <encoder>
                <pattern>${pattern}</pattern>
            </encoder>
        </appender>
        <root>
            <appender-ref ref="STDOUT"/>
        </root>
    </then>
</if>

<!-- ***************************************************************** -->
<!-- info级别的日志appender,这里把所有日志都输出到一个文件,没有区分不同的业务类型 -->
<!-- ***************************************************************** -->
<appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="cs-info">
    <file>${logPath}/cs-info-30dt.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${logPath}/cs-info-30dt.log.%d{yyyy-MM-dd}.%i
        </fileNamePattern>
        <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <maxFileSize>1024MB</maxFileSize>
        </timeBasedFileNamingAndTriggeringPolicy>
    </rollingPolicy>
    <encoder>
        <pattern>${pattern}</pattern>
    </encoder>
</appender>

<!-- ***************************************************************** -->
<!-- error级别日志appender -->
<!-- ***************************************************************** -->
<appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="cs-err">
    <file>${logPath}/cs-error-30dt.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${logPath}/cs-error-30dt.%d{yyyy-MM-dd}.%i
        </fileNamePattern>
        <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <maxFileSize>1024MB</maxFileSize>
        </timeBasedFileNamingAndTriggeringPolicy>
    </rollingPolicy>
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>ERROR</level>
    </filter>
    <encoder>
        <pattern>${pattern}</pattern>
    </encoder>
</appender>

<!-- ***************************************************************** -->
<!-- 性能日志appender -->
<!-- ***************************************************************** -->
<appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="cs-perf">
    <file>${logPath}/cs-perf-30dt.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${logPath}/cs-perf-30dt.log.%d{yyyy-MM-dd}.%i
        </fileNamePattern>
        <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <maxFileSize>1024MB</maxFileSize>
        </timeBasedFileNamingAndTriggeringPolicy>
    </rollingPolicy>
    <encoder>
        <pattern>${pattern}</pattern>
    </encoder>
</appender>
<!-- 异步日志包裹 -->
<appender class="com.yjf.common.log.LogbackAsyncAppender" name="async-cs-info">
    <appender-ref ref="cs-info"/>
</appender>
<appender class="com.yjf.common.log.LogbackAsyncAppender" name="async-cs-err">
    <appender-ref ref="cs-err"/>
</appender>
<appender class="com.yjf.common.log.LogbackAsyncAppender" name="async-cs-perf">
    <appender-ref ref="cs-perf"/>
    <!--不收集栈信息-->
    <includeCallerData>false</includeCallerData>
</appender>

<!-- 性能日志logger additivity=false代表此日志不继承父logger中的appender,对于此样例,性能日志不会写到处cs-perf-30dt.log外的其他日志文件-->
<logger name="LOGGER_PERFORMANCE" additivity="false">
    <appender-ref ref="async-cs-perf"/>
</logger>


<!-- 根日志logger -->
<root level="INFO">
    <appender-ref ref="async-cs-info"/>
    <appender-ref ref="async-cs-err"/>
</root>

</configuration>

开发规范(二):maven

发表于 2018-05-20 | 分类于 java

几年前写的东西,已经离职,留作纪念

maven使用规范

版本 时间 作者 修订章节 修订内容
1.0 2015-05-11 秋波 全部 init
1.1 2015-07-16 秋波 全部 增加依赖规范
1.2 2015-07-13 秋波 全部 增加版本号命名规范,增加发布规范
1.3 2015-08-05 秋波 全部 增加maven相关命令

目录

  1. 依赖管理
  2. 模块依赖关系
  3. 发布规范
  4. 版本规范
  5. maven相关命令

此规范用于规范maven依赖管理、maven模块依赖关系、模块版本升级原则、发布规则。

1. 依赖管理

  • 业务系统父pom必须继承于yiji-parent
  • 业务系统不能在pom中定义依赖版本,所有版本由yiji-parent提供
  • 禁止使用yiji-parent中没有定义的第三方组件,如需要使用第三方组件,需由基础技术部评估

2. 模块依赖关系

这里以cs项目为例,模块依赖规范如下

2.1 父pom:

项目的版本由父pom定义

<parent>
    <groupId>com.yiji</groupId>
    <artifactId>yiji-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.yjf.cs</groupId>
<artifactId>cs-parent</artifactId>
<version>2.0.20140306</version>

禁止在父pom的dependencyManagement中定义子module的版本.

2.2 facade模块:

子模块groupId和version继承自父pom

<parent>
       <artifactId>cs-parent</artifactId>
    <groupId>com.yjf.cs</groupId>
    <version>2.0.20140306</version>
</parent>
<artifactId>cs-facade</artifactId>

当前模块pom定义中不准定义groupId和version.

2.3 web模块:

版本依赖通过${project.parent.version}定义

<parent>
    <artifactId>cs-parent</artifactId>
    <groupId>com.yjf.cs</groupId>
    <version>2.0.20140306</version>
</parent>
<artifactId>cs-web</artifactId>
 <dependencies>
    <dependency>
       <groupId>com.yjf.cs</groupId>
       <artifactId>cs-biz</artifactId>
       <version>${project.parent.version}</version>
    </dependency>
 </dependencies>

web模块需要依赖biz模块,通过${project.parent.version}来定义依赖版本.

3. 发布规范

3.1 发布流程

3.1.1 nexus环境介绍

我们有两个nexus,一个用于开发测试(在yiji-parent中profile=dev中定义),一个用于联调和线上打包(在yiji-parent中profile=online中定义)

3.1.2 发布流程规范

开发测试阶段,我们把项目输出物发布到测试nexus(profile=dev),本地开发测试通过后,发布到线上nexus(profile=online)

在上线时,必须保持线上nexus和测试nexus中的版本一致,禁止出现下面的情况:

  • 测试nexus中有,线上nexus没有
  • 测试nexus中没有,线上nexus有
  • 测试、线上nexus中都有,版本也一致,但是代码不一致

3.2 发布操作规范

3.2.1 在不需要部署的模块中覆盖配置

比如assemble模块不需要发布,需要在assemble的pom中覆盖配置,禁止把非项目输出物发布到nexus,比如biz,dal,web,assemble

<properties>
    <deploy.skip>true</deploy.skip>
</properties>

3.2.2 发布到nexus仓库

发布时,在父pom所在的目录,执行命令发布。执行此命令会把父pom和deploy.skip=false的项目提交到nexus仓库。按照我们的项目结构,大多数项目只能把facademodule发到nexus仓库。

3.2.3 发布到测试nexus

mvn -T 1C clean deploy -P dev  -Dmaven.test.skip=true

3.2.4 发布到线上nexus

mvn -T 1C clean deploy -P online  -Dmaven.test.skip=true

使用windows的同学,可以写几个bat,放到PATH中加载这几个bat.

4. 版本规范

4.1 版本命名

业务系统必须按照下面的方式命名版本号:

主版本.子版本.日期

说明如下:

  • 主版本号:

    当功能模块有较大的变动,比如增加模块或是整体架构发生变化。此版本号由项目负责人决定是否修改。

  • 次版本号:

    相对于主版本号而言,次版本号的升级对应的只是局部的变动,但该局部的变动造成程序和以前版本不能兼容,或者对该程序以前的协作关系产生了破坏,或者是功能上有大的改进或增强。此版本号由项目负责人决定是否修改。

  • 日期版本号:

    用于记录修改项目的当前日期,每此对项目的修改都需要更改日期版本号。此版本号由开发人员决定是否修改。

4.2 版本修改原则

版本修改的原则是:

  1. 所有模块的版本必须保持一致
  2. 服务输出项目:服务输出模块的功能改变时,升级模块版本
  3. 组件项目:组件功能改变时,升级版本。

我们大多数项目都是服务输出项目,我们对其他客户提供服务,当我们对外提供的服务功能变动时,需要升级服务输出模块的版本(我们的项目结构中,此模块基本上都命名为facade)。

组件项目,当功能发生变化,影响到使用方时,需要升级版本。如果是bug修复,可以在nexus上把老版本删除,强制让使用方升级。

无论的模块版本如何变动,都要保证所有的模块版本一致,可以使用下面的命令来统一修改版本。

mvn versions:set -DgenerateBackupPoms=false

5. maven相关命令

#安装到本地
alias mvni='mvn -T 1C clean install -Dmaven.test.skip=true'
#打包
alias mvnp='mvn -T 1C clean package -Dmaven.test.skip=true'
#修改版本
alias mvnv='mvn versions:set -DgenerateBackupPoms=false'
#发布到测试nexus
alias mvndd='mvn -T 1C clean deploy -P dev  -Dmaven.test.skip=true'
#发布到生产nexus
alias mvndo='mvn -T 1C clean deploy -P online -Dmaven.test.skip=true'
#清理工程
alias mvnc='mvn -T 1C clean eclipse:clean idea:clean'
#检查依赖升级情况
alias mvncv='mvn -T 4C  versions:display-dependency-updates'
#生成文档站点
alias mvns='mvn clean site -Dmaven.test.skip=true'
#生成eclipse工程结构
alias mvne='mvn eclipse:eclipse install'

开发规范

发表于 2018-05-20 | 分类于 java

几年前写的东西,已经离职,留作纪念

易极付开发规范

版本 时间 作者 修订章节 修订内容
0.5 2012-05-23 培根 全部 初始版本
1.0 2015-04-11 秋波 全部 优化、补充
1.1 2015-04-14 秋波 全部 增加目录
1.2 2015-09-24 秋波 全部 增加用户信息安全

目录

  1. 操作规范
  2. 日志规范
  3. 注释规范
  4. 安全规范
  5. 编码规范
  6. 通用规范
  7. 数据库规范
  8. maven规范
  9. 访问权限规范

一、操作规范

1. 模板及格式化

易极付开发人员必须保证代码格式化的一致性,否则可能会导致代码冲突,轻微的耗费人力合并代码;严重时可能导致代码丢失,引起bug或者故障。

  • 开发人员必须配置易极付的codetemplates.xml代码模板文件;
  • 开发人员必须配置易极付的YJFFormatter.xml代码格式化文件;
  • 每次提交代码之前,必须对java代码format;

IDE设置参考:IDE设置代码格式和代码模板配置

2. 代码提交

  • 任何代码禁止出现“System.out.println”、”// TODO Auto-generated method stub”
  • 提交代码前使用checkstyle、findbugs、pmd扫描代码,禁止提交严重以上问题
  • 不准提交工程下IDE自动生成的代码,比如.classpath、target、.idea、*.iml

3. 垃圾清理

  • 对于从来没有用到的或者被注释的方法,变量,类,配置文件,动态配置属性等要坚决从系统中清理出去,避免造成过多垃圾

4. 版本控制工具

  • git项目必须要有.gitignore文件,文件内容参考
  • git项目工作流必须遵循易极付git项目开发工作流规范
  • 为防止冲突,任何时候,代码(及配置文件)提交前,先从svn/git中更新代码和配置文件,以及早发现不兼容的代码变更和冲突
  • 提交代码(及配置文件)时,如果发生冲突时,先看历史说明,再找相关人员确认,坚决不允许强制覆盖
  • svn目录结构应该包括3个代码目录:trunk、tags、branches。如果项目有文档,应该增加doc目录,禁止把文档放到代码目录

二、日志规范

参考:日志规范

三、注释规范

  • 类注释和版权注释,请使用file template自动生成。
  • 接口方法注释如下:

    /**
     * 删除用户收藏的一个商品。
     * 逻辑删除:delete_flag置为1
     *
     * @param memberId  会员ID
     * @param itemSku   商品序列号
     *
     * @return 是否删除成功:1.成功,0.失败
     * @throws ServiceCustomException
     */
    

    接口方法注释每个入参必须有中文解释,且全局统一名称,返回对象是boolean或int型时,解释返回结果含义,加入必要的抛出异常规则。

  • 方法体内注释如下:

    // 1. 尝试更新已有的收藏
    int updateForCreate = dao.doUpdate(memberId, itemSku);
    // 2. 更新成功,返回新增成功
    if (updateForCreate > 0) {
    return updateForCreate;
    }
    // 3. 没有更新成功则需要添加用户收藏信息
    

    禁止在每行的尾部注释。

四、安全规范

请参考安全checklist

五、编码规范

java代码必须严格遵循google编码规范

六、通用规范

1. 异常处理

  • 捕捉到的异常,不允许不作任何处理就截断,至少要记入日志,或重新抛出
  • 最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容

2. 资源的使用

  • 对系统资源的访问,使用后必须释放系统资源。这类资源包括:文件流、线程、网络连接、数据库连接等。
  • 对于文件、流的IO操作,必须通过finally关闭。可以考虑使用jdk7以后新增的try-resource来关闭资源
  • 对于线程,线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
  • 对于网络连接不数据库连接,必须由框架通过连接池提供,不允许应用 中自行建立网络不数据库连接

3. 本地事务操作

  • 对于业务逻辑上不允许并发访问的数据(例如:具有全局唯一性的数据, 涉及到总和类的数据等) ,必须采用事务和加锁的方式进行处理
  • 对于业务逻辑上要求数据完整性的数据(例如:同时操作多个表,对同一 个表反复进行操作等) ,必须采用事务的方式进行处理

4. 线程安全处理

  • 虽然容器会负责多线程的处理,但是程序中还是会遇到很多线程安全的问题, 开发人员必须注意并发处理,否则可能导致死锁或者资损
  • 线程上下文变量的设置清除必须配对
  • 静态Util或单例必须是线程安全。
  • DateFormat是非线程安全的,类变量使用时会被破坏。每次使用都要重新构造,或者使用DateUtil工具类
  • 为记录加锁时,需要保持一致的加锁顺序,否则可能会造成死锁

5. 用户信息安全

  • 禁止把用户敏感信息(用户密码、银行卡、信用卡等信息)输出到日志

    学习并使用下面三个注解:

    @ToString.Maskable 调用ToString#tostring时输出掩码
    @ToString.Invisible 调用ToString#tostring时不输出
    @JSONField(serialize = false) json序列化时不输出,要考虑是否有反序列的情况
    
  • 禁止把用户敏感信息告知给其他人

  • 出现用户敏感信息泄露时,不能扩散、传播、留存,请立即通知技术总监

七、数据库规范

MySQL数据库开发规范

八、maven规范

maven规范

九、访问权限规范

  • 禁止把测试页面暴露到公网
  • 禁止把http定时任务url暴露到公网
  • 禁止测试环境直接访问线上
  • 禁止把生产数据导到本地
  • 禁止把管理、统计功能直接暴露到公网(boss功能、druid管理页面等)

开发规范(三):mysql

发表于 2018-05-20 | 分类于 java

几年前写的东西,已经离职,留作纪念

MySQL数据库开发规范

版本 时间 作者 修订章节 修订内容
1.0 2015-04-15 帕拉丁 全部 init
1.1 2015-04-20 秋波 全部 修订内容
1.2 2015-07-17 秋波 全部 修订内容
1.3 2015-07-28 秋波、帕拉丁 全部 修订内容

一. 数据库核心规范

  1. 尽量不要在数据库做运算:cpu计算请移至业务层.
  2. 控制单表数据量:单表记录控制在1000w行.(也可以考虑分区)
  3. 控制列数量:单表字段数上限控制在20到50之内.字段少而精可以提高并发,IO更高效 (优化InnoDB表BLOB列的存储效率)
  4. 平衡范式与冗余:效率优先,提高性能. 适时牺牲范式增加冗余
  5. 拒绝3B:拒绝大sql(BIG SQL),大事务(BIG TRANSATION),大批量(BIG BATCH).

二. 命名规范

在MySQL数据库中,表名、字段名、触发器、存储过程以及函数的命名,统一采用26个英文字母(区分大小写)和0-9这十个自然数,加上下划线组成,共63个字符.不能出现其他字符(注释除外),也不能以数字或‘’ 开头,非必须情况下,不使用自然数.

对于MySQL数据库, 表名、字段名统一采用小写字母; (Oracle数据库中表名、字段名统一采用大写字母)若名称过长可采用单词缩写.

1. 表名:

根据表所描述的业务实体,采用英文单词加_的形式命名.若表名太长,英文单词可采用缩写.如:user_info.

2. 视图命名:

视图名加_view”后缀。 如:user_withdraw_view.

MySQL因为没有物化视图,因此视图能不用就尽量少用。对于sql监控来讲,视图的sql存储在数据库中,分析时很不直观。

3. 触发器命名:

触发器功能描述名加“_tr”后缀。 如:insert_balance_hist_tr.

4. 存储过程:

存储过程功能描述名加“_sp”后缀。如:load_user_trade_sp.

5. 函数名:

函数功能描述加“_fn”后缀。 如:generate_password_fn.

6. 键名:

  • 主键:主键字段或主键描述加“_pk” 如:user_id_pk
  • 外键:外键字段 加“_fk” 如:user_id_fk
  • 索引:索引字段或索引描述 加 _idx 如:sex_idx.

三.设计规范

1. 注释

表结构中须包含表注释和列注释. 对于函数、触发器以及存储过程等,代码开头应有阐述其功能的注释.若有复杂逻辑,则应加上局部注释.

2. 数据引擎选择

全部选择InnoDB。MyISAM一旦出现系统宕机或者进程崩溃情况容易造成存储数据损坏。

此外,频繁读写的InnoDB表,一定要使用具有自增/顺序特征的整型作为显式主键。

为什么InnoDB表要建议用自增列做主键

3. 编码

所有数据表均采用UTF8编码,并在表DDL中明确标出.所有字段都不单独设编码,即采用默认的表编码UTF8.

比如可以设置表的编码

CREATE DATABASE IF NOT EXISTS my_db default charset utf8 COLLATE utf8_general_ci;

注意后面这句话COLLATE utf8_general_ci,意思是在排序时根据utf8校验集来排序,那么在这个数据库下创建的所有数据表的默认字符集都会是utf8了

4. 字段选择

4.1 IP字段

如果是使用的IPV4,则使用int存储不使用char(15).

在MySQL中提供了INER_ATONO()和INET_NTOA()函数来对IP和数字之间进行转换. 前者提供IP到数字的转换后者提供数字到IP的转换.

insert into table column(ipvalues(INET_ATONO('127.0.0.1')) ;

如业务需求需要存储IPV6,可采用varchar(40)类型.

4.2 手机字段

如果考虑到varchar占用空间大影响查询性能,请使用bigint来存储手机号码.

  • 不要使用int,因为int类型的最大长度不能超过11位
  • 如果手机号码中含有地区码,则用varchar

4.3 enum,set和tinyint类型使用

枚举类型可以使用ENUM,ENUM的内部存储机制是采用TINYINT或SMALLINT(并非CHAR/VARCHAR)。

注意:ENUM类型扩展性较差,如果新增枚举值,需要修改表字段定义,而且在执行ddl时会对性能有影响

4.4 金额字段

对于金额字段,统一采用decimal(17,0)类型,金额以“分”为单位保存.

4.5 时间字段

时间字段优先考虑datetime.

4.6 精确浮点数字段必须使用decimal替代float和double

MySQL中的数值类型(不包括整型)IEEE754浮点数:单精度(float)、双精度(double和real)、 定点数(decimal和numeric).

float,double等非标准类型,在DB中保存的是近似值,而decimal则以字符串的形式保存数值

4.7 辅助字段

为便于数据分析,所有表必须添加两个字段:

raw_add_time timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
raw_update_time timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间'

这两个字段只记录每条记录的创建时间及更新时间.

5 禁止在数据库里存图片和文件

禁止在数据库中使用varbinary、blob、text存储图片和文件.

6 数据库完整性要求

不在数据库层面约束数据的完整性,数据的完整性由程序来保证.

所以也禁止使用外键。

四. 索引规范

MySQL的查询速度依赖良好的索引设计,因此索引对于高性能至关重要.

合理的索引(哪怕是基于索引的条件过滤,如果优化器意识到总共需要扫描的数据量超过30%时,就会直接改变执行计划为全表扫描,不再使用索引。)会加快查询速度(包括UPDATE和DELETE的速度,MySQL会将包含该行的page加载到内存中,然后进行UPDATE或者DELETE操作),不合理的索引会降低速度.如果没有索引,MySQL会进行全表扫描,消耗大量IO.

1 谨慎合理的添加索引

索引能改善查询的效率,但是也会增加额外的开销并减慢更新的速度(更新时会同时更新索引). 索引的数量不是越多越好,能不加的索引尽量不加. InnoDB的secondary index(非主键索引)使用b+tree来存储,因此在UPDATE、DELETE、INSERT的时候需要对b+tree进行调整,过多的索引会减慢更新的速度.

按照目前的业务需求,单表的索引应符合下列要求:

  • 索引数量控制在5个左右,单个索引的字段不超过5个.在设计的时候要结合SQL和需求考虑索引的覆盖.
  • 唯一键由3个以下字段组成,当字段都是整形时,使用唯一键作为主键.
  • 唯一键不和主键重复,即不得在主键上建唯一索引.
  • 较长的字段需加入前缀索引来减少索引长度,提高效率.

例子如下:

create table url( address VARCHAR(100) NOT NULL,index idx_url(address(10)));

前缀索引长度依据索引的覆盖率来定,建立索引之前最好查看下对应字段建立索引的概率.MySQL5.6优化了合并索引,也就是说一条SQL上可以使用两个索引了.

2 提高索引的覆盖率

合理利用覆盖索引.

关于覆盖索引:InnoDB 存储引擎中,secondary index(非主键索引)中没有直接存储行地址,而存储主键值. 如果用户需要查询secondary index中所不包含的数据列时,需要先通过secondary index查找到主键值,然后再通过主键查询到其他数据列,因此需要查询两次. 覆盖索引的概念就是查询可以通过在一个索引中完成,覆盖索引效率会比较高,主键查询是覆盖索引.

合理的创建索引以及合理的使用查询语句,当使用到覆盖索引时可以获得性能提升.
比如SELECT email,uid FROM user_email WHERE uid=xxx,可以将索引添加为index(uid,email),以提升性能.

索引字段的顺序需要考虑字段值去重之后的个数,个数多的放在前面.
合理创建联合索引(避免冗余),(a,b,c相当于 (a、(a,b、(a,b,c). 遵循最左原则.
UPDATE、DELETE语句需要根据WHERE条件添加索引.

3 索引使用需要注意的事项

  • 不建议使用%前缀模糊查询,例如LIKE "%xxx",这样会扫全表.
  • 不要在索引列进行数学或者函数计算.

    select * from table where id +1 =10000;
    

    这样不会使用索引,导致扫全表,改为:

    select * from table where id =10000-1;
    
  • 使用EXPLAIN判断SQL语句是否合理使用索引,尽量避免extra列出现:Using File Sort,Using Temporary.

    下面列出extra列常见的值:

    a. Using Temporary

    为了解决查询,MySQL需要创建一个临时表来容纳结果. 典型情况如查询包含可以按不同情况列出列的GROUP BY和ORDER BY子句. 使用临时表的开销是比较大的.

    b. Using File Sort

    MySQL需要额外的一次传递,以找出如何按排序顺序检索行. 出现这个说明SQL没有走索引. MySQL通过根据联接类型浏览所有行并为所有匹配WHERE子句的行保存排序关键字和行的指针来完成排序.

    c. Using index

    从只使用索引树中的信息而不需要进一步搜索读取实际的行来检索表中的列信息.

    d. Using where

    MySQL使用where条件进行过滤找到匹配行返回客户端.

  • 禁止使用外键. 因为会产生额外开销,并且是逐行进行操作. 最关键的是在高并发的情况下很容易造成死锁.

  • SQL变更需要确认索引是否需要变更,并通知DBA.

五. SQL规范

1. 核心思想

  • 使用prepared statement,可以提高性能并且避免SQL注入.
  • 尽量避免大SQL,因为在高并发的情况下,一个大SQL容易堵死数据库.

2. 注意事项

线上MySQL采用的是5.6版本,所以下列都以5.6版本为例子.

2.1 使用合理的分页方式以提高分页的效率.

例子如下:

SELECT * FROM TABLE ORDER BY IDLIMIT 1000000,10;

这种分页方式会导致大量的io,因为MySQL使用的是提前读取策略, LIMIT越大效率越低. 另外UPDATE、DELETE语句不使用LIMIT.

上面sql应改为:

SELECT * FROM TABLE WHERE ID > LAST_ID ORDER BY ID LIMIT 10 (LAST_ID为具体值)

2.2 SELECT语句只获取需要的字段

用select * 时会消耗更多的CPU,内存,IO,当表越大时消耗越大.

只读取有效的字段,可以提高索引覆盖,而且更安全(可以减少表变化带来的影响),减少网络传输、磁盘io时间。

2.3 避免负向查询和前缀模糊查询

前缀模糊查询是不能使用索引. 负向查询是!=,<>, not in ,not exits,not like.是会导致无法使用索引的.

2.4 WHERE条件中必须使用合适的类型

WHERE条件中的字符类型与对应字段的字符类型要一致。当类型不匹配时,MySQL会使用隐式转换,此时不会走索引.

2.5 OR改写为IN

同一字段将OR改为IN. OR的效率为O(N),IN的效率为O(LOG N).N越大差距越大,当N很大时,OR会慢很多. 另外在使用IN的时候注意控制IN中的N个数.

2.6 合理的排序

随机排序不要使用ORDER BY RAND(),使用其他方法替换.

SELECT * FROM test1 ORDER BY RAND(LIMIT 1)

该语句EXPLAIN type为ALL EXTRA为 Using File Sort.
可以改为:

SELECT id FROM test1 ORDER BY RAND(LIMIT 1);
SELECT * FROM test1 WHERE id=?;

这2个语句都使用到了索引,性能比较好.

2.7 HAVING子句

HAVING在检索出所有记录后才会对结果集进行过滤.如果能通过where子句限制记录数量,那就能减少开销.

比如将:

select * from test1 group by id having id >3;

替换为

select * from test1 where id >3 group by id;

2.8 使用合理的SQL语句减少与数据库的交互次数.

减少与数据库的交互次数的SQL:

`INSERT IGNORE`
`INSERT INTO values()`

改为

`INSERT … ON DUPLICATE KEY UPDATE`

ON DUPLICATE KEY UPDATE 是一种高效的唯一键或者主键冲突判断. 冲突则执行UPDATE,不冲突则执行INSERT语句.

2.9 COUNT(*)

在不加WHRER条件下count(col)跟count(*)差不多,但是在加入了WHERE条件后count(*)的性能比count(col)和count(1)好. MySQLcount(*) 对作了特殊处理.

2.10 or和union

在MySQL5.6中优化了合并索引.一条SQL可以使用2个索引(INDEX_MERGE).但是如果三个字段的索引则使用不上索引合并.3个字段条件查找时候用UNION替代OR,如果不需要去重则用UNION ALL代替OR.(被查找的字段上都有相应的索引)

例如,下面的例子在数据均匀分布的情况下:

`SELECT * FROM test1 WHERE id ='3' OR address ='5' or age =10`

改为

SELECT * FROM test1 WHERE id ='1'
UNION SELECT * FROM test1 WHERE address ='5'
UNION SELETE * FROM test1 WHERE age ='10'`

第一种只执行一步但是无法使用索引会全表扫描.第二种会执行4步,分别查询值然后再union result.虽然步骤多但是查询走索引,union result只是从union临时表获取结果集合. 性能比第一种好一些.

2.11 触发器,存储过程

尽量减少触发器,存储过程的使用和MySQL函数对结果集的处理.

2.12 事务

事务的原则是即开即用,用完即停.与事务无关的操作放到事务外面,减少锁资源的占用.

六. 数据库基本优化策略

参考 面向程序员的数据库访问性能优化法则

数据库优化策略有以下几种方式:

  • 减少数据访问(减少磁盘访问)
  • 返回更少数据(减少网络传输或磁盘访问)
  • 减少交互次数(减少网络传输)
  • 减少服务器CPU开销(减少CPU及内存开销)
  • 利用更多资源(增加资源)

这几种方法的收益如下图:
image

image

下面列出每种优化方式的一些具体方法。

1 减少数据访问

1.1 创建并使用正确的索引

通过合理的创建和使用索引来减少数据访问。

1.2 尽量通过索引访问数据

合理利用覆盖索引.

1.3 优化SQL执行计划

SQL执行计划是关系型数据库最核心的技术之一,它表示SQL执行时的数据访问算法。通过执行计划可以判断SQL是否合理。

2 返回更少数据

2.1 数据分页处理

通过分页的处理,减少数据的返回量。还有通过分表控制表的大小,增加查询的效率。

2.2 只返回需要的字段

通过去除不必要的返回字段可以提高性能。

3 减少交互次数

3.1 批量提交

通过批量提交的方式来减少交互次数,如当你要往一个表中插入1000万条数据时,如果采用普通的executeUpdate处理,那么和服务器交互次数为1000万次,按每秒钟可以向数据库服务器提交10000次估算,要完成所有工作需要1000秒。如果采用批量提交模式,1000条提交一次,那么和服务器交互次数为1万次,交互次数大大减少。

3.2 合并查询语句

for :var in ids[] do begin
      select * from mytable where id=:var;
end;

我们也可以做一个小的优化, 如下所示,用ID INLIST的这种方式写SQL:

select * from mytable where id in(:id1,id2,...,idn);

3.3 设置Fetch Size

当我们采用select从数据库查询数据时,数据默认并不是一条一条返回给客户端的,也不是一次全部返回客户端的,而是根据客户端fetch_size参数处理,每次只返回fetch_size条记录,当客户端游标遍历到尾部时再从服务端取数据,直到最后全部传送完成。所以如果我们要从服务端一次取大量数据时,可以加大fetch_size,这样可以减少结果数据传输的交互次数及服务器数据准备时间,提高性能。

4 减少CPU开销

4.1 使用绑定变量

绑定变量是指SQL中对变化的值采用变量参数的形式提交,而不是在SQL中直接拼写对应的值。这样可以防止SQL注入并且提高SQL解析性能(硬解析变为软解析).

4.2 大量复杂运算在客户端处理

一些复杂的运算,不要用数据库来处理.如含小数的对数及指数运算和加密处理等.

4.3 减少特殊比较操作和合理使用排序

我们SQL的业务逻辑经常会包含一些比较操作,如a=b,a<b之类的操作,对于这些比较操作数据库都体现得很好,但是对一些特殊的比较操作,我们需要保持警惕. (如:like和IN(1,2,….n)n值过多),另外在使用SQL的确定是否需要排序,大量数据排序会增加CPU的开销。详细例子在后面章节列出。


spring-boot应用前后端分离工程实践

发表于 2018-05-02 | 分类于 java

前后端分离

https://blog.codecentric.de/en/2018/04/spring-boot-vuejs/

前后端分离实施比较简单,但是会引入额外的开发、部署成本,这篇文章思路值得借鉴。

1. 前后端物理分离

前端项目和后端项目由不同的团队开发,并且要维持前端和后端代码的一致(单一仓库原则)。通过maven多模块工程划分前端子模块、后端子模块。

2. 构建方案

2.1 前端环境

首先保证前端构建环境一致,使用frontend-maven-plugin插件安装基础环境并在打包时执行前端构建命令。

2.2 all in one jar

部署时可以引入nginx来serve静态页面,动态请求反向代理到后端服务器。但是对于小型项目来说,这样的部署成本太大了,每次上线成本也比较大。理想的方式是用后端应用服务器提供静态页面输出能力,前端后端all in one jar.

使用maven-resources-plugin插件把前端模块的打包输出物拷贝到后端项目中,最终打一个jar包。

2.3 打包

通过以上的工作,使用mvn clean package即可以把项目打成一个jar包。

3. 前端开发

前后端开发过程中,前端访问后端服务面临js同源策略问题。可以使用CORS方案解决,文中给出了一个很好的解决方案,使用http-proxy-middleware,把某些路径的请求转发到后端服务器,优雅的避免了同源问题。

前端开发过程中,需要mock掉后端服务器,可以使用http://rapapi.org,后端服务开发人员提供接口契约,前端开发前期使用mock数据。

4. 工程实践

4.1 项目结构

我们的工程结构大致如下:

xxx-assemble (项目打包,依赖core和frontend)
xxx-core (核心代码)
xxx-frontend (前端代码)

4.2 配置

4.2.1. 前端模块
<build>
    <plugins>
        <plugin>
            <groupId>com.github.eirslett</groupId>
            <artifactId>frontend-maven-plugin</artifactId>
            <version>1.6</version>
            <executions>
                <!-- Install our node and npm version to run npm/node scripts-->
                <execution>
                    <id>install node and npm</id>
                    <goals>
                        <goal>install-node-and-npm</goal>
                    </goals>
                    <configuration>
                        <nodeVersion>v9.11.1</nodeVersion>
                    </configuration>
                </execution>
                <!-- Install all project dependencies -->
                <execution>
                    <id>npm install</id>
                    <goals>
                        <goal>npm</goal>
                    </goals>
                    <!-- optional: default phase is "generate-resources" -->
                    <phase>generate-resources</phase>
                    <!-- Optional configuration which provides for running any npm command -->
                    <configuration>
                        <arguments>install</arguments>
                    </configuration>
                </execution>
                <!-- Build and minify static files -->
                <execution>
                    <id>npm run build</id>
                    <goals>
                        <goal>npm</goal>
                    </goals>
                    <phase>generate-resources</phase>
                    <configuration>
                        <arguments>run build</arguments>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
4.2.2. 打包模块
<plugin>
        <artifactId>maven-resources-plugin</artifactId>
        <executions>
            <execution>
                <id>copy frontend content</id>
                <phase>generate-resources</phase>
                <goals>
                    <goal>copy-resources</goal>
                </goals>
                <configuration>
                <!-- copy all build frontend resouces to assemble module  -->
                    <outputDirectory>src/main/resources/public</outputDirectory>
                    <overwrite>true</overwrite>
                    <resources>
                        <resource>
                            <directory>${project.parent.basedir}/xxx-frontend/dist</directory>
                            <filtering>false</filtering>
                            <includes>
                                <include>**</include>
                            </includes>
                        </resource>
                    </resources>
                </configuration>
            </execution>
        </executions>
    </plugin>
4.2.3 git ignore

在git ignore文件中添加

node_modules
xxx-frontend/node
xxx-frontend/dist
xxx-assemble/src/main/resources/public

4.3 相关命令

#安装node/npm依赖
mvn -T 1C clean install -Dmaven.test.skip=true
#打包
mvn -T 1C clean package -Dmaven.test.skip=true
#线上运行
java -jar xxx-assemble/target/xxx.jar
#在打包模块通过maven运行
mvn spring-boot:run
#构建前端代码
npm run build
12…7
qiubo

qiubo

Keep running!

66 日志
2 分类
76 标签
Links
  • qiuduo
  • daidai
  • bingwei
© 2013 — 2019 qiubo
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4