Java开发 您所在的位置:网站首页 redis的插槽 Java开发

Java开发

#Java开发| 来源: 网络整理| 查看: 265

前言

es我们已经在前文中有所了解,和es有相似功能的是Redis,他们都不是纯粹的数据库。两者使用场景也是存在一定的差异的,本文目的并不重点说明他们之间的差异,但会简要说明,重点还是在对Redis的了解和学习上。学完本篇,你将了解Redis的特点和作用,掌握Redis的基础用法,这将有助于你在后续的项目中更好的使用Redis。建议大家都动手和博主一起实操,莫要养成眼高手低的毛病,下面,让我们提起精神,一起开始这场Redis盛宴吧。

Redis 什么是Redis

Redis全名Remote Dictionary Server,即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

其官网:Redis

虽然Redis也是数据库,但又有别于我们所知的mysql等关系型数据库,Redis是一款基于内存的NoSQL数据存储服务,也就是非关系型的数据库,这点要搞搞清楚。

为什么使用Redis

相信大家在学习SQL的时候都有过这样的经历,往数据库插入50w条数据,然后去条件检索某些数据,效率如何?大家心里多少还是有点x数的,试想,这种暴力型操作要是出现在我们常用的一些网站和应用上,且每次请求到这么做,那该是何等的疯狂?

说到这里,其实还没有说出Redis的主要应用场景,每每想到这个,总是会跳出另一个东西:ES。算了,咱们看下面的对比吧,有对比才有伤害啊!

Redis和ES的区别 Redis使用场景

因为Redis是基于内存运行的,说起内存,你应该知道,其运行效率远高于和硬盘的交互。这也就导致了Redis运行效率非常高。

Redis同样支持将数据存储在硬盘上,支持主从和分布式使用,但在事务上有着严重不足,所以在关系比较复杂的地方就不适合使用Redis,但却可以配合关系型数据库做缓存,也就是通过复杂SQL查找到数据缓存在Redis。

此类缓存数据,我准备用一个绝对一些的词,必须是稳定型,通用型,高频次数据,比如类别,商品信息,资讯信息等。如果每次请求都会变,每个人又都不一样的数据,不太建议存储在Redis,不是不行,只是不建议,因为会占用大量的内存,造成数据的冗余,还不利于做数据同步。

ES使用场景

ES是非关系型数据库,我们通常说他是一个引擎,实时搜索引擎,他是把数据按照一定的规律存储起来,达到比关系型数据库查询效率更高的目的。

ES扩展容易,前文中曾使用了ik插件,ES同样支持主从,由于其存储的数据结构特点,所以其查询效率非常高,在微服务这种大数据形态下的表现尤为优秀,可以快速实现数据的整合,对于日志和数据分析非常友好,对于实时状态的高也并发有着极强的适应能力,且延迟也很低。

想了解ES的童鞋可以点击下面链接前往查看:Java开发 - Elasticsearch初体验

Redis安装

由于博主是Mac电脑,这里就以Mac为例,Windows没试过,Windows的童鞋可自行百度安装。

打开终端,输入:brew install redis测试安装成功输入:redis-server,看到Redis 的启动日志则说明安装成功,通过Ctrl-C可停止此redis使用 launchd 启动Redis:brew services start redis 暂停Redis:brew services stop redis                                                                                                               查看Redis信息:brew services info redis

其实博主觉得这么做挺麻烦的,有个东西叫:Docker Desktop

5d384f54094144a1beea3e1e21a26303.png

直接安装这个,Mac电脑省去了安装虚拟机的麻烦,直接在此软件内安装各种服务,不要太爽:

65d7099a7a4c4472b2148efc02b3ccf2.png

数据库,nacos,senta等都可以在这里安装并且一键启动,推荐大家去装一个,就不用每一个东西都要自己装,太麻烦了。

还有个Redis的客户端也推荐大家装一下 :

27a3894a286c4931b9855eb02c0dafd0.png

此软件在Mac上App Store是收费的,推荐安装方式如下:

brew install --cask another-redis-desktop-manager

安装后就可以找到启动图标了,Windows可在网上自行搜索,其工作页面如下:

cc863d2fa1464e4393925bf7197783a0.png

可看到目前我们是启动状态,客户端连接数是1。

最好弄好了这些东西后再来跟着学下去,这种可视化工具可以在我们调用接口时看到存储在Redis中的数据,非常方便。

