springboot实战学习笔记

安装spring cli

创建项目

  • 用网页创建:http://start.spring.io/
  • 用Intellij创建.

    如果是创建了gradle项目,一般需要加上插件:
    apply plugin: ‘idea’
    执行:
    gradle cleanIdea idea
    才能正常使用.(idea生成的项目默认使用eclipse的插件,多么讽刺)

JPA相关:

https://zhuanlan.zhihu.com/p/25000309

第二章:

  • 依赖顺序:(可以与书写顺序相反,自顶向下或自底向上)
    实体Book<=仓库(<Book,Long>)

注解

  • 实体

    1
    2
    3
    4
    @Entity 领域对象
    @Id 主键
    @GeneratedValue(strategy=GenerationType.AUTO) 自增id
    其他就是getter,setter了.
  • repository

    1
    interface xxx extends JpaRepository<Book, Long>
  • controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    //@Controller 用于类
    //@RequestMapping 用于类或方法
    //@Autowired 可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
    // 最后返回视图名(String)

    import java.util.List;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;

    @Controller // 注册ctrl到spring context中,成为一个bean.
    @RequestMapping("/readingList") // 匹配url.
    public class ReadingListController {

    private static final String reader = "craig";

    private ReadingListRepository readingListRepository;

    @Autowired // 注入构造函数的输入参数
    public ReadingListController(ReadingListRepository readingListRepository) {
    this.readingListRepository = readingListRepository;
    }

    @RequestMapping(method=RequestMethod.GET)// 匹配GET
    public String readersBooks(Model model) {

    List<Book> readingList = readingListRepository.findByReader(reader);
    if (readingList != null) {
    model.addAttribute("books", readingList);
    }
    return "readingList";
    }

    @RequestMapping(method=RequestMethod.POST)// 匹配POST
    public String addToReadingList(Book book) {
    book.setReader(reader);
    readingListRepository.save(book);
    return "redirect:/readingList";
    }

    }

条件化注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ConditionalOnBean 配置了某个特定Bean
@ConditionalOnMissingBean 没有配置特定的Bean
@ConditionalOnClass Classpath里有指定的类
@ConditionalOnMissingClass Classpath里缺少指定的类
@ConditionalOnExpression
//给定的Spring Expression Language(SpEL)表达式计算结果为true
@ConditionalOnJava Java的版本匹配特定值或者一个范围值
@ConditionalOnJndi
//参数中给定的JNDI位置必须存在一个,如果没有给参数,则要有JNDI
InitialContext

@ConditionalOnProperty 指定的配置属性要有一个明确的值
@ConditionalOnResource Classpath里有指定的资源
@ConditionalOnWebApplication 这是一个Web应用程序
@ConditionalOnNotWebApplication 这不是一个Web应用程序

数据源相关spring源代码:// DataSourceAutoConfiguration

1
2
3
4
5
6
7
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })// classpath里必须有这两个类
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration {
// do something
}

springboot配置决策:

  1. ClassPath里有H2时,创建一个嵌入式的H2数据库Bean.
    //类型是javax.sql.DataSource,JPA实现(Hibernate)需要.
    //org.springframework.orm里放的是Hibernate3,4,5代码
  1. ClassPath里有Hibernate时,自动配置相关bean,包括:
    Spring的实体管理相关,和JpaVendorAdaptor.
  1. ClassPath里有Spring Data JPA时,自动配置为:
    根据仓库接口创建仓库实现. (Repository相关代码)
  1. ClassPath里有Thymeleaf时,将其配置为Spring MVC的视图,包括模板解析器,模板引擎及视图解析器.
    视图会解析相对于classPath根目录的/templates目录里的模板.
  1. ClassPath里有Spring MVC时(web starter里有), 配置Spring DispatcherServlet并启用Spring MVC.
  1. 发现是SpringMVC web项目时,注册一个资源处理器,提供:
    /static
    /public
    /resources
    /META-INF/resources
    中静态内容.
  1. ClassPath里有Tomcat时(Web starter里有),启动嵌入式Tomcat.

第三章 自定义配置

主要讲如何覆盖第二章中的默认配置.

  1. 覆盖自动配置的bean
  2. 用外置属性进行配置
  3. 自定义错误页
  • bean覆盖的原理:
    springboot先加载应用级配置,然后使用@ConditionalOnMissingBean加载自动配置.
    因此覆盖的第一种办法是重写一个完整的bean,比较繁琐.
  • 用外置属性
    微调一些属性.
  1. 运行时命令行参数中指定:
    1
    java -jar readinglist-0.0.1-SNAPSHOT.jar --spring.main.show-banner=false
  2. application.properties中指定:
    1
    spring.main.show-banner=false
  3. 为application.yml中指定:
    1
    2
    3
    spring:
    main:
    show-banner: false
  4. 环境变量中指定:
    1
    export spring_main_show_banner=false

优先级:

  1. 命令行 – 最优先
  2. java:comp/env中的JNDI属性.
  3. JVM系统属性
  4. 操作系统环境变量
  5. 随机生成的带random.*前缀的属性
  6. 应用程序外的application.properties或yml文件
  7. 应用程序内的application.properties或yml文件
  8. 通过@PropertySource标注的属性源
  9. 默认属性

位置优先级:
//application.properties或application.yml文件

  1. 外置 运行目录的/config目录
  2. 外置 运行目录
  3. 内置 config包内
  4. 内置 classPath根目录.

##可以调整的属性例子:

  1. 禁用模板缓存

    1
    2
    3
    spring:
    thymeleaf:
    cache: false

    同样适用于:

    1
    2
    3
    spring.freemarker.cache
    spring.groovy.template.cache
    spring.velocity.cache
  2. 配置嵌入式服务器

    1
    server.port=8000

    配置https服务的key:

    1
    keytool -keystore mykeys.jks -genkey -alias tomcat -keyalg RSA

    yml:

    1
    2
    3
    4
    5
    6
    server:
    port: 8443
    ssl:
    key-store: file:///path/to/mykeys.jks
    key-store-password: letmein
    key-password: letmein
  3. 日志配置
    使用Log4j://默认使用logback

    1
    2
    3
    4
    5
    6
    7
    8
    9
    gradle:
    configurations {
    all*.exclude group:'org.springframework.boot',
    module:'spring-boot-starter-logging'
    }

    compile("org.springframework.boot:spring-boot-starter-log4j")
    //log4j2
    compile("org.springframework.boot:spring-boot-starter-log4j2")

单独的日志配置文件:

1
2
3
logging:
config:
classpath:logging-config.xml

日志级别:

1
2
3
4
5
6
7
8
logging:
path: /var/logs/
file: BookWorm.log
level:
root: WARN
org:
springframework:
security: DEBUG
  1. 配置数据源
    1
    2
    3
    4
    5
    spring:
    datasource:
    url: jdbc:mysql://localhost/readinglist
    username: dbuser
    password: dbpass
    数据库连接池寻找顺序://ClassPath
  2. tomcat
  3. HikariCP
  4. Commons DBCP
  5. Commons DBCP2

打开debug看日志的话,dataSourceInitializer数据源初始化的时候会自动执行resources目录下的data.sql文件.

@ManyToOne等数据库相关注解:
https://www.cnblogs.com/yxjdragon/p/6008603.html

  1. 一个A对应多个B:
    方法1:A里设置主键;B里使用@ManyToOne设置关联;
    方法2:A里使用@OneToMany,Set;B里设置主键.

第三种配置方式: 应用程序Bean的配置外置

详见:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-profiles

真的是博大精深,千变万化.
代码示例中的注入过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
网页模板中${amazonID}
<= model.addAttribute("amazonID", amazonConfig.getAssociateId());
<= amazonConfig是AmazonProperties的一个对象:
AmazonProperties:
@Component
@ConfigurationProperties("amazon")
public class AmazonProperties {

private String associateId;

public void setAssociateId(String associateId) {
this.associateId = associateId;
}

public String getAssociateId() {
return associateId;
}

}

<= Controller的构造函数中注入:
@ConfigurationProperties("amazon")
public class ReadingListController {
@Autowired// 自动注入方法的参数
public ReadingListController(ReadingListRepository readingListRepository,
AmazonProperties amazonConfig) {
this.readingListRepository = readingListRepository;
this.amazonConfig = amazonConfig;
}
}

<= 最根源: application.yml文件:
amazon:
associate_id: habuma-20

第四章: 单元测试

  1. 用Mock进行测试
  2. 用Mock,带着登录用户信息
  3. 用模拟器测试(Selenium)
  4. 用模拟器测试(Selenium),带着cookie(这个没写)

第五章: Groovy与Spring Boot CLI

似乎是凑字数的一章. 大致跑了一下代码,浏览了一下.

第六章:

感觉是私货,大致扫了一下内容,代码都没跑.

先配数据源:
http://www.jianshu.com/p/34730e595a8c

注解笔记

阅读过程遇到的注解越来越多,有必要总结一下:

1. 声明Bean的注解: 其实可以都用@Component

  • @Service用于标注业务层组件
  • @Controller用于标注控制层组件(如struts中的action)
  • @Repository用于标注数据访问组件,即DAO组件
  • @Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
  • @Bean声明一个bean,还可以定义的属性:name,initMethod,destroyMethod,autowire(装配策略:Autowire.BY_NAMEAutowire.BY_TYPEAutowire.NO)
  • @Primary, 修饰这个Bean应优先被使用.// @Qualifier则是在使用的时候显示指定使用哪个.

