美文网首页
Redis(二) - Jedis

Redis(二) - Jedis

作者: YMeng_Zhang | 来源:发表于2021-11-04 16:58 被阅读0次

    Jedis

    Java 和 Redis 打交道的 API 客户端。

    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>commons-pool</groupId>
            <artifactId>commons-pool</artifactId>
            <version>1.6</version>
        </dependency>
    </dependencies>
    

    连接 Redis

    public class Test1 {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("192.168.186.128",6379);
            String pong = jedis.ping();
            System.out.println("pong = " + pong);
        }
    }
    

    常用 API

    package com.zm;
    import redis.clients.jedis.Jedis;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Set;
    
    public class testAPI {
    
        private void testString() {
            Jedis jedis = new Jedis("192.168.186.128", 6379);
    
            jedis.set("k1", "v1");
            jedis.set("k2", "v2");
            jedis.set("k3", "v3");
    
            Set<String> set = jedis.keys("*");
            Iterator<String> iterator = set.iterator();
            for (set.iterator(); iterator.hasNext(); ) {
                String k = iterator.next();
                System.out.println(k + " -> " + jedis.get(k));
            }
    
            // 查看 k2 是否存在
            Boolean k2Exists = jedis.exists("k2");
            System.out.println("k2Exists = " + k2Exists);
            // 查看 k1 的过期时间
            System.out.println(jedis.ttl("k1"));
    
            jedis.mset("k4", "v4", "k5", "v5");
            System.out.println(jedis.mget("k1", "k2", "k3", "k4", "k5"));
        }
    
        private void testList() {
            Jedis jedis = new Jedis("192.168.186.128", 6379);
    
            jedis.lpush("list01", "l1", "l2", "l3", "l4", "l5");
            List<String> list01 = jedis.lrange("list01", 0, -1);
            for (String s : list01) {
                System.out.println(s);
            }
        }
    
        private void testSet() {
            Jedis jedis = new Jedis("192.168.186.128", 6379);
    
            jedis.sadd("order", "jd001");
            jedis.sadd("order", "jd002");
            jedis.sadd("order", "jd003");
            Set<String> order = jedis.smembers("order");
            for (String s : order) {
                System.out.println(s);
            }
    
            jedis.srem("order", "jd002");
    
            System.out.println(jedis.smembers("order").size());
        }
    
        private void testHash() {
            Jedis jedis = new Jedis("192.168.186.128", 6379);
    
            jedis.hset("user1", "username", "renda");
            System.out.println(jedis.hget("user1", "username"));
    
            HashMap<String, String> map = new HashMap<String, String>();
            map.put("username", "Blair");
            map.put("gender", "female");
            map.put("address", "wuxi");
            map.put("phone", "1523641256");
    
            jedis.hmset("user2", map);
    
            List<String> list = jedis.hmget("user2", "username", "phone");
            for (String s : list) {
                System.out.println(s);
            }
        }
    
        private void testZset() {
            Jedis jedis = new Jedis("192.168.186.128", 6379);
    
            jedis.zadd("zset01", 60d, "zs1");
            jedis.zadd("zset01", 70d, "zs2");
            jedis.zadd("zset01", 80d, "zs3");
            jedis.zadd("zset01", 90d, "zs4");
    
            Set<String> zset01 = jedis.zrange("zset01", 0, -1);
            for (String s : zset01) {
                System.out.println(s);
            }
        }
    
        public static void main(String[] args) {
            testAPI testApi = new testAPI();
            test2Api.testString();
            test2Api.testList();
            test2Api.testSet();
            test2Api.testHash();
            test2Api.testZset();
        }
    }
    

    事务

    初始化余额和支出

    set balance 100
    set expense 0
    
    public class TestTransaction {
    
        public static void main(String[] args) throws InterruptedException {
            Jedis jedis = new Jedis("192.168.186.128",6379);
    
            int balance = Integer.parseInt(jedis.get("balance"));
            int expense = 10;
    
            // 监控余额
            jedis.watch("balance");
            // 模拟网络延迟
            Thread.sleep(10000);
    
            if (balance < expense) {
                // 解除监控
                jedis.unwatch();
                System.out.println("余额不足");
            } else {
                // 开启事务
                Transaction transaction = jedis.multi();
                // 余额减少
                transaction.decrBy("balance", expense);
                // 累计消费增加
                transaction.incrBy("expense", expense);
                // 执行事务
                transaction.exec();
                System.out.println("余额:" + jedis.get("balance"));
                System.out.println("累计支出:" + jedis.get("expense"));
            }
        }
    
    }
    

    模拟网络延迟:10 秒内,使用 linux 窗口修改 balance 为 5 模拟另一个线程的操作,此时因为 balance 被监控到改动,事务将被打断不会提交执行;输出的余额和累计支出将没有变化。

    JedisPool

    Redis 的连接池技术详情:https://help.aliyun.com/document_detail/98726.html

    <dependency>
        <groupId>commons-pool</groupId>
        <artifactId>commons-pool</artifactId>
        <version>1.6</version>
    </dependency>
    

    使用单例模式进行优化:

    public class JedisPoolUtil {
    
        private JedisPoolUtil () {
        }
    
        private volatile static JedisPool jedisPool = null;
        private volatile static Jedis jedis = null;
    
        /**
         * 返回一个连接池
         */
        private static JedisPool getInstance() {
            // 双层检测锁(企业中用的非常频繁)
            if (jedisPool == null) {
                synchronized (JedisPoolUtil.class) {
                    if (jedisPool == null) {
                        JedisPoolConfig config = new JedisPoolConfig();
                        config.setMaxTotal(1000);
                        config.setMaxIdle(30);
                        config.setMaxWaitMillis(60*1000);
                        config.setTestOnBorrow(true);
                        jedisPool = new JedisPool(config, "192.168.186.128", 6379);
                    }
                }
            }
            return jedisPool;
        }
    
        /**
         * 返回 jedis 对象
         */
        public static Jedis getJedis() {
            if (jedis == null) {
                jedis = getInstance().getResource();
            }
            return jedis;
        }
    
    }
    

    测试类:

    public class TestJedisPool {
    
        public static void main(String[] args) {
            Jedis jedis1 = JedisPoolUtil.getJedis();
            Jedis jedis2 = JedisPoolUtil.getJedis();
    
            System.out.println(jedis1 == jedis2);
        }
    
    }
    

    高并发下的分布式锁

    经典案例:秒杀,抢购优惠券等。

    使用 Linux 窗口的 Redis Client 执行 set phone 10 设置测试案例的商品。

    搭建工程并测试单线程

    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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.zm</groupId>
        <artifactId>high-concurrency-redis</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    
        <!-- 指定编码及版本 -->
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
            <java.version>1.11</java.version>
            <maven.compiler.source>11</maven.compiler.source>
            <maven.compiler.target>11</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.2.7.RELEASE</version>
            </dependency>
            <!-- 实现分布式锁的工具类 -->
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.6.1</version>
            </dependency>
            <!-- spring 操作 redis 的工具类 -->
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-redis</artifactId>
                <version>2.3.2.RELEASE</version>
            </dependency>
            <!-- redis 客户端 -->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>3.1.0</version>
            </dependency>
            <!-- json 解析工具 -->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.9.8</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <configuration>
                        <port>8001</port>
                        <path>/</path>
                    </configuration>
                    <executions>
                        <execution>
                            <!-- 打包完成后,运行服务 -->
                            <phase>package</phase>
                            <goals>
                                <goal>run</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    src\main\webapp\WEB-INF\web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
             id="WebApp_ID" version="3.1">
    
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:spring/spring.xml</param-value>
            </init-param>
        </servlet>
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    

    src\main\resources\spring\spring.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:component-scan base-package="com.zm.controller"/>
    
        <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
            <property name="hostName" value="192.168.186.128"/>
            <property name="port" value="6379"/>
        </bean>
        <!-- spring 为连接 redis,提供的一个模版工具类 -->
        <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
            <property name="connectionFactory" ref="connectionFactory"/>
        </bean>
    
    </beans>
    

    com.zm.controller.TestConcurrency

    @Controller
    public class TestConcurrency {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        /**
         * 只能解决一个 tomcat 的并发问题:
         * synchronized 锁只解决了一个进程下的线程并发;
         * 如果分布式环境,多个进程并发,这种方案就失效了。
         */
        @RequestMapping("purchase")
        @ResponseBody
        public synchronized String purchase() {
            // 1.从 redis 中获取手机的库存数量
            int phoneCount = Integer.parseInt(stringRedisTemplate.opsForValue().get("phone"));
            // 2.判断手机的数量是否够秒杀
            if (phoneCount > 0) {
                phoneCount--;
                // 库存减少后,再将库存的值保存回 redis
                stringRedisTemplate.opsForValue().set("phone", phoneCount + "");
                System.out.println("库存减一,剩余:" + phoneCount);
            } else {
                System.out.println("库存不足");
            }
            return "over";
        }
    }
    

    高并发测试

    1. 启动两次工程,端口号分别 8001 和 8002。

    2. 使用 nginx 做负载均衡:

    # 配置 Redis 多进程测试
    upstream zm{
        server 192.168.1.116:8001;
        server 192.168.1.116:8002;
    }
    
    server {
        listen       80;
        server_name  www.redistest.com;
        location / {
            proxy_pass http://zm;
            index index.html index.htm;
        }
    }
    

    重新启动 Nginx:

    /usr/local/nginx/sbin/nginx -s stop
    /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
    

    使用 SwitchHosts 编辑本地 host 地址:

    # Redis
    192.168.186.128 www.redistest.com
    

    使用 Linux 窗口的 Redis Client 执行 set phone 20 设置测试案例的商品为 20 个。

    1. 使用 JMeter 模拟 1 秒内发出 100 个 http 请求,会发现同一个商品会被两台服务器同时抢购。

    实现 Redis 的分布式锁的思路

    1. 因为 redis 是单线程的,所以命令也就具备原子性,使用 setnx (判断如果不存在才执行 set)命令实现锁,保存 key / value。如果 key 不存在,则执行 set key value 给当前线程加锁,执行完成后,删除 key 表示释放锁;如果 key 已存在,阻塞线程执行,表示有锁。

    2. 如果加锁成功,在执行业务代码的过程中出现异常,导致没有删除 key(释放锁失败),那么就会造成死锁(后面的所有线程都无法执行)。为了解决这个问题,可以设置过期时间,例如 10 秒后,Redis 自动删除。

    3. 高并发下,由于时间段等因素导致服务器压力过大或过小,每个线程执行的时间不同:第一个线程,执行需要 13 秒,执行到第 10 秒时,redis 的 key 自动过期了(释放锁);第二个线程,执行需要 7 秒,加锁,执行第 3 秒(锁被释放了,为什么,是因为被第一个线程的 finally 主动 deleteKey 释放掉了)。。。。连锁反应,当前线程刚加的锁,就被其他线程释放掉了,周而复始,导致锁会永久失效。

    4. 给每个线程加上唯一的标识 UUID 随机生成,释放的时候判断是否是当前的标识即可。

    5. 另外,还需要考虑过期时间如果设定。如果 10 秒太短不够用怎么办?设置 60 秒,太长又浪费时间。可以开启一个定时器线程,当过期时间小于总过期时间的 1/3 时,增长总过期时间。

    Redisson

    Redis 是最流行的 NoSQL 数据库解决方案之一,而 Java 是最流行的编程语言之一。

    虽然两者看起来很自然地在一起,但是 Redis 其实并没有对 Java 提供原生支持。

    相反,作为 Java 开发人员,想在程序中集成 Redis,必须使用 Redis 的第三方库。

    而 Redisson 就是用于在 Java 程序中操作 Redis 的库,可以在程序中轻松地使用 Redis。

    Redisson 在 java.util 中常用接口的基础上,提供了一系列具有分布式特性的工具类。

    @Controller
    public class TestConcurrency {
    
        @Autowired
        private Redisson redisson;
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Bean
        public Redisson redisson() {
            Config config = new Config();
            // 使用单个 redis 服务器
            config.useSingleServer().setAddress("redis://192.168.186.128:6379").setDatabase(0);
            // 如果使用集群 redis:
            // config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://192.168.186.128:6379","redis://192.168.186.129:6379","redis://192.168.186.130:6379");
            return (Redisson) Redisson.create(config);
        }
    
        @RequestMapping("purchase")
        @ResponseBody
        public synchronized String purchase() {
            // 定义商品 id,写死
            String productKey = "HUAWEI-P40";
            // 通过 redisson 获取锁(底层源码就是集成了 setnx,过期时间等操作)
            RLock rLock = redisson.getLock(productKey);
            // 上锁(过期时间为 30 秒)
            rLock.lock(30, TimeUnit.SECONDS);
    
            try {
                // 1.从 redis 中获取手机的库存数量
                int phoneCount = Integer.parseInt(stringRedisTemplate.opsForValue().get("phone"));
                // 2.判断手机的数量是否够秒杀
                if (phoneCount > 0) {
                    phoneCount--;
                    // 库存减少后,再将库存的值保存回 redis
                    stringRedisTemplate.opsForValue().set("phone", phoneCount + "");
                    System.out.println("库存减一,剩余:" + phoneCount);
                } else {
                    System.out.println("库存不足");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 释放锁
                rLock.unlock();
            }
            return "over";
        }
    
    }
    

    实现分布式锁的方案有很多,比如 ZooKeeper 的分布式锁特点就是高可靠性,Redis 的分布式锁的特点就是高性能。

    目前分布式锁应用最多的仍然是 Redis。

    相关文章

      网友评论

          本文标题:Redis(二) - Jedis

          本文链接:https://www.haomeiwen.com/subject/ffekzltx.html