1. Redis概述

基于内存存储的,NoSQL数据库(非关系型数据库),存储结构:key-value,Redis是一个开放源代码(BSD许可)内存中的数据结构存储用作数据库丶缓存和消息代理。对于数据量多,数据交互效率要求高的场景,可以考虑使用Redis

Redis:开源、免费、高性能、K-V数据库、内存数据库、非关系型数据库,支持持久化、集群和事务

1.1 什么是Redis

Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:

  • 字符串类型 string
  • 哈希类型 hash
  • 列表类型 list
  • 集合类型 set
  • 有序集合类型 sortedset

1.2 redis的应用场景

  • 缓存(数据查询、短连接、新闻内容、商品内容等等)
  • 聊天室的在线好友列表
  • 任务队列。(秒杀、抢购、12306等等)
  • 应用排行榜
  • 网站访问统计
  • 数据过期处理(可以精确到毫秒
  • 分布式集群架构中的session分离

2. Redis安装

2.1 在线安装

环境:Ubunto 16.04

1
2
3
4
输入命令 apt-get install redis-server
输入y 确认安装并使用空间
安装完成后,使用service redis status 可以查看redis服务的状态为active(running),说明安装完成系统自动启动了服务
使用ps -aux|grep redis命令可以看到服务器系统进程默认端口6379

2.2 安装包安装

环境:已安装GCC,Ubunto 16.04,redis-4.0.11.tar.gz,使用Xshell工具上传Redis的tar包文件

2.2.1 在Ubuntu上安装GCC

GNU编译器集合(GCC)是C,C ++,Objective-C,Fortran,Ada,Go和D编程语言的编译器和库的集合。许多开源项目包括GNU工具和Linux内核都是用GCC编译的。默认的Ubuntu存储库包含一个名为build-essential的元包,它包含GCC编译器以及编译软件所需的许多库和其他实用程序。

1
2
3
首先更新包列表:apt update
安装build-essential软件包: apt install build-essential,该命令将安装一堆新包,包括gcc,g ++和make
使用gcc --version命令打印GCC版本:gcc --version

2.2.2 tar安装

1
2
3
4
5
解压:tar -zxvf redis-4.0.11.tar.gz  
复制:mv redis-4.0.11 /usr/local/redis
进入redis目录:cd /usr/local/redis/
编译,并使用标准的libc中的内存管理函数,必须进入Redis的安装目录才能执行该命令:make MALLOC=libc
安装,并选择安装目录:make PREFIX=/usr/local/redis install

查看安装目录下的文件,cd /usr/local/redis/bin/ 开启Redis的服务端,./redis-server 开启Redis的客户端./redis-cli

##docker安装

####拉取镜像

1
docker pull redis

####启动镜像

1
docker run -d  -p 6379:6379  --name myredis

3. 命令操作

3.1 redis的数据结构:

redis存储的是:key,value格式的数据,其中key都是字符串,value有5种不同的数据结构

value的数据结构:

  • 字符串类型 string
  • 哈希类型 hash : map格式
  • 列表类型 list : linkedlist格式。支持重复元素
  • 集合类型 set : 不允许重复元素
  • 有序集合类型 sortedset : 不允许重复元素,且元素有顺序

3.1.1 字符串类型 String

存储 : set key value

1
2
127.0.0.1:6379> set username zhangsan
OK

获取 : get key

1
2
127.0.0.1:6379> get username
"zhangsan"

删除 : del key

1
2
127.0.0.1:6379> del age
(integer) 1

3.1.2 哈希类型 hash

存储 : hset key field value

1
2
3
4
127.0.0.1:6379> hset myhash username lisi
(integer) 1
127.0.0.1:6379> hset myhash password 123
(integer) 1

获取 : hget key field : 获取指定的field对应的值

1
2
127.0.0.1:6379> hget myhash username
"lisi"

hgetall key: 获取所有的field和value

1
2
3
4
5
127.0.0.1:6379> hgetall myhash
1) "username"
2) "lisi"
3) "password"
4) "123"

删除 : hdel key field

1
2
127.0.0.1:6379> hdel myhash username
(integer) 1

3.1.3 列表类型 list

可以添加一个元素到列表的头部(左边)或者尾部(右边)