还有一个有用的注解:

  • @Order: 声明装配的顺序.经过测试,加载的顺序还是字母序,不受影响. 如果装配到List容器里,装配的顺序由@Order决定.以此类推,同级别的切面实现,执行顺序也是按@Order.
1
2
3
4
5
6
7
// 使用样例:
@Order(100)
public abstract class WebSecurityConfigurerAdapter implements
// 源码:
public @interface Order {
int value() default 2147483647;
}

默认Bean的名字:
> 类名(头字母小写)

更改Bean的名字:
> @Service(“beanName”)

默认Bean是单例,要改变:
> @Scope(“prototype”)

bean的生命周期

  1. singleton: 默认是单例,与容器共存亡,基本就是始终存活;
  2. prototype: 每次都是新的,与单例相反,调用者自己负责销毁;

– 后三种是新增的,专用于web应用的
3. request: 每个HTTP请求一个,是prototype的一种扩展;
4. session: 每个Session一个,但咱公司一般不用session,用cookie;
5. global session: 基于porlet的web应用程序中才有意义.

增加初始化方法:

1
@PostConstruct public void init() { }

增加析构方法:

1
@PreDestroy public void destory() {}

2. 使用Bean的注解:

  • @Autowired 不需要写getter和setter
  • @Qualifier 如果同一个接口有多个实现类的话,需要指定具体注入哪一个:

    @Qualifier(“chinese”)

  • @Resource 约等于@Autowired,区别是默认按name进行注入.也可以选择类型.

@Autowired:

构造函数注入和字段注入的区别:(Spring场景下)

  1. 构造函数注入时,spring能检测循环依赖并报错;而字段注入的时候,spring检测出来以后会想办法解决,不会报错.
  2. 构造函数注入时,能控制注入顺序.
  3. 字段注入时,spring能按需注入,写起来简单;
  • 字段注入的缺点:
  1. 不能创建不可变对象;
  2. 与DI容器紧耦合,不能在类外使用;
  3. 除了反射,类不能被实例化(例如在单元测试中).//只能进行集成测试
  4. 从外部(接口或构造函数)无法看出依赖,依赖情况隐藏在实现中.
  5. 如果有循环依赖,并不会报错,隐藏了可能的错误.
    // 如果一个类依赖太多,很可能是违反了设计原则.

原则:

  1. 必须项\不可变项: 使用构造函数注入;
  2. 可选项\可变项: 使用字段注入;
  3. 大部分情况下避免字段注入. 尽量使用构造函数注入.

3.配置相关的注解:

  • @EnableAutoConfiguration

    启动自动配置,就是根据classpath中的jar,自动配置.

  • @SpringBootConfiguration

    继承自@Configuration, 标记当前类为配置类.
    实例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

    @Configuration
    @EnableAutoConfiguration
    @ComponentScan
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private ReaderRepository readerRepository;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http
    .authorizeRequests()
    .antMatchers("/").access("hasRole('READER')")
    .antMatchers("/**").permitAll()
    .and()
    .formLogin()
    .loginPage("/login")
    .failureUrl("/login?error=true");
    }
    @Override
    protected void configure(
    AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService());
    }
    @Bean
    public UserDetailsService userDetailsService() {
    return new UserDetailsService() {
    @Override
    public UserDetails loadUserByUsername(String username)
    throws UsernameNotFoundException {
    UserDetails userDetails = readerRepository.findOne(username);
    if (userDetails != null) {
    return userDetails;
    }
    throw new UsernameNotFoundException("User '" + username + "' not found.");
    }
    };
    }
    }

    Spring在解析到这种类时,将识别出标注@Bean的所有方法执行,
    并将方法的返回值 (UserDetailsService)注册到IoC容器中。
    默认情况下,Bean 的名字为方法名。

  • @ComponentScan

    扫描当前包及其子包下声明为Bean的类.(包括@Configure,@Contrller等等).
    纳入Spring容器.
    可以通过@SpringBootApplication(scanBasePackages = "com.xxx.yyy"),更改扫描的范围.//子包相关:default包并不是其他包的父包.

4.复合注解:

第七章 深入Actuator

  • Actuator Web端点
  • 调整Actuator
  • 通过shell连入运行中的应用程序
  • 保护Actuator

7.1 Actuator的Web端点

HTTP方法 路径 描述

1
2
3
4
5
6
7
8
9
10
11
12
GET /autoconfig 自动配置报告.记录各自动配置条件通过与否.
GET /configprops 描述配置属性(含默认值)如何注入Bean
GET /beans 描述上下文中所有Bean及关系.
GET /dump 获取线程活动的快照
GET /env 获取全部环境属性
GET /env/{name} 根据名称获取特定的环境属性
GET /healthy 报告app的健康指标(由实现类提供)
GET /info 获取app的定制信息(由info开头的属性提供)
GET /mappings 描述全部的URI路径及其控制器(含Actuator端点)的映射关系
GET /matrics 报告各种应用程序度量信息,比如内存用量和HTTP请求计数
GET /trace 提供基本的HTTP请求跟踪信息(时间戳,HTTP头)
POST /shutdown 关闭app,要求endpoints.shutdown.enabled设置为true

7.2 查看配置明细

知道SpringBoot具体怎么装配的组件.

配置yml :

1
2
3
endpoints.sensitive: true
management.context-path: /admin
management.security.enabled: true

/beans 可读

用户权限上增加角色new SimpleGrantedAuthority("ROLE_ACTUATOR");
然后访问:
http://localhost:8000/admin/beans
获取所有Beans的信息.包括依赖,以及具体是使用JDK动态代理的还是CGLIB代理的.

基于JDK动态代理 ,可以将@Transactional放置在接口和具体类上。
基于CGLIB类代理,只能将@Transactional放置在具体类上。

/autoconfig 难读

/env 可读,易读

  • 环境变量信息

    包括启用的是哪个profiles,用的哪个log配置,各种运行时变量.
    其中数据库连接是明文,数据库密码是星号代替.因此密码不能写到数据库连接里.

/configprops 可读

  • 配置属性报告
    可以看到有哪些属性被注入了Bean,可以从中学到有哪些属性可以在yml中设置.

/mappings 可读

  • 端点和控制器的映射.
    可以看到所有url都映射到了哪个controller,可以从中学到有哪些url可以使用.

/metrics 可读

  • 运行时统计.
    包括线程,内存,数据库连接活跃等信息.

/trace 可读

  • 报告所有Web请求的详细信息,包括请求方法、路径、时间戳以及请求和响应的头信息。实际能显示最近100个请求的信息,似乎对于测试应用有用,线上应用请求量一大,100条里应该就不会有想要的日志了.

/dump 难读

  • 当前活跃线程快照.
    太长了.不过每个线程的信息很短.

/health 可读

  • 当前程序,磁盘,数据库是否活着.

/shutdown POST

发送关闭命令:

1
curl -X POST http://localhost:8000/admin/shutdown

如果要身份验证,命令就比较不好搞了,需要python脚本或者别的代码辅助.

/info 可读

显示在yml里配的info属性的值.比如联系方式.

其他

接下来书里介绍了用shell完成和REST api一样的功能,感觉有点难用.

JMX监控

1
2
jconsole [进程号]
// eg. jconsole 61611

除了进程\线程运行的信息,在MBean面板还有org.springframework.boot目录,下面有REST api中的属性,可以通过data属性的值得到相同的值.
值得注意的是,与REST api不同的是,一旦启用了shutdown,即使不登录用户,也可以通过Jconsole关闭app.

其他

接下来介绍了如何自定义端点url,以及加入自定义内容.
还可以把/trace接口的内容存到mongodb.
细节略过不提.

第八章 部署

讲了下如何部署的细节,网上有别的教程.各种对照着做才行.

附录

包括开发工具,远程调试. 如果有需要可以看看.
剩下的是起步依赖的一些详细信息.

这本书还蛮短的.

CommandLineRunner接口

CommandLineRunner、ApplicationRunner 接口是在容器启动成功后的最后一步回调(类似开机自启动)。
可以在集成测试的时候使用.

mysql,sqoop,hive分隔符总结

mysql分隔符

Mysql默认的分隔符设置,字段之间以逗号,隔开,行之间以换行\n隔开,默认转义符号是\,字段值以单引号'包含起来。

sqoop分隔符

sqoop可以设定分隔符按照mysql的约定.
使用配置 --mysql-delimiters.
也可以自定义以下设置:

--enclosed-by <char> (所有字段)

给字段值前后加上指定的字符,比如双引号,示例:–enclosed-by ‘"‘,显示例子:”3”,”jimsss”,”dd@dd.com

--optionally-enclosed-by <char> (含有单(双)引号的字段)

一般指定这一项为”‘“,而不指定--enclosed-by <char>

--escaped-by <char>

转义时使用的char,一般为”"

--fields-terminated-by <char>

字段分隔字段,默认为,.

--lines-terminated-by <char>

行分隔符,默认为’\n’

hive分隔符

hive不可以设置行分隔符,hive的行分隔符只能是’\n’.
hive可以设置的分隔符如下:

1
2
3
4
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
COLLECTION ITEMS TERMINATED BY ','
MAP KEYS TERMINATED BY ':'

这些也可以设置为\0001,\0002,\0003.

