Redis

1、Redis的经典问题

缓存雪崩:指缓存大面积失效,导致大量查询落到了数据库上,使数据库挂掉。
缓存击穿:缓存中热点key突然失效,原本走缓存的大量请求直接打向了数据库,就好像在缓存中击穿了一个洞。
缓存穿透:用户一直请求缓存和数据库都不存在的数据,每次请求缓存不命中,数据库也不命中,就像缓存不存在一样,同时不断地请求也给数据库带来压力。

2、Redis的特点

1、Redis是基于内存的
2、速度快
3、如果出现了断电 那么内存数据会发生丢失
4、Redis的使用场景 一定是对数据的要求不是严格的
5、Redis的所有存储结构中都是以Key---Value的形式进行存储的
6、提供了五种不同的数据类型来满足不同的开发场景(String Hash List Set Sorted Set)
7、Redis中还提供了3中持久化模式来保证数据的持久化(将内存的数据放到硬盘)
8、Redis还提供多种淘汰策略来满足缓存的数据一定是最新的
9、Redis中还提供了大量的命令集、来完成我们开发中的操作
10、Redis从提供了三种架构(主从、哨兵、集群)

3、Redis的安装

1、将redis的安装文件复制到 /usr/local目录下去
cp redis-5.0.7.tar.gz /usr/local/
2、下载Redis运行的时候所需要的运行环境
yum install gcc
3、解压上面的Redis文件
tar -zxvf redis-5.0.7.tar.gz
4、进入Redis的根目录进行编译
cd redis-5.0.7
make
5、进入到src目录下进行安装
cd /usr/local/redis-5.0.7/src
make install
6、创建运行的命令的目录和配置文件的目录
mkdir -p /usr/local/redis/bin
mkdir -p /usr/local/redis/etc
7、移动配置文件到 /usr/local/redis/etc目录下
cp redis.conf /usr/local/redis/etc/
8、将运行命令放到 /usr/local/redis/bin目录下去
cp redis-server /usr/local/redis/bin/
cp redis-cli /usr/local/redis/bin/
    
redis-server :Redis的服务端的启动程序
redis-cli:Redis的客户端的启动程序

客户端启动之后 就可以对数据库的数据进行操作
cd /usr/local/redis/bin
./redis-server /usr/local/redis/etc/redis.conf

netstat -apn | grep 6379   :查看6379的端口使用情况

4、学习Redis的基本的命令

在Redis难道有多个数据库?
在redis中默认是16个数据库、怎么在这些数据库之间 进行切换呢?
这个数据库的下标 0----15
默认选中的是 第0号数据库 默认数据存储也是存储到第0号数据库的

1、和key相关的

keys * :查看当前数据库中存在的键
select 数据库的下标 选中某一个数据库
del key :删除某一条数据
exists key :判定某一个key是否存在

token需要使用
所有缓存的地方都需要使用这个命令

expire key 过期时间 :这个命令可以用在前后分离项目中token设置的过期时间 过期之后 key会被删除
ttl key :查看这个key剩余的时间

场景:这个主要就是key如果是需要续期的话 就可以使用这个命令
返回-2 这个key不存在
返回-1 表示没有过期时间永远都有效

move key 数据库索引 :将一个key--value移动到另外一个数据库中去

场景:一般情况下做数据库的迁移(基本用的很少)

randomkey 随机返回一个key

2、和String相关的

set key value :在数据库中设置一个键值对的数据
get key :获取某一个值

image.png

场景:存储对象的数据
set user:1:userName xiaobobo
set user:1:password 123
set user:1:count 300
get user:1:userName 只取用户名

mset k v k1 v1  :同时设置多个键值对
mget k v k1 v1  :通古斯获取多个键值对的数据

场景:存储一个对象的数据
mset user:1:userName xiaobobo user:1:password 123 user:1:count 100
mget user:1:userName user:1:password user:1:count

incr key  ;自增
decr key  :自减
incrby key 步长  :一次性增加多少
decrby key 步长  :一次性减去多少

场景:


image.png

使用:
set weibo-readcount-{文章id} 0
incr weibo-readcount-1101

setnx key value:表示的是如果这个key不存在的话  那么就设置这个键值对

场景:主要是用在分布式的锁上面


