栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【Java从0到架构师】Redis 应用 - Jedis 基本使用、使用缓存的用户登陆、缓存常见问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【Java从0到架构师】Redis 应用 - Jedis 基本使用、使用缓存的用户登陆、缓存常见问题

Redis 原理与实战
  • Jedis 基本使用
    • Jedis 连接 Redis 服务器
    • Jedis 使用 pipeline
    • Jedis 发布订阅
    • Jedis 使用 bitmap
    • Jedis 使用 HyperLogLog
    • Jedis 使用 GEO
    • Jedis 使用哨兵
    • Jedis 使用 Redis 连接池
  • 实战应用 - 使用 Redis 缓存的用户登陆
  • 缓存常见问题
    • 缓存穿透
    • 雪崩
    • 热点 key

Java 从 0 到架构师目录:【Java从0到架构师】学习记录

Jedis 基本使用

参考:Redis 笔记之 Java 操作 Redis(Jedis)

引入依赖:


	redis.clients
	jedis
	3.1.0

Jedis 连接 Redis 服务器
public class BasicDemo {
    private Jedis jedis = null; //客户端连接对象

    @Before
    public void init() throws Exception {
        jedis = new Jedis("192.168.52.128", 6379);
    }

    @After
    public void close() throws Exception {
        if (jedis != null) {
            jedis.close();
        }
    }

    @Test
    public void test1() throws Exception {
        String val = jedis.set("hello", "java");
        System.out.println("val = " + val); // val = OK
    }

}
Jedis 使用 pipeline
public class PipeLineDemo {

    private Jedis jedis = null; //客户端连接对象

    @Before
    public void init() throws Exception {
        jedis = new Jedis("192.168.52.128", 6379);
    }

    @After
    public void close() throws Exception {
        if (jedis != null) {
            jedis.close();
        }
    }

    // 模拟发送写入10w个数据
    @Test
    public void testBasic() throws Exception {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            jedis.set("base:key" + i, "value");
        }
        long end = System.currentTimeMillis();
        // 10w->51s
        System.out.println("耗时时长: " + (end - start));
    }

    // 模拟发送写入10w个数据 pipeline
    @Test
    public void testPipeline() throws Exception {
        long start = System.currentTimeMillis();
        // 创建一个管道
        Pipeline pipelined = jedis.pipelined();
        for (int i = 0; i < 100000; i++) {
            // 把需要执行的命令放管道中
            pipelined.set("pipe2:key" + i, "value");
        }
        // 同步执行命令
        pipelined.sync();
        long end = System.currentTimeMillis();
        // 10w -> 0.9s
        // 100w -> 6.2s
        System.out.println("耗时时长: " + (end - start));
    }
}
Jedis 发布订阅

PublisherDemo.java:

public class PublisherDemo {

    // 客户端连接对象
    private Jedis jedis = null;

    @Before
    public void init() throws Exception {
        jedis = new Jedis("192.168.52.128", 6379);
    }

    @After
    public void close() throws Exception {
        if (jedis != null) {
            jedis.close();
        }
    }

    // 生成消息
    @Test
    public void produce() throws Exception {
        for (int i = 0; i < 100; i++) {
            jedis.publish("9527", "hello" + i);
            TimeUnit.MICROSECONDS.sleep(10);
        }
    }
}

Subscriber.java:

public class Subscriber {

    // 客户端连接对象
    private Jedis jedis = null;

    @Before
    public void init() throws Exception {
        jedis = new Jedis("192.168.52.128", 6379);
    }

    @After
    public void close() throws Exception {
        if (jedis != null) {
            jedis.close();
        }
    }

    // 订阅channel 消费消息
    @Test
    public void produce() throws Exception {
        JedisPubSub jedisPubSub = new JedisPubSub() {
            @Override
            public void onMessage(String channel, String message) {
                System.out.println("channel = " + channel);//哪个频道
                System.out.println("message = " + message);// 消息内容

            }
        };
        jedis.subscribe(jedisPubSub, "9527");
    }
}
Jedis 使用 bitmap
// 记录每天用户的登录数量
public class UserLoginDemo {

    private Jedis jedis = null; //客户端连接对象

    @Before
    public void init() throws Exception {
        jedis = new Jedis("192.168.52.128", 6379);
    }

    @After
    public void close() throws Exception {
        if (jedis != null) {
            jedis.close();
        }
    }

    @Test
    public void initData() throws Exception {
        // 初始化用户登录数据
        jedis.setbit("user:login:20191206", 1, "1");
        jedis.setbit("user:login:20191206", 3, "1");
        jedis.setbit("user:login:20191206", 5, "1");
        jedis.setbit("user:login:20191206", 5, "1");
        jedis.setbit("user:login:20191206", 8, "1");

        jedis.setbit("user:login:20191207", 11, "1");
        jedis.setbit("user:login:20191207", 3, "1");
        jedis.setbit("user:login:20191207", 5, "1");
        jedis.setbit("user:login:20191207", 18, "1");

        jedis.setbit("user:login:20191208", 21, "1");
        jedis.setbit("user:login:20191208", 13, "1");
        jedis.setbit("user:login:20191208", 5, "1");
        jedis.setbit("user:login:20191208", 28, "1");
    }

