Redis – Wasting_Misaka.Blog https://forelink.top Hso! Mon, 02 Sep 2024 16:01:22 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.7.1 Redis6.0新特征 https://forelink.top/index.php/2024/09/03/redis6-0%e6%96%b0%e7%89%b9%e5%be%81/ Mon, 02 Sep 2024 16:01:21 +0000 https://forelink.top/?p=485 ACL

Redis ACL 是 Access Control List (访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接。

Redis 6 提供的ACL的功能对用户进行更细粒度的权限控制:

(1)接入权限:用户名和密码

(2)可以执行的命令

(3)可以操作的KEY

在Redis 5 版本之前,Redis安全规则只有密码控制 和rename调整高危命令(如flushdb,keys *, shutdown等)

命令:

使用 acl list 来查看当前用户的权限。

image-20240902233959748

user 用户 default 用户名字 on 启用 nopass 不需要密码 ~* &* +@all 所有命令

查看所有的操作命令 acl cat [specific usage]

image-20240902234234920

使用acl whoami命令查看当前用户

image-20240902234303486

ACL创建用户

acl setuser [username] on >[password] ~[key name] +get
创建一个用户 on启用 有密码 只能对[key name]使用get指令

切换并测试新建用户:

//切换用户
auth [username] [password]

只能使用get指令,并只能对[key name]进行操作

image-20240902234946009

IO多线程

简介:

IO多线程指的是客户端交互部分的网络IO交互处理模块多线程,而Redis6执行命令仍然是单线程。

修改配置文件开启:

// redis.conf
io-threads-do-reads yes

工具支持 Cluster:

redis5开始将redis-trib.rb的功能集成到版本中。

其他新功能:

1、RESP3 新的Redis通信协议

2、Client side caching 客户端缓存,基于RESP3协议实现的客户端缓存功能

3、Proxy集群代理模式:Proxy功能降低了cluster的使用门槛

]]>
分布式锁 https://forelink.top/index.php/2024/09/03/%e5%88%86%e5%b8%83%e5%bc%8f%e9%94%81/ Mon, 02 Sep 2024 16:00:25 +0000 https://forelink.top/?p=483 简介:

分布式集群系统演化的结果,是多线程多进程的分布在不同主机。导致原单机部署情况下的并发控制锁策略失效。

大部分Web应用都是依赖于Java的开发框架,而 Java 并没有提供分布式锁的支持。 为了解决这个问题,需要选择一种跨JVM的互斥机制来控制共享资源的访问。

分布式锁与它的不同方案,就是要解决这个问题。

目前的主流方案有:
1、基于数据库实现分布式锁
2、基于缓存实现分布式锁
3、基于Zookeeper

追求性能,基于缓存实现的分布式锁最好
希望可靠性,选择Zookeeper更胜一筹

使用redis实现分布式锁

setnx 就是redis实现分布式锁的命令

setnx lock 1
-->(integer) 1
setnx lock 1
-->(integer) 0

设置了lock之后,继续访问锁的操作。

如果锁在,会等待重试。锁不在时,才会放行其他请求。 只有把锁解开后(del lock),才能继续其他操作。

死锁问题:

锁释放会出现死锁问题。 自动化解决死锁问题,可以根据具体业务,给锁设定一个合理的过期时间:

setnx lock 1 //设置锁
expire lock 10 // 10秒后锁过期

考虑到上锁操作原子性的必要性,应该将设置锁和设置过期时间在一条指令中执行

set lock 1 nx ex 10 // nx上锁 ex过期时间

实例代码:

public void testLock(){
    // 1 获取锁 3秒后过期
    Boolean lock = redisTemplage.opsForValue().setIfAbsent("lock","1",3,TimeUnit.SECONDS);
    // 2 获取锁成功、查询num的值
    if(lock){
        Object value = redisTemplate.opsForValue().get("num");
        // 判断num为空return
        if(StringUtils.isEmpty(value))
            return;
        //将redis的num加一(业务操作),并手动释放锁
        redisTemplate.opsForValue().set("num",++num);
        redisTemplate.delete("lock");
    }else{
        // 获取锁失败,0.1s后再获取
        try{
            Thread.sleep(100);
             testLock();
        }catch(...)...
    }
}