image.png

3、和Hash相关的

hset 集合的名字  键的名字  值
hget 集合的名字  键的名字  值
hlen 集合的名字  键的名字  获取当前集合中值的数量
hdel 集合的名字  键的名字
hincrby 集合的名字 键的名字 增加的数量
hgetall 集合的名字  直接实现全选的功能
hexists 集合的名字   键的名字  (判断某个人是否有购物车)
hkeys 集合的名字  获取当前这个人的所有的购物车中的商品
hvals 集合的名字   取出当前集合中所有的值

场景:购物车


image.png

存购物车的数据

hset cart:100 100001 1
hset cart:100 10998 2

命令

hmset 集合的名字   键的名字  值的名字   键的名字  值的名字
hmget 集合的名字  键的名字 键的名字

场景:存储对象数据


image.png

hmset user 1:userName xiaobobo 1:password 123
hmset user 2:userName xiaowangzi 2:password 123
hmget user 2:userName 2:password
hmset dept 部门的id:部门的字段 值

4、和List相关的

lpush  键  值  :表示的是将一个或者多个值 插入到列表的表头
lpop  键      :移除并返回这个列表的头元素   
rpush 键  值  :在列表的表位添加元素
rpop 键   从列表的右侧弹出元素
lrange 键 start  stop
BLPOP 键:从列表的表头弹出一个元素  如果列表的表头没有元素  那么就阻塞等待
BRPOP 键:从列表的右侧弹出一个元素 如果没有元素 那么就阻塞等待

说明图


image.png

场景:栈的玩法
LPUSH + LPOP
场景:构造队列
LPUSH +RPOP
场景:阻塞队列
LPUSH+BRPOP
实际的应用场景

image.png

小明关注了 Modtech 还关注了 微博开放平台
假设现在 Mobtech发消息 消息的id : 10086
List结构来进行设计 要怎么设置
需求:小明现在关注了两个平台 这两个平台在发送消息的时候 只是发送给关注了这个平台的用户 现在 mobtech要发送消息给小明了 那么现在mobtech发送的这个消息 使用list结果如何来进行设计 就能够 让小明在登陆这个系统的时候 就自动的取到这个消息呢?

lpush mobtech:msg:{小明的id} 发送这条数据的id
lpush mobtech:msg:{小红的id} 发送这条数据的id
lpush weibo:msg:{小明id} 发送这条的id
lrange mobtech:msg:{小明id} 0 50

5、和Set相关的

sadd  键  值  :就是向set集合中添加一个值  (可以用在点赞上)
srem  键  值  :删除这个键中的某一个值  (可以用在取消点赞上)
sismember 键  值  检查某一个值是否在这个集合中存在
smembers key :表示的是获取这个集合中的所有数据
srandmemebr key count :从集合中选出count个元素 ,元素不从key中删除(值是随机的)
spop key count :从集合中选中count个元素 元素从集合中删除
sinter key :做做交集运算
sinterstore desternation key:将交集的结果存入新的集合
sunion key :并集运算
sunionstore desternation key :将并集的结果存入这个集合
sdiff key :差集运算
adiffstore desternation key :将差集的结果存入这个集合
scard key  :计算当前set集合中的元素的个数

适用场景:朋友圈点赞

我要使用sadd 来存储当前这哥心情的点赞
怎么设计这个点赞才是最合理的?
点赞
sadd weixin:like:{消息的id} 用户的id
sadd 集合的名字 集合的值
取消点赞
srem weixin:like:10010 10 取消某一个人的点赞
检查某一个人是否点过赞
sismember weixin:10010 120 :检查id是120的用户是否点赞过这条消息
获取点赞的用户列表
smembers weixin:like:10010
获取当前点过赞的所有的用户
scard weixin:like:10010

多个集合之间运算


image.png

场景:


image.png

6、和Sorted Set相关的

备注:Sorted Set是自动根据打分实现排序的

zadd 集合的名字  打分  键的名字   :向Sorted Set中添加一个数据
zrange 集合的名字 开始的位置  结束的位置  获取集合中某一个区间的值
zincrby 集合的名字 加的分值  key的名字   //给某一个值添加分值
zrevrank 集合的名字 键的值   :查看当前数据的排名
zrem 集合的名字 键的名字     :删除某一条数据
zscore 集合的名字  键的名字 
zcount 集合的名字   开始  结束   :获取得分在某一个区间类的数据的个数

