专栏首页攻城狮的那点事SpringCloud微服务项目实战 - 缓存详解及高效缓存接入

SpringCloud微服务项目实战 - 缓存详解及高效缓存接入

缓存,已经是现在系统中必不可少的内容,如何使用好缓存,对系统的性能和效率至关重要,这里我就来分析一下使用缓存的正确姿势吧。

如今的微服务项目,都是前后端分离,上面就是简单的服务架构图。在整个服务器项目中,有哪些需要我们做缓存呢,这里大致有:客户端缓存、文件缓存及网络加速和后端数据缓存。

缓存技术简介

1,客户端缓存

客户端缓存主要包含如客户使用的PC上的浏览器、手机App、微信小程序等。客户端缓存的原则就是尽量减少或消灭请求,以达到降低服务器压力和提升用户体验的效果。静态文件,例如Js、html、css、图片等内容,它们的很多可以只请求1次,然后缓存在本地,之后就优先从本地缓存中获取,这样就减少了对Web服务器的频繁请求。

如常见的浏览器缓存,可以通过Http的头信息控制缓存是通过Expires(强缓存)、Cache-control(强缓存)、Last-Modified/If-Modified-Since(协商缓存)、Etag/If-None-Match(协商缓存)实现。具体使用方法和区别,这里就不赘叙了哈。

客户端缓存的优缺点:

优点:减少网络传输,加快页面内容展示速度,提升用户体验。

成本:占用客户端的部分内存和磁盘,影响实时性。

2,文件缓及网络加速

除了前面说的前端缓存,针对一些静态资源,我们可以一次加载,实现本地缓存。那如果同时有一万、十万甚至更多的用户初次使用呢,这就使得大量的请求同时请求web服务器,势必给服务器带来很大的压力。基于这种情况,我们又如何来解决应对呢?这就是要说的新的内容--网络加速。目前流行的网络加速解决方案主要是CDN技术,那什么又是CDN呢,我这里做简单介绍。

CDN(Content Delivery Network)是指内容分发网络,也称为内容传送网络,为了能在传统IP网上发布丰富的宽带媒体内容,在现有互联网基础上建立一个内容分发平台专门为网站提供服务。CDN网络是在用户和服务器之间增加了一层缓存层,将用户的请求引导到最优的缓存节点而不是服务器源站,从而加块访问速度。

CDN的一般使用场景:

  1. 加速静态资源; 网站中有大量的html、js、css、图片等文件,可以将这些静态内容推送到CDN。
  2. 大文件的下载;软件下载,视频点播等存储网站。
  3. 部分与用户无关的高频接口加速,在高并发情况下提升接口响应时间。

关于CDN的接入使用,使用云服务器的可以直接通过域名去云服务厂商去买就行,这里不在多说。

3,数据缓存

这是整个整个项目中最重要的,也就是我们所说的服务端缓存。服务端缓存有服务自身的Session,也有第三方的缓存技术。在分布式微服务的环境下,几乎没人使用Session作为服务端缓存,基本都采用了第三方的Nosql缓存技术。

常用的Nosql缓存有:Memcached、Redis、mongoDB。那么我们到底选用那个呢,先来看看他们的优缺点。

1)Memcached

优点:可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS(取决于key、value的字节大小以及服务器硬件性能,日常环境中QPS高峰大约在4-6w左右)。适用于最大程度扛量。

缺点:只支持简单的key/value数据结构,不像Redis可以支持丰富的数据类型。无法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据全部丢失。无法进行数据同步,不能将MC中的数据迁移到其他MC实例中。

2)Redis

优点:支持多种数据结构,支持持久化操作,可以进行aof及rdb数据持久化到磁盘,从而进行数据备份或数据恢复等操作,较好的防止数据丢失的手段。支持通过Replication进行数据复制,通过master-slave机制,可以实时进行数据同步复制,支持多级复制和增量复制,master-slave机制是Redis进行HA的重要手段。

缺点:只能使用单线程,性能受限于CPU性能,故单实例CPU最高才可能达到5-6wQPS每秒。支持简单的事务需求,但业界使用场景很少,并不成熟,既是优点也是缺点。Redis在string类型上会消耗较多内存,可以使用dict(hash表)压缩存储以降低内存耗用。MC和Redis都是Key-Value类型,不适合在不同数据集之间建立关系,也不适合进行查询搜索。