使用UUID,给锁唯一的标识:

X设置锁后,在手动释放前宕机。Y此时在X设置的锁自动释放后,上了锁。 X在功能恢复后,会将Y设置的锁释放。

为了避免这个问题的出现,应该给锁加uuid。

String uuid = new UUID.RandomUUID().toString();
Boolean lock = redisTemplage.opsForValue().setIfAbsent("lock",uuid,3,TimeUnit.SECONDS);
if(get(lock)==uuid)
redisTemplate.delete("lock");
else{
    ~~~
}

但删除操作缺少原子性 在删除时,锁过期自动释放。此时上锁后,锁会被前置的删除操作删除。

(共享锁的问题)

LUA脚本:

弱脚本在执行时,其他指令会等待。

所以用LUA脚本可以保证删除操作的原子性。

分布式锁需要满足

-互斥性。在任意时刻,只有一个客户端能持有锁。

-不会发生死锁。

-加锁和解锁过程必须有原子性。

]]>
Redis缓存崩溃&缓存击穿&缓存雪崩 https://forelink.top/index.php/2024/09/02/redis%e7%bc%93%e5%ad%98%e5%b4%a9%e6%ba%83%e7%bc%93%e5%ad%98%e5%87%bb%e7%a9%bf%e7%bc%93%e5%ad%98%e9%9b%aa%e5%b4%a9/ Mon, 02 Sep 2024 14:30:20 +0000 https://forelink.top/?p=479 简介:

1、应用服务器压力增大

2、大量访问,但redis命中率降低(先访问缓存,未命中再访问数据库。)

3、数据库压力增大,导致崩溃

redis查询不到数据库,出现很多非正常url访问。

解决方案:

(1)对空值缓存

把空结果进行缓存,并设置空结果的过期时间

(2)设置白名单

用bitmaps类型定义可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里的id进行比较,如果访问id不在bitmaps里面,拦截不允许访问

(3)采用布隆过滤器

布隆过滤器,实际上是二进制向量(位数组)和随机映射函数(哈希函数) 用于快速判断一个一个元素是否可能在一个集合中,尽管可能会将不在集合中的元素误判在集合中,但不会把在集合中的元素误判不在集合中

(4)进行实时监控

Redis命中率下降时,将非正常访问的url和用户拉入黑名单

以上的方法可以综合使用,减少服务器压力。

Redis缓存击穿

特征:

1、数据库访问压力瞬时增加

2、redis里面没有出现大量key过期

3、redis正常运行

出现原因:

在高并发场景下,redis某个热点数据缓存过期的瞬间,收到大量请求同时访问该数据,这些请求会绕过缓存直接查询数据库,导致数据库压力骤增,甚至可能引发数据库宕机。

解决方案:

(1)预先设置热门key

在访问量增加之前,将热门key提前存入到redis中,实时调整key的过期时长

(2)实时调整

监控并实时调整key的过期时长

(3)使用锁

在缓存失效的时候(值为null),不立即去重新从数据库中取值 而是先检查缓存,如果数据存在,则直接返回 如果缓存中没有该数据,则表示该缓存可能已经过期,或数据未被缓存

缓存未命中时,尝试获取一个分布式锁(如SETNX命令),只有获取到锁的请求才可以访问数据库,其他请求则会在等待锁的释放。

获取锁的请求查询数据库后,写入Redis缓存,并根据以上的方法实时调整过期时间, 然后立即释放锁,请求会在锁被释放后,读取缓存返回给客户端。

缓存雪崩

1、在极少时间段内,查询大量key的集中过期情况

2、数据库压力变大,服务崩溃

解决方案

(1)构建多级缓存架构

