redis

it2024-03-21  84

redis

文章目录

rediswindows系统linux系统redis基础知识五大数据类型String(字符串)Hash(哈希)List(列表)Set(集合)zset(sorted set:有序集合) 三种特殊数据类型geospatial 地理位置hyperloglogbitmaps 位储存 统计数量 Redis的事务、乐观锁和悲观锁一、是什么二、能干嘛三、怎么玩常用命令:正常执行:放弃事务:全体连坐:【在事务块中只要有一条命令执行是错的,那么整个事务块就不会执行】冤头债主:【如果在事务块中所有命令都正确,但是结果会产生错误,那么冤有头债有主,谁错找谁】watch监控:*********************************************************************无加塞篡改,先监控再开启 MULTI ,保证两笔金额变动在同一个事务内*********************************************************************注意:**********************************************一旦执行了 EXEC,之前加的监控所都会被取消掉***********************************************四、小结五、三阶段六、三特性 Jedis导入依赖 springboot整合redis连接redis服务器配置RedisTemplate测试 redis的持久化(RDB及AOF)为什么要对redis进行持久化操作?二者的区别rdb及aof的优劣势 redis发布订阅使用场景 Redis主从复制环境配置一主二从 哨兵模式redis缓存穿透及雪崩(面试高频,工作常用)缓存雪崩:缓存穿透:缓存击穿:

windows系统

双击启动服务默认端口号6379使用redis客户端来连接redis )]

4.基本操作

linux系统

1.启动服务,连接redis

1.启动服务 [root@localhost ~]# redis-server /usr/local/bin/xuconfig/redis.conf 2.连接redis [root@localhost bin]# redis-cli -p 6379

2.redis-benchmark压力测试工具

例如 [root@localhost bin]# redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000

redis基础知识

redis默认有16个数据库

默认使用的是第0个

可以使用select n 进行切换数据库

redis是单线程的!

明白redis是很快的,官方表示,redis是基于内存操作,cpu不是redis性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就没必要使用多线程

Redis是c语言写的,官方提供的数据为100000+的QPS,完全不比同样是使用key-value的Memecache差!!

redis为什么单线程还能这么快?

1.误区1:高性能的服务器一定是多线程的?

2.误区2:多线程(cpu上下文会切换!)一定比单线程效率高?

核心:redis是将所有的数据全部放在内存中的,所以说使用单线程操作效率就是最高的,多线程(cpu上下文会切换,耗时的操作!!!),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在同一个cpu上的,在内存情况下,这个就是最佳的方案

[root@localhost bin]# redis-cli 127.0.0.1:6379> select 2 # 切换数据库 OK 127.0.0.1:6379[2]> set cgl ssb # 存键值对 OK 127.0.0.1:6379[2]> get cgl # 取值 "ssb" 127.0.0.1:6379[2]> keys * # 查看所有的键 1) "cgl" 127.0.0.1:6379[2]> flushdb # 清空数据库 OK 127.0.0.1:6379[2]> keys * (empty list or set) 127.0.0.1:6379[2]> select 0 OK 127.0.0.1:6379> keys * #第一个数据库默认的值 1) "key:__rand_int__" 2) "myset:__rand_int__" 3) "counter:__rand_int__" 4) "mylist" 127.0.0.1:6379> flushall # 清空全部的数据库 OK 127.0.0.1:6379> set name xjp OK 127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> exists name # 查看当前的键是否存在 (integer) 1 127.0.0.1:6379> move name 1 # 将当前库的name键移到数据库1中 (integer) 1 127.0.0.1:6379[1]> del name # 删除当前库的某个键 (integer) 1 127.0.0.1:6379> set name xjp OK 127.0.0.1:6379> keys * 1) "name" 2) "age" 127.0.0.1:6379> expire name 10 # 定时任务 让name10s后失效 (integer) 1 127.0.0.1:6379> ttl name # 查看name还有多长时间失效 (integer) 4 127.0.0.1:6380> exists myset # 查看某个键是否存在 (integer) 1 127.0.0.1:6379> type name # 查看key对应值的类型 string 127.0.0.1:6379>

五大数据类型

String(字符串)

string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。

string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。

string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。

