最近的工作比较杂,因此一直没有整理一篇博文。刚好五一假期了,想着不能再拖下去了,即使写出的东西太琐碎,也稍微记录下,作个备忘也挺好的。

spring boot应用中使用redis缓存

如子标题,有需求要在spring boot应用中使用redis缓存,这个还是比较简单的,如下:

添加maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

添加配置

spring:
  redis:
    # Redis服务器连接密码(默认为空)
    password: 
    # Redis数据库索引(默认为0)
    database: 0
    # Redis服务器连接端口
    port: 6379
    pool:
      # 连接池中的最大空闲连接
      max-idle: 8
      # 连接池中的最小空闲连接
      min-idle: 0
      # 连接池最大连接数(使用负值表示没有限制)
      max-active: 8
      # 连接池最大阻塞等待时间(使用负值表示没有限制)
      max-wait: -1
    # Redis服务器地址
    host: localhost
    # 连接超时时间(毫秒)
    timeout: 0

然后就可以使用了StringRedisTemplate等Bean了

  @Autowired
	private StringRedisTemplate stringRedisTemplate;

	@Test
	public void test() throws Exception {
		// 保存字符串
		stringRedisTemplate.opsForValue().set("aaa", "111");
		Assert.assertEquals("111", stringRedisTemplate.opsForValue().get("aaa"));
  }

上面的代码通过自动配置的StringRedisTemplate对象进行Redis的读写操作,该对象从命名中就可注意到支持的是String类型。StringRedisTemplate就相当于RedisTemplate<String, String>的实现。除了String类型,实战中我们还经常会在Redis中存储对象,这时可以自己实现RedisSerializer<T>接口来对传入对象进行序列化和反序列化,进而将该对象写入Redis缓存。

public class User implements Serializable {
    private static final long serialVersionUID = -1L;
    private String username;
    ...
}


public class RedisObjectSerializer implements RedisSerializer<Object> {
  private Converter<Object, byte[]> serializer = new SerializingConverter();
  private Converter<byte[], Object> deserializer = new DeserializingConverter();
  static final byte[] EMPTY_ARRAY = new byte[0];
  
  public Object deserialize(byte[] bytes) {
    if (isEmpty(bytes)) {
      return null;
    }
    try {
      return deserializer.convert(bytes);
    } catch (Exception ex) {
      throw new SerializationException("Cannot deserialize", ex);
    }
  }

  public byte[] serialize(Object object) {
    if (object == null) {
      return EMPTY_ARRAY;
    }
    try {
      return serializer.convert(object);
    } catch (Exception ex) {
      return EMPTY_ARRAY;
    }
  }

  private boolean isEmpty(byte[] data) {
    return (data == null || data.length == 0);
  }
}

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, User> userRedisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, User> template = new RedisTemplate<String, User>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new RedisObjectSerializer());
        return template;
    }
}

  @Autowired
	private RedisTemplate<String, User> redisTemplate;

	@Test
	public void test() throws Exception {
		// 保存对象
		User user = new User("超人", 20);
		redisTemplate.opsForValue().set(user.getUsername(), user);
		user = new User("蝙蝠侠", 30);
		redisTemplate.opsForValue().set(user.getUsername(), user);
		user = new User("蜘蛛侠", 40);
		redisTemplate.opsForValue().set(user.getUsername(), user);
		Assert.assertEquals(20, redisTemplate.opsForValue().get("超人").getAge().longValue());
		Assert.assertEquals(30, redisTemplate.opsForValue().get("蝙蝠侠").getAge().longValue());
		Assert.assertEquals(40, redisTemplate.opsForValue().get("蜘蛛侠").getAge().longValue());
 }

RedisTemplate接口的方法很多,基本上涵盖了Redis了绝大部分操作,使用时参考其API文档就可以了。

spring boot应用中使用rabbitmq

如子标题,有需求要在spring boot应用中使用redis缓存,这个还是比较简单的,如下:

添加maven依赖

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

添加配置项

spring:
  rabbitmq:
    password: 123456
    port: 5672
    host: localhost
    username: spring

配置队列、交换器、路由等高级信息,一般会用到org.springframework.amqp.core.Queueorg.springframework.amqp.core.QueueBuilder等类、org.springframework.amqp.core.Exchange的实现类,使用时参考其API文档

@Configuration
public class RabbitConfig {
    @Bean
    public Queue helloQueue() {
        return new Queue("hello");
    }
}

使用AmqpTemplate发送MQ消息,基本使用AmqpTemplate就可以进行MQ消息的绝大部分操作,使用时参考其API文档就可以了。

    @Autowired
    private AmqpTemplate rabbitTemplate;

    public void send() {
        String context = "hello " + new Date();
        System.out.println("Sender : " + context);
        this.rabbitTemplate.convertAndSend("hello", context);
    }

使用RabbitListener配合RabbitHandler接收MQ消息,一般会用到org.springframework.amqp.rabbit.annotation.RabbitListenerorg.springframework.amqp.rabbit.annotation.RabbitHandlerorg.springframework.amqp.rabbit.annotation.Queueorg.springframework.amqp.rabbit.annotation.Exchange等类,使用时参考其API文档

@Component
@RabbitListener(queues = "hello")
public class Receiver {
    @RabbitHandler
    public void process(String hello) {
        System.out.println("Receiver : " + hello);
    }
}

spring boot应用打包成docker镜像

spring boot应用的构建工具已经很完善了,要完成子标题所述的任务已经有很成熟的maven plugin - docker-maven-plugin

简单使用方法