如:nginx缓存 + redis缓存 + 其他缓存(ehcache等)

(2)使用锁或队列

用加锁或者队列的方式,保证不会有大量线程对数据库进行一次性读写。 不使用于高并发情况。

(3)设置过期标志更新缓存

记录缓存数据是否过期,过期时触发通知,在后台更新实际key的缓存

(4)将缓存失效时间分散

降低缓存过期的重复率,避免引发集体失效的事件。

]]>
Redis集群 https://forelink.top/index.php/2024/09/02/redis%e9%9b%86%e7%be%a4/ Mon, 02 Sep 2024 12:54:12 +0000 https://forelink.top/?p=475 简介:

容量不够时,redis如何进行扩容。 –建立服务器集群,分担写入压力。 并发写操作,redis如何分摊。

主从模式的从机继承,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。

redis3.0中提供了 无中心化集群。

无中心化集群简介

任何一台服务器都可以作为集群的入口,之间可以互相联通。

Redis集群是对Redis的水平扩容,即启动N个节点。整个数据库分布存储在这N个节点,每个节点存储总数据的 1/N。

Redis集群通过分区来提供一定程度的可用性:及时集群中有一部分节点失效或者失去通讯,也能继续进行服务。

集群搭建示例

制作6个示例并启动

在配置文件追加以下内容

cluster-enabled yes // 开启集群
cluster-config-file [filename] // 设置集群文件名称
cluster-node-timeout [time] // 设置超时时间

然后启动6个服务器

集群配置:

新版本redis不需要再装ruby环境。ruby环境配置在redis目录src文件夹中的redis-trib.rb中。 需要在该目录中执行集群配置的命令

// --cluster create 创建一个集群
// cluster-replicas 集群配置方式,1为最简单的方式
// 随后输入不同的节点[ip:port]加入集群
redis-cli --cluster create --cluster-replicas 1 192.168.199.129:6379 192.168.199.129:6380 192.168.199.129:6381 192.168.199.129:6389 192.168.199.129:6390 192.168.199.129:6391

执行后会自动分配主机和从机,输入yes后接受分配,当看到如下图片,说明集群配置成功

image-20240902144759411

测试:

用集群方式开启redis客户端,连接集群。

// -c 采用集群策略连接,设置数据会自动切换到相应的写主机
redis -c -p [port]

用 cluster nodes查看集群信息

image-20240902145144990

能看到节点的分析情况,一主一从的模式,共有三组。

Cluster自动分配:

一个集群至少要有三个主节点。 –cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。 分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。

Slots:

在集群创建成功信息中,可以看到日志包含:

image-20240902145640621

一个Redis集群包含16384个slot (0-16383),数据库中的每个键都属于这16384个slot中的其中一个。

集群使用公式 CRC16(key) % 16384 来计算键key数据哪个槽 其中 CRC16(key) 语句用于计算键 key的CRC16校验和。

集群中的每个节点是插槽的一部分,在cluster node插槽信息中可以看到。

在集群中录入值:

由于redis集群是无中心化集群,任何一个主机都可以作为集群的入口

image-20240902154547693

图中k1的slot为12706,由对应的主机完成写操作。

在集群中使用mset操作会报错,不能计算多个key的hash slot。 image-20240902154723152

如果需要添加多个key-value,需要用组的形式,格式例:

// 向数据库中加入多个值,根据{}中的值计算slot
mset name{user} lucy age{user} 20
image-20240902160451740

查询集群中的值

// 计算key的插槽值
cluster keyslot [key]
// 计算该插槽中的key数量。(在对应的主机只能看它具有的插槽)
cluster countkeysinslot [slot] [count]
// 会返回count个在slot中的key。
cluster getkeysinslot [slot] [count]

故障恢复:

如果主机下线,从节点能否自动升为主节点?(可以)

image-20240902165636533

如果主机再上线,还会成为主机吗?(不会,会成为从机)

image-20240902165756369

