使用 Spring Boot AOP 实现 Web 日志处理和分布式锁

AOP

AOP 的全称为 Aspect Oriented Programming,译为面向切面编程。实际上 AOP 就是通过预编译和运行期动态代理实现程序功能的统一维护的一种技术。在不同的技术栈中 AOP 有着不同的实现,但是其作用都相差不远,我们通过 AOP 为既有的程序定义一个切入点,然后在切入点前后插入不同的执行内容,以达到在不修改原有代码业务逻辑的前提下统一处理一些内容(比如日志处理、分布式锁)的目的。

为什么要使用 AOP

在实际的开发过程中,我们的应用程序会被分为很多层。通常来讲一个 Java 的 Web 程序会拥有以下几个层次:

  • Web 层:主要是暴露一些 Restful API 供前端调用。
  • 业务层:主要是处理具体的业务逻辑。
  • 数据持久层:主要负责数据库的相关操作(增删改查)。

虽然看起来每一层都做着全然不同的事情,但是实际上总会有一些类似的代码,比如日志打印和安全验证等等相关的代码。如果我们选择在每一层都独立编写这部分代码,那么久而久之代码将变的很难维护。所以我们提供了另外的一种解决方案: AOP。这样可以保证这些通用的代码被聚合在一起维护,而且我们可以灵活的选择何处需要使用这些代码。

AOP 的核心概念

  • 切面(Aspect):通常是一个类,在里面可以定义切入点和通知。
  • 连接点(Joint Point):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中连接点指的就是被拦截的到的方法,实际上连接点还可以是字段或者构造器。
  • 切入点(Pointcut):对连接点进行拦截的定义。
  • 通知(Advice):拦截到连接点之后所要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
  • AOP 代理:AOP 框架创建的对象,代理就是目标对象的加强。Spring 中的 AOP 代理可以使 JDK 动态代理,也可以是 CGLIB 代理,前者基于接口,后者基于子类。

Spring AOP

Spring 中的 AOP 代理还是离不开 Spring 的 IOC 容器,代理的生成,管理及其依赖关系都是由 IOC 容器负责,Spring 默认使用 JDK 动态代理,在需要代理类而不是代理接口的时候,Spring 会自动切换为使用 CGLIB 代理,不过现在的项目都是面向接口编程,所以 JDK 动态代理相对来说用的还是多一些。在本文中,我们将以注解结合 AOP 的方式来分别实现 Web 日志处理和分布式锁。

Spring AOP 相关注解

  • @Aspect: 将一个 java 类定义为切面类。
  • @Pointcut:定义一个切入点,可以是一个规则表达式,比如下例中某个 package 下的所有函数,也可以是一个注解等。
  • @Before:在切入点开始处切入内容。
  • @After:在切入点结尾处切入内容。
  • @AfterReturning:在切入点 return 内容之后切入内容(可以用来对处理返回值做一些加工处理)。
  • @Around:在切入点前后切入内容,并自己控制何时执行切入点自身的内容。
  • @AfterThrowing:用来处理当切入内容部分抛出异常之后的处理逻辑。

其中 @Before@After@AfterReturning@Around@AfterThrowing 都属于通知。

AOP 顺序问题

在实际情况下,我们对同一个接口做多个切面,比如日志打印、分布式锁、权限校验等等。这时候我们就会面临一个优先级的问题,这么多的切面该如何告知 Spring 执行顺序呢?这就需要我们定义每个切面的优先级,我们可以使用 @Order(i) 注解来标识切面的优先级, i 的值越小,优先级越高。假设现在我们一共有两个切面,一个 WebLogAspect,我们为其设置 @Order(100);而另外一个切面 DistributeLockAspect 设置为 @Order(99),所以 DistributeLockAspect 有更高的优先级,这个时候执行顺序是这样的:在 @Before 中优先执行 @Order(99) 的内容,再执行 @Order(100) 的内容。而在 @After 和 @AfterReturning 中则优先执行 @Order(100) 的内容,再执行 @Order(99) 的内容,可以理解为先进后出的原则。

基于注解的 AOP 配置

使用注解一方面可以减少我们的配置,另一方面注解在编译期间就可以验证正确性,查错相对比较容易,而且配置起来也相当方便。相信大家也都有所了解,我们现在的 Spring 项目里面使用了非常多的注解替代了之前的 xml 配置。而将注解与 AOP 配合使用也是我们最常用的方式,在本文中我们将以这种模式实现 Web 日志统一处理和分布式锁两个注解。下面就让我们从准备工作开始吧。

准备工作

准备一个 Spring Boot 的 Web 项目

你可以通过 Spring Initializr 页面生成一个空的 Spring Boot 项目,当然也可以下载 springboot-pom.xml 文件,然后使用 maven 构建一个 Spring Boot 项目。项目创建完成后,为了方便后面代码的编写你可以将其导入到你喜欢的 IDE 中,我这里选择了 Intelli IDEA 打开。

添加依赖

我们需要添加 Web 依赖和 AOP 相关依赖,只需要在 pom.xml 中添加如下内容即可:

清单 1. 添加 web 依赖
1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
清单 2. 添加 AOP 相关依赖
1
2
3
4
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

其他准备工作

为了方便测试我还在项目中集成了 Swagger 文档,具体的集成方法可以参照在 Spring Boot 项目中使用 Swagger 文档。另外编写了两个接口以供测试使用,具体可以参考本文源码。由于本教程所实现的分布式锁是基于 Redis 缓存的,所以需要安装 Redis 或者准备一台 Redis 服务器。

利用 AOP 实现 Web 日志处理

为什么要实现 Web 日志统一处理

在实际的开发过程中,我们会需要将接口的出请求参数、返回数据甚至接口的消耗时间都以日志的形式打印出来以便排查问题,有些比较重要的接口甚至还需要将这些信息写入到数据库。而这部分代码相对来讲比较相似,为了提高代码的复用率,我们可以以 AOP 的方式将这种类似的代码封装起来。

Web 日志注解

清单 3. Web 日志注解代码
1
2
3
4
5
6
7
8
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ControllerWebLog {
     String name();
     boolean intoDb() default false;
}

其中 name 为所调用接口的名称,intoDb 则标识该条操作日志是否需要持久化存储,Spring Boot 连接数据库的配置,可以参考 SpringBoot 项目配置多数据源这篇文章,具体的数据库结构可以点击这里获取。现在注解有了,我们接下来需要编写与该注解配套的 AOP 切面。

实现 WebLogAspect 切面

  1. 首先我们定义了一个切面类 WebLogAspect 如清单 4 所示。其中@Aspect 注解是告诉 Spring 将该类作为一个切面管理,@Component 注解是说明该类作为一个 Spring 组件。
    清单 4. WebLogAspect
    1
    2
    3
    4
    5
    @Aspect
    @Component
    @Order(100)
    public class WebLogAspect {
    }
  2. 接下来我们需要定义一个切点。
    清单 5. Web 日志 AOP 切点
    1
    2
    @Pointcut("execution(* cn.itweknow.sbaop.controller..*.*(..))")
    public void webLog() {}

    对于 execution 表达式,官网的介绍为(翻译后):

    清单 6. 官网对 execution 表达式的介绍
    1
    execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

    其中除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。这个解释可能有点难理解,下面我们通过一个具体的例子来了解一下。在 WebLogAspect 中我们定义了一个切点,其 execution 表达式为 * cn.itweknow.sbaop.controller..*.*(..),下表为该表达式比较通俗的解析:

    表 1. execution() 表达式解析
  3. @Before 修饰的方法中的内容会在进入切点之前执行,在这个部分我们需要打印一个开始执行的日志,并将请求参数和开始调用的时间存储在 ThreadLocal 中,方便在后面结束调用时打印参数和计算接口耗时。
    清单 7. @Before 代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Before(value = "webLog()& &  @annotation(controllerWebLog)")
        public void doBefore(JoinPoint joinPoint, ControllerWebLog controllerWebLog) {
            // 开始时间。
            long startTime = System.currentTimeMillis();
            Map<String, Object> threadInfo = new HashMap<>();
            threadInfo.put(START_TIME, startTime);
            // 请求参数。
            StringBuilder requestStr = new StringBuilder();
            Object[] args = joinPoint.getArgs();
            if (args != null && args.length > 0) {
                for (Object arg : args) {
                    requestStr.append(arg.toString());
                }
            }
            threadInfo.put(REQUEST_PARAMS, requestStr.toString());
            threadLocal.set(threadInfo);
            logger.info("{}接口开始调用:requestData={}", controllerWebLog.name(), threadInfo.get(REQUEST_PARAMS));
     }
  4. @AfterReturning,当程序正常执行有正确的返回时执行,我们在这里打印结束日志,最后不能忘的是清除 ThreadLocal 里的内容。
    清单 8. @AfterReturning 代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @AfterReturning(value = "webLog()&& @annotation(controllerWebLog)", returning = "res")
    public void doAfterReturning(ControllerWebLog controllerWebLog, Object res) {
            Map<String, Object> threadInfo = threadLocal.get();
            long takeTime = System.currentTimeMillis() - (long) threadInfo.getOrDefault(START_TIME, System.currentTimeMillis());
            if (controllerWebLog.intoDb()) {
                insertResult(controllerWebLog.name(), (String) threadInfo.getOrDefault(REQUEST_PARAMS, ""),
                            JSON.toJSONString(res), takeTime);
            }
            threadLocal.remove();
            logger.info("{}接口结束调用:耗时={}ms,result={}", controllerWebLog.name(),
                    takeTime, res);
    }
  5. 当程序发生异常时,我们也需要将异常日志打印出来:
    清单 9. @AfterThrowing 代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @AfterThrowing(value = "webLog()& &  @annotation(controllerWebLog)", throwing = "throwable")
        public void doAfterThrowing(ControllerWebLog controllerWebLog, Throwable throwable) {
            Map< String, Object> threadInfo = threadLocal.get();
            if (controllerWebLog.intoDb()) {
                insertError(controllerWebLog.name(), (String)threadInfo.getOrDefault(REQUEST_PARAMS, ""),
                        throwable);
            }
            threadLocal.remove();
            logger.error("{}接口调用异常,异常信息{}",controllerWebLog.name(), throwable);
    }
  6. 至此,我们的切面已经编写完成了。下面我们需要将 ControllerWebLog 注解使用在我们的测试接口上,接口内部的代码已省略,如有需要的话,请参照本文源码
    清单 10. 测试接口代码
    1
    2
    3
    4
    5
    @PostMapping("/post-test")
    @ApiOperation("接口日志 POST 请求测试")
    @ControllerWebLog(name = "接口日志 POST 请求测试", intoDb = true)
    public BaseResponse postTest(@RequestBody BaseRequest baseRequest) {
    }
  7. 最后,启动项目,然后打开 Swagger 文档进行测试,调用接口后在控制台就会看到类似图 1 这样的日志。
    图 1. 基于 Redis 的分布式锁测试效果

    基于 Redis 的分布式锁测试效果

