赞
踩
官方已经不提供安装包
资源:链接:https://pan.baidu.com/s/1CnMlSGMcZPWFc8qW_EgFwQ
提取码:w2ef
解压即安装
运行
ping返回pong连接成功
官方文档存在安装方式
https://redis.io/docs/install/install-redis/install-redis-from-source/
收到邮件:
无脑下一步安装
复制公网id
新建连接输入公网id以及密码
cd /
切换到根目录
程序一般放在opt目录下
解压:
1.redis是c++写的
yum install gcc-c++
2.make两次
make
3.
make install
4.redis默认安装路径为 /usr/local/bin
5.将redis配置文件复制到当前目录下
我们之后使用这个配置文件进行启动
6.redis默认不是后台启动,修改配置文件
vim redis.conf
改为后台启动
退出
7.启动redis服务
cd ..
回到上级目录
连接:
8.查看redis进程是否开启
开启两个进程
9.关闭redis
10.后面使用单机多redis启动集群测试
redis-benchmark是一个压力测试工具,官方自带的性能测试工具
redis-benchmark 命令参数
我们来简单测试一下
#测试:100个并发,10万个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
redis默认有16个数据库,默认使用的是第0个,
1.可以使用select 进行切换数据库
2.查看数据库大小
3.清空当前数据库
flushdb
4.清空全部数据库内容
flushall
redsi很快,redis基于内存操作,cpu不是redis性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程。
redis是c语言写的,官方数据是100000+的QPS,这个不必同样使用key-value的Memecache差
redis为什么单线程还这么快
1.误区:高性能的服务器一定是多线程的?
2.误区二:多线程一定比单线程高!
核心:redis将所有数据全部放在内存中,所以说使用单线程去操作效率高,多线程(cpu上下文切换:耗时操作),对于内存系统来说,如果没有上下文切换效率是最高的,多次读写都是在一个cpu上的,在内存情况下,这个就是最佳方案!
Redis是开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息中间件MQ。Redis提供数据结构,例如字符串,哈希,列表,集合,带范围查询的排序集合,位图,超日志,地理空间索引和流。Redis具有内置的复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供了高可用性
此图红色箭头有误,
exists name判断name是否存在
后面不会的命令可以在官网查
==90%的程序员使用redis只会使用string类型。==api调用工程师
127.0.0.1:6379> set views 0 OK 127.0.0.1:6379> get views "0" 127.0.0.1:6379> incr views #自增一 (integer) 1 127.0.0.1:6379> incr views (integer) 2 127.0.0.1:6379> get views "2" 127.0.0.1:6379> decr views #自减一 (integer) 1 127.0.0.1:6379> incrby views 10 #自增10 (integer) 11 127.0.0.1:6379> incrby views 10 (integer) 21 127.0.0.1:6379> decrby views 5 #自减5 (integer) 16 127.0.0.1:6379>
截取字符串getrange
127.0.0.1:6379> clear
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set key1 "fang"
OK
127.0.0.1:6379> get key1
"fang"
127.0.0.1:6379> getrange key1 0 2 #截取012字符串
"fan"
127.0.0.1:6379> getrange key1 0 -1 #获取全部字符串
"fang"
127.0.0.1:6379>
替换setrange
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
127.0.0.1:6379>
setex #设置过期时间
setnx #不存在在设置(在分布式锁中常常使用)
127.0.0.1:6379> clear 127.0.0.1:6379> setex key3 30 "hello" OK 127.0.0.1:6379> ttl key3 (integer) 17 127.0.0.1:6379> setnx mykey "redsi" #如果mykey存在,则创建失败。 (integer) 1 127.0.0.1:6379> keys * 1) "mykey" 2) "key2" 3) "key1" 127.0.0.1:6379> ttl key3 (integer) -2 127.0.0.1:6379> setnx mykey "MongDB" (integer) 0 127.0.0.1:6379> get mykey "redsi"
一次性获取,设置多个值:mset,mget
127.0.0.1:6379> clear 127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #同时设置多个值 OK 127.0.0.1:6379> keys * 1) "k3" 2) "k2" 3) "k1" 127.0.0.1:6379> mget k1 k2 k3 #同时获取多个值 1) "v1" 2) "v2" 3) "v3" 127.0.0.1:6379> msetnx k1 v1 k4 v4 #msetnx是一个原子性操作,要么成功要么失败 (integer) 0 127.0.0.1:6379> get k4 (nil) 127.0.0.1:6379>
对象
mset user:1{name:zhangsan,age:3}#设置一个user:1对象 值为json字符来保存一个对象
127.0.0.1:6379> mset user:1:name fang user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "fang"
2) "2"
127.0.0.1:6379>
先get在set-------getset
127.0.0.1:6379> clear
127.0.0.1:6379> getset db redis
(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"
string类型的使用场景:value除了字符串还可以是数字
1.计数器
2.统计多单位数量
3.粉丝数
4.对象缓存存储!
在redis里面可以把list完成栈,队列,阻塞队列!
所有的list命令以l开头
1.
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
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> lrange list 0 1
1) "three"
2) "two"
倒着输出
默认象栈
移除
lpop
rpop
lindex 通过下表获得值
127.0.0.1:6379> lindex list 1
"one"
127.0.0.1:6379> lindex list 0
"two"
llen返回列表长度
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> llen list
(integer) 3
移除指定值
取关 uid
lrem
trim 修剪:list截断
rpop lpush,将列表右边元素移到另一个列表的左边
127.0.0.1:6379> rpush mylist "0"
(integer) 1
127.0.0.1:6379> rpush mylist "1"
(integer) 2
127.0.0.1:6379> rpush mylist "2"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myother
"2"
127.0.0.1:6379> lrange mylist 0 -1
1) "0"
2) "1"
127.0.0.1:6379> lrange myother 0 -1
1) "2"
lset list 0 item将下标为0的元素替换为item
127.0.0.1:6379> exists list //判断列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 item
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
127.0.0.1:6379>
linset 在指定值的前面或者后面插入值
127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist world
(integer) 2
127.0.0.1:6379> linsert mylist before world other
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379>
小结:
1.实际上是一个双向链表
2.key不存在,创建新链表
3.移除所有元素,空链表也代表不存在
4.在两边插入或者改动值,效率高,中间元素,相对效率低
消息队列
set值是不能重复的
sdd:插入
smembers:查询指定set中所有值
sismember:查看值是否存在
127.0.0.1:6379> sadd myset hello (integer) 1 127.0.0.1:6379> sadd myset fang (integer) 1 127.0.0.1:6379> sadd myset lovefang (integer) 1 127.0.0.1:6379> smembers myset 1) "lovefang" 2) "fang" 3) "hello" 127.0.0.1:6379> sismember myset hello (integer) 1 127.0.0.1:6379> sismember myset hello1 (integer) 0 127.0.0.1:6379> scard myset #获取集合元素个数 (integer) 3 127.0.0.1:6379>
移除:srem myset hello
set:无序不重复集合
######################################
随机抽选元素
srandmember
127.0.0.1:6379> srandmember myset
"hello"
127.0.0.1:6379> srandmember myset
"fang"
127.0.0.1:6379> srandmember myset 2
1) "lovefang"
2) "fang"
127.0.0.1:6379>
随机删除value
127.0.0.1:6379> smembers myset
1) "lovefang"
2) "fang"
3) "hello"
127.0.0.1:6379> spop myset
"hello"
127.0.0.1:6379> smembers myset
1) "lovefang"
2) "fang"
#######################################################
将一个指定的值移动到另外的set
127.0.0.1:6379> sadd myset2 1
(integer) 1
127.0.0.1:6379> smove myset myset2 fang
(integer) 1
127.0.0.1:6379> smembers myset2
1) "fang"
2) "1"
127.0.0.1:
###########################################################
微博,b站共同关注(并集)
数字集合类:
差集,交集,并集
微博,将a用户所有关注的人放在一个集合中,将他的粉丝也放在一个集合中!
共同关注,共同爱好
map集合,key-Map集合
127.0.0.1:6379> hset myhash field1 fang (integer) 1 127.0.0.1:6379> hget myhash field1 "fang" 127.0.0.1:6379> hmset myhash filed1 hello filed2 world1 ##一次设置多个值 OK 127.0.0.1:6379> hmget myhash filed1 filed2 ##一次取多个值 1) "hello" 2) "world1" 127.0.0.1:6379> hgetall myhash #获取全部数据 1) "field1" 2) "fang" 3) "filed1" 4) "hello" 5) "filed2" 6) "world1" 127.0.0.1:6379>
删除指定key:hdel myhash filed1
获取字段数量
127.0.0.1:6379> hlen myhash
(integer) 2
127.0.0.1:6379>
####################################################
判断指定字段是否存在
hexists myhash filed1
####################################################
只获得所有的key
hkeys myhash
只获得所有的value
hvals myhash
####################################################
incr decr
127.0.0.1:6379> hset myhash field3 3
(integer) 1
127.0.0.1:6379> hincrby myhash field3 1
(integer) 4
127.0.0.1:6379> hincrby myhash field3 -1
(integer) 3
127.0.0.1:6379>
hsetnx myhash filed4 hello #如果不存在则可以设置
hsetnx myhash filed4 hello #存在则不能设置
hash 变更的数据,user name age,尤其使用户信息之类的,经常变更的数据
hash更适合对象的存储,string更加适合字符串存储
在set的基础上增加了一个值
set v1 k1
zset k1 score1 v1
排序如何实现
127.0.0.1:6379> zadd myset 1 one
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three
(integer) 1
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379>
##############################################
排序如何实现
27.0.0.1:6379> zadd salary 2500 1 (integer) 1 127.0.0.1:6379> zadd salary 5000 2 (integer) 1 127.0.0.1:6379> zadd salary 500 3 (integer) 1 127.0.0.1:6379> zrangebyscore salary -inf +inf #从负无穷到正无穷排序 1) "3" 2) "1" 3) "2" 127.0.0.1:6379> zrangebyscore salary -inf +inf withscores 1) "3" 2) "500" 3) "1" 4) "2500" 5) "2" 6) "5000" 127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores #工资小于2500的升序排列 1) "3" 2) "500" 3) "1" 4) "2500" 127.0.0.1:6379>
##############################################
移除rem
127.0.0.1:6379> zrange salary 0 -1
1) "3"
2) "1"
3) "2"
127.0.0.1:6379> zrem salary 1 #移除指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "3"
2) "2"
127.0.0.1:6379> zcard salary #获取有序集合中的个数
(integer) 2
127.0.0.1:6379>
###################################################
获取指定区间的成员数量
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 fangqing
(integer) 2
127.0.0.1:6379> zcount myset 1 3
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
127.0.0.1:6379>
redis官网可以查看api的使用
案例思路;set 排序,存储班级成绩表,工资表排序
普通消息1,重要消息2,带权重进行排序判断!
排行榜应用实现
朋友定位,附近的人,打车距离技术,这个功能可以推算地理位置信息,两地之间的距离,方圆几里的人!
可以查询一些测试数据
只有六个命令!
添加数据
规则:两级无法添加,我们一般会下载城市数据,直接通过java程序一次性导入!
参数(纬度 经度 名称)
超过有效的经度纬度就会报错
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijin
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqi
(integer) 1
127.0.0.1:6379> geoadd china:city 114.05 22.52 shengzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 118.96 34.26 xian
(integer) 2
127.0.0.1:6379>
获取当前定位:一定是一个坐标值
127.0.0.1:6379> geopos china:city beijin
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijin chongqi
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
127.0.0.1:6379>
两人之间的距离
127.0.0.1:6379> geodist china:city beijin shanghai
"1067378.7564" #北京上海的==直线距离,默认单位为米==
127.0.0.1:6379> geodist china:city beijin shanghai km
"1067.3788"
我附近的人?(获得所有附近的人的地址,定位!)通过半径来查询
127.0.0.1:6379> georadius china:city 110 30 1000 km #以100经度30纬度为中心1000km为半径的圆内城市 1) "chongqi" 2) "shengzhen" 3) "hangzhou" 4) "xian" 127.0.0.1:6379> georadius china:city 110 30 500 km 1) "chongqi" 127.0.0.1:6379> georadius china:city 110 30 500 km withdist #显示到中心位置的距离 1) 1) "chongqi" 2) "341.9374" 127.0.0.1:6379> georadius china:city 110 30 500 km withcoord #显示他人的定位信息 1) 1) "chongqi" 2) 1) "106.49999767541885376" 2) "29.52999957900659211" 127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 1 #筛选出指定数量的结果 1) 1) "chongqi" 2) "341.9374" 3) 1) "106.49999767541885376" 2) "29.52999957900659211" 127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 2 1) 1) "chongqi" 2) "341.9374" 3) 1) "106.49999767541885376" 2) "29.52999957900659211" 127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 3 1) 1) "chongqi" 2) "341.9374" 3) 1) "106.49999767541885376" 2) "29.52999957900659211" 127.0.0.1:6379>
找出指定城市周围的位置
127.0.0.1:6379> georadiusbymember china:city beijin 1000 km
1) "xian"
2) "beijin"
127.0.0.1:6379>
该命令返回长度为11的字符串
127.0.0.1:6379> geohash china:city beijin chongqi
1) "wx4fbxxfke0" #将二维的经纬度转换为一维的字符串,如果字符串月接近,那么距离则越近
2) "wm5xzrybty0"
原理其实就是zset,我们可以使用zset命令操作geo!
127.0.0.1:6379> zrange china:city 0 -1 ##查看元素 1) "chongqi" 2) "shengzhen" 3) "hangzhou" 4) "shanghai" 5) "xian" 6) "beijin" 127.0.0.1:6379> zrem china:city beijin (integer) 1 127.0.0.1:6379> zrange china:city 0 -1 1) "chongqi" 2) "shengzhen" 3) "hangzhou" 4) "shanghai" 5) "xian" 127.0.0.1:6379>
a{1,3,5,7,8,7}
b{1,2,5,7,8}
基数(不重复的元素)=5,可以接受误差
基数统计算法
优点:占用内存是固定的,2^64不同元素计数,只需要费12kb的内存,如果要从内存角都比较hyperloglog首选
网页的uv(一个人访问一个网站多次,但是还是算作一个人)
传统的方式,set保护用户的id,其元素不重复,然后就可以统计set中元素的数量作为标准判断
这个方式如果保持大量用户id就会比较麻烦,我们的目的就是为了计数,而不是保存用户id;
0.81%的错误率,可以接受
127.0.0.1:6379> pfadd mykey a b c d e f g h i j (integer) 1 127.0.0.1:6379> pfcount mykey #统计mykey元素基数数量 (integer) 10 127.0.0.1:6379> pfcount mykey2 i j z x c v b n m # (integer) 0 127.0.0.1:6379> pfadd mykey2 i j z x c v b n m (integer) 1 127.0.0.1:6379> pfcount mykey2 (integer) 9 127.0.0.1:6379> pfmerge mykey3 mykey mykey2 #合并两组到mykey3 OK 127.0.0.1:6379> pfcount mykey3 (integer) 15 127.0.0.1:6379>
如果允许容错统计数量,可以使用hyperloglog
如果不允许使用set或者其他数据类型
位存储
统计用户信息,活跃,不活跃,登入,为登入,打卡,365打卡!两个状态的都可以使用bitmaps
bitmaps位图,数据结构,都是操作二进制位来进行记录的,就只有0,1两个状态
365=365bit 1字节=8bit 46字节左右!
测试
记录周一到周日打卡
周一:1,周二:0,周三:0.。。。
127.0.0.1:6379> setbit sign 0 0 (integer) 0 127.0.0.1:6379> setbit sign 1 0 (integer) 0 127.0.0.1:6379> setbit sign 2 0 (integer) 0 127.0.0.1:6379> setbit sign 3 0 (integer) 0 127.0.0.1:6379> setbit sign 4 1 (integer) 0 127.0.0.1:6379> setbit sign 5 0 (integer) 0 127.0.0.1:6379> setbit sign 6 0 (integer) 0 127.0.0.1:6379>
查看某一天是否打卡
127.0.0.1:6379> getbit sign 3
(integer) 0
127.0.0.1:6379> getbit sign 4
(integer) 1
127.0.0.1:6379>
统计打卡天数,可以看到是否有缺勤
127.0.0.1:6379> bitcount sign
(integer) 1
redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,执行过程中按照顺序执行
一次性,顺序性,排他性,执行一些列的命令
redis单条命令是保持原子性,但是事务不保证原子性
redis事务没有隔离级别的概念
所有的命令在事务中,并没有被直接执行,只有发起执行命令才会被执行!exec
redis事务
a.开启事务(multi)
b.命令入队
c.执行事务(exec)
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set k1 v1 QUEUED 127.0.0.1:6379(TX)> set k2 v2 QUEUED 127.0.0.1:6379(TX)> get k2 QUEUED 127.0.0.1:6379(TX)> set k3 v3 QUEUED 127.0.0.1:6379(TX)> exec 1) OK 2) OK 3) "v2" 4) OK 127.0.0.1:6379>
discard
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379>
事务中所有命令都不会被执行
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set k1 v1 QUEUED 127.0.0.1:6379(TX)> set k2 v2 QUEUED 127.0.0.1:6379(TX)> set k3 v3 QUEUED 127.0.0.1:6379(TX)> getset k3 (error) ERR wrong number of arguments for 'getset' command 127.0.0.1:6379(TX)> set k4 v4 QUEUED 127.0.0.1:6379(TX)> exec (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get k1 (nil) 127.0.0.1:6379>
127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> incr k1 QUEUED 127.0.0.1:6379(TX)> set k2 v2 QUEUED 127.0.0.1:6379(TX)> get k2 QUEUED 127.0.0.1:6379(TX)> exec 1) (error) ERR value is not an integer or out of range #虽然第一条命令报错,但是依旧正常执行 2) OK 3) "v2" 127.0.0.1:6379> get k2 "v2" 127.0.0.1:6379>
悲观锁:很悲观,认为什么时候都会出现问题,无论什么都会加锁,效率降低
乐观锁:很乐观,认为什么时候都不会出现问题,所以不上锁,更新数据时会判断在此期间是否有人改动数据,version!
1.获取version
2.更新时比较version
正常执行成功
127.0.0.1:6379> set money 100 OK 127.0.0.1:6379> set out 0 OK 127.0.0.1:6379> watch money #监视money对象 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> decrby money 20 QUEUED 127.0.0.1:6379(TX)> incrby out 20 QUEUED 127.0.0.1:6379(TX)> exec 1) (integer) 80 2) (integer) 20 127.0.0.1:6379>
新开客户端模拟第二个线程
执行事务之前另一个线程修改值
127.0.0.1:6379> unwatch
OK #r#如果事务执行失败,先解锁
127.0.0.1:6379> watch money
OK## 获取最新值,再次监视
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 1
QUEUED
127.0.0.1:6379(TX)> incrby money 1
QUEUED
127.0.0.1:6379(TX)> exec ##对比监视值是否发送变化,如果没有变化可以执行,变化则执行失败
1) (integer) 999
2) (integer) 1000
127.0.0.1:6379>
我们要使用java操作redis
jedis是官方推荐的java连接开发工具,使用java操作redis中间件,如果你要使用java操作redis,那么你一定要对jedis十分熟悉
2.新建新的maven模块
a.连接数据库
b.操作命令
c.断开连接
所有api都是上面对应的命令
springboo操作数据库:spring-data jpa jdbc mongodb redis
springData也是和springboot其名的项目
新建springboot模块
spring Native不能勾选否则后面测试会失败
记得改名,不然不会创建同级目录
说明在springboot2.x之后,原来的jedis被替换为lettuce
jedis采用直连,多个线程操作的化是不安全的,如果想要避免不安全,使用jedis pool连接池。bio模式
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数量。更像nio模式
ctrl+f搜索redis
源码分析
@ConditionalOnSingleCandidate(RedisConnectionFactory.class) //我们可以自己定义一个redisTemplate来替换这个默认的! public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { //默认的RedisTemplate没有过多的设置,redis对象都需要序列化! //两个泛型都是object类型,我们使用需要强制转换<String,Object> RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate(RedisConnectionFactory.class) //由于String是Redis中最常使用的类型,所有单独提出来一个bean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
1.导入依赖
操作redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置连接
3.测试
所有的对象都需要序列化不然会报错
企业中所有的pojo类一般都需要序列化
自定义序列化,企业开发中可以直接使用
package com.example.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 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; import java.net.UnknownHostException; @Configuration public class RedisConfig { //编写自己的redsiTemplate @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); // json序列化利用ObjectMapper进行转义 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 2.序列化String类型 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; } }
乱码问题得到解决
//在企业开发中我们80%不会使用原生的方式去编写代码,一般使用工具类RedisUtils
网上可以直接搜索RedisUtils
```
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
redisTemplate封装
*/
@Component
public class RedisUtils {
//此写法可防止RedisTemplate 注入失败
private static RedisTemplate redisTemplate;
@Autowired
public void setRedisTemplate(RedisTemplate redisTemplate) {
RedisUtils.redisTemplate = redisTemplate;
}
/**
/**
/**
/**
//String=
/**
/**
/**
/**
/**
//Map=
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
//set=
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
/**
}
```
自动装配并使用
@Autowired
private RedisUtils redisUtils;
启动时通过配置文件来启动
面试和工作的重点
redis是内存数据库,如果不将内存中的数据保存到磁盘,那么一旦服务器进程退出,服务器中的数据状态也会消失,所以redis提供了持久化的功能
原理是redis会单独创建(fork)一个与当前线程一模一样的子进程来进行持久化,这个子线程的所有数据(变量。环境变量,程序程序计数器等)都和原进程一模一样,会先将数据写入到一个临时文件中,待持久化结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程不进行任何的io操作,这就确保了极高的性能。
我们默认的就是RDB,一般情况下不需要修改这个配置
如果需要进行大规模数据恢复,且对于数据的完整性回复不是非常的敏感,那么RDB方式比AOF的方式更加高效。RDb的缺点是最后一次持久化的数据可能会丢失
rdb保存的文件是dump.rdb
搜索snapshot
删除dump.rdb文件
一分钟内设置5次key,自动生成dump.rdb文件
进程被占用,重启云服务器
触发机制
1.满足save规则
2.执行flushdb命令
3.退出redis,都会产生rdb文件
如何恢复rdb文件
1.只需要将rdb文件放在redis启动目录就可以,redis启动时就会自动检测dum.rdb文件,恢复其中数据
2.rdb文件存放的位置
优点:
1.适合大规模的数据恢复
2.对数据的完整性要求不高
缺点
1.需要一定时间间隔进程操作,如果redis意外宕机(直接杀掉redis的进程),这个最后一次修改的数据就没了
2.fork进程的时候,会占用一定的内存空间
因此有时候生产环境会将这个文件备份
追加文件
将我们所以的命令都记录下来,回复的时候将这个文件全部执行一遍
以日志的形式来记录每一个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件不许改写文件,redis重启之处会会读取该文件重新构建数据,inother words,如果redis重启就会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
aof保存的文件是appendonly.aof
默认为no需要手动进行开启
重启redis后读取.aof文件
如果aof文件有错位,这时候redis启动不起来,我们需要修复配置文件
redis给我们提供了一个工具
redis-check-aof --fix appendonly.aof
优点:
1.每一次修改都同步,文件的完整性会更好
2.每秒同步一次可能会丢失一秒的数据
3.从不同不效率最高
缺点
1.对于数据文件来说,aof’远大于rdb,修复速度比rdb慢
2.aof运行效率要比rdb慢,所以redis的默认配置是rdb持久化。
aof默认是文件无线追加,文件会越来越大
如果aof文件大于64m,fork一个新进程将我们的文件进行重写
只做缓存,如果你只希望数据在服务器运行的时候存在,你可以不使用持久化
redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接受消息。微信,微博,关注系统
redis客户端可以订阅任意数量的频道
订阅/发布消息图
第一个:消息发送者
第二个:频道
第三个:消息订阅者
订阅频道
发布消息
接收到消息
原理:
1.实时消息系统(注册相当于订阅系统频道,收到系统信息)
2.实时聊天(频道当作聊天室,将信息回显给所有人即可)
3.订阅关注系统
稍微复杂的系统我们会使用消息中间件MQ
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
master处理写请求,slave处理读请求
主从复制,读写分离,80%的情况下都在进行读操作!减缓服务器压力,架构中经常使用
最低一主二从
主从复制的作用主要包括:
数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
高可用(集群)基石:主从复制还是哨兵和集群能够实施的基础。
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(宕机),原因如下:
1、从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较 大;
2、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有 内存用作Redis存储内存,一般来说,单台Redis大使用内存不应该超过20G。
电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。
对于这种场景,我们可以使如下这种架构:
在公司的中,主从复制必须要使用,因为在真实项目中不可能单机使用redis!
只配置从库,不用配置主库
查看当前库的信息
127.0.0.1:6379> info replication//查看当前库的信息
# Replication
role:master//角色
connected_slaves:0//没有从机
master_failover_state:no-failover
master_replid:2987407e2725e84643b9b79b471ccd0e6fda32c4
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
复制配置文件,开启四个会话,模拟单机多服务环境
正常情况使用多机多服务
修改端口
修改为后台启动
修改日志
修改rdb文件名
启动三个redis服务,可以通过进程信息查看
默认情况下,每台Redis服务器都是主节点
默认情况下三台都是主机,我们一般情况下只要配置从机!认老大
一主(79)二从(80,81)
给从机配主人
主机有slave信息
真实的主从配置应该在配置文件中配置,这样的话是永久的,我们这里使用的命令是暂时的
authorize,配置文件配置地点
主机可以设置值(可以写),从机不能(只能读)
主机中所有的数据都会被从机自动保存
主机写
从机读,但是不能写
主机断开连接,从机依旧能能连接到主机,但是没有写操作,这个时候,主机如果回来,从机依旧可以以获取主机信息
如果是使用命令行配置的主从,如果从机重启就会自动变为主机,只要变为从机,立马会从主机中获取值
slave启动成功连接到一个master后会发送一个sync同步命令
master接受到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制:slave服务在接受到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master继续将所收集到的修改命令一次传给slave,完成同步
但是只要是重新连接一次master,全量复制会被自动执行,我们的数据可以在从机中看到
即做主机又做从机,此时依旧为slave
设置6380为主节点
6080节点信息,依旧为从机
谋朝篡位
当主机宕机对从机执行
slave no one
从机变主机
主从切换的方法是:当主服务器宕机后,需要手动把一台服务器切换为主服务器,需要人工干预,费时费力,还会造成一段时间内服务器不可用。更多的时候我们考虑的是哨兵模式,redis从2.8开始正式提供了sentinel架构来解决这个问题。
谋朝篡位的自动版,能够后台监控主机是否故障,如果故障根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先redis提供了哨兵命令,哨兵是一个独立的进程,作为进程,他会独立运行,其原理是哨兵通过发送命令,等待redis服务器响应,从而监控运行的多个redis实例
哨兵的作用
1.通过发送命令,让redis返回其运行状态,包括主服务器和从服务器
2.当哨兵检测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他从服务器修改配置文件,让他们切换为主机
然而一个哨兵进程对redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵相互监控,这样就形成了多哨兵模式。
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会立马进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用,并且达到一定数量,那么哨兵之间会进行一次投票,投票的结果由一个哨兵发起,进行failover(故障转移操作)。q切换成功后,通过发布订阅模式,让各个哨兵把自己的监控的从服务器实现切换主机,这个过称为客观下线
测试
1.配置哨兵配置文件,名字不能错
myredis:被监控的名称
1:代表主机挂了,slave投票看让谁成为主机
2.配置完成,启动哨兵
查看哨兵日志
如果主机此时回来,只能归并到新的主机下,当作从机,这就是哨兵模式的规则。
优点:
1.哨兵集群,基于主从复制模式,所有主从配置的优点他都有
2.主从可以切换,故障可以转移,系统可用性会更好
3.哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点:
1.redis不好在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦
2.实现哨兵模式的配置其实是很麻烦的,里面有很多选择
哨兵模式的全部配置
。。。。。。。省略
这些配置一般是由运维配置
概念
用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败,当用户很多的时候,缓存都没有命中(秒杀),于是都去请求了持久层数据库。这就会给持久层数据库造成大压力,这时候就相当于出现了缓存穿透。
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,先在控制层进行校验,不符合规则丢弃,从而避免了对底层存储系统的查询压力
缓存空对象
当存储层不命中后,即使返回空对象也将其缓存起来,同时会设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端的数据源
但是这个方法存在两个问题
1.如果空值能被缓存下来,这就意味着缓存需要更多的空间存储更多的键,因为这个当中可能会有很多的空值和键
2.即使对空值设置了过期时间,还会存在缓存层和存储层的数据会有一段时间窗口不一致,这对于需要保持一致性的业务会有影响
微博服务器宕机(热点)
缓存击穿是指一个key非常热点,不停的扛着大量并发,大并发集中对这个点进行访问,当key在这个瞬间失效,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新的数据,并且回写缓存,会导致数据库压力瞬间过大。
是指在某个时间段,缓存集中过期失效。redis宕机
缓存雪崩的原因之一,比如写文本时,马上就要到双十二零点,很快就迎来一波抢购,这波商品比较集中的放入了缓存假设缓存缓存一个小时。到凌晨一点钟,这批商品的缓存就都过期了。而对于这批商品的查询访问都落到数据库上,数据库产生周期性的压力波值。于是所有的请求都会达到存储层,存储层的调用量就会爆增,造成存储器也会挂掉的情况。
其实集中过期不是最致命的,比较致命的缓存雪崩是缓存服务器某个节点宕机或者断网。因为自然原因形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候数据库也会顶住压力,无非是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
双十一:停到一些服务。如退款(保证主要服务可用)
解决方案
1.redis高可用
2.限流降级(springcloud讲过)
3.数据预热
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。