Redis实用总结(1/3)

目录

1.什么是Redis 

1.1.Redis

1.2.NOSQL

1.3.Redis应用场景

2. Redis安装

3.redis的数据类型

3.1.String

 3.2.Hash

 3.3.List

 3.4.Set

 3.5.zset

 3.6.Bitmaps

3.7.HyperLogLog

3.7.1.HyperLogLog特点总结:

3.7.2.命令演示:

3.8. Geo

3.8.1.添加坐标点命令:(经纬度查询:拾取坐标系统)

3.8.2.获取坐标

3.8.3.计算坐标距离

 3.8.4.以给定的经纬度为中心,返回半径被所包含的位置

3.8.5.georadiusbymember

 3.8.6.geohash

 4.Redis设置密码(平时不用,作为了解)

5.Redis常用命令

6.Java操作redis(Jedis技术)

6.1.环境准备

6.2.Jedis操作Redis

6.3.Jedis连接池

6.3.1.Jedis连接池操作redis

6.3.2.使用连接池存储java对象

7.使用redis实现分布式锁

7.1.java中的常见锁

7.2.分布式锁

8.redis持久化方案

8.1.RDB(默认)

8.1.1.什么是RDB

8.1.2.持久化时间间隔流程

8.1.3.RDB三种触发机制

8.1.4.RDB优缺点

8.2.AOF

8.2.1.什么是AOF机制

8.2.2.AOF写数据的三种策略

8.2.3.开启方式

8.2.4.aof文件被篡改怎么修复

8.2.5.AOF优缺点

8.3.RDB&AOF对比总结

8.4.持久化应用场景

9.下期预告


1.什么是Redis 

1.1.Redis

        Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,而且Redis通过提供多种键值数据类型来适应不同场景下的存储需求,是一款高性能的NOSQL系列的非关系型数据库。Redis把数据缓存到内存中的,效率极高,速度极快(很重要)

简单理解:redis是一个通过键值对的形式来存储数据的NoSQL数据库(基于内存)

1.2.NOSQL

把上一句话牢记后,下一个问题,nosql什么鬼???

        这里不要用字面意思理解,nosql = not only sql,不仅仅是sql,是一项全新的数据库理念,泛指非关系型的数据库。

nosql起源(了解)

        随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

nosql都有哪些数据库类型?(了解)

键值存储数据库列存储数据库文档型数据库图形数据库
代表产品Tokyo Cabinet/Tyrant、RedisCassandra, HBaseCouchDB、MongoDBNeo4J、InfoGrid
典型应用内容缓存,主要用于处理大量数据的高访问负载分布式的文件系统Web应用社交网络
数据类型键值对以列簇式存储,将同一列数据存在一起键值对(value是结构化的)图结构
优势快速查询查询快,扩展性强数据结构要求不高图结构相关算法
劣势存储的数据缺少结构化功能相对局限查询性能不高不容易做分布式的集群方案

nosql的优点:

1.nosql数据库简单易部署,基本都是开源软件(成本便宜)

2.nosql将数据存储在缓存里,而关系型数据库则是存在硬盘里,随用随取,查询速率nosql极快

3.nosql可以存储多种形式的数据,如对象,键值对(redis),图片,集合等等等,关系型数据库只能存储基础类型的数据

4.扩展性比关系型好

nosql的缺点:

1.算是比较新型的技术,与传统技术相比,肯定没有那么成熟全面

2.不提供对sql的支持

3.不提供关系型数据库对事务的处理 

总结:nosql和关系型数据库就像天敌一样,类似火影里的鸣人佐助,相爱相杀,对方的优点就是自己的缺点,这样记可能更容易理解

1.3.Redis应用场景

        既然redis是基于缓存的,它的很多优势是利用缓存建立起来的,这也就有了很多应用场景,如:

        新闻页,购物网站,抢票,朋友圈,刷视频等等,例子很多

2. Redis安装

官网:Redis

中文网:Redis中文网

下载完成后解压直接就能使用:

网上也有一些可视化工具,在这里不过多讲解,大家自行去找,新手还是练习命令为好 

3.redis的数据类型