如果集群中某一节点(插槽段)的主机和从机都下线后,Redis集群还能继续运行吗?(不一定)

// 如果该配置属性为yes,则集群整个都会下线
// 如果为no,集群中该插槽数据全都无法访问,新数据也无法存储。
cluster-require-full-coverage [yes/no]

集群Jedis开发

Jedis可以针对集群进行操作(创建集群Jedis对象而不是Jedis对象)

// 创建对象
HostAndPort hostAndPort = new HostAndPort([host],[port]);
JedisCluster jedisCluster = new JedisCluster(hostAndPort);

// 进行操作
jedisCluster.set("b1","value1");

String value = jedisCluster.get("b1");
sout("value: "+value);

jedisCluster.close();

总结:

Redis集群实现了扩容,分摊IO压力,且无中心配置相对简单,每个主机都可以作为集群的入口。

Redis集群也有不足,多键操作不被支持,需要分组来进行操作。 多键的Redis事务是不被支持。lua脚本也不被支持。

]]>
Redis主从复制 https://forelink.top/index.php/2024/09/02/redis%e4%b8%bb%e4%bb%8e%e5%a4%8d%e5%88%b6/ Sun, 01 Sep 2024 17:10:23 +0000 https://forelink.top/?p=465 简介:

主机数据更新后根据配置和策略,自动同步到备机的 master/slaver机制 ,Master以写为主,Slaver以读为主。

从机建立时,会把主机的数据同步过来。Redis主从复制时异步的,主服务器不会等待从服务器确认数据已收到后再继续处理其他命令。 Redis2.8 引入了部分重同步(PSYNC)命令。在网络中断等情况下,从服务器重新连接主服务器时,Redis会尝试使用PSYNC来避免全量同步,只传输缺失的数据部分,从而提高效率。只有当PSYNC失败时,才会进行全量同步。

实现读写分离,性能扩展。 容灾的快速恢复。 一主多从。

一主两从示例:

在一台主机中启动两个redis-server,要创建一个新的redis.conf,并创建三个redis.conf包含以下内容

include /path/[original_redis_conf].conf
pidfile /var/run/[pid_name].pid
port [port_name]
dbfilename [file_name].rdb

然后再在指定目录的终端下启动三台redis

redis-server [subRedisConf].conf

通过端口号连接到对应的redis-server

redis-cli -p [port]

查看当前客户端连的服务端的状态

info replication
#Replication
role:master // 当前角色[主机/从机]
connected_slaves: 2333 //当前从机数量
slave0: ...
slave1: ... //从机信息

将6380和6381设置为6379的从机,需要在对应的客户端执行以下命令

slaveof [ip] [port]

在主机上执行写操作时,从机 得到数据 从机只能执行读操作,不能写入新数据。

// 6379主机
set a1 v1
// 6380从机
get key * -->
return:    a1
set a2 v2
return: (error) READONLY You can't write against a read only replica.

一主两从

从机停止后,主机不会停止 从机启动后,默认role是master,需要重新建立主从关系。

主机停止后,从机不会停止。 主机再次启动后,主从关系不用重新建立。

复制原理:

1、从机连接主服务器之后,从服务器向主服务发送数据同步消息

2、主服务器接到从服务器发送过来的同步消息,将主服务器数据进行持久化rdb文件,把rdb文件发送到从服务器,从服务器读取rdb中的数据

3、每次主机进行写操作,都会把数据更新同步到从机。

多层从机:

部署多层从机后,方便管理。

缺点是一个点从机关机后,后续从机都无法拿到新的数据。

同时成为主机和从机

从机继承主机:

// 将从机变成主机
slaveof no one

在主机下线后,

哨兵模式(sentinel):

实现自动将从机变成主机。

配置一个哨兵sentinel.conf,包含以下内容

// mymaster 是给监控对象起的名称 num为至少有多少个哨兵同意迁移的数量
// 当主机
sentinel monitor mymaster [ip] [port] [num]

哨兵启动方式

