redis与memcached的区别:
memcached是高性能的分布式内存缓存服务器。一般使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态web应用的速度、提高可扩展性。
redis是一个开源的内存数据库,它以键值对的形式存储数据。由于数据存储在内存中,因此Redis的速度很快,但是每次重启Redis服务时,其中的数据也会丢失,因此,Redis也提供了持久化存储机制,将数据以某种形式保存在文件中,每次重启时,可以自动从文件加载数据到内存当中。
相关知识:
redis默认的端口号为6379,默认有16个数据库,类似数组小标从0开始,初始默认使用0号数据库。
使用select 命令来切换数据库。例如 select 2 切换到2号数据库。
使用dbsize命令来查看当前数据库的key的数量。
使用flushdb命令来情空当前数据库中所有数据。
使用flushall命令来删除所以数据库中的数据。
Redis是单线程+多路IO复用技术。多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。
String 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个value。
String 类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
String 类型是 Redis 最基本的数据类型,String 类型的值最大能存储 512MB。
String类型一般用于缓存、限流、计数器、分布式锁、分布式Session。
结构图:
相关命令:
Redis列表是简单的字符串列表,按照插入顺序排序(后进先出)。你可以添加一个元素到列表的头部(左边)或者尾部(右边)一个列表最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
List类型一般用于关注人、简单队列等。
结构图:
相关命令:
Redis 的 Set 是 String 类型的无序集合。集合中成员是唯一的,这就意味着集合中不能出现重复的数据。Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。
Set类型一般用于赞、踩、标签、好友关系等。
结构图:
相关命令:
SCAN 是一个基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程。SCAN 以 0作为游标,开始一次新的迭代,直到命令返回游标 0 完成一次遍历。此命令并不保证每次执行都返回某个给定数量的元素,甚至会返回 0 个元素,但只要游标不是 0,程序都不会认为 SCAN 命令结束,但是返回的元素数量大概率符合 Count 参数。另外,SCAN 支持模糊查询。
例:
SSCAN names 0 MATCH test* COUNT 10
#每次返回10条以test为前缀的key。
Redis 有序集合和集合一样也是string类型元素的集合且不允许重复的成员。不同的是每个元素都会关联一个 double类型的分数 。redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。
Zset类型一般用于排行榜等。
结构图:
相关命令:
Redis hash 是一个 string 类型的 field(字段)和 value(值) 的映射表,hash 特别适合用于存储对象。Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)
Hash类型一般用于存储用户信息、用户主页访问量、组合查询等。
结构图:
相关命令:
介绍:现代计算机用二进制(位) 作为信息的基础单位, 1个字节等于8位, 例如“abc”字符串是由3个字节组成, 但实际在计算机存储时将其用二进制表示, “abc”分别对应的ASCII码分别是97、 98、 99, 对应的二进制分别是01100001、 01100010和01100011,如下图:
合理地使用操作位能够有效地提高内存使用率和开发效率。
Redis 6 中提供了 Bitmaps 这个“数据类型”可以实现对位的操作:
1)Bitmaps本身不是一种数据类型,实际上它就是字符串(key-value),但是它可以对字符串的位进行操作。
2)Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。
相关命令:
1.setbit
这个命令用于设置Bitmaps中某个偏移量的值(0或1),offset偏移量从0开始。格式如下:
setbit
例如,把每个独立用户是否访问过网站存放在Bitmaps中, 将访问的用户记做1,没有访问的用户记做0,用偏移量作为用户的id。
设置键的第offset个位的值(从0算起) , 假设现在有20个用户,userid=1,6,11,15,19的用户对网站进行了访问, 那么当前Bitmaps初始化结果如图:
注意:
很多应用的用户id以一个指定数字(例如10000) 开头, 直接将用户id和Bitmaps的偏移量对应势必会造成一定的浪费, 通常的做法是每次做setbit操作时将用户id减去这个指定数字。
在第一次初始化Bitmaps时, 假如偏移量非常大, 那么整个初始化过程执行会比较慢, 可能会造成Redis的阻塞。
2.getbit
这个命令用于获取Bitmaps中某个偏移量的值。格式为:
getbit
获取键的第offset位的值(从0开始算)。例如获取id=6的用户是否在2022-07-18这天访问过, 返 回0说明没有访问过:
3.bitcount
这个命令用于统计字符串被设置为1的bit数。一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。start 和 end 参数的设置,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,start、end 是指bit组的字节的下标数,二者皆包含。格式如下:
bitcount [start end]
用于统计字符串从start字节到end字节比特值为1的数量。例如,统计id在第1个字节到第3个字节之间的独立访问用户数, 对应的用户id是11, 15, 19。
4.bitop
这个命令是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。格式如下:
bitop and(or/not/xor) [key…]
简介:在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV(PageView 页面访问量),可以使用Redis的incr、incrby轻松实现。
但像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。
解决基数问题有很多种方案:
1)数据存储在MySQL表中,使用distinct count计算不重复个数
2)使用Redis提供的hash、set、bitmaps等数据结构来处理
以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。
为了能够降低一定的精度来平衡存储空间,Redis推出了HyperLogLog。
HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以HyperLogLog 不能像集合那样,返回输入的各个元素。
什么是基数?
比如数据集 {1, 3, 5, 7, 5, 7, 8},那么这个数据集的基数集为 {1, 3, 5 ,7, 8},基数(不重复元素) 为5。 基数估计就是在误差可接受的范围内,快速计算基数。
相关命令:
Redis 3.2 中增加了对GEO类型的支持。**GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。**redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。
相关命令:
配置大小单位,开头定义了一些基本的变量单位,只支持 byte,不支持 bit。大小不敏感
它类似于 JSP 中的 include,大多数实例可以把公共部分用到的配置文件提取来,然后通过include 把这个公共部分包含进来。
(1) bind
默认情况下 bind=127.0.0.1 只能接受本机的访问请求。在不写的情况下,无限制接受任何 IP 地址的访问。
生产环境需要填写你应用服务器的地址。由于服务器是需要远程访问的,所以需要将其注释掉。
注意:如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应。
我们把 bind 127.0.0.1 -::1 它注释掉。
(2)protected-mode
我们如果希望远程访问,那么需要把它设置为 no 。
(3)port
默认端口号为 6379,可以在这里对它进行设置。
(4)tcp-backlog
设置 tcp 的 backlog,backlog 其实是一个连接队列,backlog队列总和 = 未完成三次握手队列 + 已经完成三次握手队列。
在高并发环境下你需要一个高 backlog 值来避免慢客户端连接问题。
注意:
Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值(128),所以需要确认增大/proc/sys/net/core/somaxconn和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)两个值来达到想要的效果
(5) timeout
一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭
(6)tcp-keepalive
对访问客户端的一种心跳检测,每 n 秒检测一次。
单位为秒,如果设置为0,则不会进行 Keepalive 检测,建议设置成 60。
(1)daemonize
是否为后台进程,即守护进程,用于后台启动,设置为yes。
(2)pidfile
存放pid文件的位置,每个实例会产生一个不同的pid文件。
(3)loglevel
指定日志记录级别,Redis 总共支持四个级别:debug、verbose、notice、warning,默认为notice。
四个级别根据使用阶段来选择,生产环境选择 notice 或者warning。
(4)logfile
指定日志文件名称
(5)databases
设定库的数量,默认16,默认数据库为0,可以使用 SELECT 命令在连接上指定数据库id。
当设置好密码后(即把 requirepass foobared 注解解开),然后使用客户端连接服务器后,在执行 set 命令时,提示需要获取权限。
[root@redis-101 bin]# redis-cli 127.0.0.1:6379> set k1 v2 (error) NOAUTH Authentication required.
此时需要使用 auth 命令来输入密码:
127.0.0.1:6379> auth foobared OK127.0.0.1:6379> set k1 v2 OK
(1)maxclients
设置redis同时可以与多少个客户端进行连接。默认情况下为10000个客户端。如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。
(2)maxmemory
建议必须设置,否则可能导致将内存占满,造成服务器宕机。
设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。
如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素
(3) maxmemory-policy
用于设置内存达到使用上限后的移除规则。有以下参数可设置:
volatile-lru:使用LRU(最近最少使用算法)算法移除key,只对设置了过期时间的键;(最近最少使用)
allkeys-lru:在所有集合key中,使用LRU算法移除key
volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
allkeys-random:在所有集合key中,移除随机的key
volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
noeviction:不进行移除。针对写操作,只是返回错误信息
(4)maxmemory-samples
用于设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。
一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小。
在使用 Jedis 连接 Redis 之前,服务器需要做如下操作:
1.关闭防火墙
systemctl stop firewalld.service
systemctl disable firewalld.service
2.redis开启远程访问
1)把 bind 值注释
2)把 protected-mode 的值设置为 no
org.springframework.boot spring-boot-starter-web 2.7.1 org.springframework.boot spring-boot-starter-data-redis 2.7.1 org.apache.commons commons-pool2 2.11.1 org.springframework.boot spring-boot-starter-test test 2.7.1
application.yml
spring:redis:host: 192.168.124.131port: 6379password:database: 0# 连接超时时间,单位为毫秒timeout: 180000lettuce:pool:enabled: true# 最大的活动数max-active: 20# 最大的空闲数max-idle: 5# 最大阻塞等待时间,如果为-1表示没有限制max-wait: -1
package com.openlab.redis.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;/*** redis配置类*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic RedisTemplate redisTemplate(RedisConnectionFactory factory) {RedisTemplate template = new RedisTemplate<>();// 配置序列化对象RedisSerializer serializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);template.setConnectionFactory(factory);// key序列化方式template.setKeySerializer(serializer);// value序列化template.setValueSerializer(jackson2JsonRedisSerializer);// value hashmap序列化template.setHashValueSerializer(jackson2JsonRedisSerializer);return template;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer redisSerializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);// 用于解决查询缓存转换异常的问题ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 配置序列化(解决乱码的问题),过期时间600秒RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer( jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;}
}
package com.openlab.redis;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class RedisApplication {public static void main(String[] args) {SpringApplication.run(RedisApplication.class, args);}
}
package com.openlab.redis.test;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;/*** redisTemplate.opsForValue() 获取的是 string类型* * redisTemplate.opsForHash() 获取hash类型* * redisTemplate.opsForSet() 获取set类型* * redisTemplate.opsForList() 获取list类型* * redisTemplate.opsForZSet() 获取zset类型* * redisTemplate.opsForHyperLogLog() 获取hyperLogLog类型* * redisTemplate.opsForGeo() 获取geo类型 */
@SpringBootTest
public class RedisApiTest {@Autowiredprivate RedisTemplate redisTemplate;@Testvoid testRedis() {/*//添加数据redisTemplate.opsForValue().set("hello","world");*///获取数据Object hello = redisTemplate.opsForValue().get("hello");System.out.println(hello);}
}
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。Redis 客户端可以订阅任意数量的频道。下图展示了频道 channel1 ,以及订阅这个频道的三个客户端 —— client1 、client2 和 client3 之间的关系(观察者模式):
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
我们打开一个客户端,然后执行如下命令来订阅一个通道:
在另一个客户端中执行消息发布
发布完成后,在第一个客户端中就可以看到所订阅的频道所传过来的消息。
(1)配置发布订阅
修改 RedisConfig 类,在类中增加如下的配置:
package com.openlab.redis.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.openlab.redis.subscribe.Receiver;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;
import java.util.concurrent.CountDownLatch;/*** redis配置类*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {// 配置 RedisTemplate 模板对象@Beanpublic RedisTemplate redisTemplate(RedisConnectionFactory factory) {RedisTemplate template = new RedisTemplate<>();// 配置序列化对象RedisSerializer serializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);template.setConnectionFactory(factory);// key序列化方式template.setKeySerializer(serializer);// value序列化template.setValueSerializer(jackson2JsonRedisSerializer);// value hashmap序列化template.setHashValueSerializer(jackson2JsonRedisSerializer);return template;}// 配置缓存管理器@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer redisSerializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);// 用于解决查询缓存转换异常的问题ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 配置序列化(解决乱码的问题),过期时间600秒RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer( jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;}/*-------------------- 下面的配置是用于发布订阅,如果不使用这个功能可以删除 ------------------------*/
// 创建连接工厂@BeanRedisMessageListenerContainer container(RedisConnectionFactory factory, MessageListenerAdapter adapter) {RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(factory);// 指定了一个消息的主题,叫 myTopiccontainer.addMessageListener(adapter, new PatternTopic("myTopic"));return container;}// 绑定消息监听者和接收监听的方法@Beanpublic MessageListenerAdapter listenerAdapter(Receiver receiver) {return new MessageListenerAdapter(receiver, "receiveMessage");}// 注册订阅者@Beanpublic Receiver receiver(CountDownLatch latch) {return new Receiver(latch);}// 计数器,用来控制线程@Beanpublic CountDownLatch latch() {return new CountDownLatch(1); // 指定了计数的次数 1}
}
(2)编写消息接收者类
package com.openlab.redis.subscribe;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;import java.util.concurrent.CountDownLatch;/*
* 消息接收者类
* */
@Slf4j
public class Receiver {private CountDownLatch latch;@Autowiredpublic Receiver(CountDownLatch latch) {this.latch = latch;}//用于接收消息的方法public void receiveMessage(String message) {log.info("接收到的消息是:<" + message + ">");latch.countDown();}
}
注意:为了使用日志,我们添加了如下的依赖:
org.projectlombok lombok 1.18.24
(3)编写消息发送者
package com.openlab.redis.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class SenderController {@Autowiredprivate RedisTemplate redisTemplate;@GetMapping("/send")public String send(String msg) {redisTemplate.convertAndSend("myTopic",msg);return "消息发送成功";}
}
(4)运行测试
启动项目,在浏览器中输入 http://localhost:8080/send?msg=hello 后就可以在 IDEA 控制台中看到如下信息:
Redis 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行(相当于隔离起来,加了锁),执行完之后才会释放锁。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis 事务的主要作用就是串联多个命令防止别的命令插队。
从输入 Multi 命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入 Exec 后,Redis 会将之前的命令队列中的命令依次执行。
组队的过程中可以通过 discard 来放弃组队。
(1)组队成功,提交成功的情况:
(2)组队阶段报错,提交失败的情况:
(3)组队成功,提交失败的情况:
(1)问题场景
有一账户余额为 10000 元,现在三个请求,第一个请求想给金额减 8000 元,第二个请求想给金额减 5000 元,第三个请求想给金额减 1000 元。如果没有事务,可能会发生如下情况。
如果没有事务来控制的话,那么账户就会出现透支的问题。解决这个问题有以下两种方式。
(2)悲观锁
悲观锁(Pessimistic Lock)顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库就用到了很多这种锁机制,比如行锁,表锁,读锁,写锁等,都是在做操作之前先上锁。
这种方式能够解决透支问题,但是性能不高。
(3)乐观锁
乐观锁(Optimistic Lock)顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
Redis就是利用这种check-and- set机制实现事务的。
(4)watch
在执行 multi 之前,先执行 watch key1 [key2]
可以监视一个(或多个) key ,如果在事务执行之前这个/些 key被其他命令所改动,那么事务将被打断。
为了演示这个功能,我们先设置一个 balance 的初始值为 10000。然后打开一个客户端,在两个客户端中都去监视 balance,同时也开启事务。
首先设置值:
在客户端 A :
首先在客户端 A 中监听 balance ,然后开记事务操作。同时,在客户端 B 中也监听 balance,然后开启事务。
当客户端 A 提交了事务后,客户端B也提交事务,会发现,客户端B的事务没有成功。
事务是一个整体,里面有一个不成功,都不会执行。
Redis 的事务有以下三个特性:
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。
在电商项目中,经常会有秒杀活动,我们使用 Redis 来模拟秒杀功能。
假设有 10 个商品库存,现有 200 个用户来抢购这 10 个商品。
(1)搭建工程
引入依赖:
org.springframework.boot spring-boot-starter-web 2.7.1 org.springframework.boot spring-boot-starter-data-redis 2.7.1 org.apache.commons commons-pool2 2.11.1 org.projectlombok lombok 1.18.24
配置application.yml
spring:redis:host: 192.168.124.131port: 6379lettuce:pool:enabled: truemax-wait: -1max-active: 8max-idle: 8timeout: 5000
配置RedisTemplate
package com.openlab.redis.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;/*** redis配置类*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {// Lettuce 配置方式@Beanpublic RedisTemplate redisTemplate(LettuceConnectionFactoryfactory) {// key序列化器 RedisSerializer keySerializer = new StringRedisSerializer(); // value序列化器 Jackson2JsonRedisSerializer
启动入口:
package com.openlab.redis;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class RedisApplication {public static void main(String[] args) {SpringApplication.run(RedisApplication.class,args);}
}
秒杀页面:
秒杀页面
外星人笔记本1元秒杀!!!