利用 AOP 实现分布式锁

为什么要使用分布式锁

我们程序中多多少少会有一些共享的资源或者数据,在某些时候我们需要保证同一时间只能有一个线程访问或者操作它们。在传统的单机部署的情况下,我们简单的使用 Java 提供的并发相关的 API 处理即可。但是现在大多数服务都采用分布式的部署方式,我们就需要提供一个跨进程的互斥机制来控制共享资源的访问,这种互斥机制就是我们所说的分布式锁。

注意

  1. 互斥性。在任时刻,只有一个客户端能持有锁。
  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。这个其实只要我们给锁加上超时时间即可。
  3. 具有容错性。只要大部分的 Redis 节点正常运行,客户端就可以加锁和解锁。
  4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

分布式锁注解

清单 11. 分布式锁注解
1
2
3
4
5
6
7
8
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DistributeLock {
    String key();
    long timeout() default 5;
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

其中,key 为分布式所的 key 值,timeout 为锁的超时时间,默认为 5,timeUnit 为超时时间的单位,默认为秒。

注解参数解析器

由于注解属性在指定的时候只能为常量,我们无法直接使用方法的参数。而在绝大多数的情况下分布式锁的 key 值是需要包含方法的一个或者多个参数的,这就需要我们将这些参数的位置以某种特殊的字符串表示出来,然后通过参数解析器去动态的解析出来这些参数具体的值,然后拼接到 key 上。在本教程中我也编写了一个参数解析器 AnnotationResolver。篇幅原因,其源码就不直接粘在文中,需要的读者可以查看源码

获取锁方法

清单 12. 获取锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private String getLock(String key, long timeout, TimeUnit timeUnit) {
        try {
            String value = UUID.randomUUID().toString();
            Boolean lockStat = stringRedisTemplate.execute((RedisCallback< Boolean>)connection ->
                    connection.set(key.getBytes(Charset.forName("UTF-8")), value.getBytes(Charset.forName("UTF-8")),
                            Expiration.from(timeout, timeUnit), RedisStringCommands.SetOption.SET_IF_ABSENT));
            if (!lockStat) {
                // 获取锁失败。
                return null;
            }
            return value;
        } catch (Exception e) {
            logger.error("获取分布式锁失败,key={}", key, e);
            return null;
        }
}

RedisStringCommands.SetOption.SET_IF_ABSENT 实际上是使用了 setNX 命令,如果 key 已经存在的话则不进行任何操作返回失败,如果 key 不存在的话则保存 key 并返回成功,该命令在成功的时候返回 1,结束的时候返回 0。我们随机产生了一个 value 并且在获取锁成功的时候返回出去,是为了在释放锁的时候对该值进行比较,这样可以做到解铃还须系铃人,由谁创建的锁就由谁释放。同时还指定了超时时间,这样可以保证锁释放失败的情况下不会造成接口永远不能访问。

释放锁方法

清单 13. 释放锁
1
2
3
4
5
6
7
8
9
10
11
12
13
private void unLock(String key, String value) {
        try {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            boolean unLockStat = stringRedisTemplate.execute((RedisCallback< Boolean>)connection ->
                    connection.eval(script.getBytes(), ReturnType.BOOLEAN, 1,
                            key.getBytes(Charset.forName("UTF-8")), value.getBytes(Charset.forName("UTF-8"))));
            if (!unLockStat) {
                logger.error("释放分布式锁失败,key={},已自动超时,其他线程可能已经重新获取锁", key);
            }
        } catch (Exception e) {
            logger.error("释放分布式锁失败,key={}", key, e);
        }
}

切面

切点和 Web 日志处理的切点一样,这里不再赘述。我们在切面中使用的通知类型为 @Around,在切点之前我们先尝试获取锁,若获取锁失败则直接返回错误信息,若获取锁成功则执行方法体,当方法结束后(无论是正常结束还是异常终止)释放锁。

清单 14. 环绕通知
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Around(value = "distribute()&& @annotation(distributeLock)")
public Object doAround(ProceedingJoinPoint joinPoint, DistributeLock distributeLock) throws Exception {
        String key = annotationResolver.resolver(joinPoint, distributeLock.key());
        String keyValue = getLock(key, distributeLock.timeout(), distributeLock.timeUnit());
        if (StringUtil.isNullOrEmpty(keyValue)) {
            // 获取锁失败。
            return BaseResponse.addError(ErrorCodeEnum.OPERATE_FAILED, "请勿频繁操作");
        }
        // 获取锁成功
        try {
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            return BaseResponse.addError(ErrorCodeEnum.SYSTEM_ERROR, "系统异常");
        } finally {
            // 释放锁。
            unLock(key, keyValue);
        }
}

测试

清单 15. 分布式锁测试代码
1
2
3
4
5
6
7
8
9
10
11
12
@PostMapping("/post-test")
@ApiOperation("接口日志 POST 请求测试")
@ControllerWebLog(name = "接口日志 POST 请求测试", intoDb = true)
@DistributeLock(key = "post_test_#{baseRequest.channel}", timeout = 10)
public BaseResponse postTest(@RequestBody BaseRequest baseRequest) {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return BaseResponse.addResult();
}

在本次测试中我们将锁的超时时间设置为 10 秒钟,在接口中让当前线程睡眠 10 秒,这样可以保证 10 秒钟之内锁不会被释放掉,测试起来更加容易些。启动项目后,我们快速访问两次该接口,注意两次请求的 channel 传值需要一致(因为锁的 key 中包含该值),会发现第二次访问时返回如下结果:

图 2. 基于 Redis 的分布式锁测试效果

基于 Redis 的分布式锁测试效果

这就说明我们的分布式锁已经生效。

结束语

在本教程中,我们主要了解了 AOP 编程以及为什么要使用 AOP。也介绍了如何在 Spring Boot 项目中利用 AOP 实现 Web 日志统一处理和基于 Redis 的分布式锁。你可以在 Github 上找到本教程的完整实现,如果你想对本教程做补充的话欢迎发邮件(gancy.programmer@gmail.com)给我或者直接在 Github 上提交 Pull Reqeust。

参考资源

from:https://www.ibm.com/developerworks/cn/java/j-spring-boot-aop-web-log-processing-and-distributed-locking/index.html

Git:Please make sure you have the correct access rights and the repository exists

1、首先我得重新在git设置一下身份的名字和邮箱
git config –global user.name “yourname”
git config –global user.email“your@email.com”
2.删除.ssh文件夹(直接搜索该文件夹)下的known_hosts
3.$ ssh-keygen -t rsa -C “your@email.com”
.ssh文件夹下生成id_rsa和id_rsa.pub 2个文件,复制id_rsa.pub的全部内容
4.打开https://github.com/,登陆你的账户,[设置]->[ssh设置],将第3步复制的内容粘贴在key中后提交。
5.ssh -T git@github.com

基于大中台小前台模式设计高并发电商架构

一、什么是大中台(业务中台、数据中台、技术中台等)

大中台小前台的组织模式最近在业界很火热,此模式最早在芬兰著名移动游戏公司Supercell实施。在Supercell公司内部以小前台的方式组织了若干个开发团队,每个开发团队包含开发一款游戏所需的各种角色,从而在开发团队内部可以快速决策、快速开发。而支撑这些开发团队的基础设施(机房、网络、架构组件等)、游戏引擎、内部开发测试发布上线工具等则由“部落”(即中台)部门提供。“部落”部门可以根据需要扩展为多个小分队,亦即中台部门划分成多个,但各个小分队都保持共同目标。“部落”作为中台部门,赋能前台业务开发团队,中台部门本身并不直接提供游戏给消费者。

在国内,2015年阿里巴巴业务种类纷繁复杂,业务之间交叉依赖,业务团队众多,不能及时响应业务需求。2015年12月张勇宣布启动中台战略,构建符合DT时代的更具备创新性和灵活性的“大中台,小前台”的组织机制和业务机制,实现管理模式创新。即将产品技术力量和数据运营能力从前台剥离,成为独立的中台,包括搜索事业部、共享业务事业部、数据平台事业部等,为前台即零售电商事业群提供服务。从而前台得到精简,保持足够的敏捷度,更好地满足业务发展和创新需求。2017年5月出版了《企业IT架构转型之道:阿里巴巴中台战略思想和架构实践》,随后很多互联网公司快速跟进中台战略:2017年12月滴滴构建业务中台、2018年12月京东宣布前台、中台、后台组织架构[1]。进入2019年,大中台小前台模式更是在各个公司如火如荼地进行中。

那么中台是什么?中台是一种组织机制和业务机制。在公司组织架构层面通过组织架构调整,物理拆分成独立的中台部门。在公司业务层面通过把公共能力下沉为服务,并做好服务间连接,持续赋能业务部门。可类比航母(大中台)携带和赋能舰载机(小前台)作战(如图1);也可类比为中台生产各种乐高颗粒,传感器和执行器(如图2)。前台把这些颗粒打包集成为各种乐高套装,再加上不同的文档和包装,以及少量个性颗粒(比如特定IP的积木,星战主题积木块),快速形成不同产品卖给不同用户。另一方面,如果开发了10000种SKU的乐高套装,反过来会形成一个强大的乐高积木中台,几乎无所不能,前台产品越多,中台也越强大,中台越强大,前台产品开发也越简单,竞争力极强。

图1 航空母舰和舰载机

图2 乐高颗粒和产品

公司执行好大中台小前台模式,首先需要进行组织架构调整,比如阿里巴巴大中台小前台组织架构(如图3)如下:中台事业群和小前台事业群。其中中台事业群包括:搜索事业部、共享业务事业部(用户、商品、交易等)、数据技术及产品部(OLAP)、基础架构事业部等;小前台事业群包括电商事业群、蚂蚁金服集团、阿里云事业群、菜鸟网络、大文娱集团、阿里妈妈等其他。

图3 阿里巴巴大中台小前台组织架构

公司的交付物是产品,为了让公司更好地完成产品的交付,需要做好业务架构、数据架构、技术架构三个层面。其中业务架构(OLTP)包括个性化的业务架构(小前台)和公共业务架构(中台),数据架构(OLAP)包括个性化的数据架构(小前台)和公共数据架构(中台),技术架构即技术支撑(中台)。这三个层面的架构,我们可以进一步抽象和拆分个性化部分和公共部分。其中个性化的部分即小前台部分,公共部分即中台部分。因此公司的中台分为业务中台、数据中台和技术中台。
假如公司的业务架构采用了目前主流的微服务架构模式(如图4),其中大中台部分包括:网关层、公共业务逻辑层、数据访问层、DB、Cache、配置中心、注册中心,小前台部分包括:业务逻辑层、App端。

图4 业务架构

假如公司的数据架构采用了目前主流的Hadoop生态架构模式(如图5),其中大中台部分包括:PAAS层(数据传输、数据计算、数据存储)、DAAS层(数据源、数据仓库、数据集市 /数据模型),小前台部分包括:DA(Data Application)(留存应用、画像应用、业务报表应用、数据智能应用)。

图5 数据架构

假如公司的技术架构采用了目前主流的技术栈(如图6),其中大中台部分包括:基础平台(消息平台、分布式锁平台、APM、立体监控平台、任务调度平台等)、基础组件(Web框架、RPC框架、分布式事务、数据库中间件等)、服务网格、存储体系(RDBMS、NoSQL、NewSQL)、容器弹性云等。

图6 技术架构

二、什么是小前台

从公司组织架构上来看,公司的个性化业务部门属于小前台,从公司业务服务上来看,公司的个性化业务服务属于小前台。

三、大中台小前台模式适用场景

大中台小前台模式特别有利于业务复制尝试和需要大量尝试创新的新业务,假如把公司的发展周期划分为0-1阶段为初创公司,1-10阶段为高速成长型公司,10-100阶段为稳定发展型公司。那么此模式比较适合10-100阶段,1-10阶段可以开始尝试了,但不适合0-1的初创公司阶段。
大中台需要通过抽象、封装共性能力和知识,可供需要使用的小前台使用(提供内部产品、服务、赋能等),从而使让前台更灵活,降低创新成本,支持更快更轻的试错和创新。

四、大中台小前台电商架构如何设计实践

在电商行业实施大中台小前台的业务架构模式,需要结合业务领域做好两个层面的工作,第一,在公司业务层面通过把公共能力下沉为服务;第二,做好服务的连接,并持续赋能业务部门。
在电商行业内,公共能力下沉为服务,比如把用户、商品、交易、支付、营销、搜索、推荐、风控等服务抽象后下沉为独立的服务。如图7所示的业务架构,其中网关层、公共业务逻辑层、数据访问层、DB、Cache以及注册中心、配置中心等属于电商的公共能力,为电商的中台服务。APP端、小程序端、个性化业务逻辑层等个性化的服务属于小前台部分。

图7 电商业务架构

在电商行业构建大中台小前台的模式中,第二步需要做好公共能力下沉服务的全连接,使得小前台业务可以做到一键接入。如何做好公共服务的全连接呢?首先需要从公司层面定义好业务线的标识标准,比如采用三级体系结构,如表1所示:

表1 业务线标识三级体系结构

公司统一了业务线三级体系结构后,需要提供统一的业务注册中心,使得业务通过业务注册中心完成所有业务线三级体系结构的注册以及查询。其次公司层面需要统一的业务线分发配置服务,分发配置服务的作用是把每个小前台业务需要连接的中台服务集中配置(比如手机前台业务需要接入商品中台、搜索中台、客服中台、交易中台等配置策略),并且配置小前台业务数据分发到每个中台服务的具体的接入策略(比如手机前台业务接入到搜索服务中台,手机业务哪些字段需要建立索引等接入策略),详见表2所示:

表2 业务线分发配置策略

在公司层面具备了统一的业务注册中心和分发配置服务后,需要进一步建立分发连接中心,分发连接中心需要分发两方面的内容:策略流和数据流,第一是策略流,分发业务线分发配置策略到各个中台服务,比如在表2中需要把业务线ID为1的商品数据类型的接入策略分发到表2中配置的商品中台服务、搜索中台服务、推荐中台服务、客服中台服务、数据中台服务等,并把订单数据类型的接入策略分发到表2中配置的搜索中台服务、客服中台服务等。这些中台服务收到分发连接中心的前台数据接入中台策略后,解析这些接入策略,后续对数据流的处理按照这些接入策略进行,完成策略的全连接。第二是数据流,当小前台业务产生相应的数据时,会分发到对应的中台服务。比如手机前台产生商品数据,由分发连接中心分发给相应的商品中台、搜索中台、推荐中台、客服中台、数据中台等,完成数据的全连接。
公司大中台小前台连接生态如图8所示,包含了小前台业务1、业务注册中心、分发配置服务、业务分发连接中心、各个中台服务,图8中包含了一个业务的策略流(黑色连接线)和数据流(红色连接线)具体的分发连接关系。

图8 大中台小前台连接生态

公司具备了大中台小前台的连接生态后,那么小前台业务产生的数据(比如手机业务的商品数据)如何存储呢?以小前台业务产生的商品数据为例,包括了商品公共的数据以及小前台业务个性化的数据。针对商品公共数据和商品个性化数据,存储有两种方案,一是商品公共数据存储在中台部门,商品个性化数据存储在小前台业务部门;第二种方案是商品公共数据和商品个性化数据全部存储在中台部门,有利用数据的统一存储和管理,并且使得业务查询等接入也非常简单。推荐大家使用第二种数据存储方案(同时同学们思考下第一种存储方案带来的问题有哪些?),那么针对商品的公共数据和个性化数据设计存储表结构:商品公共数据表 +商品业务个性化扩展数据表,其中商品公共数据表包含了所有业务线商品公共的字段,如表3所示:商品ID、发布人、分类ID、价格、发布时间、商品库存、商品状态等等。

图3 商品公共数据表

其中商品个性化数据表(如表4)采用Key,Value扩展列的方式进行存储,比如Key的类型可以固定几种类型:比如Long类型、Double类型、String类型,业务个性化数据都使用固定的几种数据类型来表示和存储,列中Key的含义在映射表(如表5)中指定了每个Key具体的的业务字段含义。

表4 商品业务个性化扩展数据表

表5 商品个性化字段映射数据表

通过以上大中台小前台的连接生态以及公共数据表和业务个性化数据表的存储方式,使得大中台小前台模式在公司内得以很好的落地和实践。
参考文献:

[1] 中台战略-中台建设与数字商业:机械工业出版社

from: 欢迎关注公众号 架构之美

一份非常完整的MySQL规范

一、数据库命令规范

  • 所有数据库对象名称必须使用小写字母并用下划线分割
  • 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来)
  • 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符
  • 临时库表必须以tmp_为前缀并以日期为后缀,备份表必须以bak_为前缀并以日期(时间戳)为后缀
  • 所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)