分为五种基本数据类型:

        3.1.String 字符串类型

        3.2.Hash

        3.3.list:列表元素有序,数据可以重复

        3.4.set:相当于无序集合,数据不可以重复

        3.5.zset:有序集合

三种高级类型:

        3.6Bitmaps

        3.7.HyperLogLog

        3.8.Geo

3.1.String

       字符串类型,命令很多只需记常用的即可

常用场景:做缓存、设置过期时间、分布式锁/幂等性

下面表格资源借鉴菜鸟教程Redis 字符串(String) | 菜鸟教程

下表列出了常用的 redis 字符串命令:

序号命令及描述
1SET key value
设置指定 key 的值
2GET key
获取指定 key 的值。
3GETRANGE key start end
返回 key 中字符串值的子字符
4GETSET key value
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
5GETBIT key offset
对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
6MGET key1 [key2..]
获取所有(一个或多个)给定 key 的值。
7SETBIT key offset value
对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
8SETEX key seconds value
将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
9SETNX key value
只有在 key 不存在时设置 key 的值。
10SETRANGE key offset value
用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。
11STRLEN key
返回 key 所储存的字符串值的长度。
12MSET key value [key value ...]
同时设置一个或多个 key-value 对。
13MSETNX key value [key value ...]
同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
14PSETEX key milliseconds value
这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
15INCR key
将 key 中储存的数字值增一。
16INCRBY key increment
将 key 所储存的值加上给定的增量值(increment) 。
17INCRBYFLOAT key increment
将 key 所储存的值加上给定的浮点增量值(increment) 。
18DECR key
将 key 中储存的数字值减一。
19DECRBY key decrement
key 所储存的值减去给定的减量值(decrement) 。
20APPEND key value
如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。

更多命令请参考:Command reference – Redis

在此只演示几个基本命令,大家可以动手尝试一下

1.存储一个账号(username)为cxk123的账号

2.获取账号

3.删除账号

 3.2.Hash

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

Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。

场景:对象、购物车

老样子搬出来我们的菜鸟教程Redis 哈希(Hash) | 菜鸟教程

下表列出了 redis hash 基本的相关命令:

序号命令及描述
1HDEL key field1 [field2]
删除一个或多个哈希表字段
2HEXISTS key field
查看哈希表 key 中,指定的字段是否存在。
3HGET key field
获取存储在哈希表中指定字段的值。
4HGETALL key
获取在哈希表中指定 key 的所有字段和值
5HINCRBY key field increment
为哈希表 key 中的指定字段的整数值加上增量 increment 。
6HINCRBYFLOAT key field increment
为哈希表 key 中的指定字段的浮点数值加上增量 increment 。
7HKEYS key
获取所有哈希表中的字段
8HLEN key
获取哈希表中字段的数量
9HMGET key field1 [field2]
获取所有给定字段的值
10HMSET key field1 value1 [field2 value2 ]
同时将多个 field-value (域-值)对设置到哈希表 key 中。
11HSET key field value
将哈希表 key 中的字段 field 的值设为 value 。
12HSETNX key field value
只有在字段 field 不存在时,设置哈希表字段的值。
13HVALS key
获取哈希表中所有值。
14HSCAN key cursor [MATCH pattern] [COUNT count]
迭代哈希表中的键值对。

更多命令请参考:Command reference – Redis

这里我习惯于把hash类型理解成value也是key:value的类型,即一个hash类型是key:(key:value)

简单演示一下:

■添加Ramboo,age是37(添加一个字段)

■添加stu,name是Jack,age为20,address是Los(添加多个字段)

 这几种基本类型其实都差不多,下面三种不再演示命令

 3.3.List

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

        一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

场景: 防止超卖、朋友圈点赞

下表列出了列表相关的基本命令:

序号命令及描述
1BLPOP key1 [key2 ] timeout
移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
2BRPOP key1 [key2 ] timeout
移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
3BRPOPLPUSH source destination timeout
从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
4LINDEX key index
通过索引获取列表中的元素
5LINSERT key BEFORE|AFTER pivot value
在列表的元素前或者后插入元素
6LLEN key
获取列表长度
7LPOP key
移出并获取列表的第一个元素
8LPUSH key value1 [value2]
将一个或多个值插入到列表头部
9LPUSHX key value
将一个值插入到已存在的列表头部
10LRANGE key start stop
获取列表指定范围内的元素
11LREM key count value
移除列表元素
12LSET key index value
通过索引设置列表元素的值
13LTRIM key start stop
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
14RPOP key
移除列表的最后一个元素,返回值为移除的元素。
15RPOPLPUSH source destination
移除列表的最后一个元素,并将该元素添加到另一个列表并返回
16RPUSH key value1 [value2]
在列表中添加一个或多个值
17RPUSHX key value
为已存在的列表添加值

 3.4.Set

Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

集合对象的编码可以是 intset 或者 hashtable。

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

集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

场景:共同好友/爱好

下表列出了 Redis 集合基本命令:

序号命令及描述
1SADD key member1 [member2]
向集合添加一个或多个成员
2SCARD key
获取集合的成员数
3SDIFF key1 [key2]
返回第一个集合与其他集合之间的差异。
4SDIFFSTORE destination key1 [key2]
返回给定所有集合的差集并存储在 destination 中
5SINTER key1 [key2]
返回给定所有集合的交集
6SINTERSTORE destination key1 [key2]
返回给定所有集合的交集并存储在 destination 中
7SISMEMBER key member
判断 member 元素是否是集合 key 的成员
8SMEMBERS key
返回集合中的所有成员
9SMOVE source destination member
将 member 元素从 source 集合移动到 destination 集合
10SPOP key
移除并返回集合中的一个随机元素
11SRANDMEMBER key [count]
返回集合中一个或多个随机数
12SREM key member1 [member2]
移除集合中一个或多个成员
13SUNION key1 [key2]
返回所有给定集合的并集
14SUNIONSTORE destination key1 [key2]
所有给定集合的并集存储在 destination 集合中
15SSCAN key cursor [MATCH pattern] [COUNT count]
迭代集合中的元素

 3.5.zset

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

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

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

场景:根据分数排序   排行榜

下表列出了 redis 有序集合的基本命令:

序号命令及描述
1ZADD key score1 member1 [score2 member2]
向有序集合添加一个或多个成员,或者更新已存在成员的分数
2ZCARD key
获取有序集合的成员数
3ZCOUNT key min max
计算在有序集合中指定区间分数的成员数
4ZINCRBY key increment member
有序集合中对指定成员的分数加上增量 increment
5ZINTERSTORE destination numkeys key [key ...]
计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination 中
6ZLEXCOUNT key min max
在有序集合中计算指定字典区间内成员数量
7ZRANGE key start stop [WITHSCORES]
通过索引区间返回有序集合指定区间内的成员
8ZRANGEBYLEX key min max [LIMIT offset count]
通过字典区间返回有序集合的成员
9ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT]
通过分数返回有序集合指定区间内的成员
10ZRANK key member
返回有序集合中指定成员的索引
11ZREM key member [member ...]
移除有序集合中的一个或多个成员
12ZREMRANGEBYLEX key min max
移除有序集合中给定的字典区间的所有成员
13ZREMRANGEBYRANK key start stop
移除有序集合中给定的排名区间的所有成员
14ZREMRANGEBYSCORE key min max
移除有序集合中给定的分数区间的所有成员
15ZREVRANGE key start stop [WITHSCORES]
返回有序集中指定区间内的成员,通过索引,分数从高到低
16ZREVRANGEBYSCORE key max min [WITHSCORES]
返回有序集中指定分数区间内的成员,分数从高到低排序
17ZREVRANK key member
返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
18ZSCORE key member
返回有序集中,成员的分数值
19ZUNIONSTORE destination numkeys key [key ...]
计算给定的一个或多个有序集的并集,并存储在新的 key 中
20ZSCAN key cursor [MATCH pattern] [COUNT count]
迭代有序集合中的元素(包括元素成员和元素分值)