添加:

  • lpush key value: 将元素加入列表左表
  • rpush key value:将元素加入列表右边
1
2
3
4
5
6
127.0.0.1:6379> lpush myList a
(integer) 1
127.0.0.1:6379> lpush myList b
(integer) 2
127.0.0.1:6379> rpush myList c
(integer) 3

获取:

  • lrange key start end : 范围获取
1
2
3
4
127.0.0.1:6379> lrange myList 0 -1
1) "b"
2) "a"
3) "c"

删除:

  • lpop key: 删除列表最左边的元素,并将元素返回
  • rpop key: 删除列表最右边的元素,并将元素返回

3.1.4 集合类型 set

不允许重复元素

存储 : sadd key value

1
2
3
4
127.0.0.1:6379> sadd myset a
(integer) 1
127.0.0.1:6379> sadd myset a
(integer) 0

获取 : smembers key : 获取set集合中所有元素

1
2
127.0.0.1:6379> smembers myset
1) "a"

删除 : srem key value : 删除set集合中的某个元素

1
2
127.0.0.1:6379> srem myset a
(integer) 1

3.1.5有序集合类型 sortedset

不允许重复元素,且元素有顺序.每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

存储 : zadd key score value

1
2
3
4
5
6
127.0.0.1:6379> zadd mysort 60 zhangsan
(integer) 1
127.0.0.1:6379> zadd mysort 50 lisi
(integer) 1
127.0.0.1:6379> zadd mysort 80 wangwu
(integer) 1

获取 : zrange key start end [withscores]

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> zrange mysort 0 -1
1) "lisi"
2) "zhangsan"
3) "wangwu"
127.0.0.1:6379> zrange mysort 0 -1 withscores
1) "zhangsan"
2) "60"
3) "wangwu"
4) "80"
5) "lisi"
6) "500"

删除 : zrem key value

1
2
127.0.0.1:6379> zrem mysort lisi
(integer) 1

3.1.6 通用命令

  • keys * : 查询所有的键
  • type key : 获取键对应的value的类型
  • del key:删除指定的key value

4. 持久化

redis是一个内存数据库,当redis服务器重启,获取电脑重启,数据会丢失,我们可以将redis内存中的数据持久化保存到硬盘的文件中。

##4.1 redis持久化机制: RDB : 默认方式,不需要进行配置,默认就使用这种机制,在一定的间隔时间中,检测key的变化情况,然后持久化数据

  1. 编辑redis.conf文件
1
2
3
4
5
6
#   after 900 sec (15 min) if at least 1 key changed
save 900 1
# after 300 sec (5 min) if at least 10 keys changed
save 300 10
# after 60 sec if at least 10000 keys changed
save 60 10000
  1. 重新启动redis服务器,并指定配置文件名称
1
./redis-server redis.conf

AOF: 日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据

  1. 编辑redis.conf文件
1
2
3
4
appendonly no(关闭aof) --> appendonly yes (开启aof)
# appendfsync always : 每一次操作都进行持久化
appendfsync everysec : 每隔一秒进行一次持久化
# appendfsync no : 不进行持久化

5. Java客户端 Jedis

Jedis: 一款java操作redis数据库的工具. 使用步骤:

  1. 下载jedis的jar包
  2. 使用
1
2
3
4
5
6
//1. 获取连接
Jedis jedis = new Jedis("localhost",6379);
//2. 操作
jedis.set("username","zhangsan");
//3. 关闭连接
jedis.close();

注意哦:

  • 如果需要连接其他服务器的redis需要修改配置文件redis.conf,注释# bind 127.0.0.1,并一配置文件启动,否则后台连接超时。
  • 如果需要添加认证,在配置文件中添加requirepass “zysheep”
1
2
3
4
5
6
7
String host="192.168.40.137";
int prot=6379;
Jedis jedis = new Jedis(host,prot);
jedis.auth("zysheep");
jedis.set("strName","李四");
System.out.println("strName的key:"+jedis.get("strName"));
System.out.println(jedis.ping()); //连接测试

5.1 Jedis操作各种redis中的数据结构

5.1.1 字符串类型 string

  • set
  • get
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1. 获取连接
Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
//2. 操作
//存储
jedis.set("username","zhangsan");
//获取
String username = jedis.get("username");
System.out.println(username);