二、数据库基本设计规范

1、所有表必须使用Innodb存储引擎

没有特殊要求(即Innodb无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用Innodb存储引擎(mysql5.5之前默认使用Myisam,5.6以后默认的为Innodb)Innodb 支持事务,支持行级锁,更好的恢复性,高并发下性能更好

2、数据库和表的字符集统一使用UTF8

兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效

3、所有表和字段都需要添加注释

使用comment从句添加表和列的备注 从一开始就进行数据字典的维护

4、尽量控制单表数据量的大小,建议控制在500万以内

500万并不是MySQL数据库的限制,过大会造成修改表结构,备份,恢复都会有很大的问题

可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小

5、谨慎使用MySQL分区表

分区表在物理上表现为多个文件,在逻辑上表现为一个表 谨慎选择分区键,跨分区查询效率可能更低 建议采用物理分表的方式管理大数据

6、尽量做到冷热数据分离,减小表的宽度

MySQL限制每个表最多存储4096列,并且每一行数据的大小不能超过65535字节 减少磁盘IO,保证热数据的内存缓存命中率(表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的IO) 更有效的利用缓存,避免读入无用的冷数据 经常一起使用的列放到一个表中(避免更多的关联操作)

7、禁止在表中建立预留字段

预留字段的命名很难做到见名识义 预留字段无法确认存储的数据类型,所以无法选择合适的类型 对预留字段类型的修改,会对表进行锁定

8、禁止在数据库中存储图片,文件等大的二进制数据

通常文件很大,会短时间内造成数据量快速增长,数据库进行数据库读取时,通常会进行大量的随机IO操作,文件很大时,IO操作很耗时 通常存储于文件服务器,数据库只存储文件地址信息

9、禁止在线上做数据库压力测试

10、禁止从开发环境,测试环境直接连接生成环境数据库

三、数据库字段设计规范

1、优先选择符合存储需要的最小的数据类型

  • 原因

列的字段越大,建立索引时所需要的空间也就越大,这样一页中所能存储的索引节点的数量也就越少也越少,在遍历时所需要的IO次数也就越多, 索引的性能也就越差

  • 方法

1)将字符串转换成数字类型存储,如:将IP地址转换成整形数据。