######################################################################################## 127.0.0.1:6379> keys * 1) "name" 127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> get name "xjp" 127.0.0.1:6379> append name hello # 从某个key中追加字符串,如果当前key不存在,相当于setkey (integer) 8 127.0.0.1:6379> get name "xjphello" 127.0.0.1:6379> append name youarecguoliangfarther (integer) 30 127.0.0.1:6379> get name "xjphelloyouarecguoliangfarther" 127.0.0.1:6379> strlen name # 获取某个key对应的值的长度 (integer) 30 ######################################################################################## i++; 步长i+= 127.0.0.1:6379> set views 0 OK 127.0.0.1:6379> get views "0" 127.0.0.1:6379> incr views # 每执行一次 views值+1 相当于i++ (integer) 1 127.0.0.1:6379> incr views (integer) 2 127.0.0.1:6379> incr views (integer) 3 127.0.0.1:6379> get views "3" 127.0.0.1:6379> decr views # 每执行一次 views值-1 相当于i-- (integer) 2 127.0.0.1:6379> decr views (integer) 1 127.0.0.1:6379> incrby views 10 # 对当前的views的值+10 (integer) 11 127.0.0.1:6379> decrby views 10 # 对当前的views的值-10 (integer) 1 127.0.0.1:6379> ######################################################################################## 127.0.0.1:6379> get name "xjphelloyouarecguoliangfarther" 127.0.0.1:6379> getrange name 1 5 # 截取字符串[1,5] "jphel" 127.0.0.1:6379> getrange name 0 -1 "xjphelloyouarecguoliangfarther" # 获取所有的字符串 127.0.0.1:6379> set key1 abcdefg OK 127.0.0.1:6379> get key1 "abcdefg" 127.0.0.1:6379> setrange key1 1 xx # 替换指定位置开始的字符串 (integer) 7 127.0.0.1:6379> get key1 "axxdefg" ####################################################################################### # setex (set with expire) # 设置过期时间 # setnx (set if not exist) # 不存在再设置,在分布式锁中会常常使用 127.0.0.1:6379> setex key2 30 hello # 修改key2的值为hello 并在30s后过期,如果key2不存在则创建 OK 127.0.0.1:6379> get key2 "hello" 127.0.0.1:6379> ttl key2 # 查看key2还有多久过期 (integer) 10 127.0.0.1:6379> setnx mykey redis # 如果mykey不存在 则创建,存在则返回0 (integer) 1 127.0.0.1:6379> keys * 1) "mykey" 2) "views" 3) "age" 4) "name" 5) "key1" 127.0.0.1:6379> ttl key2 (integer) -2 127.0.0.1:6379> setnx mykey mongoDB (integer) 0 127.0.0.1:6379> get mykey "redis" 127.0.0.1:6379> ###################################################################################### # 一次性操作多个key 127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 OK 127.0.0.1:6379> keys * 1) "k2" 2) "k3" 3) "k1" 127.0.0.1:6379> mget k1 k2 k3 1) "v1" 2) "v2" 3) "v3" 127.0.0.1:6379> ######################################################################################## # 存储对象 127.0.0.1:6379> set user:1 {name:zhangsan,age:3} # 第一种方式, OK 127.0.0.1:6379> get user:1 "{name:zhangsan,age:3}" 127.0.0.1:6379> mset user:2:name lisi user:2:age 20 # 第二种方式 OK 127.0.0.1:6379> keys * 1) "user:1" 2) "user:2:age" 3) "user:2:name" 127.0.0.1:6379> ###################################################################################### #getset 先get后set 如果没有会创建 如果有则替换 127.0.0.1:6379> getset db redis # 由于没有db这个键,所以先返回nil,后执行set (nil) 127.0.0.1:6379> get db "redis" 127.0.0.1:6379> getset db Mongodb "redis" 127.0.0.1:6379> get db "Mongodb" 127.0.0.1:6379>

Hash(哈希)

Redis hash 是一个键值(key=>value)对集合。

Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。

127.0.0.1:6379> hmset user:1 name xjp age 20 # 在同一个对象中创建多个键值对 OK 127.0.0.1:6379> hmget user:1 name age 1) "xjp" 2) "20" 127.0.0.1:6379> hset myshash field 5 # 创建一个hash对象 (integer) 1 127.0.0.1:6379> hget myshash field # 获取值 hgetall获取所有值 "5" 127.0.0.1:6379> hincrby myshash field 1 # 执行一次,值+1 (integer) 6 127.0.0.1:6379> hincrby myshash field 1 (integer) 7 127.0.0.1:6379> hincrby myshash field -1 # 执行一次,值+9 (integer) 6 127.0.0.1:6379> hincrby myshash field 9 # 执行一次,值+9 (integer) 15 127.0.0.1:6379> hsetnx myshash firld1 hello # 如果不存在则创建赋值 (integer) 1 127.0.0.1:6379> hsetnx myshash firld1 he # 如果存在则返回0 (integer) 0

