Redis 实战学习(中)

161 阅读10分钟

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

一、实战

使用 jedis,测试环境:

  1. pom.xml 配置
  2. 测试代码

  1. pom.xml
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.5.2</version>
</dependency>
  1. 测试代码
public class JedisTest {

    private Jedis jedis;

    @Before
    public void setUp() {

        jedis = new Jedis("127.0.0.1", 6379);
    }

    @After
    public void after() {

        jedis.close();
    }

    @Test
    public void testCache() {

        // 最简单的设置缓存
        jedis.set("key1", "value1");

        System.out.println(jedis.get("key1"));
    }
}

(7)实现 OA系统中的待办事项列表管理

OA 系统:自动化办公系统,企业日常运行的办公的日常事务都在 OA 系统里来做。

例如:请假、审批、开会、项目、任务和待办事项列表。

主要命令有:

  • lindex
  • lset
  • linsert
  • ltrim
  • lrem

操作有:

  • 新增待办事项:lpush list event
  • 插入待办事项:linsert list index event

[待办事项3,待办事项2,插入待办事项,待办事项1]

  • 查询待办事项列表:lrange list 0 -1,查询所有
  • 完成待办事项:lrem list 0 event,就把这个待办事项给删了,然后 lpush done_list event,添加一个已办事项
  • 批量完成待办事项,ltrim list start_index end_index,再 lpush done_list event1 event2 event3
  • 修改待办事项:使用 lindexlset
  • 查询已办事项列表:lrange done_list 0 -01

实现如下:

public class JedisTest {
    /**
     * 添加待办事项
     * @param todoEvent 代办事项
     */
    private void addTodoEvent(long userId, String todoEvent) {
        // 从左边添加
        jedis.lpush("todo_event::" + userId, todoEvent);
    }
​
    /**
     * 分页查询待办事项列表
     * @param userId 用户Id
     * @param pageNo 页号
     * @param pageSize 页大小
     * @return 列表
     */
    private List<String> findTodoEventByPage(long userId, int pageNo, int pageSize) {
        int startIndex = (pageNo - 1) * pageSize;
        int endIndex = pageNo * pageSize - 1;
        return jedis.lrange("todo_event::" + userId, startIndex, endIndex);
    }
​
    /**
     * 插入待办事项
     * @param userId 用户Id
     * @param position 位置
     * @param targetTodoEvent 目标事项
     * @param todoEvent 待办事项
     */
    private void insertTodoEvent(long userId,
                                 ListPosition position,
                                 String targetTodoEvent,
                                 String todoEvent) {
        jedis.linsert("todo_event::" + userId, position, targetTodoEvent, todoEvent);
    }
​
    /**
     * 修改一个待办事项
     * @param userId 用户Id
     * @param index 位置
     * @param updatedTodoEvent 更新事项
     */
    private void updateTodoEvent(long userId, int index, String updatedTodoEvent) {
        jedis.lset("todo_event::" + userId, index, updatedTodoEvent);
    }
​
    /**
     * 完成一个待办事项
     * @param userId 用户Id
     * @param todoEvent 待办事项
     */
    private void finishTodoEvent(long userId, String todoEvent) {
        // count等于0的话,就不分往前还是往后后,直接指定的value(元素)都删
        jedis.lrem("todo_event::" + userId, 0, todoEvent);
    }
​
    @Test
    public void testTodoEvent() {
        // 0. 添加20个待办事项
        long userId = 2;
        for(int i = 0; i < 20; i++) {
            addTodoEvent(userId, "第" + (i + 1) + "个待办事项");
        }
​
        // 1. 查询第一页待办事项
        int pageNo = 1;
        int pageSize = 10;
        List<String> todoEventPage = findTodoEventByPage(userId, pageNo, pageSize);
​
        System.out.println("第一次查询第一页待办事项......");
        for(String todoEvent :todoEventPage) {
            System.out.println(todoEvent);
        }
​
        // 2.1 插入一个待办事项
        Random random = new Random();
        int index = random.nextInt(todoEventPage.size());
        String targetTodoEvent = todoEventPage.get(index);
​
        insertTodoEvent(userId, ListPosition.BEFORE,
                targetTodoEvent, "插入的待办事项");
        System.out.println("在" + targetTodoEvent + "前面插入了一个待办事项");
​
        // 2.2重新分页查询第一页待办事项
        todoEventPage = findTodoEventByPage(
                userId, pageNo, pageSize);
​
        System.out.println("第二次查询第一页待办事项......");
        for(String todoEvent :todoEventPage) {
            System.out.println(todoEvent);
        }
​
        // 3.修改一个待办事项
        index = random.nextInt(todoEventPage.size());
        updateTodoEvent(userId, index, "修改后的待办事项");
​
        // 4.完成一个待办事项
        finishTodoEvent(userId, todoEventPage.get(0));
​
        // 最后查询一次待办事项
        todoEventPage = findTodoEventByPage(
                userId, pageNo, pageSize);
​
        System.out.println("第三次查询第一页待办事项......");
        for(String todoEvent :todoEventPage) {
            System.out.println(todoEvent);
        }
    }
}