由于hive不能自定义行分隔符,因此sqoop导入hive时只能指定行分隔符为\n,因此sqoop导入hive的时候需要过滤掉hive默认使用的分隔符,可以使用参数:

1
--hive-drop-import-delims

作用是把字符串类型的字段中的\r,\n,\001等字符丢弃.
这个参数可以与--mysql-delimiters同时使用.
也可以使用

1
2
--hive-delims-replacement
` `

将对应字符换成空格,以保持原字符串的结构.

Solution

1)
由于hive的csv解析bug太多,使用sqoop和hive组合时,应该使用hive的分隔符,sqoop中配置\0001和hive中配置’\u0001’.
1.1)hive对于'和’的识别是混乱的,如下述的构造数据,解析起来是有问题的:

1
\'异常的第一列,第二列

这行数据解析出来每一列都是NULL.

1.2)另一种解析错误的数据如下:

1
2
3
\'异常的第一列,'第二列,'
或:
\\'异常的第一列,'第二列,'

这两行解析的结果均为:

1
2
3
4
//第一列为:
异常的第一列,第二列
//第二列为:
NULL

而实际我们期望的解析结果是:

1
2
3
4
//第一列为:
'异常的第一列
//第二列为:
第二列,

hive这里解析的逻辑是,把

1
\'异常的第一列,'

看做一个整体,继续拼接剩余的字符串,直到遇到逗号.这种解析方法是错误的.

2)
一种只对这种情况有效的解决方案,使用参数--enclosed-by <char>

1
2
--enclosed-by
"'"

使用这种方法以后的数据会变成:

1
'\'异常的第一列','第二列'

此时可以用org.apache.hadoop.hive.serde2.OpenCSVSerde类正常解析,注意这里若使用--optionally-enclosed-by <char>参数,
并不能自动加上第一列的单引号.
2.1)sqoop仅在它认为字段中出现了逗号时才自动加上单引号;
2.2)sqoop对于字段中出现的逗号进行转义,变为';
2.3)对于hive而言,出现单引号时,它会把这些单引号对内部的部分认为是一个整体,对这个整体内的逗号进行忽略. 但hive的bug之处在于,它遇到'时候,也会试图去匹配下一个'或’.

3) 使用csv的opt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import
-D
mapreduce.map.speculative=false
--append
--connect
jdbc:mysql://apolo-publisher-reader:3306/apolo?useUnicode=true&characterEncoding=UTF-8
--username
xxx
--password
xxx@xxx
--boundary-query
'select unix_timestamp("2017-07-23")*1000,unix_timestamp(date_add("2017-07-23",interval 1 day))*1000'
--query
SELECT * FROM question where updatedTime between unix_timestamp("2017-07-23")*1000 and unix_timestamp(date_sub("2017-07-23",interval -1 day))*1000 and $CONDITIONS
--split-by
updatedTime
--hive-delims-replacement
' '
--target-dir
/user/fengmq01/test5/
--enclosed-by
"'"
--m
20
--direct

4)最安全的solution,不使用csv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import
-D
mapreduce.map.speculative=false
--append
--connect
jdbc:mysql://apolo-publisher-reader:3306/apolo?useUnicode=true&characterEncoding=UTF-8
--username
xxx
--password
xxx@xxx
--boundary-query
'select unix_timestamp("2017-07-23")*1000,unix_timestamp(date_add("2017-07-23",interval 1 day))*1000'
--query
SELECT * FROM question where updatedTime between unix_timestamp("2017-07-23")*1000 and unix_timestamp(date_sub("2017-07-23",interval -1 day))*1000 and $CONDITIONS
--split-by
updatedTime
--hive-delims-replacement
' '
--target-dir
/user/fengmq01/test5/
--fields-terminated-by
"\0001"
--m
20
--direct

对应的hive外部表参数:

1
2
3
4
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\u0001'
COLLECTION ITEMS TERMINATED BY '\u0002'
MAP KEYS TERMINATED BY '\u0003'

csv外部表语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
drop table temp.feng_test1;
create table temp.feng_test1
(c1 string
,c2 string
,c3 string
,c4 string
)
;
load data local inpath 'input.csv' into table temp.feng_test1;

create external table temp.feng_test2
(c1 string
,c2 string
,c3 string
,c4 string
)
ROW FORMAT SERDE
'org.apache.hadoop.hive.serde2.OpenCSVSerde'
WITH SERDEPROPERTIES (
'escapeChar'='\\',
'quoteChar'='\'',
'separatorChar'=',')
STORED AS INPUTFORMAT
'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
'hdfs://f04/user/hive/warehouse/temp.db/feng_test1'

sqoop学习笔记

https://geosmart.github.io/2016/02/24/Sqoop%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/
https://sqoop.apache.org/docs/1.4.6/SqoopUserGuide.html#_mysql
http://stackoverflow.com/questions/25887086/sqoop-export-using-update-key

1.列出数据库:

1
sqoop list-databases --connect jdbc:mysql://pipe-reader:3306/ -username pipe -password pipe

2.列出表:

1
sqoop list-tables --connect jdbc:mysql://pipe-reader:3306/pipe_tutor --username xxx --password pipe123

3. 复制表结构:

1
2
3
sqoop create-hive-table --connect jdbc:mysql://pipe-reader:3306/pipe_tutor --table dim_tutor_teacher \
--username xxx --password xxx \
--hive-table tutor.dim_tutor_teacher --fields-terminated-by "\t" --lines-terminated-by "\n";

4. 复制数据:

1
2
3
4
sqoop import --connect jdbc:mysql://pipe-reader:3306/pipe_tutor \
--username xxx --password xxx \
--table dim_tutor_teacher --hive-import --hive-table tutor.dim_tutor_teacher \
-m 1 --fields-terminated-by "\t";

工作流程为:

(1) 连接ZK
(2) 使用 SELECT t.* FROMdim_tutor_teacherAS t LIMIT 1' 测试连接 (3) 生成一个运行jar包:/tmp/sqoop-maintain/compile/a2b05194f5b67b58688dff76426380ef/dim_tutor_teacher.jar(4) 提交jar包到RM,输出文件到/user/maintain/dim_tutor_teacher(5) load数据到内部表,/user/maintain/dim_tutor_teacher下的文件除了_success文件都移动到 :/user/hive/warehouse/tutor.db/dim_tutor_teacher`

注意事项:

(1) 若输出文件夹已经存在则会报错: dfs -rmr /user/maintain/dim_tutor_teacher
(2) 内部表可以原先不存在,或者存在.
(3) 若表中原有数据,则load数据后会有两份数据. (需要自己手动删原有数据: truncate table tutor.dim_tutor_teacher )
(4) 使用--direct参数可以更快,但不支持blob和clob类型字段.
(5) 若hive表结构存在且与mysql表结构不一样,不影响sqoop工作(多退少补NULL).

启发:

若使用外部表:
(1) 不需要删表数据,只需要和原来一样,删除输出文件夹.
(2) 表结构更新:
(2.1)内部表使用alter语句,外部表可以使用drop,create语句.
(2.2)若有dt且表结构中间多了一列,数据错位,无论内部表外部表,历史数据都不可直接用.需要写脚本转换调整.

5. 将hive中的表数据导入到mysql数据库表中

1
2
3
4
5
6
sqoop export --connect jdbc:mysql://pipe-reader:3306/pipe_tutor \
--username xxx --password xxx \
--table test_teacher --export-dir /user/hive/warehouse/tutor.db/dim_tutor_teacher \
--input-fields-terminated-by '\t' \
--update-mode allowinsert --update-key id \
--input-null-string "\\\\N" --input-null-non-string "\\\\N"

注意事项:

  1. 其中mysql表pipe_tutor.test_teacher需要自己手动建立表结构,这一点和从mysql导到hive不同.
  2. pipe_tutor.test_teacher中原有一部分数据,则当出现主键冲突时,会卡死.使用--update-mode allowinsert --update-key id参数可解开卡死.
  3. 导出的时候最好在末尾加上:
    1
    --input-null-string "\\\\N" --input-null-non-string "\\\\N"
  4. 更新模式:–update-mode updateonly/allowinsert
    updateonly则只更新,allowinsert则对于没有冲突的primary key 进行插入.

6. –query 语句和–append使用 (mysql -> hdfs)

