- 不支持SQL标准
- 不支持ACID。即事务的四大特性原子性、一致性、隔离性、持久性
- 远超SQL的性能
- 对数据高并发的读写(电商里的秒杀场景)
- 海量数据的读写
- 对数据的高扩展性
- 需要事务的支持
- 基于SQL的结构化查询存储,处理复杂的关系。
- Memcache(缺点不支持数据的持久化,数据不能存储到硬盘中)
- Redis (支持数据的持久化、支持多种数据结构list\set\hash\zset)
- mongoDB(文档型数据库、存储的数据类型更加的多样化)
- ps -ef | grep redis //查看redis启动后的进程
- reids-server redis.conf //启动redis服务
- redis-cli //设置可以用客户端终端访问
- auth 123456 //输入密码
- 127.0.0.1:6379> ping //测试验证一下
- keys * :查看当前库中的所有key
- set :设置key-value
- exists key1 :判断key1是否存在——返回1:存在,2:不存在
- type key2 :查看key2所对应值的类型
- del key2 :删除key2
- unlink key2 :删除key2(它会先返回消息说已经删掉了key2,其实还没有删除,真正的删除是在以后慢慢删除,异步删除)
- expire key1 10 :设置过期时间,为key1设置10秒后过期
- ttl key1:查看key1是否已经过期——返回: -1:永不过期,-2:已经过期
- select :切换库(Redis一共有16个库,默认用0号库)
- dbsize :查看当前数据库的key的数量
- flushdb:清空当前库
- flushall :通杀全部库
- set:添加key—value (设置相同key的数据会把前面的覆盖掉)
- get:取值
- append:将指定的value追加到原来值的末尾
- strlen:获取指定值的长度;
- setnx:当库中没有对应的key时,设置key-value才能成功。(以防覆盖原来的);
- incr key:将key中存储的数值加1,如果为空,则新增值1;
- decr key:将key中存储的数值减1;
- incrby/decrby key 步长:给数值加减指定的数。
- mset :同时设置多个key-value(不保证原子性)
- mget :同时获取多个key分别对应的值(不保证原子性)
- msetnx:同时设置多个key-value,但是key不能和之前有重复,如果有一个重复则这次设置的key-value都失败;(体现原子性)
- getrange key 起始位置 结束位置:获取key对应值的
- setrange key 起始位置 value : 用value覆盖掉从起始位置开始的值
- setex key 过期时间 value 设置key-value时设置过期时间;
- getset key1 value:设置新的key1-value;同时输出原来的key1对应的value
- String的内部结构为简单的动态字符串(Simple Dynamic String),是可以修改的字符串(Java的字符串是不可变的),其内部结构类似于Java中的ArrayList,可以伸缩。
- lpush/rpush key value1 value2 value3:从左/右边边放入多个值;
- lrange key1 start stop: 按照索引下标获取元素(从左到右)
如:lrange key1 0 -1: 从左到右取出所有元素,0代表第一个位置,因为是链表List底层是双向链表,所以-1代表左后一个位置。- lpop/rpop key count: 从key对应的链表的左边或者从右边取出count个值; 每取出一个值该链表少一个值;
- rpoplpush key1 key2 :把key1最右边的一个value放到key2最左边去。
- lindex key index :按照索引下标获得元素;
- linsert key before value newvalue: 在旧的值的后面插入一个新的值;
- lrem key N value11:从左边到右边删除N个一样的value;
- lset key index value :将key对应列表中下标为index的值替换为value
- Set数据结构是dict字典,字典就是哈希表实现;
- Java中HashSet就用HashMap实现,只不过所有的value指向同一个对象。
- Redis的set结构也是一样,它内部也使用hash结构,所有的value都指向同一个内部的值。
hash类型对应的数据类型是2种:zipList(压缩列表),hashTable(哈希表)。当field-value长度较短并且个数较少时,使用zipList,否则使用hashTable。
- SUBSCRIBE channel1 chanel2————订阅频道1 和频道2
- publish channel1 nihao ————向频道1发送“nihao”
- publish channel2 nihao————向频道2发送“nihao”
(1)Redis提供了Bitmaps这个数据类型可以实现对位操作:
- setbit key offset value :往一个key对应的数组(以位为单位的数组)中的offset(偏移量)的这个位置存一个值,value值只能为“0”或“1”;
- getbit key offset:取出该offset(偏移量)对应的值
- bitcount key :统计key对应的Bitmaps中为“1”的个数。
- bitcount key start end bit :以bit为单位在start和end中有多少个“1”;
- bitcount key start end byte :以byte为单位在start和end中有多少个“1”;
- bitop and(or/not/xor) destkey key[key…],对多个Bitmaps求,and(交集)、or(并集)、not(非)、xor(异或)操作,并将结果保存在目标Bitmaps(deskey)中。
方案一:用集合Set存储活跃用户的id,每个人的id占64位,那么存储id总共花费:64bit x 5000万=32亿bit
方案二:用Bitmaps记录每一位用户是否活跃,那么花费:1bit x 1亿=1亿bit
结论:很明显当活跃用户多时,方案二很好;但是活跃用户少时,方案一好;
解决像这样的问题有很多种解决方案:
- 方案一:如果数据存储在Mysql中,使用distinct count统计不重复的值;
- 方案二:使用Redis中的hash、set、bitmaps、这些结构都能处理
- 为什么不用这些结构:因为以上数据结构如果对于大的数据集,将要浪费大量空间,不切实际。
- 方案三:使用hyperLogLog来统计
- pfadd key value[values]:把value添加,并不是存储起来,而是用来计算基数
- pfcount key :计算基数;
Geo:Geographic:地理学的
spatial:空间的
简介:
- 生成随机6位数验证码,把这个验证码存在Redis中,然后再把这个数发送个用户;
- 生成的验证码2分钟失效,并且一天内只能生成3次验证码。
- 用户输入收到的验证码,并且输入
- 把用户输入的验证码与Redis中调出的验证码做等价判断。
package com.yiheng.jedis;
import redis.clients.jedis.Jedis;
import java.util.Random;
import java.util.Scanner;
//案例需求:
//输入手机号之后,我们点击一下,能够获取随机6位的验证码。
public class PhoneCode {
public static void main(String[] args) {
//1、用户点击获取验证码后:向Redis保存生成的验证码,并且向用户发送该验证码;
int i = verifyCode("18728089510");
if(i==1){//达到了获取3次的界限
System.out.println("程序结束!");
}else {
//2、用户填写验证码,该验证码再两分钟以内有效
Scanner scanner = new Scanner(System.in);
System.out.println("请输入验证码!!");
String code = scanner.next();
System.out.println(code);
//3、调用验证码校验,
verify("18728089510",code);
}
}
//三、验证码校验:
public static void verify(String phone,String code){
//1.连接redis
Jedis jedis = new Jedis("8.142.89.219", 6379);
jedis.auth("123456");
//2.设置改手机号获取的验证码在Redis中的key
String Verify_code="Code"+phone+":code";
//3.从redis中取该用户已获取到的验证码
String s = jedis.get(Verify_code);
jedis.close();
//4.判断用户输入的验证码:code与从Redis中取出的验证码是否一致;
if (s.equals(code)){
System.out.println("验证成功");
}else {
System.out.println("验证失败");
}
}
//二、让每个手机每天只能生成3次验证码,把生成的验证码保存到redis中,并且返回给用户该验证码
public static int verifyCode(String phone){
//1.连接redis
Jedis jedis = new Jedis("8.142.89.219", 6379);
jedis.auth("123456");
//2.设置改手机号获取的验证码在Redis中的key
String Verify_code="Code"+phone+":code";
//3.设置该手机号获取验证码次数在Redis中的key
String Verify_count="Count"+phone+":count";
//4.取出redis中保存的向该手机发送验证码发送的次数;
String count = jedis.get(Verify_count);
//5.判断获取次数
if(count==null){//第一次发送
jedis.setex(Verify_count,24*60*60,"1");//设置过期时间
}else if(Integer.valueOf(count)<=2){
jedis.incr(Verify_count);
}else if(Integer.valueOf(count)>2){
//回去次数大于等于3次,直接回应给用户,并且关闭资源链接
System.out.println("今日获取已经达到3次,24小时内无法再此发送");
jedis.close();
return 1;
}
//6.把随机生成的验证码保存到Redis中;
String s = String.valueOf(getCode());
jedis.setex(Verify_code,2*60,s);
//7.从redis中获取,24小时内发送给用户验证码的 总次数
String a =jedis.get(Verify_count);
//8.把生成的验证码发送到用户的手机;
System.out.println("今天第"+a+"次获取验证码;"+"验证码为:"+s+",两分钟内有效。");
jedis.close();
return 0;
}
//一、随机生成6位验证码
public static StringBuilder getCode(){
Random random = new Random();
StringBuilder code = new StringBuilder("");
for (int i = 0; i < 6; i++) {
int c = random.nextInt(10);
code=code.append(c);
}
return code;
}
}
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
spring:
redis:
host: 8.142.89.219
port: 6379
database: 0 #数据库索引默认0;
connect-timeout: 1800000 #连接超时时间
lettuce:
pool:
max-active: 20 #最大连接数
max-idle: 5 #连接池最大空闲连接
max-wait: -1 #最大阻塞等待时间
min-idle: 0 #连接池最小空闲连接
password: 123456 #Redis连接密码
package com.yiheng.redis_config;
@EnableCaching //配置缓存
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> 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);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> 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)) //设置数据过期时间600秒
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
@RestController
public class RedisController {
@Autowired
RedisTemplate redisTemplate;
@RequestMapping("/RedisTest1")
public String test1(){
//设置key-String
redisTemplate.opsForValue().set("k1","yiheng");
//取key对应的String
return (String)redisTemplate.opsForValue().get("k1");
}
}
Multi:可以用来开启事务,开启事务后,我们可以输入命令,命令会进入队列准备执行。
Exec:执行操作,命令会依次执行
discard:在组队(进入队列的过程)中,可以使用discard命令放弃组队。
public class TestSecondKill implements Runnable {
public static void main(String[] args) {
TestSecondKill testSecondKill = new TestSecondKill();
for (int i = 0; i < 200; i++) {//生成200个线程,模拟高并发。
new Thread(testSecondKill).start();
}
}
@Override
public void run() {
//随机生成用户id
String userid=String.valueOf(new Random().nextInt(5000));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// boolean result = secKill(userid,"0101");
try {
SecKill_redisByScript.doSecKill(userid,"0101");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class SecKill_redisByScript {
private static final org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;
//主方法用来测试LUA脚本对不对
public static void main(String[] args) {
JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis=jedispool.getResource();
System.out.println(jedis.ping());
Set<HostAndPort> set=new HashSet<HostAndPort>();
// doSecKill("201","sk:0101");
}
//LUA:洛脚本语言:我们把在Redis执行的步骤写为一个脚本,然后一次提交给Redis执行,减少反复连接Redis的次数。提升性能。
static String secKillScript ="local userid=KEYS[1];\r\n" +
"local prodid=KEYS[2];\r\n" +
"local qtkey='sk:'..prodid..\":qt\";\r\n" +
"local usersKey='sk:'..prodid..\":usr\";\r\n" +
"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
"if tonumber(userExists)==1 then \r\n" +
" return 2;\r\n" +
"end\r\n" +
"local num= redis.call(\"get\" ,qtkey);\r\n" +
"if tonumber(num)<=0 then \r\n" +
" return 0;\r\n" +
"else \r\n" +
" redis.call(\"decr\",qtkey);\r\n" +
" redis.call(\"sadd\",usersKey,userid);\r\n" +
"end\r\n" +
"return 1" ;
static String secKillScript2 =
"local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +
" return 1";
public static boolean doSecKill(String uid,String prodid) throws IOException {
JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis=jedispool.getResource();
//String sha1= .secKillScript;
String sha1= jedis.scriptLoad(secKillScript);//加载脚本
Object result= jedis.evalsha(sha1, 2, uid,prodid);
String reString=String.valueOf(result);
if ("0".equals( reString ) ) {
System.err.println("已抢空!!");
}else if("1".equals( reString ) ) {
System.out.println("抢购成功!!!!");
}else if("2".equals( reString ) ) {
System.err.println("该用户已抢过!!");
}else{
System.err.println("抢购异常!!");
}
jedis.close();
return true;
}
}
package com.yiheng.controller;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* Jedis连接池工具类,解决并发问题下的连接偶尔出现的Redis超时问题。
*/
public class JedisPoolUtil {
private static volatile JedisPool jedisPool=null;
private JedisPoolUtil(){
}
public static JedisPool getJedisPoolInstance(){
if(null==jedisPool){
synchronized (JedisPoolUtil.class){
if(null==jedisPool){
JedisPoolConfig poolConfig = new JedisPoolConfig();
//MaxTotal:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted。
//maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
//MaxWaitMillis:表示当borrow(借)一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;
poolConfig.setMaxTotal(200);//最大连接数
poolConfig.setMaxIdle(32);
poolConfig.setMaxWaitMillis(100*1000);//等待时间
poolConfig.setBlockWhenExhausted(true);//连接耗尽是否进行等待
poolConfig.setTestOnBorrow(true);//从连接池获取连接,先测试一下连接是否是连通状态。
jedisPool = new JedisPool(poolConfig, "8.142.89.219", 6379, 60000, "123456");//60000超时时间,超过这个时间就不能提供服务了。
}
}
}
return jedisPool;
}
//释放Jedis资源
public static void release(JedisPool jedisPool, Jedis jedis){
if(null!=jedis){
jedisPool.returnResource(jedis);
}
}
}
Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
是根据保存在磁盘上面的dump.rdb文件,来恢复关闭Redis关之前的状态。
如果在最后一次进行持久化操作中,服务器挂掉了,那么Redis就会终止。那么导致最后一次持久化时的数据丢失。
- 一些写操作的命令
- Redis中保存的数据
AOF的,AOF>RDB优先级。再关闭Redis,再重启Redis,恢复Redis关闭前的状态中的key会根据AOF文件来恢复,而不是根据RDB文件恢复。
1数据不易丢失
- 空间开销大:因为AOF文件保存了“写”命令和Redis数据库中的数据,所以空间开销比RDB开销大,RDB过程产生的dump.rdb文件之保存了数据,没有命令
- 会影响系统吞吐量,AOF在同步操作命令时,会有额外的性能开销。
主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
- 读写分离,性能提升
- 容灾快速回复:当你如果有一台从服务器挂了,其他服务器继续工作。
1、创建/myredis目录
2、把redis.conf配置文件复制3份到myredis文件夹(1主2从)
3、再把redis.conf里面的配置修改:
——①修改pidfile 路径为:pidfile /www/server/redis/redis_6379.pid
——②修改 port 端口号为:6379
——③修改 dbfilename为dump6379.rdb
——④方式一配主从服务器:修该 replicaof IP地址 端口号 :用来表明该服务器作为谁的从服务器
——⑤方式一配主从服务器:修改 masterauth 密码 :主机密码。
——④如果开启了AOF持久化,则需要改动aof文件的路径名。
4、启动3个Redis服务。redis-server redis6379.conf
——注意:安装redis时一定要再Redis安装目录下用make install,不然会报错:redis-server: command not found
5、查看进程: ps -ef | grep redis
6、使用redis-cli -p 端口号 :开启三个客户端连接分别连接3个redis服务。
7、①输入密码②使用info replication查看主机信息
8、方式二配主从服务器:使用 slaveof ip port 把该机器变成这个IP的这个端口的从机。
主服务器写操作:set k1 v1
主服务器读操作:get k1——v1
从服务器读操作:get k1—— v1
从服务器写操作:报错:你只能进行读操作
结论:主服务器既能写也能读,而从服务器只能读
- 方法一:服务器的redis配置文件修改 replicaof IP地址 端口号 指定父从服务器
- 方法二:在服务器运行中使用命令: slaveof ip port 指定自己的父从服务器
- 优点:再从服务器的数量比较多时,有利于给主服务器分摊压力
- 缺点:当如果有中间的一台从服务器挂掉了,那么该从服务器的子从服务器,无法进行同步主服务器的数据了(中间的服务器挂了)
命令:slaveof no one
- 容量不够,redis如何进行扩容?
- 并发写操作, redis如何分摊?
- 另外,主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。
解决方案1:redis3之前使用代理主机。缺点:耗用的服务器数量太多了。在实际的开发中,一个redis服务器肯定对应一台Linux系统。
解决方案二:使用无中心化集群配置
- 创建/myredis目录
- 把redis.conf文件名改为redis6379.conf
- 再把redis6379.conf里面的配置修改:
——①修改pidfile 路径为:pidfile /www/server/redis/redis_6379.pid
——②修改 port 端口号为:6379
——③修改 dbfilename为dump6379.rdb
——④关闭aof持久化
——⑤cluster-enabled yes 打开集群模式
——⑥cluster-config-file nodes-6379.conf 设定节点配置文件名
——⑦cluster-node-timeout 15000 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换。- 把redis6379.conf文件复制五份。
- 效果为:3主3从
- 启动6个redis服务
重要:再阿里云和宝塔把12个端口放行(Redis服务端口+每个服务对应的集群总线端口)
进入www/server/redis/src目录:
配置集群的命令:redis-cli --cluster create --cluster-replicas 1 8.142.89.219:6379 8.142.89.219:6380 8.142.89.219:6381 8.142.89.219:6389 8.142.89.219:6390 8.142.89.219:6391
—— ①–cluster-replicas 1 ——表示1台主机1个从服务器。
——②写实际IP地址和端口号。不能用127.0.0.1。
- set k1 v1
- mset k1{user} v1 k2{user} v2 k3{user} v3:一次插入3个key-value,则是根据user来计算槽的位置的。原理是把k1 k2 k3 归为一个组。
- cluster keyslot k1:查询k1属于哪个槽。
- cluster countkeysinslot 5474 :查看5474这个槽有多少个键。必须要在对应的IP端口查看才能效果,5474属于8080端口管的。
/**
* jedis连接集群测试.txt
*/
public class JedisClusterTest {
public static void main(String[] args) {
//1、可以创建一个集合,但没必要
// HashSet hostAndPorts = new HashSet<>();
// hostAndPorts.add(new HostAndPort("8.142.89.219",6379));
// hostAndPorts.add(new HostAndPort("8.142.89.219",6380));
//1、可以直接创建hostAndPort:因为搭建的是集群,集群中任意一台服务器都是入口。
HostAndPort hostAndPort = new HostAndPort("8.142.89.219", 6380);
//2、
JedisCluster jedisCluster = new JedisCluster(hostAndPort);
jedisCluster.mset("k1{user}","v1","k2{user}","v2");//集群存入一次存多个值,则需要分组。这里k1,k2都放在user组
System.out.println(jedisCluster.get("k2{user}"));
}
}
/**
* 分布式锁Java代码实现.txt
*/
@RestController
public class Test {
@Autowired
RedisTemplate redisTemplate;
@GetMapping("/test")
public void testLock(){
//1 获取锁,要要给锁加过期时间;如果业务逻辑出现问题锁才会自动释放:这里为11秒过期
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",11, TimeUnit.SECONDS);
//2 获取锁成功、查询num值
if(lock){
Object value = redisTemplate.opsForValue().get("num");//redis中set num 0;
//2.1判断num为空return
if(StringUtils.isEmpty(value)){
return;
}
//2.2有值就转换为int
int num = Integer.parseInt(value + "");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num",++num);
//2.4释放锁,手动释放
redisTemplate.delete("lock");
}else {
//3.获取锁失败就隔0.1秒在获取
try {
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
改进:给每个锁加了uuid
/**
* 分布式锁Java代码实现.txt
*/
@RestController
public class Test {
@Autowired
RedisTemplate redisTemplate;
@GetMapping("/test")
public void testLock(){
//为每个拿到锁的线程都生成一个uuid.
String uuid = UUID.randomUUID().toString();
//1 获取锁,要要给锁加过期时间;如果业务逻辑出现问题锁才会自动释放:这里为11秒过期
//key:lock——————value:uuid+分界线+实际的value。
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid+"uuid与value的分界线"+111,11, TimeUnit.SECONDS);
//2 获取锁成功、查询num值
if(lock){
Object value = redisTemplate.opsForValue().get("num");//redis中set num 0;
//2.1判断num为空return
if(StringUtils.isEmpty(value)){
return;
}
//2.2有值就转换为int
int num = Integer.parseInt(value + "");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num",++num);
//2.4释放锁,手动释放——判断_当前线程持有的锁的uuid_是否与_Redis缓存中该key的锁的uuid_相等
String lock1 = (String) redisTemplate.opsForValue().get("lock");
String[] s = lock1.split("uuid与value的分界线");//把从Redis中取出的value以“分界线”,分解 成uuid和实际value值
String redis_uuid1=s[0];
System.out.println("redis_uuid:"+s[0]);
System.out.println("value:"+s[1]);
if(uuid.equals(redis_uuid1)){
redisTemplate.delete("lock");
}else {
return;
}
}else {
//3.获取锁失败就隔0.1秒在获取
try {
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@RestController
public class TestLockLua {
@Autowired
RedisTemplate redisTemplate;
@GetMapping("/testLockLua")
public void testLockLua() {
//为每个拿到锁的线程都生成一个uuid.
String uuid = UUID.randomUUID().toString();
//定义一个锁:lua脚本可以使用同意把锁来实现删除
String skuId = "25"; // 访问skuId 为25号的商品 100008348542
String locKey = "lock:" + skuId; // 锁住的是每个商品的数据
//1 获取锁,要要给锁加过期时间;如果业务逻辑出现问题锁才会自动释放:这里为11秒过期
//key:lock——————value:uuid+分界线+实际的value。
String value=uuid + "uuid与value的分界线" + 111;
Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, value, 11, TimeUnit.SECONDS);
//2 获取锁成功、查询num值
if (lock) {
Object num = redisTemplate.opsForValue().get("num");
//2.1判断num为空return
if (StringUtils.isEmpty(num)) {
return;
}
//2.2有值就转换为int
int num1 = Integer.parseInt(num + "");
//2.3把redis的num1加1
redisTemplate.opsForValue().set("num", ++num1);
//2.4释放锁,手动释放——判断_当前线程持有的锁的uuid_是否与_Redis缓存中该key的锁的uuid_相等
String lock1 = (String) redisTemplate.opsForValue().get(locKey);
//使用lua脚本
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//使用redis执行lua
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
//设置一下返回值的类型 为 Long
//因为删除判断时,返回的0时String类型,需要封装成Long来判断;
redisScript.setResultType(Long.class);
redisTemplate.execute(redisScript,Arrays.asList(locKey),value);
} else {
//3.获取锁失败就隔0.1秒在获取
try {
Thread.sleep(100);
testLockLua();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Redis6终于支撑多线程了,告别单线程了吗?
redis6多路IO默认不开启,配置文件修改,redis7好像默认启用了。
io-threads-do-reads yes
io-threads 4
- 自动化路由:每个查询被自动路由到集群的正确节点。
- 多线程,每个线程都有自己与集群的连接
- 顺序性,只执行命令时时串行的
- 把2、3点总结为:单线程+多路IO复用技术