《Kubernetes In Action》阅读小结

说起看一本书,了解一个新词汇,掌握一门新技术,总是要有契机的。毕竟新的事物的接收和消化对于我们很多人而言不是那么平滑,尽管我们对外宣称年轻的我们乐于接受新鲜事物。我当时去看这本书的原因是因为我看了另一本书——《Kubernetes 权威指南》,这本书我现在只看了前面一点点,具体到我只看到了Kubernetes的发展历史,或者说是发家历史。这一点我非常喜欢这本书中描述的故事,将清楚了Kubernetes的“恩怨情仇”,像小说一样的开局还是很吸引人的。而且我一直喜欢别人讲技术的时候,提一提它的历史。但是我当时没有看下去的原因是接下来的很多文字和源码的原因,最主要的是源码,我不喜欢一直看,毕竟只看不写点东西是不容易入门一种新技术的,所以我转向了这本书——《Kubernetes In Action》.

这本书到现在,我除了4个副录没有看,基本上看完了,包括后记。总体来说是一本写得很好的书,首先整本书看下来会对作者组织能力很欣赏,他把Kubernetes由基础到高级知识,或者说是内部实现细节娓娓道来。这本书也是围绕着一个“简单”的例子Kubia展开。从最开始的总览到到核心概念:Pod,Replication Controller,Service,Volume,ConfigMap,Secrets,Deployment,StatefulSet. 第三部分讲到“高级”的概念Service Account, Pod的资源管理,自动化伸缩Pod和节点,高级调度,最佳实践及Kubernetes扩展.作为一名java开发者最受益的地方估计是Kubernetes的最佳实践,毕竟时下微服务很火。在最佳实践章节中,我印象很深的有2点:(1)一个就是关于并发编程中经常取cpu的核数作为并发线程数,在这里我们学了一课,并不是所有的情况都是这样的,在Kubernetes中cpu的核数还受到资源限制的约束,不然你所取到的cpu核数不是真正的cpu核数。(2)另一点就是关于docker镜像打标签问题,尤其是打latest标签,有可能在Replicas中运行的是几个不同版本的代码。

最后可以提及的是后记,这本书很适合有一定基础的Docker用户看,我在看这本书的时候,脑海里也是时常想起Docker中的一些概念,比如在本书中讲到容器启动依赖的时候,我就会想起docker-compose中是如何实现这一点的。如果说Docker带领我们进入了容器时代,k8s可以说是容器时代的“掌舵人”,它在引领着前进方向。当你看了Docker的相关书籍,你或许可以尝试一下这本书,它不会让你失望。毕竟曾经你或许有过迷惑——怎么发布你的应用,green blue部署还是滚动升级Rollout Upgrate,还有就是声明式部署,不是一行行shell命令敲下去。如果你有这样的疑惑,我想再次向你推荐这本书。

小小福利:kubernetes 正确的发音是[kubə’netis],重音在第三个音节 。

链接:https://pan.baidu.com/s/1zgaUMOVQj0qxA5jLE7qtkw
提取码:sfzy

补充一点:如果你使用的是windows 10 ,也是尝试使用Minikube作为实验环境,但是就是建不好环境,我希望你可以尝试一下kind

坚持阅读

这将是一篇一直更新的文章,直到有一个单独的网页应用或者APP来替代这篇文章。我就把最近一个月阅读的书,做一个记录,也算是一个分享,曾经,我只是一个书籍的收集着,或者说是收藏者,我希望,我可以做一个真正阅读者和分享者。

最近读的几本书有:


这本书,大约看了两周(19/11/06~19/11/19),边看边敲代码,做完几个小的APP还是很有成就感,整本书的编写也是很优秀,如果评分的话,我给9分(满分10分的话),这本书可以让想学React Native的想法一一落实,算开了一个好头,不过Apple开发者的资格会员费是有点贵。
这本书,我之前是看了一部分,但是没有全部看完,这次趁着“坚持阅读”计划,我决定把它捡起看完,毕竟JDK 1.8还是一个比较大的版本更新,而且我也很喜欢1.8的新特性,一直用着,这次我就一次看完了,虽说基本生活是看的代码。用时一天(19/11/20~19/11/21),如果评分的话,我可以给7.5分,还是不错的一本书。
这边书是上面Java 8 In Action 的第二版,有一些jdk9和10的新特性,我也是用了一天的时间看完的,19/11/21~19/11/22,,如果评分的话,8分,所以,如果要看一本modern java书,这一本还是比上一本值得推荐。
这是一本我目前还在看的书,我是从19/11/23开始的,一直到现在19/12/01,已经过了一周了,客观上讲,这本书,只是让我知道了一些概念,讲得不深,算是视野扩展吧,我打算今天把它结束掉,应该在我心目中,最多可以给3分,不能最多了。
这是docker in action的第二版,我之前看过的是中文版的第一版,相对于第一版,的确变化很大,这也说明了docker发展地很快,印象深刻的是Dockerfile,之前还是version 2,现在都到了3,有了一些新的指令。这本书一共花了3天时间19/12/27~19/12/29.下次我想实践的应该是也微服务一起吧。

springboot2+redis实现注解缓存

一、项目核心依赖(pom文件)

       <!--LettuceConnectionFactory-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

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

二、核心配置文件(yaml文件)