1
2
3
sqoop import --append --connect jdbc:mysql://pipe-reader:3306/pipe_tutor --username xxx --password xxx --query "select * from test_teacher where \$CONDITIONS and id>30"  -m 2  --split-by id --target-dir /user/maintain/dim_tutor_teacher --fields-terminated-by "\t";
```
或:

sqoop import –append –connect jdbc:mysql://pipe-reader:3306/pipe_tutor –username xxx –password xxx –query “select * from test_teacher where $CONDITIONS and id>30” -m 1 –target-dir /user/maintain/dim_tutor_teacher –fields-terminated-by “\t”;

1
2
3
4
\$CONDITIONS 作为关键字,用于均摊任务到mapper中时填充筛选条件.
使用--query时必须使用\$CONDITIONS.

# 7. --columns --where 语句使用(mysql -> hdfs)

sqoop import –append –connect jdbc:mysql://pipe-reader:3306/pipe_tutor –username xxx –password xxx –table test_teacher –columns “id,status,course” –where “id < 3 “ -m 1 –target-dir /user/maintain/dim_tutor_teacher –fields-terminated-by “,”;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--fields-terminated-by默认以','分隔.

导入hdfs:
--target-dir /user/maintain/dim_tutor_teacher
导入hive表:
--hive-import --hive-table tutor.dim_tutor_teacher

分隔符:
--fields-terminated-by '\t' --lines-terminated-by '\n' --optionally-enclosed-by '\"' \

#8. 测试 --direct 参数

clob的字段是导入到hdfs上是正常显示文本,blob是二进制文件导出到hdfs上显示为16进制. 16进制转string:
http://www.aboutyun.com/thread-14008-1-1.html

对于clob和blob字段,使用--direct参数时应使用--query参数,不能使用--table参数.
或者不使用--direct参数.

参数:

–driver com.mysql.jdbc.Driver

1
语句:

sqoop import –append
–connect jdbc:mysql://mysql-ape-exercise13-reader:3306/ape_exercise13
–username ape –password ape@ytkz7c
–query “select * from exercise where $CONDITIONS limit 1”
-m 1 –target-dir /user/maintain/dim_tutor_teacher
–direct
–fields-terminated-by “\t”;

1
2


sqoop import –append
–connect jdbc:mysql://mysql-ape-conan-task4-reader:3306/ape_conan_task14
–username ape –password 87mPiRJky
–query “select * from task where $CONDITIONS”
-m 1 –target-dir /user/maintain/dim_tutor_teacher
–direct
–fields-terminated-by “\t”;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

#9. sqoop执行文件:
sqoop --options-file ./test.opt
sqoop命令格式是 `sqoop CMD [args]`
从下文示例可以看出:
test.opt文件第一行是`CMD`(`import`);
以换行分隔参数(`args`).每对参数和值分成两行,
key前加`--`,value则不用.
关键字`$CONDITIONS`前的反斜杠不能加.
`--query`的`sql`语句的引号可有可无.
分隔符的`\t`语句的引号可有可无.

```test.opt
import
--append
--connect
jdbc:mysql://mysql-ape-conan-task4-reader:3306/ape_conan_task14
--username
ape
--password
87mPiRJky
--query
select * from task where $CONDITIONS
-m
1
--target-dir
/user/maintain/dim_tutor_teacher
--direct
--fields-terminated-by
\t

10. –warehouse-dir参数:

1
2
3
4
5
sqoop import  --append --connect jdbc:mysql://pipe-reader:3306/pipe_tutor \
--username xxx --password xxx \
--table dim_tutor_teacher \
--warehouse-dir /user/maintain/dim_tutor_teacher \
-m 1 --fields-terminated-by "\t";

--warehouse-dir会在目录建立与表名同名的目录(如果没有).然后把数据导入相应目录中.
日志:

1
17/05/25 10:22:35 INFO util.AppendUtils: Creating missing output directory - dim_tutor_teacher

--target-dir则直接写入,不创建目录.

11. 指定参数:

不预测执行(降低attempt数量,降低数据库压力).

1
2
3
4
-D
mapreduce.map.speculative=false
-D
mapreduce.job.name=question_hour

scala笔记

http://www.runoob.com/scala/scala-data-types.html
scala api:
http://www.scala-lang.org/api/current/scala/Nothing.html
http://blog.csdn.net/bluishglc/article/details/55668192

运算优先级:

在Scala里所有以“:”结尾的运算符是右关联的,其他的运算符都是左关联的

数据类型

1
2
3
4
5
6
7
8
9
10
11
// Byte,Int,Short,Long等略.
Unit : 表示void. 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。

Null : null 或空引用,Null类是null引用对象的类型,它是每个引用类(继承自AnyRef的类)的子类。Null不兼容值类型

Nothing : Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型。

Any : Any是所有其他类的超类
// Any有两个子类:AnyVal和AnyRef

AnyRef : AnyRef类是Scala里所有引用类(reference class)的基类

Nothing和Unit的区别:

  1. Unit有一个值 ()
    Nothing没有值.
  2. 以Unit为返回值的话,方法能正常结束;
    以Nothing为返回值的话,方法只能以异常退出.

http://blog.csdn.net/bluejoe2000/article/details/30465175

变量与常量:

  • val

    value,值. 定义时立即求值. (饿汉求值). 只求一次.

  • var

    variable,变量. 定义时立即求值. 可改变赋值. 只求一次.

  • def

    define. 每次使用时才求值.(惰性求值). 求N次

  • lazy val

    懒求值. 第一次使用时求值,但只求一次.

  • 退出cli

    :q 或 sys.exit

  • 查看版本:

    scala –version

访问修饰符

private: Scala 中的 private 限定符,比 Java 更严格,在嵌套类情况下,外层类甚至不能访问被嵌套类的私有成员。

1
2
3
4
5
6
7
8
9
class Outer{
class Inner{
private def f(){println("f")}
class InnerMost{
f() // 正确
}
}
(new Inner).f() //错误
}

protected:在 scala 中,对保护(Protected)成员的访问比 java 更严格一些。因为它只允许保护成员在定义了该成员的的类的子类中被访问。而在java中,用protected关键字修饰的成员,除了定义了该成员的类的子类可以访问,同一个包里的其他类也可以进行访问。

1
2
3
4
5
6
7
8
9
10
11
package p{
class Super{
protected def f() {println("f")}
}
class Sub extends Super{
f() // 正确
}
class Other{
(new Super).f() //错误
}
}

public: 任何地方都可以被访问

private[x],protected[x]:这里的x指代某个所属的包、类或单例对象。如果写成private[x],读作”这个成员除了对[…]中的类或[…]中的包中的类及它们的伴生对象可见外,对其它所有类都是private。
这种技巧在横跨了若干包的大型项目中非常有用,它允许你定义一些在你项目的若干子包中可见但对于项目外部的客户却始终不可见的东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package bobsrocckets{
package navigation{
private[bobsrockets] class Navigator{
protected[navigation] def useStarChart(){}
class LegOfJourney{
private[Navigator] val distance = 100
}
private[this] var speed = 200
}
}
package launch{
import navigation._
object Vehicle{
private[launch] val guide = new Navigator
}
}
}

上述例子中,类Navigator被标记为private[bobsrockets]就是说这个类对包含在bobsrockets包里的所有的类和对象可见。
比如说,从Vehicle对象里对Navigator的访问是被允许的,因为对象Vehicle包含在包launch中,而launch包在bobsrockets中,相反,所有在包bobsrockets之外的代码都不能访问类Navigator。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Student{  
var age=20 //底层编译器会自动为age添加get和set的公有方法
private[this] var gender="male"
//private[this] 只有该类的this可以使用
private var name="clow"
//声明了private,底层编译器会自动为私有的name添加get和set的私有方法
//但是可以自己定义属性方法
def getName=this.name
def setName(value:String){this.name=value}
}
//构造器的使用
class Teacher {
var age: Int = _
var name: String = _ //可以预留

//重载的构造器 和public Teacher(){}类似
def this(age: Int, name: String){
this() //必须得调用一次主构造器
this.age=age
this.name=name
}
}

下划线的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 1、作为“通配符”,类似Java中的*。如
import scala.math._


// 2、:_*作为一个整体,告诉编译器你希望将某个参数当作参数序列处理
val s = sum(1 to 5:_*)



3、//指代一个集合中的每个元素。例如我们要在一个Array a中筛出偶数,并乘以2,可以用以下办法:
a.filter(_%2==0).map(2*_)
//又如要对缓冲数组ArrayBuffer b排序,可以这样:
val after = a.sortWith(_.compareTo(_) > 0)
// 默认顺序: val aSorted = a.sorted

4、在元组中,可以用方法_1, _2, _3访问组员。如a._2。其中句点可以用空格替代。


5、使用模式匹配可以用来获取元组的组员,
例如
val (first, second, third) = t
但如果不是所有的部件都需要,那么可以在不需要的部件位置上使用_。比如上一例中
val (first, second, _) = t
val (first, _) = (1, 2)

6、还有一点,下划线_代表的是某一类型的默认值。对于Int来说,它是0。对于Double来说,它是0.0对于引用类型,它是null.

https://my.oschina.net/leejun2005/blog/405305

scala的函数和方法:

Scala 有函数和方法,二者在语义上的区别很小。Scala 方法是类的一部分,而函数是一个对象可以赋值给一个变量。

1
2
3
4
5
6
7
8
9
10
11
函数也类似于一个变量,类似函数指针的概念.
stackoverflow上推荐的代码风格是:
1. 加上 = 号;
2. 加上函数的返回值类型.

“=”并不只是用来分割函数签名和函数体的,它的另一个作用是告诉编译器是否对函数的返回值进行类型推断!如果省去=,则认为函数是没有返回值的!

闭包:
闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。
闭包所引用的外部变量,会被trace,在闭包context内都不会被gc释放.
// 我看来闭包就是一个侧漏的函数,一点也不封闭. 作用是用于一些延迟执行的函数. 比如传递给spark的rdd.

传参:

1
2
3
4
5
6
7
//传值调用(call-by-value):先计算参数表达式的值,再应用到函数内部;

//传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部
// 类似于宏替换
def delayed( t: => Long ) = {
// ...
}

List符号函数

优先使用 ++ 而不是 ::: ;
优先使用 +: 而不是 ::;
冒号靠近List.

actor

http://www.cnblogs.com/vikings-blog/p/3942417.html

函数和方法,def和val区别:

http://www.jianshu.com/p/9b9519a36d78

经测试,scala是从Function0定义到了Function22;
而没有Function23,因此函数对象最多22个参数.