输出结果:

第一次查询第一页待办事项......
第20个待办事项
第19个待办事项
第18个待办事项
第17个待办事项
第16个待办事项
第15个待办事项
第14个待办事项
第13个待办事项
第12个待办事项
第11个待办事项
在第19个待办事项前面插入了一个待办事项
第二次查询第一页待办事项......
第20个待办事项
插入的待办事项
第19个待办事项
第18个待办事项
第17个待办事项
第16个待办事项
第15个待办事项
第14个待办事项
第13个待办事项
第12个待办事项
第三次查询第一页待办事项......
修改后的待办事项
插入的待办事项
第19个待办事项
第18个待办事项
第17个待办事项
第16个待办事项
第15个待办事项
第14个待办事项
第13个待办事项
第12个待办事项

(8)网站用户注册时的邮件验证机制

用户邮箱注册需求:

  1. 用户填写注册信息,提交注册
  2. 注册成功,后台会发送一封邮件
  3. 用户收到邮件,需点击邮件中的链接,完成激活

然而发送邮件,这个过程比较耗费时间,所以需要异步处理,将发送邮件任务丢入队列中。

实战操作:

  1. lpush :将邮件放入 list
  2. brpop:发送邮件的系统用 brpop 阻塞式得从队列中获取任务

实现如下:

public class JedisTest {
    /**
     * 让发送邮件任务入队列
     * @param sendMailTask 任务
     */
    private void enqueueSendMailTask(String sendMailTask) {
        jedis.lpush("send_mail_task_queue", sendMailTask);
    }
​
    /**
     * 阻塞式获取发送邮件任务
     * @return 队列
     */
    private List<String> takeSendMailTask() {
        return jedis.brpop(5, "send_mail_task_queue");
    }
​
    @Test
    public void testSendMail() {
        System.out.println("尝试阻塞式的获取发送邮件任务......");
        List<String> sendMailTasks = takeSendMailTask();
​
        enqueueSendMailTask("第一个邮件发送任务");
        sendMailTasks = takeSendMailTask();
        System.out.println(sendMailTasks);
    }
}

输出结果:

尝试阻塞式的获取发送邮件任务......
[send_mail_task_queue, 第一个邮件发送任务]

(9)网站每日UV数据指标去重统计

什么是 UV

网站的 UV:有多少用户访问了你的网站,但是一个用户可能会访问多次,此时要对多次访问进行去重,保留完整的不重复的访问过你网站的用户集合,然后算出集合的元素数量,就会知道你当日的 UV

说白了,就是将访问的用户放入 Set 中去重。

Tips:这里只列举演示,实际生产不会这样。

操作:

  1. sadd:将 uid放入集合
  2. scard:拿到 UV

实现如下:

public class JedisTest {
    /**
     * 添加一次用户访问记录
     */
    private void addUserAccess(long userId) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(
                "yyyy-MM-dd");
        String today = dateFormat.format(new Date());
        jedis.sadd("user_access::" + today, String.valueOf(userId));
    }
​
    /**
     * 获取当天的网站uv的值
     * @return UV值
     */
    private long getUV() {
        SimpleDateFormat dateFormat = new SimpleDateFormat(
                "yyyy-MM-dd");
        String today = dateFormat.format(new Date());
        return jedis.scard("user_access::" + today);
    }