redis-sentinel /path/to/sentinel.conf

哨兵启动后,会显示部署哨兵的主机信息,还有所有的从机信息,随后跟随的是哨兵记录的日志信息。

优先级:

1、当主机下线后,从机选举产生新的主机,根据redis.conf中配置的replica-Prioriry从机优先级决定,值越小优先级越高。

2、选择偏移量最大的(获取原主机数据最全的)

3、选择runid最小的从机(redis实例启动后随机生成)

在新的主机被退选出来时,旧的主机会变为从机。

复制延迟:

所有的写操作都是在Master上操作,然后再同步更新到slave从机上。系统繁忙时会有延迟问题。(从机机器数量的增加会增加复制的延迟。

Jedis 添加哨兵方式:

sentinelSet.add("ip:port")
jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);

补充内容:

]]>
Redis 持久化 https://forelink.top/index.php/2024/09/02/redis-%e6%8c%81%e4%b9%85%e5%8c%96/ Sun, 01 Sep 2024 17:09:41 +0000 https://forelink.top/?p=463 简介:

在指定的时间间隔中,将内存中的数据集快照写入磁盘。

Redis提供了两种不同的持久化方式

RDB(Redis Database)

Redis会单独创建(folk)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。

RDB的缺点是最后一次持久化后的数据可能丢失。

备份:
Redis内存 -> 临时文件 -> dump.rdb

读取:
dump.rdb -> Redis内存

Fork:
保证数据一致性

Fork

​ Fork的作用是复制一个与当前进程,用到了写时复制技术。

Dump.rdb

是用来存储数据的文件,持久化。

在redis.conf – snapshotting(快照)中可以找到相关配置

打开save后才会启用持久化

dbfilename [filename] // 文件名
dir./[路径] // 文件生成的路径,默认为桌面
stop-writes-on-bgsave-error [yes/no] // 当Redis无法写入磁盘时,关闭redis,默认为yes
rbdchecksum [yes/no] // 检查完整性 CRC64算法数据校验 ~10%性能消耗 默认为yes
save [interval] [changes] // 在间隔中如果数据库的变化的key计数到达设定值,保存快照。保存时会阻塞所有其他请求,不建议。

优势:

​ 适合大规模的数据恢复

​ 对数据完整性和一致性要求不高

​ 节省磁盘空间

​ 恢复速度快

劣势:

Fork的时候,内存中的数据被克隆,要考虑空间资源的占用

写实复制计数在数据庞大时,仍会比较消耗性能。

在备份周期的一定间隔时间做一次备份,如果Redis意外下线,可能会造成最后一次备份的数据丢失

AOF(Append Only File)

简介:

以日志的形式来记录每个 写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许以append方式修改文件,redis启动时会读取该文件重新构建数据。

AOF 持久化流程

  1. 客户端的请求写命令会被append追加到AOF缓冲区中
  2. AOF缓冲区根据AOF持久化策略将操作sync同步到磁盘的AOF文件中
  3. AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量
  4. Redis重启时,会重新load加载AOF文件,达到恢复数据的目的

AOF默认设置

在redis.conf中开启AOF并配置文件名称,默认为appendonly.aof

appendonly yes

appendfilename "[filename]"
// 和rdb文件路径相同

AOF和RDB优先级

如果AOF和RDB同时开启,系统会优先取AOF的数据(因为它包含了更详细的写日志操作)

从Redis 4.0开始,Redis引入了混合持久化(Hybrid Persistence),将RDB快照和AOF日志结合在一起,以同时利用两者的优点。RDB用于快速恢复,AOF用于保证数据的完整性。这可以大大减少数据恢复时间。

AOF启动 / 修复 / 恢复

是以文件形式保存,只需要拷贝就可以读取。 如果AOF文件损坏,redis-server不能正常启动

如遇到AOF文件损坏,通过在/usr/local/bin 目录下执行 redis-check-aof –fix appendonly.aof 恢复

根据语法恢复。