object和class的区别

  1. class中均为实例成员函数\变量;
  2. object中均为static函数变量,为单例模式使用.
    object还与类同名,则为伴生对象,类似于友元,还可以访问私有成员.
  3. 声明一个object, 一个匿名类就会被创建。

使用方法:

  1. 成员函数,变量: new一个对象出来调用;
  2. 静态函数,变量,或者想使用单例模式: 在同文件中写一个object,然后通过object来定义静态成员,并写使用接口. 如果需要访问私有成员,则这个object需要和class同名.

总结:

  1. class: 实例成员的集合
  2. object: 静态成员的集合
  3. 单例: private构造函数,然后创建object. 因为需要访问构造函数,所以其实真正能用的单例对象都是同名的伴生对象. 非同名的object只能称为未关联class的object,可以作为main程序调用.
  4. 访问私有成员: 创建同名object(伴生对象)

scala类修饰符

1
2
3
4
5
6
7
8
//1.使用var声明field,则该field将同时拥有getter和setter 
class MongoClient(var host:String, var port:Int)

//2.使用val声明field,则该field只有getter。这意味着在初始化之后,你将无法再修改它的值。
class MongoClient(val host:String, val port:Int)

//3.如果没有任何修饰符,则该field是完全私有的。
class MongoClient(host:String, port:Int)

但是注意对于Case Class,则稍有不同!Case Class对于通过Primary Constructor声明的字段自动添加val修饰,使之变为只读的。

主构造函数:Primary Constructor

scala的主构造函数指的是在定义Class时声明的那个函数. 函数的参数就是类的参数,函数体就是类定义中可执行的部分.
(感觉连类也像是一个函数)

1
2
3
4
5
6
7
8
class MongoClient(var host: String, var port: Int) {
def this() = { // 重载构造函数
this(null, 0)
host = "127.0.0.1"
port = 27017

}
}

Trait 特征

带有方法实现\字段的接口,没有参数 (使用的extends关键字,因此更像是抽象类)
“堆栈化”(叠加)继承特性: 从左到右入栈,从右到左出栈解析;忽略已经出现的方法.(右侧优先深度优先)

  • 特征构造顺序(与序列化相反)
    1
    2
    3
    4
    父类构造器
    特征构造器(从右到左)
    每个特征当中,父特征先被构造
    子类构造器
    Trait与接口的区别在于,Trait可以直接添加到对象上,而不用在class中声明.
  • 示例如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    trait Debugger {
    def log(message: String) {
    println(message)
    }
    }

    class Worker {
    def work() {
    println("working innocently")
    }
    }

    object TestLogger extends App {
    val worker = new Worker() with Debugger // 这里直接附加trait
    worker.work()
    worker.log("log here")
    }

Case Class

主构造函数的所有参数会被自动限定为val,也就意味着这个字段是外部可读的。
Case Class可以方便得用于DTO(或者叫V0,数据传输对象)的创建,当你希望设计一个类只是作为数据载体的时候.

scala编译器会为case class自动生成一个伴生对象,为class生成4个常用方法:

1
2
3
4
5
equals
hashcode
toString
copy
// copy方法会基于当前实例的所有字段值复制一个新的实例并返回

为伴生对象object生成2个常用方法:

1
2
apply
unapply

上述行为的实现,从编译后的代码看,似乎只写了这些:

1
with scala.Product with scala.Serializable

可能这些方法就在Product和Serializable里? 点进去看是空的.

  • sealed关键字
    1
    2
    3
    4
    5
    6
    7
    //sealed关键字声明其他trait都不能再继承当前的trait
    //除非这个类与声明的这个trait在同一个class文件里!
    sealed trait QueryOption
    case object NoOption extends QueryOption
    case class Sort(sorting: DBObject, anotherOption: QueryOption) extends QueryOption
    case class Skip(number: Int, anotherOption: QueryOption) extends QueryOption
    case class Limit(limit: Int, anotherOption: QueryOption) extends QueryOption
    case class的companion object是隐式自动生成的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    case class Person(firstName:String, lastName: String)

    //注意:case class的companion object是隐式自动生成的!下面的代码并不是手动编写的。
    object Person {
    def apply(firstName:String, lastName:String) = {
    new Person(firstName, lastName)
    }
    def unapply(p:Person): Option[(String, String)] =
    Some((p.firstName, p.lastName))
    }

unapply

Scala 提取器是一个带有unapply方法的对象。unapply方法算是apply方法的反向操作:unapply接受一个对象,然后从对象中提取值,提取的值通常是用来构造该对象的值。

泛型之 Invariant 与 Covariance/Contravariance

默认情况下,scala的泛型是Invariant,也就是强类型,不允许自动上下转型.

1
2
3
4
5
6
7
8
9
10
// +A 表示自动允许向上转型
// -A 表示接受向下转型
case class Item[+A](a: A) { def get: A = a }
val c: Item[Car] = new Item[Volvo](new Volvo)
// 用父类指针接了子类

// -A 表示接受向下转型
case class Check[-A]() {def check(a: A) {}}
val d:Check[VolvoWagon]= new Check[Volvo]()
// 用子类指针接了父类

Null/None/Nothing/Nil

  1. nullNull的唯一对象。
    Null是所有引用类型的子类,
    Any<-AnyRef<-Null;

  2. Nothing是所有类型的子类.返回值是Unit表示没有返回值,返回值是Nothing表示不但没有返回值,而且还不返回,要想离开这个函数只能抛异常;

    1
    如果一个函数可以返回Int,也可以抛异常,则这个函数的返回类型写Int即可. 因为Nothing也是Int的子类, 返回类型可以接受向上转型.
  3. NoneOption的子类. Option<-Some/None;

  4. Nil是所有List[T]的子类,表示空列表. (List的声明为List[+A],接受向上转型)

type 关键字

scala里的类型,除了在定义class,trait,object时会产生类型,还可以通过type关键字来声明类型。

1
2
3
4
5
6
7
8
9
10
11
12
def typeOf[T](v: T): String = v match {
case _: Int => "Int"
case _: String => "String"
case _ => "Unknown"
}

def test1(): Unit = {
type S = String
val objS:S="string of S"
println(typeOf(objS))
println(classOf[S])
}

<: 与 >:

类型上确界和类型下确界

1
2
3
4
5
// T <: Animal的意思是:T必须是Animal的子类(<=)
def biophony[T <: Animal](things: Seq[T]): Unit = things foreach (_.sound())

// >: 传入 Animal的父类或自身, 此时若传入子类,则会被自动向上转型成Animal指针,但实际调用的依然是子类方法
def biophony[T >: Animal](things: Seq[T]) = things

Mutable 和 Immutable

Mutable: 被修改时返回原对象引用
Immutable: 被修改时返回新对象的引用

spark笔记

https://github.com/databricks/learning-spark/blob/master/bin/fakelogs.sh

scala语法相关

  • val

    value,值. 定义时立即求值. (饿汉求值). 只求一次.

  • var

    variable,变量. 定义时立即求值. 可改变赋值. 只求一次.

  • def

    define. 每次使用时才求值.(惰性求值). 求N次

  • lazy val

    懒求值. 第一次使用时求值,但只求一次.

  • 退出cli

    :q 或 sys.exit

  • 查看版本:

    scala –version

scala中文乱码问题:

http://www.runoob.com/w3cnote/mac-scala-chinese-show.html

#spark-submit作业提交相关:

  1. 提交pythonjar都是使用spark-submit命令;
  2. --master可以接受的值:
    1
    2
    3
    4
    5
    6
    7
    spark://host:port
    mesos://host:post
    yarn
    local
    local[N]
    local[*]
    yarn-cluster
    当一直申请不到资源的时候,要使用yarn-cluster.
  1. 格式:

    1
    spark-submit [options] <app jar|python file> [app options]
  2. `

  3. `可以接受的值:

    1
    2
    client : 驱动程序放到本地机器
    master : 驱动程序放到集群上

    其他参数:

    1
    2
    3
    4
    5
    6
    7
    --class 运行java或scala程序时的主类
    --name 显示的应用名
    --jars 需要上传并放到应用classpath中的jar包的列表
    --files 需要放到应用工作目录中的文件列表(如数据文件)
    --py-files 需要添加到pythonpath中的文件,可以包含.py,.egg以及.zip文件
    --executor-memory 执行器进程使用的内存量,字节为单位,可指定后缀
    --driver-memory 驱动器进程使用的内存量,字节为单位,可指定后缀
1
$ZEP_SPARK_HOME/bin/spark-submit --master yarn-cluster $SPARK_HOME/examples/src/main/python/wordcount.py /user/fengmq01/test/input.txt

spark streaming

DStream

DStream由很多RDD组成,每个时间段的数据构成一个RDD.

1
2
3
4
val lines = ssc.socketTextStream("dx-pipe-cpu1-pm", 9092)
这里的lines类型是:
ReceiverInputDStream
从TCP输入流获取行,创建DStream.

rabbitmq in action笔记

管理面板:(好用)
http://localhost:15672/
基本功能可以在网页上进行,创建vhost等高级功能需要命令行.

Channel : 信道.一个连接多个Channel. 节省开销.
Exchange: 交换器.
Queue: 队列. 带名字的邮箱.
Binding: 绑定.// 路由规则

基本工作流程:

  1. 配置类:
    创建exchange,queue;
    为某个exchange创建binding(路由规则).
    比如为exchange1创建规则:
    routing key 为info的,转发到 queue1中.