Redis缓存 缓存淘汰

Redis帮助我们解决了一些三高的问题,但在访问量非常大的时候,Redis要在同一时间保存大量的数据,但Redis内存并不是无限的,一旦内存占满,可能会发生什么?降速?阻塞?宕机?都有可能,所以在连续不断的存入新数据的同时,还要将不使用的老数据及时的从内存中删除,这就需要一个淘汰策略。

好在Redis提供了这种机制,我们看看有哪些淘汰策略:

noeviction:返回错误**(默认)** allkeys-random:所有数据中随机删除数据 volatile-random:所有过期时间的数据库中随机删除数据 volatile-ttl:删除剩余有效时间最少的数据 allkeys-lru:所有数据中删除上次使用时间最久的数据 volatile-lru:所有过期时间的数据中删除上次使用时间最久的数据 allkeys-lfu:所有数据中删除使用频率最少的 volatile-lfu:所有过期时间的数据中删除使用频率最少的

通过合理的选择以上参数配置Redis,可以有效解决这个问题,但也需要时刻监测Redis内存情况,现在的云服务做得都很好,可以提前预警通知。

缓存穿透

我们在使用Redis时,将数据库中查询出来的数据保存在Redis中,但Redis有自己的淘汰策略,所以这些数据并不会无限期保存。

正常来说,访问的请求会先去Redis中拿数据,拿不到才会去数据库中查找,再将查到的数据存储到Redis中,一旦这样的请求数量非常多的时候,数据库的压力就会变大,我们可以认为,其表现的现象即为Redis失效,没有工作,当然算是失效了,而这种情况,我们称之为缓存穿透。

开发中当然要避免缓存穿透,简单点,可以将查询回来为空的数据在Redis中存为null,防止Redis被反复穿透,但这也有缺点,比如反复更换查询关键字,反复穿透依然存在,当然,这只是特例,虽然实际中发生的概率不会太高,但还是要防范利用此情况攻击服务器的可能。

最好的做法是通过增加布隆过滤器来解决此问题,在业务进入时,提前判断用户查询的信息是否存在于数据库中,如果没有,直接返回,不再走完整的路径。

缓存击穿

缓存穿透和缓存击穿很类似,我们正常的流程是先访问Redis,Redis没有就去数据库查询,这种情况,数据库是可以查到数据的,此种现象就叫击穿,而少量的击穿并不是问题。

缓存雪崩

上面的击穿在同一时间大量发生,就变成了雪崩,数据库短时间内出现很多新的查询请求,就会发生性能问题。

这是由于Redis缓存淘汰策略把过期的数据大批量清空导致的,它本身不算异常,只是我们要避免同一时间大量的过期情况出现,所以在设置过期时间时,在基础时间上增加10分钟或30分钟以内的随机时间来解决这个问题,时间你可以自己定。

Redis持久化 存储特点

Redis是在内存中运行的,这和我们所有的软件都是一样的,内存可以保存,但Redis保存的数据却并不是在内存上,试想我们的电脑手机,关机后再打开还能恢复打开时的样子吗?这自然是不能的。

所以,为了解决断电重启等问题,Redis支持了持久化,将需要保存的数据保存在服务器硬盘上。

针对以上硬盘保存数据的特点,Redis在重新启动后恢复数据的方式有两种,我们来看看是哪两种。

RDB

RDB全称Redis Database Backup,中文名叫数据库快照,它可以将Redis数据库数据转化为二进制数据保存在硬盘上,生成一个dump.rdb的文件,想使用此恢复模式需要提前在Redis安装程序的配置文件中进行配置才能生效。

基于此模式,由于是整体Redis数据的二进制格式,所以数据恢复是整体恢复的,非常方便。但也因此存在了一个大文件的通病:读写效率不高。快照的备份不能实时进行,所以断电重启恢复只能恢复最后一次生成的rdb文件数据。可能会造成短时间的数据丢失。

AOF

AOF全称Append Only File,它的策略不是缓存数据,而是将所有命令日志备份下来,在数据丢失后,可以根据运行过的日志恢复为断电前的状态,注意一点:这种保存日志的策略也不是实时的,数据量比较大时会分批分次进行缓存。

实际中,我们一般设置1s发送一次日志,断电最多丢失1s数据。为了降低日志对内存的占用,AOF支持AOF rewrite,也就是说,如果你是删除数据,那完全没有留日志的必要,但默认时有日志的,所以,可以将这些删除操作的日志删除。

