Redis
1.Redis简介
数据库发展要求
- 1.0 用户数据访问量不大
- 2.0 利用缓存技术缓解数据压力,不同业务访问不同的数据库
- 3.0 采用主从读写分离技术,建立主库master和从库slave,一般主库响应事务性操作,如增删改,从数据库执行非事务性操作,查询
- 4.0 mysql是主从数据库,读写分离的,但仍不能满足性能需求,myISAM使用的是表锁,在对表进行事务操作时,会被锁住,并发性能差,因此建立mysql集群思想,进行分库分表处理
- 互联网的特点:高并发、高可扩、高性能
Redis
- 是一种运行在内存上的,并发性能强,运行速度快的NoSql数据库
- 与传统数据库相比,无须事先为要存储的数据建立字段,随时可以存储自定义格式数据,而传统关系型数据对数据格式有要求,增删麻烦,扩展性不强
- 常见的应用场景
- 缓存,将频繁访问的数据放在Redis里,在运行内存中,访问速度快,无须IO流进入关系型数据库
- 排行榜 通过Redis的sortSet技术实现排行
- 计算器、限速器:通过Redis自增统计用户点赞数,用户访问次数,若mysql实现需要对数据库频繁读写,压力大;限速器可以限制用户访问API的频率,常用于抢购时防止用户疯狂点击造成不必要的系统压力
- 好友关系 利用集合命令实现共同好友、爱好等功能
- 简单消息队列,实现到货通知、邮件发送,通过Redis自身发布/订阅,list实现异步解耦
- session共享 session保存在单个服务器上,在集群中,同一个用户可能进入不同服务器,导致session失效,需要频繁登录,而Redis保证无论哪一台服务器都能获取session
- RedisVSmemcache
- memcache和Redis都是内存数据库,但memcache可以缓存多种形式的数据信息,如图片视频等
- memcache数据结构单一,采用键值对的形式,而Redis支持多种数据结构,如list、set、hash等,有效的减少了网络IO次数
- 虚拟内存上,memcache都存放在内存中,而Redis在物理内存不足时,可以将一些很久没用的数据存放到磁盘中
- 存储数据安全,memcache断电丢失数据,永久丢失不可恢复,Redis可以定期更新保存数据到磁盘中,通过RBD、AOF进行数据恢复
- Redis与MongoDB
- 二者非竞争关系,协同合作,MongoDB本质上是硬盘数据库,执行复杂查询时会消耗大量资源,且不可避免的进入硬盘查询数据,
- Redis或memcache是内存数据库,可以作为中间层进行缓存和加速,将整个页面经常访问的对象存放到Redis中,并定时更新到MongoDB中
分布式数据库
- 关系型数据库事务的四大特性ACID
- 分布式数据库的三大特性为CAP
- C consistency 强一致性,所有节点在同一时间的数据完全一致,每台服务器中访问数据一致
- A Avaliability 高可用性,服务一直可以可用,不出现用户操作失败/访问超时等不好的体验
- P partition tolerance 分区容错性 当某节点或网络分区故障时仍能对外提供一致性或可用性的服务
- 分布式数据库按原则分,可以分为AP原则, CA原则, CP原则
- 一般情况下,很难满足三大特性,在保证P的情况下,要么保障高可用性,数据不能保障同步一致;要么保障一致性,等待失去联系的节点恢复前不允许提供服务
- AP 满足高可用性,分区容错,但一致性性能不高
- CA 单电集群,满足一致性,高可用性,但扩展性不高
- CP 满足一致性,分区容错,但系统速度性能不高
2. Redis安装及简单命令
- 拷贝文件并解压,安装gcc命令,进入Redis目录进行编译安装
tar -zxvf redis-5.0.4.tar.gz yum -y install gcc make make install
- 设置后台运行方式,修改配置文件daemonize=yes,以配置文件形式启动,默认有16个数据库,编号从0开始
vim /usr/redis-5.0.4/redis.conf daemonize yes
redis-server /usr/redis-5.0.4/redis.conf - 关闭Redis数据库,包括单点实例关闭和多实例关闭
redis-cli shutdown redis-cli -p 6379 shutdown
- 监听端口,查看后台进程
netstat -lntp | grep 6379 ps -ef|grep redis
- 连接Redis并测试
redis-cli ping
- 保存数据进行查询,测试性能
# 保存数据 set k1 china # 获取数据 get kl redis-benchmark [root@localhost bin]# redis-benchmark ====== PING_INLINE ====== 100000 requests completed in 1.80 seconds # 1.8秒处理了10万个请求,性能要看笔记 本的配置高低 50 parallel clients 3 bytes payload keep alive: 1 87.69% <= 1 milliseconds 99.15% <= 2 milliseconds 99.65% <= 3 milliseconds 99.86% <= 4 milliseconds 99.92% <= 5 milliseconds 99.94% <= 6 milliseconds 99.97% <= 7 milliseconds 100.00% <= 7 milliseconds 55524.71 requests per second # 每秒处理的请求数量
- 清空数据库
flushdb flushall - 模糊查询通配符,*代表任意多个字符,?代表单个字符,[]设置查询字符范围
keys 查询条件
- 查看键命令
#查看数据库键的数量 127.0.0.1:6379>dbsize (integer) 1 #exists key:判断某个key是否存在 127.0.0.1:6379> exists k1 (integer) 1 # 存在 127.0.0.1:6379> exists y1 (integer) 0 # 不存在 #move key db:移动(剪切,粘贴)键到几号库 127.0.0.1:6379> move x1 8 # 将x1移动到8号库 (integer) 1 # 移动成功 127.0.0.1:6379> exists x1 # 查看当前库中是否存在x1 (integer) 0 # 不存在(因为已经移走了) 127.0.0.1:6379> select 8 # 切换8号库 OK 127.0.0.1:6379[8]> keys * # 查看当前库中的所有键 1) "x1" #ttl key:查看键还有多久过期(-1永不过期,-2已过期) 127.0.0.1:6379[8]> ttl x1 (integer) -1 # 永不过期 #expire key 秒:为键设置过期时间(生命倒计时) 127.0.0.1:6379[8]> set k1 v1 # 保存k1 OK 127.0.0.1:6379[8]> ttl k1 # 查看k1的过期时间 (integer) -1 # 永不过期 127.0.0.1:6379[8]> expire k1 10 # 设置k1的过期时间为10秒(10秒后自动销毁) (integer) 1 # 设置成功 127.0.0.1:6379[8]> get k1 # 获取k1 "v1" 127.0.0.1:6379[8]> ttl k1 # 查看k1的过期时间 (integer) 2 # 还有2秒过期 127.0.0.1:6379[8]> get k1 (nil) 127.0.0.1:6379[8]> keys * # 从内存中销毁了 (empty list or set) #type查看数据类型 127.0.0.1:6379[8]> type k1 string # k1的数据类型是会string字符串
3. 基本数据类型
- 字符串String
- set/get/del/append/strlen
127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> get k1 "v1" 127.0.0.1:6379> del k1 (integer) 1 127.0.0.1:6379> get k1 (nil) 127.0.0.1:6379> set k1 (error) ERR wrong number of arguments for 'set' command 127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> append k1 abc (integer) 5 127.0.0.1:6379> get k1 "v1abc" 127.0.0.1:6379> strlen k1
- incr/decr/incrby/decrby:加减操作,操作的必须是数字类型
127.0.0.1:6379> set k1 1 OK 127.0.0.1:6379> incr k1 (integer) 2 127.0.0.1:6379> incre k1 2 (error) ERR unknown command `incre`, with args beginning with: `k1`, `2`, 127.0.0.1:6379> incrby k1 2 (integer) 4 127.0.0.1:6379> decrby k1 2 (integer) 2 127.0.0.1:6379> decr k1 (integer) 1
- getrange/setrange:类似between...and
127.0.0.1:6379> set k1 adcehf OK 127.0.0.1:6379> get k1 "adcehf" 127.0.0.1:6379> getrange k1 0 -1 "adcehf" 127.0.0.1:6379> get range k1 1 3 (error) ERR wrong number of arguments for 'get' command 127.0.0.1:6379> getrange k1 1 3 "dce" 127.0.0.1:6379> setrange k1 1 abc (integer) 6 127.0.0.1:6379> get k1 "aabchf"
- setex/setnx 添加数据的同时添加生命周期/如果存在就不允许添加,不存在就添加
127.0.0.1:6379> get k1 "aabchf" 127.0.0.1:6379> setex k2 10 abc OK 127.0.0.1:6379> get k2 "abc" 127.0.0.1:6379> ttl k2 (integer) 4 127.0.0.1:6379> ttl k2 (integer) 1 127.0.0.1:6379> ttl k2 (integer) -2 127.0.0.1:6379> setnx k2 acb (integer) 1 127.0.0.1:6379> get k2 "acb" 127.0.0.1:6379> set k1 ade OK 127.0.0.1:6379> get k1 "ade" 127.0.0.1:6379> setnx k1 sadi (integer) 0 127.0.0.1:6379> get k1 "ade"
- mset/mget/msetnx
127.0.0.1:6379> mset k1 v1 k2 v1 k3 v3 OK 127.0.0.1:6379> msetnx k1 v1 k4 v4 (integer) 0 127.0.0.1:6379> get k4 (nil) 127.0.0.1:6379> msetnx k4 v4 k5 v5 (integer) 1 127.0.0.1:6379> get k4 "v4" 127.0.0.1:6379> mget k1 k2 k3 k4 k5 1) "v1" 2) "v1" 3) "v3" 4) "v4" 5) "v5"
- getset:先get后set
127.0.0.1:6379> getset k5 vv5 "v5" 127.0.0.1:6379> get k5 "vv5" 127.0.0.1:6379> getset k6 v6 (nil) 127.0.0.1:6379> get k6 "v6"
- list 类型
- lpush/rpush/lrange
127.0.0.1:6379> lpush list01 1 2 3 4 5 # 从上往下添加 (integer) 5 127.0.0.1:6379> keys * 1) "list01" 127.0.0.1:6379> lrange list01 0 -1 # 查询list01中的全部数据0表示开 始,-1表示结尾 1) "5" 2) "4" 3) "3" 4) "2" 5) "1" 127.0.0.1:6379> rpush list02 1 2 3 4 5 # 从下往上添加 (integer) 5 127.0.0.1:6379> lrange list02 0 -1 1) "1" 2) "2" 3) "3" 4) "4" 5) "5"
- lpop/rpop:移除第一个元素(上左下右)
127.0.0.1:6379> lpop list02 # 从左(上)边移除第一个元素 "1" 127.0.0.1:6379> rpop list02 # 从右(下)边移除第一个元素 "5
- lindex:根据下标查询元素(从左向右,自上而下)
127.0.0.1:6379> lrange list01 0 -1 1) "5" 2) "4" 3) "3" 4) "2" 5) "1" 127.0.0.1:6379> lindex list01 2 # 从上到下数,下标为2的值 "3" 127.0.0.1:6379> lindex list01 1 # 从上到下数,下标为1的值 "4
- llen:返回集合长度
127.0.0.1:6379> llen list01 (integer) 5
- lrem:删除n个value
127.0.0.1:6379> lpush list01 1 2 2 3 3 3 4 4 4 4 (integer) 10 127.0.0.1:6379> lrem list01 2 3 # 从list01中移除2个3 (integer) 2 127.0.0.1:6379> lrange list01 0 -1 1) "4" 2) "4" 3) "4" 4) "4" 5) "3" 6) "2" 7) "2" 8) "1
- ltrim:截取指定范围的值,别的全扔掉
127.0.0.1:6379> lpush list01 1 2 3 4 5 6 7 8 9 (integer) 9 127.0.0.1:6379> lrange list01 0 -1 1) "9" # 下标0 2) "8" # 下标1 3) "7" # 下标2 4) "6" # 下标3 5) "5" # 下标4 6) "4" # 下标5 7) "3" # 下标6 8) "2" # 下标7 9) "1" # 下标8 127.0.0.1:6379> ltrim list01 3 6 # 截取下标3~6的值,别的全扔掉 OK 127.0.0.1:6379> lrange list01 0 -1 1) "6" 2) "5" 3) "4" 4) "3"
- rpoplpush:从一个集合搞一个元素到另一个集合中(右出一个,左进一个)
127.0.0.1:6379> rpush list01 1 2 3 4 5 (integer) 5 127.0.0.1:6379> lrange list01 0 -1 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 127.0.0.1:6379> rpush list02 1 2 3 4 5 (integer) 5 127.0.0.1:6379> lrange list02 0 -1 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 127.0.0.1:6379> rpoplpush list01 list02 # list01右边出一个,从左进入到 list02的第一个位置 "5" 127.0.0.1:6379> lrange list01 0 -1 1) "1" 2) "2" 3) "3" 4) "4" 127.0.0.1:6379> lrange list02 0 -1 1) "5" 2) "1" 3) "2" 4) "3" 5) "4" 6) "5"
- lset:改变某个下标的某个值
127.0.0.1:6379> lrange list02 0 -1 1) "2" 2) "2" 3) "3" 4) "4" 5) "5" 127.0.0.1:6379> lset list02 0 1 OK 127.0.0.1:6379> lrange list02 0 -1 1) "1" 2) "2" 3) "3" 4) "4" 5) "5"
- linsert:插入元素(指定某个元素之前/之后)
127.0.0.1:6379> linsert list02 before 2 4 (integer) 6 127.0.0.1:6379> lrange list02 0 -1 1) "1" 2) "4" 3) "2" 4) "3" 5) "4" 6) "5" 127.0.0.1:6379> linsert list02 after 2 4 (integer) 7 127.0.0.1:6379> lrange list02 0 -1 1) "1" 2) "4" 3) "2" 4) "4" 5) "3" 6) "4" 7) "5"
- set 数据类型,元素不允许重复
- sadd/smembers/sismember:添加/查看/判断是否存在
127.0.0.1:6379> sadd set01 1 2 3 3 2 1 (integer) 3 127.0.0.1:6379> smembers set01 1) "1" 2) "2" 3) "3" 127.0.0.1:6379> sismemver set01 (error) ERR unknown command `sismemver`, with args beginning with: `set01`, 127.0.0.1:6379> sismember set01 (error) ERR wrong number of arguments for 'sismember' command 127.0.0.1:6379> sismember set01 4 (integer) 0 127.0.0.1:6379> sismember set01 2 (integer) 1
- scard:获得集合中的元素个数
127.0.0.1:6379> scard set01 (integer) 3
- srem:删除集合中的元素
127.0.0.1:6379> srem set01 4 (integer) 0 127.0.0.1:6379> srem set01 2 (integer) 1 127.0.0.1:6379> smember set01 (error) ERR unknown command `smember`, with args beginning with: `set01`, 127.0.0.1:6379> smembers set01 1) "1" 2) "3" 127.0.0.1:6379>
- srandmember:从集合中随机获取几个元素
127.0.0.1:6379> sadd set01 1 2 4 3 4 5 6 7 (integer) 5 127.0.0.1:6379> smembers set01 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 6) "6" 7) "7" 127.0.0.1:6379> srandmember set01 3 1) "4" 2) "7" 3) "1" 127.0.0.1:6379>
- spop:随机出栈(移除)
127.0.0.1:6379> spop set01 2 1) "5" 2) "4" 127.0.0.1:6379> smembers set01 1) "1" 2) "2" 3) "3" 4) "6" 5) "7"
- smove:移动元素:将key1某个值赋值给key2
127.0.0.1:6379> sadd set02 1 1 2 3 (integer) 3 127.0.0.1:6379> smove set01 set02 7 (integer) 1 127.0.0.1:6379> smembers set02 1) "1" 2) "2" 3) "3" 4) "7" 127.0.0.1:6379>
- 数学集合类
- 交集:sinter
- 并集:sunion
- 差集:sdiff
127.0.0.1:6379> sinter set01 set02 1) "1" 2) "2" 3) "3" 127.0.0.1:6379> sdiff set01 set02 1) "6" 127.0.0.1:6379> sdiff set02 set01 1) "7" 127.0.0.1:6379> sunion set01 set02 1) "1" 2) "2" 3) "3" 4) "6" 5) "7"
- hash数据类型:类似java里面的Map<String,Object>
- hset/hget/hmset/hmget/hgetall/hdel:添加/得到/多添加/多得到/得到全部/删除属性
127.0.0.1:6379> hset class num 1 (integer) 1 127.0.0.1:6379> get class (error) WRONGTYPE Operation against a key holding the wrong kind of value 127.0.0.1:6379> hget class num "1" 127.0.0.1:6379> hmset class num 1 grade 2 student 3 OK 127.0.0.1:6379> hmget class num grade student 1) "1" 2) "2" 3) "3" 127.0.0.1:6379> hmgetall (error) ERR unknown command `hmgetall`, with args beginning with: 127.0.0.1:6379> hgetall class 1) "num" 2) "1" 3) "grade" 4) "2" 5) "student" 6) "3" 127.0.0.1:6379> hdel class num (integer) 1 127.0.0.1:6379> hgetall class 1) "grade" 2) "2" 3) "student" 4) "3"
- hlen:返回元素的属性个数
127.0.0.1:6379> hlen class (integer) 2
- hexists:判断元素是否存在某个属性
127.0.0.1:6379> hexists class student (integer) 1
- hkeys/hvals:获得属性的所有key/获得属性的所有value
127.0.0.1:6379> hkeys class 1) "grade" 2) "student" 127.0.0.1:6379> hvals class 1) "2" 2) "3"
- hincrby/hincrbyfloat:自增(整数)/自增(小数)
127.0.0.1:6379> hincrby class student 55 (integer) 58 127.0.0.1:6379> hincrby class grade 0.89 (error) ERR value is not an integer or out of range 127.0.0.1:6379> hgetall class 1) "grade" 2) "4" 3) "student" 4) "58" 127.0.0.1:6379> hincrybyfloat class grade 0.89 (error) ERR unknown command `hincrybyfloat`, with args beginning with: `class`, `grade`, `0.89`, 127.0.0.1:6379> hincrbyfloat class grade 0.89 "4.89" 127.0.0.1:6379> hgetall class 1) "grade" 2) "4.89" 3) "student" 4) "58"
- hsetnx:添加的时候,先判断是否存在
127.0.0.1:6379> hsetnx class grade 1 (integer) 0 127.0.0.1:6379> hgetall class 1) "grade" 2) "4.89" 3) "student" 4) "58" 127.0.0.1:6379> hsetnx class num 3 (integer) 1 127.0.0.1:6379> hgetall class 1) "grade" 2) "4.89" 3) "student" 4) "58" 5) "num" 6) "3"
- 有序集合ZSET
- zadd/zrange (withscores):添加/查询
127.0.0.1:6379> zadd vip 10 vip1 20 vip2 30 vip3 40 vip4 50 vip5 (integer) 5 127.0.0.1:6379> zrange vip 0 -1 1) "vip1" 2) "vip2" 3) "vip3" 4) "vip4" 5) "vip5" 127.0.0.1:6379> zrange vip 0 -1 withscores 1) "vip1" 2) "10" 3) "vip2" 4) "20" 5) "vip3" 6) "30" 7) "vip4" 8) "40" 9) "vip5" 10) "50"
- zrangebyscore:模糊查询
127.0.0.1:6379> zrangebyscore vip 10 30 1) "vip1" 2) "vip2" 3) "vip3" 127.0.0.1:6379> zrangebyscore vip 10 40 whthscores (error) ERR syntax error 127.0.0.1:6379> zrangebyscore vip 10 40 withscores 1) "vip1" 2) "10" 3) "vip2" 4) "20" 5) "vip3" 6) "30" 7) "vip4" 8) "40"
- zrem:删除元素
127.0.0.1:6379> zrem vip vip1 vip4 (integer) 2 127.0.0.1:6379> zrange vip 0 -1 withscores 1) "vip2" 2) "20" 3) "vip3" 4) "30" 5) "vip5" 6) "50"
- zcard/zcount/zrank/zscore:集合长度/范围内元素个数/得元素下标/通过值得分数
127.0.0.1:6379> zcard vip (integer) 3 127.0.0.1:6379> zcount vip 10 20 (integer) 1 127.0.0.1:6379> zrank vip vip3 (integer) 1 127.0.0.1:6379> zscore vip vip5 "50"
- zrevrank:逆序找下标(从下向上)
127.0.0.1:6379> zrevrank zset01 vip3 (integer) 1
- zrevrange:逆序查询
127.0.0.1:6379> zrange zset01 0 -1 # 顺序查询 1) "vip1" 2) "vip2" 3) "vip3" 4) "vip4" 127.0.0.1:6379> zrevrange zset01 0 -1 # 逆序查询 1) "vip4" 2) "vip3" 3) "vip2" 4) "vip1"
- zrevrangebyscore:逆序范围查找
127.0.0.1:6379> zrevrangebyscore zset01 30 20 # 逆序查询分数在30~20之间的 (注意,先写大值,再写小值) 1) "vip3" 2) "vip2" 127.0.0.1:6379> zrevrangebyscore zset01 20 30 # 如果小值在前,则结果为null (empty list or set
4.持久化
- RDB:Redis DataBase ,在指定的时间间隔内将内存中的数据写到磁盘中,默认保存在/usr/local/bin中,文件名dump.rdb;
- 自动备份
- redis是内存数据库,当我们每次用完redis,关闭linux时,按道理来说,内存释放,redis中的数据也会随之消失
- 但我们再次启动redis的时候,数据并未消失,因为每次关机时,Redis会进行自动备份,保存到/usr/local/bin/dump.rdb中
- 默认的自动备份策略不利于我们测试,所以修改redis.conf文件中的自动备份策略
vim redis.conf /SNAP # 搜索 save 900 1 # 900秒内,至少变更1次,才会自动备份 save 120 10 # 120秒内,至少变更10次,才会自动备份 save 60 10000 # 60秒内,至少变更10000次,才会自动备份
- 如果只使用Redis缓存功能,可以直接一个空字符串来实现停用:save ""
- 当我们使用shutdown命令,redis会自动将数据库备份,dump.rdb文件更新
- 自动备份
- 与RDB相关的配置
- stop-writes-on-bgsave-error:
- yes:当后台备份时候发生错误,前台停止写入
- no:不管死活,就是往里怼
- rdbcompression:对于存储到磁盘中的快照,是否启动LZF压缩算法,一般都会启动
- yes:启动
- no:不启动(不想消耗CPU资源,可关闭)
- rdbchecksum:在存储快照后,是否启动CRC64算法进行数据校验,开启后,大约增加10%左右的CPU消耗;如果希望获得最大的性能提升,可以选择关闭;
- dbfilename:快照备份文件名字
- dir:快照备份文件保存的目录,默认为当前目录
- stop-writes-on-bgsave-error:
- RDB的优势和劣势
- 优:适合大规模数据恢复,对数据完整性和一致行要求不高;
- 劣:一定间隔备份一次,意外down掉,就失去最后一次快照的所有修改
- AOF:Append Only File
- 以日志的形式记录每个写操作,将redis执行过的写指令全部记录下来(读操作不记录)
- 只许追加文件,不可以改写文件
- redis在启动之初会读取该文件从头到尾执行一遍,这样来重新构建数据
- 步骤:
- 更改redis.conf文件
appendonly yes appendfilename appendonly.aof- 重启Redis,以新配置文件启动
- 连接数据库,添加修改最后清空并退出
- 找到aof文件,修改并重新启动连接,数据恢复
- 当AOF和RDB两种备份策略同时开启系统会优先载入AOF来恢复原始数据,AOF比RDB数据保存的完整性更高!1. 动手试试,编辑appendonly.aof,胡搞乱码,保存退出
- 可以通过修复AOF 文件杀光不符合Redis语法规范的所有代码 reids-check-aof --fix appendonly.aof
-
与AOF 相关的配置
- appendonly,开启AOF模式
- appendfilename:aof文件名
- appendfsync:追写策略
- always:每次数据变更就会立即记录到磁盘,影响性能但数据完整性好
- everysec:默认设置,异步操作,每秒记录,如果一秒内宕机会有数据丢失
- no:不追写
- no-appendfsync-on-rewrite:重写时是否运用Appendfsync追写策略,默认no,保障数据安全性
- AOF采用文件追加的方式,文件会越来越大,为了解决这个问题,增加了重写机制,redis会自动记录上一次AOF文件的大小
- 当AOF文件大小达到预先设定的大小时,redis就会启动,AOF文件进行内容压缩,只保留可以恢复数据的最小指令集合
- auto-aof-rewrite-percentage:如果AOF文件大小已经超过原来的100%,也就是一倍,才重写压缩
- auto-aof-rewrite-min-size:如果AOF文件已经超过了64mb,才重写压缩
- 总结:RDB:只用作后备用途,建议15分钟备份一次就好;AOF在最恶劣的情况下,也只丢失不超过2秒的数据,数据完整性比较高,但代价太大,会带来持续的IO
对硬盘的大小要求也高,默认64mb太小了,主从模式是主流
5. 事务
- 三特性
- 隔离性:所有命令都会按照顺序执行,事务在执行的过程中,不会被其他客户端送来的命令打断
- 没有隔离级别:队列中的命令没有提交之前都不会被实际的执行,
- 不保证原子性:如果一个命令失败,但是别的命令可能会执行成功,没有回滚
- discard放弃此前的所有事务操作
- 一句命令报错,整个事务都不会进行
- 三步走
- 开启multi
- 入队queued
- 执行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> set k3 v3 QUEUED 127.0.0.1:6379> exec 1) OK 2) OK 3) OK 127.0.0.1:6379> mget k1 k2 k3 1) "v1" 2) "v2" 3) "v3"
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1111 QUEUED 127.0.0.1:6379> set k2 v2222 QUEUED 127.0.0.1:6379> discard OK 127.0.0.1:6379> get k1 "v1"
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k4 v4 QUEUED 127.0.0.1:6379> setlalala (error) ERR unknown command `setlalala`, with args beginning with: 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> keys * 1) "vip" 2) "k3" 3) "k2" 4) "k1"
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> incr k1 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) (error) ERR value is not an integer or out of range 3) OK 4) OK 127.0.0.1:6379> keys (error) ERR wrong number of arguments for 'keys' command 127.0.0.1:6379> keys * 1) "k3" 2) "k2" 3) "k1"
- 与关系型数据库事务相比,multi:可以理解成关系型事务中的 begin;exec :可以理解成关系型事务中的 commit;discard :可以理解成关系型事务中的 rollback
- watch监控
127.0.0.1:6379> set in 100 # 收入100元 OK 127.0.0.1:6379> set out 0 # 支出0元 OK 127.0.0.1:6379> watch in # 监控收入in OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> decrby in 20 QUEUED 127.0.0.1:6379> incrby out 20 QUEUED 127.0.0.1:6379> exec (nil) # 在exec之前,我开启了另一个窗口(线程),对监控的in做了修改,所以本次的事务将 被打断(失效),类似于“乐观锁”
- Redis发布与订阅:发送者(pub)发送消息,订阅者(sub)接收消息,,进程间的通信机制
127.0.0.1:6379> subscribe cctv1 cctv2 cctv6 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "cctv1" 3) (integer) 1 1) "subscribe" 2) "cctv2" 3) (integer) 2 1) "subscribe" 2) "cctv6" 3) (integer) 3 1) "message" 2) "cctv1" 3) "chunwan" #当其他客户publish消息,相应的message会显示在订阅频道中 127.0.0.1:6379> publish cctv1 chunwan (integer) 1
6.主从复制
Redis集群策略,配从库不配主库
- 一主二仆
- 准备三台服务器,配置redis.conf文件
bind 0.0.0.0
- 启动三台Redis并查看每台机器的角色,都是master
info replication - 测试:将三个机器全都清空,第一台添加值 mset k1 v1 k2 v2 ;其余两台机器,复制(找大哥 ) slaveof 192.168.80.128 6379 ;第一台再添加值 set k3 v3
- 结果:slave之前的k1和k2可以拿到,slave之后的k3能拿到,数据立刻同步
- 主机可以添加数据,从机只负责读取
- 主机下线shutdown,从机仍然是slave模式,并显示master已下线,仍可以获取数据
- 主机重启,从机仍然是slave,显示master上线
- 从机下线,主机会显示少了一个slave,从机重启后,不会与原来的master形成主仆关系,从机自身为master
- 准备三台服务器,配置redis.conf文件
- 继承
- 使用java面向对象继承中的传递性来解决这个问题,减轻主机的负担
- 形成多继承
127.0.0.1:6379> slaveof 192.168.80.128 6379 # 129跟随128 OK 127.0.0.1:6379>slaveof 192.168.80.129 6379 # 130跟随129 OK
- 手动成为主库
- 1个主机,2个从机,当1个主机挂掉了,只能从2个从机中再次选1个主机
- 1为master,2和3为slave,当1挂掉后,2篡权为master
slaveof no one # 2上执行,2成为master slaveof 192.168.80.129 6379 # 3跟随2号
-
- 当1再次回归,2和3已经形成新的集群,和1没有任何的关系了
- 复制原理
![]()
- 全量复制:Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份slave接收到数据文件后,存盘,并加载到内存中;(步骤1234)
- 增量复制:Slave初始化后,开始正常工作时主服务器发生的写操作同步到从服务器的过程;(步骤56)
- 只要是重新连接master,一次性(全量复制)同步将自动执行;
- Redis主从同步策略:主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。
- 如果有需要,slave 在任何时候都可以发起全量同步。
- redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
- 哨兵模式,自动成为master,自动投票机制
- Sentinel是Redis的高可用性解决方案:由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级
为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求 - 模拟测试
- 1主,2和3从
- 每一台服务器中创建一个配置文件sentinel.conf,名字绝不能错,并编辑sentinel.conf
# sentinel monitor 被监控主机名(自定义) ip port 票数 sentinel monitor redis141 192.168.80.128 6379 1
- 启动服务的顺序:主Redis --> 从Redis --> Sentinel1/2/3
redis-sentinel sentinel.conf
- 将1号shutdown,后台自动发起激烈的投票,选出新的老大
127.0.0.1:6379> shutdown not connected> exit
- 查看最后权利的分配:3成为master,2还是slave
- 如果1号再次重启,自己成为了master,和3平起平坐;过了几秒之后,被哨兵检测到了1号机的归来,1号成为slave
- Sentinel是Redis的高可用性解决方案:由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级
- 主从模式缺点:由于所有的写操作都是在master上完成的;然后再同步到slave上,所以两台机器之间通信会有延迟;当系统很繁忙的时候,延迟问题会加重;slave机器数量增加,问题也会加重
7.总配置
# Redis 配置文件示例 # 注意单位: 当需要配置内存大小时, 可能需要指定像1k,5GB,4M等常见格式 # # 1k => 1000 bytes # 1kb => 1024 bytes # 1m => 1000000 bytes # 1mb => 1024*1024 bytes # 1g => 1000000000 bytes # 1gb => 1024*1024*1024 bytes # # 单位是对大小写不敏感的 1GB 1Gb 1gB 是相同的。
#INCLUDES 包含文件相关 # 可以在这里包含一个或多个其他的配置文件。如果你有一个适用于所有Redis服务器的标准配置模板,但也需要一些每个服务器自定义的设置,这个功能将很有用。被包含的配置文件也可以包含其他配置文 件 # #注意“inclue”选项不能被admin或Redis哨兵的"CONFIG REWRITE"命令重写。 # 因为Redis总是使用最后解析的配置行最为配置指令的值, 这个文件的开头配置includes来避免它在运行时重写配置。 # 如果相反你想用includes的配置覆盖原来的配置,你最好在该文件的最后使用include # # include /path/to/local.conf # include /path/to/other.conf
#GENERAL 综合配置 # 默认Rdis不会作为守护进程运行。如果需要的话配置成'yes' # 注意配置成守护进程后Redis会将进程号写入文件/var/run/redis.pid daemonize no
# 当以守护进程方式运行时,默认Redis会把进程ID写到 /var/run/redis.pid。你可以在这里修改路径。 pidfile /var/run/redis.pid # 接受连接的特定端口,默认是6379 # 如果端口设置为0,Redis就不会监听TCP套接字。 port 6379 # TCP listen() backlog. # server在与客户端建立tcp连接的过程中,SYN队列的大小 # 在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。注意Linux内核默默地将这个值减小# 到/proc/sys/net/core/somaxconn的值,所以需要确认增大somaxconn和tcp_max_syn_backlog # 两个值来达到想要的效果。 tcp-backlog 511 # 默认Redis监听服务器上所有可用网络接口的连接。可以用"bind"配置指令跟一个或多个ip地址来实现 # 监听一个或多个网络接口 bind 192.168.1.100 10.0.0.1 # bind 127.0.0.1 # 指定用来监听Unix套套接字的路径。没有默认值, 所以在没有指定的情况下Redis不会监听Unix套接字 unixsocket /tmp/redis.sock # unixsocketperm 755 # 一个客户端空闲多少秒后关闭连接。(0代表禁用,永不关闭) timeout 0 # TCP keepalive. # 如果非零,则设置SO_KEEPALIVE选项来向空闲连接的客户端发送ACK,由于以下两个原因这是很有用的: # 1)能够检测无响应的对端 2)让该连接中间的网络设备知道这个连接还存活 # 在Linux上,这个指定的值(单位:秒)就是发送ACK的时间间隔。 # 注意:要关闭这个连接需要两倍的这个时间值。 # 在其他内核上这个时间间隔由内核配置决定 # 这个选项的一个合理值是60秒 tcp-keepalive 0 # 指定服务器调试等级 # 可能值: # debug (大量信息,对开发/测试有用) # verbose (很多精简的有用信息,但是不像debug等级那么多) # notice (适量的信息,基本上是你生产环境中需要的) # warning (只有很重要/严重的信息会记录下来) loglevel notice # 指明日志文件名。也可以使用"stdout"来强制让Redis把日志信息写到标准输出上。 # 注意:如果Redis以守护进程方式运行,而设置日志显示到标准输出的话,日志会发送到/dev/null logfile "" # 要使用系统日志记录器,只要设置 "syslog-enabled" 为 "yes" 就可以了。 # 然后根据需要设置其他一些syslog参数就可以了。 # syslog-enabled no # 指明syslog身份 # syslog-ident redis # 指明syslog的设备。必须是user或LOCAL0 ~ LOCAL7之一。 # syslog-facility local0 # 设置数据库个数。默认数据库是 DB 0, # 可以通过select <dbid> (0 <= dbid <= 'databases' - 1 )来为每个连接使用不同的数据库。 databases 16
##SNAPSHOTTING 快照,持久化操作配置 # #把数据库存到磁盘上: # save <seconds> <changes> # 会在指定秒数和数据变化次数之后把数据库写到磁盘上。 # #下面的例子将会进行把数据写入磁盘的操作: # 900秒(15分钟)之后,且至少1次变更 # 300秒(5分钟)之后,且至少10次变更 # 60秒之后,且至少10000次变更 # # 注意:你要想不写磁盘的话就把所有 "save" 设置注释掉就行了。 # # 通过添加一条带空字符串参数的save指令也能移除之前所有配置的save指令 # 像下面的例子: save "" save 900 1 save 300 10 save 60 10000 # 默认如果开启RDB快照(至少一条save指令)并且最新的后台保存失败,Redis将会停止接受写操作 # 这将使用户知道数据没有正确的持久化到硬盘,否则可能没人注意到并且造成一些灾难。 # 如果后台保存进程能重新开始工作,Redis将自动允许写操作 # 然而如果你已经部署了适当的Redis服务器和持久化的监控,你可能想关掉这个功能以便于即使是硬盘,权限等出问题了Redis也能够像平时一样正常工作, stop-writes-on-bgsave-error yes # 当导出到 .rdb 数据库时是否用LZF压缩字符串对象? # 默认设置为 "yes",因为几乎在任何情况下它都是不错的。 # 如果你想节省CPU的话你可以把这个设置为 "no",但是如果你有可压缩的key和value的话,那数据文件就会更大了。 rdbcompression yes # 因为版本5的RDB有一个CRC64算法的校验和放在了文件的最后。这将使文件格式更加可靠但在生产和加载RDB文件时,这有一个性能消耗(大约10%),所以你可以关掉它来获取最好的性能。 # #生成的关闭校验的RDB文件有一个0的校验和,它将告诉加载代码跳过检查 rdbchecksum yes # 持久化数据库的文件名 dbfilename dump.rdb # 工作目录 # 数据库会写到这个目录下,文件名就是上面的 "dbfilename" 的值,累加文件也放这里。 # 注意你这里指定的必须是目录,不是文件名。 dir ./
# REPLICATION 主从复制的配置 # 主从同步。通过 slaveof 指令来实现Redis实例的备份。 # 注意,这里是本地从远端复制数据。也就是说,本地可以有不同的数据库文件、绑定不同的IP、监听不同的端口。 # slaveof <masterip> <masterport> # 如果master设置了密码保护(通过 "requirepass" 选项来配置),那么slave在开始同步之前必须进行身份验证,否则它的同步请求会被拒绝。 # masterauth <master-password> # 当一个slave失去和master的连接,或者同步正在进行中,slave的行为有两种可能: # 1) 如果 slave-serve-stale-data 设置为 "yes" (默认值),slave会继续响应客户端请求,可能是正常数据,也可能是还没获得值的空数据。 # 2) 如果 slave-serve-stale-data 设置为 "no",slave会回复"正在从master同步(SYNC with master in progress)"来处理各种请求,除了 INFO 和 SLAVEOF 命令。 # slave-serve-stale-data yes # 你可以配置salve实例是否接受写操作。可写的slave实例可能对存储临时数据比较有用(因为写入salve的数据在同master同步之后将很容被删除),但是如果客户端由于配置错误在写入时也可能产生一些问 题。 # 从Redis2.6默认所有的slave为只读 # 注意:只读的slave不是为了暴露给互联网上不可信的客户端而设计的。它只是一个防止实例误用的保护层。 # 一个只读的slave支持所有的管理命令比如config,debug等。为了限制你可以用'renamecommand'来隐藏所有的管理和危险命令来增强只读slave的安全性 slave-read-only yes # slave根据指定的时间间隔向master发送ping请求。时间间隔可以通过 repl_ping_slave_period 来设置。 默认10秒。 repl-ping-slave-period 10 # 以下选项设置同步的超时时间 # 1)slave在与master SYNC期间有大量数据传输,造成超时 # 2)在slave角度,master超时,包括数据、ping等 # 3)在master角度,slave超时,当master发送REPLCONF ACK pings # #确保这个值大于指定的repl-ping-slave-period,否则在主从间流量不高时每次都会检测到超时 # # repl-timeout 60 # 是否在slave套接字发送SYNC之后禁用 TCP_NODELAY ? # 如果你选择“yes”Redis将使用更少的TCP包和带宽来向slaves发送数据。但是这将使数据传输到slave上有延迟,Linux内核的默认配置会达到40毫秒 # 如果你选择了 "no" 数据传输到salve的延迟将会减少但要使用更多的带宽,默认我们会为低延迟做优化,但高流量情况或主从之间的跳数过多时,把这个选项设置为“yes” repl-disable-tcp-nodelay no # 设置数据备份的backlog大小。backlog是一个slave在一段时间内断开连接时记录salve数据的缓冲 # 所以一个slave在重新连接时,不必要全量的同步,而是一个增量同步就足够了,将在断开连接的这段时间内slave丢失的部分数据传送给它。 # #同步的backlog越大,slave能够进行增量同步并且允许断开连接的时间就越长。backlog只分配一次并且至少需要一个slave连接 # repl-backlog-size 1mb # 当master在一段时间内不再与任何slave连接,backlog将会释放。以下选项配置了从最后一个slave断开开始计时多少秒后,backlog缓冲将会释放。0表示永不释放backlog
# repl-backlog-ttl 3600 # slave的优先级是一个整数展示在Redis的Info输出中。如果master不再正常工作了,哨兵将用它来 # 选择一个slave提升=升为master。 # 优先级数字小的salve会优先考虑提升为master,所以例如有三个slave优先级分别为10,100,25,哨兵将挑选优先级最小数字为10的slave。 # 0作为一个特殊的优先级,标识这个slave不能作为master,所以一个优先级为0的slave永远不会被哨兵挑选提升为master # 默认优先级为100 slave-priority 100 # 如果master少于N个延时小于等于M秒的已连接slave,就可以停止接收写操作。N个slave需要是“oneline”状态 # 延时是以秒为单位,并且必须小于等于指定值,是从最后一个从slave接收到的ping(通常每秒发送)开始计数。例如至少需要3个延时小于等于10秒的slave用下面的指令: # # min-slaves-to-write 3 # min-slaves-max-lag 10 # 两者之一设置为0将禁用这个功能。 ## 默认 min-slaves-to-write 值是0(该功能禁用)并且 min-slaves-max-lag 值是10。
#SECURITY 安全相关配置 # 要求客户端在处理任何命令时都要验证身份和密码。 # 这个功能在有你不信任的其它客户端能够访问redis服务器的环境里非常有用。 # 为了向后兼容的话这段应该注释掉。而且大多数人不需要身份验证(例如:它们运行在自己的服务器上) # 警告:因为Redis太快了,所以外面的人可以尝试每秒150k的密码来试图破解密码。这意味着你需要一个高强度的密码,否则破解太容易了。 # requirepass foobared # 命令重命名 # 在共享环境下,可以为危险命令改变名字。比如,你可以为 CONFIG 改个其他不太容易猜到的名字,这样内部的工具仍然可以使用,而普通的客户端将不行。 # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 # 也可以通过改名为空字符串来完全禁用一个命令 # rename-command CONFIG "" # 请注意:改变命令名字被记录到AOF文件或被传送到从服务器可能产生问题。
#LIMITS 范围配置 # 设置最多同时连接的客户端数量。默认这个限制是10000个客户端,然而如果Redis服务器不能配置处理文件的限制数来满足指定的值,那么最大的客户端连接数就被设置成当前文件限制数减32(因为Redis服务器保留了一些文件描述符作为内部使用) #一旦达到这个限制,Redis会关闭所有新连接并发送错误'max number of clients reached' # maxclients 10000 # 不要用比设置的上限更多的内存。一旦内存使用达到上限,Redis会根据选定的回收策略(参见:maxmemmory-policy)删除key # #如果因为删除策略Redis无法删除key,或者策略设置为 "noeviction",Redis会回复需要更多内存的错误信息给命令。例如,SET,LPUSH等等,但是会继续响应像Get这样的只读命令。 # #在使用Redis作为LRU缓存,或者为实例设置了硬性内存限制的时候(使用 "noeviction" 策略)的时候 # 警告:当有多个slave连上达到内存上限的实例时,master为同步slave的输出缓冲区所需内存不计算在使用内存中。这样当驱逐key时,就不会因网络问题 / 重新同步事件触发驱逐key的循环,反过来slaves的输出缓冲区充满了key被驱逐的DEL命令,这将触发删除更多的key, # 直到这个数据库完全被清空为止 # 总之...如果你需要附加多个slave,建议你设置一个稍小maxmemory限制,这样系统就会有空闲的内存作为slave的输出缓存区(但是如果最大内存策略设置为"noeviction"的话就没必要了)
# maxmemory <bytes> # 最大内存策略:如果达到内存限制了,Redis如何选择删除key。你可以在下面五个行为里选: # volatile-lru -> 根据LRU算法生成的过期时间来删除。 # allkeys-lru -> 根据LRU算法删除任何key。 # volatile-random -> 根据过期设置来随机删除key。 # allkeys->random -> 无差别随机删。 # volatile-ttl -> 根据最近过期时间来删除(辅以TTL) # noeviction -> 谁也不删,直接在写操作时返回错误。 # 注意:对所有策略来说,如果Redis找不到合适的可以删除的key都会在写操作时返回一个错误。 # 目前为止涉及的命令:set setnx setex append # incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd # sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby # zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby # getset mset msetnx exec sort # 默认值如下: # # maxmemory-policy volatile-lru # LRU和最小TTL算法的实现都不是很精确,但是很接近(为了省内存),所以你可以用样本量做检测。 # 例如:默认Redis会检查3个key然后取最旧的那个,你可以通过下面的配置指令来设置样本的个数。 # maxmemory-samples 3
#APPEND ONLY MODE AOF模式配置 # 默认情况下,Redis是异步的把数据导出到磁盘上。这种模式在很多应用里已经足够好,但Redis进程出问题或断电时可能造成一段时间的写操作丢失(这取决于配置的save指令)。 # #AOF是一种提供了更可靠的替代持久化模式,例如使用默认的数据写入文件策略(参见后面的配置) # 在遇到像服务器断电或单写情况下Redis自身进程出问题但操作系统仍正常运行等突发事件时,Redis能只丢失1秒的写操作。 # AOF和RDB持久化能同时启动并且不会有问题。 # 如果AOF开启,那么在启动时Redis将加载AOF文件,它更能保证数据的可靠性。 # 请查看 http://redis.io/topics/persistence 来获取更多信息. appendonly no # 纯累加文件名字(默认:"appendonly.aof") appendfilename "appendonly.aof" # fsync() 系统调用告诉操作系统把数据写到磁盘上,而不是等更多的数据进入输出缓冲区。 # 有些操作系统会真的把数据马上刷到磁盘上;有些则会尽快去尝试这么做。 # Redis支持三种不同的模式: # no:不要立刻刷,只有在操作系统需要刷的时候再刷。比较快。# always:每次写操作都立刻写入到aof文件。慢,但是最安全。 # everysec:每秒写一次。折中方案。 # 默认的 "everysec" 通常来说能在速度和数据安全性之间取得比较好的平衡。根据你的理解决定,如果你能放宽该配置为"no" 来获取更好的性能(但如果你能忍受一些数据丢失,可以考虑使用默认的快照持久化模式),或者相反,用“always”会比较慢但比everysec要更安全。 # 请查看下面的文章来获取更多的细节 # http://antirez.com/post/redis-persistence-demystified.html # 如果不能确定,就用 "everysec" # appendfsync always appendfsync everysec # appendfsync no # 如果AOF的同步策略设置成 "always" 或者 "everysec",并且后台的存储进程(后台存储或写入AOF日志)会产生很多磁盘I/O开销。某些Linux的配置下会使Redis因为 fsync()系统调用而阻塞很久。 # 注意,目前对这个情况还没有完美修正,甚至不同线程的 fsync() 会阻塞我们同步的write(2)调用。 # #为了缓解这个问题,可以用下面这个选项。它可以在 BGSAVE 或 BGREWRITEAOF 处理时阻止 fsync()。 # #这就意味着如果有子进程在进行保存操作,那么Redis就处于"不可同步"的状态。 # 这实际上是说,在最差的情况下可能会丢掉30秒钟的日志数据。(默认Linux设定) # #如果把这个设置成"yes"带来了延迟问题,就保持"no",这是保存持久数据的最安全的方式。 no-appendfsync-on-rewrite no # 自动重写AOF文件 # 如果AOF日志文件增大到指定百分比,Redis能够通过 BGREWRITEAOF 自动重写AOF日志文件。 # 工作原理:Redis记住上次重写时AOF文件的大小(如果重启后还没有写操作,就直接用启动时的AOF大小) # 这个基准大小和当前大小做比较。如果当前大小超过指定比例,就会触发重写操作。你还需要指定被重写日志的最小尺寸,这样避免了达到指定百分比但尺寸仍然很小的情况还要重写。 # 指定百分比为0会禁用AOF自动重写特性。 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
################################ LUA SCRIPTING ############################### # 设置lua脚本的最大运行时间,单位为毫秒,redis会记个log,然后返回error。当一个脚本超过了最大时限。只有SCRIPT KILL和SHUTDOWN NOSAVE可以用。第一个可以杀没有调write命令的东西。要是已 经调用了write,只能用第二个命令杀。 lua-time-limit 5000
################################## SLOW LOG ################################### # 是redis用于记录慢查询执行时间的日志系统。由于slowlog只保存在内存中,因此slowlog的效率很高,完全不用担心会影响到redis的性能。 # 只有query执行时间大于slowlog-log-slower-than的才会定义成慢查询,才会被slowlog进行记录。 # 单位是微妙 slowlog-log-slower-than 10000# slowlog-max-len表示慢查询最大的条数 slowlog-max-len 128
############################ EVENT NOTIFICATION ############################## # 这个功能可以让客户端通过订阅给定的频道或者模式,来获知数据库中键的变化,以及数据库中命令的执行情况,所以在默认配置下,该功能处于关闭状态。 # notify-keyspace-events 的参数可以是以下字符的任意组合,它指定了服务器该发送哪些类型的通知: # K 键空间通知,所有通知以 __keyspace@__ 为前缀 # E 键事件通知,所有通知以 __keyevent@__ 为前缀 # g DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知 # $ 字符串命令的通知 # l 列表命令的通知 # s 集合命令的通知 # h 哈希命令的通知 # z 有序集合命令的通知 # x 过期事件:每当有过期键被删除时发送 # e 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送 # A 参数 g$lshzxe 的别名 # 输入的参数中至少要有一个 K 或者 E,否则的话,不管其余的参数是什么,都不会有任何通知被分发。详细使用可以参考http://redis.io/topics/notifications notify-keyspace-events ""
############################### ADVANCED CONFIG ############################### # 单位字节:数据量小于等于hash-max-ziplist-entries的用ziplist,大于hash-max-ziplistentries用hash hash-max-ziplist-entries 512 # value大小小于等于hash-max-ziplist-value的用ziplist,大于hash-max-ziplist-value用 hash。 hash-max-ziplist-value 64 # 数据量小于等于list-max-ziplist-entries用ziplist(压缩列表),大于list-max-ziplistentries用list。 list-max-ziplist-entries 512 # value大小小于等于list-max-ziplist-value的用ziplist,大于list-max-ziplist-value用 list。 list-max-ziplist-value 64 # 数据量小于等于set-max-intset-entries用iniset,大于set-max-intset-entries用set。 set-max-intset-entries 512 # 数据量小于等于zset-max-ziplist-entries用ziplist,大于zset-max-ziplist-entries用 zset。 zset-max-ziplist-entries 128 # value大小小于等于zset-max-ziplist-value用ziplist,大于zset-max-ziplist-value用 zset。 zset-max-ziplist-value 64 # 基数统计的算法 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数
# 设置HyeperLogLog的字节数限制,这个值通常在0~15000之间,默认为3000,基本不超过16000。value大小小于等于hll-sparse-max-bytes使用稀疏数据结构(sparse),大于hll-sparse-maxbytes使用稠密的数据结构(dense)。一个比16000大的value是几乎没用的,建议的value大概为 3000。如果对CPU要求不高,对空间要求较高的,建议设置到10000左右。 hll-sparse-max-bytes 3000 # 重置hash。 Redis将在每100毫秒时使用1毫秒的CPU时间来对redis的hash表进行重新hash,可以降低内存的使用。当你的使用场景中,有非常严格的实时性需要,不能够接受Redis时不时的对请求有2毫秒 的延迟的话,把这项配置为no。如果没有这么严格的实时性要求,可以设置为yes,以便能够尽可能快的释放内存。 activerehashing yes # 对于Redis服务器的输出(也就是命令的返回值)来说,其大小通常是不可控制的。有可能一个简单的命令,能够产生体积庞大的返回数据。另外也有可能因为执行了太多命令,导致产生返回数据的速率超过了往 客户端发送的速率,这是也会导致服务器堆积大量消息,从而导致输出缓冲区越来越大,占用过多内存,甚至导致系统崩溃。 # 用于强制断开出于某些原因而无法以足够快的速度从服务器读取数据的客户端的连接。 #对于normal client,包括monitor。第一个0表示取消hard limit,第二个0和第三个0表示取消,soft limit,normal client默认取消限制,因为如果没有寻问,他们是不会接收数据的。 client-output-buffer-limit normal 0 0 0 #对于slave client和MONITER client,如果client-output-buffer一旦超过256mb,又或者超过64mb持续60秒,那么服务器就会立即断开客户端连接。 client-output-buffer-limit slave 256mb 64mb 60 #对于pubsub client,如果client-output-buffer一旦超过32mb,又或者超过8mb持续60秒,那么服务器就会立即断开客户端连接。 client-output-buffer-limit pubsub 32mb 8mb 60 # redis执行任务的频率 hz 10 # aof rewrite过程中,是否采取增量"文件同步"策略,默认为"yes",而且必须为yes. # rewrite过程中,每32M数据进行一次文件同步,这样可以减少"aof大文件"写入对磁盘的操作次数. aof-rewrite-incremental-fsync yes
8.Jedis
- java和redis打交道的API客户端
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.1.0</version> </dependency>
- 连接redis
public static void main(String[] args) { Jedis jedis = new Jedis("192.168.80.128",6379); String pong = jedis.ping(); System.out.println("pong = " + pong); } / 运行前: // 1.关闭防火墙 systemctl stop firewalld.service // 2.修改redis.conf [ bind 0.0.0.0 ] 允许任何ip访问,以这个redis.conf启动redis服务 (重启redis) // redis-server /opt/redis5.0.4/redis.conf
- 常用API
private void testString(){ Jedis jedis = new Jedis("192.168.204.141",6379); // string jedis.set("k1","v1"); jedis.set("k2","v2"); jedis.set("k3","v3"); Set<String> set = jedis.keys("*"); Iterator<String> iterator = set.iterator(); for (set.iterator();iterator.hasNext();){ String k = iterator.next(); System.out.println(k+"->"+jedis.get(k)); }
Boolean k2Exists = jedis.exists("k2"); // 查看k2是否存在 System.out.println("k2Exists = " + k2Exists); System.out.println( jedis.ttl("k1") );// 查看k1的过期时间 jedis.mset("k4","v4","k5","v5"); System.out.println( jedis.mget("k1","k2","k3","k4","k5") ); System.out.println("-------------------------------------------------------- "); }
private void testList(){ Jedis jedis = new Jedis("192.168.204.141",6379); // list jedis.lpush("list01", "l1","l2","l3","l4","l5"); List<String> list01 = jedis.lrange("list01", 0, -1); for(String s : list01){ System.out.println(s); } S ystem.out.println("-------------------------------------------------------- "); }
private void testSet(){ Jedis jedis = new Jedis("192.168.204.141",6379); // set jedis.sadd("order","jd001"); jedis.sadd("order","jd002"); jedis.sadd("order","jd003"); Set<String> order = jedis.smembers("order"); Iterator<String> order_iterator = order.iterator(); while(order_iterator.hasNext()){ String s = order_iterator.next(); System.out.println(s); }
jedis.srem("order", "jd002"); System.out.println( jedis.smembers("order").size() ); }
private void testHash(){ Jedis jedis = new Jedis("192.168.204.141",6379); jedis.hset("user1", "username","james"); System.out.println( jedis.hget("user1", "username") ); HashMap<String, String> map = new HashMap<String, String>(); map.put("username", "tom"); map.put("gender", "boy"); map.put("address", "beijing"); map.put("phone", "13590875543"); jedis.hmset("user2", map); List<String> list = jedis.hmget("user2", "username", "phone"); for(String s: list){ System.out.println(s); } }
private void testZset(){ Jedis jedis = new Jedis("192.168.204.141",6379); jedis.zadd("zset01", 60d, "zs1"); jedis.zadd("zset01", 70d, "zs2"); jedis.zadd("zset01", 80d, "zs3"); jedis.zadd("zset01", 90d, "zs4"); Set<String> zset01 = jedis.zrange("zset01", 0, -1); Iterator<String> iterator = zset01.iterator(); while (iterator.hasNext()){ String s = iterator.next(); System.out.println(s); } }
public static void main(String[] args) { new Test2_API().testZset(); }
- 事务
public static void main(String[] args) throws Exception{ Jedis jedis = new Jedis("192.168.204.141",6379); int yue = Integer.parseInt( jedis.get("yue") ); int zhichu = 10; jedis.watch("yue"); // 监控余额 Thread.sleep(5000); // 模拟网络延迟 if(yue < zhichu){ jedis.unwatch(); //解除监控 System.out.println("余额不足!"); }else{ Transaction transaction = jedis.multi(); // 开启事务 transaction.decrBy("yue", zhichu); // 余额减少 transaction.incrBy("zhichu", zhichu); // 累计消费增加 transaction.exec(); System.out.println("余额:" + jedis.get("yue")); System.out.println("累计支出:" + jedis.get("zhichu")); } }
- JedisPool
<dependency> <groupId>commons-pool</groupId> <artifactId>commons-pool</artifactId> <version>1.6</version> </dependency>
public class JedisPoolUtil { private JedisPoolUtil(){} private volatile static JedisPool jedisPool = null; private volatile static Jedis jedis = null; // 返回一个连接池 private static JedisPool getInstance(){ // 双层检测锁(企业中用的非常频繁) if(jedisPool == null){ // 第一层:检测体温 synchronized (JedisPoolUtil.class){ // 排队进站 if(jedisPool == null) { //第二层:查看健康码 JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(1000); // 资源池中的最大连接数 config.setMaxIdle(30); // 资源池允许的最大空闲连接数 config.setMaxWaitMillis(60*1000); // 当资源池连接用尽后,调用的最大等待时间(单位为毫秒) config.setTestOnBorrow(true); //向资源池借用连接时是否做连接有效性检测(业务量很大时候建议设置为false,减少一次ping的开销) jedisPool = new JedisPool( config, "192.168.204.141",6379 ); } } } return jedisPool; } // 返回jedis对象 public static Jedis getJedis(){ if(jedis == null){ jedis = getInstance().getResource(); } return jedis; } }
9.高并发式下的分布锁,以秒杀为例
- 搭建工程并测试单线程
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>Redis</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.7.RELEASE</version> </dependency> <!--实现分布式锁的工具类--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.6.1</version> </dependency> <!--spring操作redis的工具类--> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.3.2.RELEASE</version> </dependency> <!--redis客户端--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.1.0</version> </dependency> <!--json解析工具--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <configuration> <port>8001</port> <path>/</path> </configuration> <executions> <execution> <!-- 打包完成后,运行服务 --> <phase>package</phase> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="controller"></context:component-scan> <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="connectionFactory"></property> </bean> <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="192.168.80.128"></property> <property name="port" value="6379"/> </bean> </beans>
package controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class JedisController { @Autowired private StringRedisTemplate stringRedisTemplate; @RequestMapping("/kill") // 只能解决一个tomcat的并发问题:synchronized锁的一个进程下的线程并发,如果分布式环境,多个进程并发,这种方案就失效了! public synchronized String kill(){ // 1.从redis中获取 手机的库存数量 Integer phone = Integer.valueOf(stringRedisTemplate.opsForValue().get("phone")); // 2.判断手机的数量是否够秒杀的 if (phone > 0) { // 库存减少后,再将库存的值保存回redis phone--; stringRedisTemplate.opsForValue().set("phone", String.valueOf(phone)); System.out.println("库存-1,剩余:"+ phone); }else { System.out.println("库存不足!"); } return "over"; } }
- nginx高并发测试
upstream sga{ server 192.168.204.1:8001; server 192.168.204.1:8002; } server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://sga; root html; index index.html index.htm; } /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
- 实现分布式锁
- redis是单线程的,命令具备原子性,使用setnx命令实现锁,保存k-v
- 如果k不存在,保存(当前线程加锁),执行完成后,删除k表示释放锁
- 如果k已存在,阻塞线程执行,表示有锁
- 如果加锁成功,在执行业务代码的过程中出现异常,导致没有删除k(释放锁失败),那么就会造成死锁(后面的所有线程都无法执行),设置过期时间,例如10秒后,redis自动删除
- 高并发下,由于时间段等因素导致服务器压力过大或过小,每个线程执行的时间不同第一个线程,执行需要13秒,执行到第10秒时,redis自动过期了k(释放锁);第二个线程,执行需要7秒,加锁,执行第3秒(锁 被释放了,为什么,是被第一个线程的
finally主动deleteKey释放掉了) - 连锁反应,当前线程刚加的锁,就被其他线程释放掉了,周而复始,导致锁会永久失效
- 给每个线程加上唯一的标识UUID随机生成,释放的时候判断是否是当前的标识即可,开启一个定时器线程,当过期时间小于总过期时间的1/3时,增长总过期时间
- redis是单线程的,命令具备原子性,使用setnx命令实现锁,保存k-v
- Redisson 实现分布式锁
- Redis 其实并没有对 Java 提供原生支持,若想在程序中集成 Redis,必须使用 Redis 的第三方库
- Redisson 就是用于在 Java 程序中操作 Redis 的库,它使得我们可以在程序中轻松地使用Redis
- Redisson 在 java.util 中常用接口的基础上,为我们提供了一系列具有分布式特性的工具类
package controller; import org.redisson.Redisson; import org.redisson.api.RLock; import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import java.util.concurrent.TimeUnit; @Controller public class JedisController { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private Redisson redisson; @RequestMapping("/kill") // 只能解决一个tomcat的并发问题:synchronized锁的一个进程下的线程并发,如果分布式环境,多个进程并发,这种方案就失效了! public synchronized String kill(){ // 定义商品id String phonekey="huawei"; // 通过redisson获取锁 RLock lock = redisson.getLock(phonekey); // 上锁(过期时间为30秒) lock.lock(30, TimeUnit.SECONDS); try { // 1.从redis中获取 手机的库存数量 Integer phone = Integer.valueOf(stringRedisTemplate.opsForValue().get("phone")); // 2.判断手机的数量是否够秒杀的 if (phone > 0) { // 库存减少后,再将库存的值保存回redis phone--; stringRedisTemplate.opsForValue().set("phone", String.valueOf(phone)); System.out.println("库存-1,剩余:"+ phone); }else { System.out.println("库存不足!"); } }catch (Exception e){ e.printStackTrace(); }finally { // 释放锁 lock.unlock(); } return "over"; } @Bean public Redisson redisson(){ Config config=new Config(); // 使用单个redis服务器 config.useSingleServer().setAddress("redis://192.168.80.128:6379").setDatabase(0); // 使用集群redis // config.useClusterServers().setScanInterval(3000).addNodeAddress("redis://192.168 .204.141:6379","redis://192.168.204.142:6379","redis://192.168.204.143:6379"); return (Redisson) Redisson.create(config); } }

浙公网安备 33010602011771号