2.生产者:

将消息打上routing key,发送到某个exchange中.
如果不指定exchange,默认发送到名为””的exchange中.

3.消费者:

从某个queue提取消息.

4.exchange:

根据配置好的路由规则,转发收到的消息到符合的queue.

basic.cosume: 订阅
basic.get : 先订阅,然后获取一条,然后取消订阅.

同一个队列多个消费者的话,1个消费者取了消息(ack),消息被删除,别的消费者就取不到了. 因此这种情况应该使用多个队列.(每人有自己的邮箱)

exclusive: 声明队列为私有的.(单消费者)
durable: 适用于交换器和队列,重启后是否消失.

msg–>Exchange->Queue

生产者:
msg–>Exchange
消费者:
Queue->msg
RabbitMQ:
Exchangebinding–>Queue
e.g:

1
2
binding:
"hello-queue"--"routingkey"-->"hello-exchange"

Exchange

  • direct 直连
  • fanout 一个消息到多个队列
  • topic 多个消息到一个队列
  • header // 不实用,即将弃用

默认Exchange名字为空字符串"",连接到所有队列.

1
2
private static final String DEFAULT_EXCHANGE = "";
private static final String DEFAULT_ROUTING_KEY = "";

topic的routing key格式:

  • *: 通配符,.以外的任何字符任意个
  • .: 分隔符
  • #: 所有队列.

vhost (多租户)

默认vhost是/.
每个vhost里可以有自己的Exchange,Queue,用户,权限等等.
彼此隔离.

持久化消息

  1. 投递时指定
  2. 投递到持久交换器
  3. 投递到持久队列

pig笔记

pig描述的是一个有向无环图(DAG).
节点代表处理数据的操作符,节点间的向量代表数据流。

数据流语言,所以无if和for等控制流语言的元素
(有foreach)
(有别的方法加入控制流)
无关系数据库的事务和索引。

Pig的大小写敏感没有一致的规定。操作和命令是不区分大小写的,比如load,group。而函数是区分大小写的,比如MAX。关系名(类似于变量)也是大小写敏感的。(如A=load 'table1';a=LOAD 'table2')。

pig语句的完全执行是在遇到输出语句的时候(如dumpstore)。
之前即使试图装载一个不存在的文件也不会报错。
在输出语句前,可以重赋值变量,以最后一次赋值为准。

  • run和exec

    这两个命令都可以执行Pig脚本。区别是exec以批处理方式运行,这意味着所有该脚本文件中定义的标示符,当脚本执行完后在脚本调用窗口将不可访问。而以run运行时,相当于将脚本文件中的内容手工在调用窗口输入并执行,这就意味着脚本文件中所有的标示符,比如定义的关系在脚本执行完后仍然可访问,同时在shell的命令历史记录中可以找到脚本中的语句。

  • 运行时参数设定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    -- 运行.pig脚本
    pig -p date=${DATE} -p store_path=${STORE_PATH} getLiveInfoFromLog.pig
    -- -p参数指定键值对
    -- -f参数指定运行的脚本,可省略。

    -- 指定参数文件:
    pig -m daily.params daily.pig

    # -p指定的参数设置值 会覆盖 参数文件中的设置。
    # -r或-dryrun参数可以查看参数替换后的pig脚本,而不会真正执行这个脚本。
    -- 脚本内部定义参数:
    %declare paralle_factor 10;
    -- 通常的默认值,可通过传入参数覆盖
    %default paralle_factor 10;

    使用参数:
    yesterday =fileter daily by data == '$DATE';
  • set设置:
    pig脚本中设置是全局可见的,后面的设置会覆盖前面的值,即使在最后面进行的设置也会对前面的执行语句生效。

1
2
3
-- 引入其他脚本
set pig.import.search.path 'usr/local/pig,/grid/pig';
import 'acme/macros.pig';
  • 宏命令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    -- 简单地内联,不可递归调用
    -- 给定每天的输入和指定的年,分析在支持的股息的那些天里股票价格是如何变化的
    define dividend_analysis(daily,year,daily_symbol,daily_open,daily_close)
    return analyzed
    {
    divs=load 'NYSE_dividends' as
    (exchange:chararray,
    symbol:chararray,
    date:chararray,
    dividends:float
    );
    divsthisyear=filter divs by date matches '$year-.*';
    dailythisyear=filter $daily by date matches '$year-.*';
    jnd=join divsthisyear by symbol,dailythisyear by $daily_symbol;
    $analyzed=foreach jnd generate dailythisyear::$daily_symbol,$daily_close-$daily_open;
    };
1
2
3
4
5
6
7
8
9
/*多行注释*/
-- 单行注释
ls / ; --列出目录
fs -l ; --hdfs命令
rmr filename ; --递归删除
pig命令后可以看到版本:
/*Apache Pig version 0.14.0 (r1640057) compiled Nov 16 2014, 18:02:05*/
-- 结束任务:
kill jobid;
  • 先进行分组后进行连接:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    -- 加载交易表,(select customer,purchase from transactions)
    txns = load 'transactions' as (customer,purchase);
    -- 分组
    grouped= group txns by customer;
    -- 聚合函数sum, total表含group和tp两个字段
    total = foreach grouped generate group, sum (txns.purchase)as tp;
    -- 加载customer_profile表
    profile= load 'customer_profile' as (customer,zipcode);
    -- 表连接
    answer= join total by group, profile by customer;
    -- 输出结果
    dump answer;

filter是什么操作?
好像是限制、过滤操作,类似sql中的where

1
2
3
startswithcm= filter divs by symbol matches 'CM.*';
nostartswith= filter divs by no symbol matches 'CM.*';
-- 布尔操作符的优先级是: not、and、or

x==null返回null,filter只允许判断条件返回true的记录通过,所以2,null,4中,对于条件x==2,只有2通过。(不报错)
null既不会匹配也不会失败,因为null表示未知。

explain语法?


