redis 的五种数据类型
2、Redis 是一个 key-value 存储系统,它支持存储的 value 类型相对更多,包括 string、list、set、zset(sorted set --有序集合)和 hash。这些数据结构都支持 push/pop、add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis 支持各种不同方式的排序。为了保证效率,数据都是缓存在内存中,Redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了 master-slave(主从)同步。
3、Redis 提供了 java、C/C++、PHP、JavaScript、Perl、Object-C、Python、Ruby、Erlang 等客户端,使用很方便。
4、Reids 支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他服务器的主服务器。这使得 Redis 可执行单层数复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。
5、 在我们日常的 Java Web 开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一使用数据库来保存数据的系统会因为面向磁盘,磁盘读/写速度比较慢的问题而存在严重的性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机的严重生产问题。
[](()
二、NoSQL 技术
为了克服上述问题,java web 项目通常会引入 NoSQL 技术,这是 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 一种基于内存的数据库,并且提供一定的持久化功能。
Redis 和 MongoDB 是当前使用最广泛的 NoSQL,?而就 Redis 技术而言,它的性能十分优越,可以支持每秒十几万的读写操作,其性能远超数据库,并且还支持集群、。分布式、主从同步等配置,原则上可以无限扩展,让更多的数据存储在内存中,更让人欣慰的是它还支持一定的事务能力,这保证了高并发的场景下数据的安全和一致性。
三、Redis 的高并发和快速原因
Redis 是基于内存的,内存的读写速度非常快;
Redis 是单线程的,省去了很多上下文切换线程的时间;
Redis 使用多路复用技术,可以处理并发的连接。非 IO 内部实现采用 epoll,采用了 epoll 自己实现的简单的事件框架。epoll 的读写、关闭、连接都转化为事件,然后利用 epoll 的多路复用特性,绝不在 IO 上浪费一点时间。
四、Redis 为什么是单线程的
1、官方答案
Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络宽带。既然单线程容易实现,而且 CPU 不会成为瓶颈,那么顺理成章的采用单线程的方案。
2、详细原因
(1)不需要各种锁的性能消耗
Redis 的数据结构并不全是 key-value 形式的,还有 list,hash 等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在 hash 中添加或删除一个对象,这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。
总之,在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁和释放锁的操作,没有因为可能出现的死锁而导致的性能消耗。
(2)单线程多进程集群方案
单线程的威力实际上非常强大,每核心效率也非常高,多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。
所以单线程、多进程的集群不失为一个时髦的解决方案。
(3)CPU 消耗
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU。
但是如果 CPU 称为 Redis 的瓶颈,或者不想让服务器其它 CPU 核闲置,那怎么办?
可以考虑多起几个 Redis 进程,Redis 是 key-value 数据库,不是关系型数据库,数据之间没有约束。只要客户端分清哪些 key 放在哪个 Redis 进程中就可以了。
五、单线程的优劣势
1、优势
代码更清晰,处理逻辑更简单
不用考虑各种锁的问题,不存在加锁和释放锁的操作,没有因为可能出现死锁而导致的性能消耗
不存在多线程切换而消耗 CPU
2、劣势
无法发挥多核 CPU 性能优势,不过可以通过单击开多个 Redis 实例来完善。
六、Redis 高并发总结
1、Redis 是纯内存数据库,一般都是简单存取操作,线程占用的时间很多,时间的花费主要集中在 IO 上,所以读取速度快;
2、Redis 使用的是非阻塞 IO,IO 多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成事件,减少了线程切换时上下文切换和竞争。
3、Redis 采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。
4、Redis 全程使用 hash 结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如跳表,使用有序的数据结构加快读写的速度。
5、Redis 采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
七、在 java 中使用 Redis
[](()
1、添加 Jedis 依赖
想要在 Java 中使用 Redis 缓存,需要添加相关的 Jar 包依赖,打开 Maven 仓库的网站:[https://mvnrepository.com/](()?,搜索 Jedis:
把它导入工程中去就可以啦,下面我们来对 Redis 的写入性能做一下测试:
@Test
public void redisTester() {
Jedis jedis = new Jedis("localhost", 6379, 100000);
int i = 0;
try {
long start = System.currentTimeMillis();// 开始毫秒数
while (true) {
long end = System.currentTimeMillis();
if (end - start >= 1000) {// 当大于等于 1000 毫秒(相当于 1 秒)时,结束操作
break;
}
i++;
jedis.set("test" + i, i + "");
}
} finally {// 关闭连接
jedis.close();
}
// 打印 1 秒内对 Redis 的操作次数
System.out.println("redis 每秒操作:" + i + "次");
}
-----------测试结果-----------
redis 每秒操作:10734 次
[](()
2、使用 Redis 连接池
跟数据库连接池相同,Java Redis 也同样提供了类?redis.clients.jedis.JedisPool
来管理我们的 Reids 连接池对象,并且我们可以使用?redis.clients.jedis.JedisPoolConfig
来对连接池进行配置,代码如下:
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 最大空闲数
poolConfig.setMaxIdle(50);
// 最大连接数
poolConfig.setMaxTotal(100);
// 最大等待毫秒数
poolConfig.setMaxWaitMillis(20000);
// 使用配置创建连接池
JedisPool pool = new JedisPool(poolConfig, "localhost");
// 从连接池中获取单个连接
Jedis jedis = pool.getResource();
// 如果需要密码
//jedis.auth("password");
Redis 只能支持六种数据结构?(string/hash/list/set/zset/hyperloglog)的操作?,但在 Java 中我们通常以类对象为主,所以在 Redis 存储的数据结构月 java 对象之间进行转换,如自己编写一些工具类?比如一个角色对象的转换,还是比较容易的,但是涉及到许多对象的时候,这其中无论工作量还是工作难度都是很大的,所以总体来说,?就操作对象而言,使用 Redis 还是挺难的,好在 spring 对这些进行了封装和支持。?
八、Redis 在 Java Web 中的应用
Redis 在 Java Web 主要有两个应用场景:
存储缓存用的数据
需要高速读写的场合
[](()
1、存储缓存用的数据?
在日常对数据库的访问中,读操作的次数远超写操作,比例大概在?1:9?到?3:7,所以需要读的可能性是比写的可能大得多的。当我们使用 SQL 语句去数据库进行读写操作时,数据库就会去磁盘把对应的数据索引取回来,这是一个相对较慢的过程。?
如果放在 Redis 中,也就是放在内存中,让服务器直接读取内存中的数据,那么速度就会快很多,并且会极大减少数据库的压力,但是使用内存进行数据存储开销也是比较大的,限于成本的原因,一般我们只是使用 Redis 存储一些常用的和主要的数据,比如用户登录信息等。
一般而言在使用 Redis 进行存储的时候,我们需要从以下几个方面来考虑:
[](()
(1)业务数据常用吗?使用率如何?
如果使用率较低,就没必要写入缓存。
[](()
(2)该业务是读操作多,还是写操作多?
如果写操作多,频繁需要写入数据库,也没必要使用缓存。
[](()
(3)业务数据大小如何?
如果要存储几百兆字节的文件,会给缓存带来很大的压力,这样也没必要。
在考虑了这些问题之后,如果觉得有必要使用缓存,那么就使用它!使用 Redis 作为缓存的读取逻辑如下图所示:
从上图我们可以知道以下两点:
(1)当第一次读取数据的时候,读取 Redis 的数据就会失败,此时就会触发程序读取数据库,把数据读取出来,并且写入 Redis 中
(2)当第二次以及以后需要读取数据时,就会直接读取 Redis,读取数据后就结束了流程,这样速度大大提高了。
从上面的分析可以知道,读操作的可能性是远大于写操作的,所以使用 Redis 来处理日常中需要经常读取的数据,速度提升是显而易见的,同时也降低了对数据库的依赖,使得数据库的压力大大减少。
分析了读操作的逻辑,下面我们来看看写操作流程:
从流程可以看出,更新或者写入的操作,需要多个 Redis 的操作,如果业务数据写次数远大于读次数那么就没有必要使用 Redis。
[](()
2、高速读写场合
在如今的互联网中,越来越多的存在高并发的情况,比如天猫双 11、抢红包、抢演唱会门票等,这些场合都是在某一个瞬间或者是某一个短暂的时刻有成千上万的请求到达服务器,如果单纯的使用数据库来进行处理,就算不崩,也会很慢的,轻则造成用户体验极差用户量流水,重则数据库瘫痪,服务宕机,而这样的场合都是不允许的!
所以我们需要使用 Redis 来应对这样的高并发需求的场合,我们先来看看一次请求操作的流程:
我们来进一步阐述这个过程:
(1)当一个请求到达服务器时,只是把业务数据在 Redis 上进行读写,而没有对数据库进行任何的操作,这样就能大大提高读写的速度,从而满足高速相应的需求。
(2)但是这些缓存的数据仍然需要持久化,也就是存入数据库之中,所以在一个请求操作完 Redis 的读写之后,会去判断该高速读写的业务是否结束,这个判断通常会在秒杀商品为 0,红包金额为 0 时成立,如果不成立,则不会操作数据库;如果成立,则触发事件将 Redis 的缓存的数据以批量的形式一次性写入数据库,从而完成持久化的工作。
九、在 spring 中使用 Redis
上面说到了 Redis 无法操作对象的问题,无法在那些基础类型和 Java 对象之间方便的转换,但是在 Spring 中,这些问题都可以通过使用 RedisTemplate 得到解决!
想要达到这样的效果,除了 Jedis 包以外还需要在 Spring 引入 spring-data-redis 包。
[](()
1、使用 spring 配置 JedisPoolConfig 对象
大部分的情况下,我们还是会用到连接池的,于是先用 Spring 配置一个 JedisPoolConfig 对象:
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="50"/>
<property name="maxTotal" value="100"/>
<property name="maxWaitMillis" value="20000"/>
</bean>
2、为连接池配置工厂模型
好了,我们现在配置好了连接池的相关属性,那么具体使用哪种工厂实现呢?在 Spring Data Redis 中有四种可供我们选择的工厂模型,它们分别是:
JredisConnectionFactory
JedisConnectionFactory
LettuceConnectionFactory
SrpConnectionFactory
我们这里就简单配置成 JedisConnectionFactory:
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="localhost"/>
<property name="port" value="6379"/>
评论