Redis
一、什么是Redis?
Redis是一个开源(BSD许可)的内存数据结构存储,用作数据库、缓存和消息代理。它支持诸如字符串、散列、列表、集、带范围查询的排序集、位图、hyperloglogs、带半径查询和流的地理空间索引等数据结构。Redis具有内置的复制、Lua脚本、LRU清除、事务和不同级别的磁盘持久性,并通过Redis Sentinel和带有Redis Cluste的automatic分区提供高可用性。
什么是Lua脚本?
Lua 脚本功能是 Reids 2.6 版本的最大亮点, 通过内嵌对 Lua 环境的支持, Redis 解决了长久以来不能高效地处理 CAS (check-and-set)命令的缺点, 并且可以通过组合使用多个命令, 轻松实现以前很难实现或者不能高效实现的模式。
参考文档:https://redisbook.readthedocs.io/en/latest/feature/scripting.html
什么是LRU清除?
redis默认情况下就是使用LRU策略的,因为内存是有限的,但是如果你不断地往redis里面写入数据,那肯定是没法存放下所有的数据在内存的;所以redis默认情况下,当内存中写入的数据很满之后,就会使用LRU算法清理掉部分内存中的数据,腾出一些空间来,然后让新的数据写入redis缓存中
参考博客:https://blog.youkuaiyun.com/kang123488/article/details/79512556
Redis官网地址:https://redis.io/
二、Redis的适用场合
1、Redis能做什么?
- 缓存
- 毫无疑问这是Redis当今最为人熟知的使用场景。再提升服务器性能方面非常有效
- 排行榜
- 如果使用传统的关系型数据库来做,非常麻烦,而利用Redis的SortSet数据结构能够非常方便搞定
- 计算器/限速器
- 利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如果用MySQL,频繁的读写会带来相当大的压力;限速器比较典型的使用场景是限制某个用户访问某个API的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力
- 好友关系
- 利用集合的一些命令,比如求交集、并集、差集等。可以方便搞定一些共同好友、共同爱好之类的功能
- 简单消息队列
- 除了Redis自身的发布/订阅模式,我们也可以利用List来实现一个队列机制,比如:到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的DB压力,完全可以用List来完成异步解耦
- Session共享
- 以PHP为例,默认Session是保存在服务器的文件中,如果是集群服务,同一个用户过来可能落在不同机器上,这就会导致用户频繁登陆;采用Redis保存Session后,无论用户落在那台机器上都能够获取到对应的Session信息
2、Redis不能做什么?
用Redis去保存用户的基本信息,虽然它能够支持持久化,但是它的持久化方案并不能保证数据绝对的落地,并且还可能带来Redis性能下降,因为持久化太过频繁会增大Redis服务的压力;所以说数据太大会增加成本,访问频率太低,保存在内存中纯属浪费资源。
三、Redis的优点
-
速度快
- C语言实现,距离系统更近
- 数据都存在内存中
- 单线程,避免线程切换开销以及多线程的竞争问题
- 注意:单线程仅仅是说在网络请求这一模块上用一个线程处理客户端的请求,像持久化它就会重开一个线程/进程去进行处理
- 采用epoll,非阻塞I/O,不在网络上浪费时间
- epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率–摘抄百度百科
-
支持多种数据类型
- String(字符串)
- Hash(哈希)
- List(列表)
- Set(集合)
- ZSet(有序集合)
- Bitmaps(位图)
- HyperLogLog(做基数统计的算法)
- CEO(地理信息定位)
-
功能丰富
- 可设置键过期
- 基于发布订阅可实现简单的消息队列
- 通过Lua创建新命令,具有原子性
- Pipeline功能,减少网络开销
-
服务器简单
- 代码优雅,下功夫都能吃透
- 采用单线程模型,减少竞争,规避并发问题
- 不依赖操作系统类库(Redis自己实现多路复用)
-
客户端语言多
- PHP/Java/Go/C++/Ruby/Python…等等
-
支持持久化
-
RDB
-
AOF
-
两种持久化机制了解文档:
https://www.cnblogs.com/shizhengwen/p/9283973.html
-
-
主从复制,高可用/分布式
四、安装Redis
1、下载地址
http://redis.io/ 注:本文示范的redis版本是5.0.3
2、上传至服务器
3、解压
tar zxvf redis-5.0.3.tar.gz
4、安装依赖
yum-y install gcc-c++ autoconf automake
5、预编译
①切换到解压目录 -> cd redis-5.0.3/
②输入make命令
6、安装
①创建安装目录
mkdir -p /usr/local/redis
注意:
不使用:make install(make install默认安装到/usr/local/bin目录下)
使用:如果需要指定安装路径,需要添加PREFIX参数
[root@localhost redis-5.0.3]# make PREFIX=/usr/local/redis/ install
②安装成功如图:
Redis-cli:客户端
Redis-server:服务器端
7、启动
安装的默认目标路径:/usr/local/redis/bin
启动:./redis-server
注意:默认为前台启动,若修改为后台启动,需复制redis.conf至安装路径下,修改安装路径下的redis.conf,将daemonize修改为yes
命令如下:
[root@localhost redis-5.0.3]# cp redis.conf /usr/local/redis/bin/
启动时,指定配置文件路径即可
8、通过windows客户端访问
首先需要安装Redis客户端(网上可下载,也可私聊要)但注意的是安装成功后即使输入相应的IP地址和端口号,也会连接失败。这时需要修改配置文件redis.conf,注释掉bind127.0.0.1,这样可以使所有的ip访问redis,然后关闭保护模式。为了数据的安全性需要手动添加访问认证,然后杀掉redis进程,重启redis,再次建立连接。详情如图:
注释bind
关闭保护模式
添加访问认证
杀掉进程,重启redis
五、操作Redis五种数据类型
首先需要通过redis-cli连接redis,连接命令如下图:
注:-h用于指定ip -p用于指定端口 -a用于指定认证密码
PING命令返回PONG
指定操作数据的database
1、操作String类型
- Set:添加一条String类型数据
- Get:获取一条String类型数据
- Mset:添加多条String类型数据
- Mget:获取多条String类型数据
2、操作hash类型
- Hset:添加一条hash类型数据
- Hget:获取一条hash类型数据
- Hmset:添加多条hash类型数据
- Hmget:获取多条hash类型数据
- HgetAll:获取指定所有hash类型数据
- hdel:删除指定hash类型数据(一条或多条)
3、操作list
- Lpush:左添加(头)list类型数据
- Rpush:右添加(尾)类型数据
- Lrange: 获取list类型数据start起始下标 end结束下标 包含关系
- llen:获取条数
- lrem:删除列表中几个指定list类型数据
4、操作set
- Sadd:添加set类型数据
- Smembers:获取set类型数据
- scard:获取条数
- srem:删除数据
5、操作sorted se
- sorted set是通过分数值来进行排序的,分数值越大,越靠后。
- Zadd:添加sorted set类型数据
- Zrange:获取sorted set类型数据
- zcard:获取条数
- zrem:删除数据
注意:Zadd需要将Float或者Double类型分数值参数,放置在值参数之前
6、Redis中以层级关系、目录形式存储数据