<build>
  <plugins>
    ...
    <plugin>
      <groupId>com.spotify</groupId>
      <artifactId>docker-maven-plugin</artifactId>
      <version>VERSION GOES HERE</version>
      <configuration>
        <imageName>example</imageName>
        <baseImage>java</baseImage>
        <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint>
        <!-- copy the service's jar file from target into the root directory of the image --> 
        <resources>
           <resource>
             <targetPath>/</targetPath>
             <directory>${project.build.directory}</directory>
             <include>${project.build.finalName}.jar</include>
           </resource>
        </resources>
      </configuration>
    </plugin>
    ...
  </plugins>
</build>

如果要使用自定义的Dockerfile,则如下配置,在dockerDirectory指定的目录下放入Dockerfile文件就可以了:

<build>
  <plugins>
    ...
    <plugin>
      <groupId>com.spotify</groupId>
      <artifactId>docker-maven-plugin</artifactId>
      <version>VERSION GOES HERE</version>
      <configuration>
        <imageName>example</imageName>
        <dockerDirectory>docker</dockerDirectory>
        <resources>
           <resource>
             <targetPath>/</targetPath>
             <directory>${project.build.directory}</directory>
             <include>${project.build.finalName}.jar</include>
           </resource>
        </resources>
      </configuration>
    </plugin>
    ...
  </plugins>
</build>

其它还可以在package时自动打docker镜像,在deploy时自动将docker镜像推入registry,这些高级功能参考官方文档

centos7下手动设置DNS服务器

centos7下网络默认是由NetworkManager管理的,如果直接修改/etc/resolv.conf设置的DNS服务器很容易被冲掉,因此找到了一个办法解决这个问题。

  • 修改 /etc/NetworkManager/NetworkManager.conf 文件,在main部分添加 “dns=none” 选项:

    [main]
    plugins=ifcfg-rh
    dns=none
    
  • NetworkManager重新装载上面修改的配置

    # systemctl restart NetworkManager.service
    
  • 手工修改 /etc/resolv.conf

    nameserver 114.114.114.114
    nameserver 8.8.8.8
    

专业的bash脚本

最近看了istio-sidecar相关的bash脚本,发现一个专业的bash脚本最好还是不要像写流水帐一样书写脚本逻辑,是很有必要加入必要的注释、输入参数解析、脚本使用说明、定义主函数及各分支函数。

  • 在脚本开关需要用英文书写必要的注释详细说明脚本的用途,这一点参考一些专业的脚本都可以看到。

  • 建议使用Linux风格的输入参数风格解析,可以使用bash的内置命令getopts和外部命令getopt,这两种方法的使用方法可参考shell脚本之shift和getoptsshell中的getopt与getopts

  • 脚本使用说明可使用usage函数完成,如下:

    usage() { 
    	echo "bla bla bla ..." 
    } 
      
    # 解析参数时,当发现-h或--help参数,立即执行usage,输出脚本使用说明
    -h|--help)
    usage
    ;;
    
  • 为了避免bash脚本成为流水帐,建议整个脚本按以下函数组织

    # 解析参数
    parse_args() {
    ...
    }
      
    # 校验参数
    validate_args() {
    ...
    }
      
    # 脚本所做工作第一步
    do_work_step1() {
    ...
    }
      
    # 脚本所做工作第二步
    do_work_step2() {
    ...
    }
      
    # 脚本所做工作第三步
    do_work_step3() {
    ...
    }
      
    main() {
    parse_args
    validate_args
    do_work_step1
    do_work_step2
    do_work_step3
    ...
    }
      
    # 脚本入口函数
    main
    

## swagger文档的妙用

很多后端的项目都以swagger文档的方式向外暴露API文档,最近在工作确实体会到这种方式的好处。

  • 前端拿到swagger API文档后,可使用swagger-editor轻松生成nodejs-server版的server stub,在此基础上即可开发简易的mock server了,这样前端的开发即可不再依赖后端了。

  • 后端可使用swagger-editor生成对应语言的server stub,生成的代码很有参考价值,可直接在此基础上改造或将部分代码拷贝到已有后端项目中。

  • 如果是微服务架构的应用,可使用swagger-editor生成相应语言的客户端代码,这样服务间的调用直接用客户端代码组合形成的SDK即可,不再需要手动发送HTTP请求及解析HTTP响应了。

  • 微服务架构的应用,每个微服务都以swagger方式暴露API,这时可以将这些API文档聚合起来,请团队中的成员在统一的文档中心查看各微服务的文档,如下:

    docker run -d --name swagger-docs -p 8888:8080 -e 'URLS=[{ url: "http://petstore.swagger.io/v2/swagger.json", name: "Petstore" }, { url: "http://generator.swagger.io/api/swagger.json", name: "Generator" }]' swaggerapi/swagger-ui:latest
    

    这里使用了swagger-ui的一个urls选项,这个选项在2017年初就已经存在了,不知道为什么网上讲swagger API文档聚合的方案基本都是让改造swagger-ui的代码,汗!

参考

  1. http://blog.didispace.com/springbootredis/
  2. http://blog.didispace.com/spring-boot-rabbitmq/
  3. https://github.com/spotify/docker-maven-plugin
  4. http://www.pubyun.com/blog/announce/centos-7-%E4%B8%8B%EF%BC%8C%E5%A6%82%E4%BD%95%E8%AE%BE%E7%BD%AEdns%E6%9C%8D%E5%8A%A1%E5%99%A8/
  5. http://www.361way.com/shell-shift-getopts/4973.html
  6. http://www.361way.com/shell-getopt/4981.html
  7. https://github.com/swagger-api/swagger-ui/pull/3261