​
    @Test
    public void testUV()  {
​
        // 100 个用户,每个用户访问10次
        for(int i = 0; i < 100; i++) {
            long userId = i + 1;
​
            for(int j = 0; j < 10; j++) {
                addUserAccess(userId);
            }
        }
​
        long uv = getUV();
        // 输出应为 100
        System.out.println("当日uv为:" + uv);
    }
}

输出结果:

100

(10)朋友圈点赞功能的实现

朋友圈操作:

  1. 查看朋友圈的动态
  2. 对某个动态点赞
  3. 取消某个动态点赞
  4. 查看动态被谁点赞

实战操作如下:

  1. sadd:给某一条朋友圈添加点赞的一个好友
  2. srem:用户取消点赞的话,用 srem 删除某个好友的点赞
  3. sismember:查看你是否对某条朋友圈进行过点赞
  4. smembers:发出的朋友圈被哪些人点赞了
  5. scard:朋友圈的点赞次数

实现如下:

public class JedisTest {
    /**
     * 对朋友圈进行点赞
     * @param userId 用户Id
     * @param momentId 动态Id
     */
    private void likeMoment(long userId, long momentId) {
        jedis.sadd("moment_like_users::" + momentId, String.valueOf(userId));
    }
​
    /**
     * 对朋友圈取消点赞
     * @param userId 用户Id
     * @param momentId 动态Id
     */
    private void dislikeMoment(long userId, long momentId) {
        jedis.srem("moment_like_users::" + momentId, String.valueOf(userId));
    }
​
    /**
     * 查看自己是否对某条朋友圈点赞过
     * @param userId 用户Id
     * @param momentId 动态Id
     * @return 是否
     */
    private boolean hasLikedMoment(long userId, long momentId) {
        return jedis.sismember("moment_like_users::" + momentId, String.valueOf(userId));
    }
​
    /**
     * 获取你的一条朋友圈有哪些人点赞了
     * @param momentId 动态Id
     * @return 哪些人
     */
    private Set<String> getMomentLikeUsers(long momentId) {
        return jedis.smembers("moment_like_users::" + momentId);
    }
​
    /**
     * 获取你的一条朋友圈被几个人点赞了
     * @param momentId  动态Id
     * @return 点赞数
     */
    private long getMomentLikeUsersCount(long momentId) {
        return jedis.scard("moment_like_users::" + momentId);
    }
​
    @Test
    public void testMoment() {
        // 你的用户id
        long userId = 11;
        // 你的朋友圈id
        long momentId = 151;
        // 你的朋友1的用户id
        long friendId = 12;
        // 你的朋友2的用户id
        long otherFriendId = 13;
​
        // 你的朋友1对你的朋友圈进行点赞,再取消点赞
        likeMoment(friendId, momentId);
        dislikeMoment(friendId, momentId);
        boolean hasLikedMoment = hasLikedMoment(friendId, momentId);
        System.out.println("朋友1刷朋友圈,看到是否对你的朋友圈点赞过:" + (hasLikedMoment ? "是" : "否"));
​
        // 你的朋友2对你的朋友圈进行点赞
        likeMoment(otherFriendId, momentId);
        hasLikedMoment = hasLikedMoment(otherFriendId, momentId);
        System.out.println("朋友2刷朋友圈,看到是否对你的朋友圈点赞过:" + (hasLikedMoment ? "是" : "否"));
​
        // 你自己刷朋友圈,看自己的朋友圈的点赞情况
        Set<String> momentLikeUsers = getMomentLikeUsers(momentId);
        long momentLikeUsersCount = getMomentLikeUsersCount(momentId);
        System.out.println("你自己刷朋友圈,看到自己发的朋友圈被" + momentLikeUsersCount + "个人点赞了,点赞的用户为:" + momentLikeUsers);
    }
}

输出结果:

朋友1刷朋友圈,看到是否对你的朋友圈点赞过:否
朋友2刷朋友圈,看到是否对你的朋友圈点赞过:是
你自己刷朋友圈,看到自己发的朋友圈被1个人点赞了,点赞的用户为:[13]

(11)实现一个网站投票统计程序

实战操作如下:

  1. sadd:添加投票
  2. sismember:检查用户是否已经对任何一个投票项发起过投票
  3. smembers:可以拿到每个投票项的投票人
  4. scard:可以统计每个投票箱的投票人数