3)mongoDB

mongoDB 是一种文档性的数据库。先解释一下文档的数据库,即可以存放xml、json、bson类型系那个的数据。这些数据具备自述性(self-describing),呈现分层的树状数据结构。

mongoDB 存放json格式的数据。适合场景如:事件记录、内容管理或者博客平台,比如评论系统。

mongodb与MC和Redis不同的是,它可以使用语句进行CRUD操作,处理和获取文档数据。操作语句如下:

  • 查询:db.col.find().pretty();
  • 添加:db.col.insert({name:'Lily',sex:'female',age:'18'});
  • 删除:db.col.remove(...);
  • 更新:db.col.update(...);

mongodb与mysql不同,mysql的每一次更新操作都会直接写入硬盘,但是mongo不会,做为内存型数据库,数据操作会先写入内存,然后再会持久化到硬盘中去。

服务端缓存选取接入

经过上面的介绍说明,考虑到服务端接口数据类型已经持久化等,Redis似乎是我们的首选,当然你也可以选择Memcached,这个完全取决与自己的项目。这里我介绍下Redis在Springboot+SpringCloud微服务项目的接入。

1,首先在pom.xml里引入Redis依赖

<!-- Redis version: 2.1.5.RELEASE -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>${springboot-redis.version}</version>
</dependency>

2,配置Redis连接属性(application.yml)

spring:
  redis:
    # Redis库索引(默认为0)
    database: 0
    # Redis服务器地址
    host: 192.168.1.1
    # Redis服务端口
    port: 6379
    # Redis连接密码(默认为空)
    password: 
    lettuce:
      pool:
        # 连接池最大连接数(默认为8)
        max-active: 8
        # 连接池最大空闲连接(默认为8)
        max-idle: 8
        # 连接池最小空闲连接(默认为0)
        min-idle: 0
        # 连接池最大阻塞等待时间
        max-wait: -1ms

3,创建RedisConfig

@Configuration
public class RedisConfig 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];
  @Autowired
  RedisConnectionFactory redisConnectionFactory;

  @Bean
  public RedisTemplate<String, Object> functionDomainRedisTemplate() {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
    return redisTemplate;
  }
  /**
   * 设置数据存入 redis 的序列化方式
   * @param redisTemplate
   * @param factory
   */
  private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
    redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
    redisTemplate.setConnectionFactory(factory);
  }
  @Bean
  public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
    return redisTemplate.opsForHash();
  }
  @Bean
  public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
    return redisTemplate.opsForValue();
  }
  @Bean
  public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
    return redisTemplate.opsForList();
  }
  @Bean
  public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
    return redisTemplate.opsForSet();
  }
  @Bean
  public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
    return redisTemplate.opsForZSet();
  }
  @Override
  public Object deserialize(byte[] bytes) {
    if (bytes == null || bytes.length == 0) {
      return null;
    }
    try {
      return deserializer.convert(bytes);
    } catch (Exception ex) {
      throw new SerializationException("Cannot deserialize", ex);
    }
  }
  @Override
  public byte[] serialize(Object object) {
    if (object == null) {
      return EMPTY_ARRAY;
    }
    try {
      return serializer.convert(object);
    } catch (Exception ex) {
      return EMPTY_ARRAY;
    }
  }
}

4,缓存操作接口及实现

public interface CacheService {

  boolean hasKey(String key);

  boolean delete(String key);

  Object get(String key);

  void set(String key, Object value);

  void set(String key, Object value, long timeout);
}
@Component
public class CacheServiceImpl implements CacheService {

  @Autowired
  private ValueOperations<String, Object> valueOperation;
  @Autowired
  private RedisTemplate<String, Object> redisTemplate;

  @Override
  public boolean hasKey(String key) {
    return redisTemplate.hasKey(key);
  }

  @Override
  public boolean delete(String keys) {
    return redisTemplate.delete(keys);
  }

  @Override
  public void set(String key, Object value) {
    valueOperation.set(key, value);
  }

  @Override
  public Object get(String key) {
    return valueOperation.get(key);
  }

  @Override
  public void set(String key, Object value, long timeout) {
    valueOperation.set(key,value,timeout,TimeUnit.SECONDS);
  }
}

5,编写测试实战

