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

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文档,最近在工作确实体会到这种方式的好处。