实现如下:

public class JedisTest {
    /**
     * 投票
     * @param userId 用户Id
     * @param voteItemId 投票事项
     */
    private void vote(long userId, long voteItemId) {
        jedis.sadd("vote_item_users::" + voteItemId, String.valueOf(userId));
    }
​
    /**
     * 检查用户对投票项是否投过票
     * @param userId 用户Id
     * @param voteItemId 投票事项
     * @return 是否
     */
    private boolean hasVoted(long userId, long voteItemId) {
        return jedis.sismember("vote_item_users::" + voteItemId, String.valueOf(userId));
    }
​
    /**
     * 获取一个投票项被哪些人投票了
     * @param voteItemId 投票事项
     * @return 列表
     */
    private Set<String> getVoteItemUsers(long voteItemId) {
        return jedis.smembers("vote_item_users::" + voteItemId);
    }
​
    /**
     * 获取一个投票项被多少人投票了
     * @param voteItemId 投票事项
     * @return 总数
     */
    private long getVoteItemUsersCount(long voteItemId) {
        return jedis.scard("vote_item_users::" + voteItemId);
    }
​
    @Test
    public void testVote() {
​
        // 定义用户id
        long userId = 1;
        // 定义投票项id
        long voteItemId = 110;
​
        // 进行投票
        vote(userId, voteItemId);
        // 检查我是否投票过
        boolean hasVoted = hasVoted(userId, voteItemId);
        System.out.println("用户查看自己是否投票过:" +(hasVoted ? "是" : "否"));
        // 归票统计
        Set<String> voteItemUsers = getVoteItemUsers(voteItemId);
        long voteItemUsersCount = getVoteItemUsersCount(voteItemId);
        System.out.println("投票项有哪些人投票:" + voteItemUsers + ",有几个人投票:" + voteItemUsersCount);
    }
}

输出结果:

用户查看自己是否投票过:是
投票项有哪些人投票:[1],有几个人投票:1

(12)实现类似微博的社交关系

微博社交关系,主要动作:

  • 关注
  • 取关

实战操作:

  1. sadd:关注目标用户
  2. srem:取消用户集合里的关注
  1. smembers:获取你关注的所有人和你被哪些人关注了
  2. scard:获取你关注的人数和关注你的人数

实现如下:

public class JedisTest {
    /**
     * 关注别人
     * @param userId 用户Id
     * @param followUserId 跟随用户Id
     */
    private void follow(long userId, long followUserId) {
        jedis.sadd("user::" + followUserId + "::followers", String.valueOf(userId));
        jedis.sadd("user::" + userId + "::follow_users", String.valueOf(followUserId));
    }
​
    /**
     * 取消关注别人
     * @param userId 用户Id
     * @param followUserId 跟随用户Id
     */
    public void unfollow(long userId, long followUserId) {
        jedis.srem("user::" + followUserId + "::followers", String.valueOf(userId));
        jedis.srem("user::" + userId + "::follow_users", String.valueOf(followUserId));
    }
​
    /**
     * 查看有哪些人关注了自己
     * @param userId 用户Id
     * @return 用户列表
     */
    private Set<String> getFollowers(long userId) {
        return jedis.smembers("user::" + userId + "::followers");
    }
​
    /**
     * 查看关注了自己的人数
     * @param userId 用户Id
     * @return 人数
     */
    private long getFollowersCount(long userId) {
        return jedis.scard("user::" + userId + "::followers");
    }
​
    /**
     * 查看自己关注了哪些人
     * @param userId 用户Id
     * @return 人
     */
    private Set<String> getFollowUsers(long userId) {
        return jedis.smembers("user::" + userId + "::follow_users");
    }
​
    /**
     * 查看自己关注的人数
     * @param userId 用户Id
     * @return 人数
     */
    private long getFollowUsersCount(long userId) {
        return jedis.scard("user::" + userId + "::follow_users");
    }
​
    /**
     * 获取用户跟其他用户之间共同关注的人有哪些
     * @param userId 用户Id
     * @param otherUserId 其他用户Id
     * @return 人
     */
    private Set<String> getSameFollowUsers(long userId, long otherUserId) {
        return jedis.sinter("user::" + userId + "::follow_users",
                "user::" + otherUserId + "::follow_users");
    }
​
    /**
     * 获取给我推荐的可关注人
     * 我关注的某个好友关注的一些人,我没关注那些人,此时推荐那些人给我
     * @param userId 用户Id
     * @return 人
     */
    private Set<String> getRecommendFollowUsers(long userId, long otherUserId) {
        return jedis.sdiff("user::" + otherUserId + "::follow_users",
                "user::" + userId + "::follow_users");
    }
​
    @Test
    public void testWeiBo()  {
​
        // 定义用户id
        long userId = 31;
        long friendId = 32;
        long superstarId = 33;
        long classmateId = 34;
        long motherId = 35;
​
        // 定义关注的关系链
        follow(userId, friendId);
        follow(userId, motherId);
        follow(userId, superstarId);
        follow(friendId, superstarId);
        follow(friendId, classmateId);
​
        // 明星看看自己被哪些人关注了
        Set<String> superstarFollowers = getFollowers(superstarId);
        long superstarFollowersCount = getFollowersCount(superstarId);
        System.out.println("明星被哪些人关注了:" + superstarFollowers + ",关注自己的人数为:" + superstarFollowersCount);
​
        // 朋友看看自己被哪些人关注了,自己关注了哪些人
        Set<String> friendFollowers = getFollowers(friendId);
        long friendFollowersCount = getFollowersCount(friendId);
​
        Set<String> friendFollowUsers = getFollowUsers(friendId);
        long friendFollowUsersCount = getFollowUsersCount(friendId);
​
        System.out.println("朋友被哪些人关注了:" + friendFollowers + ",被多少人关注了:" + friendFollowersCount
                + ",朋友关注了哪些人:" + friendFollowUsers + ",关注了多少人:" + friendFollowUsersCount);
​
        // 查看我自己关注了哪些
        Set<String> myFollowUsers = getFollowUsers(userId);
        long myFollowUsersCount = getFollowUsersCount(userId);
        System.out.println("我关注了哪些人:" + myFollowUsers + ", 我关注的人数:" + myFollowUsersCount);
​
        // 获取我和朋友共同关注的好友
        Set<String> sameFollowUsers = getSameFollowUsers(userId, friendId);
        System.out.println("我和朋友共同关注的人有哪些:" + sameFollowUsers);
​
        // 获取推荐给我的可以关注的人,就是我关注的人关注的其他人
        Set<String> recommendFollowUsers = getRecommendFollowUsers(userId, friendId);
        System.out.println("推荐给我的关注的人有哪些:" + recommendFollowUsers);
    }
}

输出结果:

明星被哪些人关注了:[31, 32],关注自己的人数为:2
朋友被哪些人关注了:[31],被多少人关注了:1,朋友关注了哪些人:[33, 34],关注了多少人:2
我关注了哪些人:[32, 33, 35], 我关注的人数:3
我和朋友共同关注的人有哪些:[33]
推荐给我的关注的人有哪些:[34]

(13)实现网站上的抽奖程序

需求:从一堆人中选取几人。

实战操作如下:

  • srandommember:随机从 set 里返回几个元素
  • spop:机从 set 里弹出几个元素

实现如下:

public class JedisTest {
    /**
     * 添加抽奖候选人
     * @param userId 用户Id
     */
    private void addLotteryDrawCandidate(long userId, long lotteryDrawEventId) {
        jedis.sadd("lottery_draw_event::" + lotteryDrawEventId +"::candidates",
                String.valueOf(userId));
    }
​
    /**
     * 实际进行抽奖
     * @param lotteryDrawEventId 抽奖事件
     * @return 人数
     */
    private List<String> doLotteryDraw(long lotteryDrawEventId, int count) {
        return jedis.srandmember("lottery_draw_event::" + lotteryDrawEventId +"::candidates", count);
    }
​
    @Test
    public void testLottery() {
​
        int lotteryDrawEventId = 120;
​
        for(int i = 0; i < 20; i++) {
            addLotteryDrawCandidate(i + 1, lotteryDrawEventId);
        }
​
        List<String> lotteryDrawUsers = doLotteryDraw(lotteryDrawEventId, 3);
        System.out.println("获奖人选为:" + lotteryDrawUsers);
    }
}

输出结果:

获奖人选为:[9, 13, 3]