场景:
就是给做最热商品 或者 人气商品的时候用
什么是最热商品 和 人气商品?
最热商品:购买数最多的 人气商品(点击量最多的、收藏量最多)
//假设最热商品
1:只要商品产生购买
zadd good:hot 10 商品id
又产生了购买
加分:zincrby 集合的名字 加的分值 key的名字
张三在直播平台看自己礼物的排名?
zrevrank 集合的名字 键的值 :查看当前数据的排名
产品下架了不卖了?
zrem good:hot goodId1 :删除某一条数据
人气得分(热度)
热度:弹幕的数量(算法)+不同礼物不同的得分+播放的时间点+粉丝数量
zscore good:hot goodId3

拓展
1、缓存的请求逻辑


image.png

Redis的使用二

1、发布订阅模式

例子:比如说你们家有个收音机 你收听了 xxxxx 频道 那么只要你打开这个频道 你就能收听到这个频道的所有的内容
你的收音机-----------接收方(订阅方)
频道的内容发送方-------内容的发布者
subscribe 订阅的频道的名称
publish 频道名字 内容
场景:这个功能实际上就是咋们的 MQ中的功能(不用管它)


image.png

2、rdb模式实现持久化

Redis我们说是基于内存的、所以速度快、但是Redis的数据放到内存里面、当Redis重启的时候 这个数据会发生丢失
假设我们能把写入到内存的数据、持久化到硬盘 那是不是就能保证我们的数据即使发生丢失 也不会全部丢失、或者全部不丢失呢?
Redis的持久化就产生了----默认情况下 Redis本身也是有持久化策略的
我们即使不配置 那么这个Redis的持久化依然存在的

rdb是数据库默认的持久化模式-----又称为快照模式
这个模式是将内存的数据内容 直接保存到 dump.rdb这样的一个二进制文件中的
特点:因为保存的是二进制文件、所以做数据的恢复是相当的快的---适合于做数据的备份
rdb到底是如何保存我们的数据库的:rdb每一次在保存这个数据的时候、首先都会清空原有的dump.rdb文件、然后将整个内存的数据全部写入到这个文件中 rbd-------保存的是redis内存中某一个时刻的数据(适合备份、不适合开发用)
假设刚好清空dump.rdb这个文件、现在断电了------------数据全丢了
什么时候 rdb模式会触发内存的数据和硬盘进行同步呢?

save 900 1  在900秒的时间内如果有1和key发生改变 那么将触发内存和硬盘同步
save 300 10 在300秒的事件内如果有10个key发生改变  那么将会触发内存和硬盘同步
save 60 10000 在60秒的时间类假设有10000个key发生改变那么也变触发内存和硬盘同步

rdb模式是不用开启的、这个模式的redis自动开启的

4、aof实现持久化

aof模式是在redis1.1的版本的时候、才增加的一种持久化模式
aof模式在使用的时候 保存的不是数据 是我们操作的时候的一条一条的命令
只要调用了Redis 那么只要有命令的使用那么都会被记录到aof文件中
aof---------------记录的是操作的命令 不记录实际的数据
aof模式如果是在数据恢复还原的时候 效率并不高 数据恢复的话采用rdb文件恢复是最快的
aof模式如何使用
appendonly yes :表示的是开启aof的模式

*3  *代表的是命令的开始   3 :这个表示的是命令中一共有3块内容
$3 $使用修饰命令中的每一个参数的 3代表的是下面的这个命令一共有3个字符
set
$5
email
$5
xxxxx

rdb 存在 aof 现在也存在 ? 你们觉得会以谁优先 这里如果是开启了aof模式 那么会以aof模式为优先
aof模式是否有触发策略?

appendfsync always :只要有键发生改变 立马同步(每一次都触发IO操作、速度就慢下来、这种情况是不会丢数据)-----一般不用(效率太低了)
appendfsync everysec :每一秒钟进行数据同步一次(开发的时候一般选用他----速度上也比较快 即使出现数据的丢失也只会丢失1秒钟的数据)
appendfsync no :永远不同步、数据只是放到缓存里面 速度快 但是数据容易丢失