mysql提供了两个方法来处理ip地址:

inet_aton 把ip转为无符号整型(4-8位)

inet_ntoa 把整型的ip转为地址

插入数据前,先用inet_aton把ip地址转为整型,可以节省空间。显示数据时,使用inet_ntoa把整型的ip地址转为地址显示即可。

2)对于非负型的数据(如自增ID、整型IP)来说,要优先使用无符号整型来存储

因为:无符号相对于有符号可以多出一倍的存储空间

SIGNED INT -2147483648~2147483647

UNSIGNED INT 0~4294967295

VARCHAR(N)中的N代表的是字符数,而不是字节数

使用UTF8存储255个汉字 Varchar(255)=765个字节。过大的长度会消耗更多的内存

2、避免使用TEXT、BLOB数据类型,最常见的TEXT类型可以存储64k的数据

  • 建议把BLOB或是TEXT列分离到单独的扩展表中

Mysql内存临时表不支持TEXT、BLOB这样的大数据类型,如果查询中包含这样的数据,在排序等操作时,就不能使用内存临时表,必须使用磁盘临时表进行。

而且对于这种数据,Mysql还是要进行二次查询,会使sql性能变得很差,但是不是说一定不能使用这样的数据类型。

如果一定要使用,建议把BLOB或是TEXT列分离到单独的扩展表中,查询时一定不要使用select * 而只需要取出必要的列,不需要TEXT列的数据时不要对该列进行查询。

  • TEXT或BLOB类型只能使用前缀索引

因为MySQL对索引字段长度是有限制的,所以TEXT类型只能使用前缀索引,并且TEXT列上是不能有默认值的。

3、避免使用ENUM类型

  • 修改ENUM值需要使用ALTER语句
  • ENUM类型的ORDER BY操作效率低,需要额外操作
  • 禁止使用数值作为ENUM的枚举值

4、尽可能把所有列定义为NOT

原因:

  • 索引列需要额外的空间来保存,所以要占用更多的空间;
  • 进行比较和计算时要对值做特别的处理

5、使用TIMESTAMP(4个字节)或DATETIME类型(8个字节)存储时间

TIMESTAMP 占用4字节和INT相同,但比INT可读性高

超出TIMESTAMP取值范围的使用DATETIME类型存储。

经常会有人用字符串存储日期型的数据(不正确的做法):

  • 缺点1:无法用日期函数进行计算和比较
  • 缺点2:用字符串存储日期要占用更多的空间

6、同财务相关的金额类数据必须使用decimal类型

  • 非精准浮点:float,double
  • 精准浮点:decimal

Decimal类型为精准浮点数,在计算时不会丢失精度。占用空间由定义的宽度决定,每4个字节可以存储9位数字,并且小数点要占用一个字节。可用于存储比bigint更大的整型数据。

四、索引设计规范

1、限制每张表上的索引数量,建议单张表索引不超过5个

索引并不是越多越好!索引可以提高效率同样可以降低效率。

索引可以增加查询效率,但同样也会降低插入和更新的效率,甚至有些情况下会降低查询效率。

因为mysql优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,就会增加mysql优化器生成执行计划的时间,同样会降低查询性能。

2、禁止给表中的每一列都建立单独的索引

5.6版本之前,一个sql只能使用到一个表中的一个索引,5.6以后,虽然有了合并索引的优化方式,但是还是远远没有使用一个联合索引的查询方式好

3、每个Innodb表必须有个主键

Innodb是一种索引组织表:数据的存储的逻辑顺序和索引的顺序是相同的。

每个表都可以有多个索引,但是表的存储顺序只能有一种 Innodb是按照主键索引的顺序来组织表的。

不要使用更新频繁的列作为主键,不适用多列主键(相当于联合索引) 不要使用UUID、MD5、HASH、字符串列作为主键(无法保证数据的顺序增长)。

主键建议使用自增ID值。

五、常见索引列建议

  • 出现在SELECT、UPDATE、DELETE语句的WHERE从句中的列
  • 包含在ORDER BY、GROUP BY、DISTINCT中的字段

    并不要将符合1和2中的字段的列都建立一个索引,通常将1、2中的字段建立联合索引效果更好

  • 多表join的关联列

六、如何选择索引列的顺序

建立索引的目的是:希望通过索引进行数据查找,减少随机IO,增加查询性能 ,索引能过滤出越少的数据,则从磁盘中读入的数据也就越少。

  • 区分度最高的放在联合索引的最左侧(区分度=列中不同值的数量/列的总行数);
  • 尽量把字段长度小的列放在联合索引的最左侧(因为字段长度越小,一页能存储的数据量越大,IO性能也就越好);
  • 使用最频繁的列放到联合索引的左侧(这样可以比较少的建立一些索引)。

七、避免建立冗余索引和重复索引

因为这样会增加查询优化器生成执行计划的时间。

  • 重复索引示例:primary key(id)、index(id)、unique index(id)
  • 冗余索引示例:index(a,b,c)、index(a,b)、index(a)

八、优先考虑覆盖索引

对于频繁的查询优先考虑使用覆盖索引。

覆盖索引:就是包含了所有查询字段(where,select,ordery by,group by包含的字段)的索引

覆盖索引的好处:

  • 避免Innodb表进行索引的二次查询

Innodb是以聚集索引的顺序来存储的,对于Innodb来说,二级索引在叶子节点中所保存的是行的主键信息,

如果是用二级索引查询数据的话,在查找到相应的键值后,还要通过主键进行二次查询才能获取我们真实所需要的数据。而在覆盖索引中,二级索引的键值中可以获取所有的数据,避免了对主键的二次查询 ,减少了IO操作,提升了查询效率。

  • 可以把随机IO变成顺序IO加快查询效率

由于覆盖索引是按键值的顺序存储的,对于IO密集型的范围查找来说,对比随机从磁盘读取每一行的数据IO要少的多,因此利用覆盖索引在访问时也可以把磁盘的随机读取的IO转变成索引查找的顺序IO。

九、索引SET规范

尽量避免使用外键约束

  • 不建议使用外键约束(foreign key),但一定要在表与表之间的关联键上建立索引;
  • 外键可用于保证数据的参照完整性,但建议在业务端实现;
  • 外键会影响父表和子表的写操作从而降低性能。

十、数据库SQL开发规范

1、建议使用预编译语句进行数据库操作

预编译语句可以重复使用这些计划,减少SQL编译所需要的时间,还可以解决动态SQL所带来的SQL注入的问题 只传参数,比传递SQL语句更高效 相同语句可以一次解析,多次使用,提高处理效率。

2、避免数据类型的隐式转换

隐式转换会导致索引失效。如:select name,phone from customer where id = 111;

3、充分利用表上已经存在的索引

  • 避免使用双%号的查询条件。

如a like %123%,(如果无前置%,只有后置%,是可以用到列上的索引的)

  • 一个SQL只能利用到复合索引中的一列进行范围查询

如:有 a,b,c列的联合索引,在查询条件中有a列的范围查询,则在b,c列上的索引将不会被用到,在定义联合索引时,如果a列要用到范围查找的话,就要把a列放到联合索引的右侧。

  • 使用left join或 not exists来优化not in操作

因为not in 也通常会使用索引失效。

4、数据库设计时,应该要对以后扩展进行考虑

5、程序连接不同的数据库使用不同的账号,进制跨库查询

  • 为数据库迁移和分库分表留出余地
  • 降低业务耦合度
  • 避免权限过大而产生的安全风险

6、禁止使用SELECT * 必须使用SELECT <字段列表> 查询

原因:

  • 消耗更多的CPU和IO以网络带宽资源
  • 无法使用覆盖索引
  • 可减少表结构变更带来的影响

7、禁止使用不含字段列表的INSERT语句

如:insert into values (a,b,c);

应使用insert into t(c1,c2,c3) values (a,b,c);

8、避免使用子查询,可以把子查询优化为join操作

通常子查询在in子句中,且子查询中为简单SQL(不包含union、group by、order by、limit从句)时,才可以把子查询转化为关联查询进行优化。