    @Test
    public void resultData() throws Exception {
        // 1  20191206 有多少用户登录
        System.out.println(jedis.bitcount("user:login:20191206")); // 4
        // 2  最近三天 20191206 20191207 20191208 有多少用户登录
        jedis.bitop(BitOP.OR, "user:login:last31",
                "user:login:20191206", "user:login:20191207", "user:login:20191208");
        System.out.println(jedis.bitcount("user:login:last31")); // 9
        // 3  统计连续登录三天的用户
        jedis.bitop(BitOP.AND, "user:login:last32",
                "user:login:20191206", "user:login:20191207", "user:login:20191208");
        System.out.println(jedis.bitcount("user:login:last32")); // 1 --> 5
        System.out.println("=========");
        System.out.println(jedis.bitpos("user:login:last32", true));
    }

    // 比较一下用set 和用bitmap直接的一个存储量的差别
    @Test
    public void testSet() throws Exception {
        Pipeline pipelined = jedis.pipelined();
        for (int i = 0; i < 1000000; i++) {
            pipelined.sadd("set:user:login:20191206", "user:" + i);
        }
        pipelined.sync();
        // 11888895 b
    }

    // 比较一下用set 和用bitmap直接的一个存储量的差别
    @Test
    public void testBitMap() throws Exception {
        Pipeline pipelined = jedis.pipelined();
        for (int i = 0; i < 10000000; i++) {
            pipelined.setbit("bit:user:login:20191207", i, true);
        }
        pipelined.sync();
        // 100w --> 1436 b
        // 1000w --> 14219 b
    }

}
Jedis 使用 HyperLogLog
public class HyperLogLogDemo {

    // 客户端连接对象
    private Jedis jedis = null;

    @Before
    public void init() throws Exception {
        jedis = new Jedis("192.168.52.128", 6379);
    }

    @After
    public void close() throws Exception {
        if (jedis != null) {
            jedis.close();
        }
    }

    @Test
    public void initData() throws Exception {
        // 初始化用户登录数据
        jedis.pfadd("pf:login:20191206", "1");
        jedis.pfadd("pf:login:20191206", "3");
        jedis.pfadd("pf:login:20191206", "5");
        jedis.pfadd("pf:login:20191206", "5");
        jedis.pfadd("pf:login:20191206", "8");
    }

    @Test
    public void testLog() throws Exception {
        Pipeline pipelined = jedis.pipelined();
        for (int i = 0; i < 10000000; i++) {
            pipelined.pfadd("pf:user:login:20191207", "" + i);
        }
        pipelined.sync();
        System.out.println(jedis.pfcount("pf:user:login:20191207"));
    }
    
}
Jedis 使用 GEO
public class GeaDemo {

    // 客户端连接对象
    private Jedis jedis = null;

    @Before
    public void init() throws Exception {
        jedis = new Jedis("192.168.52.128", 6379);
    }

    @After
    public void close() throws Exception {
        if (jedis != null) {
            jedis.close();
        }
    }

    @Test
    public void initData() throws Exception {
        HotelPosition h1 = new HotelPosition(113.23121, 23.117933, "如家酒店");
        HotelPosition h2 = new HotelPosition(113.203641, 23.382214, "速8酒店");
        HotelPosition h3 = new HotelPosition(113.361532, 23.128617, "7天连锁酒店");
        HotelPosition h4 = new HotelPosition(113.258358, 23.162526, "广州曼克顿酒店");
        jedis.geoadd("hotel", h1.getLng(), h1.getLat(), h1.getName());
        jedis.geoadd("hotel", h2.getLng(), h2.getLat(), h2.getName());
        jedis.geoadd("hotel", h3.getLng(), h3.getLat(), h3.getName());
        jedis.geoadd("hotel", h4.getLng(), h4.getLat(), h4.getName());
    }

    @Test
    public void handler() throws Exception {
        // 离当前位置10km之内的酒店
        // 当前的位置 113.324981,23.150597
        List hotels = jedis.georadius("hotel", 113.324981, 23.150597, 10, GeoUnit.KM);
        for (GeoRadiusResponse hotel : hotels) {
            System.out.println(new String(hotel.getMember()));
        }
        //查看距离信息
        System.out.println(jedis.geodist("hotel", "如家酒店", "7天连锁酒店", GeoUnit.KM));
    }
}
Jedis 使用哨兵
public class SentinelDemo {

    private JedisSentinelPool pool = null;

    @Before
    public void init() throws Exception {
        // redis服务集群的名字
        String masterName = "mymaster";
        // 哨兵集群的名字
        Set sentinels = new HashSet<>();
        pool = new JedisSentinelPool(masterName, sentinels);
    }