@RestController
@RequestMapping(value = "/customer/")
public class CustomerController {

    @Autowired
    private CustomerService customerService;

    @Autowired
    private CacheService cacheService;

    @GetMapping("/id")
    public Object findCustomerById(Integer customerId) {
        Object obj = null;
        if ((obj = cacheService.getCacheByKey(CustomerConstant.CUSTOMER_CACHE_PREFIX+customerId)) != null) {
            return  ResultData.ok((CustomerInfo) obj);
        } else {
            // 从数据库获取
            CustomerInfo customerInfo = customerService.findById(customerId);
            if(customerInfo != null) {
               cacheService.setCacheToRedis(CustomerConstant.CUSTOMER_CACHE_PREFIX+customerId,customerInfo,3600);
            }
            return ResultData.ok(customerInfo);
        }
    }
}

今天就说到这里,下一篇继续说说缓存使用中的注意事项,以及面试时经常问到的缓存使用的相关问题。

推荐阅读:

SpringCloud微服务项目实战 - 限流、熔断、降级处理

SpringCloud微服务项目实战 - API网关Gateway详解实现

SpringCloud微服务项目实战 - 网关zuul详解及搭建

SpringCloud微服务项目实战 - 微服务调用详解(附面试题)

SpringCloud微服务项目实战,服务注册与发现(附面试题)

本文分享自微信公众号 - 攻城狮的那点事(gh_e40249fc5212),作者:林同学

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java中大量if...else语句的消除替代方案

    在我们平时的开发过程中,经常可能会出现大量If else的场景,代码显的很臃肿,非常不优雅。那我们又没有办法处理呢?

    攻城狮的那点事
  • Java里有哪些锁?Synchronized如何实现同步锁?

    说到Java中的锁,大伙们到底知道多少呢?这可是面试中常问的话题哦。在说Java中有哪些锁之前,首先咱们说说Java锁是什么,他解决了什么问题?

    攻城狮的那点事
  • Spring Boot实现分布式微服务开发实战系列(四)

    上一篇主要讲了整个项目的子模块及第三方依赖的版本号统一管理维护,数据库对接及缓存(Redis)接入,今天我来说说过滤器配置及拦截设置、接口安全处理、AOP切面实...

    攻城狮的那点事
  • Mybatis框架和插件将动态代理玩出了新境界

    又是一年毕业季,很多小伙伴开始去大城市打拼。来大城市第一件事就是租房,免不了和中介打交道,因为很多房东很忙,你根本找不到他。从这个场景中就可以抽象出来代理模式

    Java识堂
  • 面试官:mybatis你只写了接口为啥就能执行sql啊?

    又是一年毕业季,很多小伙伴开始去大城市打拼。来大城市第一件事就是租房,免不了和中介打交道,因为很多房东很忙,你根本找不到他。从这个场景中就可以抽象出来代理模式

    纯洁的微笑
  • 【asp.net core 系列】13 Identity 身份验证入门

    通过前两篇我们实现了如何在Service层如何访问数据,以及如何运用简单的加密算法对数据加密。这一篇我们将探索如何实现asp.net core的身份验证。

    程序员小高
  • Android使用Service实现IPC通信的2种方式

    上面的代码都是在当前进程内跟Service通信,现在我们来实现一下,不同进程内Service如何绑定。

    砸漏
  • springboot中的@ConfigurationProperties注解的使用

    将配置文件中的配置,以属性的形式自动注入到 实体中。 要特别说明的一个注属性 ignoreUnknownFields = false 这个超好用,自动检查配置文...

    潇洒
  • 学习ASP.NET Core, 怎能不了解请求处理管道[6]: 管道是如何随着WebHost的开启被构建出来的?

    注册的服务器和中间件共同构成了ASP.NET Core用于处理请求的管道, 这样一个管道是在我们启动作为应用宿主的WebHost时构建出来的。要深刻了解这个管道...

    蒋金楠
  • C#开发BIMFACE系列34 服务端API之模型对比5:获取模型构件对比差异

      BIMFACE平台提供了服务端“获取修改构件属性差异”API,其返回的结果也是一个列表,仅针对修改的构件(不包含新增、删除的构件),是指对于一个修改过的构件...

    张传宁老师

扫码关注云+社区

领取腾讯云代金券