子查询性能差的原因:

  • 子查询的结果集无法使用索引,通常子查询的结果集会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响;
  • 特别是对于返回结果集比较大的子查询,其对查询性能的影响也就越大;
  • 由于子查询会产生大量的临时表也没有索引,所以会消耗过多的CPU和IO资源,产生大量的慢查询。

9、避免使用JOIN关联太多的表

对于Mysql来说,是存在关联缓存的,缓存的大小可以由join_buffer_size参数进行设置。

在Mysql中,对于同一个SQL多关联(join)一个表,就会多分配一个关联缓存,如果在一个SQL中关联的表越多,所占用的内存也就越大。

如果程序中大量的使用了多表关联的操作,同时join_buffer_size设置的也不合理的情况下,就容易造成服务器内存溢出的情况,就会影响到服务器数据库性能的稳定性。

同时对于关联操作来说,会产生临时表操作,影响查询效率Mysql最多允许关联61个表,建议不超过5个。

10、减少同数据库的交互次数

数据库更适合处理批量操作 合并多个相同的操作到一起,可以提高处理效率

11、对应同一列进行or判断时,使用in代替or

in的值不要超过500个in操作可以更有效的利用索引,or大多数情况下很少能利用到索引。

12、禁止使用order by rand 进行随机排序

会把表中所有符合条件的数据装载到内存中,然后在内存中对所有数据根据随机生成的值进行排序,并且可能会对每一行都生成一个随机值,如果满足条件的数据集非常大,就会消耗大量的CPU和IO及内存资源。

推荐在程序中获取一个随机值,然后从数据库中获取数据的方式

13、WHERE从句中禁止对列进行函数转换和计算

对列进行函数转换或计算时会导致无法使用索引。

  • 不推荐:

where date(create_time)=20190101

  • 推荐:

where create_time >= 20190101 and create_time < 20190102

14、在明显不会有重复值时使用UNION ALL而不是UNION

  • UNION会把两个结果集的所有数据放到临时表中后再进行去重操作
  • UNION ALL不会再对结果集进行去重操作

15、拆分复杂的大SQL为多个小SQL

  • 大SQL:逻辑上比较复杂,需要占用大量CPU进行计算的SQL
  • MySQL:一个SQL只能使用一个CPU进行计算
  • SQL拆分后可以通过并行执行来提高处理效率

十一、数据库操作行为规范

1、超100万行的批量写(UPDATE、DELETE、INSERT)操作,要分批多次进行操作

  • 大批量操作可能会造成严重的主从延迟

主从环境中,大批量操作可能会造成严重的主从延迟,大批量的写操作一般都需要执行一定长的时间,而只有当主库上执行完成后,才会在其他从库上执行,所以会造成主库与从库长时间的延迟情况

  • binlog日志为row格式时会产生大量的日志

大批量写操作会产生大量日志,特别是对于row格式二进制数据而言,由于在row格式中会记录每一行数据的修改,我们一次修改的数据越多,产生的日志量也就会越多,日志的传输和恢复所需要的时间也就越长,这也是造成主从延迟的一个原因。

  • 避免产生大事务操作

大批量修改数据,一定是在一个事务中进行的,这就会造成表中大批量数据进行锁定,从而导致大量的阻塞,阻塞会对MySQL的性能产生非常大的影响。

特别是长时间的阻塞会占满所有数据库的可用连接,这会使生产环境中的其他应用无法连接到数据库,因此一定要注意大批量写操作要进行分批。

2、对于大表使用pt-online-schema-change修改表结构

  • 避免大表修改产生的主从延迟
  • 避免在对表字段进行修改时进行锁表

对大表数据结构的修改一定要谨慎,会造成严重的锁表操作,尤其是生产环境,是不能容忍的。

pt-online-schema-change它会首先建立一个与原表结构相同的新表,并且在新表上进行表结构的修改,然后再把原表中的数据复制到新表中,并在原表中增加一些触发器。

把原表中新增的数据也复制到新表中,在行所有数据复制完成之后,把新表命名成原表,并把原来的表删除掉。

把原来一个DDL操作,分解成多个小的批次进行。

3、禁止为程序使用的账号赋予super权限

当达到最大连接数限制时,还运行1个有super权限的用户连接super权限只能留给DBA处理问题的账号使用。

4、对于程序连接数据库账号,遵循权限最小原则

程序使用数据库账号只能在一个DB下使用,不准跨库 程序使用的账号原则上不准有drop权限。

一、数据库命令规范

  • 所有数据库对象名称必须使用小写字母并用下划线分割
  • 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来)
  • 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符
  • 临时库表必须以tmp_为前缀并以日期为后缀,备份表必须以bak_为前缀并以日期(时间戳)为后缀
  • 所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)

二、数据库基本设计规范

1、所有表必须使用Innodb存储引擎

没有特殊要求(即Innodb无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用Innodb存储引擎(mysql5.5之前默认使用Myisam,5.6以后默认的为Innodb)Innodb 支持事务,支持行级锁,更好的恢复性,高并发下性能更好

2、数据库和表的字符集统一使用UTF8

兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效

3、所有表和字段都需要添加注释

使用comment从句添加表和列的备注 从一开始就进行数据字典的维护

4、尽量控制单表数据量的大小,建议控制在500万以内

500万并不是MySQL数据库的限制,过大会造成修改表结构,备份,恢复都会有很大的问题

可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小

5、谨慎使用MySQL分区表

分区表在物理上表现为多个文件,在逻辑上表现为一个表 谨慎选择分区键,跨分区查询效率可能更低 建议采用物理分表的方式管理大数据

6、尽量做到冷热数据分离,减小表的宽度

MySQL限制每个表最多存储4096列,并且每一行数据的大小不能超过65535字节 减少磁盘IO,保证热数据的内存缓存命中率(表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的IO) 更有效的利用缓存,避免读入无用的冷数据 经常一起使用的列放到一个表中(避免更多的关联操作)

7、禁止在表中建立预留字段

预留字段的命名很难做到见名识义 预留字段无法确认存储的数据类型,所以无法选择合适的类型 对预留字段类型的修改,会对表进行锁定

8、禁止在数据库中存储图片,文件等大的二进制数据

通常文件很大,会短时间内造成数据量快速增长,数据库进行数据库读取时,通常会进行大量的随机IO操作,文件很大时,IO操作很耗时 通常存储于文件服务器,数据库只存储文件地址信息

9、禁止在线上做数据库压力测试

10、禁止从开发环境,测试环境直接连接生成环境数据库

三、数据库字段设计规范

1、优先选择符合存储需要的最小的数据类型

  • 原因

列的字段越大,建立索引时所需要的空间也就越大,这样一页中所能存储的索引节点的数量也就越少也越少,在遍历时所需要的IO次数也就越多, 索引的性能也就越差

  • 方法

1)将字符串转换成数字类型存储,如:将IP地址转换成整形数据。

mysql提供了两个方法来处理ip地址:

inet_aton 把ip转为无符号整型(4-8位)

inet_ntoa 把整型的ip转为地址

插入数据前,先用inet_aton把ip地址转为整型,可以节省空间。显示数据时,使用inet_ntoa把整型的ip地址转为地址显示即可。

2)对于非负型的数据(如自增ID、整型IP)来说,要优先使用无符号整型来存储

因为:无符号相对于有符号可以多出一倍的存储空间

SIGNED INT -2147483648~2147483647

UNSIGNED INT 0~4294967295

VARCHAR(N)中的N代表的是字符数,而不是字节数

使用UTF8存储255个汉字 Varchar(255)=765个字节。过大的长度会消耗更多的内存

2、避免使用TEXT、BLOB数据类型,最常见的TEXT类型可以存储64k的数据

  • 建议把BLOB或是TEXT列分离到单独的扩展表中

Mysql内存临时表不支持TEXT、BLOB这样的大数据类型,如果查询中包含这样的数据,在排序等操作时,就不能使用内存临时表,必须使用磁盘临时表进行。

而且对于这种数据,Mysql还是要进行二次查询,会使sql性能变得很差,但是不是说一定不能使用这样的数据类型。

如果一定要使用,建议把BLOB或是TEXT列分离到单独的扩展表中,查询时一定不要使用select * 而只需要取出必要的列,不需要TEXT列的数据时不要对该列进行查询。

  • TEXT或BLOB类型只能使用前缀索引

因为MySQL对索引字段长度是有限制的,所以TEXT类型只能使用前缀索引,并且TEXT列上是不能有默认值的。

3、避免使用ENUM类型

  • 修改ENUM值需要使用ALTER语句
  • ENUM类型的ORDER BY操作效率低,需要额外操作
  • 禁止使用数值作为ENUM的枚举值

4、尽可能把所有列定义为NOT

原因:

  • 索引列需要额外的空间来保存,所以要占用更多的空间;
  • 进行比较和计算时要对值做特别的处理

5、使用TIMESTAMP(4个字节)或DATETIME类型(8个字节)存储时间

TIMESTAMP 占用4字节和INT相同,但比INT可读性高

超出TIMESTAMP取值范围的使用DATETIME类型存储。

经常会有人用字符串存储日期型的数据(不正确的做法):

  • 缺点1:无法用日期函数进行计算和比较
  • 缺点2:用字符串存储日期要占用更多的空间

6、同财务相关的金额类数据必须使用decimal类型

  • 非精准浮点:float,double
  • 精准浮点:decimal

Decimal类型为精准浮点数,在计算时不会丢失精度。占用空间由定义的宽度决定,每4个字节可以存储9位数字,并且小数点要占用一个字节。可用于存储比bigint更大的整型数据。

四、索引设计规范

1、限制每张表上的索引数量,建议单张表索引不超过5个