zset使用注意事项:

  • sorted_set 底层是基于set结构的,因此数据不能重复,如果重复添加相同的数据,score值将被反复覆盖,保留最后一次修改的结果
  • score保存的数据也可以是一个双精度的double值,基于双精度浮点数的特征,可能会丢失精度的问题

 3.6.Bitmaps

        Bitmaps是基于二进制记录数据,获取话说就只有0,1两个值(状态),随说很简单但这个特性已经决定了其用途很广泛,凡是具有这两种状态的场景,都可以使用Bitmaps,例如上班打卡签到,用户是否在线,用户是否活跃,是否完成支付等等

例如,记录用户名为cxk的用户在2021-1-1~2021-1-5日的签到情况: (0未打卡)

 然后获取1号到5号某一天的打卡情况:  

命令: getbit key offset

统计一月前五天打了几次卡,即查询1的个数

即用户cxk前五天只打了两次卡... 

类似的场景有很多,遇到两种且只有两种相对情况时,脑海中要立刻想到Bitmaps

3.7.HyperLogLog

这里借鉴菜鸟教程:

        Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

        在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

        但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

3.7.1.HyperLogLog特点总结:

1.基数统计,计算时每个hyperloglog key所需空间固定12k

2.不是集合,不保存数据,只记录数量

3.方便归方便,但基数估计的结果是一个带有0.81%标准错误的近似值

4.pfadd命令不是一次性分配12k内存使用,会随着基数的增加内存逐渐增大

5.pfmerge命令合并后占用的存储空间为12k,与合并前数据量无关

由以上总结可推出HyperLogLog的应用场景:

        比如统计某篇文章的阅读量,统计网站在线人数,尤其是数据量大的情况下,占用内存较大,就可以使用HyperLogLog ,要明白一点,统计阅读量及在线人数等不必要特别精确,可以有较小的误差,而且我们的目的是计数统计,不是保存数据

下表列出了 redis HyperLogLog 的基本命令:

序号命令及描述
1PFADD key element [element ...]
添加指定元素到 HyperLogLog 中。
2PFCOUNT key [key ...]
返回给定 HyperLogLog 的基数估算值。
3PFMERGE destkey sourcekey [sourcekey ...]
将多个 HyperLogLog 合并为一个 HyperLogLog

3.7.2.命令演示:

案例:假如有个网站,只有用户(user),管理员(admin),和第三方(other)三个模块, 我们需要统计访问本网站的总人数

1.假如用户模块有zs,li,ww,zl四人访问,管理员模块有 cxk zjl wq abb访问,第三方有xx xxx xxxx访问,添加数据

 2.分别统计三个模块访问人数

3.统计访问此网站总人数

切记:用于统计不追求很具体的数据量

3.8. Geo

       Redis Geo 主要用于存储地理位置信息,并对存储的信息进行操作

       比如说:定位,附近的人,地图某两地计算,附近美食,随着信息化的发展,Geo在我们日常生活中用处十分广泛!

常用命令(菜鸟教程):

  • geoadd:添加地理位置的坐标。
  • geopos:获取地理位置的坐标。
  • geodist:计算两个位置之间的距离。
  • georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
  • georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
  • geohash:返回一个或多个位置对象的 geohash 值。

命令演示