group是关键字吗? 是的。
for each grouped generate group是固定用法,因为:
group关键字有重载,两个含义(两个场景):

  1. grpd= group daily by stock;中,作为group by语句中关键字元素;
  2. group by语句的输出中,第一列字段名也为group, 所以在紧接着的语句中作为字段名:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    cnt= foreach grpd generate group, COUNT(daily);
    -- 可以通过describe语句查看grpd的具体模式(结构):
    describe grpd;
    -- 对多个字段进行分组:(多个字段会组合成tuple放入'group'列中)
    daily = load 'NYSE_daily' as (exchange,stock,date,dividends);
    grpd= group daily by (exchange,stock);
    avg= foreach grpd generate group, AVG(daily.dividends);
    -- 也可以对所有字段进行分组 'all'为关键字
    grpd= group daily all;
    cnt = foreach grpd generate COUNT(daily);
    -- 此时group的输出字段为all而不是group.
  • 查找访问次数最多的前5个URL

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Users = load 'users' as (name,age);--as声明具体模式,列名
    -- 多的列舍弃不要,缺的列填充为null (null表示未知)
    Fltrd = filter Users by age >=18 and age<=25;
    Pages = load 'pages' as (user,url);
    -- 连接获取每个用户访问过的URL
    Jnd = join Fltrd by name, Pages by user;
    Grpd = group Jnd by url;
    -- 每个url,被年龄[18,25]的用户访问了多少次
    Smmd = foreach Grpd generate group , COUNT(Jnd) as clicks;
    Srtd = order Smmd by clicks desc;
    Top5 = limit Srtd 5;
    Store Top5 into 'top5sites';
  • 加载文件:

    1
    LOG = LOAD '/user/hive/warehouse/temp.db/tmp_solar_user_stat/dt=2015-10-01/*' USING PigStorage('\t') AS (userid:int, timestamp:chararray, log_time:chararray, url:chararray, method:chararray, sc:chararray, fplatform:chararray, vendor:chararray, version:chararray, create_date:chararray, total_query:int, failed_query:int, total_favorate:int, today_favorate:int);
  • 特殊数据类型(pig为强类型语言)

  1. chararray: java.lang.String.单引号使用:’fred’.
  2. map: [‘name’#’bob’,’age’#55] 键值间使用’#’号,键值对间使用逗号’,’。 mapkey一定为chararray类型,value可以为任意类型而且可以不同。(也可以设定为都一样。)
  3. tuple: (‘bob’,55)
  4. bag: {(‘bob’,55),(‘john’,25)} 无序的tuple集合。
  • 声明模式:

    1
    2
    3
    4
    -- 限定类型或不限定统一类型:
    as (aaa:map[],bbb:map[int])
    as (aaa:tuple(),bbb:tuple(x:int,y:int))
    as (aaa:bag{},bbb:bag{t:(x:int,y:int)})
  • 未声明时根据下标(从0开始)引用,类型推测:

    1
    2
    daily= load 'NYSE_daily'
    calcs =foreach daily generate $7/1000;

    猜测不出时,会推断为chararray.

    1
    2
    3
    4
    5
    6
    7
    -- 使用范围表示位置(闭区间)
    prices = load 'NYSE_daily' as (exchange,symbol,date,open,high,low,close,volume,adj_close);
    beginning= foreach prices generate ..open;
    -- [exchange,open]
    middle= foreach prices generate open..close;
    end = foreach prices generate volume..;
    -- 也可以使用'*'表示所有字段。
  • 三元条件操作符
    判断条件为null时整个表达式返回null.
    null==2 ? 1:4返回null.
    此外冒号两边应该是同一数据类型。

  • 访问maptuplebag:

    1
    2
    3
    4
    5
    6
    7
    -- map:
    avg = foreach bball generate bat#'batting_average';
    -- tuple:
    B = foreach A generate t.x, t.$1;
    -- bag:
    A = load 'input' as (b:bag {t:(x:int,y:int)});
    B = foreach A generate b.x;

P39为什么A.ybag?

  • order by

    1
    2
    bydate= order daily by date desc,symbol;
    -- desc 仅对紧靠着的date生效.

    null一般是最小的.(升序时排最前.)
    复合类型不可排序。
    pigorder语句在shuffle阶段分发任务时,同一个键对应的任务可能分发到不同的reducer上.

  • distinct

    1
    uniq= distinct daily;
参与作用阶段 map shuffle reduce
? order by
group by
limit
order by
group* by
聚合函数
limit
distinct
join*
cogroup*
cross
影响`reduce`的操作符可以使用`parallel`以进行并行化优化:
1
2
3
4
bysymbol= group daily by symbol parallel 10;
-- 使任务具有10个reducer.
set default_parallel 10;
-- 使整个脚本范围内的reducer数量为10.
  • Join连接后通过::访问同名列:
    1
    daily::exchange : bytearray
  • 外连接
    左外连接要求右边表模式已知。以此类推。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    -- 左外连接 `left outer`
    jnd = join daily by(simbol,date) left,
    divs by (simbol,date);
    -- 右外连接 `right outer`
    -- 全外连接 `full outer`.
    -- 设定连接具体算法:
    jnd = join daily by (exchange,symbol),
    divs by (exchange,symbol)
    using 'replicated'
    ;
    --内连接和左外连接可以使用分片-复制算法,从小到大排列表,最左边的表读入内存。
    -- 数据倾斜时,可对两个表使用skewed join(抽样):
    jnd = join cinfo by city, users by city using 'skewed';

    -- 按连接键排好序的表:
    using 'merge';
  • 抽样(sample)

    1
    2
    divs = load 'NYSE_dividends';
    some= sample divs 0.1;
  • 注册pythonUDF

    1
    2
    3
    4
    register 'production.py' using jython as bballudfs;
    -- bballudfs表示命名空间
    -- jython表示编译器(脚本是python便携)
    -- 调用时使用bballudfs.production(参数)
  • 宏定义define

    1
    2
    3
    4
    define convert com.acme.financial.Convert('dollar','euro');
    -- 可调用java函数:(有性能代价)
    define hex InvokeForString('java.lang.Integer.toHexString','int');
    -- 参数列表以空格作为分隔符 如'int int[] long'
  • flatten
    foreach中的flatten修饰符,bag中每条记录会和generate中其他所有表达式进行交叉乘积。

    1
    2
    3
    4
    5
    6
    7
    8
    players= load 'baseball' as  
    (
    name : chararray,
    position: bag {t: (p:chararray)},
    bat: map[]
    );
    pos=foreach players generate name,flatten(position)as position;
    bypos= group pos by position;

    如记录为:
    Jorge,{(Catcher),(Designated_hitter)}
    则生成两条:
    Jorge,Catcher
    Jorge,Designated_hitter
    flatten对于tuple不会进行交叉乘积,只会把tuple展开为顶层字段。

  • 内嵌foreach语句
    (支持distinct,filter,limit,order)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    -- 每笔交易对应的不同股票交易号的个数。
    daily= load 'NYSE_daily' as (exchange,symbol);
    grpd = group daily by exchange;
    uniqcnt = foreach grpd {
    sym = daily.symbol;
    uniq_sym= distinct sym;
    generate group, COUNT(uniq_sym);
    };
    --查找每只股票支付的前三个股息值:
    divs= load 'NYSE_dividends' as (exchange,symbol,date,dividends);
    grpd = group divs by symbol;
    tops= foreach grpd {
    sorted = order divs by dividends desc;
    top =limit sorted 3;
    generate group, flatten(top);
    };

复习一下semi-join?

  • union

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    A= load 'input1' as (x:int,y:float);
    B= load 'input2' as (x:int,y:double);
    C= union A , B;
    describe C;
    /* * 类型自动转换
    C:{x:int , y: double}
    */
    -- union onschema A,B;
    -- 强制新增列:
    A = load 'input1' as (w:chararray,x:int,y:float);
    B = load 'input2' as (x:int,y:double,z:chararray);
    C = union onschema A,B;
    describe C;
    /*
    C: {w:chararray,x:int,y:double,z:chararray}
    */
  • cross?
    crossfilter以实现非等值join
    (pig不直接支持非等值join)

    1
    2
    crossed = cross table1,table2;
    output= filter crossed by table1::date< table2::date;
  • cogroup:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    A= load 'input1' as (id:int,val1:float);
    B= load 'input2' as (id:int,val2:int);
    C= cogroup A by id, B by id;
    describe C;
    /**注意到第一个字段名依然是group。
    C:{
    group:int,
    A:{id:int,val1:float}},
    B:{id:int,val2:int}
    }
    */
  • stream:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    -- 把py脚本加载到集群,
    -- getLiveInfoFromLog表示加载后的别名,
    -- `getLiveInfoFromLog.py`表示加载的脚本,
    -- SHIP ('getLiveInfoFromLog.py');表示依赖的文件。
    -- 可以依赖多个文件,
    -- 例如可以SHIP ('getLiveInfoFromLog.py','rule.conf')。

    define getLiveInfoFromLog `getLiveInfoFromLog.py` SHIP ('getLiveInfoFromLog.py');

    -- 使用stream xxx through语句,调用脚本生成新表。
    new_table = STREAM table1 THROUGH getLiveInfoFromLog AS (date:chararray,roomId:chararray,userId:chararray);
    # 加载目录必须是工作目录的相对路径,不能是绝对路径。
  • 分布式缓存

    1
    2
    3
    4
    5
    crawl = load 'webcrawl as (url,pageid)';
    normalized =foreach crawl generate normalized (url);
    define blc 'blacklistchecker.py' cache('/data/share/badurls#badurls');
    goodurls=stream normalized through blc as (url,pageid);
    -- '#'号前是hdfs上的路径,后面是读取的文件名。
  • 非标准输入输出的脚本

    1
    2
    define blc 'blacklistchecker.py -i urls -o good' input('urls') output('good');
    -- input和output中指定的都是工作目录的相对路径下文件。
  • mapreduce任务

    1
    2
    3
    4
    goodurls=mapreaduce 'blacklistchecker.jar'
    store table1 into 'input'
    load 'output' as (url,pageid)
    'com.acmeweb.security.BlackLiskChecker -i input -o output';
  • split

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    split wlogs into 
    apr03 if timestamp < '20110404',
    apr02 if timestamp < '20110403' and timestamp>'20110401',
    apr01 if timestamp < '20110402' and timestamp>'20110331'
    ;

    store apr03 into '20110403';
    store apr02 into '20110402';
    store apr01 into '20110401';
    -- 可由fliter by等效改写
  • 数据仓库:

    ODS(Operational Data Store,操作型存储)、EDW(Enterprise Data Warehouse,企业数据仓库)、DM(DataMart,数据集市)区别开来,共分三层。

如何查看环境变量如$date?

  • 正则:
    {ab,cd}匹配字符串集合中任一字符串。
  • 完整示例脚本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    set mapred.output.compress false;
    set pig.exec.reducers.bytes.per.reducer 500000000;
    set mapreduce.reduce.memory.mb 10240;
    DEFINE dwSolarUserStat `dwSolarUserStat.py` SHIP('dwSolarUserStat.py');
    DEFINE solarFrog `solarFrog.py` SHIP('solarFrog.py','rule.conf');
    DEFINE solarSpam `solarSpam.py` SHIP('solarSpam.py');
    LOG = LOAD '/user/hive/warehouse/temp.db/tmp_solar_user_stat/dt=$date/*' USING PigStorage('\t') AS (userid:int, timestamp:chararray, log_time:chararray, url:chararray, method:chararray, sc:chararray, fplatform:chararray, vendor:chararray, version:chararray, create_date:chararray, total_query:int, failed_query:int, total_favorate:int, today_favorate:int);

    LOG_FROG = LOAD '/user/hive/warehouse/temp.db/tmp_solar_frog/dt=$date/*' USING PigStorage('\t') AS (userid:chararray, url:chararray, duration:chararray, imagesize:chararray, net:chararray, timestamp:chararray, log_dt:chararray, competitors:chararray);

    LOG_FROG_ORDER = ORDER LOG_FROG BY userid,timestamp;


    SESSION_TASKINFO_GRP = GROUP LOG BY userid;
    SESSION_TASKINFO_GRP_SORTED = FOREACH SESSION_TASKINFO_GRP {
    ORDERED = ORDER LOG BY log_time;
    GENERATE FLATTEN(ORDERED);
    }


    SESSION_TASKINFO_GRP_FROG = GROUP LOG_FROG_ORDER BY userid;
    SESSION_TASKINFO_GRP_SORTED_FROG = FOREACH SESSION_TASKINFO_GRP_FROG {
    ORDERED_FROG = ORDER LOG_FROG_ORDER BY timestamp;
    GENERATE FLATTEN(ORDERED_FROG);
    }


    DATA = STREAM SESSION_TASKINFO_GRP_SORTED THROUGH dwSolarUserStat AS (userid:int,create_date:chararray,total_query:int,failed_query:int,total_favorate:int,today_favorate:int,session_num:int,session_time:int,vendor:chararray,platform:chararray,version:chararray);

    SPAM_FROG = STREAM SESSION_TASKINFO_GRP_SORTED_FROG THROUGH solarSpam AS (userid:int);

    DATA_FROG = STREAM SESSION_TASKINFO_GRP_SORTED_FROG THROUGH solarFrog AS (userid:int,favorite_action:int,load_image_action:int,load_image_lasttime:int,load_image_failed_action:int,search_lasttime:int,load_image_imagesize:int,net_action:chararray,net_lasttime:chararray,mark_compeitorst:chararray);


    STORE DATA INTO '$store_path/$date/stat';
    STORE SPAM_FROG INTO '$store_path/$date/spam';
    STORE DATA_FROG INTO '$store_path/$date/frog';

究竟谁是zhengguanyu?


##调试:

  • describe

    1
    2
    3
    4
    5
    describe grpd;
    /*
    输出:
    grpd:{group:chararray,trimmed:{(symbol:chararray,dividends:float)}}
    */
  • explain

    1
    2
    3
    pig -x local -e 'explain -script explain.pig
    -- -x local 指定运行模式为本地模式
    -- -e excute后的命令

– pigUnit测试脚本

kylin笔记

星型模型:

有冗余,快 所有维度表直接连接到事实表;(间接,eg. 地域id->(国家,省份))
维度表之间没有关联.

雪花模型:

无冗余,慢 不是所有维度表直接连接到事实表.(间接,eg. 地域id->(国家id,省份id)->国家维度表,省份维度表)
维度表之间存在关联.

kylin只支持星型模型,所以不能太复杂.

聚合组:

通过聚合组分隔维度,使cube数量降低;

Mandatory:(必选列)

将总是出现在where或group by 里的列设置为Mandatory,减少预计算开销;
A为必选:
(AB,C,AC)=>(AB,AC)

Hierarchy:(WITH ROLLUP)

将国家\省\市,设置为层级关系Hierarchy,减少预计算开销.

Derived:(推断):

LOOKUP表(维度表),若DimA的值唯一确定DimB的值,则DimB能从DimA获得(Derived),则标记DimB为Derived可以将group by 简化.
(ABC,AB,AC => AC,A)

job状态:

PENDING
RUNNING
ERROR
DISCARD
FINISH

api:

get xxx/kylin/api/jobs/{job_uuid}/steps/{step_id}/output

PUT xxx/kylin/api/jobs/{job_uuid}/resume
(ERROR可以resume,DISCARD无法resume)

PUT xxx/kylin/api/jobs/Cubes/{cube_name}/rebuild
cubename
startTime
endTime
buildType: BUILD,MERGE,REFRESH

POST xxx/kylin/api/query
sql:”select 1”
offset:0
limit:2
acceptPartial:false
project:”learn_kylin”

可以用视图解决字段数据类型的约束.

拷贝工厂

尽量不使用继承,使用聚合。

聚合:雁群与雁
组合:雁与翅膀

除了builder模式,还有一个有用的技巧:拷贝工厂

cloneable接口和clone方法依赖于整个对象继承树的健壮性,由于设计理念是链式调用父类的clone方法,维护难度过高,出错概率太大。

可行的方案之一是使用拷贝构造函数。

更推荐的方案是使用拷贝工厂,例如下文的:
public static ClassA newInstance(ClassA obj);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package test1;
import static java.lang.System.out;
public class ClassA {
public static void main(String[] args) {
ClassA a1=new ClassA("a1",1);
ClassA a2=ClassA.newInstance(a1);
a1.AInt=2;
a1.AString="changed";
a1.array[0]=100;
out.println(a1.AString);
out.println(a2.AString);
out.println(a1.AInt);
out.println(a2.AInt);
out.println(a1.array[0]);
out.println(a2.array[0]);
}
String AString;
int AInt;
int array[];
public ClassA(String aString,int aInt) {
this.AString=aString;
this.AInt=aInt;
array=new int[1];
array[0]=1;
}
private ClassA() {}
public static ClassA newInstance(ClassA obj) {
ClassA classA = new ClassA();
classA.AInt = obj.AInt;
classA.AString = obj.AString;// 并没有任何问题
classA.array = obj.array.clone();// 基本对象
//other deepcopy here
return classA;
}
}

log4j2配置学习笔记

根节点(1级): Configuraion

2级节点:

Appenders

常见三种:

  • Console : 控制台
  • File: 指定位置的文件. fileName可带路径.
  • RoolingFile: 超过指定大小自动删除旧的创建新的Appender.
    • filePattern: 指定新建日志文件的名称格式.
    • Policies: 可以以时间滚动\文件大小滚动\日志文件数量.

Loggers

常见两种:Root和Logger

  • Root: 默认配置,Logger里没有的就会继承Root里的.
    • level: ALL<Trace<Debug<Info<Warn<Error<Fatal<OFF
    • AppenderRef: 具体输出到哪个Appender.
  • Logger: 比如为指定包下的class指定不同的日志级别.
    • name: 指定所适用的类或包的全路径.

异步logger

需要加入依赖:

1
compile group: 'com.lmax', name: 'disruptor', version: '3.3.7'

log4j2.component.properties文件:

1
2
log4j2.AsyncQueueFullPolicy: Discard
#AsyncLoggerConfig.RingBufferSize: 524288

异步配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
Configuration:
properties:
property:
- name: logPath
value: /Users/xiaoyue26/learn/logs/
- name: filename
value: dev.log
- name: pattern
value: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%p] [%t] [%c] @@@traceId=%X{TRACE_ID}@@@ %m%n"
- name: httpRequestPattern
value: "%-d{yyyy-MM-dd HH:mm:ss.SSS} @@@traceId=%X{TRACE_ID}@@@ %m%n"