//可以使用setex()方法存储可以指定过期时间的 key value
jedis.setex("activecode",20,"hehe");//将activecode:hehe键值对存入redis,并且20秒后自动删除该键值对

//3. 关闭连接
jedis.close();

5.1.2 哈希类型 hash : map格式

  • hset
  • hget
  • hgetAll
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//1. 获取连接
Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
//2. 操作
// 存储hash
jedis.hset("user","name","lisi");
jedis.hset("user","age","23");
jedis.hset("user","gender","female");

// 获取hash
String name = jedis.hget("user", "name");
System.out.println(name);


// 获取hash的所有map中的数据
Map<String, String> user = jedis.hgetAll("user");

// keyset
Set<String> keySet = user.keySet();
for (String key : keySet) {
//获取value
String value = user.get(key);
System.out.println(key + ":" + value);
}

//3. 关闭连接
jedis.close();

5.1.3 列表类型 list : linkedlist格式。支持重复元素

  • lpush / rpush
  • lpop / rpop
  • lrange start end : 范围获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//1. 获取连接
Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
//2. 操作
// list 存储
jedis.lpush("mylist","a","b","c");//从左边存
jedis.rpush("mylist","a","b","c");//从右边存

// list 范围获取
List<String> mylist = jedis.lrange("mylist", 0, -1);
System.out.println(mylist);

// list 弹出
String element1 = jedis.lpop("mylist");//c
System.out.println(element1);

String element2 = jedis.rpop("mylist");//c
System.out.println(element2);

// list 范围获取
List<String> mylist2 = jedis.lrange("mylist", 0, -1);
System.out.println(mylist2);

//3. 关闭连接
jedis.close();

5.1.4 集合类型 set : 不允许重复元素

  • sadd
  • smembers:获取所有元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1. 获取连接
Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
//2. 操作


// set 存储
jedis.sadd("myset","java","php","c++");

// set 获取
Set<String> myset = jedis.smembers("myset");
System.out.println(myset);

//3. 关闭连接
jedis.close();

5.1.5 有序集合类型 sortedset:不允许重复元素,且元素有顺序

  • zadd
  • zrange
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   //1. 获取连接
Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
//2. 操作
// sortedset 存储
jedis.zadd("mysortedset",3,"亚瑟");
jedis.zadd("mysortedset",30,"后裔");
jedis.zadd("mysortedset",55,"孙悟空");

// sortedset 获取
Set<String> mysortedset = jedis.zrange("mysortedset", 0, -1);

System.out.println(mysortedset);


//3. 关闭连接
jedis.close();

5.2 jedis连接池: JedisPool

使用:

  • 1.创建JedisPool连接池对象
  • 2.调用方法 getResource()方法获取Jedis连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  	//0.创建一个配置对象
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(50);
config.setMaxIdle(10);

//1.创建Jedis连接池对象
JedisPool jedisPool = new JedisPool(config,"localhost",6379);

//2.获取连接
Jedis jedis = jedisPool.getResource();
//3. 使用
jedis.set("hehe","heihei");


//4. 关闭 归还到连接池中
jedis.close();

5.2.1 连接池工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
jedis.properties
host=127.0.0.1 # redis服务器ip地址
port=6379 #端口
maxTotal=50 # 最大连接数
maxIdle=10 # 空闲连接数

public class JedisPoolUtils {
private static JedisPool jedisPool;
static{
//读取配置文件
InputStream is = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
//创建Properties对象
Properties pro = new Properties();
//关联文件
try {
pro.load(is);
} catch (IOException e) {
e.printStackTrace();
}
//获取数据,设置到JedisPoolConfig中
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.parseInt(pro.getProperty("maxTotal")));
config.setMaxIdle(Integer.parseInt(pro.getProperty("maxIdle")));

//初始化JedisPool
jedisPool = new JedisPool(config,pro.getProperty("host"),Integer.parseInt(pro.getProperty("port")));
}
/**
* 获取连接方法
*/
public static Jedis getJedis(){
return jedisPool.getResource();
}
}

SpringBoot整合redis

Pom

引入redisstarter依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!--redis start-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redis end-->
<!--cache start-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--cache end-->
<!--mysql-connector start-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mysql-connector end-->
<!--mybatis start-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<!--mybatis end-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