AOF同步频率设置

appendfsync always 始终同步,每次Redis的写入都会立即写入日志
性能较差但完整性好
appendfsync everysec 每一秒都会同步
appendfsync no AOF更新时机由主机决定

AOF Rewrite重写机制

AOF采用文件追加(append)方式,文件会越来越大。重写机制在AOF文件的大小超过设定的阈值时,Redis会对AOF文件进行内容压缩,只保留可以恢复数据的最小指令集。可以使用命令bgrewriteaof,AOF会fork出一条新进程重写文件,类似rdb的快照。

Redis会记录上次重写后的AOF大小,默认配置是当AOF文件大小事上次rewrite后大小的一倍且文件大于基准值时触发,也可以自行配置。

auto-aof-rewrite-min-size [num] 设置重写的基准值

RDB和AOF,用哪个?

如果对数据恢复要求高,容忍少量数据丢失,可以使用RDB 如果对数据丢失非常敏感,AOF会比RDB更好。

官方推荐两个都启用。

]]>
https://forelink.top/index.php/2024/09/02/%e9%94%81/ Sun, 01 Sep 2024 17:08:54 +0000 https://forelink.top/?p=461 乐观锁

简介

加入版本标识,根据版本进行操作。 乐观锁适用于多读的应用类型,提高吞吐量。

Redis利用check set机制实现事务。

WATCH key

在执行multi前,先执行 watch key 监视一个/多个key,如果在执行事务之前,有key被其他命令改动,事务被打断

执行事务会返回(nil),事务没有执行。

watch [key1] [key2] ...

unwatch key

取消先前在key上设置的watch监视命令

unwatch [key1] [key2] ...

取消watch

Redis事务三特性:

  1. 单独的隔离操作


    事务中的所有命令都会序列化,按顺序的执行。在事务的执行过程中,不会被其他客户端发送来的命令所打断


  2. 没有隔离级别的概念


    队列中的任何命令在提交之前都不会实际执行,因为事务提交前任何指令都不会被实际执行


  3. 不保证原子性


    事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。


## 悲观锁

### 简介:

在进行数据处理时会上锁。
传统的关系型数据库用到了 行锁,表锁等,读锁写锁等。
都是在操作之前先上锁

示例(乐观锁)

需要多次调用的方法:

@GetMapping("/SecKill/{uid}/{goods}")
    @ResponseBody
    public boolean doSecKill(@PathVariable("uid") String uid, @PathVariable("goods") String goods){
        System.out.println("uid: "+uid+" goods: "+goods);

        // 建立redis连接
        Jedis jedis = new Jedis("192.168.199.129",6379);
        // 1 uid和goods非空判断
        if(uid == null || goods ==null){
            return false;
        }
        // 2 库存key
        String stockKey = "sk:"+goods+":qt";
        // 3 秒杀成功用户key
        String userKey = "sk:"+goods+":user";
        // 4 获取库存,如果库存null,秒杀还没有开始
        String stock = jedis.get(stockKey);
        if(stock == null){
            System.out.println("秒杀还没有开始,请稍等");
            jedis.close();
            return false;
        }
        // 5 判断用户是否重复操作
        if(jedis.sismember(userKey,uid)){
            System.out.println("您已经秒杀过该商品");
            jedis.close();
            return false;
        }
        // 6 判断是否还有库存
        if(Integer.parseInt(stock)<=0){
            System.out.println("秒杀已经结束了");
            jedis.close();
            return false;
        }
        // 7 秒杀过程
        // 库存-1
        jedis.decr(stockKey);
        // 把秒杀用户添加进清单中
        jedis.sadd(userKey,uid);
        System.out.println("秒杀成功了");
        jedis.close();
        return true;

    }

下载ab工具模拟高并发

yum install httpd-tools
ab 常用参数
    -n requests 请求次数
    -c concurrency 同时进行的次数
    -T content-type 提交模式{GET/POST...}
    -p postfile 文件,以post方式提交
