1 Java缓存
1.1 jvm内置缓存
Java中实现缓存的方式有很多,比如用static hashMap基于内存缓存的jvm内置缓存,简单不实用,保对象的有效性和周期无法控制,容易造成内存急剧上升。常用的有Oscache(主要针对jsp页面),Ehcache(主要针对数据库访问层),Jcache,Jbosscache等等很多
缺点:容易内存溢出、没有持久化(服务重启后丢失)、线程安全、多个服务器(多个jvm)之间的数据不能共享。
1.2 java操作eache
利用spring Boot搭建应用(可参考这里的缓存配置)。
访问http://localhost:8080/getUser?name=springboot2.2后查询后返回
-
{
-
id: 35,
-
name: "springboot2.2",
-
age: 99
-
}
修改数据库中age字段后再次访问http://localhost:8080/getUser?name=springboot2.2后结果不变?代表使用了缓存,成功配置了缓存。
访问http://localhost:8080/removeCache清除缓存后,再次访问http://localhost:8080/getUser?name=springboot2.2后结果就为修改后的值。
原理?
1、当客户端请求数据时,如果服务器端配置了缓存,第一步去缓存里面查找,如果有跳4,没有则往2
2、发送jdbc请求操作数据库查询数据
3、将查询到的数据返回给缓存,并保存在缓存中
4、将从(缓存|数据库)查询到的数据返回给客户端。
好处?效率高(因为不用建立jdbc连接等)、降低了数据库的压力(不用每次请求都要去数据库查,数据库也会累啊)。
缺点?从上述也看到了,有可能产生数据不一致的情况,清除缓存可解决。
和oscache区别? ehcache 主要是对数据库访问的缓存,相同的查询语句只需查询一次数据库,从而提高了查询的速度,使用spring的AOP可以很容易实现这一功能。 oscache 主要是对页面的缓存,可以整页或者指定网页某一部分缓存,同时指定他的过期时间,这样在此时间段里面访问的数据都是一样的。
2 Redis
关系型数据库:持久、主外键、编写SQL语句、存放在硬盘。
非关系型数据库:一般用于缓存、值存放在内存(所以效率是真的高)、key-vakye形式、容易数据丢失(不过很好解决)、有点小类似jvm内置缓存(不过这个更牛嗨,因为可以多个服务器间共享数据)。
redis?可以持久化mongdb?存储json格式
2.1 Redis概述
完全开源免费、最受BSD协议、高性能的key-value费关系型数据库。支持持久化、支持key-value(String)\list\set\zert\hash等数据结构的存储、支持备份
好处?减轻数据库访问的压力。效率高?(访问内存肯定比访问硬盘快,这是常识)
应用场景?(token生成、session共享、分布式锁、验证码、自增id(订单id))
2.2 安装redis
2.2.1 windows安装redis
1、下载redis
2、解压redis-latest-windws.zip文件,将start.bat文件拷贝纸redis目录
3、编辑redis.windows.conf文件,取消requirepass的注释(前面不能有空格),空格然后添加密码比如(123456)保存。以requirepass 123456为例
4、双击start.bat运行
5、通过这个客户端工具测试一下
2.2.2 linux安装redis
1、下载redis
2、mkdir -p /usr/local/redis/bin,mkdir -p /usr/local/redis/etc
3、copy redis-3.0.0.tar.gz到用户目录比如/root
4、解压tar -zxvf redis-3.0.0.tar.gz
5、cd redis-3.0.0/后,make一下
6、进入src目录make install后就安装成功了。
7、cd cd /root/redis-3.0.0/(redis安装目录)
8、cp redis.conf /usr/local/redis/etc
9、cd src
10、cp mkreleasehdr.sh redis-benchmark redis-check-aof redis-check-dump redis-cli redis-server redis-sentinel /usr/local/redis/bin
11、修改 redis.conf文件
daemonize yes --- 修改为yes 后台启动
requirepass 123456 ----注释取消掉设置账号密码
ps aux | grep '6379' --- 查询端口
kill -15 9886 --- 杀死重置
kill -9 9886 --- 强制杀死
service iptables stop 停止防火墙
12、cd /usr/local/redis/bin
./redis-server /usr/local/redis/etc/redis.conf启动服务
13、./redis-cli -h 127.0.0.1 -p 6379 -a "123456" --- redis 使用账号密码连接或者windows下的客户端工具进行连接测试
PING 结果表示成功
14、停止redis
redis-cli shutdown 或者 kill redis进程的pid
15、放开一下端口,好像外部即使注释掉bind 127.0.0.1也不能访问/sbin/iptables -I INPUT -p tcp --dport 6379 -j ACCEPT
2.3 redis基本数据类型
2.3.1 字符串
-
127.0.0.1:6379> set name raolei
-
OK
-
127.0.0.1:6379> set itboy www.itboy.com
-
OK
-
127.0.0.1:6379> get name
-
"raolei"
-
127.0.0.1:6379> get itboy
-
"www.itboy.com"
常用命令:
编号 | 命令 | 描述说明 |
1 | 此命令设置指定键的值。 | |
2 | 获取指定键的值。 | |
3 | 获取存储在键上的字符串的子字符串。 | |
4 | 设置键的字符串值并返回其旧值。 | |
5 | 返回在键处存储的字符串值中偏移处的位值。 | |
6 | 获取所有给定键的值 | |
7 | 存储在键上的字符串值中设置或清除偏移处的位 | |
8 | 使用键和到期时间来设置值 | |
9 | 设置键的值,仅当键不存在时 | |
10 | 在指定偏移处开始的键处覆盖字符串的一部分 | |
11 | 获取存储在键中的值的长度 | |
12 | 为多个键分别设置它们的值 | |
13 | 为多个键分别设置它们的值,仅当键不存在时 | |
14 | 设置键的值和到期时间(以毫秒为单位) | |
15 | 将键的整数值增加 | |
16 | 将键的整数值按给定的数值增加 | |
17 | 将键的浮点值按给定的数值增加 | |
18 | 将键的整数值减 | |
19 | 按给定数值减少键的整数值 | |
20 | 将指定值附加到键 |
2.3.2 list
-
127.0.0.1:6379> lpush listkey redis
-
(integer) 1
-
127.0.0.1:6379> lpush listkey mysql
-
(integer) 2
-
127.0.0.1:6379> lpush listkey mongdb
-
(integer) 3
-
127.0.0.1:6379> lpush listkey hbase
-
(integer) 4
-
127.0.0.1:6379> lrange listkey 0 10
-
1) "hbase"
-
2) "mongdb"
-
3) "mysql"
-
4) "redis"
list是简单的字符串列表,按照插入顺序排序,可在头和尾插入,最多包含2^32-1个元素(40多亿)。
常用命令:
序号 | 命令及描述 |
1 | BLPOP key1 [key2 ] timeout |
2 | BRPOP key1 [key2 ] timeout |
3 | BRPOPLPUSH source destination timeout |
4 | LINDEX key index |
5 | LINSERT key BEFORE|AFTER pivot value |
6 | LLEN key |
7 | LPOP key |
8 | LPUSH key value1 [value2] |
9 | LPUSHX key value |
10 | LRANGE key start stop |
11 | LREM key count value |
12 | LSET key index value |
13 | LTRIM key start stop |
14 | RPOP key |
15 | RPOPLPUSH source destination |
16 | RPUSH key value1 [value2] |
17 | RPUSHX key value |
2.3.3 set
的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
哈希表实现。添加,删除,查找的复杂度都是O(1)。每个集合可存储40多亿个成员
-
127.0.0.1:6379> sadd setkey redis
-
(integer) 1
-
127.0.0.1:6379> sadd setkey redis
-
(integer) 0
-
127.0.0.1:6379> sadd setkey redis
-
(integer) 0
-
127.0.0.1:6379> sadd setkey redis
-
(integer) 0
-
127.0.0.1:6379> sadd setkey redis
-
(integer) 0
-
127.0.0.1:6379> sadd setkey mysql
-
(integer) 1
-
127.0.0.1:6379> sadd setkey mongdb
-
(integer) 1
-
127.0.0.1:6379> SMEMBERS setkey
-
1) "mysql"
-
2) "mongdb"
-
3) "redis"
常用命令:
序号 | 命令及描述 |
1 | SADD key member1 [member2] |
2 | SCARD key |
3 | SDIFF key1 [key2] |
4 | SDIFFSTORE destination key1 [key2] |
5 | SINTER key1 [key2] |
6 | SINTERSTORE destination key1 [key2] |
7 | SISMEMBER key member |
8 | SMEMBERS key |
9 | SMOVE source destination member |
10 | SPOP key |
11 | SRANDMEMBER key [count] |
12 | SREM key member1 [member2] |
13 | SUNION key1 [key2] |
14 | SUNIONSTORE destination key1 [key2] |
15 |
2.3.4 sorted set
与set一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
-
127.0.0.1:6379> zadd zsetkey 1 redis
-
(integer) 1
-
127.0.0.1:6379> zadd zsetkey 2 mysql
-
(integer) 1
-
127.0.0.1:6379> zadd zsetkey 2 mongdb
-
(integer) 1
-
127.0.0.1:6379> smembers setkey
-
1) "mysql"
-
2) "mongdb"
-
3) "redis"
常用命令:
2.3.5 hash
是一个string类型的field和value的映射表(和map差不多,只是兼职都是字符串),hash特别适合用于存储对象。Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
-
127.0.0.1:6379> hmset hmset name "redis tutorial"
-
OK
-
127.0.0.1:6379> hmset hmset age 24
-
OK
-
127.0.0.1:6379> hgetall hmset
-
1) "name"
-
2) "redis tutorial"
-
3) "age"
-
4) "24"
序号 | 命令及描述 |
1 | HDEL key field2 [field2] |
2 | HEXISTS key field |
3 | HGET key field |
4 | HGETALL key |
5 | HINCRBY key field increment |
6 | HINCRBYFLOAT key field increment |
7 | HKEYS key |
8 | HLEN key |
9 | HMGET key field1 [field2] |
10 | HMSET key field1 value1 [field2 value2 ] |
11 | HSET key field value |
12 | HSETNX key field value |
13 | HVALS key |
14 | HSCAN key cursor [MATCH pattern] [COUNT count] |
redis怎么存放对象?通过将对象序列化为json字符串,存放为字符串形式,之后读取在反序列化为对象,这样很快很快
2.4 Spring Boot集成redis
添加依赖:
-
<dependency>
-
<groupId>org.springframework.boot</groupId>
-
<artifactId>spring-boot-starter-data-redis</artifactId>
-
</dependency>
-
<dependency>
-
<groupId>org.springframework.boot</groupId>
-
<artifactId>spring-boot-starter-web</artifactId>
-
</dependency>
-
<!--spring2.0集成redis所需common-pool2-->
-
<dependency>
-
<groupId>org.apache.commons</groupId>
-
<artifactId>commons-pool2</artifactId>
-
<version>2.4.2</version>
-
</dependency>
配置文件:
-
########################################################
-
###Redis (RedisConfiguration)
-
########################################################
-
spring:
-
redis:
-
database: 0
-
host: 192.168.245.134
-
port: 6379
-
password: 123456
-
jedis:
-
pool:
-
max-idle: 8
-
min-idle: 0
-
max-active: 8
-
max-wait: -1ms
-
timeout: 5000ms
service:
-
@Service
-
public class RedisService {
-
@Autowired
-
private StringRedisTemplate stringRedisTemplate;
-
//字符串
-
public void setStringKey(String key,String value,Long time){
-
setObject(key,value,time);
-
}
-
public void setStringKey(String key,String value){
-
setObject(key,value,null);
-
}
-
//set
-
public void setSetKey(String key,Set value){
-
setObject(key,value,null);
-
}
-
//list
-
public void setListKey(String key,List value){
-
setObject(key,value,null);
-
}
-
public String getStringKey(String key){
-
return (String) getObject(key,new String());
-
}
-
public Set getSetKey(String key){
-
return (Set) getObject(key,new HashSet<String>());
-
}
-
public List getListKey(String key){
-
return (List) getObject(key,new ArrayList<String>());
-
}
-
public void setObject(String key,Object value,Long time){
-
if(StringUtils.isEmpty(key)||value==null){
-
return;
-
}
-
//字符串类型
-
if(value instanceof String){
-
String value1= (String) value;
-
if(time!=null){
-
stringRedisTemplate.opsForValue().set(key,value1,time,TimeUnit.SECONDS);
-
} else{
-
stringRedisTemplate.opsForValue().set(key,value1);
-
}
-
return;
-
}
-
//list类型
-
else if(value instanceof List){
-
List<String> list= (List<String>) value;
-
for (String s:list) {
-
stringRedisTemplate.opsForList().leftPush(key,s);
-
}
-
return;
-
}
-
//set
-
else if(value instanceof Set){
-
Set<String> strings= (Set<String>) value;
-
for (String s : strings) {
-
stringRedisTemplate.opsForSet().add(key,s);
-
}
-
return;
-
}
-
/**
-
* .....
-
*/
-
}
-
public Object getObject(String key,Object object){
-
if(StringUtils.isEmpty(key)||object==null){
-
return null;
-
}
-
else if (object instanceof String){
-
return stringRedisTemplate.opsForValue().get(key);
-
}
-
else if(object instanceof List){
-
return stringRedisTemplate.opsForList().range(key,0,stringRedisTemplate.opsForList().size(key));
-
}
-
else if(object instanceof Set){
-
return stringRedisTemplate.opsForSet().members(key);
-
}
-
return null;
-
}
-
}
controller:
-
@RestController
-
public class IndexController {
-
@Autowired
-
private RedisService redisService;
-
@RequestMapping("/setString")
-
public String setString(@PathParam("key") String key,
-
@PathParam("value") String value){
-
redisService.setStringKey(key,value);
-
return redisService.getStringKey(key);
-
}
-
@RequestMapping("/setSet")
-
public Set<String> setSet(@PathParam("key") String key,
-
@PathParam("value") String value){
-
HashSet<String> strings = new HashSet<>();
-
strings.add(value);
-
strings.add(value+"1");
-
strings.add(value+"2");
-
strings.add(value+"3");
-
redisService.setSetKey(key,strings);
-
return redisService.getSetKey(key);
-
}
-
@RequestMapping("/setList")
-
public List<String> setList(@PathParam("key") String key,
-
@PathParam("value") String value){
-
ArrayList<String> strings = new ArrayList<>();
-
strings.add(value);
-
strings.add(value+"1");
-
strings.add(value+"2");
-
strings.add(value+"3");
-
redisService.setListKey(key,strings);
-
return redisService.getListKey(key);
-
}
-
}
访问http://localhost:8080/setSet?key=itboySet&value=123456,http://localhost:8080/setString?key=itboySet&value=123456,
http://localhost:8080/setList?key=itboySet&value=123456进行测试一下是否成功!!!!!!!!
2.5 主从复制和哨兵机制理解
为什么?数据备份、读写分离、集群、高可用(宕机容错机制)。
一般情况下,Redis高可用都是一主多从,而不像其他比如Nginx多主多从。
所谓主从复制,主要是为了减轻单台服务器的压力(比如图中的master),通过多台服务器的冗余来保证高可用(单台宕机容错),实现读写分离、数据备份、集群等。
如图,其中master可读可写,但是当有客户端连接达到集群时,如果是读操作就从slave从节点中随机选择一台服务器进行响应,如果是写操作,那么操作主服务器。这就是读写分离了不是吗。。。
问题?主服务器写之后,怎么同步到从服务器?(主从复制搞定,往下看)
问题?主服务器宕机了,怎么写?通过哨兵机制,哨兵其实就是一个监听器,一直监听这主服务器,如果主服务器挂了,他就会使用投票(随机)从主服务器中选择一台服务器作为主服务器,此乃高可用。
问题?那如果整个集群挂了呢?有一个东西叫做keepalived监听器(其实就是一个shell写的重启服务的命令脚本),如果监听到某一台服务器挂了,他就会自动重启的(一般30秒内,听说可能不准确)。如果一直启动失败?那就没办法了,他只能发送一封邮件给运维人员了。
2.5.1 主从复制实现
原理:通过快照文件(类似mysql的二进制可执行文件),当master有更新时,从服务器slave会实时请求得到该快照文件,进行执行,如果网络问题?别担心过,会重试的。
过程:
1:当一个从数据库启动时,会向主数据库发送sync命令,
2:主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来
3:当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。
4:从数据库收到后,会载入快照文件并执行收到的缓存的命令。
对于redis服务器来说,和mysql有很大不同,只要设置好主从服务器之后,主服务器master可读可写,从服务器slave仅可读,不像mysql那样需要分配用户和mycat插件来控制读写分离。
配置过程:
1、准备服务器,如上图中三台服务器(192.168.245.134,192.168.245.135,192.168.245.136),选择一台为主服务器master(这台服务器什么也不用做)
2、所以redis主从复制只需要配置从服务器slave就OK,修改redis.conf配置文件,放开以下两行的注释,添加如下内容。两台从服务器都要修改。
-
slaveof 192.168.245.134 6379
-
#主服务器的ip和端口号
-
# If the master is password protected (using the "requirepass" configuration
-
# directive below) it is possible to tell the slave to authenticate before
-
# starting the replication synchronization process, otherwise the master will
-
# refuse the slave request.
-
#
-
masterauth 123456
-
#主服务器的认证密码
3、测试
-
#进入master主服务器
-
192.168.245.134:6379> info
-
#回车后看到如下内容即代表成功
-
# Replication
-
role:master
-
connected_slaves:2 #两台从服务器
-
slave0:ip=192.168.245.135,port=6379,state=online,offset=127,lag=1 #一些描述信息
-
slave1:ip=192.168.245.136,port=6379,state=online,offset=127,lag=1 #一些描述信息
-
#进入任何一台服务器比如135,同样info以下,看到如下内容即代表成功。
-
# Replication
-
role:slave #角色从服务器
-
master_host:192.168.245.134 #主服务器ip
-
master_port:6379 #主服务端口
-
master_link_status:up #状态
-
master_last_io_seconds_ago:10
-
master_sync_in_progress:0
-
#主服务器
-
192.168.245.134:6379> set master "192.168.245.134"
-
OK
-
192.168.245.134:6379> get master
-
"192.168.245.134"
-
192.168.245.134:6379>
-
##可读可写是吧???????
-
#########刚刚设置的这条数据从服务器有吗??????############
-
192.168.245.135:6379> get master
-
"192.168.245.134"
-
192.168.245.135:6379> set slave "192.168.245.135"
-
(error) READONLY You can't write against a read only slave.
-
192.168.245.135:6379>
-
############哟呵有数据的,主从复制成功,主从间数据同步问题解决,而且不能写,读写分离也搞定了########
-
[root@localhost bin]# ./redis-cli -h 192.168.245.136 -p 6379 -a "123456"
-
192.168.245.136:6379> ping
-
PONG
-
192.168.245.136:6379> get master
-
"192.168.245.134"
-
192.168.245.136:6379> set slave "192.168.245.136"
-
(error) READONLY You can't write against a read only slave.
-
192.168.245.136:6379>
-
################另一台从服务器也一样######################
2.5.2 哨兵机制实现
原理:哨兵用于管理多个redis服务器,执行以下三个任务:
1、监控(monitoring):哨兵(sentinel)会不断检查你的master和slave是否运作正常
2、提醒(notification):当被监控的某个redis出现问题时,哨兵(sentinel)可以通过API向管理员或者其他应用程序发送通知
3、自动故障迁移(automatic failover):当一个master1不能正常工作时,哨兵会开始一次自动故障迁移操作,他将会失效master1的其中一个slave升级为新的master2,并让失效master1的其他slave的master1改为新的master2。当客户端试图连接失效的master1时,集群也会向客户端返回新的master2地址,使得集群可以使用新的master2代替失效的master1
哨兵是一个分布式系统,可以在一个架构中运行多个哨兵进程,这些进程使用流言协议(gossipprotocols)来接收关于master是否下线的消息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移以及选择哪个slave作为新的master。
每个哨兵会向其他哨兵(sentinel)、master、slave定时发送消息,来确认对方是否还活着,如果对方在指定的时间(可配置)内未响应,则暂时认为对方已挂(主观认为宕机,Subjective Down,sdown)
若哨兵群中的多数sentinel都报告某一个master没响应,系统认为该master彻底死亡(客观真正的宕机,Objective Down,oDwon),通过一定vote算法,从生下的slave节点中选择提升一台为master,然后自动修改相关配置
虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis 服务器,你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵(sentinel).
实现:
这里以上面三台服务器为基础,选择192.168.245.136这台服务器为哨兵(可以选择多台,此处仅一台为例)。
注意:如果主从复制时,主服务器没有配置masterauth 123456请加上,这个坑了我很久很久,导致哨兵时一直连不上,最后查看日志才搞定,记得先将这个加到主服务器的redis.conf中,然后重启一下
1、修改redis安装目录下的sentinel.conf
-
sentinel monitor mymaster 192.168.245.134 6379 1
-
# 主节点名称 主机ip 端口号 选举次数(就是说当有几台sentinel认为master挂了才是真的挂了,因为这里只有一个哨兵,所以为1)
-
sentinel down-after-milliseconds mymaster 30
-
#就是说多少ms后没有给老子响应,老子就觉得你挂了。,默认30s,这里设置为30ms,本地测试追求实时
-
sentinel config-epoch mymaster 1
-
#这个数字表示在发生主从复制的时候,比如master1向master2切换时,可以同时有多少个slave能对master2执行同步(也就是复制其实),越多越好?>如果太多了那么大家都去复制了,谁来响应客户端的请求?太少?太少的话,每个都要来一遍,怕是要到天黑哦,根据实际情况吧,这里只有三台所以
-
为设为1.
-
sentinel auth-pass mymaster 123456
-
#主服务器密码
2、启动哨兵:nohup ./redis-server ../etc/sentinel.conf --sentinel 2>1 1>nohup.log &
3、如何停止ps -aux | grep 端口号,kill -9 pid即可
4、测试?
-
#我们首先查看master(134)的info master
-
role:master
-
connected_slaves:2
-
slave0:ip=192.168.245.135,port=6379,state=online,offset=5349,lag=1
-
slave1:ip=192.168.245.136,port=6379,state=online,offset=5349,lag=0
-
#135的 slave
-
role:slave
-
master_host:192.168.245.134
-
master_port:6379
-
master_link_status:up
-
#136 的 slave
-
role:slave
-
master_host:192.168.245.134
-
master_port:6379
-
master_link_status:up
-
#我现在讲master停掉? master
-
#134
-
192.168.245.134:6379> shutdown
-
not connected>
-
#135 的info replication
-
192.168.245.135:6379> info replication
-
# Replication
-
role:slave
-
master_host:192.168.245.136
-
master_port:6379
-
master_link_status:up
-
#看到主服务器变为136,
-
#136?
-
192.168.245.136:6379> info replication
-
# Replication
-
role:master
-
connected_slaves:2
-
slave0:ip=192.168.245.135,port=6379,state=online,offset=739,lag=0
-
slave1:ip=192.168.245.134,port=6379,state=online,offset=739,lag=0
-
#基本上成功了,最后试试读写分离以及在测试一些其他的,这里不再展示
2.6 数据持久化
数据持久化:就是将内存中的数据保存到硬盘,redis支持AOF和RDB两种存储方式
2.6.1 RDB存储
RDB是指在一个时间点,如果达到所配置的数据修改量,就写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。(二进制文件方式)
有两种保存方式:1、(阻塞)主进程直接拍快照(snapshot),然后阻塞客户端请求写入IO磁盘。2、(非阻塞)当要写入磁盘时,新建(fork)一个子进程,子进程将当前数据库快照写入磁盘,而主进程继续处理客户端请求。
每次快照持久化都是将内存数据完整写入到磁盘一次,并不 是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。
优点:使用单独子进程进行持久化,主进程不会进行任何IO操作,保证了redis的高性能
缺点:RDB需要间隔一段时间进行持久化,而且必须达到相应修改数量,所以如果持久化之间发生故障,会造成数据丢失,常用于数据要求不严谨的时候。
配置:
-
#dbfilename:持久化数据存储在本地的文件
-
dbfilename dump.rdb
-
#dir:持久化数据存储在本地的路径,如果是在/redis/redis-3.0.6/src下启动的redis-cli,则数据会存储在当前src目录下
-
dir ./
-
##snapshot触发的时机,save
-
##如下距离上一次持久化已经900s了,如果有大于等于1个变更,才会snapshot
-
save 900 1
-
##对于此值的设置,需要谨慎,评估系统的变更操作密集程度
-
##可以通过“save “””来关闭snapshot功能
-
save 300 10 #表示间隔达到300s时如果更改了10次以上,就snapshot,如果你在10s时已经10次了,立马持久化
-
save 60 10000 #同理间隔达到60s时如果更改了10000次以上,就snapshot,如果你在10s时已经10000次了,立马持久化
-
##当snapshot时出现错误无法继续时,是否阻塞客户端“变更操作”,“错误”可能因为磁盘已满/磁盘故障/OS级别异常等
-
stop-writes-on-bgsave-error yes
-
##是否启用rdb文件压缩,默认为“yes”,压缩往往意味着“额外的cpu消耗”,同时也意味这较小的文件尺寸以及较短的网络传输时间
-
rdbcompression yes
这里我将save 300 10改为save 30 10进行测试一下(首先先停掉哨兵机制,不然宕机后他就重新选一台主的了):
改好后重启一下服务:
-
进行5次更改操作
-
192.168.245.136:6379> get name
-
"1"
-
192.168.245.136:6379> set name 1
-
OK
-
192.168.245.136:6379> set name 2
-
OK
-
192.168.245.136:6379> set name 3
-
OK
-
192.168.245.136:6379> set name 4
-
OK
-
192.168.245.136:6379> set name 5
-
OK
-
192.168.245.136:6379> get name
-
"5"
-
#立马kill掉redis进程,一定要kill如果主动关闭服务,他是会进行snapshot的
-
[root@localhost bin]# ps -aux | grep 6379
-
root 1572 0.1 0.9 140840 9640 ? Ssl 02:02 0:00 ./redis-server *:6379
-
root 1577 0.0 0.5 20160 5184 pts/0 S+ 02:02 0:00 ./redis-cli -h 192.168.245.136 -p 6379 -a 123456
-
root 1580 0.0 0.0 112676 984 pts/1 R+ 02:03 0:00 grep --color=auto 6379
-
[root@localhost bin]# kill -9 1572
-
#重启服务
-
[root@localhost bin]# ./redis-server ../etc/redis.conf
-
[root@localhost bin]# ./redis-cli -h 192.168.245.136 -p 6379 -a "123456"
-
192.168.245.136:6379> get name
-
"1"
-
192.168.245.136:6379>
-
#可以看到我最后的是“5”,而这里是“1”,数据丢失了##########################
-
#更改10次呢?,是成功进行持久化了的,这里不展示了,篇幅过大。
-
#而且如果你将dump.rdb文件删除后,达到snapshot条件时,会自动创建一个新的文件,持久化其实就是将该文件备份,下次将那些持久化后的文件再放过来不就达到数据恢复了吗????????????
2.6.2 AOF存储
以日志文件方式存储,其实就是将你“操作+数据”指令格式化后追加到操作日志文件的尾部,必须append(已经写入到文件或者即将写入),才会进行数据的实际变更。“日志文件”保存了历史所有的操作过程;当 server 需要数据恢复时,可以直接 replay 此日志文件,即可还原所有的操作过程。内容是字符串,容易阅读和解析。
缺点:AOF 文件比 RDB 文件大,且恢复速度慢。
只会记录“变更操作”(例如:set/del 等),如果 server 中持续的大量变更操作,将会导致 AOF 文件非常的庞大,意味着 server 失效后,数据恢复的过程将会很长;事实上,一条数据经过多次变更,将会产生多条 AOF 记录,其实只要保存当前的状态,历史的操作记录是可以抛弃的;因为 AOF 持久化模式还伴生了“AOF rewrite”。
因为最多丢失最后一次写入文件的数据,所以很好修复,直接手工更改文件或者重新来一次即可。
修改redis.conf文件:
-
##此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能
-
##只有在“yes”下,aof重写/文件同步等特性才会生效
-
appendonly yes
-
##指定aof文件名称
-
appendfilename appendonly.aof
-
##指定aof操作中文件同步策略,有三个合法值:always everysec no,默认为everysec每秒
-
appendfsync everysec
-
##在aof-rewrite期间,appendfsync是否暂缓文件同步,"no"表示“不暂缓”,“yes”表示“暂缓”,默认为“no”
-
no-appendfsync-on-rewrite no
-
##aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默认“64mb”,建议“512mb”
-
auto-aof-rewrite-min-size 64mb
-
##相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比。
-
##每一次rewrite之后,redis都会记录下此时“新aof”文件的大小(例如A),那么当aof文件增长到A*(1 + p)之后
-
##触发下一次rewrite,每一次aof记录的添加,都会检测当前aof文件的尺寸。
-
auto-aof-rewrite-percentage 100
重启服务后,根据你启动redis所在目录下出现appendonly.aof文件。
执行以下操作:
-
192.168.245.136:6379> set name 124
-
OK
-
192.168.245.136:6379> set name 145
-
OK
-
192.168.245.136:6379> set age 56
-
OK
-
192.168.245.136:6379> del age
-
(integer) 1
-
192.168.245.136:6379> get name
-
"145"
查看aof文件内容:
-
[root@localhost bin]# cat appendonly.aof
-
*2
-
$6
-
SELECT
-
$1
-
0
-
*3
-
$3
-
set
-
$4
-
name
-
$3
-
124
-
*3
-
$3
-
set
-
$4
-
。。。
查询的是不会出现在里面,而且这个文件是动态的。。看起来挺好的,就是频繁的更改造成文件过大,到时候恢复起来有点太慢了,有人说那么RDB中将时间改快点要求改小点?大哥,人家是全量复制,那岂不是时间都花去复制了,还处理什么请求?这个虽然慢,可是是追加方式啊,将就了。
2.6.3 redis宕机后,值会失效吗?
不会,redis默认开启RDB存储,如果是直接关闭服务,那么会自动备份,如果是kill或者断电如果没达到RDB配置的要求则不会持久化,而且这个虽然是非阻塞的,但是毕竟全量复制啊,保证了redis的性能,但是CPU可受不了啊。因此实际情况中,最好采用AOP方式,实时而且快,但是就是容易造成文件过大,恢复困难。
2.7 redis事务
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
multi 开启事务 exec提交事务。
序号 | 命令及描述 |
1 | DISCARD |
2 | EXEC |
3 | MULTI |
4 | UNWATCH |
5 | WATCH key [key ...] |
2.8 发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。Redis 客户端可以订阅任意数量的频道
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
例子:
1、创建订阅频道名为 redisChatSimple
-
192.168.245.136:6379> subscribe redsiChatSample
-
Reading messages... (press Ctrl-C to quit)
-
1) "subscribe"
-
2) "redsiChatSample"
2、重新开个客户端,同一频道发布消息
-
192.168.245.136:6379> publish redsiChatSample "gogog"
-
(integer) 1
-
192.168.245.136:6379> publish redsiChatSample "gogog22"
-
(integer) 1
-
192.168.245.136:6379>
3、客户端显示如下信息:
-
192.168.245.136:6379> subscribe redsiChatSample
-
Reading messages... (press Ctrl-C to quit)
-
1) "subscribe"
-
2) "redsiChatSample"
-
3) (integer) 1
-
1) "message"
-
2) "redsiChatSample"
-
3) "gogog"
-
1) "message"
-
2) "redsiChatSample"
-
3) "gogog22"
厉害了!!!
序号 | 命令及描述 |
1 | PSUBSCRIBE pattern [pattern ...] |
2 | PUBSUB subcommand [argument [argument ...]] |
3 | PUBLISH channel message |
4 | PUNSUBSCRIBE [pattern [pattern ...]] |
5 | SUBSCRIBE channel [channel ...] |
6 | UNSUBSCRIBE [channel [channel ...]] |
显示推荐内容