3.8.1.添加坐标点命令:(经纬度查询:拾取坐标系统

案例:添加郑州,西安的坐标点(当地火车站坐标)

3.8.2.获取坐标

案例:获取郑州和西安的坐标

3.8.3.计算坐标距离

案例:计算郑州西安的距离(直线距离)

 由上图可以看出,默认单位是米,km代表千米,mi代表英里,ft代表英尺

 3.8.4.以给定的经纬度为中心,返回半径被所包含的位

GEORADIUS key longitude latitude radius m|km|ft|mi

[WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] 

[STORE key] [STOREDIST key]

参数说明:

m :米,默认单位。
km :千米。
mi :英里。
ft :英尺。
WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
WITHCOORD: 将位置元素的经度和维度也一并返回。
WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 
这个选项主要用于底层应用或者调试, 实际中的作用并不大。
COUNT 限定返回的记录数。
ASC: 查找结果根据距离从近到远排序。

 演示案例准备:先添加几个地点的坐标

 共五个城市坐标点

案例:给定113.667636 34.764353 查询附近200km以内的地点(只能查询存储到指定key中的地点)

        可以看到距离指定坐标200km内的地点有之前添加的郑州,焦作,新乡和洛阳,西安超出了200km,所以查不到 ,上图两个命令末尾加上withdist会查询出来value和距离,加上withcoord会查询出value和value的坐标,末尾不加则只查询出value 

3.8.5.georadiusbymember

和georadius命令一样,只不过georadiusbymember是以给定的位置元素来当中心点,georadius是以坐标点当中心点

案例:返回距离郑州100km内的value城市(指定key中的value)

 3.8.6.geohash

geohash 可以获取元素的经纬度编码字符串,它是 base32 编码。 你可以使用这个编码值去 http://geohash.org/${hash}中进行直接定位,它是 geohash 的标准编码值。

 

 4.Redis设置密码(平时不用,作为了解)

在配置文件中添加以下配置即可

windows下如何启动:

使用命令进入到安装目录,使用以下命令启动:

>redis-server.exe redis.windows.conf

启动服务端后,打开客户端

输入:auth 密码

linux下:修改完配置文件,直接重启服务即可

5.Redis常用命令

强烈推荐!!! --> redis常用命令大全 - chenxiangxiang - 博客园

    keys * 获取所有的key
    select 0 选择第一个库
    move myString 1 将当前的数据库key移动到某个数据库,目标库有,则不能移动
    flush db      清除指定库
    randomkey     随机key
    type key      类型
    
    set key1 value1 设置key
    get key1    获取key
    mset key1 value1 key2 value2 key3 value3
    mget key1 key2 key3
    del key1   删除key
    exists key      判断是否存在key
    expire key 10   10过期
    pexpire key 1000 毫秒
    persist key     删除过期时间


6.Java操作redis(Jedis技术)

        学习Jedis前,要把redis基本常用命令记住,因为等你使用java代码时你会发现方法名和命令基本相同,用起来更加得心应手

6.1.环境准备

需要用到jar包,windows环境下把redis.windows.conf确认

 如果是linux下:修改redis.conf把bind改为0.0.0.0

建议使用maven,添加pom依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.3</version>
</dependency>

6.2.Jedis操作Redis

@Test
    public void t1(){
        Jedis jedis = new Jedis();
        //存数据
//        jedis.set("name","李四");
        System.out.println(jedis.get("name"));
        jedis.set("music","small stamp");
        //获取所有key
        Set<String> keys = jedis.keys("*");
        System.out.println(keys);
        jedis.close();
    }

上述代码只执行了三个操作,就是Redis中String类型set和get命令,以及获取所有key,方法名是不是和Redis中的命令一样?

存储对象也有很多种方法,可以用hash类型,也可以用string类型存储,下面代码使用了jackson和Jedis

@Test
    public void t4() throws JsonProcessingException {
        ArrayList<User> users = new ArrayList<User>();
        User user1 = new User("Author",40,"Birmingham");
        User user2 = new User("Tommy",37,"Birmingham");
        User user3 = new User("John",32,"Birmingham");
        users.add(user1);
        users.add(user2);
        users.add(user3);
        ObjectMapper objectMapper = new ObjectMapper();
        String userList = objectMapper.writeValueAsString(users);
        jedis.set("Peaky Blinders",userList);
        System.out.println(jedis.get("Peaky Blinders"));
    }

其余的类型及操作自己下去要多练习,这里不做过多演示,可以查相关API

6.3.Jedis连接池

6.3.1.Jedis连接池操作redis

    @Test
    public void t1(){
        //获取连接池配置对象
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        //设置最大空闲连接数
        jedisPoolConfig.setMaxIdle(10);
        //设置最大连接数
        jedisPoolConfig.setMaxTotal(15);
        //最大等待毫秒数
        jedisPoolConfig.setMaxWaitMillis(1500);
        //获得连接池
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379);
        //获取连接
        Jedis jedis = jedisPool.getResource();
        //余下操作略
    }

6.3.2.使用连接池存储java对象

需满足两点条件:

1.实体类必须实现序列化

2.创建工具类:序列化和反序列化的工具类

演示案例:

第一步:实现序列化的实体类(这里引用lombok)

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    String name;
    int age;
    String address;
}