aof模式是如何同步数据的呢?
每一次在进行数据同步的时候 使用的是 追加的模式 以前的数据不用删除 只需要追加新的操作即可
aof模式消息的重写

no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100  (这个表示的是必须达到100%的增加才重写  64M+64M=128M )
auto-aof-rewrite-min-size 64mb   aof文件(简单的说就是至少aof文件达到64M才重写)
手动重写
bgrewriteaof :这个命令就是手动重写 
重写的好处是对aof文件进行优化(最终的结果都是一样的)

如果你要去手动重写(需要关闭混合持久化的开关才能看到功能)
aof-use-rdb-preamble no

4、混合持久化的问题

我们在开发的时候到底是选择 rdb 还是aof呢?

如果你关心的是你的数据、但是任然可以接受在一段时间内依然可以接受一些数据的丢失 那么你可以使用rdb(速度快)
我们以前开发的时候基本上都使用aof模式来做开发、如果你用的版本是4.0以前的版本那么建议你呢还是使用aof
在4.0以后 就出现了一种新的持久化模式(混合持久化)
这个混合持久化 就集成了 rdb的有点和aof所有的有点

理解混合持久化


image.png

5、缓存的淘汰策略

什么是缓存淘汰策略、简单的说就是咋们的redis中的内存已经满了、现在要保证内存中的Redis的数据 一定是最新的、那么这个时候就需要配置缓存的淘汰策略了

noeviction:只要缓存满了、那么就不继续服务器里面的写的请求、读的请求是可以完成的、这种模式缓存里面的所有数据 都不会丢失、这种情况会导致参与Redis的业务会失败
volatile-lru:他会优先淘汰掉 设置了过期时间的这个key、然后第二步才淘汰掉使用的比较少的key 假设我们的key没有设置过期时间的话 那么不会优先淘汰
这种模式也是咋们在开发中使用的比较多的一种缓存策略模式
allkeys-lfu:和lru是有区别的、这个在淘汰的时候、淘汰的是全体key的集合、不是过期的key的集合(过期这一说法没有)、这就意味着你没有设置过期时间的key 只要使用的比较少那么依然会被淘汰
volatile-ttl:这个淘汰策略不是LRU 、而是key剩余的寿命的ttl值 ttl值越小 越先被淘汰
allkeys-random:使用这个淘汰策略的时候 淘汰的是随机的key
maxmemory-policy volatile-lru 这个就是配置缓存的淘汰策略的
maxmemory :这个是配置Redis的缓存的大小

6、主从复制问题

image.png

image.png

一台服务器主服务器(39.99.200.54)

另外两台作为从服务器

106.54.13.167 47.96.119.26

实操

主服务器更改

daemonize yes    //后台启动
bind 0.0.0.0     //表示的是允许所有人访问

从服务器配置的更改

daemonize yes    //后台启动
bind 0.0.0.0     //表示的是允许所有人访问
slaveof 主服务器的ip地址  主服务器端口

使用如下命名 检查主从是否配置好

./redis-cli
info

如果主服务器是如下的


image.png

从服务器的信息如下


image.png

7、哨兵模式

image.png

哨兵是安装在任意一台服务器上 、跟咋们的主服务器 并没有任何的关联
哨兵配置的节点:47.96.119.26
哨兵的配置
1、将Redis解压目录下的sentinal.xml文件复制到 /usr/local/zhucong/目录下
2、配置sentinal.xml文件

#配置的是配置文件的目录
dir /usr/local/zhucong/
#配置的是监听主服务器信息  最后一个参数很重要 一般设置为1  多少票通过
sentinel monitor mymaster 39.99.200.54 6379 1
#配置的是意思是心跳信号发给你了 多久没回应就认为主服务器死了...
sentinel down-after-milliseconds mymaster 5000  
哨兵的启动命令
./redis-server /usr/local/zhucong/sentinel.conf  --sentinel &

8、集群模式

image.png

集群的实操

