文章目录
Redis教程
1.mysql数据库和nosql数据库的数据一致性
ACID 定理
CAP 定理
数据类型的不同:
mysql 用ER图
NOSQL BSON 其实就是json来描述关系;
高并发的操作是不太建议有关联查询的,互联网公司用冗余数据来避免关联查询
分布式事务是支持不了太多的并发的。
OA,CRM系统是强一致性,事务的提交是准确、快速,完整的,早晨打卡,下午没有打卡,必须一致。
传统是使用join进行多张表的联合查询;
分布式不需要join,直接查询出json的字符串,有冗余,不需要进行多张表的联合查询。
NOSQL :采用聚合模型 KV键值,BSON,列族,图形,主要是前面两个;
安装redis 依赖gcc来编译,因为redis是c语言开发的。运行redis的test的时候需要安装tcl插件;
yum
wget:外网上之间下载。
安装的软件都在bin目录下;
行末:shift+$
查看端口号是否被占用:netstat -anp|grep 6379
查看redis是否正在运行:ps -ef|grep redis
在文章中查找固定单词:/abc
2.redis的数据类型
2.1 五大数据类型
名称 | 说明 | 举例 |
---|---|---|
String | 二进制安全的 | 图片,视频等等 |
Hash(哈希) | Redis hash是一个键值对集合,redis hash是一个String类型的field和value的映射表,hash特别适合用于存储对象。 | 类似java里面的Map<String,Object> |
List列表 | Redis列表是简单的字符串列表,按照插入顺序排序,是LinkedList | 有序有重复 |
set集合 | Redis的Set是string类型的无序集合。它是通过HashTable实现的。 | 无序无重复 |
sorted set | Redis zset 和set一样也是string类型元素的集合,且不允许重复的成员,不同的是每个元素都关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序 | zset的成员是唯一的,但分数却可以重复 |
关于Zset的分数说明:游戏中排序的分数,全区多少名,排多少个人等等。
2.2常用命令(重点):
String常用命令:
List常用命令:
***看别人怎么给方法类命名,可以查看源代码中和一些项目中别人的写法,记录一些。
set的常用命令:
去重、随机等等
hash的常用命令:
kv模式不变,但v是一个键值对;
hset/hget/hmget/hgetall/hgel
java、mybatis、redis、底层基本都是map;
2.3 Redis 配置文件
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
569 # is reached. You can select among five behaviors:
570 #
571 # volatile-lru -> Evict using approximated LRU among the keys with an expire set.
572 # allkeys-lru -> Evict any key using approximated LRU.
573 # volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
574 # allkeys-lfu -> Evict any key using approximated LFU.
575 # volatile-random -> Remove a random key among the ones with an expire set.
576 # allkeys-random -> Remove a random key, any key.
577 # volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
578 # noeviction -> Don't evict anything, just return an error on write operations.
缓存策略:如上
2.4 Redis 的持久化
2.4.1RDB redis database
rdb:redis database
aof:append of file
默认有16个库,从0-15编号,默认是9号库。
rdb:在指定的时间间隔内将内存中的数据集快照写入磁盘,
也就是行话说的snapshot快照,它恢复是将快照文件直接读回到内存。
Redis会单独创建(fork)一个子进程进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
整个过程中,主进程不进行任何IO操作的,这就确保了极高的性能,如果需要大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
RDB的缺点是最后一次持久化后的数据可能丢失。
5分钟备份一次,结果最后一次出问题了,就会数据丢失;
fork:复制一份与当前进程一样的进程,可能导致系统资源更紧张。
RDB:保存的是dump.rdb文件。
flushALL:刷新现有的数据,导致没有了数据,会进行备份
SHUTDOWN吗,命令同上
sava:如果有一个特别重要的值 k100 100 ,需要及时的保存,直接输入sava会进行备份。
save只管保存,不管其他,如:夜间运维进行备份
bgsave:redis会在后台异步进行快照操作,快照同时还可以响应客户端的请求。可以通过lastsave命令获取最后一次成功执行快照的时间。
flushall:命令也产生dump.rdb文件,但里面是空的,无意义。
#
198 # Save the DB on disk:
199 #
200 # save <seconds> <changes>
201 #
202 # Will save the DB if both the given number of seconds and the given
203 # number of write operations against the DB occurred.
204 #
205 # In the example below the behaviour will be to save:
206 # after 900 sec (15 min) if at least 1 key changed
207 # after 300 sec (5 min) if at least 10 keys changed
208 # after 60 sec if at least 10000 keys changed
209 #
210 # Note: you can disable saving completely by commenting out all "save" lines.
211 #
212 # It is also possible to remove all the previously configured save
213 # points by adding a save directive with a single empty string argument
214 # like in the following example:
215 #
216 # save ""
217
218 save 900 1
219 save 300 10
220 save 60 10000
启动redis
[root@hadoop1 bin]# redis-server /myredis/redis.conf
3301:C 21 Aug 2019 11:04:53.033 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
3301:C 21 Aug 2019 11:04:53.034 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=3301, just started
3301:C 21 Aug 2019 11:04:53.034 # Configuration loaded
[root@hadoop1 bin]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>
缺点:最后一次没有办法备份;fork导致资源使用紧张。
[外链图片转存失败(img-YdChGDnJ-1568889418414)(RDB总结.jpg)]
2.4.2 AOF(Append Only File)
类似备份了mysql的数据到本地的数据文件中。
appendonly.aof
和dump.rdb可以共存。启动的时候去找appendonly.aof
如果appendonly.aof出现问题,网络延迟,丢包,出现错误之后,直接可以修复。
修复appendonly.aof
redis-check-aof --fix appendonly.aof
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
使用AOF的策略是什么?
Appendfsync:备份策略
Always: 同步持久化 每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好
everysec:每秒同步,异步操作,如果一秒内宕机,有数据丢失。
no:不同步
AOF启动、修复、恢复
正常恢复:
1。启动,设置配置文件中appendonly=yes,
2.将有数据文件的aof复制一份保存到对应的目录(config get dir)
3.恢复:重启redis然后重新加载
异常恢复:
1.启动,设置配置文件中appendonly=yes
2.将有数据的aof文件复制一份
3.修复被破坏的aof文件,使用命令:redis-check-aof --fix 进行修复
4.重启redis然后进行重新加载恢复。
rewrite 重写
1.概念:
appendonly 文件越来越大,因为文件是一直追加的。
当超过设置的阈值的时候,就会启动aof文件的内容压缩。
只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof.
2.重写原理
AOF文件持续增长而过大时,会fork出一条心进程来讲文件重写,(也是先写临时文件,最后再rename)
遍历新进程的内存中数据,每条记录有一条的set语句,重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存 中的数据库内容用命令的方式重写了一个aof文件,这点和快照有点像。
3.触发机制
redis会记录上次重写时的AOF大小,默认配饰是当AOF文件大小是上次rewrite后大小的一倍且文件大小大于64M时触发。日志一般会很多的,3G是起步。
# Automatic rewrite of the append only file.
# Redis is able to automatically rewrite the log file implicitly calling
# BGREWRITEAOF when the AOF log size grows by the specified percentage.
#
# This is how it works: Redis remembers the size of the AOF file after the
# latest rewrite (if no rewrite has happened since the restart, the size of
# the AOF at startup is used).
#
# This base size is compared to the current size. If the current size is
# bigger than the specified percentage, the rewrite is triggered. Also
# you need to specify a minimal size for the AOF file to be rewritten, this
# is useful to avoid rewriting the AOF file even if the percentage increase
# is reached but it is still pretty small.
#
# Specify a percentage of zero in order to disable the automatic AOF
# rewrite feature.
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
总结
[外链图片转存失败(img-wjbgP7Eh-1568889418415)(AOF.jpg)]
官方建议:
RDB持久化方式能够在指定的时间间隔对你的数据进行快照存储
AOF持久化方式记录每次对服务器的写的操作,当服务器重启的是会重新执行这些命令来恢复原始的数据,AFO命令以redis协议追加保存每次写的操作到文件的末尾。
redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于太大。
只做缓存: 如果你只是希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式
同时开启两种持久化方式:
1)首先会加重AOF的持久化文件,如果有错,就会连接失败,可以修复后,再启动redis。因为AOF文件保存的数据要比RDB文件保存的数据更完整。
2)RDB的数据不实时,同时使用两者时服务器重启也会只找AOF文件。那要不要只使用AOF呢?
作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份)
快速重启,并且不会有AOF可能潜在的bug,留着作为一个万一的手段。
[外链图片转存失败(img-Vy8mrtDL-1568889418415)(性能建议.jpg)]
2.5 Redis的事务
1.概念
可以是一次执行多个命令,本质是一组命令的集合,一个事务中的所有命令都会序列化,按顺序串行化执行,而不会被其它命令打断,不许加塞。
2,能干什么
一个队列中,一次性、顺序性、排他性的执行一系列命令
3.怎么用:
常用命令:
DISCARD: 取消事务,放弃执行事务块内的所有命令
EXEC:执行所有事务块内的命令
MULTI 标记一个事务的开始
UNWATHCH :取消watch命令对所有key的监视
WATCH key[key…] :监视一个或多个key,如果事务执行之前这个key,被其它命令所改动,那么事务将被打断。
1.正常执行
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
127.0.0.1:6379>
2.放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 22
QUEUED
127.0.0.1:6379> set k3 33
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379>
3.全体连坐
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5
(nil)
127.0.0.1:6379> get k2
"v2"
4.冤头债主
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> set k2 22
QUEUED
127.0.0.1:6379> set k3 33
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) OK
redis 对事务的支持是部分性的。
watch监控:
悲观锁、乐观锁、CAS(compare and set)
悲观锁:
类似mysql的表锁,表的记录很少无所谓,如果有20w条数据。
并发性极差、一致性很好。
传统的数据库都是很多锁,如:行锁,表锁,读锁。写锁,如:备份数据库的时候很有用。
乐观锁(常用):在每行数据后面都加一个版本号,为了保证并发和一致性。
修改之前先查询出来数据,并找到version字段的值。
乐观锁策略:提交版本必须大于记录当前版本才能执行更新。
总结:watch是乐观锁。
watch指令,类似于乐观锁,事务提交时,如果key的值已经被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行;
通过watch命令在事务执行之前监控了多个key,倘若在watch之后有任何key的值发生变化,exec命令执行的事务都将被抛弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。
特性:
1.单独的隔离操作:事务中的所有命令都会被序列化、顺序地执行。事务在执行过程中,不会被其它客户端发送来的命令请求所打断
2.没有隔离级别的概念;队列中的命令在没有提交之前都不会被实际的执行,因为事务提交前任何指令都不会被实际执行,也就不存在“事务内的查询要看事务里的更新,在事务外查询不能看到”这个令人万分头疼的问题
3.不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚机制。
2.6 Redis的发布订阅
概念:类似于rabbitmq
2.7 Redis的主从复制
概念:
能干嘛:
怎么玩:
1.从机的复制,是从头开始的。
2.主机挂了,从机仍然是从机,主机恢复后还是保持原来的主机状态
3.从机挂了以后未恢复之前,主机写入的数据,从机不能读取,如果在配置文件中没有配置主从关系的话,从机重新启动后就会变成主机,该系统中就有了两个主机,可以手动指定slaveof 主机的ip+端口号如127.0.0.1 6379
复制原理:
哨兵模式:
从后台监控主机是否故障了,如果故障了,从从库中选出一个主库,是自动完成的。
如果master又回来了。选出来的master不会更改。
一组sentinel能通过时监控多个master。
工作中,一般由运维来完成这些工作。
复制的缺点:
复制的延时,写的都是在master,然后同步更新到slave,网络延迟产生
2.8 redis在编辑器中的使用
watch 命令就是标记一个键,如果标记了一个键,在提交事务前如果键被别人修改过,那事务就会失败,这种情况通常在程序中重新尝试一次。
首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减,
足够的话,就启动事务进行更新操作。
如果在此期间键balance被其他人修改,那在提交事务时就会报错;
程序中通常可以捕获这类错误再重新执行一次,直到成功。
事务加锁执行如下:
public class TestTX {
public static void main(String[] args) throws InterruptedException {
TestTX tx = new TestTX();
boolean retValue = tx.trans();
System.out.println("retValue: " + retValue);
}
private boolean trans() throws InterruptedException {
boolean flag = false;
int balance;
int debt;
int amtToSubtrace = 10;
Jedis jedis = new Jedis("192.168.158.134", 6379);
// 观察者
jedis.watch("balance");
// 模拟网络延时
Thread.sleep(7000);
balance = Integer.parseInt(jedis.get("balance"));
if (balance < amtToSubtrace) {
jedis.unwatch();
System.out.println("modify");
jedis.close();
return flag;
} else {
// 开启事务
System.out.println("**************transaction");
Transaction transcation = jedis.multi();
transcation.decrBy("balance", amtToSubtrace);
transcation.incrBy("debt", amtToSubtrace);
transcation.exec();
jedis.close();
return !flag;
}
}
}
eclipse中连接不上redis的原因:
1.redis.conf的配置文件中默认绑定的是127.0.0.1 即本地的地址,将本地地址修改为0.0.0.0;
2.防火墙没有关闭,查看防火墙的命令:service iptables status 打开和关闭分别如下:
service iptables start service iptables stop
public class TestMasterSlave {
public static void main(String[] args) {
Jedis jedis_M = new Jedis("192.168.158.134", 6379);
Jedis jedis_S = new Jedis("192.168.158.134", 6380);
jedis_S.slaveof("192.168.158.134", 6379);
jedis_M.set("class", "1122");
String result = jedis_S.get("class");
System.out.println(result);
jedis_M.close();
jedis_S.close();
}
}
结果会出现null,因为内存数据库太快了。
2.8.1 Redis pool
是单例模式:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisPoolUtil {
private static volatile JedisPool jedisPool = null;
private JedisPoolUtil() {
}
public static JedisPool getJedisInstance() {
if (null == jedisPool) {
synchronized (JedisPoolUtil.class) {
if (null == jedisPool) {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxWaitMillis(100 * 1000);
jedisPoolConfig.setMaxIdle(32);
jedisPoolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(jedisPoolConfig, "192.168.158.134", 6379);
}
}
}
return jedisPool;
}
public static void release(JedisPool jedisPool, Jedis jedis) {
try {
jedis = jedisPool.getResource();
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}