第二步:工具类

public class RedisUtil {

    private static final Long UNLOCK_SUCCESS = 1L;

    /**
     * 序列化操作
     */
    public static byte[] seriaObj(Object obj) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] bytes = null;
        try {
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);
            bytes = bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }

    /**
     * 反序列化
     */
    public static Object revSeriaObj(byte[] by){

        ByteArrayInputStream bis = new ByteArrayInputStream(by);
        Object o = null;
        try {
            ObjectInputStream ois = new ObjectInputStream(bis);
            o = ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return o;
    }

    /**
     * 存储对象(序列化)
     */
    public static void setObj(String key,Object obj){
        Jedis jedis = new Jedis("127.0.0.1",6379);
        jedis.set(key.getBytes(),seriaObj(obj));
    }

    /**
     * 获取对象(反序列化)
     */
    public static Object getObj(String key){
        Jedis jedis = new Jedis("127.0.0.1",6379);
        byte[] bytes = jedis.get(key.getBytes());
        Object o = revSeriaObj(bytes);
        return o;
    }

}

接下来测试

@Test
    //序列化
    public void t6(){
        User user = new User("zjl", 45, "台湾");
        RedisUtil.setObj("star",user);
    }
    @Test
    //反序列化
    public void t7(){
        User user = (User) RedisUtil.getObj("star");
        System.out.println(user);
    }

7.使用redis实现分布式锁

7.1.java中的常见锁

        我们在编码的时候,在多线程的情况下为了保护数据,我们会使用锁进行线程同步,如synchronized关键字修饰,Lock锁等...

7.2.分布式锁

        分布式锁可以理解为:分布式系统对共享资源通过互斥来保证一致性,从而进行操作。举个低俗却又恰当的例子,假设共享的资源是一个公厕(只有一间),第一个人需要使用需要先打开门,然后在里面把门锁住,这样第二个人来了就只能等待,等第一个人出来,以此类推,所谓的分布式锁就好比公厕的门锁,只有一个人能获得该锁。

        7.1中提到的synchronized,lock只能作用于当前代码,当前环境或者当前服务器,假设该项目被部署到了好几台服务器上,显而易见其中某一台的代码并不能影响别的服务器上的项目,这个时候分布式锁就成了很好的解决办法,可以实现“原子爆炸”,即影响多台服务器的内容

        原理:

■安全性:只有加锁才能解锁

■互斥性:同一时间段,资源只能被一人使用

■避免死锁:发生死锁是指后面的人都无法获取锁,即后面的人都无法使用该资源,由于互斥性,可以避免死锁

■保证加锁和解锁是原子性操作

        其实就是调用了set key val ex time nx 这个命令,ex是设置一个key并且赋予生命周期,nx在生命周期结束之前是不允许第二个key进行设置的,所以nx保证了互斥性,ex保证了不会死锁,利用这个特性可以实现分布式锁(幂等性)。

案例:

public class RedisUtil {

    private static final Long UNLOCK_SUCCESS = 1L;

    /**
     * 上锁
     */
    public static boolean lock(String key,String value,int timeOut){
        Jedis jedis = new Jedis("127.0.0.1",6379);
        String set = jedis.set(key, value, "NX", "EX", timeOut);
        if("OK".equals(set))
            return true;
        else
            return false;
    }

    /**
     * 使用lua脚本进行解锁,防止多线程解锁了其他线程的锁
     */
    public static Boolean unLock(String key,String value){
        Jedis jedis = new Jedis("127.0.0.1",6379);
        String luaScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else  return 0 end";
        Object eval = jedis.eval(luaScript, Collections.singletonList(key), Collections.singletonList(value));
        if(UNLOCK_SUCCESS.equals(eval))
            return true;
        return false;
    }

}

        我们可以看到解锁操作用到了lua脚本,作用就是为了防止多线程情况下这个线程解了别的线程的锁,即解锁前先判断一下解的锁是否是当前线程的!

        举个例子:a,b,c三个线程,开始a抢到了锁,开始执行任务,但是a很耗时,结果锁超时自动释放,a还在执行任务,这时看到锁释放了,b和c开始争抢,b抢到了,b开始执行任务,但这个过程中a执行完毕,a把b的锁给释放了......可以结合上面公厕的例子,张三在使用厕所方便,结果李四开门进来了。。。

        我们肯定不能允许上面这种情况发生,但是要编写代码判断是否是当前线程又会使整体代码显得很繁重,我们引入了lua脚本解决方案,因为redis执行lua脚本也是原子性的,所以很合适!