存储原理

存储原理博主简单给大家说说,想要深入了解的推荐这篇博客:Redis存储原理深入剖析 - 墨天轮

也可以自行查找。

Redis将内存划分为16384个槽,类似哈希槽,将要存储的数据的key通过CRC16算法处理,得到一个0~16383之间的值,然后将这条数据存储到对应的槽中,下次查找的时候也是通过CRC16算法处理过的数字去对应槽中查找当前key是否存在,因为有可能直接一次就找到对应key,所以这种存储查找方式效率非常高。这也是一种散列算法,和数据库主键查找的原理很类似。推荐读一下博主这篇博客:Java开发 - 数据库索引的数据结构

Redis集群

Redis我们一般说起来都会说Redis服务器,所以,Redis本质上也是一台服务器,Redis即服务器,服务器即Redis。服务器宕机,Redis肯定也好不到哪里去。如果只有一台Redis服务器,那将会面临一定的风险,比如系统崩溃。

主从

为了解决单Redis服务器可能存在的问题,我们一般会使用一台备用机,这就叫做主从。主从状态下,备用机Redis会实时同步主机Redis的数据,如果主机掉线,备用机就可以起到预备队的效果。但这也存在一定的问题,主机正常工作时,从机就在那里歇着,主机累的喘不过来气,肯定不愿意,从机的钱不是也白花了吗?如下图:

6f1d13be39994e4f929a5049ee28b33a.png

读写分离

为了解决从机不干活的问题,我们一般会将读写分离,主机可读可写,从机也可以读取,这样不仅让从机干活,还减轻了主机的压力,提高了项目运行的流畅度。如下图:

4b4aa3a9b26a442bbd61372918b103b8.png

哨兵

此时,还存在一个问题,主机宕机后,需要人手动切换到从机,要是及时发现还好,要是不及时,将会造成严重的后果。这时候,我们需要一个可以自动切换到从机的机制:哨兵模式。如下图:

62352435b0ff4d21a365aef026d29e2d.png

哨兵每隔固定时间向主从节点发送请求,如果节点正常相应,则说明节点正常工作,否则,将视为节点异常,启将自动切换备用机。

有时候,因为网络问题或者其他因素会导致哨兵接受请求返回异常而切换备用机,这不仅没起到保护的作用,还降低了Redis的工作效率,这时,有两种方式来解决:一是多次请求后都返回异常再切换备用机,二是采用多个哨兵的形式,当多台哨兵都认为某台机器存在异常,再切换到备用机。但是切记一点,哨兵不能和Redis在同一台服务器上,否则服务器异常哨兵也将离线。

 哨兵的配置推荐看看这篇博客:Redis中的哨兵模式 - 简书 

Redis基本使用

下面,我们就来看看Redis有哪些API,具体该怎么使用。在原来微服务项目中,上一篇Quartz是在stock子项目下运行的,Redis我们也在stock子项目下添加吧,你也可以选择其他模块,或者独立建一个项目也是可以的。

添加依赖 org.springframework.boot spring-boot-starter-data-redis

请注意,博主依赖中没有添加version信息是因为主工程中对版本进行了管理,如果你单独创建的项目是需要加上版本的,其他依赖在需要时按需添加。

添加配置 spring: redis: database: 0 host: localhost port: 6379 password: jedis: pool: #最大连接数 max-active: 8 #最大阻塞等待时间(负数表示没限制) max-wait: -1 #最大空闲 max-idle: 8 #最小空闲 min-idle: 0 #连接超时时间 timeout: 10000 cache: redis: time-to-live: 360000000

推荐这篇博客:SpringDataRedis知识概括,可以去了解下Redis配置相关的详细信息,博主觉得写得还是很全面的。

创建配置类

操作Redis需要使用RedisTemplate对象,我们在config包下创建RedisConfiguration类:

package com.codingfire.cloud.stock.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import java.io.Serializable; @Configuration public class RedisConfiguration { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(RedisSerializer.string()); redisTemplate.setValueSerializer(RedisSerializer.json()); return redisTemplate; } }

固定模式,也没啥好说的,只是需要使用这样一个实例来操作Redis。

编写测试方法 

由于我们之前删除了用于测试的文件夹,还是需要新建的,选择src,新建,file:

17cd4b439d3c4f5fa37e2bc6981de910.png

选择test/java,新建测试类:

package com.codingfire.cloud.stock; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class CloudStockApplicationTests { }

 包名,路径和格式注意下,不要错了。下面我们来添加Redis的测试方法。

存储普通字符串:

package com.codingfire.cloud.stock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.junit.jupiter.api.Test; import java.io.Serializable; import java.util.concurrent.TimeUnit; @SpringBootTest class CloudStockApplicationTests { @Autowired RedisTemplate redisTemplate; @Test void testSetValue() { redisTemplate.opsForValue().set("name", "codingfire"); } }

运行测试方法,成功后我们去Redis客户端看看有没有什么变化:

d675f196441349b8b138803efdea806f.png

这个就是我们测试方法中存入的数据,表示我们已经将此字符串存入Redis中。有问题的童鞋看看自己的Redis有没有启动连接。

有存就有取,我们去把刚才存进去的字符串取出来:

@Test void testGetValue() { // 当key存在时,可获取到有效值 // 当key不存在时,获取到的结果将是null Serializable name = redisTemplate.opsForValue().get("name"); System.out.println("get value --> " + name); }

运行测试方法,查看控制台输出:

get value --> codingfire

成功从Redis取到了我们存入的数据。

不知道你注意到没,我们前面的配置类里面对Redis的存取类型做了限制:

283fcda1f26b44bd81547b38130834ae.png

redis存储类型很有限,不过好在string和json几乎能满足所有的需要,下面我们来存取对象类型试试:

@Test void testSetAdminValue() { AdminLoginDTO adminLoginDTO = new AdminLoginDTO(); adminLoginDTO.setUsername("codingfire"); adminLoginDTO.setPassword("123456"); redisTemplate.opsForValue().set("user", adminLoginDTO); } @Test void testGetAdminValue() { // 当key存在时,可获取到有效值 // 当key不存在时,获取到的结果将是null Serializable user = redisTemplate.opsForValue().get("user"); System.out.println("get value --> " + user); if (user != null) { AdminLoginDTO adminLoginDTO = (AdminLoginDTO) user; System.out.println("get value --> " + adminLoginDTO); } }

分别运行以上存取对象类型方法,看看Redis客户端和控制台会输出什么。

存储对象后Redis客户端:

34d181a0133e4684b6e8f946d6b39a02.png

获取对象后控制台输出:

2945ec8802ed42ab91cc2794f180b6a6.png

两次输出一样为什么还要强转呢?不强转你就不知道获取的对象类型,也无法直接使用序列化后对象进行数据的操作。

有添加就有删除,那么删除Redis中数据该怎么做呢:

@Test void testDeleteKey() { // 删除key时,将返回“是否成功删除” // 当key存在时,将返回true // 当key不存在时,将返回false Boolean result = redisTemplate.delete("name"); System.out.println("result --> " + result); }

 运行此测试方法查看结果:

由于Redis中存在名为name的参数,所以result为true,对象类型的删除也是一样的操作。

接下来我们设置Redis的过期时间,时间到了就自动删除,我们通过查看源码得知,set有三个参数,第二个为过期时间,第三个为时间单位:

604e99c5b1b44076b6518f970f503b2f.png

下面我来写代码:

@Test void testSetValueTimeout() { redisTemplate.opsForValue().set("name", "codingfire",20, TimeUnit.SECONDS); }

 运行测试方法,在Redis中能看到存入的数据,20s后,数据自动删除:

31bbd9c6a8004556ab3af3a6a65052ee.png

在客户端中能看到TTL,就是倒计时的意思,打开自动刷新功能,你能看见数字是在变化的。

在存储数据时还有一个特殊情况,比如分页数据,是一个数组,这该怎么存呢?Redis中ops是操作器,opsForValue可以操作普通字符串或对象类型,既然是对象,为什么不能操作数组?数组也是对象啊,博主也不死心,我们来试试看:

f4fe1009bdbc4be2ad037ae7f8eb6e33.png

ea989634494b4c2ab1634cc6ac3fa3e5.png

 好像成功了?为了对比这两种方式存储数组的能力我们来试试ospForList存数组后是否一样:

@Test void testRightPushList() { // 存入List时,需要redisTemplate.opsForList()得到针对List的操作器 // 通过rightPush()可以向Redis中的List追加数据 // 每次调用rightPush()时使用的key必须是同一个,才能把多个数据放到同一个List中 List list = new ArrayList(); for (int i = 1; i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有