1、在/usr/local目录下创建一个文件夹redis-cluster1
mkdir /usr/local/redis-cluster1
2、在redis-cluster1中创建6个文件夹
mkdir 7001 
mkdir 7002
....
mkdir 7006
3、将redis解压目录中的 redis.conf文件复制到7001中
cp redis.conf /usr/local/redis-cluster1/7001
4、vim这个文件进行更改
daemonize yes
port 7001
dir /usr/local/redis-cluster1/7001
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 5000
appendonly yes
5、将7001中的redis.conf文件 分别复制到 7002-7006中 更改里面所有是7001的地方更改成 对应的端口号就可以了
...
6、安装ruby的相关的工具(5.0的时候没有使用ruby去创建集群这个步骤可省略)
yum install ruby 
yum install rubygems
gem install redis
安装最后一个工具就出错了....
(centos7 安装较高版本ruby2.2/2.3/2.4+)按照步骤玩一次就对了

7、开启每一个服务
./redis-server /usr/local/redis-cluster1/7001/redis.conf
./redis-server /usr/local/redis-cluster1/7002/redis.conf
./redis-server /usr/local/redis-cluster1/7003/redis.conf
./redis-server /usr/local/redis-cluster1/7004/redis.conf
./redis-server /usr/local/redis-cluster1/7005/redis.conf
./redis-server /usr/local/redis-cluster1/7006/redis.conf
8、创建集群
以前的版本:
./redis-trib.rb create --replicas 1 106.54.13.167:7001 106.54.13.167:7002 106.54.13.167:7003 106.54.13.167:7004 106.54.13.167:7005 106.54.13.167:7006
现在的版本
redis-cli --cluster create 106.54.13.167:7001 106.54.13.167:7002 106.54.13.167:7003 106.54.13.167:7004 106.54.13.167:7005 106.54.13.167:7006 --cluster-replicas 1
    
登陆集群某一个客户端的命令
./redis-cli -c -h 106.54.13.167 -p 7001

9、玩下SpringBoot整合Redis

9.1、导包


            com.alibaba
            fastjson
            1.2.54
        


        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.projectlombok
            lombok
            true
        

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        

9.2、配置文件的编写

#配置redis

spring.redis.host=39.99.200.54
#设置端口
spring.redis.port=6379

#给数据库设置密码
#spring.redis.password=xxxx
#设置对大的连接数
spring.redis.jedis.pool.max-active=10

#设置线程池中最大的空闲的连接
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=0

#连接池最大的阻塞时间  -1的话那么 表示没有限制
spring.redis.jedis.pool.max-wait=-1ms

9.3、manager的使用

@Component     //先放入IOC的容器
public class RedisManager {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    /**
     * 向Redis中存放一个键值对
     * @param key
     * @param value
     */
    public void addKeyAndValue(String key,String value){
        /**
         * stringRedisTemplate.opsForValue(); 这个就是用来操作 String类型的
         * stringRedisTemplate.opsForList(); 这个主要就用来操作list的
         * stringRedisTemplate.opsForZSet(); 这个主要用来操作sorted set
         * stringRedisTemplate.opsForHash(); 用来操作hash结构的
         * stringRedisTemplate.opsForSet();  这个就是用来操作Set数据类型的
         */
        stringRedisTemplate.opsForValue().set(key,value);
    }

    /**
     * 通过key获取String类型中的值
     * @param key
     * @return
     */
    public String getValueForKey(String key){
         return stringRedisTemplate.opsForValue().get(key);
    }
}

9.4、常见的api说明

/**
     * 常用的API的意思
     * //设置key过期的API
     * stringRedisTemplate.expire("NZ1904",60, TimeUnit.SECONDS);
     * //ttl :获取一个键的过期时间
     * stringRedisTemplate.getExpire()
     * //  exists :判断一个键是否存在
     * stringRedisTemplate.hasKey("");
     * //del :删除一个键值对
     * stringRedisTemplate.delete("key");
     *incrby 这个命令
     * stringRedisTemplate.opsForValue().increment("key",1);
     *decrby这个命令
     * stringRedisTemplate.opsForValue().increment("key",-1);
     *hmget这个命令
     *stringRedisTemplate.opsForHash().entries("");
     *hmset
     *stringRedisTemplate.opsForHash().putAll("",null);
     *hset这个命令
     *stringRedisTemplate.opsForHash().put();
     * hdel命令
     *stringRedisTemplate.opsForHash().delete()
     * 判断hash结构中这个键是否存在
     * stringRedisTemplate.opsForHash().hasKey()
     * Set集合中获取某一个值
     * stringRedisTemplate.opsForSet().members()
     * 判断set集合中是否存在某一个值
     *stringRedisTemplate.opsForSet().isMember()
     * set集合设置值
     * stringRedisTemplate.opsForSet().add()
     */