List(列表)

list实际上是个双向链表,但是可以完成栈和队列

消息队列(lpush rpop) 栈(lpush lpop)

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

# 大部分的list命令都是以L开头的 ####################################################################################### 127.0.0.1:6379> Lpush list one two # 将一个或者多个值插入到列表的头部,以栈的形式 (integer) 2 127.0.0.1:6379> keys * 1) "user:1" 2) "list" 3) "user:2:age" 4) "user:2:name" 5) "db" 127.0.0.1:6379> Lpop list # 弹栈 "two" 127.0.0.1:6379> lpop list # 弹栈 "one" 127.0.0.1:6379> lpop list # 弹栈 (nil) 127.0.0.1:6379> keys * 1) "user:1" 2) "user:2:age" 3) "user:2:name" 4) "db" 127.0.0.1:6379> Lpush list one two (integer) 2 127.0.0.1:6379> Lrange list 0 -1 # 查看所有压入的值 1) "two" 2) "one" 127.0.0.1:6379> ####################################################################################### # 在现有的列表中插入值 查出 删除 127.0.0.1:6379> Lrange list 0 -1 1) "two" 2) "one" 127.0.0.1:6379> lpush list three # 相当于在栈顶压入值 (integer) 3 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 127.0.0.1:6379> Rpush list for # 相当于在队头插入值 (integer) 4 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "for" 127.0.0.1:6379> lindex list 1 # 根据下标查出值 下标从0开始 "two" 127.0.0.1:6379> lindex list 0 "three" 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 127.0.0.1:6379> lrem list 1 two # 删除值 (integer) 1 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "one" ######################################################################################## # ltrim 修剪 127.0.0.1:6379> lrange list 0 -1 1) "one" 2) "for" 3) "two" 4) "three" 127.0.0.1:6379> ltrim list 1 2 # 修剪list的下标为1和2的 只剩下1和2 OK 127.0.0.1:6379> lrange list 0 -1 1) "for" 2) "two" 127.0.0.1:6379> ####################################################################################### # 在指定的位置插入值 linsert 127.0.0.1:6379> lrange list 0 -1 1) "zsy" 2) "cc" 3) "wsx" 127.0.0.1:6379> linsert list before cc love # 在cc的前面加个love (integer) 4 127.0.0.1:6379> lrange list 0 -1 1) "zsy" 2) "love" 3) "cc" 4) "wsx" 127.0.0.1:6379> linsert list after cc 1130 # 在cc的后面加个1130 (integer) 5 127.0.0.1:6379> lrange list 0 -1 1) "zsy" 2) "love" 3) "cc" 4) "1130" 5) "wsx" 127.0.0.1:6379>

Set(集合)

set中的值是不能重复的

Redis的Set是string类型的无序集合。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

用例:

微博,a用户的所有关注的人放在一个set集合中!将它的粉丝也放在一个集合中!共同关注。共同爱好,二度好友,推荐好友!(六度分割理论)