模拟分布式环境下的抢票案例:

public class MyRun implements Runnable {
    //模拟竞争资源
    public int sockets = 10;
    public String key = "sockets";
    public void run() {
        int i=0; //重试次数
        ticket(i);
    }

    /**
     * 使用分布式锁加锁
     */
    public void ticket(int i){
        String name = Thread.currentThread().getName();
        String val = sockets+"";

        if(RedisUtil.lock(key,val,1)&&i<3){
            if(sockets==0){
                System.out.println(name+"对不起票卖完了...");
                return;
            }
            sockets--;
            System.out.println(name+"==================>>>正在抢购第"+(10-sockets)+"张票!");
            //不使用lua表达式很有可能本线程中redis锁已经过时,解锁其他线程的锁,所以建议使用lua脚本解锁
            RedisUtil.unLock(key,val);
        }else {
            //重试机制,设置重试次数为2,自旋锁思想
            if(sockets!=0&&i<2){
                System.out.println(name+"正在进行第"+(i+1)+"次重试");
                i++;
                ticket(i);
            }
        }
    }
}

主方法: 模拟100个线程抢票

public class MyTest2 {
    public static void main(String[] args) {
        MyRun myRun = new MyRun();
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(myRun);
            thread.start();
        }
    }
}

 结果:

并没有出现超卖,错卖,紊乱等情况 

8.redis持久化方案

        redis是一个内存数据库,数据保存在内存中,但是内存的数据变化是很快的,且容易丢失。我们可以将redis内存中的数据持久化保存到硬盘的文件中,Redis提供两种持久化的机制,分别是RDB(Redis DataBase)和AOF(Append Only File)。

8.1.RDB(默认)

8.1.1.什么是RDB

        RDB,即Redis DataBase,redis默认的持久化方案,不需要进行配置。采用数据集快照的方式(半持久化)记录redis数据库的所有键值对,在某个时间点对数据写入一个临时文件,待持久化结束后,用该临时文件替换上次的文件,达到数据恢复。

        简单理解:在一定的间隔时间中,检测key的变化情况,然后持久化数据  

8.1.2.持久化时间间隔流程

1.编辑redis.windwos.conf文件

#   after 900 sec (15 min) if at least 1 key changed
save 900 1
#   after 300 sec (5 min) if at least 10 keys changed
save 300 10
#   after 60 sec if at least 10000 keys changed
save 60 10000

2.重新启动redis服务器,并指定配置文件名称  

        找到redis安装目录,打开黑窗口,>redis-server.exe redis.windows.conf

         因为刚编辑过redis.windwos.conf,要将redis-server.exe 和redis.windows.conf同时起作用

Redis会根据key值的变化,自动生成一个.rdb的文件

8.1.3.RDB三种触发机制

        save、bgsave、自动化

这里引用这位仁兄的博客 

RDB持久化触发机制_j_ychen的博客-CSDN博客_rdb触发条件

补充一下触发机制:(默认使用默认的配置即可)

a、满足save规则的情况下,会自动出发rdb规则

b、执行flushall命令,会触发rbd规则

c、退出redis,会触发rbd规则

8.1.4.RDB优缺点

优点:

1.只有一个dump.rdb(默认),方便持久化

2.容灾性好(保存在磁盘)

3.性能最大化,fork子进程进行写,主进程进行读,互不影响但相互配合,性能高

4.相对于数据集大时,比AOF启动效率更高

8.2.AOF

8.2.1.什么是AOF机制

        AOF(Append Only File) ,是指所有的命令行记录以redis命令请求协议的格式(完全持久化)保存为aof文件(通过Write函数),注意是保存命令行,恢复的时候把命令再执行一遍

        AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式

