Redis部署openEuler
一、操作过程
①以ROOT用户身份登录服务器
②配置编译环境
使用wget下载相应的redis
云服务已经帮我们安装了所需要的环境
③下载源码
wget http://download.redis.io/releases/redis-4.0.9.tar.gz
④解压包
tar -zxvf redis-4.0.9.tar.gz
⑤进入deps目录。
cd redis-4.0.9/deps
⑥ 编译Redis依赖库。
make -j4 hiredis lua jemalloc linenoise
⑦依次执行如下命令,编译Redis。
cd …
make -j4 #加快编译速度
make install
⑧ 配置编译好的软件。
- 执行如下命令,建立redis配置文件。
cp redis.conf /usr/local/etc/
- 执行如下命令,配置redis为后台启动。
vim /usr/local/etc/redis.conf
将“daemonize” 由“no”改成“yes”//此处设置了守护进程
⑨ 设置Redis开机启动。
- 执行如下命令,将Redis启动脚本放置“/etc/init.d/”目录下,并命名为redis。
cp /root/redis-4.0.9/utils/redis_init_script /etc/init.d/redis
- 执行如下命令,修改脚本内容。
vim /etc/init.d/redis
⑩ 设置服务开启启动。
chkconfig redis on
运行和验证!!!!
- 执行如下命令,查看Redis版本。
redis-server -v
系统会显示如下类似信息,表示Redis的版本是4.0.9。
[root@ecs-1-0002 redis-4.0.9]# redis-server -v
Redis server v=4.0.9 sha=00000000:0 malloc=jemalloc-4.0.3 bits=64 build=1e80e86f2ae3f0d8
- 执行如下命令,查看Redis的CLI版本。
redis-cli -v
系统会显示如下类似信息,表示Redis的CLI版本是4.0.9。
redis-cli 4.0.9
- 执行如下命令,启动redis-server。
service redis start
系统会显示次类似信息,表示Redis启动完成。
\4. 执行如下命令,使用redis-cli连接server,并执行k-v请求。
此时我们可以看到通过set get命令可以对redis进行简单的增添查询,通过del命令可以对redis进行简单的删除。证明此时数据库已经连接部署成功了。
二、原理分析
1.什么是redis
中文文档
http://www.redis.cn/
英文文档
https://redis.io/
开源,免费,提供多种语言API,c语言编写,持久化,k-v型数据库
2.redis可以做什么
区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
支持多种语言
3.Nosql概述
单机mysql时代
遇到的问题?
1.数据量太大
2.数据的索引太大
3.读写混合访问量,服务器承受不住
晋级!
Memcached(缓存)+Mysql+垂直拆分(读写分离)
访问服务器有80%在进行读操作,所以有了缓存
发展过程:优化数据结果和索引–》文件缓存(i/o)–》Memcached
晋级!!
分库分表+水平拆分+mysql集群
解决写的问题
晋级!!!
大数据时代
技术爆炸时代
MySQL关系型数据不够用
4.什么是Nosql
关系型数据库无法应付web2.0时代,尤其是超大规模的社区
暴露出很多问题,Nosql的发展十分迅速,redis的发展是最快的
很多数据的比如个人信息,地理位置不需要固定的格式,不需要多于操作就可以横向扩展
nosql的特点
解耦
1.方便扩展
2.大数据量高性能
3.数 据类型多样性
三、Redis-benchmark性能测试
redis到底有多快?
-
-h指定主机名
-
-p指定端口号
-
-c指定并非连接数
-
-n指定请求数
取set测试为例
四、redis基础知识
默认redis有16个数据库
1.基本操作
数据库切换(select)
keys *可以查看当前所有的key
flushdb可以清空当前数据可
flushall情空全部数据库
2.redis是单线程的
原因:redis是基于内存操作的,CPU并不是redis的性能瓶颈,机器的内存和网络带宽是redis的性能瓶颈的依据。
redis是将所有的数据都放在内存中,所以单线程效率就是最高的,多线程在cpu上下文切换消耗时间。
五、五大基本数据类型(Redis-Key)
127.0.0.1:6379> move guass 1#移除
(integer) 1
127.0.0.1:6379> exists guass
(integer) 0
127.0.0.1:6379> expire zyc 10#设置过期时间
(integer) 1
127.0.0.1:6379> ttl zyc#查看剩余时间
(integer) 6
127.0.0.1:6379> ttl zyc
(integer) 5
127.0.0.1:6379> ttl zyc
(integer) 4
127.0.0.1:6379> ttl zyc
(integer) 4
127.0.0.1:6379> ttl zyc
(integer) 3
127.0.0.1:6379> ttl zyc
(integer) 2
127.0.0.1:6379> ttl zyc
(integer) 2
127.0.0.1:6379> ttl zyc
(integer) 1
127.0.0.1:6379> ttl zyc
(integer) 1
127.0.0.1:6379> ttl zyc
(integer) 0
127.0.0.1:6379> ttl zyc
(integer) -2
127.0.0.1:6379> ttl zyc
(integer) -2
操作!!
127.0.0.1:6379>
[root@ZYC2503190202 ~]# redis-cli
127.0.0.1:6379> append name zyc#拼接字符串,若没有创建,则创建
(integer) 3
127.0.0.1:6379> append name lalala#添加成功后返回字符串长度
(integer) 9
127.0.0.1:6379> get name
"zyclalala"
127.0.0.1:6379> strlen name#字符串长度
(integer) 9
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> incr views
(integer) 3
127.0.0.1:6379> get views
"3"
127.0.0.1:6379> decr views#自减
(integer) 2
127.0.0.1:6379> decr views
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> decr views
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> incrby views 10#设置步长自增
(integer) 9
127.0.0.1:6379> incrby views 10
(integer) 19
127.0.0.1:6379> incrby views 10
(integer) 29
127.0.0.1:6379> get views
"29"
127.0.0.1:6379> decrby views 5
(integer) 24
127.0.0.1:6379> decrby views 5
(integer) 19
127.0.0.1:6379> decrby views 5
(integer) 14
127.0.0.1:6379> get views
"14"
127.0.0.1:6379> keys *
1) "views"
2) "name"
3) "key:__rand_int__"
4) "mylist"
5) "counter:__rand_int__"
6) "myset:__rand_int__"
127.0.0.1:6379> get name
"zyclalala"
127.0.0.1:6379> getrange name 0 3#range截取字符串
"zycl"
127.0.0.1:6379> getrange name 0 -1
"zyclalala"
127.0.0.1:6379> setrange name 1 ss#替换字符串
(integer) 9
127.0.0.1:6379> get name
"zsslalala"
127.0.0.1:6379> setex wowo 30 "world"#设置消亡时间
OK
127.0.0.1:6379> ttl wowo
(integer) 26
127.0.0.1:6379> ttl wowo
(integer) 24
127.0.0.1:6379> ttl wowo
(integer) 23
127.0.0.1:6379> setnx uu "java"#如果不存在则成功
(integer) 1
127.0.0.1:6379> keys *
1) "uu"
2) "views"
3) "name"
4) "key:__rand_int__"
5) "mylist"
6) "counter:__rand_int__"
7) "myset:__rand_int__"
127.0.0.1:6379> ttl wowo
(integer) -2
127.0.0.1:6379> setnx uu "lala"
(integer) 0
127.0.0.1:6379> get uu
"java"
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3#添加多个数据
OK
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#因为k1已经添加所以失败
(integer) 0
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> mset user:1:name zyc user:1:age 20#设置json格式的对象
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zyc"
2) "20"
127.0.0.1:6379> getset db redis#先get 返回get的值然后set
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongdb#先返回get的值然后在将set的值重新赋值
"redis"
127.0.0.1:6379> get db
"mongdb"
六、List
#####################################################################################
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 -2#查看元素,下面是范围的测试
1) "three"
2) "two"
127.0.0.1:6379> lrange list 0 -3
1) "three"
127.0.0.1:6379> lrange list 0 3
1) "three"
2) "two"
3) "one"
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"
127.0.0.1:6379>
127.0.0.1:6379> rpush list right
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379>
127.0.0.1:6379> lpop list#默认以栈的方式存储,若从右边弹出,可以相当于队列
"three"
127.0.0.1:6379> rpop list
"right"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 0#根据下标查看当前的元素
"two"
127.0.0.1:6379> llen list#查看当前的list长度
(integer) 2
127.0.0.1:6379> lrem list 1 one#删除下标元素
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "two"
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "two"
127.0.0.1:6379> lrem list 2 two
(integer) 2
127.0.0.1:6379> lrange list 0 -1
(empty list or set)
127.0.0.1:6379> rpush list l1
(integer) 1
127.0.0.1:6379> rpush list l2
(integer) 2
127.0.0.1:6379> rpush list l3
(integer) 3
127.0.0.1:6379> rpush list l4
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "l1"
2) "l2"
3) "l3"
4) "l4"
127.0.0.1:6379> ltrim list 1 2#截断list的值
OK
127.0.0.1:6379> lrange list 0 -1
1) "l2"
2) "l3"
127.0.0.1:6379> lrange list 0 -1
1) "l2"
127.0.0.1:6379> lpush list hello
(integer) 2
127.0.0.1:6379> lpush list hello1
(integer) 3
127.0.0.1:6379> lpush list hello2
(integer) 4
127.0.0.1:6379> lpush list hello3
(integer) 5
127.0.0.1:6379> rpoplpush list list2#从右侧弹出的第一值插到新的list里
"l2"
127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello2"
3) "hello1"
4) "hello"
127.0.0.1:6379> lrange list2 0 -1
1) "l2"
######################################################################################
127.0.0.1:6379> exists list#是否存在list
(integer) 1
127.0.0.1:6379> exists list1
(integer) 0
127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello2"
3) "hello1"
4) "hello"
127.0.0.1:6379> lset list 0 hellowiwiw#替换对应下标的元素
OK
127.0.0.1:6379> lrange list 0 -1
1) "hellowiwiw"
2) "hello2"
3) "hello1"
4) "hello"
127.0.0.1:6379> lrange list 0 -1
1) "hellowiwiw"
2) "hello2"
3) "hello1"
4) "hello"
127.0.0.1:6379> linsert list before "hello1" "before1"#插入元素,可以选择插入前或者后
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "hellowiwiw"
2) "hello2"
3) "before1"
4) "hello1"
5) "hello"
127.0.0.1:6379> linsert list after "hello1" "after1"
(integer) 6
127.0.0.1:6379> lrange list 0 -1
1) "hellowiwiw"
2) "hello2"
3) "before1"
4) "hello1"
5) "after1"
6) "hello"
七、Set
127.0.0.1:6379> sadd myset "hello" #向set集合种添加元素
(integer) 1
127.0.0.1:6379> sadd myset "hello1"
(integer) 1
127.0.0.1:6379> sadd myset "hello2"
(integer) 1
127.0.0.1:6379> smembers myset #查看set集合
1) "hello2"
2) "hello1"
3) "hello"
127.0.0.1:6379> sismember myset hello #查看是否存在在该集合中
(integer) 1
127.0.0.1:6379> sismember myset helloew
(integer) 0
127.0.0.1:6379> scard myset #计算元素总和
(integer) 3
127.0.0.1:6379> SRANDMEMBER myset # 从set集合中随机抽出一个元素
"hello2"
127.0.0.1:6379> SRANDMEMBER myset
"hello1"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset
"hello1"
127.0.0.1:6379> SRANDMEMBER myset
"hello2"
127.0.0.1:6379> SMEMBERS myset
1) "hello2"
2) "hello1"
3) "hello"
127.0.0.1:6379> SREM myset hello # 删除固定元素
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello2"
2) "hello1"
127.0.0.1:6379> SPOP myset # 随机弹出某个元素
"hello1"
127.0.0.1:6379> SMEMBERS myset
1) "hello2"
127.0.0.1:6379> SADD myset2 "lalalal"
(integer) 1
127.0.0.1:6379> SMOVE myset myset2 hello2 # 移动元素到另一个set
(integer) 1
127.0.0.1:6379> SMEMBERS myset
(empty list or set)
127.0.0.1:6379> SMEMBERS myset2
1) "hello2"
2) "lalalal"
127.0.0.1:6379> SADD t1 1
(integer) 1
127.0.0.1:6379> SADD t1 2
(integer) 1
127.0.0.1:6379> SADD t1 3
(integer) 1
127.0.0.1:6379> SADD t1 4
(integer) 1
127.0.0.1:6379> SADD t2 2
(integer) 1
127.0.0.1:6379> SADD t2 4
(integer) 1
127.0.0.1:6379> SADD t2 5
(integer) 1
127.0.0.1:6379> SADD t2 6
(integer) 1
127.0.0.1:6379> SMEMBERS t1
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> SMEMBERS t2
1) "2"
2) "4"
3) "5"
4) "6"
127.0.0.1:6379> SDIFF t1 t2 # 求差集
1) "1"
2) "3"
127.0.0.1:6379> SINTER t1 t2 # 求交集
1) "2"
2) "4"
127.0.0.1:6379> SUNION t1 t2 # 求并集
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
八、Hash
本质和String没有太大的区别
127.0.0.1:6379> HSET myhash field1 yc #添加
(integer) 1
127.0.0.1:6379> HGET myhash field1
"yc"
127.0.0.1:6379> HMSET myhash field1 yycc field2 ycyc #多个添加
OK
127.0.0.1:6379> HGETALL myhash #获取所有
1) "field1"
2) "yycc"
3) "field2"
4) "ycyc"
127.0.0.1:6379> hdel myhash field1 #删除
(integer) 1
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "ycyc"
127.0.0.1:6379> hlen myhash#长度
(integer) 1
127.0.0.1:6379> HEXISTS myhash field1
(integer) 0
127.0.0.1:6379> HEXISTS myhash field2
(integer) 1
127.0.0.1:6379> HKEYS myhash #获取所有的key
1) "field2"
127.0.0.1:6379> HVALS myhash # 获取所有的value
1) "ycyc"
127.0.0.1:6379> HSET myhash num 1
(integer) 1
127.0.0.1:6379> HINCRBY myhash num 1 #自增
(integer) 2
127.0.0.1:6379> HINCRBY myhash num 1
(integer) 3
127.0.0.1:6379> HINCRBY myhash num 1
(integer) 4
127.0.0.1:6379> HINCRBY myhash num -1
(integer) 3
127.0.0.1:6379> HSETNX myhash filed22 lala
(integer) 1
127.0.0.1:6379> HSETNX myhash filed22 lawdw
(integer) 0
九、Zset
在set的基础上增加了一个值
127.0.0.1:6379> zadd salary 2500 xaioming #添加
(integer) 1
127.0.0.1:6379> zadd salary 2800 xaiozhang
(integer) 1
127.0.0.1:6379> zadd salary 5000 xaiodi
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf #以score排序
1) "xaioming"
2) "xaiozhang"
3) "xaiodi"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
1) "xaioming"
2) "xaiozhang"
3) "xaiodi"
127.0.0.1:6379> ZREM salary xaioming #移除
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
1) "xaiozhang"
2) "xaiodi"
127.0.0.1:6379> ZCARD salary #zset内元素数量
(integer) 2
127.0.0.1:6379> ZREVRANGE salary 0 -1
1) "xaiodi"
2) "xaiozhang"
127.0.0.1:6379> ZCOUNT salary 1 2
(integer) 0
十、三种特殊数据类型
①geospatial地理位置
推算地理位置信息。两地之间距离,方圆几公里之内的人
127.0.0.1:6379> GEOADD china:city 116.40 39.90 beijing #添加 城市经纬度
(integer) 1
127.0.0.1:6379> GEOADD china:city 121.47 31.23 shanghai 106.50 29.53 chongqing 120.16 30.24 hangzhou 108.96 34.26 xian #连续添加多个
(integer) 4
127.0.0.1:6379> GEOPOS china:city beijing # 获取该地的坐标
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> GEOPOS china:city xian
1) 1) "108.96000176668167114"
2) "34.25999964418929977"
127.0.0.1:6379> GEOPOS china:city shanghai
1) 1) "121.47000163793563843"
2) "31.22999903975783553"
127.0.0.1:6379> GEODIST china:city beijing xian # 查看两地距离
"910056.5237"
127.0.0.1:6379> GEODIST china:city beijing xian km
"910.0565"
127.0.0.1:6379> GEOHASH china:city beijing xian # 通过该地的坐标生成hash值
1) "wx4fbxxfke0"
2) "wqj6zky6bn0"
127.0.0.1:6379> GEOPOS china:city shanghai
1) 1) "121.47000163793563843"
2) "31.22999903975783553"
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km # 计算经度 维度 在1000km'内的城市
1) "chongqing"
2) "xian"
3) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "chongqing"
2) "xian"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist # 并且显示距离
1) 1) "chongqing"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord # 显示经纬度
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord count 1 #只返回范围内的一个
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord count 2 #返回范围内的两个
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km # 以该地为中心 1000km范围的城市
1) "beijing"
2) "xian"
②Hyperloglog
基数?
就是不重复的元素,有接受误差
127.0.0.1:6379> PFADD keke a b c d e f g h #创建第一组
(integer) 1
127.0.0.1:6379> PFCOUNT keke # 统计数量
(integer) 8
127.0.0.1:6379> PFADD keke1 a b g h j k l
(integer) 1
127.0.0.1:6379> PFMERGE keke2 keke keke1 # 合并两组的数据,只合并不重复的
OK
127.0.0.1:6379> PFCOUNT keke2
(integer) 11
若可以接收误差则首选hyperloglog,不能接受误差则选用set
③Bitmaps
只有0和1两个状态,可以用于处理数据量大的情况
eg:统计用户信息是否活跃,登录或者未登录
测试某一天是否打卡
127.0.0.1:6379> SETBIT sign 0 1
(integer) 0
127.0.0.1:6379> SETBIT sign 1 1
(integer) 0
127.0.0.1:6379> SETBIT sign 2 1
(integer) 0
127.0.0.1:6379> SETBIT sign 3 0
(integer) 0
127.0.0.1:6379> SETBIT sign 4 0
(integer) 0
127.0.0.1:6379> SETBIT sign 5 0
(integer) 0
127.0.0.1:6379> SETBIT sign 6 1
(integer) 0
127.0.0.1:6379> BITCOUNT sign # 统计打卡的天数
(integer) 4
127.0.0.1:6379> GETBIT sign 2 #获取某天的打卡情况
(integer) 1
十一、事物处理
Redis单条命令是保存原子性的,但整个事物不保证原子性。
一次性,循序性,排他性,执行一序列的命令。
事物:一组命令的集合
正常执行事物:
- 开启事物(multi)
- 命令入队(…)
- 执行事务(exec)
放弃事物:
- discard取消事物
- 事物中命令不执行
异常:
- 编译型异常(是其中代码出现问题)其所有的命令都不会执行
- 运行时异常:事物队列中出现语法性异常,其他命令是可以正常执行的,错误命令抛出异常
监控 Watch
悲观锁:
- 认为什么时候都会出现问题,无论做什么都会加锁
乐观锁:
- 认为什么时候都不会出现问题,无论做什么都不会加锁
- 更新的时候取出version进行判断version
- 若在事物操作未提交前另外一个线程修改了值,这个时候加了watch的乐观锁的事物就会执行失败
- 若发现事物执行失败:
- 1.先解锁
- 2.获取最新的值,再次监视
- 3.对比监视的值,如果没有变化,那么就执行成功,如果变化就执行失败
十二、Jedis
Redis推荐的java连接开发工具,使用Java操作redis的中间件。
jedis操作
1.导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
2.测试连接
import redis.clients.jedis.Jedis;
public class redisText {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
}
}
3.jedis事物处理
package com.yc;
import com.alibaba.fastjson.JSONObject;
import org.json.JSONArray;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class redisText {
public static void main(String[] args) {
//所有的方法都是之前redis的操作
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.flushDB();
JSONObject jsonObject= new JSONObject();
jsonObject.put("zyc","shay");
jsonObject.put("babe","shir");
//开启事物
Transaction multi = jedis.multi();
String s = jsonObject.toJSONString();
//jedis.watch(s);开监控
try {
multi.set("user1",s);
multi.set("user2",s);
multi.exec();//执行事物
}catch (Exception e){
multi.discard();//放弃事物
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
}
十三、SpringBoot整合Redis
boot2.0.x后jedis被替代为lettuce
jedis:采用直连,多线程操作的话 是不安全的,如果想要避免不安全的操作需要使用jedis pool连接池,BIO模式
lettuce:采用Netty,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程数据。更类似NIO模式。
源码分析,通过找寻配置类可以找到redis的自动配置类和配置文件
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
依赖配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
连接测试配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
package com.zyc.redis;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class RedisApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Test
void contextLoads() {
//操做String
//其中所有的操作都用方法opsforxxx实现ops(operation)然后在通过点就可以使用具体的操作方法,eg set get方法
//redisTemplate.opsForValue().set("","");
//除了基本的操作其他的方法都可以直接通过redistemplate比如事物。。
//下面是通过获得reids的连接工厂创建连接然后实现操作。
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisConnection connection = connectionFactory.getConnection();
//connection.flushDb();
redisTemplate.opsForValue().set("one","zyc");
System.out.println(redisTemplate.opsForValue().get("one"));
}
}
对象序列化问题
//对象需要序列化
//1.真实开发中需要把user转换成json格式存入,此处使用的ObjectMapper.writeValueAsString()方法
User user = new User("yc", "handsome");
String json = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",json);
System.out.println(redisTemplate.opsForValue().get("user"));
//2.也可以对user类让他继承序列化implements Serializable
User user = new User("yc", "handsome");
redisTemplate.opsForValue().set("user",user);
System.out.println(redisTemplate.opsForValue().get("user"));
工具类的使用
https://www.cnblogs.com/zhzhlong/p/11434284.html
十四、redis.config
1.配置文件unit单位大小写不敏感
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.
# include .\path\to\local.conf
# include c:\path\to\other.conf
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)生产环境
# warning (only very important / critical messages are logged)
loglevel notice 日志级别
logfile "" 日志文件位置名
databases 16 默认数据库数量16
由于Redis是基于内存的数据库,需要将数据由内存持久化到文件中
持久化方式:
- RDB
- AOF
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DBoWvBQ9-1645083995690)(C:\Users\89737\AppData\Roaming\Typora\typora-user-images\image-20220116192801318.png)]
stop-writes-on-bgsave-error yes 持久化出错是否需要继续工作
rdbcompression yes 是否压缩rdb文件,需要消耗cpu资源
rdbchecksum yes 保存rdb文件的时候进行错误的检查校验
dir ./ rdb文件保存的目录
client
maxclients 10000 最大客户端数量
maxmemory <bytes> 最大内存限制
maxmemory-policy noeviction # 内存达到限制值的处理策略
config set maxmemory-policy volatile-lru
maxmemory-policy 六种方式
**1、volatile-lru:**只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
**3、volatile-random:**随机删除即将过期key
**4、allkeys-random:**随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
十五、持久化
RDB Redis Databases
在指定时间间隔后,将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复 ;
默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件中进行自定义
工作原理
在进行 RDB 的时候,redis 的主线程是不会做io
操作的,主线程会 fork 一个子线程来完成该操作;
Redis 调用forks。同时拥有父进程和子进程。
子进程将数据集写入到一个临时 RDB 文件中。
当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。
)
触发机制
- save的规则满足的情况下,会自动触发rdb原则
- 执行flushall命令,也会触发我们的rdb原则
- 退出redis,也会自动产生rdb文件(shutdown)
save
使用 save
命令,会立刻对当前内存中的数据进行持久化 ,但是会阻塞,也就是不接受其他操作了;
由于
save
命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save
命令执行速度会非常慢,阻塞所有客户端的请求。
flushall命令
flushall
命令也会触发持久化 ;
触发持久化规则
满足配置条件中的触发条件 ;
bgsave
bgsave
是异步进行,进行持久化的时候,redis
还可以将继续响应客户端请求 ;
bgsave和save对比
命令 | save | bgsave |
---|---|---|
IO类型 | 同步 | 异步 |
阻塞? | 是 | 是(阻塞发生在fock(),通常非常快) |
复杂度 | O(n) | O(n) |
优点 | 不会消耗额外的内存 | 不阻塞客户端命令 |
缺点 | 阻塞客户端命令 | 需要fock子进程,消耗内存 |
优缺点
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
- fork进程的时候,会占用一定的内容空间。
Append Only File
将我们所有的命令都记录下来,history,恢复的时候就把这个文件全部再执行一遍
什么是AOF
快照功能(RDB)并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、以及未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。
appendonly no yes则表示启用AOF
默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!
如果这个aof文件有错位,这时候redis是启动不起来的,我需要修改这个aof文件
redis给我们提供了一个工具redis-check-aof --fix
appendonly yes # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分的情况下,rdb完全够用
appendfilename "appendonly.aof"
# appendfsync always # 每次修改都会sync 消耗性能
appendfsync everysec # 每秒执行一次 sync 可能会丢失这一秒的数据
# appendfsync no # 不执行 sync ,这时候操作系统自己同步数据,速度最快
优点
- 每一次修改都会同步,文件的完整性会更加好
- 没秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
缺点
- 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
- Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化
RDB和AOF的比较
RDB 和 AOF 对比如何选择使用哪种持久化方式?
RDB | AOF | |
---|---|---|
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 丢数据 | 根据策略决定 |
一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。
十六、Redis发布与订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
命令
PSUBSCRIBE pattern [pattern…] 订阅一个或多个符合给定模式的频道。
PUNSUBSCRIBE pattern [pattern…] 退订一个或多个符合给定模式的频道。
PUBSUB subcommand [argument[argument]] 查看订阅与发布系统状态。
PUBLISH channel message 向指定频道发布消息
SUBSCRIBE channel [channel…] 订阅给定的一个或多个频道。
SUBSCRIBE channel [channel…] 退订一个或多个频道
------------订阅端----------------------
127.0.0.1:6379> SUBSCRIBE sakura # 订阅sakura频道
Reading messages... (press Ctrl-C to quit) # 等待接收消息
1) "subscribe" # 订阅成功的消息
2) "sakura"
3) (integer) 1
1) "message" # 接收到来自sakura频道的消息 "hello world"
2) "sakura"
3) "hello world"
1) "message" # 接收到来自sakura频道的消息 "hello i am sakura"
2) "sakura"
3) "hello i am sakura"
--------------消息发布端-------------------
127.0.0.1:6379> PUBLISH sakura "hello world" # 发布消息到sakura频道
(integer) 1
127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 发布消息
(integer) 1
-----------------查看活跃的频道------------
127.0.0.1:6379> PUBSUB channels
1) "sakura"
原理
每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。
客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。
缺点
如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。
应用
消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
多人在线聊天室。
稍微复杂的场景,我们就会使用消息中间件MQ处理。
十七、redis主从复制
概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。
作用
数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
高可用基石:主从复制还是哨兵和集群能够实施的基础。
为什么使用集群
单台服务器难以负载大量的请求
单台服务器故障率高,系统崩坏概率大
单台服务器内存容量有限。
环境配置
我们在讲解配置文件的时候,注意到有一个replication
模块 (见Redis.conf中第8条)
查看当前库的信息:info replication
127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 从机数量
master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
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
既然需要启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息:
- 端口号
- pid文件名
- 日志文件名
- rdb文件名
使用SLAVEOF host port
就可以为从机配置主机了。//认老大
使用规则
-
从机只能读,不能写,主机可读可写但是多用于写。
-
当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
-
当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理
-
第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:
从机手动执行命令slaveof no one,这样执行以后从机会独立出来成为一个主机 使用哨兵模式(自动选举)
如果主机断开了连接,我们可以使用SLAVEOF no one
让自己变成主机!其他的节点就可以手动连接到最新的主节点(手动)!如果这个时候老大修复了,那么久重新连接!
十八、哨兵模式
**主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。*这不是一种推荐的方式,更多时候,我们优先考虑*哨兵模式。
哨兵的作用:
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
哨兵的核心配置
sentinel monitor mymaster 127.0.0.1 6379
优点:
哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
主从可以切换,故障可以转移,系统的可用性更好
哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
实现哨兵模式的配置其实是很麻烦的,里面有很多配置项
完整的哨兵模式配置文件 sentinel.conf
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
十九、缓存穿透与雪崩
概念
在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。解决方案
布隆过滤器
对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。
缓存空对象
一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。
这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿(量太大,缓存过期)
概念
相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。
比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。
解决方案
设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
缓存雪崩
概念
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。