127.0.0.1:6379> sadd myset hello xjp lovecc # 添加元素到myset集合中 (integer) 3 127.0.0.1:6379> smembers myset # 遍历 1) "xjp" 2) "hello" 3) "lovecc" 127.0.0.1:6379> sismember myset xjp # 查询集合中是否存在该元素 (integer) 1 127.0.0.1:6379> sismember myset zsy (integer) 0 127.0.0.1:6379> scard myset # 查询集合的总长度 (integer) 3 127.0.0.1:6379> srem myset hello # 删除该集合中的hello元素 (integer) 1 127.0.0.1:6379> smembers myset 1) "xjp" 2) "lovecc" 127.0.0.1:6379> srandmember myset # 随机查出一个值 "lovecc" 127.0.0.1:6379> srandmember myset "xjp" 127.0.0.1:6379> spop myset # 随机弹出一个值 "lovecc" 127.0.0.1:6379> smembers myset 1) "xjp" ######################################################################################### 127.0.0.1:6379> sadd myset abd def ghi jkl mn (integer) 5 127.0.0.1:6379> smembers myset 1) "abd" 2) "def" 3) "jkl" 4) "ghi" 5) "mn" 127.0.0.1:6379> smove myset myset1 jkl # 将一个指定的值移到另一个set集合中 (integer) 1 127.0.0.1:6379> smembers myset1 1) "jkl" 127.0.0.1:6379> smembers myset 1) "def" 2) "ghi" 3) "mn" 4) "abd" ######################################################################################### 数字集合类: - 差集 sdiff - 交集 sinter - 并集 sunion 127.0.0.1:6379> sadd myset a b c d e f g (integer) 7 127.0.0.1:6379> sadd myset1 a b c d (integer) 4 127.0.0.1:6379> sdiff myset myset1 # 差集 1) "f" 2) "g" 3) "e" 127.0.0.1:6379> sinter myset myset1 # 交集 1) "c" 2) "b" 3) "d" 4) "a" 127.0.0.1:6379> sunion myset myset1 # 并集 1) "g" 2) "b" 3) "c" 4) "a" 5) "e" 6) "d" 7) "f" 127.0.0.1:6379>

zset(sorted set:有序集合)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

zset的成员是唯一的,但分数(score)却可以重复。

127.0.0.1:6379> zadd myset 1 xjp 2 zk 3 zsy # 创建个有序集合,1,2相当于代表下标 (integer) 3 127.0.0.1:6379> zrange myset 0 -1 #遍历 1) "xjp" 2) "zk" 3) "zsy" 127.0.0.1:6379> zadd salary 2500 xiaohong 5000 zhangsan 500 kuangsen (integer) 3 127.0.0.1:6379> 127.0.0.1:6379> zrangebyscore salary -inf +inf # 排序 相当于从负无穷到正无穷进行排序 1) "kuangsen" 2) "xiaohong" 3) "zhangsan" 127.0.0.1:6379> zrangebyscore salary -inf +inf withscores # 排序 并将下标一起显示 1) "kuangsen" 2) "500" 3) "xiaohong" 4) "2500" 5) "zhangsan" 6) "5000" 127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores # 取负无穷到2500之间的值 1) "kuangsen" 2) "500" 3) "xiaohong" 4) "2500" 127.0.0.1:6379> zrevrange salary 0 -1 # 逆序 1) "zhangsan" 2) "xiaohong" 3) "kuangsen" 127.0.0.1:6379> zcard salary # 查看该集合中有多少个值 (integer) 3 127.0.0.1:6379> zcard myset (integer) 3 127.0.0.1:6379> zcount myset 0 1 # 查询0到1这个范围类有多少个值 (integer) 1 127.0.0.1:6379>

三种特殊数据类型

geospatial 地理位置

底层是zset zset的所有命令都适用

127.0.0.1:6379> geoadd china:city 116.16 40.05 beijing # 添加位置 经度 纬度 城市 (integer) 1 127.0.0.1:6379> geoadd china:city 121.48 31.10 shanghai 113.88 22.55 shenzhen 113.27 23.15 guangzhou (integer) 3 127.0.0.1:6379> geoadd china:city 120.21 30.20 hangzhou 114.02 30.58 wuhan 112.89 30.40 qianjiang (integer) 3 127.0.0.1:6379> geopos china:city hangzhou # 查看城市的经度 纬度 1) 1) "120.21000176668167114" 2) "30.19999988833350102" 127.0.0.1:6379> geopos china:city hangzhou beijing 1) 1) "120.21000176668167114" 2) "30.19999988833350102" 2) 1) "116.1600002646446228" 2) "40.04999982043828055" 127.0.0.1:6379> geodist china:city beijing wuhan # 查看两个城市之间的距离 "1070968.9641" 127.0.0.1:6379> geodist china:city beijing wuhan km "1070.9690" 127.0.0.1:6379> georadius china:city 100 20 10000 km # 查看以经度100 纬度20为中心半径为10000km的城市 1) "shenzhen" 2) "guangzhou" 3) "qianjiang" 4) "wuhan" 5) "hangzhou" 6) "shanghai" 7) "beijing" 127.0.0.1:6379> GEORADIUSBYMEMBER china:city wuhan 1000 km # 以某个城市为中心画圆 1) "shenzhen" 2) "guangzhou" 3) "qianjiang" 4) "wuhan" 5) "hangzhou" 6) "shanghai" 127.0.0.1:6379>