索引并不是越多越好!索引可以提高效率同样可以降低效率。

索引可以增加查询效率,但同样也会降低插入和更新的效率,甚至有些情况下会降低查询效率。

因为mysql优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,就会增加mysql优化器生成执行计划的时间,同样会降低查询性能。

2、禁止给表中的每一列都建立单独的索引

5.6版本之前,一个sql只能使用到一个表中的一个索引,5.6以后,虽然有了合并索引的优化方式,但是还是远远没有使用一个联合索引的查询方式好

3、每个Innodb表必须有个主键

Innodb是一种索引组织表:数据的存储的逻辑顺序和索引的顺序是相同的。

每个表都可以有多个索引,但是表的存储顺序只能有一种 Innodb是按照主键索引的顺序来组织表的。

不要使用更新频繁的列作为主键,不适用多列主键(相当于联合索引) 不要使用UUID、MD5、HASH、字符串列作为主键(无法保证数据的顺序增长)。

主键建议使用自增ID值。

五、常见索引列建议

  • 出现在SELECT、UPDATE、DELETE语句的WHERE从句中的列
  • 包含在ORDER BY、GROUP BY、DISTINCT中的字段

    并不要将符合1和2中的字段的列都建立一个索引,通常将1、2中的字段建立联合索引效果更好

  • 多表join的关联列

六、如何选择索引列的顺序

建立索引的目的是:希望通过索引进行数据查找,减少随机IO,增加查询性能 ,索引能过滤出越少的数据,则从磁盘中读入的数据也就越少。

  • 区分度最高的放在联合索引的最左侧(区分度=列中不同值的数量/列的总行数);
  • 尽量把字段长度小的列放在联合索引的最左侧(因为字段长度越小,一页能存储的数据量越大,IO性能也就越好);
  • 使用最频繁的列放到联合索引的左侧(这样可以比较少的建立一些索引)。

七、避免建立冗余索引和重复索引

因为这样会增加查询优化器生成执行计划的时间。

  • 重复索引示例:primary key(id)、index(id)、unique index(id)
  • 冗余索引示例:index(a,b,c)、index(a,b)、index(a)

八、优先考虑覆盖索引

对于频繁的查询优先考虑使用覆盖索引。

覆盖索引:就是包含了所有查询字段(where,select,ordery by,group by包含的字段)的索引

覆盖索引的好处:

  • 避免Innodb表进行索引的二次查询

Innodb是以聚集索引的顺序来存储的,对于Innodb来说,二级索引在叶子节点中所保存的是行的主键信息,

如果是用二级索引查询数据的话,在查找到相应的键值后,还要通过主键进行二次查询才能获取我们真实所需要的数据。而在覆盖索引中,二级索引的键值中可以获取所有的数据,避免了对主键的二次查询 ,减少了IO操作,提升了查询效率。

  • 可以把随机IO变成顺序IO加快查询效率

由于覆盖索引是按键值的顺序存储的,对于IO密集型的范围查找来说,对比随机从磁盘读取每一行的数据IO要少的多,因此利用覆盖索引在访问时也可以把磁盘的随机读取的IO转变成索引查找的顺序IO。

九、索引SET规范

尽量避免使用外键约束

  • 不建议使用外键约束(foreign key),但一定要在表与表之间的关联键上建立索引;
  • 外键可用于保证数据的参照完整性,但建议在业务端实现;
  • 外键会影响父表和子表的写操作从而降低性能。

十、数据库SQL开发规范

1、建议使用预编译语句进行数据库操作

预编译语句可以重复使用这些计划,减少SQL编译所需要的时间,还可以解决动态SQL所带来的SQL注入的问题 只传参数,比传递SQL语句更高效 相同语句可以一次解析,多次使用,提高处理效率。

2、避免数据类型的隐式转换

隐式转换会导致索引失效。如:select name,phone from customer where id = 111;

3、充分利用表上已经存在的索引

  • 避免使用双%号的查询条件。

如a like %123%,(如果无前置%,只有后置%,是可以用到列上的索引的)

  • 一个SQL只能利用到复合索引中的一列进行范围查询

如:有 a,b,c列的联合索引,在查询条件中有a列的范围查询,则在b,c列上的索引将不会被用到,在定义联合索引时,如果a列要用到范围查找的话,就要把a列放到联合索引的右侧。

  • 使用left join或 not exists来优化not in操作

因为not in 也通常会使用索引失效。

4、数据库设计时,应该要对以后扩展进行考虑

5、程序连接不同的数据库使用不同的账号,进制跨库查询

  • 为数据库迁移和分库分表留出余地
  • 降低业务耦合度
  • 避免权限过大而产生的安全风险

6、禁止使用SELECT * 必须使用SELECT <字段列表> 查询

原因:

  • 消耗更多的CPU和IO以网络带宽资源
  • 无法使用覆盖索引
  • 可减少表结构变更带来的影响

7、禁止使用不含字段列表的INSERT语句

如:insert into values (a,b,c);

应使用insert into t(c1,c2,c3) values (a,b,c);

8、避免使用子查询,可以把子查询优化为join操作

通常子查询在in子句中,且子查询中为简单SQL(不包含union、group by、order by、limit从句)时,才可以把子查询转化为关联查询进行优化。

子查询性能差的原因:

  • 子查询的结果集无法使用索引,通常子查询的结果集会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响;
  • 特别是对于返回结果集比较大的子查询,其对查询性能的影响也就越大;
  • 由于子查询会产生大量的临时表也没有索引,所以会消耗过多的CPU和IO资源,产生大量的慢查询。

9、避免使用JOIN关联太多的表

对于Mysql来说,是存在关联缓存的,缓存的大小可以由join_buffer_size参数进行设置。

在Mysql中,对于同一个SQL多关联(join)一个表,就会多分配一个关联缓存,如果在一个SQL中关联的表越多,所占用的内存也就越大。

如果程序中大量的使用了多表关联的操作,同时join_buffer_size设置的也不合理的情况下,就容易造成服务器内存溢出的情况,就会影响到服务器数据库性能的稳定性。

同时对于关联操作来说,会产生临时表操作,影响查询效率Mysql最多允许关联61个表,建议不超过5个。

10、减少同数据库的交互次数

数据库更适合处理批量操作 合并多个相同的操作到一起,可以提高处理效率

11、对应同一列进行or判断时,使用in代替or

in的值不要超过500个in操作可以更有效的利用索引,or大多数情况下很少能利用到索引。

12、禁止使用order by rand 进行随机排序

会把表中所有符合条件的数据装载到内存中,然后在内存中对所有数据根据随机生成的值进行排序,并且可能会对每一行都生成一个随机值,如果满足条件的数据集非常大,就会消耗大量的CPU和IO及内存资源。

推荐在程序中获取一个随机值,然后从数据库中获取数据的方式

13、WHERE从句中禁止对列进行函数转换和计算

对列进行函数转换或计算时会导致无法使用索引。

  • 不推荐:

where date(create_time)=20190101

  • 推荐:

where create_time >= 20190101 and create_time < 20190102

14、在明显不会有重复值时使用UNION ALL而不是UNION

  • UNION会把两个结果集的所有数据放到临时表中后再进行去重操作
  • UNION ALL不会再对结果集进行去重操作

15、拆分复杂的大SQL为多个小SQL

  • 大SQL:逻辑上比较复杂,需要占用大量CPU进行计算的SQL
  • MySQL:一个SQL只能使用一个CPU进行计算
  • SQL拆分后可以通过并行执行来提高处理效率

十一、数据库操作行为规范

1、超100万行的批量写(UPDATE、DELETE、INSERT)操作,要分批多次进行操作

  • 大批量操作可能会造成严重的主从延迟

主从环境中,大批量操作可能会造成严重的主从延迟,大批量的写操作一般都需要执行一定长的时间,而只有当主库上执行完成后,才会在其他从库上执行,所以会造成主库与从库长时间的延迟情况

  • binlog日志为row格式时会产生大量的日志

大批量写操作会产生大量日志,特别是对于row格式二进制数据而言,由于在row格式中会记录每一行数据的修改,我们一次修改的数据越多,产生的日志量也就会越多,日志的传输和恢复所需要的时间也就越长,这也是造成主从延迟的一个原因。

  • 避免产生大事务操作

大批量修改数据,一定是在一个事务中进行的,这就会造成表中大批量数据进行锁定,从而导致大量的阻塞,阻塞会对MySQL的性能产生非常大的影响。

特别是长时间的阻塞会占满所有数据库的可用连接,这会使生产环境中的其他应用无法连接到数据库,因此一定要注意大批量写操作要进行分批。

2、对于大表使用pt-online-schema-change修改表结构

  • 避免大表修改产生的主从延迟
  • 避免在对表字段进行修改时进行锁表

对大表数据结构的修改一定要谨慎,会造成严重的锁表操作,尤其是生产环境,是不能容忍的。

pt-online-schema-change它会首先建立一个与原表结构相同的新表,并且在新表上进行表结构的修改,然后再把原表中的数据复制到新表中,并在原表中增加一些触发器。

把原表中新增的数据也复制到新表中,在行所有数据复制完成之后,把新表命名成原表,并把原来的表删除掉。

把原来一个DDL操作,分解成多个小的批次进行。

3、禁止为程序使用的账号赋予super权限

当达到最大连接数限制时,还运行1个有super权限的用户连接super权限只能留给DBA处理问题的账号使用。

4、对于程序连接数据库账号,遵循权限最小原则

程序使用数据库账号只能在一个DB下使用,不准跨库 程序使用的账号原则上不准有drop权限。