8.2.2.AOF写数据的三种策略

■always(每次)

        每次写入操作均同步到AOF文件中,数据零误差,性能较低,不建议使用。

■everysec(每秒)

        每秒将缓冲区中的指令同步到AOF文件中,数据准确性较高,性能较高,建议使用,也是默认配置,在系统突然宕机的情况下丢失1秒内的数据 。

■no(系统控制)

        由操作系统控制每次同步到AOF文件的周期,整体过程不可控。

8.2.3.开启方式

        AOF默认是关闭的,通过redis.conf配置文件进行开启    

## 此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能  
## 只有在“yes”下,aof重写/文件同步等特性才会生效  
appendonly yes  

## 指定aof文件名称  
appendfilename “appendonly.aof”

## 指定aof操作中文件同步策略,有三个合法值:always everysec no,默认为everysec  
appendfsync everysec  
## 在aof-rewrite期间,appendfsync是否暂缓文件同步,"no"表示“不暂缓”,“yes”表示“暂缓”,默认为“no”  
no-appendfsync-on-rewrite no  

## aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默认“64mb”,建议“512mb”  
auto-aof-rewrite-min-size 64mb  

## 相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比  
## 每一次rewrite之后,redis都会记录下此时“新aof”文件的大小(例如A)
## aof文件增长到A*(1 + p)之后,触发下一次rewrite,每一次aof记录的添加,都会检测当前aof文件的尺寸。  
auto-aof-rewrite-percentage 100

 注意在windows环境下,要重启redis,跟8.1.2.2一样,需要进入安装目录打开黑窗口

>redis-server.exe redis.windows.conf

8.2.4.aof文件被篡改怎么修复

        aof文件被篡改会导致redis启动失败,这时我们可以使用redis自带的工具:redis-check-aof来进行修复

在windows下使用以下命令修复:

redis-check-aof.exe --fix appendonly.aof

在linux下使用以下命令修复:

redis-check-aof --fix appendonly.aof

8.2.5.AOF优缺点

优点:

        1.数据安全:aof持久化可以配置appendfsync属性,always,即进行一条命令就在aof中记录

        2.通过append模式写文件,即使中途服务器故障,宕机,也可通过redis-check-aof工具解决数据一致性问题

        3.AOF的rewrite模式,aof文件没被rewrite之前,可以删除其中的某些命令

补充:其实AOF机制包含了两件事,rewrite和AOF,rewrite类似于普通数据库管理系统日志恢复点,当aof文件随着命令的写入运行膨胀时,当文件大小触碰到临界时,rewrite会被运行。(rewrite运行成功这些操作就会复制到临时文件中,最后临时文件会替代aof文件)

缺点:

        1.AOF比RDB文件大,恢复慢

        2.数据集大的时候,比RDB启动效率慢(对手的缺点就是我的优点)

8.3.RDB&AOF对比总结

持久化方式RDBAOF
占用存储空间小(数据级:压缩)大(指令级:重写)
存储速度
恢复速度
数据安全性会丢失数据策略决定
资源消耗高/重量级低/轻量级
启动优先级

选用对比:

1.RDB和AOF可以理解为一种权衡,双方各有利有弊

2.如不能承受数分钟的以内的数据丢失,对业务数据较为敏感,选用AOF

3.如能承受数分钟内的数据丢失,且追求大数据集的恢复速度,选用RDB

4.灾难恢复用RDB

5.同时开启(双保险策略),优先使用AOF来恢复数据,降低丢失数据的量

8.4.持久化应用场景

■redis 应用于抢购,限购类、限量发放优惠卷、激活码等业务的数据存储设计

■redis 应用于具有操作先后顺序的数据控制

■redis 应用于最新消息展示

■redis 应用于基于黑名单与白名单设定的服务控制

■redis 应用于计数器组合排序功能对应的排名

9.下期预告

redis内容偏多,本篇只是简单介绍基础部分,计划分为三部分总结,下篇内容计划:

■SSM整合Redis

■Springboot整合Redis

Springboot整合Redis序列化方案

■redis事务

■redis集群

制作不易希望对你们有所帮助

       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岳有才

希望能帮到你

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值