Redis6入门学习(三)--Redis_Jedis、事务、LUA脚本


title: Redis6入门学习(三)

Redis_Jedis_测试

Jedis所需要的jar包

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

连接Redis注意事项

禁用Linux的防火墙:Linux(CentOS7)里执行命令

systemctl stop/disable firewalld.service 

Redis.conf中

#bind 127.0.0.1
protected-mode no

Jedis常用操作

测试程序

public class ConnectRedis {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);
        String pong = jedis.ping();
        System.out.println("连接成功" + pong);
        //测试相关数据类型
        //Key
        jedis.set("k1","v1");
        jedis.set("k2","v2");
        jedis.set("k3","v3");
        Set<String> keys = jedis.keys("*");
        System.out.println(keys.size());
        for (String key : keys) {
            System.out.println(key);
        }
        System.out.println("Key API exists " + jedis.exists("k1"));
        System.out.println("Key API ttl " + jedis.ttl("k1"));
        System.out.println("Key API get " +jedis.get("k1"));

        //String
        jedis.mset("str1","v1","str2","v2","str3","v3");
        System.out.println("String API >>mset >>> mget " + jedis.mget("str1","str2","str3"));

        //List
        jedis.lpush("mylist","l1","l2","l3");
        List<String> mylist = jedis.lrange("mylist", 0, -1);
        for (String s : mylist) {
            System.out.println("List API lpush>>lrange: " + s);
        }


        //set
        jedis.sadd("orders","order01","order02","order3");
        Set<String> orders = jedis.smembers("orders");
        for (String order : orders) {
            System.out.println("Set API>>sadd>>smembers " + order);
        }
        jedis.srem("orders","order02");


        //hash
        jedis.hset("hash","userName","lisi");
        System.out.println("hash API:hset>>hget :" + jedis.hget("hash","userName"));
        HashMap<String, String> map = new HashMap<>();
        map.put("telphone","15759029165");
        map.put("address","longyan");
        map.put("email","1575018859@qq.com");
        jedis.hmset("hash2",map);
        List<String> result = jedis.hmget("hash2", "telphone", "email");
        for (String s : result) {
            System.out.println("hash " + s);
        }

        //zset
        jedis.zadd("zset01",100d,"z3");
        jedis.zadd("zset01",90d,"l4");
        jedis.zadd("zset01",80d,"w5");
        jedis.zadd("zset01",70d,"z5");
        Set<String> zset01 = jedis.zrange("zset01", 0, -1);
        for (String s : zset01) {
            System.out.println("zset>> " + s);
        }
        jedis.close();
    }
}

Redis-Jedis-实例

手机验证码

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

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

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

public class PhoneCode {
    public static void main(String[] args) {
        verifyCode("15759029165");
        //getRedisCode("15759029165","438209");
    }

    //1 生成手机验证码
    public static String getCode() {
        Random random = new Random();
        String code = "";
        for (int i = 0; i < 6; i++) {
            int ran = random.nextInt(10);
            code += ran;
        }
        return code;
    }

    // 2 每个手机每天只能发送3次,验证码放到redis中,设置过期时间
    public static void verifyCode(String phone) {
        Jedis jedis = new Jedis("121.199.76.44", 6379);
        //拼接Key
        //手机发送次数key
        String countKey = "VerifyCode" + phone + ":count";
        //验证码key
        String codeKey = "VerifyCode" + phone + ":code";

        String count = jedis.get(countKey);
        if (count == null) {
            //没有发送次数,第一次发送
            //设置发送次数是1
            jedis.setex(countKey, 24 * 60 * 60, "1");
        } else if (Integer.parseInt(count) <= 2) {
            jedis.incr(countKey);
        }else if (Integer.parseInt(count)>2){
            System.out.println("今天发送次数已经超过3次");
            jedis.close();
        }

        //发送验证码放到redis里面
        String vcode = getCode();
        jedis.setex(codeKey,60,vcode);
        jedis.close();
    }

    //3 验证码校验
    public static void getRedisCode(String phone,String code){
        Jedis jedis = new Jedis("121.199.76.44",6379);
        String codeKey = "VerifyCode" + phone + ":code";
        String redisCode = jedis.get(codeKey);
        if (redisCode.equals(code)){
            System.out.println("成功");
        }else {
            System.out.println("失败");
        }

        jedis.close();
    }
}

Redis与SpringBoot整合

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.luxiaobai</groupId>
    <artifactId>spring-redis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-redis</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- spring2.X集成redis所需common-pool2-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

appplication.properties

#Redis服务器地址
spring.redis.host=192.168.0.101
spring.redis.port=6379

#Redis数据库索引(默认为0)
spring.redis.database=0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没有限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.min-idle=0

配置类

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @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))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

controller

@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping
    public String testRedist(){
        //设置值到redis
        redisTemplate.opsForValue().set("name","lucy");
        //从redis取值
        String name = (String) redisTemplate.opsForValue().get("name");
        return name;
    }
}