hyperloglog

一般用来统计访问量,基数

例如:同一个用户的访问网站,网站只能加1

127.0.0.1:6379> pfadd mykey a b c d e f g h # 添加 (integer) 1 127.0.0.1:6379> pfcount mykey # 显示无重复的个数 (integer) 8 127.0.0.1:6379> pfadd mykey1 q qe r g d a s b (integer) 1 127.0.0.1:6379> pfcount mykey1 (integer) 8 127.0.0.1:6379> PFMERGE mykey2 mykey mykey1 #合并 取并集 OK 127.0.0.1:6379> pfcount mykey2 (integer) 12 127.0.0.1:6379>

bitmaps 位储存 统计数量

统计是否打卡,活跃用户,不活跃用户,登陆用户 未登录用户等

bitmaps位图,数据结构! 都是操作二进制来进行记录。只有0和1两个状态,占内存特别少

127.0.0.1:6379> setbit bit 0 0 # 模拟每周打卡 (integer) 0 127.0.0.1:6379> setbit bit 1 0 (integer) 0 127.0.0.1:6379> setbit bit 2 1 (integer) 0 127.0.0.1:6379> setbit bit 3 1 (integer) 0 127.0.0.1:6379> setbit bit 4 1 (integer) 0 127.0.0.1:6379> setbit bit 5 0 (integer) 0 127.0.0.1:6379> setbit bit 6 1 (integer) 0 127.0.0.1:6379> getbit bit 6 # 查看周六有没有打卡 (integer) 1 127.0.0.1:6379> getbit bit 5 # 查看周五有没有打卡 (integer) 0 127.0.0.1:6379> bitcount bit # 查看总打卡天数 (integer) 4

Redis的事务、乐观锁和悲观锁

redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,都会按顺序执行

一次性,顺序性,排他性!执行一些列的命令!

redis事务没有隔离级别的概念

Redis单条命令式保存原子性的,但是redis事务不保证原子性

事务每执行完成后 该事务就没了,需要重新开启事务

redis的事务:

开启事务(multi)命令入队(…)执行事务(exec) 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> get k1 QUEUED 127.0.0.1:6379> exec # 执行事务 1) OK 2) OK 3) "v1" 127.0.0.1:6379>

一、是什么

可以一次执行多个命令,本质是一组命令的集合。

一个事务中的所有命令都会序列化,按照顺序地串行化执行而不会被其他命令插入,不许加塞

二、能干嘛

一个队列中,一次性、顺序性、排他性的执行一系列命令

三、怎么玩

Redis中开启事务的命令是:MULTI ,这个命令通常会回复一个OK【回复的是OK,但是这个事能不能办,什么时候办,办不办的成不知道】,用户将会一次性的打多个命令,而代替执行,按顺序执行,Redis将这些命令入队,所有的命令将会通过命令:EXEC 来被调用执行。

如果用命令:DISCARD 表示放弃丢弃,言下之意是放弃本次的批处理操作

常用命令:

DISCARD:取消事务,放弃执行事务块内的所有命令

EXEC:执行所有事务块内的命令

MULTI:标记一个事务块的开始

UNWATCH:取消 WATCH 命令对所有 key 的监控

WATCH key [key . . . ]:件事一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将被打断

正常执行:

放弃事务:

全体连坐:【在事务块中只要有一条命令执行是错的,那么整个事务块就不会执行】

冤头债主:【如果在事务块中所有命令都正确,但是结果会产生错误,那么冤有头债有主,谁错找谁】

watch监控:

悲观锁

悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,当其他线程想要访问数据时,都需要阻塞挂起。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁、表锁,读锁,写锁等,都是在操作之前先上锁。

在Java中,synchronized的思想也是悲观锁。

乐观锁:【冲突检测和数据更新】

乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候回判断一下再次期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。

乐观锁策略:提交版本必须大于记录当前版本才能执行更新

一般会使用版本号机制或CAS操作实现:

version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

核心SQL代码:

update table set x=x+1, version=version+1 where id=#{id} and version=#{version};

CAS(Check And Set【先检查再动手设置】)

CAS操作方式:即 compare and set,CAS是乐观锁技术,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。

********************************************************************

无加塞篡改,先监控再开启 MULTI ,保证两笔金额变动在同一个事务内

********************************************************************