spring:
  application:
    name: postgresql-demo
  datasource:
    url: jdbc:postgresql://localhost:5432/test
    username: postgres
    password: 123456
    driver-class-name: org.postgresql.Driver
  jpa:
    show-sql: true
    properties:
      hibernate:
        temp:
          use_jdbc_metadata_defaults: false
        dialect: org.hibernate.dialect.PostgreSQLDialect
        hbm2ddl:
          auto: update
  cache:
    type: redis
  redis:
    timeout: 10s
    lettuce:
      pool:
        max-active: 8
        max-wait: 30s
        max-idle: 8
        min-idle: 0

三、Redis的配置文件(Java文件)

package com.wangyousong.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.Arrays;


@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    private final LettuceConnectionFactory lettuceConnectionFactory;

    @Autowired
    public RedisConfig(LettuceConnectionFactory lettuceConnectionFactory) {
        this.lettuceConnectionFactory = lettuceConnectionFactory;
    }

    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            Arrays.stream(params).map(Object::toString).forEach(sb::append);
            return sb.toString();
        };
    }
 
 
    // key键序列化方式
    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }
 
    /**
     * json序列化
     */
    @Bean
    @SuppressWarnings({"rawtypes", "unchecked"})
    public RedisSerializer<Object> jackson2JsonRedisSerializer() {
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = setObjectMapper();
        serializer.setObjectMapper(mapper);
        return serializer;
    }

    private ObjectMapper setObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        return mapper;
    }

    /**
     * 配置缓存管理器
     */
    @Bean
    public CacheManager cacheManager() {
        // 生成一个默认配置,通过config对象即可对缓存进行自定义配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // 设置缓存的默认过期时间--10分钟,也是使用Duration设置,默认-1表示不过期
        config = config.entryTtl(Duration.ofMinutes(10))
         // 设置 key为string序列化
         .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
         // 设置value为json序列化
         .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer()))
         // 不缓存空值
         .disableCachingNullValues();
        // 使用自定义的缓存配置初始化一个cacheManager
        return RedisCacheManager
         .builder(lettuceConnectionFactory)
         .cacheDefaults(config)
         .transactionAware()
         .build();
    }
 
    /**
     * RedisTemplate配置
     */
    @Bean
    @SuppressWarnings({"rawtypes", "unchecked"})
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = setObjectMapper();
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        //key
        redisTemplate.setKeySerializer(keySerializer());
        redisTemplate.setHashKeySerializer(keySerializer());
        //value 值采用json序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
 
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

四、实例代码及运行效果

五、后记(代码小结)

每次写小结的时候,我都知道要认真写了,应该写代码不仅仅是为了写代码,而是为了回顾与总结在写代码时弄清楚的问题,解决问题是结果,但是我却享受过程。
(1)yaml文件中与spring.redis.timeout=10s相等的那条配置,如果写错了,比如timeout=0了,那么会遇到超时的错误;
(2)spring.redis.pool其实都有默认的值,不写也是可以的,只要要注意一下spring.redis.pool.max-wait,它的默认值是-1.


(3)关于KeyGenerator,在RedisConfig中配置,我们是如下配置:

类名+方法名+各个参数

但是实际应用的时候,我们需要根据实际情况应用,有时候还是需要自己定义。
(4)关于@Cacheable中的unless = “#result == null”,这个需要注意。如果没有写这个属性,我们缓存一个null,会报错的。显然缓存null也不是我们希望的,所以在RedisConfig中,我们是设置了不缓存空值的。

当然,这点会在程序报错的时候看到友好的提示的。比如调用UserService.findById(String id),如果id是一个不存的,那么返回的结果Optional中value便是null.
(5)关于默认缓存的时间,默认如果不配置的话TTL=-1,表示永久不过期,在上面RedisConfig中我们设置的是10分钟,通常这个值需要根据业务需求设置。
(6)最后也是看了缓存应用中的问题。UserService中findById(String id)负责查询并缓存查询结果,void update(UserDTO userDTO)负责更新数据,并清空缓存,不然 findById(String id)拿到数据就是“脏数据”,或者说是过期数据,实际的效果就是用户的体验不是很好,因为用户看不到自己实时提交(修改)的数据。所以这时候@CacheEvict注解就很重要了。当时我演示的时候,发现就是没有清空OptionalUser,我当时就很好奇,为什么?注解难道用错了?还是环境配置有问题,还是什么其它的问题?想了一番,乱搜了一番,无果。冷静下来,看看Spring Cache是如果是实现@CacheEvict机制的,换句话说,就是spring cache 是如何解析 @CacheEvict 这个注解的?后来看了一下源码,从@interface CacheEvict 看到interface CacheAnnotationParser,再看到class SpringCacheAnnotationParser,再看到abstract class CacheOperation,再追踪到class CacheEvictOperation,再一搜被调用的地方:

看到CacheAspectSupport中Aspect,就一下子想通了,肯定是通过SOP,所以结果就是在这个类中

然后就打了一下断点运行,看到update()到底做了什么,为什么没有更新findById查询出来的OptionalUser,后来一下他们的key不一样,如果我不指定key值,将采用默认的key生成策略,就是前面介绍的目标类::方法限定名+参数列表,显然查询和更新的方法名是不一样的,尽管我定义了value的值。所以我重写了@Cacheable(value = “OptionalUser”, key = “#id”, unless = “#result == null”)
@CacheEvict(value = “OptionalUser”, key = “#userDTO.id”) 这样doEvict(cache,key)就可以如期清除掉指定key的缓存。

写在最后,理解Spring Cache的缓存机制对于缓存的应用是有莫大的帮助的,不然都理解不了为什么@CacheEvict会失效。