yml

配置redis连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
spring:
datasource:
url: jdbc:mysql://172.16.0.192:3306/springboot_cache
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: 172.16.0.192 # 主机地址
cache:
redis:
time-to-live: -1 #毫秒
#开启驼峰命名
mybatis:
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn:
panyucbale:
springboot:
mapper: debug

redisTemplate

测试redisTemplate常用api

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@SpringBootTest
class Springboot08RedisApplicationTests {

@Autowired
StringRedisTemplate stringRedisTemplate; //操作k-v都是字符串的

@Autowired
private RedisTemplate redisTemplate; //k-v都是对象的

/**
* Redis常见的五大数据类型
* String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
* stringRedisTemplate.opsForValue()[String(字符串)]
* stringRedisTemplate.opsForList()[List(列表)]
* stringRedisTemplate.opsForSet()[Set(集合)]
* stringRedisTemplate.opsForHash()[Hash(散列)]
* stringRedisTemplate.opsForZSet()[ZSet(有序集合)]
*/
@Test
public void test01() {
//给redis中保存数据
//stringRedisTemplate.opsForValue().set("string_msg","hello");
String msg = stringRedisTemplate.opsForValue().get("string_msg");

System.out.println(msg);

// stringRedisTemplate.opsForList().leftPush("mylist","1");
// stringRedisTemplate.opsForList().leftPush("mylist","2");
}
}

测试缓存

###原理

CacheManager===ache 缓存组件来实际给缓存中存取数据

  1. 引入redisstarter,容器中保存的是RedisCacheManager
  2. RedisCacheManager帮我们创建RedisCache来作为缓存组件;RedisCache通过操作redis缓存数据的
  3. 默认保存数据k-v 都是Object;利用序列化保存;如何保存为json
    • 引入了redisstartercacheManager变为 RedisCacheManager
    • 默认创建的 RedisCacheManager 操作redis的时候使用的是 RedisTemplate<Object, Object>
    • RedisTemplate<Object, Object>是 默认使用jdk的序列化机制
  4. 自定义CacheManager;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Configuration
public class MyRedisConfig {
@Value("${spring.cache.redis.time-to-live}")
private Duration timeToLive = Duration.ZERO;

@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(mapper);
template.setValueSerializer(jackson2JsonRedisSerializer);

StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(stringRedisSerializer);
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}


@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);

// 配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(timeToLive)
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();

RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}

##环境准备

###Department

实体类Department

1
2
3
4
5
6
7
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Department implements Serializable {
private Integer id;
private String departmentName;
}

###Mapper

1
2
3
4
5
6
@Mapper
public interface DepartmentMapper {

@Select("SELECT * FROM department WHERE id = #{id}")
Department getDeptById(Integer id);
}

###DeptService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Service
public class DeptService {

@Autowired
DepartmentMapper departmentMapper;

@Autowired
CacheManager cacheManager;

/**
* 缓存的数据能存入redis;
* 第二次从缓存中查询就不能反序列化回来;
* 存的是dept的json数据;CacheManager默认使用RedisTemplate<Object, Employee>操作Redis
*
* @param id
* @return
*/
@Cacheable(cacheNames = "dept")
public Department getDeptById(Integer id) {
System.out.println("查询部门" + id);
Department department = departmentMapper.getDeptById(id);
return department;
}
// 使用缓存管理器得到缓存,进行api调用
public Department getDeptByIdManager(Integer id) {
System.out.println("查询部门" + id);
Department department = departmentMapper.getDeptById(id);
//获取某个缓存
Cache cache = cacheManager.getCache("dept");
cache.put("dept::" + id, department);
return department;
}

}

###DeptController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class DeptController {

@Autowired
DeptService deptService;

@GetMapping("/dept/{id}")
public Department getDept(@PathVariable("id") Integer id) {
return deptService.getDeptById(id);
}


@GetMapping("/depts/{id}")
public Department getDepts(@PathVariable("id") Integer id) {
return deptService.getDeptByIdManager(id);
}
}

记得在启动类中开启注解缓存,否则不会生效

问题

  1. 缓存的数据能存入redis;第二次从缓存中查询就不能反序列化回来, 存的是dept的json数据;CacheManager默认使用RedisTemplate<Object, Employee>操作Redis