如下图:就模拟了一个购物的过程,在买的过程中,别人会给你打钱,当你要完成支付的时候报错了

解决:【使用 UNWATCH 取消对当前 key 的监控,之后再一次进行监控(得到最新的数据),直到成功为止】

注意:

**********************************************

一旦执行了 EXEC,之前加的监控所都会被取消掉*

**********************************************

四、小结

通过 WATCH 命令在事务执行之前监控了多个 Keys,倘若在 WATCH 之后有任何 Key 的值发生变化,EXEC 命令执行的事务都将被放弃,同时返回 Nullmulti-bulk 应答以通知调用者事务执行失败

五、三阶段

开启:以 MULTI 开始一个事务

入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面

执行:由 EXEC 命令触发事务

六、三特性

单独的隔离操作:事务中所有的命令多会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断

没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际的执行,也就是不存在 “ 事务内的查询要看到事务里面的更新,在事务外查询不能看到 ” 这个是让人万分头痛的问题

不保证原子性:Redis 同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

Jedis

什么是jedis?是redis官方推荐的java连接开发工具,使用java操作redis中间件!

导入依赖

<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.3.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.73</version> </dependency>

springboot整合redis

连接redis服务器

spring.redis.host=127.0.0.1 spring.redis.port=6379

配置RedisTemplate

package com.xu.redis.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean @SuppressWarnings("all") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); template.setConnectionFactory(factory); //配置指定序列化,防止乱码 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); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }

测试

