一、缓存简介
(1)什么是缓存
缓存就是数据存放在距离计算最近的位置以加快处理速度。缓存是改善软件性能的第一手段,缓存意在减少系统对数据库的过多访问,通过减少对数据库访问次数,改用访问内存的方式,提升系统的性能。
系统由于多次查询数据库,消耗大量系统资源,且查询效率可能因为开发者的个人能力导致查询效率不高,或占用过多资源影响系统性能。使用缓存管理,系统只有第一次查询访问数据库,而后皆通过访问内存物的数据,不需要再计较这次查询的SQL是否效率低下,直接取得数据返回即可。
(2)缓存的需求
在复杂的软件设计中,缓存几乎无处不在,大型网站架构设计在很多方面都使用了缓存设计。缓存分为两种:本地缓存和分布式缓存。
本地缓存:在应用服务器本地缓存着热点数据,应用程序可以在本机内存中直接访问数据,而无需访问数据库。
分布式缓存:在应用服务器的数据量非常庞大,及时只缓存一小部分,需要的内存空间也不是单机能承受的,所以除了本地缓存,还需要分布式缓存,将数据缓存在一个专门的分布式缓存集群中,应用程序通过网络通信访问数据。
(3)主流的缓存技术介绍
主流缓存技术有Redis和Memcached。二者谁的性能更高?单纯从缓存命中的角度来说,是Memcached要高,Redis和Memcached的差距不大。但是Redis提供的功能更加强大。
1、Memcached是多线程
2、Redis是单线程
二、Redis简介
Redis(Remote Dictionary Server),即远程字典服务,是一个开源的、使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、key-value数据库,并提供多种语言API。Redis是一个key-value存储系统。它支持存储的value类型相对更多,包括string、list、set、zset和hash。与Memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,且在此基础上实现master-slave(主从)同步。
Redis是一个高性能的key-value数据库。redis的出现,很大程度上补偿了Memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好地补充作用。他提供了java、C/C++、C##、PHP、JavaScript等。
Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可用时关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接受主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。
三、redis安装和操作
(1)下载redis
打开redis官网redis.io找到Download it选择对应的版本开始下载
(2)linux下安装redis
1、安装redis运行环境
redis是C语言开发的安装redis需要C语言的编译环境,如果没有gcc需要在线安装。
yum install gcc-c++
yum -y install gcc automake autoconf libtool make
yum -y install tcl
2、上传到linux上并进行解压
打开FileZilla把redis-5.0.5上传到 /usr/local目录下,并进行解压
tar zxf redis-5.0.5.tar.gz
3、进入redis源码目录输入make命令进行编译
(3)redis的常用操作
1、启动redis服务
![](https://i-blog.csdnimg.cn/blog_migrate/5f6f8f185b69f9a38516253d7a01cffb.png)
2、通过客户端连接服务端
[root@localhost redis-5.0.5]]# ./src/redis-cli
退出连接:exit
3、设置密码
myredis/redis.conf
用vi编辑器对redis.conf进行修改,把改行去掉注释,同时可以指定密码
4、redis连接
[root@localhost redis-5.0.5]]# ./src/redis-cli
四、redis的数据类型
1、String类型
(1)String类型介绍
string是redis最基本的类型,一个key对应一个value。string类型是二进制安全的。意思是redis的string可以包括任何数据,比如jpg图片或序列化的对象。strin类型是Redis最基本的数据类型,string类型的值最大能存储512MB。
(2)String类型命令
set命令 | 设置key和value并保存 —— 语法:set key value |
get命令 | 获取指定key的值 —— 语法:get key |
append命令 | 在原来的值的基础上追加新值,并返回总字符串的长度——语法:append key value |
strlen命令 | 获取指定key的值的长度—— 语法:strlen key |
getrange命令 | 获取指定key的值,从start位置开始,end位置结束——语法:getrange key start end |
setrange命令 | 从起始位置替换值——语法:setrange key offset value |
setex命令 | 设置key的值并指定存活时间—— 语法:setex key second value |
setnx命令 | 设置key的值并且永久有效——语法:setnx key value |
incr命令 | 对key的值自增1——语法:incr key |
decr命令 | 对key的值自减1——语法:decr key |
incrby命令 | 对key的值递增指定步长的值——语法:incrby key number |
decrby命令 | 对key的值递减指定步长的值——语法:decrby key number |
mset命令 | 一次性设置多个key-value键值对 —— 语法:mset key1 value1 key2 value2 ... |
mget命令 | 一次性获取多个key的值—— 语法:mget key1 key2 . . . |
expire命令 | 设置key的存货时间 seconds秒数—— 语法:ttl key |
ttl命令 | 设置指定的key永久有效——语法:persist key |
2、List类型
(1)List类型介绍
Redis列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部或者尾部,一个列表最多可以包含232 - 1个元素。
(2)List类型命令
lpush命令 | 向key集合的左边一次添加值 —— 语法:lpush key value1 value2. . . |
rpush命令 | 向key集合的右边一次添加值 —— 语法:rpush key value1 value2 . . . |
lrange命令 | 取key集合中指定的start位置到stop位置的内容(-1代表集合结束)—— 语法:lrange key start stop |
llen命令 | 查看key集合的长度 —— 语法:llen key |
lpop命令 | 删除key集合的第一个元素 —— 语法:lpop key |
rpop命令 | 删除key集合的最后一个元素——语法:rpop key |
lindex命令 | 获取key集合的index索引的元素 —— 语法:lindex key index |
lrem命令 | 删除key集合的count个索引的元素(集合中有重复值)—— 语法:lrem key count value |
3、Hash类型
(1)Hash类型介绍
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。Redis中每个hash可以存储232-1键值对。
(2)Hash类型命令
hset命令 | 设置keyfield字段的值 —— 语法:hset key field value |
hget命令 | 获取key的field字段的值 —— 语法:hget key field |
hmset命令 | 一次设置key的多个字段的值 —— 语法:hmset key field1 value1 field2 value2. . . |
hmget命令 | 一次获取key的多个字段的值 —— 语法:hmget key field1 field2 . . . |
hgetall命令 | 获取key的多个字段的值(包括键和值)—— 语法:hgetall key |
hkeys命令 | 获取key的所有字段 —— 语法:hkeys key |
hvals命令 | 获取key的所有字段的值 —— 语法:hvals key |
hdel命令 | 删除大key中指定的多个小key的值 —— 语法:hdel key field1 field2 . . . |
hlen命令 | 返回key的所有键值对的个数 —— 语法:hlen key |
hexists命令 | 返回key的field字段是否存在 —— 语法:hexists key field |
4、Set类型
(1)String类型介绍
Redis的Set是String类型的无序集合,集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis中集合是通过哈希表实现的,所有添加、查找的复杂度都是0(1)。集合中最大的成员数为232 -1.
(2)String类型命令
sadd命令 | 向为key的集合中添加多个值(去重)—— 语法:sadd key value1 value2 . . . |
smember命令 | 循环key集合中的所有值 —— 语法:members key |
scard命令 | 统计key集合中的元素个数 —— 语法:scard key |
srem命令 | 删除key集合中的value值 —— 语法:srem key value |
spop命令 | 随机删除key集合中的某个值 —— 语法:spop key |
smove命令 | 将集合的某个值赋给另为一个集合: SMOVE 源集合 目的集合 值 —— 语法:smove source destination member |
Sdiff命令 | 求差集 —— 语法:Sdiff 集合1 集合2 |
sinter命令 | 求交集 —— 语法:sinter 集合1 集合2 |
sunion命令 | 求并集 —— 语法:sunion 集合1 集合2 |
5、SortedSet类型
(1)SortedSet类型介绍
Redis有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。集合是通过哈希表实现的,所以添加、删除、查找的复杂度都是0(1)。集合中最大的成员为232-1。
(2)SortedSet类型命令
zadd命令 | 向为key的集合中添加多个(score-value)—— 语法:zadd key score value . . . |
zrange命令 | 循环key的集合从start位到stop位置的值 —— 语法:zrang key start stop [WITHSCORES] |
zrem命令 | 删除key集合中指定的value元素 —— 语法:zrem key value |
zrevrange命令 |
逆序显示key集合中的元素 —— 语法:zrevrange key start stop [WITHSCORES]
|
zrangebyscore命令 |
根据分数区间查询内容 —— 语法:zrangebyscore key min max
|
zcard命令 |
统计key集合中有多少个键值对 —— 语法:zcard key
|
zcount命令 |
统计key集合中min到max区间元素 —— 语法:zcount key min max
|
zrank命令 |
统计key集合中指定元素索引 —— 语法:zrank students wangwu
|
zscore命令 |
统计key集合中指定元素的分数 —— 语法:zscore key value
|
五、Redis的持久化方式
Redis提供了两种方式,实现数据的持久化到硬盘。
- 【全量】RDB持久化,是指在指定的时间间隔内将内存中的数据集快照写入磁盘。实际操作过程是,fork一个子进程,先将数据集写入临时文件,写入成功后,在替换之前的文件,用二进制压缩存储。
- 【增量】AOF持久化,以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
1、RDB优缺点
(1)优点
- 灵活设置备份频率和周期。你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
- 非常适合冷备份,对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后在转移到其他存储介质上。
- 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子程序,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作。
- 恢复更快。相比于AOF机制,RDB的恢复速度更快,更适合恢复数据,特别是在数据集非常大的情况。
(2)缺点
- 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将会丢失。
- 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至1秒钟。
2、AOF优缺点
(1)优点
- 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3种同步策略,即每秒同步。每修改(执行一个命令)同步和不同步。
- 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。
-
如果日志过大,Redis可以自动启用 rewrite 机制。即使出现后台重写操作,也不会影响客户端的读写。因为 在 rewrite log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志 文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
-
AOF 包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。
(2)缺点
-
对于相同数量的数据集而言,AOF 文件通常要大于 RDB 文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
-
根据同步策略的不同,AOF 在运行效率上往往会慢于 RDB 。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和 RDB 一样高效
-
以前 AOF 发生过 bug ,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志/merge/回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug 。不过 AOF 就是为了避免 rewrite 过程导致的 bug ,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。
六、java操作redis
1、单实例方式
(1)构建maven工程添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
(2)创建Jedis连接对象
Jedis jedis=new Jedis("192.168.18.200",6379);
(3)操作redis数据库
@Test
public void testJedis() {
Jedis jedis=new Jedis("192.168.18.131",6379);
jedis.set("birthday", "1991-10-30");
jedis.set("address", "beijing");
String address=jedis.get("address");
String birthday=jedis.get("birthday");
System.out.println("address:"+address);
System.out.println("birthday:"+birthday);
jedis.close();
}
2、连接池方式
(1)构建maven工程添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
(2)构件连接池对象
//创建JedisPoolConfig对象
JedisPoolConfig poolConfig=new JedisPoolConfig();
//最大空闲连接数
poolConfig.setMaxIdle(3);
//大连接数
poolConfig.setMaxTotal(5);
//连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
poolConfig.setBlockWhenExhausted(true);
//获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常
poolConfig.setMaxWaitMillis(30000);
//在获取连接的时候检查有效性
poolConfig.setTestOnBorrow(true);
JedisPool jedisPool=new
JedisPool(poolConfig,"192.168.18.200",6379,30000,"123456");
(3)操作redis数据库
Jedis jedis=jedisPool.getResource();
jedis.set("name", "wangxiaobing");
jedis.set("age", "22");
String name=jedis.get("name");
String age=jedis.get("age");
System.out.println("name:"+name);
System.out.println("age:"+age);
3、操作java对象
(1)构建maven工程添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.8</version>
</dependency>
(2)构建Person类型
public class Person implements Serializable{
private int id;
private String name;
private String gender;
private int age;
public Person() {}
public Person(int id, String name, String gender, int age) {
super();
this.id = id;
this.name = name;
this.gender = gender;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", gender=" + gender
+ ", age=" + age + "]";
}
}
(3)redis存储Person类型的对象实现
一般情况下我们不会存储区java对象,因为在redis中对于对象都会把对象变成一个JSON格式的字符串,然后进行存储。
工具类:
public class JsonUtils {
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 将对象转换成json字符串。
* <p>Title: entityToJson</p>
* <p>Description: </p>
* @param data
* @return
*/
public static String objectToJson(Object data) {
try {
String string = MAPPER.writeValueAsString(data);
return string;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
/**
* 将json结果集转化为对象
* @param jsonData json数据
* @param clazz 对象中的object类型
* @return
*/
public static <T> T jsonToEntity(String jsonData, Class<T> beanType) {
try {
T t = MAPPER.readValue(jsonData, beanType);
return t;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将json数据转换成entity对象list
* <p>Title: jsonToList</p>
* <p>Description: </p>
* @param jsonData
* @param beanType
* @return
*/
public static <T>List<T> jsonToList(String jsonData, Class<T> beanType){
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class,beanType);
try {
List<T> list = MAPPER.readValue(jsonData, javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
测试类:
@Test
public void testAddPerson(){
//创建JedisPoolConfig对象
JedisPoolConfig poolConfig=new JedisPoolConfig();
//最大空闲连接数
poolConfig.setMaxIdle(3);
//大连接数
poolConfig.setMaxTotal(5);
//连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
poolConfig.setBlockWhenExhausted(true);
//获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常
poolConfig.setMaxWaitMillis(30000);
//在获取连接的时候检查有效性
poolConfig.setTestOnBorrow(true);
JedisPool jedisPool=new JedisPool(poolConfig,"192.168.18.200",6379,30000,"123456");
Person person=new Person(100,"张三","男",22,new Date());
Jedis jedis = jedisPool.getResource();
jedis.set("person", JsonUtils.objectToJson(person));
//jedis.hset("persons",person.getName(),JsonUtils.objectToJson(person));
jedis.close();
}
@Test
public void testAddPerson2(){
//创建JedisPoolConfig对象
JedisPoolConfig poolConfig=new JedisPoolConfig();
//最大空闲连接数
poolConfig.setMaxIdle(3);
//大连接数
poolConfig.setMaxTotal(5);
//连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
poolConfig.setBlockWhenExhausted(true);
//获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常
poolConfig.setMaxWaitMillis(30000);
//在获取连接的时候检查有效性
poolConfig.setTestOnBorrow(true);
JedisPool jedisPool=new JedisPool(poolConfig,"192.168.18.200",6379,30000,"123456");
Jedis jedis = jedisPool.getResource();
String jsonData=jedis.get("person");
Person person=JsonUtils.jsonToEntity(jsonData,Person.class);
System.out.println(person);
jedis.close();
}