10、分析票超卖的问题

image.png

参考图


image.png

最终分析的代码

public String produceStock(){
        String lock="lock";
        String value=UUID.randomUUID().toString();
        //设置个字符串
        try {
            // setnx命令
            //Boolean tag = stringRedisTemplate.opsForValue().setIfAbsent(lock, "");
            //给这个key设置过期时间
            //执行下面这一句话的时候突然死了?  是不是又出现死锁了
            //stringRedisTemplate.expire(lock,30,TimeUnit.SECONDS);
            //这个命令的底层实际上 也运行的是 咋们的命令  在底层他是怎样来实现原子性的呢?
            // 充分的利用了 redis和lua脚本  在C的层面上来实现原子性的a
            //setnx  只有这个可以不存在的时候 这个才会被删除
            Boolean tag=stringRedisTemplate.opsForValue().setIfAbsent(lock,value,30,TimeUnit.SECONDS);
            if (!tag) {
                return "目前排队人数过多...请稍后重试";
            }
            //超过了15
            //开一个守护线程
            MyThread myThread = new MyThread(lock);
            myThread.setDaemon(true);
            myThread.start();

            // 每隔设置时间的3分支1就进行线程的续命

            //一会初始值的时候我将火车票  放到redis中去
            //减去库存
            //去Redis中将库存数据给取出来
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("traintickes"));
            //首先要判断下 这个库存是否>0
            if (stock > 0) {  //说明可以减去库存
                int rStock = stock - 1;
                //下一步:将真实的库存放到咋们的Redis中去
                stringRedisTemplate.opsForValue().set("traintickes", String.valueOf(rStock));
                logger.info("扣减库存成功....剩余库存:" + rStock);

            } else {   //说明不能扣减库存
                logger.info("库存扣减失败、库存是负数、不足...");
            }  //已经用了15秒钟

        }finally {
            if(value.equals(stringRedisTemplate.opsForValue().get(lock))){
                stringRedisTemplate.delete(lock);
            }
        }
        return "抢票成功....";

    }

11、基于Redis的分布式锁问题Redssion的简单的使用

Redission这个框架就解决了 分布式锁的问题
Reddsion这个框架实际上也是咋们的Redis的客户端代码

11.1、首先是导包


        
            org.redisson
            redisson
            3.11.0
        

11.2、编写配置文件

 @Bean
    public RedissonClient redissonClient(){
        RedissonClient redissonClient=null;
        //获取config的实例
        Config config = new Config();
        //设置请求的URL地址
        String url="redis://106.54.13.167:6379";
        //设置config
        config.useSingleServer().setAddress(url);
        config.useSingleServer().setPass...(xxxx);
        //通过Redisson来创建一个客户端对象
        try{
            redissonClient= Redisson.create(config);
            logger.info("创建RedissonClient成功");
            return redissonClient;
        }catch (Exception err){
            logger.info("创建RedissonClient失败:"+err.fillInStackTrace());
            return null;
        }

    }

11.3、编写lock的类

@Component
public class DistributeRedisLock {

    private Logger logger= LoggerFactory.getLogger(getClass());

    @Autowired
    private RedissonClient redissonClient;


    //一个方法用来加锁

    /**
     * 加锁成功....
     * @param lockName
     * @return
     */
    public boolean lock(String lockName){
        try {
            if(null==redissonClient){  //如果对象没有注入进来那么说明是有问题的
                logger.info("注入redissonClient对象失败....");
                return false;
            }
            //获取这个锁
            RLock lock = redissonClient.getLock(lockName);
            //锁住了
            lock.lock(30, TimeUnit.SECONDS);
            logger.info("加锁成功.......");
            return true;
        } catch (Exception e) {
            logger.info("不可预期的异常造成了加锁失败....");
           return false;
        }
    }


