Redis入门及应用
一、什么是Redis
1、Redis概述
在我们日常的Java Web开发中,都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题。可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一使用数据库来保存数据的系统会因为面向磁盘,磁盘读/写速度比较慢的问题而存在严重的性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机的严重生产问题。
2、NoSQL技术
为了克服上述的问题,Java Web项目通常会引入NoSQL技术,这是一种基于内存的数据库,并且提供一定的持久化功能。Redis和MongoDB是当前使用最广泛的NoSQL,而就Redis技术而言,它的性能十分优越,可以支持每秒十几万此的读/写操作,其性能远超数据库,并且还支持集群、分布式、主从同步等配置,原则上可以无限扩展,让更多的数据存储在内存中,更让人欣慰的是它还支持一定的事务能力,这保证了高并发的场景下数据的安全和一致性。
3、Redis功能
缓存,毫无疑问这是Redis当今最为人熟知的使用场景,再提升服务器性能方面非常有效。
a)排行榜,如果使用传统的关系型数据库来做,非常麻烦,而利用Redis的SortSet数据结构能够非常方便搞定;
b)计算器/限速器,利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如果用MySQL,频繁的读写会带来相当大的压力;限速器比较典型的使用场景是限制某个用户访问某个API的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力;
c)好友关系,利用集合的一些命令,比如求交集、并集、差集等,可以方便搞定一些共同好友、共同爱好之类的功能;
d)简单消息队列,除了Redis自身的发布/订阅模式,我们也可以利用List来实现一个队列机制,比如到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的DB压力,完全可以用List来完成异步解耦;
e)Session共享,以PHP为例,默认Session是保存在服务器的文件中,如果是集群服务,同一个用户过来可能落在不同机器上,这就会导致用户频繁登陆;采用Redis保存Session后,无论用户落在那台机器上都能够获取到对应的Session信息。
4、Redis不能干什么事儿
Redis感觉能干的事情特别多,但它不是万能的,合适的地方用它事半功倍,如果滥用可能导致系统的不稳定、成本增高等问题。
1、用Redis去保存用户的基本信息,虽然它能够支持持久化,但是它的持久化方案并不能保证数据绝对的落地,并且还可能带来Redis性能下降,因为持久化太过频繁会增大Redis服务的压力。
2、简单总结就是数据量太大、数据访问频率非常低的业务都不适合使用Redis,数据太大会增加成本,访问频率太低,保存在内存中纯属浪费资源。
5、Redis的优点
1、速度快,完全基于内存,使用C语言实现,网络层使用epoll解决高并发问题,单线程模型避免了不必要的上下文切换及竞争条件;
注意:单线程仅仅是说在网络请求这一模块上用一个请求处理客户端的请求,像持久化它就会重开一个线程/进程去进行处理。
2、丰富的数据类型,Redis有8种数据类型,当然常用的主要是 String、Hash、List、Set、 SortSet 这5种类型,他们都是基于键值的方式组织数据。每一种数据类型提供了非常丰富的操作命令,可以满足绝大部分需求,如果有特殊需求还能自己通过 lua 脚本自己创建新的命令(具备原子性);
3、除了提供的丰富的数据类型,Redis还提供了像慢查询分析、性能测试、Pipeline、事务、Lua自定义命令、Bitmaps、HyperLogLog、发布/订阅、Geo等个性化功能。
二、Redis安装
1、windows安装
访问地址:https://github.com/ServiceStack/redis-windows/tree/master/downloads
下载后解压,可以得到如下目录
为了方便启动,我们在该目录下新建一个 startup.cmd 的文件,然后将以下内容写入文件:
redis-server redis.windows.conf
这个命令其实就是在调用 redis-server.exe 命令来读取 redis.window.conf 的内容。
我们双击刚才创建好的 startup.cmd 文件,就能成功的看到 Redis 启动:
上图的提示信息告诉了我们:Redis 当前的版本为 3.0.503;64位;运行在 6379 端口;进程的 PID 为 14748。
打开同一个文件夹下的 redis-cli.exe 文件,这是 Redis 自带的一个客户端工具,它可以用来连接到我们当前的 Redis 服务器,我们做以下测试:
至此,我们便在 Windows 的环境下安装好了 Redis。
2、MAC安装
访问地址:https://redis.io 下载最新版。
下载后解压到磁盘任意路径,切换到解压后的文件夹,如:/Users/David/soft/redis-5.0.5。运行安装命令:
sudo make install
安装后进入src文件夹,如:/Users/David/soft/redis-5.0.5/src,运行命令:
./redis-server
Redis启动成功,运行该目录下的redis-cli可以操作查看Redils内容。
3、linux安装
推荐进入到linux路径/usr/local/src
$ wget http://download.redis.io/releases/redis-4.0.10.tar.gz $ tar xzf redis-4.0.10.tar.gz $ cd redis-4.0.10/ $ make
redis安装完毕,我们可以利用以下两个命令测试:
#二进制文件是编译完成后在src目录下,通过下面的命令启动Redis服务 $ src/redis-server #另开窗口启动客户端 $ src/redis-cli
#存值取值操作 redis> set foo bar OK redis> get foo "bar"
关闭服务,在客户端使用shutdown命令关闭
redis> shutdown
或者使用kill杀进程
pkill -9 redis-server
三、Redis远程连接
1、连接方式
远程连接可以使用命令:
$ redis-cli -h 192.168.153.131 -p 6379
也可以使用工具Redis Desktop Manager。该工具是一个远程可视化Redis管理工具。
2、服务器配置
默认安装的Redis无法直接实现连接,需要配置。打开安装路径的redis.conf配置文件。
1、是否以守护进程的方式启动;daemonize yes 将原来的no改成yes(可选)
2、绑定的主机地址;将默认的 bind 127.0.0.1 注释掉:#bind 127.0.0.1 或者改为客户端IP(必选)
3、安全模式运行:protected-mode no 这里讲原来的yes改为no(必选)
修改后重启Redis,可以使用常识远程连接。
./redis-server /usr/local/redis-4.0.10/redis.conf
注意:Redis默认启动服务的时候不读取配置文件,当我们修改完配置文件后需要使用上面这种指定配置启动的方式启动服务。
四、Redis基础操作
1、单独使用Redis
想要在 Java 中使用 Redis 缓存,需要添加相关的Jar包依赖。
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.3.0</version> </dependency>
把它导入工程中去就可以啦,下面我们来对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次
跟数据库连接池相同,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();
Redis 只能支持六种数据类型(string/hash/list/set/zset/hyperloglog)的操作,但在 Java 中我们却通常以类对象为主,所以在需要 Redis 存储的五中数据类型与 Java 对象之间进行转换,如果自己编写一些工具类,比如一个角色对象的转换,还是比较容易的,但是涉及到许多对象的时候,这其中无论工作量还是工作难度都是很大的,所以总体来说,就操作对象而言,使用 Redis 还是挺难的,好在 Spring 对这些进行了封装和支持。
2、spring整合使用Redis(可以跳过直接看SpringBoot整合Redis)
上面说到了 Redis 无法操作对象的问题,无法在那些基础类型和 Java 对象之间方便的转换,但是在 Spring 中,这些问题都可以通过使用RedisTemplate得到解决!
想要达到这样的效果,除了Jedis包以外还需要在 Spring 引入 spring-data-redis 包:
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.3.3.RELEASE</version> </dependency>
a)使用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>
b)为连接池配置工厂模型
好了,我们现在配置好了连接池的相关属性,那么具体使用哪种工厂实现呢?在Spring Data Redis中有四种可供我们选择的工厂模型,它们分别是:
- JredisConnectionFactory
- JedisConnectionFactory
- LettuceConnectionFactory
- SrpConnectionFactor
我们这里就简单配置成JedisConnectionFactory:
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<!--Redis服务地址-->
<property name="hostName" value="localhost"/>
<!--端口号-->
<property name="port" value="6379"/>
<!--如果有密码则需要配置密码-->
<!--<property name="password" value="password"/>-->
<!--连接池配置-->
<property name="poolConfig" ref="poolConfig"/>
</bean>
c)配置RedisTemplate
普通的连接根本没有办法直接将对象直接存入 Redis 内存中,我们需要替代的方案:将对象序列化(可以简单的理解为继承Serializable接口)。我们可以把对象序列化之后存入Redis缓存中,然后在取出的时候又通过转换器,将序列化之后的对象反序列化回对象,这样就完成了我们的要求:
RedisTemplate可以帮助我们完成这份工作,它会找到对应的序列化器去转换Redis的键值,我们只需要保证实体类继承序列化接口即可。
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="connectionFactory"/>
d)编写测试
编写POJO类,注意序列化并且保证具有不带参的构造方法。
public class Student implements Serializable{ private String name; private int age; //省略getter和setter方法 }
编写测试类
@Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); RedisTemplate redisTemplate = context.getBean(RedisTemplate.class); Student student = new Student(); student.setName("david"); student.setAge(21); redisTemplate.opsForValue().set("student", student); Student student1 = (Student) redisTemplate.opsForValue().get("student"); System.out.println(student1); }
运行结果略。
Redis操作List
// list数据类型适合于消息队列的场景:比如12306并发量太高,而同一时间段内只能处理指定数量的数据!必须满足先进先出的原则,其余数据处于等待 @Test public void listPushResitTest(){ // leftPush依次由右边添加 stringRedisTemplate.opsForList().rightPush("myList","1"); stringRedisTemplate.opsForList().rightPush("myList","2"); stringRedisTemplate.opsForList().rightPush("myList", "A"); stringRedisTemplate.opsForList().rightPush("myList", "B"); // leftPush依次由左边添加 stringRedisTemplate.opsForList().leftPush("myList", "0"); } @Test public void listGetListResitTest(){ // 查询类别所有元素 List<String> listAll = stringRedisTemplate.opsForList().range( "myList", 0, -1); logger.info("list all {}", listAll); // 查询前3个元素 List<String> list = stringRedisTemplate.opsForList().range( "myList", 0, 3); logger.info("list limit {}", list); } @Test public void listRemoveOneResitTest(){ // 删除先进入的B元素 stringRedisTemplate.opsForList().remove("myList",1, "B"); } @Test public void listRemoveAllResitTest(){ // 删除所有A元素 stringRedisTemplate.opsForList().remove("myList",0, "A"); }
参考大神的博客:
https://blog.csdn.net/qq_42894896/article/details/84260908
https://www.jianshu.com/p/56999f2b8e3b