Redis事务----锁机制-秒杀

Redis的事务定义

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

Redis事务的主要作用就是串联多个命令防止别的命令插队。

Multi、Exec、discard

从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。

组队的过程中可以通过discard来放弃组队。

redistran

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
组队成功,提交成功

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set m1 v1
QUEUED
127.0.0.1:6379(TX)> set m2
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> set m3 v3
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
组队阶段报错,提交失败

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set m1 v1
QUEUED
127.0.0.1:6379(TX)> incr m1
QUEUED
127.0.0.1:6379(TX)> set m2 v2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
组队成功,提交有成功有失败情况

事务的错误处理

组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FSQOyFlA-1633918917122)(/Users/lushengyang/Desktop/LSY/StudeyNotes/redis/Redis3.assets/redistran1.png)]

如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

redistran2

事务冲突问题

实例

一个请求想给金额减8000

一个请求想给金额减5000

一个请求想给金额减1000

redistan3

悲观锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vWpT67rC-1633918917127)(http://qiliu.luxiaobai.cn/img/redistran4.png)]

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

乐观锁

WATCH key[key…]

在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby balance 10
QUEUED
127.0.0.1:6379(TX)> incrby debt 10
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) -10
2) (integer) 10

unwatch

取消 WATCH 命令对所有 key 的监视。

如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。

Redis事务三特性

  • 单独的隔离操作

    • 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 没有隔离级别的概念

    • n 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
  • 不保证原子性

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

Redis-事务-秒杀案例

解决计数器和人员记录的事务操作

redistran5

Redis事务–秒杀并发模拟

工具ab模拟测试
#ubuntu
sudo apt-get install apache2-utils 

#CentOS
sudo yum -y install httpd-tools
#联网
yum install httpd-tools

vim postfile 模拟表单提交参数,以&符号结尾;存放当前目录。

prodid=0101&
ab -n 2000 -c 200 -k -p ~/postfile -T application/x-www-form-urlencoded http://127.0.0.1:8081/Seckill/doseckill

超卖问题

redischao1

利用乐观锁淘汰用户,解决超卖问题

rediscaho2

连接超时,通过连接池解决

节省每次连接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();
                    poolConfig.setMaxTotal(200);
                    poolConfig.setMaxTotal(32);
                    poolConfig.setMaxWaitMillis(100 * 1000);
                    poolConfig.setTestOnBorrow(true);//ping PONG

                    jedisPool = new JedisPool(poolConfig, "121.199.76.44", 6379, 6000);
                }
            }
        }
        return jedisPool;
    }

    public static void release(JedisPool jedisPool, Jedis jedis) {
        if (null != jedis) {
            jedisPool.returnResource(jedis);
        }
    }
}

连接池参数

  • MaxTotal:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted。
  • maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
  • MaxWaitMillis:表示当borrow一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;
  • testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;

库存遗留问题

redisyiliu

LUA脚本

LUA

Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。

这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂。

https://www.w3cschool.cn/lua/

ubuntu 安装

#下载编译包
curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz
tar zxf lua-5.3.0.tar.gz
cd lua-5.3.0
make linux
make install

lua1

错误原因就是缺少依赖包libreadline-dev

centos: yum install readline-devel
Ubuntu: sudo apt-get install libreadline-dev.

LUA脚本在Redis中的优势

将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。利用lua脚本淘汰用户,解决超卖问题。

redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题

lua2

local userid=KEYS[1]; 
local prodid=KEYS[2];
local qtkey="sk:"..prodid..":qt";
local usersKey="sk:"..prodid..":usr'; 
local userExists=redis.call("sismember",usersKey,userid);
if tonumber(userExists)==1 then 
  return 2;
end
local num= redis.call("get" ,qtkey);
if tonumber(num)<=0 then 
  return 0; 
else 
  redis.call("decr",qtkey);
  redis.call("sadd",usersKey,userid);
end
return 1;
public class SecKill_redisByScript {
    private static final Logger logger = LoggerFactory.getLogger(SecKill_redisByScript.class);

    public static void main(String[] args) throws IOException {
        JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedisPool.getResource();
        System.out.println(jedis.ping());
        Set<HostAndPort> set = new HashSet<HostAndPort>();

        //doSecKill("201","sk:0101");
    }

    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 = jedis.scriptLoad(secKillScript);
        Object result = jedis.evalsha(sha1, 2, uid, prodid);
        String reString = String.valueOf(result);
        if ("0".equals(reString)) {
            System.out.println("已抢空!!");
        } else if ("1".equals(reString)) {
            System.out.println("抢购成功!!");
        } else if ("2".equals(reString)) {
            System.out.println("该用户已抢过!!");
        } else {
            System.out.println("抢购异常!!");
        }
        jedis.close();
        return true;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

路小白&

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值