    /**
     * 释放锁
     * @param lockName
     * @return
     */
    public boolean unlock(String lockName){
        try {
            if(null==redissonClient){  //说明没法释放出问题了....
                logger.info("释放锁失败----"+lockName);
            }
            //获取到这个锁对象
            RLock lock = redissonClient.getLock(lockName);
            if(null!=lock){
               lock.unlock();
               logger.info("释放锁成功....");
               return true;
            }
            return false;
        } catch (Exception e) {
            logger.info("释放锁失败了....");
            return false;
        }
    }
}

11.4、调用

 public String produceStockRedisson(){
        String lock="lock";
        try {
            boolean lock1 = distributeRedisLock.lock(lock);
            if(true==lock1){//说明加锁成功
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("traintickes"));
                //首先要判断下 这个库存是否>0
                if (stock > 0) {  //说明可以减去库存
                    int rStock = stock - 1;
                    //下一步:将真实的库存放到咋们的Redis中去
                    stringRedisTemplate.opsForValue().set("traintickes", String.valueOf(rStock));
                    logger.info("扣减库存成功....剩余库存:" + rStock);
                } else {   //说明不能扣减库存
                    logger.info("库存扣减失败、库存是负数、不足...");
                }  //已经用了15秒钟
            }else{
                return "当前的排队人数过多...";
            }
        }finally {
            distributeRedisLock.unlock(lock);
        }
        return "抢票成功....";
    }

12、SpringBoot整合下的键值序列化的话题

为什么键值要序序列化呢?

不同平台之间的数据传输 深拷贝 浅拷贝

Redis的序列化到底是什么?

简单的是说 就是 key 和 value存储到redis中的形式 这个样子是可以自己定义的

12.1、自定义一个序列化转换器

public class BoboSerializer implements RedisSerializer {

    private Class clazz;

    public BoboSerializer(Class clazz){
        this.clazz=clazz;
    }

    /**
     * 就是序列化的方法
     * 简单的说就是将对象转换成字符串的方法
     * @param o
     * @return
     * @throws SerializationException
     */
    @Override
    public byte[] serialize(Object o) throws SerializationException {
        if(null==o){
           return null;
        }
        //我们要将这个值转换成json对象存储到Redis中
        String jsonString = JSON.toJSONString(o);
        return jsonString.getBytes(Charset.forName("UTF-8"));
    }

    /**
     * 反序列化
     * 简单的说就是将redis中的字符串转换成 java对象的
     * @param bytes
     * @return
     * @throws SerializationException
     */
    @Override
    public Object deserialize(byte[] bytes) throws SerializationException {
        if(null==bytes) {
            return null;
        }
        String strResult = new String(bytes);
        //将String类型的数据(JSON)转换成java对象
        return JSON.parseObject(strResult,clazz);
    }
}

12.2、编写配置文件

@Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate redisTemplate = new RedisTemplate<>();
        //设置连接工厂
  redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置的是值的序列化器
//        redisTemplate.setValueSerializer(new BoboSerializer(Object.class));
        //在Redis着有提供json格式的的序列化器
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));

        return redisTemplate;
    }

12.3、测试代码的编写

redisTemplate.opsForValue().set("user111",new User(2,"xiaobobo2","1232"));

13、Redis开发中的常见问题

13.1、Redis的缓存穿透

什么是缓存穿透 ...

简单的说就是获取数据的时候后 先去redis找数据 结果没找到 又去MySQL中找数据 结果还是没有找到 这样的话 那么 每一个线程进来都要去访问数据库、这样的话数据库的压力就很大 数据库就会奔溃 这种现象就叫做 缓存穿透


image.png

13.2、Redis下的缓存雪崩的问题

image.png

13.3、Redis的脑裂问题

image.png

客户端向主服务器写入了数据 但是主服务器还没有来得及同步的情况下 主服务器死了 那么这个时候就会选举新的主服务器 原来的主服务器在一段时间之后 又好了 那么这个时候 原来的主服务器 只能作为从服务器了 原来主服务器的数据 没有办法进行同步 这种问题 就是redis的脑裂问题
解决方案
min-slaves-to-write 1 这个表示的意思是:在我们客户端写入数据的时候 至少保证 主服务器上有一个从服务器 处于正常连接才能写入这个数据
min-slaves-max-lag 10 :这个表示的的意思是 主从同步的时间 10s

你可能感兴趣的:(Redis)