from:https://www.cnblogs.com/duaimili/p/10277515.html

AI领域必看的45篇论文

而AI领域的发展会是IT中最快的。我们所看到的那些黑客技,其后面无不堆积了大量的论文。而且都是最新、最前沿的论文。

从某种调度来讲,他们所用的技术跟书籍里的内容确实不是一个时代。要想与时俱进,就必须改变思路——从论文入手。

今天给大家介绍45篇让你跟上AI时代的论文。

一、神经网络基础部分

No1  wide_deep模型论文:

关于神经元、全连接网络之类的基础结构,想必每个AI学者都有了解。那么你是否真的了解全连接网络中深层与浅层的关系呢?来看看wide_deep模型吧。这篇论文会使你对全连接有个更深刻的理解。

关于该模型的更多介绍可以参考论文:

https://arxiv.org/pdf/1606.07792.pdf

在wide_deep模型中,wide模型和deep模型具有各自不同的分工。

  • —wide模型:一种浅层模型。它通过大量的单层网络节点,实现对训练样本的高度拟合性。它的缺点是泛化能力很差。
  • —deep模型:一种深层模型。它通过多层的非线性变化,使模型具有很好的泛化性。它的缺点是拟合度欠缺。

将二者结合起来——用联合训练方法共享反向传播的损失值来进行训练—可以使两个模型综合优点,得到最好的结果。

No2  wide_deep模型论文:

为什么Adam被广泛使用?光会用可不行,还得把原理看懂。这样出去喷一喷,才会显得更有面子。

Adam的细节请参阅论文《Adam: A Method for Stochastic Optimization》,该论文的链接网址是:

https://arxiv.org/pdf/1412.6980v8.pdf

No3  Targeted Dropout模型论文:

你还再用普通的Dropout吗?我已经开始用Targeted Dropout了。比你的又快,又好。你不知道吧,赶紧学习一下。

Targeted Dropout不再像原有的Dropout那样按照设定的比例随机丢弃部分节点,而是对现有的神经元进行排序,按照神经元权重重要性来丢弃节点。这种方式比随机丢弃的方式更智能,效果更好。更多理论见以下论文:

https://openreview.net/pdf?id=HkghWScuoQ

二、图像分类部分

No4  Xception模型论文:

在那个图像分类的时代,谷歌的Xception系列,像x战警一样,一个一个的打破记录。其中的技术也逐渐成为AI发展的知识体系。有必要看一下。或许会对自己的工作有所启发。

详细情况请查看原论文《Xception: Deep Learning with Depthwise Separable Convolutions》,该论文网址是:

https://arxiv.org/abs/1610.02357

No5  残差结构论文:

运气好到没朋友,现有模型,后完善理论指的就是残差结构这哥们。他的传奇导致即使到今天的AI技术,也无法将它割舍,就来常微分方程都得拿它比肩。快来学学吧。用处大着呢。好多模型都拿他当先锋。

利用残差结构,可以使得网络达到上百层的深度。详情请参阅原始论文《Deep ResidualLearning for Image Recognition》,该论文网址是:

https://arxiv.org/abs/1512.03385

No6  空洞卷积论文:

NasNet的招牌动作,虽然不是出于NASNet,但是却被人家用得如火纯青。有时不得不惊叹,机器设计出来的模型还真实跟人设计的不一样!

想知道空洞卷积感受野为什么与层数呈指数级关系吗?

细节请查看原论文《Multi-scale context aggregation by dilated convolutions》,该论文网址是:

https://arxiv.org/abs/1511.07122v3

No7  DenseNet论文:

这个模型使我想到了“一根筋”,再次证明了只有轴的人才能成大事!令类的模型,神奇的效果,快来体验一下吧。这可是比华佗还牛的神医哦!

有关DenseNet模型的细节,请参考原始论文《Densely Connected Convolutional Networks》,该论文的连接是:

https://arxiv.org/abs/1608.06993

No8  EfficientNet模型论文:

知道目前位置图像分类界谁是老大吗? 来,看看这个!

EfficientNet模型的论文地址如下:

https://arxiv.org/pdf/1905.11946.pdf

No9  Grad-CAM模型论文:

如果你能把神经元搞得透彻,你也会想到这个点子。不想聊太多!一个字“绝”!这TMD才叫卷积网络的可视化!

详细情况请参阅论文《Grad-CAM:Visual Explanations from Deep Networks via Gradient-based Localization》,该论文的链接网址是:

https://arxiv.org/pdf/1610.02391.pdf

No10  分类模型泛化能力论文:

知道为啥都喜欢使用ResNet模型当先锋吗?运气好就是运气好!好到大家都喜欢用它,还说不出为啥它那么好!反正就是好,不信的话看看这篇论文的实验结果。

论文中,在选取模型的建议中,多次提到了ResNet模型。原因是,ResNet模型在Imgnet数据集上输出的特征向量所表现的泛化能力是最强的。具体可以参考以下论文:

https://arxiv.org/pdf/1805.08974.pdf

三、批量正则化部分

No11  批量正则化论文:

这个没的说,必修课,不懂的化,会被鄙视成渣渣!

论文《Batch Normalization Accelerating Deep Network Training by Reducing Internal Covariate Shift》,该论文网址是:

https://arxiv.org/abs/1502.03167

No12  实例归一化论文:

时代不同了,批量归一化也升级了,赶紧学学新的归一化吧。

在对抗神经网络模型、风格转换这类生成式任务中,常用实例归一化取代批量归一化。

因为,生成式任务的本质是——将生成样本的特征分布与目标样本的特征分布进行匹配。生成式任务中的每个样本都有独立的风格,不应该与批次中其他的样本产生太多联系。所以,实例归一化适用于解决这种基于个体的样本分布问题。详细说明见以下链接:

https://arxiv.org/abs/1607.08022

No13  ReNorm算法论文:

ReNorm算法与BatchNorm算法一样,注重对全局数据的归一化,即对输入数据的形状中的N维度、H维度、W维度做归一化处理。不同的是,ReNorm算法在BatchNorm算法上做了一些改进,使得模型在小批次场景中也有良好的效果。具体论文见以下链接:

https://arxiv.org/pdf/1702.03275.pdf

No14  GroupNorm算法论文:

GroupNorm算法是介于LayerNorm算法和InstanceNorm算法之间的算法。它首先将通道分为许多组(group),再对每一组做归一化处理。

GroupNorm算法与ReNorm算法的作用类似,都是为了解决BatchNorm算法对批次大小的依赖。具体论文见下方链接:

https://arxiv.org/abs/1803.08494

No15  SwitchableNorm算法论文:

我们国人做产品都喜欢这么干!all in one  ,好吧。既然那么多批量归一化的方法。来,来,来,我们来个all in one吧。不服来辩,我这啥都有!

SwitchableNorm算法是将BN算法、LN算法、IN算法结合起来使用,并为每个算法都赋予权重,让网络自己去学习归一化层应该使用什么方法。具体论文见下方链接:

https://arxiv.org/abs/1806.1077

四、注意力部分

No16  大道至简的注意力论文:

把AI搞成玄学也就算了!居然还扯到道家了!谷歌的工程师真实中外通吃啊!搞出来了一个只用注意力就能做事的模型,连卷积都不要了!你所好玩不好玩!至简不至简!刺激不刺激!

大名鼎鼎的Attention is All You Need 注意力机制论文,注意力机制因2017年谷歌的一篇论文Attention is All You Need而名声大噪。下面就来介绍该技术的具体内容。如果想了解更多,还可以参考原论文,具体地址如下:

https://arxiv.org/abs/1706.03762

No17-18  孪生注意力论文:

好比LSTM与GRU一样,注意力他们家也除了一对双胞胎,长得略微有点不同。但是功能一样,都能吃能喝,还能注意。老虎老鼠傻傻的不清楚!

—BahdanauAttention:https://arxiv.org/abs/1409.0473。

—LuongAttention:https://arxiv.org/abs/1508.04025。

No19  各自升级的孪生注意力论文:

话说这对双胞胎,出生后就分开了。各自学的不同的语言,一个学习汉语,一个学习中文。若干年后,见面,发现二者的能力还是一样!

BahdanauAttention注意力升级成了normed_BahdanauAttention,而LuongAttention注意力升级成了scaled_LuongAttention。都一样的效果,你爱用哪个用哪个吧!

例如:

在BahdanauAttention类中有一个权重归一化的版本(normed_BahdanauAttention),它可以加快随机梯度下降收敛速度。在使用时,将初始化函数中的参数normalize设为True即可。

具体可以参考以下论文:

https://arxiv.org/pdf/1602.07868.pdf

No20  单调注意力机制论文:

老公主动表忠心,我以后不看别的美女。老婆觉得不够,再加个限制:你以后不准看别的女人!于是单调注意力就出来了。

单调注意力机制(monotonic attention),是在原有注意力机制上添加了一个单调约束。该单调约束的内容为:

  • 假设在生成输出序列过程中,模型是以从左到右的方式处理输入序列的。
  • 当某个输入序列所对应的输出受到关注时,在该输入序列之前出现的其他输入将不能在后面的输出中被关注。

即已经被关注过的输入序列,其前面的序列中不再被关注。

更多描述可以参考以下论文:

https://arxiv.org/pdf/1704.00784.pdf

No21  混合注意力机制论文:

这个注意力很强大,比一般的注意力专注的地方更多,信息更丰富。我已经注意你很久了!呵呵呵~~~

因为混合注意力中含有位置信息,所以它可以在输入序列中选择下一个编码的位置。这样的机制更适用于输出序列大于输入序列的Seq2Seq任务,例如语音合成任务。