    // 批量插入500w个数据
    @Test
    public void test1() throws Exception {
        // 获取到连接客户端
        Jedis jedis = pool.getResource();
        Pipeline pipelined = jedis.pipelined();
        for (int i = 0; i < 5000000; i++) {
            pipelined.set("key" + i, "value" + i);
        }
        pipelined.sync();
    }

    // 客户端自动连接重试
    @Test
    public void testfailOver() throws Exception {
        //获取到连接客户端
        Jedis jedis = null;
        try {
            while (true) {
                jedis = pool.getResource();
                jedis.incr("count");
                System.out.println("count=" + jedis.get("count"));
                TimeUnit.SECONDS.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
    
}
Jedis 使用 Redis 连接池
public class JedisPoolDemo {

    @Test
    public void testPool() throws Exception {
        // 连接池参数配置
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        // 最多连接数
        config.setMaxTotal(10);
        // 最多空闲数
        config.setMaxIdle(5);
        // 最小空闲数
        config.setMinIdle(5);
        // 最多等待时间
        config.setMaxWaitMillis(2000);
        String host = "192.168.52.128";
        int port = 6379;
        // jedis连接池对象配置
        JedisPool jedisPool = new JedisPool(config, host, port);
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            jedis.set("a", "1");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
            	// 不会真正关闭连接, 把连接对象归还到连接池
                jedis.close();
            }
        }
        
    }
}
实战应用 - 使用 Redis 缓存的用户登陆

参考:SpringBoot整合Redis 之 StringRedisTemplate、RedisTemplate 基础

整体思路:

  1. 用户根据用户名和密码进行登录
  2. 优先在 redis 缓存中根据用户名查询用户信息
  3. 如果 redis 缓存中没有对应的用户信息,再从数据库查询用户信息,返回对应的数据,保存到 redis 缓存中
  4. 对于缓存到 redis 中的用户信息设置一个有效时间(7天)

引入 redis 依赖和连接池:


    org.springframework.boot
    spring-boot-starter-data-redis



    org.apache.commons
    commons-pool2

在配置文件中配置 redis 集群信息:

redis:
  cluster:
    nodes: 192.168.52.129:7000,192.168.52.129:7001,192.168.52.129:7002,192.168.52.129:7003,192.168.52.129:7004,192.168.52.129:7005
  lettuce:
    pool:
      max-wait: 15000
      max-idle: 10
      max-active: 10
      min-idle: 10

根据用户名查询的业务代码:

@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public User queryByName(String username) {
        System.out.println("从数据库查询结果:" + username);
        return userMapper.queryByName(username);
    }

    @Override
    public User queryByNameByCache(String username) throws Exception {
        System.out.println("从redis缓存中查询查询结果:" + username);
        ValueOperations ops = redisTemplate.opsForValue();
        // 直接从本地内存(比较短的时间1min)查询, 对于redis的压力会小
        String value = ops.get("user:" + username);
        ObjectMapper objectMapper = new ObjectMapper();
        User user = null;
        // 判断redis的返回结果
        if (StringUtils.isEmpty(value)) {
            // 查询数据库
            user = queryByName(username);
            if (user != null) { // 添加到缓存中
                ops.set("user:" + username, objectMapper.writevalueAsString(user), 7, TimeUnit.DAYS);
            } else {
                ops.set("user:" + username, objectMapper.writevalueAsString(new User()), 1, TimeUnit.MINUTES);
            }
        } else {
            user = objectMapper.readValue(value, User.class);
        }
        return user;
    }
}
缓存常见问题 缓存穿透

缓存穿透:查询一个一定不存在的数据,由于缓存在不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次都需要到数据库中去查询,造成缓存穿透

解决方案:

  • 单个 key 穿透:持久层查询不到就缓存空节点,查询时先判断缓存中是否 exists(key),如果有就直接返回空,没有则查询后返回(对于空节点设置一个很小的缓存过期时间)
  • 多个 key 穿透:先判断 key 是否是一个存在的 key,如果 key 合法则查询数据库;如果 key 不合法,则不查询数据库
    数据量不大,可以把 key 都存放到 redis 的 set 集合中;数据量非常大(上亿级别),可以考虑使用布隆过滤器
雪崩

如果缓存集中在一个时间段内失效,发生大量的缓存穿透,所有的查询都落到了数据库上,造成数据库服务器雪崩

解决方案:

  1. 尽量让 key 的过期时间均匀分布
  2. 控制同一个 key 的线程数量(读取数据库的线程数量)–> 限流
    常见的限流算法有:计数算法、滑动窗口、令牌桶、漏桶
热点 key

某个 key 的访问频率非常频繁,导致 Redis 服务器的压力剧增,无法保证可以正常提供服务

比如说鹿晗和关晓彤的"官宣"直接导致微博的服务瘫痪

由于是 Redis 服务器的压力导致瘫痪,所以主要解决方案集中在:提高 Redis 的 qps、控制对 Redis 的访问

解决方案:

  1. 扩容,添加集群中的从服务器,提高读数据的能力
  2. 考虑使用本地缓存,把 redis 中的热点数据直接缓存在本地的服务器的内存中,减少对 redis 的访问
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/298487.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号