// 向请求路径以-T标注的类型发送包含-p标注的文件夹共计1000次,一次100个请求。
ab -n 1000 -c 100 -p {路径/文件名} -T {类型} {请求路径}
// 模拟一次高并发
ab -n 500 -c 160 http://192.168.8.155:8080/redis/SecKill/1/2

高并发解决

连接超时问题:

往往是连接池问题,建立一个连接池类,先建立jedis连接池对象,再通过jedis连接池对象建立jedis对象

超卖问题:

需要添加乐观锁,监视多IO冲突。

// 监视库存
jedis.watch(StockKey);

// 添加事务
Transaction multi_ = jedis.multi();

// 命令加入事务
multi_.decr(StockKey);
multi_.sadd(userKey,uid);

// 执行
List<Object> results = multi_.exec();

// 判断
if(results == null || results.size()==0){
    sout(秒杀失败了);
    jedis.close();
    return false;
}

乐观锁库存遗留问题:

因为版本号不一致,失败。

使用脚本语言Lua Lua是一个小巧的脚本语言,解释器仅200k,适合作为嵌入式脚本语言。 来实现可配置性,可扩展性。

Lua 脚本可以将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能

通过lua脚本解决争抢问题,实际上就是利用了redis任务队列的方式解决多任务并发问题。

local userid=KEYS[1];
local goods=KEYS[2];
local qtkey="sk:"..goods..":qt";
local usersKey="sk:"..goods..":user";
local userExists = redis.call("sismember",userKey,userid);
if tonumber(userExists)==1 then
    // 表示已经秒杀过商品
    return 2;
end
......
Lua脚本是类似redis事务,有原子性。
但redis的lua脚本功能,只有在Redis 2.6以上的脚本才可以使用。

+Jmeter+

Jmeter对于并发测试,是更为专业的工具。
]]>
Redis事务 https://forelink.top/index.php/2024/09/02/redis%e4%ba%8b%e5%8a%a1/ Sun, 01 Sep 2024 17:07:47 +0000 https://forelink.top/?p=459 简介

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务的主要作用就是串联多个命令。

命令

// Multi命令开始事务,输入的命令会依次进入命令队列中,但不会执行
// 出现(TX)时,说明当前处于事务当中
// 提交命令后,会返回QUEUED,表示命令已进入队列
Multi [命令]

// 直到输入Exec后,Redis才会将命令依次执行
// 执行完毕后,会分行显示所有命令的返回值
Exec

// 输入discard后,放弃队列中所有命令
discard

事务错误处理

在Multi中,如果提交了错误的命令,exec提交时,整个事务中的命令都会失败。

在exec中,如果执行过程出现了错误,只有报错的命令会失败。

解决错误的方案 悲观锁 乐观锁

]]>
SpringBoot + Redis https://forelink.top/index.php/2024/09/02/springboot-redis/ Sun, 01 Sep 2024 17:06:19 +0000 https://forelink.top/?p=457 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>3.3.3</version>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

加入配置

// application.property
spring.application.name=redus-springboot
#Redis服务器地址
spring.data.redis.host= #{machine ip address}
#Redis服务器端口 默认为6379
spring.data.redis.port= #{port} 
#Redis服务器索引*(默认为0)
spring.data.redis.database=0
#Redis连接超时时间(单位:毫秒)
spring.data.redis.timeout=1800000
#Redis连接池配置(用负数时没有限制)
spring.data.redis.lettuce.pool.max-active=20
#Redis连接池最大阻塞等待时间(使用负值表示没有限制)
spring.data.redis.lettuce.pool.max-wait=-1
#Redis连接池中的最小空闲连接
spring.data.redis.lettuce.pool.max-idle=5
#Redis连接池中的最大空闲连接
spring.data.redis.lettuce.pool.min-idle=0

创建Redis配置类

