Redis | 您所在的位置:网站首页 › Numbers1一10000 › Redis |
# Redis# NoSql发展史# Web1.0时代 主要是编辑特征 网站到用户 "单向行为" Web1.0代表者: 新浪,搜狐,网易 性能要求 简单的静态页面渲染,功能简单单一 # Web2.0时代注重用户交互作用,用户即是网站的浏览者,也是网站内容的创造者 加强网站于用户之间的互动,大数据推荐 实现了网站与用户双向的交流与参与 性能要求 数据库压力越来越大 用Redis当成缓存,来缓解数据库的压力 # 什么是NoSQLNoSQL(non-relational) 也称 Not Only SQL NoSQL仅仅是一个概念,泛指非关系行数据库 区别于关系型数据库他们不保证关系数据库的ACID特征 # NoSQl特点关系型数据库的一个补充# 应用场景高并发的读写 海量数据读写 高可扩展性 不限制语言 lua脚本增强 速度快 # 不适合的场景需要事务支持时 基于SQL的结构化查询存储,处理复杂关系,需要即席查询(用户自定义查询条件查询) # 几种NoSQL数据库简单介绍# memcache(现在有点过时了)很早出现的NoSQL数据库 数据都在内存中,不支持持久化 支持简单的key-value模式 作为缓存数据库辅助持久化的数据库 # Redis几乎覆盖了memcache的绝大部分功能 数据存储在内存中,支持持久化,主要用作备份恢复 支持简单的key-value模式,支持多种数据结构存储,例:list、set、hash、zset等 作为缓存数据库辅助持久化的数据库 # mongoDB高性能、开源、模式自由的文档型数据库 数据存储在内存中,内存不足,把不常用的数据保存在硬盘中 key-value模式,对value(尤其json)提供丰富的查询功能 支持二进制数据及大型对象 根据数据特点替代RDBMS,成为独立的数据库,或者配合RDBMS,存储特定的数据 # 列式存储HBaseHBase是Hadoop项目中的数据库,对大量数据进行随机、实时读写操作,目标是处理数据量非常庞大的表,普通计算机处理超过10亿行数据,还可以处理数百万列的数据表,用于大数据 # Redis介绍一个开源的、使用ANSI C语言编写的key-value存储系统(区别于MySQL的二维表格形式存储) # Redis应用场景# 取最新N个数据的操作取网站最新文章,可以将最新的5000条评论ID放在Redis的List集合中,并将超出集合部分从数据库获取 # 排行榜,取TOP N操作按顶的次数排序,可以使用Redis的sorted set,将要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可 # 精准设定过期时间的应用sorted set的score值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序,定时清除过期数据了,不仅是清除Redis中的过期数据,你完全可以把Redis里这个过期时间当成是对数据库中数据的索引,用Redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录。 # 计数器应用Redis的命令都是原子性的,可以利用INCR,DECR命令来构建计数器系统 # Uniq操作,获取某段时间所有数据排重值使用Redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重。 # 实时系统,反垃圾系统set功能可以知道一个终端用户是否进行了某个操作,可以找到其操作的集合并进行分析统计对比等 # 缓存将数据直接存放到内存中 # Redis特点高效性 (内存) Redis读取的速度是30w次/s,写的速度是10w次/s 原子性 (主逻辑线程是单线程) Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。 pipline 支持多种数据结构 string(字符串) a->b 配置 name--> xiaobo e list(列表) a->list 消息队列 msg--->["hello","name","xiaobo"] hash(哈希) a->map 购物车 1----->["1"=>"手机",“2”=>“电脑”] set(集合) a->set 去重 quchong-->["北京",“上海”,“深圳“] zset(有序集合) a->sorted set 排行榜 top10->[”xxx,xxx“,"xxx,xxx"] 稳定性:持久化,主从复制(集群) 其他特性:支持过期时间,支持事务,消息订阅。 # Redis安装# windows版本解压即可 # Linux版本解压 tar -zxvf redis-6.2.6.tar.gz -CC程序运行环境 因为下载的是源码 需要编译 yum -y install gcc-c++tcl yum -y install tcl编译 cd /export/server/redis-6.2.6/ #或者使用命令 make 进行编译 make MALLOC=libc make test && make install PREFIX=/export/server/redis-7.0.4创建对应的目录 以及配置对应的conf cd /export/server/redis-7.0.4/ mkdir -p /export/server/redis-7.0.4/log mkdir -p /export/server/redis-7.0.4/data vim redis.conf # 修改第61行 bind localhost # 修改第128行 后台 daemonize yes # 修改第163行 logfile "/export/server/redis-7.0.4/log/redis.log" # 修改第247行 dir /export/server/redis-7.0.4/data启动 cd /export/server/redis-7.0.4/ bin/redis-server redis.conf关闭 bin/redis-cli -h localhost shutdown连接 bin/redis-cli -h localhost查看是否已经启动 netstat -antp|grep 6379 # Redis的数据类型# 对字符串string的操作序号命令及描述示例1SET key value 设置指定 key 的值示例:SET hello world2GET key 获取指定 key 的值。示例:GET hello4GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。示例:GETSET hello world25MGET key1 [key2..] 获取所有(一个或多个)给定 key 的值。示例:MGET hello world6SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。示例:SETEX hello 10 world37SETNX key value 只有在 key 不存在时设置 key 的值。示例:SETNX name xiaobo9STRLEN key 返回 key 所储存的字符串值的长度。示例:STRLEN xiaobo10MSET key value [key value ] 同时设置一个或多个 key-value 对。示例:MSET name1 name2 name3 name412MSETNX key value key value 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。示例:MSETNX name1 name2 name3 name413PSETEX key milliseconds value这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。示例:PSETEX money 6000 value14INCR key 将 key 中储存的数字值增一。示例: set number 1 INCR number GET ydlclass715INCRBY key increment 将 key 所储存的值加上给定的增量值(increment)示例:INCRBY number 2 get number16INCRBYFLOAT key increment 将 key 所储存的值加上给定的浮点增量值(increment)示例:INCRBYFLOAT number 0.817DECR key 将 key 中储存的数字值减一。示例: set number 1 DECR number GET number18DECRBY key decrement key 所储存的值减去给定的减量值(decrement)示例:DECRBY number 319APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。示例:APPEND number hello# 对hash列表的操作Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。 Redis 中每个 hash 可以存储 2的32 - 1 键值对(40多亿) 序号命令及描述示例1HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。示例:HSET key1 field1 value12HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。示例:HSETNX key1 field2 value23HMSET key field1 value1 [field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。示例:HMSET key1 field3 value3 field4 value44HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。示例: HEXISTS key1 field4 HEXISTS key1 field65HGET key field 获取存储在哈希表中指定字段的值。示例:HGET key1 field46HGETALL key 获取在哈希表中指定 key 的所有字段和值示例:HGETALL key17HKEYS key 获取所有哈希表中的字段示例:HKEYS key18HLEN key 获取哈希表中字段的数量示例:HLEN key19HMGET key field1 [field2] 获取所有给定字段的值示例:HMGET key1 field3 field410HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment 。示例: HSET key2 field1 1 HINCRBY key2 field1 1 HGET key2 field111HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。示例:HINCRBYFLOAT key2 field1 0.812HVALS key 获取哈希表中所有值示例:HVALS key113HDEL key field1 [field2] 删除一个或多个哈希表字段示例: HDEL key1 field3 HVALS key1# 对list列表的操作Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边) 一个列表最多可以包含 2的32 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。 序号命令及描述示例1LPUSH key value1 [value2] 将一个或多个值插入到列表头部示例:LPUSH list1 value1 value22LRANGE key start stop 查看list当中所有的数据示例:LRANGE list1 0 -13LPUSHX key value 将一个值插入到已存在的列表头部示例:LPUSHX list1 value3 LINDEX list1 04RPUSH key value1 [value2] 在列表中添加一个或多个值到尾部示例: RPUSH list1 value4 value5 LRANGE list1 0 -15RPUSHX key value 为已存在的列表添加单个值到尾部示例:RPUSHX list1 value66LINSERT key BEFORE|AFTER pivot value 在列表的元素前或者后插入元素示例:LINSERT list1 BEFORE value3 beforevalue37LINDEX key index 通过索引获取列表中的元素示例:LINDEX list1 08LSET key index value 通过索引设置列表元素的值示例:LSET list1 0 hello9LLEN key 获取列表长度示例:LLEN list110LPOP key 移出并获取列表的第一个元素示例:LPOP list111RPOP key 移除列表的最后一个元素,返回值为移除的元素。示例:RPOP list112BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。示例:BLPOP list1 200013BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。示例:BRPOP list1 200014RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回示例:RPOPLPUSH list1 list215BRPOPLPUSH source destination timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。示例:BRPOPLPUSH list1 list2 200016LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。示例:LTRIM list1 0 217DEL key1 key2 删除指定key的列表示例:DEL list2# 对set集合的操作Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据 Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 2的32 - 1 (4294967295, 每个集合可存储40多亿个成员)。 序号命令及描述示例1SADD key member1 [member2] 向集合添加一个或多个成员示例:SADD set1 setvalue1 setvalue22SMEMBERS key 返回集合中的所有成员示例:SMEMBERS set13SCARD key 获取集合的成员数示例:SCARD set14SDIFF key1 [key2] 返回给定所有集合的差集示例: SADD set2 setvalue2 setvalue3 SDIFF set1 set25SDIFFSTORE destination key1 [key2] 返回给定所有集合的差集并存储在 destination 中示例:SDIFFSTORE set3 set1 set26SINTER key1 [key2] 返回给定所有集合的交集示例:SINTER set1 set27SINTERSTORE destination key1 [key2] 返回给定所有集合的交集并存储在 destination 中示例:SINTERSTORE set4 set1 set28SISMEMBER key member 判断 member 元素是否是集合 key 的成员示例:SISMEMBER set1 setvalue19SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合示例:SMOVE set1 set2 setvalue110SPOP key 移除并返回集合中的一个随机元素示例:SPOP set211SRANDMEMBER key [count] 返回集合中一个或多个随机数示例:SRANDMEMBER set2 212SREM key member1 [member2] 移除集合中一个或多个成员示例:SREM set2 setvalue113SUNION key1 [key2]] 返回所有给定集合的并集示例:SUNION set1 set214SUNIONSTORE destination key1 [key2] 所有给定集合的并集存储在 destination 集合中示例:SUNIONSTORE set5 set1 set2# 对key的操作序号命令及描述示例1DEL key 该命令用于在 key 存在时删除 key。示例:del number2DUMP key 序列化给定 key ,并返回被序列化的值。示例:DUMP key13EXISTS key 检查给定 key 是否存在。示例:exists number4EXPIRE key seconds 为给定 key 设置过期时间,以秒计。示例:expire number 56PEXPIRE key milliseconds 设置 key 的过期时间以毫秒计。示例:PEXPIRE set3 30008KEYS pattern 查找所有符合给定模式( pattern)的 key 。示例:keys *10PERSIST key 移除 key 的过期时间,key 将持久保持。示例:persist set211PTTL key 以毫秒为单位返回 key 的剩余的过期时间。示例:pttl set212TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。示例:ttl set213RANDOMKEY 从当前数据库中随机返回一个 key 。示例: randomkey14RENAME key newkey 修改 key 的名称示例:rename set5 set815RENAMENX key newkey 仅当 newkey 不存在时,将 key 改名为 newkey 。示例:renamenx set8 set1016TYPE key 返回 key 所储存的值的类型。示例:type set10# 对ZSet的操作-重要典型例子:互联网,微博热搜,最热新闻,统计网站pv Redis有序集合和集合一样也是string类型元素的集合,且不允许重复的成员 它用来保存需要排序的数据,例如排行榜,一个班的语文成绩,一个公司的员工工资,一个论坛的帖子等。 有序集合中,每个元素都带有score(权重),以此来对元素进行排序 它有三个元素:key、member和score。以语文成绩为例,key是考试名称(期中考试、期末考试等),member是学生名字,score是成绩。 #命令及描述示例1ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数向ZSet中添加页面的PV值 ZADD pv_zset 120 page1.html 100 page2.html 140 page3.html2ZCARD key 获取有序集合的成员数获取所有的统计PV页面数量 ZCARD pv_zset3ZCOUNT key min max 计算在有序集合中指定区间分数的成员数获取PV在120-140在之间的页面数量 ZCOUNT pv_zset 120 1404ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment给page1.html的PV值+1 ZINCRBY pv_zset 1 page1.html5ZINTERSTORE destination numkeys key [key ...] 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中创建两个保存PV的ZSET: ZADD pv_zset1 10 page1.html 20 page2.html ZADD pv_zset2 5 page1.html 10 page2.html ZINTERSTORE pv_zset_result 2 pv_zset1 pv_zset27ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合指定区间内的成员获取所有的元素,并可以返回每个key对一个的score ZRANGE pv_zset_result 0 -1 WITHSCORES9ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 通过分数返回有序集合指定区间内的成员获取ZSET中120-140之间的所有元素 ZRANGEBYSCORE pv_zset 120 14010ZRANK key member 返回有序集合中指定成员的索引获取page1.html的pv排名(升序) ZRANK pv_zset page3.html11ZREM key member [member ...] 移除有序集合中的一个或多个成员移除page1.html ZREM pv_zset page1.html15ZREVRANGE key start stop [WITHSCORES] 返回有序集中指定区间内的成员,通过索引,分数从高到低按照PV降序获取页面 ZREVRANGE pv_zset 0 -117ZREVRANK key member 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序获取page2.html的pv排名(降序) ZREVRANK pv_zset page2.html18ZSCORE key member 返回有序集中,成员的分数值获取page3.html的分数值 ZSCORE pv_zset page3.html# 对位图BitMaps的操作计算机最小的存储单位是位bit,Bitmaps是针对位的操作的,相较于String、Hash、Set等存储方式更加节省空间 Bitmaps不是一种数据结构,操作是基于String结构的,一个String最大可以存储512M,那么一个Bitmaps则可以设置2^32个位 Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量offset itMaps 命令说明:将每个独立用户是否访问过网站存放在Bitmaps中, 将访问的用户记做1, 没有访问的用户记做0, 用偏移量作为用户的id 。 设置值 SETBIT key offset value setbit unique:users:2022-08-04 0 1 setbit unique:users:2022-08-04 5 1 setbit unique:users:2022-08-04 10 1获取值 GETBIT key offset获取Bitmaps指定范围值为1的个数 BITCOUNT key [start end]Bitmaps间的运算 BITOP operation destkey key [key, …]bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。 bitop and unique:users:and:2022-08-04_05 unique:users:2022-08-03 unique:users:2022-08-04 bitcount unique:users:and:2022-04-04_05 # 对HyperLogLog结构的操作HyperLogLog常用于大数据量的统计,比如页面访问量统计或者用户访问量统计。 要统计一个页面的访问量(PV),可以直接用redis计数器或者直接存数据库都可以实现,如果要统计一个页面的用户访问量(UV),一个用户一天内如果访问多次的话,也只能算一次,这样,我们可以使用SET集合来做,因为SET集合是有去重功能的,key存储页面对应的关键字,value存储对应的userid,这种方法是可行的。但如果访问量较多,假如有几千万的访问量,这就麻烦了。为了统计访问量,要频繁创建SET集合对象。 uv: 一个页面被多少个用户访问 pv:一个页面的访问量 Redis实现HyperLogLog算法,HyperLogLog 这个数据结构的发明人 是Philippe Flajolet(菲利普·弗拉若莱)教授。Redis 在 2.8.9 版本添加了 HyperLogLog 结构 help @hyperloglog PFADD key element [element ...] summary: Adds the specified elements to the specified HyperLogLog. since: 2.8.9 PFCOUNT key [key ...] summary: Return the approximated cardinality(基数) of the set(s) observed by the HyperLogLog at key(s). since: 2.8.9 PFMERGE destkey sourcekey [sourcekey ...] summary: Merge N different HyperLogLogs into a single one. since: 2.8.9Redis集成的HyperLogLog使用语法主要有pfadd和pfcount,顾名思义,一个是来添加数据,一个是来统计的。 HyperLogLog算法标准误差是 0.81% pfadd和pfcount常用于统计,需求:假如两个页面很相近,现在想统计这两个页面的用户访问量呢?这里就可以用pfmerge合并统计了 pfadd page1 user1 user2 user3 user4 user5 pfadd page2 user1 user2 user3 user6 user7 pfmerge page1+page2 page1 page2 pfcount page1+page2HyperLogLog为什么适合做大量数据的统计 Redis HyperLogLog 是用来做基数统计的算法 # Redis Java API操作pom.xml redis.clients jedis 2.9.0 junit junit 4.12 test org.testng testng 6.14.3 test org.apache.maven.plugins maven-compiler-plugin 3.0 1.8 1.8 UTF-8 testpublic class RedisTest { //定义JedisPoll private JedisPool jedisPool; @BeforeTest public void beforeTest(){ // 配置对象 JedisPoolConfig config = new JedisPoolConfig(); // 最大空闲连接 config.setMaxIdle(10); // 最小空闲连接 config.setMinIdle(5); // 连接时最大等待时间 config.setMaxWaitMillis(3000); // 最大连接数 config.setMaxTotal(100); jedisPool = new JedisPool(config,"localhost",6379); } @Test public void keysTest(){ // 获取jedis连接池连接 Jedis jedis = jedisPool.getResource(); // 命令 Set keys = jedis.keys("*"); // 遍历 for (String key : keys) { System.out.println(key); } jedis.close(); } @Test public void stringTest(){ Jedis jedis = jedisPool.getResource(); // 添加一个string类型数据,key为pv,用于保存pv的值,初始值为0 jedis.set("pv","22"); // 查询该key对应的数据 String pv = jedis.get("pv"); System.out.println(pv); // 修改pv为1000 jedis.set("pv", "53"); // 实现整形数据原子自增操作 +1 Long pv1 = jedis.incr("pv"); System.out.println(pv1); // 实现整形该数据原子自增操作 +1000 Long pv2 = jedis.incrBy("pv", 100); System.out.println(pv2); // 关闭jedis连接 jedis.close(); } @Test public void hashTest(){ Jedis jedis = jedisPool.getResource(); // 往Hash结构中添加以下商品库存 // a) iphone11 => 10000 // // b) macbookpro => 9000 jedis.hset("goods","iphone11","10000"); jedis.hset("goods","macbookpro","9000"); // 获取Hash中所有的商品 Set goods = jedis.hkeys("goods"); for (String good : goods) { System.out.println(good); } // 新增3000个macbookpro库存 jedis.hincrBy("goods","macbookpro",3000); String hget = jedis.hget("goods", "macbookpro"); System.out.println(hget); // // 删除整个Hash的数据 jedis.del("goods"); // 关闭jedis连接 jedis.close(); } @Test public void listTest(){ Jedis jedis = jedisPool.getResource(); // 向list的左边插入以下三个手机号码:15841018742、17612484531、13439275383 jedis.lpush("numbers","15841018742","17612484531","13439275383"); List numbers = jedis.lrange("numbers", 0, -1); for (String number : numbers) { System.out.println(number); } System.out.println("=============="); // 从右边移除一个手机号码 jedis.rpop("numbers"); // 获取list所有的值 List numbers1 = jedis.lrange("numbers", 0, -1); for (String s : numbers1) { System.out.println(s); } // 清除numbers中数据 jedis.del("numbers"); // 关闭jedis连接 jedis.close(); } @Test public void setTest(){ Jedis jedis = jedisPool.getResource(); // 求UV就是求独立有多少个(不重复) // 往一个set中添加页面 page1 的uv,用户user1访问一次该页面 jedis.sadd("uv","user1"); // user2访问一次该页面 jedis.sadd("uv","user2"); // user1再次访问一次该页面 jedis.sadd("uv","user1"); // 最后获取 page1的uv值 System.out.println("uv:" + jedis.scard("uv")); // 关闭jedis连接 jedis.close(); } @AfterTest public void afterTest(){ // 关闭jedisPool连接 jedisPool.close(); } } # Redis的持久化在redis当中,提供了两种数据持久化的方式,分别为RDB以及AOF,且Redis默认开启的数据持久化方式为RDB方式 # RDB持久化Redis会定期保存数据快照至一个rbd文件中,并在启动时自动加载rdb文件,恢复之前保存的数据 # 手动修改save [seconds] [changes]可通过SAVE或者BGSAVE命令手动触发RDB快照保存 在seconds秒内如果发生了changes次数据修改,则进行一次RDB快照保存 SAVE和BGSAVE区别 SAVE 和 BGSAVE 两个命令都会调用 rdbSave 函数 SAVE 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。 BGSAVE 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。 Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求 # RDB配置文件save 900 1 save 300 10 save 60 10000 save 5 1redis的配置文件默认自带的存储机制。表示每隔多少秒,有多少个key发生变化就生成一份dump.rdb文件,作为redis的快照文件 例:save 60 10000 表示在60秒内,有10000个key发生变化,就会生成一份redis的快照 # 重新启动redis服务 ps -ef | grep redis bin/redis-cli -h 192.168.200.131 shutdown bin/redis-server redis.conf # AOF持久化AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新 # 开启AOFAOF默认是关闭的,要开启,进行配置 appendonly yes # AOF配置appendfsync no:不进行fsync,将flush文件的时机交给OS决定,速度最快 appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢 appendfsync everysec:折中的做法,交由后台线程每秒fsync一次 # AOF rewrite可以重写AOF文件,只保留能够把数据恢复到最新状态的最小写操作集 AOF rewrite可以通过BGREWRITEAOF命令触发,也可以配置Redis定期自动进行 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mbRedis在每次AOF rewrite时,会记录完成rewrite后的AOF日志大小,当AOF日志大小在该基础上增长了100%后,自动进行AOF rewrite auto-aof-rewrite-min-size最开始的AOF文件必须要触发这个文件才触发,后面的每次重写就不会根据这个变量了。该变量仅初始化启动Redis有效 # AOF优点安全 启用appendfsync为always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据 AOF文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof工具轻松修复 AOF文件易读,可修改,在进行某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据 # AOF的缺点AOF文件比RDB文件更大 性能消耗比RDB高 数据恢复速度比RDB慢 # 制定合理的持久化策略:AOF + fsync always的设置虽然能够绝对确保数据安全,但每个操作都会触发一次fsync,会对Redis的性能有比较明显的影响 AOF + fsync every second是比较好的折中方案,每秒fsync一次 AOF + fsync never会提供AOF持久化方案下的最优性能 使用RDB持久化通常会提供比使用AOF更高的性能,但需要注意RDB的策略配置 # Redis事务Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中 Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令 # Redis事务没有隔离级别的概念批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到 # Redis不保证原子性Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行 # 一个事务执行的阶段第一阶段:开始事务 第二阶段:命令入队 第三阶段:执行事务 # Redis事务相关命令MULTI开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令队列 EXEC执行事务中的所有操作命令 DISCARD取消事务,放弃执行事务块中的所有命令 WATCH监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令 UNWATCH取消WATCH对所有key的监视 # Redis不支持事务回滚Redis之所以保持这样简易的事务,完全是为了保证高并发下的核心问题——性能 # 数据删除与淘汰策略# 过期数据# Redis中的数据特征Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态 TTL返回的值有三种情况:正数,-1,-2 正数:代表该数据在内存中还能存活的时间 -1:永久有效的数据 -2 :已经过期的数据 或被删除的数据 或 未定义的数据 删除策略就是针对已过期数据的处理策略 # 时效性数据的存储过期数据是一块独立的存储空间,Hash结构,field是内存地址,value是过期时间,保存了所有key的过期描述,在最终进行过期处理的时候,对该空间的数据进行检测, 当时间到期之后通过field找到内存该地址处的数据,然后进行相关操作 # 数据删除策略# 数据删除策略的目标在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体redis性能的下降,甚至引发服务器宕机或 内存泄露 # 定时删除创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作 优点 节约内存,到时就删除,快速释放掉不必要的内存占用 缺点 CPU压力很大,无论CPU此时负载量多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量 总结 用处理器性能换取存储空间(拿时间换空间) # 惰性删除数据到达过期时间,不做处理。等下次访问该数据时,我们需要判断,如果未过期,返回数据,发现已过期,删除,返回不存在 优点 节约CPU性能,发现必须删除的时候才删除 缺点 内存压力很大,出现长期占用内存的数据 总结 用存储空间换取处理器性能(拿空间换时间) # 定期删除周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度 定期删除策略 Redis启动服务器初始化时,读取配置server.hz的值,默认为10 每秒钟执行server.hz次serverCron()-------->databasesCron()--------->activeExpireCycle() activeExpireCycle()对每个expires[*]逐一进行检测,每次执行耗时:250ms/server.hz 对某个expires[*]检测时,随机挑选W个key检测 如果key超时,删除key 如果一轮中删除的key的数量>W*25%,循环该过程 如果一轮中删除的key的数量≤W*25%,检查下一个expires[*],0-15循环W取值=ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP属性值参数current_db用于记录activeExpireCycle() 进入哪个expires[*] 执行 如果activeExpireCycle()执行时间到期,下次从current_db继续向下执行 特点 CPU性能占用设置有峰值,检测频度可自定义设置 内存压力不是很大,长期占用内存的冷数据会被持续清理 总结 周期性抽查存储空间(随机抽查,重点抽查) # 数据淘汰策略(逐出算法)简单明了,内存不够了,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法 # 策略配置最大可使用内存,即占用物理内存的比例,默认值为0,表示不限制。生产环境中根据需求设定,通常设置在50%以上maxmemory ?mb 每次选取待删除数据的个数,采用随机获取数据的方式作为待检测删除数据maxmemory-samples count 对数据进行删除的选择策略maxmemory-policy policypolicy策略一共3类8种 第一类 检测易失数据(可能会过期的数据集server.db[i].expires ) 同一个库 volatile-lru:挑选最近最少使用的数据淘汰 least recently used volatile-lfu:挑选最近使用次数最少的数据淘汰 least frequently used volatile-ttl:挑选将要过期的数据淘汰 volatile-random:任意选择数据淘汰 第二类:检测全库数据(所有数据集server.db[i].dict ) allkeys-lru:挑选最近最少使用的数据淘汰 allkeLyRs-lfu:挑选最近使用次数最少的数据淘汰 allkeys-random:任意选择数据淘汰,相当于随机 第三类:放弃数据驱逐 no-enviction(驱逐):禁止驱逐数据(redis4.0中默认策略),会引发OOM(Out Of Memory) 淘汰策略配置到这个属性上 maxmemory-policy volatile-lru # Redis的主从复制架构# 当今互联网的三高架构不是高血糖,高血压,和高血脂呦 # 高并发应用要提供某一业务要能支持很多客户端同时访问的能力 # 高性能性能带给我们最直观的感受就是:速度快,时间短 # 高可用一年中应用服务正常运行的时间占全年时间的百分比 4小时27分15秒+11分36秒+2分16秒=4小时41分7秒=16867秒 1年=3652460*60=31536000秒 可用性=(31536000-16867)/31536000*100%=99.9465151%业界可用性目标5个9,即99.999%,即服务器年宕机时长低于315秒,约5.25分钟 # 单点Redis可能出现的问题# 机器故障问题硬盘故障、系统崩溃 实质数据丢失,很可能对业务造成灾难性打击 # 容量瓶颈问题内存不足 本质硬件条件跟不上 解决思路为了避免单点Redis服务器故障,准备多台服务器,互相连通。将数据复制多个副本保存在不同的服务器上,连接在一起,并保证数据是同步的。即使有其中一台服务器宕机,其他服务器依然可以继续提供服务,实现Redis的高可用,同时实现数据冗余备份 master 提供数据方,主服务器,主节点,主库主客户端 slave 接收数据方,从服务器,从节点,从库 # 主从复制主从复制即将master中的数据即时、有效的复制到slave中 特点一个master可以拥有多个slave,一个slave只对应一个master 职责master 写数据 执行写操作时,将出现变化的数据自动同步到slave 读数据(可忽略)slave 读数据 写数据(禁止)读写分离:master写、slave读,提高服务器的读写负载能力 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数 量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案 # 主从复制工作流程建立连接阶段(即准备阶段) 数据同步阶段 命令传播阶段(反复同步) 当前状态: slave:保存master的地址与端口 master:保存slave的端口 总体:之间创建了socket 连接 master与slave互联 命令 方式一:客户端发送命令slaveof masterip masterport 方式二:启动服务器参数redis-server --slaveof masterip masterport 方式三:服务器配置(主流方式)slaveof masterip masterport slave系统信息master_link_down_since_seconds masterhost & masterport master系统信息uslave_listening_port(多个)master与slave断开连接 命令 断开slave与master的连接,slave断开连接后,不会删除已有数据,只是不再接受master发送的数据 slaveof no one授权访问 master客户端发送命令设置密码requirepass password master配置文件设置密码config set requirepass password config get requirepass slave客户端发送命令设置密码auth password slave配置文件设置密码masterauth password slave启动服务器设置密码redis-server –a password # 阶段二:数据同步当前状态: slave:具有master端全部数据,包含RDB过程接收的数据 master:保存slave当前数据同步的位置 总体:之间完成了数据克隆 数据同步阶段master说明 如果master数据量巨大,数据同步阶段应避开流量高峰期,避免造成master阻塞,影响业务正常执行 复制缓冲区大小设定不合理,会导致数据溢出。如进行全量复制周期太长,进行部分复制时发现数据已经存在丢失的情况,必须进行第二次全量复制,致使slave陷入死循环状态。 repl-backlog-size ?mb master单机内存占用主机内存的比例不应过大,建议使用50%-70%的内存,留下30%-50%的内存用于执 行bgsave命令和创建复制缓冲区数据同步阶段slave说明 为避免slave进行全量复制、部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的对外服务 slave-serve-stale-data yes|no数据同步阶段,master发送给slave信息可以理解master是slave的一个客户端,主动向slave发送命令 多个slave同时对master请求数据同步,master发送的RDB文件增多,会对带宽造成巨大冲击,如果master带宽不足,因此数据同步需要根据业务需求,适量错峰 slave过多时,建议调整拓扑结构,由一主多从结构变为树状结构,中间的节点既是master,也是 slave。注意使用树状结构时,由于层级深度,导致深度越高的slave与最顶层master间数据同步延迟较大,数据一致性变差,应谨慎选择 小提示 设计Redis架构时,一开始就要想好了用几主几从,然后把他们启动其起来 # 阶段三:命令传播当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的状态,同步的动作称为命令传播 master将接收到的数据变更命令发送给slave,slave接收命令后执行命令 部分复制 介绍一下三个核心要素 服务器的运行 id(run id)服务器运行ID(runid) 概念:服务器运行ID是每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行id 组成:运行id由40位字符组成,是一个随机的十六进制字符 例如:fdc9ff13b9bbaab28db42b3d50f852bb5e3fcdce 作用:运行id被用于在服务器间进行传输,识别身份 如果想两次操作均对同一台服务器进行,必须每次操作携带对应的运行id,用于对方识别 实现方式:运行id在每台服务器启动时自动生成的,master在首次连接slave时,会将自己的运行ID发送给slave, slave保存此ID,通过info Server命令,可以查看节点的runid 主服务器的复制积压缓冲区复制缓冲区 概念:复制缓冲区,又名复制积压缓冲区,是一个先进先出(FIFO)的队列,用于存储服务器执行过的命令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区 复制缓冲区默认数据存储空间大小是1M 当入队元素的数量大于队列长度时,最先入队的元素会被弹出,而新元素会被放入队列 作用:用于保存master收到的所有指令(仅影响数据变更的指令,例如set,select) 数据来源:当master接收到主客户端的指令时,除了将指令执行,会将该指令存储到缓冲区中 主从服务器的复制偏移量偏移量 概念:一个数字,描述复制缓冲区中的指令字节位置 分类: master复制偏移量:记录发送给所有slave的指令字节对应的位置(多个) slave复制偏移量:记录slave接收master发送过来的指令字节对应的位置(一个) 作用:同步信息,比对master与slave的差异,当slave断线后,恢复数据使用 数据来源: master端:发送一次记录一次 slave端:接收一次记录一次字节值工作原理 通过offset区分不同的slave当前数据传播的差异 master记录已发送的信息对应的offset slave记录已接收的信息对应的offset 进入命令传播阶段候,master与slave间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线 # master心跳任务内部指令:PING 周期:由repl-ping-slave-period决定,默认10秒 作用:判断slave是否在线 查询:INFO replication 获取slave最后一次连接时间间隔,lag项维持在0或1视为正常 # slave心跳任务内部指令:REPLCONF ACK {offset} 周期:1秒 作用1:汇报slave自己的复制偏移量,获取最新的数据变更指令 作用2:判断master是否在线 # 心跳阶段可能发生的问题当slave多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步 min-slaves-to-write 2 min-slaves-max-lag 8slave数量少于2个,或者所有slave的延迟都大于等于8秒时,强制关闭master写功能,停止数据同步 slave数量由slave发送REPLCONF ACK命令做确认 slave延迟由slave发送REPLCONF ACK命令做确认 redis中master宕机了,我们就要从其它slave中选取一个出来当master,这个时候就有Sentinel了,Sentinel中的哨兵开始执行职责了 # 哨兵哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master。 # 哨兵的作用监控监控master和slave,不断的检查master和slave是否正常运行,master存活检测、master与slave运行情况检测 通知(提醒)当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知 自动故障转移断开master与slave连接,选取一个slave作为master,将其他slave连接新的master,并告知客户端新的服务器地址 注意哨兵也是一台redis服务器,只是不提供数据相关服务,通常哨兵的数量配置为单数 # 哨兵配置设置哨兵监听的主服务器信息, sentinel_number表示参与投票的哨兵数量sentinel monitor master_name master_host master_port sentinel_number 设置判定服务器宕机时长,该设置控制是否进行主从切换sentinel down-after-milliseconds master_name million_seconds 设置故障切换的最大超时时长sentinel failover-timeout master_name million_seconds 设置主从切换后,同时进行数据同步的slave数量,数值越大,要求网络资源越高,数值越小,同步时间越长sentinel parallel-syncs master_name sync_slave_number「sentinel.conf」 port 26379 daemonize yes pidfile "/var/run/redis-sentinel26379.pid" logfile "/export/server/sentinel-26379/log/log.log" dir "/export/server/sentinel- 26379/data" sentinel monitor mymaster 0.0.0.0 6379 2 sentinel resolve-hostnames no sentinel announce-hostnames no「配置命令」 主服务./bin/redis-server redis.conf sentinel监听./bin/redis-server redis.conf slaveof 主ip 主端口 # java原生连接sentinelpom.xml 4.0.0 com.dream.xiaobo redis_operation 1.0-SNAPSHOT redis.clients jedis 2.9.0 junit junit 4.12 test org.testng testng 6.14.3 test org.apache.maven.plugins maven-compiler-plugin 3.0 1.8 1.8 UTF-8 11 11 testpublic class RedisSentinelTest { private JedisSentinelPool jedisSentinelPool; @BeforeTest public void beforeTest(){ // 配置对象 JedisPoolConfig config = new JedisPoolConfig(); // 最大空闲连接 config.setMaxIdle(10); // 最小空闲连接 config.setMinIdle(5); // 连接时最大等待时间 config.setMaxWaitMillis(3000); // 最大连接数 config.setMaxTotal(100); Set sentinel = new HashSet(); sentinel.add("152.136.22.223:26379"); sentinel.add("152.136.22.223:26380"); sentinel.add("152.136.22.223:26381"); jedisSentinelPool = new JedisSentinelPool("mymaster",sentinel,config); } @Test public void keysTest(){ Jedis jedis = jedisSentinelPool.getResource(); Set keys = jedis.keys("*"); for (String key : keys) { System.out.println(key); } } @AfterTest public void afterTest(){ jedisSentinelPool.close(); } } # spring连接sentinelapplication.ymlspring: redis: sentinel: nodes: 0.0.0.0:26379,0.0.0.0::26380,0.0.0.0::26381 master: mymaster test@SpringBootTest class RedisBootOperationApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test void contextLoads() { redisTemplate.opsForValue().set("name","xiaobo"); Object name = redisTemplate.opsForValue().get("name"); System.out.println(name); redisTemplate.boundValueOps("age").set(22); Object age = redisTemplate.boundValueOps("age").get(); System.out.println(age); } } # Redis集群 Cluster集群就是使用网络将若干台计算机联通起来,并提供统一的管理方式,使其对外呈现单机的服务效果 # 集群的作用分散单台服务器的访问压力,实现负载均衡分散单台服务器的存储压力,实现可扩展性降低单台服务器宕机带来的业务灾难# 集群搭建配置服务器(3主3从)建立通信(Meet)分槽(Slot)搭建主从(master-slave)「最少3主」 # 搭建步骤创建6台redis服务修改配置文件port 7001 bind 0.0.0.0 protected-mode no daemonize yes pidfile /var/run/redis_7001.pid logfile "/export/server/redis-7001/log/redis.log" dir /export/server/redis-7001/data/ appendonly yes cluster-enabled yes cluster-config-file nodes-7001.conf cluster-node-timeout 15000 使6台机器集群redis-cli --cluster create 0.0.0.0:7001 0.0.0.0:7002 0.0.0.0:7003 0.0.0.0:7004 0.0.0.0:7005 0.0.0.0:7006 --cluster-replicas 1 每台机器分槽./bin/redis-cli --cluster fix 0.0.0.0:端口号输入 yes 连接cluster./bin/redis-cli -c -h 0.0.0.0 -p 端口号 # cluster集群配置是否启用cluster,加入cluster节点cluster-enabled yes|no cluster配置文件名,该文件属于自动生成,仅用于快速查找文件并查询文件内容cluster-config-file filename 节点服务响应超时时间,用于判定该节点是否下线或切换为从节点cluster-node-timeout milliseconds master连接的slave最小数量cluster-migration-barrier min_slave_number # Cluster节点操作命令查看集群节点信息cluster nodes 更改slave指向新的mastercluster replicate master-id 发现一个新节点,新增mastercluster meet ip:port 忽略一个没有solt的节点cluster forget server_id 手动故障转移cluster failover # 集群操作命令创建集群redis-cli –-cluster create masterhost1:masterport1 masterhost2:masterport2 masterhost3:masterport3 [masterhostn:masterportn …] slavehost1:slaveport1 slavehost2:slaveport2 slavehost3:slaveport3 -–cluster-replicas n 添加master到当前集群中,连接时可以指定任意现有节点地址与端口redis-cli --cluster add-node new-master-host:new-master-port now-host:now-port 添加slaveredis-cli --cluster add-node new-slave-host:new-slave-port master-host:master-port --cluster-slave --cluster-master-id masterid 删除节点,如果删除的节点是master,必须保障其中没有槽slotredis-cli --cluster del-node del-slave-host:del-slave-port del-slave-id 重新分槽,分槽是从具有槽的master中划分一部分给其他master,过程中不创建新的槽redis-cli --cluster reshard new-master-host:new-master:port --cluster-from src- master-id1, src-master-id2, src-master-idn --cluster-to target-master-id -- cluster-slots slots 重新分配槽,从具有槽的master中分配指定数量的槽到另一个master中,常用于清空指定master中的槽redis-cli --cluster reshard src-master-host:src-master-port --cluster-from src- master-id --cluster-to target-master-id --cluster-slots slots --cluster-yes # java原生连接Clustertestpublic class RedisClusterTest { private JedisCluster jedisCluster; @BeforeTest public void beforeTest(){ JedisPoolConfig config = new JedisPoolConfig(); // 最大空闲连接 config.setMaxIdle(10); // 最小空闲连接 config.setMinIdle(5); // 连接时最大等待时间 config.setMaxWaitMillis(3000); // 最大连接数 config.setMaxTotal(100); Set nodes = new HashSet(); nodes.add(new HostAndPort("152.136.22.223",7001)); nodes.add(new HostAndPort("152.136.22.223",7002)); nodes.add(new HostAndPort("152.136.22.223",7003)); nodes.add(new HostAndPort("152.136.22.223",7004)); nodes.add(new HostAndPort("152.136.22.223",7005)); nodes.add(new HostAndPort("152.136.22.223",7006)); jedisCluster = new JedisCluster(nodes,config); } @Test public void test(){ jedisCluster.set("name","xiaobo"); String a = jedisCluster.get("a"); System.out.println(a); } @AfterTest public void afterTest(){ try { jedisCluster.close(); } catch (IOException e) { throw new RuntimeException(e); } } } # springboot连接Clusterapplication.ymlspring: redis: pool: max-idle: 100 min-idle: 1 max-active: 1000 max-wait: -1 cluster: nodes: - 0.0.0.0:7001 - 0.0.0.0:7002 - 0.0.0.0:7003 - 0.0.0.0:7004 - 0.0.0.0:7005 - 0.0.0.0:7006 database: 0 timeout: 15000 connect-timeout: 5000 test@SpringBootTest class RedisBootOperationApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test void contextLoads() { redisTemplate.opsForValue().set("name","xiaobo"); Object name = redisTemplate.opsForValue().get("name"); System.out.println(name); redisTemplate.boundValueOps("age").set(22); Object age = redisTemplate.boundValueOps("age").get(); System.out.println(age); } }「你知道的越多 你不知道的越多 嘿 我是小博 带你一起看我目之所及的世界......」 |
CopyRight 2018-2019 实验室设备网 版权所有 |