开箱即用,一键集成 Redis 缓存

Spring Boot 作为主流微服务框架,拥有成熟的社区生态。市场应用广泛,为了方便大家,整理了一个基于spring boot的常用中间件快速集成入门系列手册,涉及RPC、缓存、消息队列、分库分表、注册中心、分布式配置等常用开源组件,大概有几十篇文章,陆续会开放出来,感兴趣同学可以关注&收藏

简介

Redis 是一个开源、高级的键值存储和一个适用的解决方案,用于构建高性能,可扩展的 Web 应用程序。支持更丰富的数据结构,例如 String、List、hash、 set、 zset 等,同时支持数据持久化。

除此之外,Redis 还提供一些类数据库的特性,比如事务,HA,主从备份。可以说 Redis 兼具了缓存系统和数据库的一些特性。

Redis特性

高并发读写

持久化

丰富的数据类型

单进程单线程模型

数据自动过期

发布订阅

分布式

支持lua脚本

目前在从阿里巴巴、美团、百度、拼多多、快手等一线大厂到五六线小厂中广泛使用,对系统的高并发能力贡献极大,深受好评,开源社区非常活跃。

数据类型

1、String

二进制的字符串,最简单的k-v存储,类似于memcached的存储结构,它不仅能够存储字符串、还能存储图片、视频等多种类型, 最大长度512M。支持丰富的操作命令,如:

GET/MGET

SET/SETEX/MSET/MSETNX

INCR/DECR

GETSET

DEL

2、Hash

采用主子key存储信息,由field和关联的value组成Map。比如计数器,key表示帖子id,field表示点赞数、评论数、转发数等,value则表示计数值。常用命令:

HGET/HMGET/HGETALL

HSET/HMSET/HSETNX

HEXISTS/HLEN

HKEYS/HDEL

HVALS

3、List

该类型是一个有序的元素集合,基于双向链表实现。比较适合存储一些有序且数据相对固定的数据。如省市区表、字典表等。常用命令:

LPUSH/LPUSHX/LPOP/RPUSH/RPUSHX/RPOP/LINSERT/LSET

LINDEX/LRANGE

LLEN/LTRIM

4、Set

Set类型是一种无顺序集合。它和List类型最大的区别是:集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。底层是通过哈希表实现的。常用命令:

SADD/SPOP/SMOVE/SCARD

SINTER/SDIFF/SDIFFSTORE/SUNION

5、Sorted Set

是set的增强版本,有序集合类型,每个元素都会关联一个double类型的分数权值,通过这个权值来为集合中的成员进行从小到大的排序。与Set类型一样,其底层也是通过哈希表实现的。常用命令:

ZADD/ZPOP/ZMOVE/ZCARD/ZCOUNT

ZINTER/ZDIFF/ZDIFFSTORE/ZUNION

适用场景

1、高性能缓存。缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以Redis用在缓存的场合非常多。

作为缓存使用时,一般有两种方式保存数据:

读取前,先去读Redis。如果没有数据,读取数据库,然后将数据预热到Redis。

写入时,先更新数据库,然后再写入Redis。

2、丰富的数据类型,满足多样化业务需求。

3、分布式锁

在很多互联网公司中都使用了分布式技术,分布式技术带来的挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的set nx功能来编写分布式锁,如果设置返回1说明获取锁成功,否则获取锁失败。

4、消息队列

Redis 中 list 的数据结构实现是双向链表,所以可以非常便捷的应用于消息队列(生产者 / 消费者模型)。消息的生产者只需要通过 lpush 将消息放入 list,消费者便可以通过 rpop 取出该消息,并且可以保证消息的有序性。如果需要实现带有优先级的消息队列也可以选择 sorted set。而 pub/sub 功能也可以用作发布者 / 订阅者模型的消息。无论使用何种方式,由于 Redis 拥有持久化功能,也不需要担心由于服务器故障导致消息丢失的情况。

 整理了一份大厂常考面试题,这份pdf包括 Java基础、Java并发、JVM、MySQL、Redis、Spring、MyBatis、Kafka、设计模式等面试题,分享给大家。下载地址:百度云链接:https://pan.baidu.com/s/1XHT4ppXTp430MEMW2D0-Bg 提取码: s3ab

5、其他场景,如:秒杀、限流、计数器、排行榜、实时系统、共享session等

单线程的Redis为什么快

纯内存操作

单线程操作,避免了频繁的上下文切换

合理高效的数据结构

采用了非阻塞I/O多路复用机制(有一个文件描述符同时监听多个文件描述符是否有数据到来)

如何实现键值对的快速访问

Redis 使用了一个哈希表来保存所有键值对。一个哈希表,其实就是一个数组,数组的每个元素称为一个哈希桶。每个哈希桶中保存了键值对数据。

当然哈希桶中的元素保存的并不是值本身,而是指向具体值的指针。这也就是说,不管值是 String,还是集合类型,哈希桶中的元素都是指向它们的指针。

哈希表的最大优势就是让我们可以用 O(1) 的时间复杂度来快速查找到键值对。我们只需要计算key的哈希值,就可以知道它所对应的哈希桶位置,然后就可以访问相应的 entry 元素。