// RedisConfig
// 启用Spring缓存技术
// 配置文件标记注解
@EnableCaching
@Configuration
public class RedisConfig {
        // 固定写法,创建一个RedisTemplate类 并交给IOC容器管理。
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(connectionFactory);

            // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
            Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);

            template.setValueSerializer(serializer);
            template.setHashValueSerializer(serializer);
            // 使用StringRedisSerializer来序列化和反序列化redis的key值
            template.setKeySerializer(new StringRedisSerializer());
            template.setHashKeySerializer(new StringRedisSerializer());

            template.afterPropertiesSet();
            return template;
        }
}

使用

@RestController
@RequestMapping("/redis")
public class redisTest {
    // 创建一个redis模板类对象。
    @Autowired
    private RedisTemplate<String,Object> redis;

    @GetMapping("/hso")
    public void hso(){
        redis.opsForValue().set("misaka","H");
        redis.opsForValue().get("misaka");
    }
}
]]>
Jedis依赖和支持 https://forelink.top/index.php/2024/09/02/jedis%e4%be%9d%e8%b5%96%e5%92%8c%e6%94%af%e6%8c%81/ Sun, 01 Sep 2024 17:05:44 +0000 https://forelink.top/?p=455 能支持java对Redis的操作

jedis依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.3.1</version>
</dependency>

连接方式

// 测试类程序
import redis.clients.jedis.Jedis;
// 创建一个Jedis对象,并传入地址和端口号
Jedis jedis = new Jedis(url,port);
sout(jedis.pong())

需要注释掉redis.conf中的bind本地和保护模式。 连接超时,有可能是防火墙开启,没有开放对应的端口导致。

在Linux中在防火墙中开启端口的方式 – Wasting_Misaka.Blog (forelink.top)

操作支持

// 示例
public void key_demo(){
    // 创建Jedis对象
    Jedis jedis = new Jedis(url,port)
    
    // 添加
    jedis.set("name","misaka")
    
    //获取
    String name = jedis.get("name")
    sout(name)
    
    // 遍历输出所有key
    Set<String> keys = jedis.keys("*")
    for(String key : keys)
        sout(key)
        
    // 添加多个key-value
    jedis.mset("k1","v1","k2","v2", ...)
    
    // 获取多个key-value
    List<String> mget = jedis.mget("k1","k2", ...)
    for(String key : keys)
        sout(key)
}

模拟验证码发送

  1. 输入手机号,点击发送后随机生成发送6位数字码,2分钟有效


  2. 输入验证码,点击验证,返回成功或失败


  3. 每个手机号每天只能输入3次


    // 1 生成随机6位数字验证码
    public static String getCode(){
    Random random = new Random();
    random.nextInt(10);
    String code = "";
    for(int i=0;i<6;i++){
    int rand = random.nextInt(10);
    code += rand;
    }
    return code;
    }
    // 2.1 验证码在2分钟内有效
    // 2.2 每个手机每天只能发送三次验证码
    public static void verifyCode(String phone,String code){
    Jedis jedis = new Jedis(url,port);

    String countKey = "VerifyCode"+phone+":count";

    String codeKey = "VerifyCode"+phone+":code";

    String count = jedis.get(countKey);
    if(count==null){
    jedis.setnx(countKey,24*60*60,"1");
    }else if(Integer.parseInt(count) <= 2){
    jedis.incr(countKey);
    }else if(Integer.parseInt(count) > 2){
    sout("次数超三次");
    jedis.close();
    return;
    }
    // 获取验证码 设置过期时间为120s
    String vcode = getCode;
    jedis.setex(codeKey,120,vcode);
    jedis.close();
    }
    // 3 判断验证码是否一致
    public static void getRedisCode(String phone,String code){
    // 从redis中获取验证码
    Jedis jedis = new Jedis(url,port);
    String codeKey = "VerifyCode"+phone+":code";
    String redisCode = jedis.get(codeKey);
    // 判断
    if(redisCode.equals(redisCode)){
    sout("成功");
    }else{
    sout("失败");
    }
    }



]]>