具体可以参考以下论文:

https://arxiv.org/pdf/1506.07503.pdf

五、高级的卷积网络知识

No22  胶囊网络与动态路由的论文:

这是一股为图像分类降温的寒风,深刻而又尖锐的点出了卷积网络的硬伤! 从事最大池化再无翻身之日。

虽然胶囊网络再实际应用中,不像它的理论那么牛,但是对AI的帮助,卷积的理解是革命性的。非常值得一读。另外,这也是一篇绝对让你对数学彻底绝望的论文。花几根白头发把里面的算法啃下来吧。这样你与大神就能更近一步。

胶囊网络分为主胶囊与数字胶囊,主胶囊与数字胶囊之间的耦合系数是通过训练得来的。在训练过程中,耦合系数的更新不是通过反向梯度传播实现的,而是采用动态路由选择算法完成的。该算法来自以下论文链接:

https://arxiv.org/pdf/1710.09829.pdf

目前胶囊网络的研究还处于初级阶段,随着人们研究的深入,相信这些问题会得到解决。

No23  矩阵胶囊网络与EM路由算法:

如果你觉得不过瘾,那么还可以再看一篇。继续自虐一下。

带有EM(期望最大化)路由的矩阵胶囊网络是动态路由胶囊网络的一个改进版本。论文链接如下:

https://openreview.net/pdf?id=HJWLfGWRb

No24  胶囊网络的其它用处:

胶囊网络混身是宝,但就是自己不争气。这也说明还有上升的空间。就拿其中一个动态路由算法来讲,居然比普通的注意力还好。

看完之后,相信你一定会手痒!要不要也试试?把你的注意力换一下。值得你尝试,会有彩蛋的!

该论文的实践也证明,与原有的注意力机制相比,动态路由算法确实在精度上有所提升。具体介绍可见以下论文:

https://arxiv.org/pdf/1806.01501.pdf

No25  卷积网络新玩法TextCNN模型:

早先小编在一个项目中,自己用卷积网络处理字符数据。自己感觉很Happy。没想到,无意间居然发现了一篇同样这么干的论文。居然还有个名字,叫TextCNN。哎!可惜啊!小编文化少,只会写代码,不会写论文。

TextCNN模型是利用卷积神经网络对文本进行分类的算法,由 Yoon Kim 在 Convolutional Neural Networks for Sentence Classification 一文中提出。论文地址:

https://arxiv.org/pdf/1408.5882.pdf

六、图像内容处理部分

No26  FPN模型论文(包含了ROIAlign的匹配算法):

要是搞计算机视觉,还是要建议看一下。非常的基础。也是图像分割方面的用得最多得模型。

FPN的原理是:将骨干网络最终特征层和中间特征层的多个尺度的特征以类似金字塔的形式融合在一起。最终的特征可以兼顾两个特点——指向收敛目标的特征准确、特征语义信息丰富。更多信息可以参考论文:

ROIAlign层中的匹配算法也来自于这篇FPN论文,链接如下:

https://arxiv.org/abs/1612.03144

No27 Mask R-CNN模型论文:

效果好,代码多!硬货!来啃吧!

Mask R-CNN模型是一个简单、灵活、通用的对象实例分割框架。它能够有效地检测图像中的对象,并为每个实例生成高质量的分割掩码,还可以通过增加不同的分支完成不同的任务。它可以完成目标分类、目标检测语义分割、实例分割、人体姿势识别等多种任务。具体细节可以参考以下论文:

https://arxiv.org/abs/1703.06870

No28  YOLO V3模型论文:

这个模型的提点就是快!

目标识别强烈推荐,YOLO V3模型的更多信息可以参考以下链接中的论文:

https://pjreddie.com/media/files/papers/YOLOv3.pdf

No29  Anchor-Fress模型–FCOS模型论文:

随着AI技术的进步Anchor-Fress模型死灰复燃(早先是YOLO V1那一批模型),这次不一样的是彻底干掉带Anchor的模型。训练起来那就一个爽!妈妈再也不用为我准备单独的Anchor标签了。

YOLO V1相比, FCOS模型的思想与YOLO V1模型非常相似,唯一不同的是FCOS模型没有像YOLOv1那样只考虑中心附近的点,而是利用了ground truth边框中所有的点来进行预测边框。并且通过 center-ness 分支来抑制那些效果不行的检测边框。这样FCOS 就可以改善YOLO V1模型总会漏掉部分检测边框的缺点。

相关论文地址:

https://arxiv.org/abs/1904.01355

No30  Anchor-Fress模型–CornerNet-Lite模型论文:

一样也是Anchor-Fress模型,与FCOS效果差不多少。具体看一下论文吧CornerNet-Lite模型。相关论文地址:

https://arxiv.org/pdf/1904.08900.pdf

No31  栈式沙漏网络模型–Hourglass论文:

最初用户人的姿态估计,在符合模型中也是常被使用的模型。论文地址:

https://arxiv.org/abs/1603.06937

No32  OCR必修课——STN模型论文:

可以让模型自动仿射变化,你说牛不牛!要学OCR,就得从这个开始。

有关STN模型的论文链接如下:

https://arxiv.org/abs/1506.02025

七、循环神经网络部分

No33  QRNN模型论文:

在RNN模型的cell里,如果还只知道LSTM和GRU。那就太low了。快了补补吧:

如果想更多了解QRNN,可以参考以下论文:

https://arxiv.org/abs/1611.01576

No34  SRU模型论文:

接着来,各种RNN的Cell。又漂亮,又好吃!

SRU单元在本质上与QRNN单元很像。从网络构建上看,SRU单元有点像QRNN单元中的一个特例,但是又比QRNN单元多了一个直连的设计。

若需要研究SRU单元更深层面的理论,可以参考如下论文:

https://arxiv.org/abs/1709.02755

No35  IndRNN模型论文:

再补一个,这可都是好cell啊!

将IndRNN单元配合ReLu等非饱和激活函数一起使用,会使模型表现出更好的鲁棒性。

有关IndRNN单元的更多理论,可以参考论文:

https://arxiv.org/abs/1803.04831

No36  IndRNN模型论文:

最后,再来一个cell,如想要了解更多关于JANET单元的内容,可以参考以下论文:

https://arxiv.org/abs/1804.04849

八、AI合成部分

No37-38  Tacotron与Tacotron-2模型论文:

AI合成部分的经典模型,以上结构来自Tacotron与Tacotron-2两个结构,更多内容可以参考以下两篇论文:

https://arxiv.org/pdf/1703.10135.pdf

https://arxiv.org/pdf/1712.05884.pdf

No39  DeblurGAN模型论文:

图片合成的论文太多了。这里简单列几个,大体原理和思路了解,即可。

DeblurGAN模型是一个对抗神经网络模型,由生成器模型和判别器模型组成。

  • —生成器模型,根据输入的模糊图片模拟生成清晰的图片。
  • —判别器模型,用在训练过程中,帮助生成器模型达到更好的效果。

具体可以参考论文:

https://arxiv.org/pdf/1711.07064.pdf

No40  AttGAN模型论文:

同样,这也是个图片合成的。不同的是多属性合成,相对比较有意思。

AttGAN模型由两个子模型组成:

  • 利用编码器模型将图片特征提取出来。
  • 将提取的特征与指定的属性值参数一起输入编码器模型中,合成出最终的人脸图片。

更多细节可以参考论文:

https://arxiv.org/pdf/1711.10678.pdf

No41  RNN.WGAN模型论文:

可以合成文本的GAN。离散数据也能干!

RNN.WGAN模型使用了WGAN模型的方法进行训练。详细做法可以参考如下论文:

https://arxiv.org/abs/1704.00028

九、多任务学习

No42  MKR模型论文:

多任务学习模型有必要了解一下。这里推荐一个论文给你看看。

MKR是一个多任务学习的端到端框架。该框架能够将两个不同任务的低层特征抽取出来,并融合在一起实现联合训练,从而达到最优的结果。有关MKR的更多介绍可以参考以下链接:

https://arxiv.org/pdf/1901.08907.pdf

十、NLP部分

No43  BERT模型论文:

如果你搞NLP,那么这个就不用我来介绍了。如果你准备搞NLP,那么赶紧来看看这个,跟上时代。

BERT相关论文链接

https://arxiv.org/abs/1810.04805

在BERT之后,又出了好多优秀的模型。但是,还是先把这个啃下来,再看别的才不费劲。

十一、模型攻防

No44  FGSM模型论文:

攻击模型的经典方法。值得掌握。

FGSM(Fast Gradient Sign Method)是一种生成对抗样本的方法。该方法的描述如下:

  • 将输入图片当作训练的参数,使其在训练过程中可以被调整。
  • 在训练时,通过损失函数诱导模型对图片生成错误的分类。
  • 当多次迭代导致模型收敛后,训练出来的图片就是所要得到的对抗样本

具体可以参考论文:

https://arxiv.org/pdf/1607.02533.pdf

No45  黑箱攻击论文:

基于雅可比(Jacobian)矩阵的数据增强方法,是一种常用的黑箱攻击方法。该方法可以快速构建出近似于被攻击模型的决策边界,从而使用最少量的输入样本。即:构建出代替模型,并进行后续的攻击操作。

详细请见如下链接:

https://arxiv.org/abs/1602.02697

这里只是列了一些基础的论文。如果这45篇论文看完。可以保证你再看到大厂的产品时,不会感觉有代沟。

from:https://www.jiqizhixin.com/articles/2019-07-25-3