hash值并不是唯一的,当面对海量数据存储,计算时可能会存在哈希冲突,导致两个entry落在同一个哈希桶中。解决方式也比较简单,引入链式哈希。同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接。

项目实战

在pom.xml 中引入Spring Boot 官方提供的 starter组件

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

spring-boot-starter-data-redis 依赖于 spring-data-redislettuce

Spring Boot 1.X 版本默认使用的是 Jedis 客户端。2.X 版本替换成 Lettuce  客户端,如果习惯使用 Jedis 的话,可以从 spring-boot-starter-data-redis 中排除 Lettuce 并引入 Jedis。

Lettuce 是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀 netty NIO 框架来高效地管理多个连接。

application.yaml 配置redis的地址信息以及lettuce 连接池参数

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    #    password: abEvH46*YsH&S25d89
    lettuce:
      pool:
        maxIdle: 1000  # 连接池中的最大空闲连接
        minIdle: 2  # 连接池中的最小空闲连接
        maxWait: 10  # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        maxActive: 1000 # 连接池最大连接数(使用负值表示没有限制)

初始化 redis的 RedisTemplate 模板bean实例

@Configuration
public class RedisConfig {

    @Bean
    RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        final StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericToStringSerializer<>(Object.class));
        template.setValueSerializer(new GenericToStringSerializer<>(Object.class));
        template.afterPropertiesSet();
        return template;
    }
}

RedisTemplate 提供了多种类型的数据类型操作接口,满足多场景的业务需求。

接下来就可以通过单元测试来验证缓存效果了

@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartApplication.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class UserMapperTest {

    @Resource
    private CacheService cacheService;

    @Test
    public void test1_set1() {
        boolean result = cacheService.set("k1", "微观技术");
        System.out.println(result);
    }

    @Test
    public void test2_get1() {
        String cacheResult = cacheService.get("k1");
        System.out.println("k1 的缓存结果:" + cacheResult);
    }
}

上面讲的都是通过手动方式写入、删除、查询缓存,缓存的处理逻辑散落在业务代码中。有没有更简单的方式?比如调用一个方法,通过方法上标注的注解自动从缓存中获取,如果查找不到再从数据库查,并自动将结果预热到缓存中。

Spring 注解式缓存

首先通过 RedisCacheConfiguration 生成默认配置,然后对缓存进行自定义化配置,比如过期时间、缓存前缀、key/value 序列化方法等,然后构建出一个RedisCacheManager,其中通过keySerializationPair 方法为 key 配置序列化,valueSerializationPair方法为 value 配置序列化。

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .prefixKeysWith("cache:user:")
            .disableCachingNullValues()
            .serializeKeysWith(keySerializationPair())
            .serializeValuesWith(valueSerializationPair());

    Map map = new HashMap<String, RedisCacheConfiguration>();
    map.put("user", redisCacheConfiguration);

    return RedisCacheManager.builder(factory)
            .withInitialCacheConfigurations(map).build();
}

private RedisSerializationContext.SerializationPair<String> keySerializationPair() {
    return RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
}

private RedisSerializationContext.SerializationPair<Object> valueSerializationPair() {
    return RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
}

修改、删除、查询等常见操作,官方都提供了对应的注解类,只需要在对应的方法上标注即可享受缓存功能,对研发同学及其便利,可以将精力专注到其它业务逻辑处理上。

@Component
@CacheConfig(cacheNames = "user")
public class UserService {

    @Cacheable(key = "#id")
    public User getUserById(Long id) {
        User user = User.builder().id(id).userName("雪糕( " + id + ")").age(18).address("杭州").build();
        return user;
    }

    @CachePut(key = "#user.id")
    public User updateUser(User user) {
        user.setUserName("雪糕(新名称)");
        return user;
    }

    @CacheEvict(key = "#id")
    public void deleteById(Long id) {
        System.out.println("db删除数据:" + id);
    }
}

常用注解类

1、 @CacheConfig 类级别的缓存注解,允许共享缓存名称

2、 @Cacheable 一般用于查询操作,根据 key 查询缓存

如果 key 存在,直接返回缓存中的数据。

如果 key 不存在,查询 db,并将结果更新到缓存中。

3、 @CachePut 一般用于更新和插入操作,每次都会请求 db,然后通过 key 对 Redis 进行写操作。

如果 key 存在,更新缓存

如果 key 不存在,插入缓存

4、 @CacheEvict 触发移除缓存

根据 key 删除缓存中的数据。

项目源码地址

https://github.com/aalansehaiyang/spring-boot-bulking  

模块:spring-boot-bulking-redis


关于我:

Tom哥计算机研究生,校招进阿里,期间还拿了百度、华为、中兴、腾讯 等6家大厂offer,P7 技术专家。出过专利CSDN博客专家

多年的大厂浸染,参加多次淘宝双11大促活动,在系统架构方面有丰富经验,沉淀总结在微信公众号:微观技术

他专注于微服务、高并发、高性能缓存、分布式架构、高可用、团队管理等方面,喜欢挖掘开源框架亮点设计,内容都是面试官喜欢考察的强烈推荐关注一波。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容