status: "info"
Appenders:
RollingRandomAccessFile:
- name: "FileAppender"
fileName: "${logPath}${filename}"
filePattern: "${logPath}${filename}.%d{yyyy-MM-dd}"
PatternLayout:
pattern: "${pattern}"
Policies:
TimeBasedTriggeringPolicy: {}
immediateFlush: false
- name: "AnalysisFileAppender"
fileName: "${logPath}analysis-${filename}"
filePattern: "${logPath}analysis-${filename}.%d{yyyy-MM-dd}"
PatternLayout:
pattern: "${pattern}"
Policies:
TimeBasedTriggeringPolicy: {}
immediateFlush: false
- name: "HTTPRequestFileAppender"
fileName: "${logPath}http-request-${filename}"
filePattern: "${logPath}http-request-${filename}.%d{yyyy-MM-dd}"
PatternLayout:
pattern: "${httpRequestPattern}"
Policies:
TimeBasedTriggeringPolicy: {}
immediateFlush: false
- name: "RPCRequestFileAppender"
fileName: "${logPath}rpc-request-${filename}"
filePattern: "${logPath}rpc-request-${filename}.%d{yyyy-MM-dd}"
PatternLayout:
pattern: "${pattern}"
Policies:
TimeBasedTriggeringPolicy: {}
immediateFlush: false
Async:
- name: "AsyncFileAppender"
AppenderRef:
- ref: FileAppender
bufferSize: 10000
- name: "AsyncAnalysisFileAppender"
AppenderRef:
- ref: AnalysisFileAppender
bufferSize: 15000
- name: "AsyncHTTPRequestFileAppender"
AppenderRef:
- ref: HTTPRequestFileAppender
bufferSize: 10000
- name: "AsyncRPCRequestFileAppender"
AppenderRef:
- ref: RPCRequestFileAppender
bufferSize: 10000

Loggers:
AsyncLogger:
- name: "RequestLogger"
level: info
additivity: false
AppenderRef:
- ref: AsyncHTTPRequestFileAppender
- name: "RpcRequestLogger"
level: info
additivity: false
AppenderRef:
- ref: AsyncRPCRequestFileAppender
- name: "AnalysisLogger"
level: info
additivity: false
AppenderRef:
- ref: AsyncAnalysisFileAppender
AsyncRoot:
level: info
AppenderRef:
- ref: AsyncFileAppender

示例同步配置yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Configutation:
status: warn

Appenders:
Console: # 控制台的appender
name: CONSOLE
target: SYSTEM_OUT
PatternLayout:
Pattern: "%d{ISO8601} %-5p [%c{3}] [%t] %m%n"
RollingFile: # 输出到文件,超过的归档
- name: APPLICATION
fileName: /Users/xiaoyue26/learn/logs/test.log
filePattern: "/Users/xiaoyue26/learn/logs/$${date:yyyy-MM}/test-%d{yyyy-MM-dd}-%i.log.gz"
PatternLayout:
Pattern: "%d{ISO8601} %-5p [%c{3}] [%t] %m%n"
policies:
TimeBasedTriggeringPolicy:
interval: 1 # 1小时
modulate: true # 自动对齐时间间隙

Loggers:
Root:
level: info
AppenderRef:
- ref: CONSOLE
- ref: APPLICATION
Logger:
- name: com.myco.myapp.Foo
additivity: false
level: info
AppenderRef:
- ref: CONSOLE
- ref: APPLICATION
- name: com.myco.myapp.Bar
additivity: false # 不输出到Root的appenderRef里了
level: debug
AppenderRef:
- ref: CONSOLE
- ref: APPLICATION