package com.xu.redis; import com.xu.redis.config.RedisConfig; import com.xu.redis.entity.User; import com.xu.redis.utils.RedisUtil; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisTemplate; @SpringBootTest class RedisSpringbootApplicationTests { @Autowired private RedisTemplate redisTemplate; @Autowired //redis工具类 private RedisUtil redisUtil; @Test void contextLoads() { redisTemplate.opsForValue().set("name","xjp"); System.out.println(redisTemplate.opsForValue().get("name")); //获取redis对象 RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); System.out.println(connection.ping()); connection.flushDb(); } @Test void der(){ redisUtil.set("xjp","zs"); System.out.println(redisUtil.get("xjp")); } }

redis的持久化(RDB及AOF)

为什么要对redis进行持久化操作?

由于Redis的数据都存放在内存中,如果没有配置持久化,redis重启后数据就全丢失了,于是需要开启redis的持久化功能,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据。redis提供两种方式进行持久化,一种是RDB持久化(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化),另外一种是AOF持久化(原理是将Reids的操作日志以追加的方式写入文件)。

二者的区别

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

rdb及aof的优劣势

rdb:

优点:适合大规模的数据恢复

​ 对数据的完整性要求不高

缺点:需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改的数据就丢失了

​ fork子进程的时候,会占用一定的内存空间

​ aof:

​ 优点:AOF可以更好的保护数据不丢失。 一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作, 最多丢失1秒钟的数据,Redis进程挂了,最多丢掉1秒钟的数据;

​ 每一次修改都同步 文件的完整性比较好

​ AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。每一次进行aof持久化都会写入,当文件越来越大大于64m时,则会创建新的日志文件;

​ 缺点 :相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢

​ aof的运行效率也比rdb慢,redis默认支持的是rdb 持久化

redis发布订阅

订阅端

127.0.0.1:6379> subscribe dmxu # 订阅了一个频道dmxu Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "dmxu" 3) (integer) 1 # 等待读取推送的信息 1) "message" # 消息 2) "dmxu" # 频道 3) "successxu" # 消息的具体内容 1) "message" 2) "dmxu" 3) "jiayou" 1) "message" 2) "dmxu" 3) "qifei"

发送端

127.0.0.1:6379> publish dmxu successxu # 发送消息到频道 (integer) 1 127.0.0.1:6379> publish dmxu jiayou (integer) 1 127.0.0.1:6379> publish dmxu qifei (integer) 1 127.0.0.1:6379>

使用场景

实时消息系统!实时聊天!(频道当做聊天室,将信息回显给所有人即可!)订阅,关注系统

Redis主从复制

环境配置

只配置从库,不用配置主库

127.0.0.1:6379> info replication # 查看当前库的信息 # Replication role:master # 角色 master connected_slaves:0 # 没有从机 master_replid:3c89d46ff34efb6cc1586b724a41008b864f4397 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0 127.0.0.1:6379>

复制3个配置文件,然后修改对应的信息

端口后台运行 daemonize yespidfile 名字log文件名字dump.rdb名字

一主二从

默认情况下,每台redis服务器都是主节点一般情况下只用配置从机

认老大! 一主(79)二从(80,81)

######################################################################################### #主机 6379 127.0.0.1:6379> info replication # Replication role:master connected_slaves:2 # 多了两个从机的配置 slave0:ip=127.0.0.1,port=6380,state=online,offset=1316,lag=0 slave1:ip=127.0.0.1,port=6381,state=online,offset=1316,lag=0 master_replid:185e074eb80a6ec61f60a428023b9671f39e1159 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:1316 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:1316 127.0.0.1:6379> ######################################################################################### # 从机6380 127.0.0.1:6380> slaveof 127.0.0.1 6379 # 认6379为老大 OK 127.0.0.1:6380> info replication # Replication role:slave # 角色 从机 master_host:127.0.0.1 # 主机的信息 master_port:6379 master_link_status:up master_last_io_seconds_ago:2 master_sync_in_progress:0 slave_repl_offset:1302 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:185e074eb80a6ec61f60a428023b9671f39e1159 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:1302 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:1302 127.0.0.1:6380> ######################################################################################### # 从机6381 127.0.0.1:6381> slaveof 127.0.0.1 6379 #认6379为老大 OK 127.0.0.1:6381> info replication # Replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:up master_last_io_seconds_ago:5 master_sync_in_progress:0 slave_repl_offset:1274 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:185e074eb80a6ec61f60a428023b9671f39e1159 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:1274 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1261 repl_backlog_histlen:14 127.0.0.1:6381>

真实的主从配置是在配置文件中配置 是永久的,命令中配置是暂时的

主机可以写,从机不能写只能读!主机中的所有信息和数据,都会自动被从机保存

主机:

从机:

测试:如果主机断开连接,从机依旧连接到主机,也可以读到主机之前写入的值;此时主机如果回来了,从机依然可以获得主机写的信息

​ 如果从机断开连接,再回来;如果使用的是命令行配置,则回来后默认变成主机,不能读取之前主机上的任何操作,需要将其变成从机,从而可以继续读取主机上的信息

哨兵模式

哨兵模式是主从复制的一种优化形式;当主机宕机后,哨兵会在剩下的从机中投票选出一个主机,当原有的主机重新恢复后,原有的主机则变成现有主机的从机

1.配置配置文件

创建多个sentinel.conf 配置集群 例如sentinel79.conf sentinel80.conf

port 26379 # sentinel monitor是命令 mymast是名称 随机取的 sentinel monitor mymast 127.0.0.1 6379 1 #主节点 名称 IP 端口号 选举次数 logfile "/usr/local/bin/xuconfig/sentinel.log.26379" port 26379 # sentinel monitor是命令 mymast是名称 随机取的 sentinel monitor mymast 127.0.0.1 6380 1 #主节点 名称 IP 端口号 选举次数 logfile "/usr/local/bin/xuconfig/sentinel.log.26380"

2.启动sentinel.conf

[root@localhost bin]# redis-sentinel xuconfig/sentinel79.conf & [1] 6241 [root@localhost bin]# redis-sentinel xuconfig/sentinel80.conf & [2] 6245

redis缓存穿透及雪崩(面试高频,工作常用)

缓存雪崩:

原理:在某个时间段,redis中key集中过期,而此时刚好客户端请求很多,于是在redis上访问不到,只能去数据库中查找,当数据量特别多的时候,那么数据库就会雪崩,从而带着服务器,redis一起崩溃解决方案: 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。设置热点数据永远不过期。

缓存穿透:

原理:当接收到许多非正常的url请求时(redis和数据库中都没有这个数据),该请求会不断的去数据库中进行查询操作,当数据量过大时就会导致数据库服务器崩溃;一般是黑客攻击解决方案: 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击 (不过这样做很lou)布隆过滤器==(效率高)==

缓存击穿:

原理:当客户端有大量的请求,并且这些请求都是请求同一个key,而redis中这个key刚好过期,于是这些请求都会去访问数据库,从而导致数据库崩溃解决方案 设置热点数据永远不过期。接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些 服务 不可用时候,进行熔断,失败快速返回机制。布隆过滤器。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小加互斥锁,互斥锁参考代码如下:

最新回复(0)