7、设置key的失效时间
Redis 有四个不同的命令可以用于设置键的生存时间(键可以存在多久)或过期时间(键什么时候会被删除) ,具体如下:
- EXPlRE 命令用于将键key 的生存时间设置为ttl 秒。
- PEXPIRE 命令用于将键key 的生存时间设置为ttl 毫秒。
- EXPIREAT < timestamp> 命令用于将键key 的过期时间设置为timestamp所指定的秒数时间戳。
- PEXPIREAT < timestamp > 命令用于将键key 的过期时间设置为timestamp所指定的毫秒数时间戳。
- TTL:获取的值为-1说明此key没有设置有效期,当值为-2时证明过了有效期
8、删除
Del:用于删除数据(通用,适用于所有数据类型)
Hdel:用于删除hash类型数据
六、Java中操作Redis五种数据类型
1、创建项目
创建到这一步引入Redis依赖
2、添加依赖
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.shsxt</groupId>
<artifactId>redis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- spring data redis 组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!--
1.x 的版本默认采用的连接池技术是 Jedis,
2.0 以上版本默认连接池是 Lettuce,
如果采用 Jedis,需要排除 Lettuce 的依赖。
-->
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- jedis 依赖 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- web 组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- test 组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
3、配置文件
spring:
redis:
# Redis服务器地址
host: 192.168.16.22
# Redis服务器端口
port: 6379
# Redis服务器密码
password: root
# 选择哪个库,默认0库
database: 0
# 连接超时时间
timeout: 10000ms
jedis:
pool:
# 最大连接数,默认8
max-active: 1024
# 最大连接阻塞等待时间,单位毫秒,默认-1ms
max-wait: 10000ms
# 最大空闲连接,默认8
max-idle: 200
# 最小空闲连接,默认0
min-idle: 5
4、Java连接Redis
/**
* 连接Redis
*/
@Test
public void initConn01() {
// 创建jedis对象,连接redis服务
Jedis jedis = new Jedis("192.168.16.22", 6379);
// 设置认证密码
jedis.auth("root");
// 指定数据库 默认是0
jedis.select(1);
// 使用ping命令,测试连接是否成功
String result = jedis.ping();
System.out.println(result);// 返回PONG
// 添加一条数据
jedis.set("username", "zhangsan");
// 获取一条数据
String username = jedis.get("username");
System.out.println(username);
// 释放资源
if (jedis != null)
jedis.close();
}
五、通过Redis连接池获取连接对象并操作服务器
/**
* 通过Redis连接池获取连接对象
*/
@Test
public void initConn02() {
// 初始化redis客户端连接池
JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), "192.168.16.22", 6379, 10000, "root");
// 从连接池获取连接
Jedis jedis = jedisPool.getResource();
// 指定数据库 默认是0
jedis.select(2);
// 使用ping命令,测试连接是否成功
String result = jedis.ping();
System.out.println(result);// 返回PONG
// 添加一条数据
jedis.set("username", "zhangsan");
// 获取一条数据
String username = jedis.get("username");
System.out.println(username);
// 释放资源
if (jedis != null)
jedis.close();
}
六、封装JedisUtil对外提供连接对象获取方法
@Configuration
public class RedisConfig {
//服务器地址
@Value("${spring.redis.host}")
private String host;
//端口
@Value("${spring.redis.port}")
private int port;
//密码
@Value("${spring.redis.password}")
private String password;
//超时时间
@Value("${spring.redis.timeout}")
private String timeout;
//最大连接数
@Value("${spring.redis.jedis.pool.max-active}")
private int maxTotal;
//最大连接阻塞等待时间
@Value("${spring.redis.jedis.pool.max-wait}")
private String maxWaitMillis;
//最大空闲连接
@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;
//最小空闲连接
@Value("${spring.redis.jedis.pool.min-idle}")
private int minIdle;
@Bean
public JedisPool redisPoolFactory(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//注意值的转变
jedisPoolConfig.setMaxWaitMillis(Long.parseLong(maxWaitMillis.substring(0,maxWaitMillis.length()-2)));
//注意属性名
jedisPoolConfig.setMaxTotal(maxTotal);
jedisPoolConfig.setMaxIdle(maxjavaIdle);
jedisPoolConfig.setMinIdle(minIdle);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, Integer.parseInt(timeout.substring(0,
timeout.length() - 2)), password);
return jedisPool;
}
}
七、Java操作Redis五种数据类型(⭐)
连接与释放
@Autowired
private JedisPool jedisPool;
private Jedis jedis = null;
@Before
public void initConn(){
jedis = jedisPool.getResource();
}
① 操作String
// 1.操作String
@Test
public void testString() {
// 添加一条数据
jedis.set("username", "zhangsan");
jedis.set("age", "18");
// 添加多条数据 参数奇数为key 参数偶数为value
jedis.mset("address", "bj", "sex", "1");
// 获取一条数据
String username = jedis.get("username");
System.out.println(username);
// 获取多条数据
List<String> list = jedis.mget("username", "age", "address", "sex");
for (String str : list) {
System.out.println(str);
}
// 删除
//jedis.del("username");
}
② 操作hash
// 2.操作Hash
@Test
public void testHash() {
/*
* 添加一条数据
* 参数一:redis的key
* 参数二:hash的key
* 参数三:hash的value
*/
jedis.hset("userInfo", "name", "lisi");
// 添加多条数据
Map<String, String> map = new HashMap<>();
map.put("age", "20");
map.put("sex", "1");
jedis.hmset("userInfo", map);
// 获取一条数据
String name = jedis.hget("userInfo", "name");
System.out.println(name);
// 获取多条数据
List<String> list = jedis.hmget("userInfo", "age", "sex");
for (String str : list) {
System.out.println(str);
}
// 获取Hash类型所有的数据
Map<String, String> userMap = jedis.hgetAll("userInfo");
for (Entry<String, String> userInfo : userMap.entrySet()) {
System.out.println(userInfo.getKey() + "--" + userInfo.getValue());
}java
// 删除 用于删除hash类型数据
//jedis.hdel("userInfo", "name");
}
③ 操作list
// 3.操作list
@Test
public void testList() {
// 左添加(上)
// jedis.lpush("students", "Wang Wu", "Li Si");
// 右添加(下)
// jedis.rpush("students", "Zhao Liu");
// 获取 start起始下标 end结束下标 包含关系
List<String> students = jedis.lrange("students", 0, 2);
for (String stu : students) {
System.out.println(stu);
}
// 获取总条数
Long total = jedis.llen("students");
System.out.println("总条数:" + total);
// 删除单条 删除列表中第一次出现的Li Si
// jedis.lrem("students", 1, "Li Si");
// 删除多条
// jedis.del("students");
}
④ 操作set
// 4.操作set-无序
@Test
public void testSet() {
// 添加数据
jedis.sadd("letters", "aaa", "bbb", "ccc", "ddd", "eee");
// 获取数据
Set<String> letters = jedis.smembers("letters");
for (String letter: letters) {
System.out.println(letter);
}
// 删除
//jedis.srem("letters", "aaa", "bbb");
}
⑤ 操作sorted set
// 5.操作sorted set-有序
@Test
public void testSortedSet() {
Map<String, Double> scoreMembers = new HashMap<>();
scoreMembers.put("zhangsan", 7D);
scoreMembers.put("lisi", 3D);
scoreMembers.put("wangwu", 5D);
scoreMembers.put("zhaoliu", 6D);
scoreMembers.put("tianqi", 2D);
// 添加数据
jedis.zadd("score", scoreMembers);
// 获取数据
Set<String> scores = jedis.zrange("score", 0, 4);
for (String score: scores) {
System.out.println(score);
}
// 获取总条数
Long toliutal = jedis.zcard("score");java
System.out.println("总条数:" + total);
// 删除
//jedis.zrem("score", "zhangsan", "lisi");
}
⑥ Redis中以层级关系、目录形式存储数据
// Redis中以层级关系、目录形式存储数据
@Test
public void testdir(){
jedis.set("user:01", "user_zhangsan");
System.out.println(jedis.get("user:01"));
}java
⑦ 设置key的失效时间
@Test
public void testExpire() {
// 方法一:
jedis.set("code", "test");
jedis.expire("code", 180);// 180秒
jedis.pexpire("code", 180000L);// 180000毫秒
jedis.ttl("code");// 获取秒
// 方法二:
jedis.setex("code", 180, "test");// 180秒
jedis.psetex("code", 180000L, "test");// 180000毫秒
jedis.pttl("code");// 获取毫秒
// 方法三:
/*
第一个参数:key
第二个参数:value
第三个参数:NX是不存在时才set,XX是存在时才set
第四个参数:EX是秒,PX是毫秒
*/
jedis.set("asd", "test", "NX", "EX", 180);
jedis.ttl("code");// 获取秒java
}
⑧ 获取所有key&事务&删除
// 获取所有key
@Test
public void testAllKeys() {
// 当前库key的数量
System.out.println(jedis.dbSize());
// 当前库key的名称
Set<String> keys = jedis.keys("*");
for (String key: keys) {
System.out.println(key);
}
}
// 操作事务
@Test
public void testMulti() {
Transaction tx = jedis.multi();
// 开启事务
tx.set("tel", "10010");
// 提交事务
// tx.exec();
// 回滚事务
tx.discard();
}
// 删除
@Test
public void testDelete() {
// 删除 通用 适用于所有数据类型
jedis.del("score");
}
⑨ 操作byte
SerializeUtil.java
package com.shsxt.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* 序列化工具类
*/
public class SerializeUtil {
/**
* 将java对象转换为byte数组 序列化过程
*/
public static byte[] serialize(Object object) {
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
try {
// 序列化
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
byte[] bytes = baos.toByteArray();
return bytes;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将byte数组转换为java对象 反序列化
*/
public static Object unserialize(byte[] bytes) {
if(bytes == null)return null;
ByteArrayInputStream bais = null;
try {
// 反序列化
bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
User.java
package com.shsxt.entity;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 9148937431079191022L;
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
JedisTest.java
// 操作byte
@Test
public void testByte() {
User user = new User();
user.setId(2);
user.setUsername("zhangsan");
user.setPassword("123");
// 序列化
byte[] userKey = SerializeUtil.serialize("user:" + user.getId());
byte[] userValue = SerializeUtil.serialize(user);
jedis.set(userKey, userValue);
// 获取数据
byte[] userResult = jedis.get(userKey);
// 反序列化
User u = (User) SerializeUtil.unserialize(userResult);
System.out.println(u);
}