My logo
Published on

Redis Java客户端 Jedis 的使用

  1. 博客网站的文章发布与查看
  2. 基于hash数据结构重构博客网站案例
  3. 短网址追踪案例
  4. 基于令牌的用户登录会话机制
  5. 秒杀活动下的公平队列抢购机制
  6. 实现OA系统中的待办事项列表管理
  7. 网站用户注册时的邮件验证机制
  8. 网站每日UV数据指标去重统计
  9. 朋友圈点赞功能的实现
  10. 实现一个网站投票统计程序
  11. 实现网站上的抽奖程序
  12. 为商品搜索构建反向索引
  13. 实现类似微博的社交关系
  14. 实现音乐网站的排行榜程序
  15. 实现一个新闻推荐机制
  16. 大数据商品关系推荐机制
  17. 网站搜索框的自动补全功能
  18. 基于HyperLogLog的网站UV统计程序
  19. 网站重复垃圾数据的快速去重和过滤
  20. 周活跃用户数、月活跃用户数、年活跃用户数的统计
  21. 基于位图的网站用户行为记录程序
  22. 基于GeoHash的你与商铺距离计算的程序
  23. 陌生人社交APP里的查找附近的人功能实现
  24. 带有自动过期时间的分布式缓存实现
  25. 支持超时自动释放的分布式锁实现
  26. 支持自动过期的用户登录会话实现
  27. 支持冷数据自动淘汰的自动补全程序
  28. 支持身份验证功能的分布式锁释放机制

博客网站的文章发布与查看

mset,mget,msetnx,m -> multi的意思,mset一下子设置多个key-value对,mget就是一下子获取多个key的value,msetnx就是在多个key都不存在的情况下,一次性设置多个key的value。 mset和mget,相当于是batch批量设置和查询,比如说假设你一次性要往redis里塞入20条数据,假设你是通过for循环加上set,执行20次的set,每一次set操作都要一次完整的网络通信过程。 mset,一次性把20个key-value对通过一次网络通信,交给redis去执行,此时就是10ms就可以了,mget也是一个意思,批量查询,msetnx,必须所有key都不存在,然后才可以完成本次的设置。

博客字数统计与文章预览:strlen,getrange。

实现博客点赞次数计数器:incr,decr。

博客网站文章浏览次数统计:hincrby hash view_count 1,hget hash view_count

标签其实是不能重复的,就直接可以用set来保存:sadd是添加标签,srem是删除标签,sismember是判断该文章是否包含某个标签,smembers是返回这个文章的全部标签,scard是这个文章的标签数量。

微博的共同关注与推荐关注。sinter set1 set2,取交集,就是共同关注好友;推荐好友关注的人,sdiff获取差集,然后用差集再和你的好友集合sdiff一下,再取差集,就可以得到你没关注的但是你好友关注的人,此时就可以推荐一下;sunion,如果+store还可以存储。

/**
 * @author JingGo
 * @version 1.0.0
 * @Description 博客网站案例 set 使用
 * @createTime 2021年05月07日 09:34:00
 */
public class BlogDemo {

    private Jedis jedis = null;
    private final static String PEFIX = "JG:";

    public BlogDemo(Jedis jedis) {
        this.jedis = jedis;
    }

    /**
     * 获取博客id
     *
     * @return
     */
    public long getBlogId() {
        return jedis.incr(PEFIX + "blog_id_counter");
    }

    /**
     * 发表一篇博客
     */
    public boolean publishBlog(long id, Map<String, String> blog, String[] tags) {
        if (jedis.hexists(PEFIX + "article:" + id, "title")) {
            return false;
        }

        blog.put("content_length", String.valueOf(blog.get("content").length()));

        // Redis Hmset 命令用于同时将多个 field-value (字段-值)对设置到哈希表中。
        // 此命令会覆盖哈希表中已存在的字段。如果哈希表不存在,会创建一个空哈希表,并执行 HMSET 操作。
        jedis.hmset(PEFIX + "article:" + id, blog);
        jedis.lpush(PEFIX + "blog_list", String.valueOf(id));
        // Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
        jedis.sadd(PEFIX + "article:" + id + ":tags", tags);

        return true;
    }

    /**
     * 查看一篇博客
     *
     * @param id
     * @return
     */
    public Map<String, String> findBlogById(long id) {
        Map<String, String> blog = jedis.hgetAll(PEFIX + "article:" + id);
        Set<String> tags = jedis.smembers(PEFIX + "article:" + id + "::tags");
        blog.put("tags", tags.toString());

        incrementBlogViewCount(id);
        return blog;
    }

    /**
     * 更新一篇博客
     */
    public void updateBlog(long id, Map<String, String> updatedBlog) {
        String updatedContent = updatedBlog.get("content");
        if (updatedContent != null && !"".equals(updatedContent)) {
            updatedBlog.put("content_length", String.valueOf(updatedContent.length()));
        }

        jedis.hmset(PEFIX + "article:" + id, updatedBlog);
    }

    /**
     * 对博客进行点赞
     *
     * @param id
     */
    public void incrementBlogLikeCount(long id) {
        jedis.hincrBy(PEFIX + "article:" + id, "like_count", 1);
    }

    /**
     * 增加博客浏览次数
     *
     * @param id
     */
    public void incrementBlogViewCount(long id) {
        jedis.hincrBy(PEFIX + "article:" + id, "view_count", 1);
    }

    /**
     * 分页查询博客
     *
     * @param pageNo
     * @param pageSize
     * @return
     */
    public List<String> findBlogByPage(int pageNo, int pageSize) {
        int startIndex = (pageNo - 1) * pageSize;
        int endIndex = pageNo * pageSize - 1;
        return jedis.lrange(PEFIX + "blog_list", startIndex, endIndex);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");
        BlogDemo demo = new BlogDemo(jedis);

        // 1.发表一篇博客
       /* long id = demo.getBlogId();
        System.out.println("====》》》blog id:" + id);

        Map<String, String> blog = new HashMap<String, String>();
        blog.put("id", String.valueOf(id));
        blog.put("title", "我喜欢学习Redis");
        blog.put("content", "学习Redis是一件特别快乐的事情");
        blog.put("author", "呱呱");
        blog.put("time", "2020-01-01 10:00:00");

        demo.publishBlog(id, blog, new String[]{"中间件", "Redis", "缓存"});*/

        // 2.更新一篇博客
       /* Map<String, String> updatedBlog = new HashMap<String, String>();
        updatedBlog.put("title", "我特别的喜欢学习Redis");
        updatedBlog.put("content", "我平时喜欢到官方网站上去学习Redis");

        demo.updateBlog(2, updatedBlog);*/

        // 构造20篇博客数据
        /*long id = demo.getBlogId();
        for (int i = 0; i < 20; i++) {
            id = demo.getBlogId();

            Map<String, String> blog = new HashMap<String, String>();
            blog.put("id", String.valueOf(id));
            blog.put("title", "第" + (i + 1) + "篇博客");
            blog.put("content", "学习第" + (i + 1) + "篇博客,是一件很有意思的事情");
            blog.put("author", "呱呱");
            blog.put("time", "2020-01-01 10:00:00");

            demo.publishBlog(id, blog, new String[]{"中间件", "Redis", "缓存"});
        }*/

        // 有人分页浏览所有的博客,先浏览第一页
        /*int pageNo = 1;
        int pageSize = 10;

        List<String> blogPage = demo.findBlogByPage(pageNo, pageSize);
        System.out.println("展示第一页的博客......");
        for (String blogId : blogPage) {
            Map<String, String> blog = demo.findBlogById(Long.valueOf(blogId));
            System.out.println(blog);
        }*/

        /*pageNo = 2;
        blogPage = demo.findBlogByPage(pageNo, pageSize);
        System.out.println("展示第二页的博客......");
        for (String blogId : blogPage) {
            blog = demo.findBlogById(Long.valueOf(blogId));
            System.out.println(blog);
        }*/

        // 有别人点击进去查看你的博客的详细内容,并且进行点赞
        int pageNo = 1;
        int pageSize = 10;
        List<String> blogPage = demo.findBlogByPage(pageNo, pageSize);
        Random random = new Random();
        int blogIndex = random.nextInt(blogPage.size());
        String blogId = blogPage.get(blogIndex);

        Map<String, String> blogResult = demo.findBlogById(Long.valueOf(blogId));
        System.out.println("查看博客的详细内容:" + blogResult);
        demo.incrementBlogLikeCount(Long.valueOf(blogId));

        // 你自己去查看自己的博客,看看浏览次数和点赞次数
        blogResult = demo.findBlogById(Long.valueOf(blogId));
        System.out.println("自己查看博客的详细内容:" + blogResult);
    }
}

基于hash数据结构重构博客网站案例

redis的hash数据结构,特别适合存放什么呢,就是类似于我们自己Java代码里搞的一些对象,很适合用hash数据结构来存储的,如果说你的一些对象不用hash结构来存储的话,比如你直接用json串把java对象序列化成json,然后用key-value对的字符串形势放在redis里,但是操作起来不是太方便。 hexists判断博客是否存在,不存在可以发表新博客,用hmset一次性设置多个key-value对在hash数据结构里 查看博客的时候,直接用hgetall hash,一次性把一个hash里全部key-value对拿出来,然后返回就可以了,更新博客,也是用hmset就可以了。 getrange,setrange,append,key-value的字符串形式,可以用到过期时间,过期时间key-value对在适当的时候是会过期的。

/**
 * 博客网站案例
 */
public class BlogHashDemo {

    private Jedis jedis = null;

    public BlogHashDemo(Jedis jedis) {
        this.jedis = jedis;
    }

    /**
     * 获取博客id
     * @return
     */
    public long getBlogId() {
        return jedis.incr("blog_id_counter");
    }

    /**
     * 发表一篇博客
     */
    public boolean publishBlog(long id, Map<String, String> blog) {
        if(jedis.hexists("article::" + id, "title")) {
            return false;
        }
        blog.put("content_length", String.valueOf(blog.get("content").length()));

        jedis.hmset("article::" + id, blog);

        return true;
    }

    /**
     * 查看一篇博客
     * @param id
     * @return
     */
    public Map<String, String> findBlogById(long id) {
        Map<String, String> blog = jedis.hgetAll("article::" + id);
        incrementBlogViewCount(id);
        return blog;
    }

    /**
     * 更新一篇博客
     */
    public void updateBlog(long id, Map<String, String> updatedBlog) {
        String updatedContent = updatedBlog.get("content");
        if(updatedContent != null && !"".equals(updatedContent)) {
            updatedBlog.put("content_length", String.valueOf(updatedContent.length()));
        }

        jedis.hmset("article::" + id, updatedBlog);
    }

    /**
     * 对博客进行点赞
     * @param id
     */
    public void incrementBlogLikeCount(long id) {
        jedis.hincrBy("article::" + id, "like_count", 1);
    }

    /**
     * 增加博客浏览次数
     * @param id
     */
    public void incrementBlogViewCount(long id) {
        jedis.hincrBy("article::" + id, "view_count", 1);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");
        BlogHashDemo demo = new BlogHashDemo(jedis);

        // 发表一篇博客
        long id = demo.getBlogId();

        Map<String, String> blog = new HashMap<String, String>();
        blog.put("id", String.valueOf(id));
        blog.put("title", "我喜欢学习Redis");
        blog.put("content", "学习Redis是一件特别快乐的事情");
        blog.put("author", "呱呱");
        blog.put("time", "2020-01-01 10:00:00");

        demo.publishBlog(id, blog);

        // 更新一篇博客
        Map<String, String> updatedBlog = new HashMap<String, String>();
        updatedBlog.put("title", "我特别的喜欢学习Redis");
        updatedBlog.put("content", "我平时喜欢到官方网站上去学习Redis");

        demo.updateBlog(id, updatedBlog);

        // 有别人点击进去查看你的博客的详细内容,并且进行点赞
        Map<String, String> blogResult = demo.findBlogById(id);
        System.out.println("查看博客的详细内容:" + blogResult);
        demo.incrementBlogLikeCount(id);

        // 你自己去查看自己的博客,看看浏览次数和点赞次数
        blogResult = demo.findBlogById(id);
        System.out.println("自己查看博客的详细内容:" + blogResult);
    }
}

短网址追踪案例

社交网站(微博)一般会把你发表的一些微博里的长连接转换为短连接,这样可以利用短连接进行点击数量追踪,然后再让你进入短连接对应的长连接地址里去,所以可以利用hash数据结构去实现网址点击追踪机制。 比如:http://t.cn/XsGGA9d -> http://redis.com/index.html?dfd=sf& dfd=sf& dfd=sf& dfd=sf& dfd=sf& dfd=sf& 利用redis的incr自增长,然后10进制转36进制,接着hset存放在hash数据结构里,再提供一个映射转换的hget获取方法,hash数据结构,说白了就是我们的Java里的HashMap,redis里的hash就是一个map,一个map里可以放一些key-value对:

public class ShortUrlDemo {

    private static final String[] X36_ARRAY = "0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z".split(",");

    private Jedis jedis = null;

    public ShortUrlDemo(Jedis jedis) {
        this.jedis = jedis;
        this.jedis.set("short_url_seed", "51167890045");
    }

    /**
     * 获取短连接网址
     *
     * @param url
     * @return
     */
    public String getShortUrl(String url) {

        long shortUrlSeed = jedis.incr("short_url_seed");
        StringBuffer buffer = new StringBuffer();
        while (shortUrlSeed > 0) {
            buffer.append(X36_ARRAY[(int) (shortUrlSeed % 36)]);
            shortUrlSeed = shortUrlSeed / 36;
        }
        String shortUrl = buffer.reverse().toString();

        jedis.hset("short_url_access_count", shortUrl, "0");
        jedis.hset("url_mapping", shortUrl, url);
        return shortUrl;
    }

    /**
     * 给短连接地址进行访问次数的增长
     *
     * @param shortUrl
     */
    public void incrementShortUrlAccessCount(String shortUrl) {
        jedis.hincrBy("short_url_access_count", shortUrl, 1);
    }

    /**
     * 获取短连接地址的访问次数
     *
     * @param shortUrl
     */
    public long getShortUrlAccessCount(String shortUrl) {
        return Long.valueOf(jedis.hget("short_url_access_count", shortUrl));
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");

        ShortUrlDemo demo = new ShortUrlDemo(jedis);

        String shortUrl = demo.getShortUrl("http://redis.com/index.html");
        System.out.println("页面上展示的短链接地址为:" + shortUrl);

        for (int i = 0; i < 152; i++) {
            demo.incrementShortUrlAccessCount(shortUrl);
        }

        long accessCount = demo.getShortUrlAccessCount(shortUrl);
        System.out.println("短链接被访问的次数为:" + accessCount);
    }
}

基于令牌的用户登录会话机制

用户平时会访问我们的系统,在处理任何一个请求之前,必须检查一下,这个请求是否带上了一个令牌,如果带了一个令牌,那么此时就必须在redis里检查一下,这个令牌是否有在redis里合法的、有效的一个session会话,如果有这个session会话,此时就可以允许这个请求被处理,因为说明这个人之前已经登录过我们的系统了,登录过后才会在redis里放一个有效的session会话;如果说没有这个session的话,此时就会导致用户必须强制被迫登录,如果用户登录通过之后,就会返回给浏览器或者客户端一块令牌,同时在redis里初始化好一个session会话,后续客户端就会在指定时间范围内发送请求的时候带上一块令牌,每次令牌和服务器端的session校验通过就可以执行请求。 过一段时间过后,服务端的redis里的session会话就会过期,过期了之后,又会导致你必须要重新登录,虽然你可能带上了令牌,但是一检查发现这块令牌对应的redis里的session已经过期了,hset把用户id和令牌存储一下,hset把用户id和过期令牌过期时间存储一下。 每次访问系统都让用户带上令牌,如果令牌不存在就是没登录,hget获取存储的令牌和过期时间,如果令牌过期了也要强制登录,如果令牌校验通过,这次请求就可以通过,如果令牌要是过期了,就用hdel把存储的令牌和过期时间都删了。

public class SessionDemo {

    /**
     * 检查session是否有效
     *
     * @return
     */
    public boolean isSessionValid(Jedis jedis, String token) throws Exception {
        // 校验token是否为空
        if (token == null || "".equals(token)) {
            return false;
        }

        // 这里拿到的session可能就是一个json字符串
        // 我们这里简化一下,就放一个用户user_id作为这里的value
        String session = jedis.hget("sessions", "session:" + token);
        if (session == null || "".equals(session)) {
            return false;
        }

        // 检查一下这个session是否在有效期内
        String expireTime = jedis.hget("sessions:expire_time",
                "session:" + token);
        if (expireTime == null || "".equals(expireTime)) {
            return false;
        }

        SimpleDateFormat dateFormat = new SimpleDateFormat(
                "yyyy-MM-dd HH:mm:ss");
        Date expireTimeDate = dateFormat.parse(expireTime);

        Date now = new Date();

        if (now.after(expireTimeDate)) {
            return false;
        }

        // 如果token不为空,而且获取到的session不为空,而且session没过期
        // 此时可以认为session在有效期内
        return true;
    }

    /**
     * 模拟的登录方法
     *
     * @param jedis
     * @param username
     * @param password
     * @return
     */
    public String login(Jedis jedis, String username, String password) {
        // 基于用户名和密码去登录
        System.out.println("基于用户名和密码登录:" + username + ", " + password);
        Random random = new Random();
        long userId = random.nextInt() * 100;
        // 登录成功之后,生成一块令牌
        String token = UUID.randomUUID().toString().replace("-", "");
        // 基于令牌和用户id去初始化用户的session
        initSession(jedis,userId, token);
        // 返回这个令牌给用户
        return token;
    }

    /**
     * 用户登录成功之后,初始化一个session
     *
     * @param jedis
     * @param userId
     * @param token
     */
    public void initSession(Jedis jedis, long userId, String token) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(
                "yyyy-MM-dd HH:mm:ss");

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.HOUR, 24);
        Date expireTime = calendar.getTime();

        jedis.hset("sessions",
                "session:" + token, String.valueOf(userId));
        jedis.hset("sessions:expire_time",
                "session:" + token, dateFormat.format(expireTime));
    }

    public static void main(String[] args) throws Exception {
        SessionDemo demo = new SessionDemo();
        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");

        // 第一次访问系统,token都是空的
        boolean isSessionValid = demo.isSessionValid(jedis, null);
        System.out.println("第一次访问系统的session校验结果:" + (isSessionValid == true ? "通过" : "不通过"));

        // 强制性进行登录,获取到token
        String token = demo.login(jedis, "zhangsan", "123456");
        System.out.println("登陆过后拿到令牌:" + token);

        // 第二次再次访问系统,此时是可以访问的
        isSessionValid = demo.isSessionValid(jedis, token);
        System.out.println("第二次访问系统的session校验结果:" + (isSessionValid == true ? "通过" : "不通过"));
    }
}

秒杀活动下的公平队列抢购机制

秒杀系统有很多实现方案,其中有一种技术方案,就是对所有涌入系统的秒杀抢购请求,都放入redis的一个list数据结构里去,进行公平队列排队,然后入队之后就等待秒杀结果,专门搞一个消费者从list里按顺序获取抢购请求,按顺序进行库存扣减,扣减成功了就让你抢购成功。 如果说你要是不用公平队列的话,可能就会导致你很多抢购请求进来,大家都在尝试扣减库存,此时可能先涌入进来的请求并没有先对redis执行抢购请求,此时可能后涌入进来的请求先执行了抢购请求,此时就是不公平的。公平队列,基于redis里的list数据结构,搞一个队列,抢购请求都进队列,先入先出,先出来的人先抢购,此时就是公平的。list数据结构,你可以把他理解为是Java里的ArrayList,LinkedList,就是一种有序的数据结构,也可以把他作为队列来使用也是可以的。 对于抢购请求入队列,就用lpush list request就可以了,然后对于出队列进行抢购,就用rpop list就可以了,lpush就是左边推入,rpush就是右边推入,lpop就是左边弹出,rpop就是右边弹出。 所以你lpush+rpop,就是做了一个左边推入和右边弹出的先入先出的公平队列。

/**
 * @author 秒杀活动案例
 * @version 1.0.0
 * @Description TODO
 * @createTime 2021年05月08日 11:22:00
 */
public class SecKillDemo {

    /**
     * 秒杀抢购请求入队
     * @param secKillRequest
     */
    public void enqueueSecKillRequest(Jedis jedis,String secKillRequest) {
        jedis.lpush("sec_kill_request_queue", secKillRequest);
    }

    /**
     * 秒杀抢购请求出队
     * @return
     */
    public String dequeueSecKillRequest(Jedis jedis) {
        return jedis.rpop("sec_kill_request_queue");
    }

    public static void main(String[] args) throws Exception {
        SecKillDemo demo = new SecKillDemo();
        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");

        for(int i = 0; i < 10; i++) {
            demo.enqueueSecKillRequest(jedis,"第" + (i + 1) + "个秒杀请求");
        }

        while(true) {
            String secKillRequest = demo.dequeueSecKillRequest(jedis);

            if(secKillRequest == null
                    || "null".equals(secKillRequest)
                    || "".equals(secKillRequest)) {
                break;
            }
            System.out.println(secKillRequest);
        }
    }
}

实现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 修改待办事项,lindex和lset 查询已办事项列表,lrange done_list 0 -01

/**
 * @author JingGo
 * @version 1.0.0
 * @Description OA系统的待办事项的管理案例
 * @createTime 2021年05月10日 12:04:00
 */
public class TodoEventDemo {

    private final static String CACHE_KEY = "zhss:todo_event:";

    /**
     * 添加待办事项
     *
     * @param jedis
     * @param todoEvent
     */
    public void addTodoEvent(Jedis jedis, long userId, String todoEvent) {
        jedis.lpush(CACHE_KEY + userId, todoEvent);
    }

    /**
     * 分页查询待办事项列表
     *
     * @param userId
     * @param pageNo
     * @param pageSize
     * @return
     */
    public List<String> findTodoEventByPage(Jedis jedis, long userId, int pageNo, int pageSize) {
        int startIndex = (pageNo - 1) * pageSize;
        int endIndex = pageNo * pageSize - 1;
        return jedis.lrange(CACHE_KEY + userId, startIndex, endIndex);
    }

    /**
     * 插入待办事项
     */
    public void insertTodoEvent(Jedis jedis, long userId, ListPosition position, String targetTodoEvent, String todoEvent) {
        jedis.linsert(CACHE_KEY + userId, position, targetTodoEvent, todoEvent);
    }

    /**
     * 修改一个待办事项
     *
     * @param userId
     * @param index
     * @param updatedTodoEvent
     */
    public void updateTodoEvent(Jedis jedis, long userId, int index, String updatedTodoEvent) {
        jedis.lset(CACHE_KEY + userId, index, updatedTodoEvent);
    }

    /**
     * 完成一个待办事项
     *
     * @param userId
     * @param todoEvent
     */
    public void finishTodoEvent(Jedis jedis, long userId, String todoEvent) {
        jedis.lrem(CACHE_KEY + userId, 0, todoEvent);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");
        TodoEventDemo demo = new TodoEventDemo();

        // 添加20个待办事项
        long userId = 2;
        for (int i = 0; i < 20; i++) {
            demo.addTodoEvent(jedis, userId, "第" + (i + 1) + "个待办事项");
        }

        // 查询第一页待办事项
        int pageNo = 1;
        int pageSize = 10;
        List<String> todoEventPage = demo.findTodoEventByPage(jedis, userId, pageNo, pageSize);

        System.out.println("第一次查询第一页待办事项......");
        for (String todoEvent : todoEventPage) {
            System.out.println(todoEvent);
        }

        // 插入一个待办事项
        Random random = new Random();
        int index = random.nextInt(todoEventPage.size());
        String targetTodoEvent = todoEventPage.get(index);
        demo.insertTodoEvent(jedis, userId, ListPosition.BEFORE,
                targetTodoEvent, "插入的待办事项");
        System.out.println("在" + targetTodoEvent + "前面插入了一个待办事项");

        // 重新分页查询第一页待办事项
        todoEventPage = demo.findTodoEventByPage(jedis, userId, pageNo, pageSize);

        System.out.println("第二次查询第一页待办事项......");
        for (String todoEvent : todoEventPage) {
            System.out.println(todoEvent);
        }

        // 修改一个待办事项
        index = random.nextInt(todoEventPage.size());
        demo.updateTodoEvent(jedis, userId, index, "修改后的待办事项");

        // 完成一个待办事项
        demo.finishTodoEvent(jedis, userId, todoEventPage.get(0));

        // 最后查询一次待办事项
        todoEventPage = demo.findTodoEventByPage(jedis, userId, pageNo, pageSize);

        System.out.println("第三次查询第一页待办事项......");
        for(String todoEvent :todoEventPage) {
            System.out.println(todoEvent);
        }
    }
}

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

填写完注册信息,然后提交注册,此时后台完成注册校验和用户数据入库,返回一个消息告诉你说注册成功,但是稍后发送了一封验证邮件到你的邮箱,希望你去邮箱里点击一个链接确认你的邮箱。 一般来说在注册的后台里,是否在注册的逻辑里直接就发送一封邮件呢?发送邮件是比较耗费时间的,所以通常来说,你后台注册成功之后,都会把一个发送邮件的任务给放到一个队列里去,然后直接返回给你了。 接着lpush将发送邮件任务放入list,然后发送邮件的系统就用brpop阻塞式的等待从队列里获取任务,这就可以把list做成阻塞队列的效果了,加一个timeout超时时间即可。

/**
 * @author JingGo
 * @version 1.0.0
 * @Description 注册之后发送邮件的案例
 * @createTime 2021年05月10日 12:00:00
 */
public class SendMailDemo {

    /**
     * 让发送邮件任务入队列
     *
     * @param sendMailTask
     */
    public void enqueueSendMailTask(Jedis jedis, String sendMailTask) {
        jedis.lpush("send_mail_task_queue", sendMailTask);
    }

    /**
     * 阻塞式获取发送邮件任务
     *
     * @return
     */
    public List<String> takeSendMailTask(Jedis jedis) {
        return jedis.brpop(5, "send_mail_task_queue");
    }

    public static void main(String[] args) {
        SendMailDemo demo = new SendMailDemo();
        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");

        System.out.println("尝试阻塞式的获取发送邮件任务......");
        List<String> sendMailTasks = demo.takeSendMailTask(jedis);

        demo.enqueueSendMailTask(jedis, "第一个邮件发送任务");
        sendMailTasks = demo.takeSendMailTask(jedis);
        System.out.println(sendMailTasks);
    }
}

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

set数据结构,跟Java里的set是一样的,就是说放无序的、不重复的数据集合,list一般来说做一个操作,时间复杂度都是O(n),set一般的操作,时间复杂度是O(1),无序的,不重复的数据集合,数据结构上的优化,可以做到,如果说你把重复的数据放到一个set里,他会自动给你进行去重。 网站的uv,有多少用户访问了你的网站,但是一个用户可能会访问多次,此时要对多次访问进行去重,保留完整的不重复的访问过你网站的用户集合,然后算出集合的元素数量,就会知道你当日的uv,sadd把uid放入集合,scard可以拿到uv值,当然这个仅仅是演示,实际实现是不可能这么做的。

/**
 * 网站uv统计案例
 */
public class UVDemo {


    /**
     * 添加一次用户访问记录
     */
    public void addUserAccess(Jedis jedis, long userId) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(
                "yyyy-MM-dd");
        String today = dateFormat.format(new Date());
        jedis.sadd("user_access::" + today, String.valueOf(userId));
    }

    /**
     * 获取当天的网站uv的值
     *
     * @param jedis
     * @return
     */
    public long getUV(Jedis jedis) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(
                "yyyy-MM-dd");
        String today = dateFormat.format(new Date());
        return jedis.scard("user_access::" + today);
    }

    public static void main(String[] args) throws Exception {
        UVDemo demo = new UVDemo();
        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");

        for (int i = 0; i < 100; i++) {
            long userId = i + 1;

            for (int j = 0; j < 10; j++) {
                demo.addUserAccess(jedis, userId);
            }
        }

        long uv = demo.getUV(jedis);
        System.out.println("当日uv为:" + uv);
    }
}

朋友圈点赞功能的实现

假设说你发了一条朋友圈,此时可能你的好友会对你的朋友圈进行点赞,还可以取消点赞,你的好友在刷朋友圈的时候可以查看到自己是否对你点赞过,你自己还可以查看到你的朋友圈有哪些人点赞了,有多少人给你点赞了。 sadd给某一条朋友圈添加点赞的一个好友,用户取消点赞的话,那就是srem删除某个好友的点赞,查看你是否对某条朋友圈进行过点赞,sismember,你发出的朋友圈被哪些人点赞了,smembers,你的朋友圈的点赞次数,scard。

/**
 * @author JingGo
 * @version 1.0.0
 * @Description 朋友圈点赞案例
 * @createTime 2021年05月11日 10:24:00
 */
public class MomentsDemo {

    /**
     * 对朋友圈进行点赞
     * @param userId
     * @param momentId
     */
    public void likeMoment(Jedis jedis,long userId, long momentId) {
        jedis.sadd("zhss:moment_like_users:" + momentId, String.valueOf(userId));
    }

    /**
     * 对朋友圈取消点赞
     * @param userId
     * @param momentId
     */
    public void dislikeMoment(Jedis jedis,long userId, long momentId) {
        jedis.srem("zhss:moment_like_users:" + momentId, String.valueOf(userId));
    }

    /**
     * 查看自己是否对某条朋友圈点赞过
     * @param userId
     * @param momentId
     * @return
     */
    public boolean hasLikedMoment(Jedis jedis,long userId, long momentId) {
        return jedis.sismember("zhss:moment_like_users:" + momentId, String.valueOf(userId));
    }

    /**
     * 获取你的一条朋友圈有哪些人点赞了
     * @param momentId
     * @return
     */
    public Set<String> getMomentLikeUsers(Jedis jedis,long momentId) {
        return jedis.smembers("zhss:moment_like_users:" + momentId);
    }

    /**
     * 获取你的一条朋友圈被几个人点赞了
     * @param momentId
     * @return
     */
    public long getMomentLikeUsersCount(Jedis jedis,long momentId) {
        return jedis.scard("moment_like_users:" + momentId);
    }


    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");

        MomentsDemo demo = new MomentsDemo();

        // 你的用户id
        long userId = 11;
        // 你的朋友圈id
        long momentId = 151;
        // 你的朋友1的用户id
        long friendId = 12;
        // 你的朋友2的用户id
        long otherFriendId = 13;

        // 你的朋友1对你的朋友圈进行点赞,再取消点赞
        demo.likeMoment(jedis,friendId, momentId);
        demo.dislikeMoment(jedis,friendId, momentId);
        boolean hasLikedMoment = demo.hasLikedMoment(jedis,friendId, momentId);
        System.out.println("朋友1刷朋友圈,看到是否对你的朋友圈点赞过:" + (hasLikedMoment ? "是" : "否"));

        // 你的朋友2对你的朋友圈进行点赞
        demo.likeMoment(jedis,otherFriendId, momentId);
        hasLikedMoment = demo.hasLikedMoment(jedis,otherFriendId, momentId);
        System.out.println("朋友2刷朋友圈,看到是否对你的朋友圈点赞过:" + (hasLikedMoment ? "是" : "否"));

        // 你自己刷朋友圈,看自己的朋友圈的点赞情况
        Set<String> momentLikeUsers = demo.getMomentLikeUsers(jedis,momentId);
        long momentLikeUsersCount = demo.getMomentLikeUsersCount(jedis,momentId);
        System.out.println("你自己刷朋友圈,看到自己发的朋友圈被" + momentLikeUsersCount + "个人点赞了,点赞的用户为:" + momentLikeUsers);
    }
}

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

sadd把用户添加到某个投票项的投票用户集合里去,sismember可以检查用户是否已经对任何一个投票项发起过投票,scard可以统计每个投票箱的投票人数,smembers可以拿到每个投票项的投票人。

/**
 * @author JingGo
 * @version 1.0.0
 * @Description 投票统计案例
 * @createTime 2021年05月13日 14:30:00
 */
public class VoteDemo {

    /**
     * 投票
     *
     * @param jedis
     * @param userId
     * @param voteItemId
     */
    public void vote(Jedis jedis, long userId, long voteItemId) {
        jedis.sadd("zhss:vote_item_users:" + voteItemId, String.valueOf(userId));
    }

    /**
     * 检查用户对投票项是否投过票
     *
     * @param userId
     * @param voteItemId
     * @return
     */
    public boolean hasVoted(Jedis jedis, long userId, long voteItemId) {
        return jedis.sismember("zhss:vote_item_users:" + voteItemId, String.valueOf(userId));
    }

    /**
     * 获取一个投票项被哪些人投票了
     *
     * @param voteItemId
     * @return
     */
    public Set<String> getVoteItemUsers(Jedis jedis, long voteItemId) {
        return jedis.smembers("zhss:vote_item_users:" + voteItemId);
    }

    /**
     * 获取一个投票项被多少人投票了
     *
     * @param voteItemId
     * @return
     */
    public long getVoteItemUsersCount(Jedis jedis, long voteItemId) {
        return jedis.scard("zhss:vote_item_users:" + voteItemId);
    }

    public static void main(String[] args) {
        VoteDemo demo = new VoteDemo();
        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");

        // 定义用户id
        long userId = 1;
        // 定义投票项id
        long voteItemId = 110;

        // 进行投票
        demo.vote(jedis, userId, voteItemId);
        // 检查我是否投票过
        boolean hasVoted = demo.hasVoted(jedis, userId, voteItemId);
        System.out.println("用户查看自己是否投票过:" + (hasVoted ? "是" : "否"));
        // 归票统计
        Set<String> voteItemUsers = demo.getVoteItemUsers(jedis, voteItemId);
        long voteItemUsersCount = demo.getVoteItemUsersCount(jedis, voteItemId);
        System.out.println("投票项有哪些人投票:" + voteItemUsers + ",有几个人投票:" + voteItemUsersCount);
    }
}

实现网站上的抽奖程序

srandommember,spop,前者是随机从set里返回几个元素,后者是随机从set里弹出几个元素,sadd可以加入待抽奖的人,smembers返回所有待抽奖人,scard返回参与抽奖的人数,srandmember返回随机抽中奖的人。

/**
 * @author JingGo
 * @version 1.0.0
 * @Description 实现网站上的抽奖程序
 * @createTime 2021年05月11日 12:10:00
 */
public class LotteryDrawDemo {

    /**
     * 添加抽奖候选人
     *
     * @param userId
     */
    public void addLotteryDrawCandidate(Jedis jedis, long userId, long lotteryDrawEventId) {
        jedis.sadd("zhss:lottery_draw_event:" + lotteryDrawEventId + ":candidates",
                String.valueOf(userId));
    }

    /**
     * 实际进行抽奖
     *
     * @param lotteryDrawEventId
     * @return
     */
    public List<String> doLotteryDraw(Jedis jedis, long lotteryDrawEventId, int count) {
        return jedis.srandmember("zhss:lottery_draw_event:" + lotteryDrawEventId + ":candidates", count);
    }

    public static void main(String[] args) {

        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");

        LotteryDrawDemo demo = new LotteryDrawDemo();

        int lotteryDrawEventId = 120;

        for (int i = 0; i < 20; i++) {
            demo.addLotteryDrawCandidate(jedis, i + 1, lotteryDrawEventId);
        }

        List<String> lotteryDrawUsers = demo.doLotteryDraw(jedis, lotteryDrawEventId, 3);
        System.out.println("获奖人选为:" + lotteryDrawUsers);
    }
}

为商品搜索构建反向索引

为商品添加索引,sadd,给商品添加一个关键词索引集合,sadd把商品添加到每个关键词的商品集合里去,删除商品是一个反向的过程,走srem,获取一个商品所有的关键词,smembers,根据某几个关键词去搜索商品,对每个关键词都smembers一下拿到商品集合,然后走一个sintern对多个集合进行交集。 仅仅是演示,实际上如果你要考虑到分页,那就更加复杂了。

/**
 * @author JingGo
 * @version 1.0.0
 * @Description 商品搜索案例
 * @createTime 2021年05月11日 14:20:00
 */
public class ProductSearchDemo {

    /**
     * 添加商品的时候附带一些关键词
     *
     * @param productId
     * @param keywords
     * @param jedis
     */
    public void addProduct(long productId, String[] keywords, Jedis jedis) {
        for (String keyword : keywords) {
            jedis.sadd("keyword:" + keyword + ":products", String.valueOf(productId));
        }
    }

    /**
     * 根据多个关键词搜索商品
     *
     * @param keywords
     * @param jedis
     * @return
     */
    public Set<String> searchProduct(String[] keywords, Jedis jedis) {
        List<String> keywordSetKeys = new ArrayList<String>();
        for (String keyword : keywords) {
            keywordSetKeys.add("keyword:" + keyword + ":products");
        }

        String[] keywordArray = keywordSetKeys.toArray(new String[keywordSetKeys.size()]);

        return jedis.sinter(keywordArray);
    }

    public static void main(String[] args) throws Exception {
        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");
        ProductSearchDemo demo = new ProductSearchDemo();

        // 添加一批商品
        demo.addProduct(11, new String[]{"手机", "iphone", "潮流"}, jedis);
        demo.addProduct(12, new String[]{"iphone", "潮流", "炫酷"}, jedis);
        demo.addProduct(13, new String[]{"iphone", "天蓝色"}, jedis);

        // 根据关键词搜索商品
        Set<String> searchResult = demo.searchProduct(new String[]{"iphone", "潮流"}, jedis);
        System.out.println("商品搜索结果为:" + searchResult);
    }
}

实现类似微博的社交关系

关注目标用户,sadd把你加入到人家的关注用户集合里去,sadd把别人加入到你正关注的用户集合里去,取消关注,srem取消两个用户集合的关注,smembers获取你关注的所有人和你被哪些人关注了,scard获取你关注的人数和关注你的人数。

/**
* @author JingGo
* @version 1.0.0
* @Description 微博社交案例
* @createTime 2021年05月10日 20:12:00
*/
public class MicroBlogDemo {
    
    /**
    * 关注别人
    *
    * @param userId
    * @param followUserId
    */
    public void follow(Jedis jedis, long userId, long followUserId) {
        jedis.sadd("zhss:user::" + followUserId + "::follwer", String.valueOf(userId));
        jedis.sadd("zhss:user::" + userId + "::follow_users", String.valueOf(followUserId));
    }
    
    /**
    * 取消关注别人
    *
    * @param userId
    * @param followUserId
    */
    public void unfollow(Jedis jedis, long userId, long followUserId) {
        jedis.srem("zhss:user::" + followUserId + "::follwers", String.valueOf(userId));
        jedis.srem("zhss:user::" + userId + "::follow_users", String.valueOf(followUserId));
    }
    
    /**
    * 查看有哪些人关注了自己
    *
    * @param userId
    * @return
    */
    public Set<String> getFollowers(Jedis jedis, long userId) {
        return jedis.smembers("zhss:user::" + userId + "::follow_users");
    }
    
    /**
    * 查看关注了自己的人数
    *
    * @param userId
    * @return
    */
    public long getFollowersCount(Jedis jedis, long userId) {
        return jedis.scard("zhss:user::" + userId + "::follwers");
    }
    
    /**
    * 查看自己关注的人数
    *
    * @param userId
    * @return
    */
    public long getFollowUsersCount(Jedis jedis, long userId) {
        return jedis.scard("zhss:user::" + userId + "::follow_users");
    }
    
    /**
    * 查看自己关注了哪些人
    *
    * @param userId
    * @return
    */
    public Set<String> getFollowUsers(Jedis jedis, long userId) {
        return jedis.smembers("zhss:user::" + userId + "::follow_users");
    }
    
    /**
    * 获取用户跟其他用户之间共同关注的人有哪些
    *
    * @param userId
    * @param otherUserId
    * @return
    */
    public Set<String> getSameFollowUsers(Jedis jedis, long userId, long otherUserId) {
        return jedis.sinter("zhss:user::" + userId + "::follow_users",
                            "user::" + otherUserId + "::follow_users");
    }
    
    /**
    * 获取给我推荐的可关注人
    * 我关注的某个好友关注的一些人,我没关注那些人,此时推荐那些人给我
    *
    * @param userId
    * @return
    */
    public Set<String> getRecommendFollowUsers(Jedis jedis, long userId, long otherUserId) {
        return jedis.sdiff("zhss:user::" + otherUserId + "::follow_users",
                           "user::" + userId + "::follow_users");
    }
    
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");
        jedis.auth("jing62661206");
        MicroBlogDemo demo = new MicroBlogDemo();
        
        // 定义用户id
        long userId = 31;
        long friendId = 32;
        long superstarId = 33;
        long classmateId = 34;
        long motherId = 35;
        
        // 定义关注的关系链
        demo.follow(jedis, userId, friendId);
        demo.follow(jedis, userId, motherId);
        demo.follow(jedis, userId, superstarId);
        demo.follow(jedis, friendId, superstarId);
        demo.follow(jedis, friendId, classmateId);
        
        
        // 明星看看自己被哪些人关注了
        Set<String> superstarFollowers = demo.getFollowers(jedis, superstarId);
        long superstarFollowersCount = demo.getFollowersCount(jedis, superstarId);
        System.out.println("明星被哪些人关注了:" + superstarFollowers + ",关注自己的人数为:" + superstarFollowersCount);
        
        // 朋友看看自己被哪些人关注了,自己关注了哪些人
        Set<String> friendFollowers = demo.getFollowers(jedis, friendId);
        long friendFollowersCount = demo.getFollowersCount(jedis, friendId);
        
        Set<String> friendFollowUsers = demo.getFollowUsers(jedis, friendId);
        long friendFollowUsersCount = demo.getFollowUsersCount(jedis, friendId);
        
        System.out.println("朋友被哪些人关注了:" + friendFollowers + ",被多少人关注了:" + friendFollowersCount
                           + ",朋友关注了哪些人:" + friendFollowUsers + ",关注了多少人:" + friendFollowUsersCount);
        
        // 查看我自己关注了哪些
        Set<String> myFollowUsers = demo.getFollowUsers(jedis, userId);
        long myFollowUsersCount = demo.getFollowUsersCount(jedis, userId);
        System.out.println("我关注了哪些人:" + myFollowUsers + ", 我关注的人数:" + myFollowUsersCount);
        
        // 获取我和朋友共同关注的好友
        Set<String> sameFollowUsers = demo.getSameFollowUsers(jedis, userId, friendId);
        System.out.println("我和朋友共同关注的人有哪些:" + sameFollowUsers);
        
        // 获取推荐给我的可以关注的人,就是我关注的人关注的其他人
        Set<String> recommendFollowUsers = demo.getRecommendFollowUsers(jedis, userId, friendId);
        System.out.println("推荐给我的关注的人有哪些:" + recommendFollowUsers);
    }
}

实现音乐网站的排行榜程序

sorted set,不能有重复的数据,加入进去的每个数据都可以带一个分数,他里面的数据都会按照分数进行排序,有序的set,他自动按照分数来排序,相当于你可以定制他里面的排序规则了。 zadd,把音乐加入排行榜中,刚开始分数可能就是0;zscore可以获取音乐的分数;zrem可以删除某个音乐;zincrby可以给某个音乐增加分数,这个增加分数可能就是说有人下载了,或者是有人播放了,或者有人分享了, 有人点赞了,此时可以按照规则去加分,那么排序就会移动了;当然也可以减去分数;zrevrank获取音乐在排行榜里的排名;zrevrange set 0 100 withscores,可以获取排名前100的热门歌曲。

/**
* 实现音乐网站的排行榜程序
* @author: wangyj
* @create: 2022-05-06
* @version: 1.0.0
**/
public class MusicRankingListDemo {
    
    private static Jedis jedis = null;
    static {
        jedis = new Jedis("127.0.0.1");
        jedis.auth("jing62661206");
    }
    
    /**
    * 把新的音乐加入到排行榜里去
    * @param songId
    */
    public void addSong(long songId) {
        jedis.zadd("music_ranking_list", 0, String.valueOf(songId));
    }
    
    /**
    * 增加歌曲的分数
    * @param songId
    * @param score
    */
    public void incrementSongScore(long songId, double score) {
        jedis.zincrby("music_ranking_list", score, String.valueOf(songId));
    }
    
    /**
    * 获取歌曲在排行榜里的排名
    * @param songId
    * @return
    */
    public long getSongRank(long songId) {
        return jedis.zrevrank("music_ranking_list", String.valueOf(songId));
    }
    
    /**
    * 获取音乐排行榜
    * @return
    */
    public Set<Tuple> getMusicRankingList() {
        return jedis.zrevrangeWithScores("music_ranking_list", 0, 2);
    }
    
    public static void main(String[] args) throws Exception {
        MusicRankingListDemo demo = new MusicRankingListDemo();
        
        for(int i = 0; i < 20; i++) {
            demo.addSong(i + 1);
        }
        
        demo.incrementSongScore(5, 3.2);
        demo.incrementSongScore(15, 5.6);
        demo.incrementSongScore(7, 9.6);
        
        long songRank = demo.getSongRank(5);
        System.out.println("查看id为5的歌曲的排名:" + (songRank + 1));
        
        Set<Tuple> musicRankingList = demo.getMusicRankingList();
        System.out.println("查看音乐排行榜排名前3个的歌曲:" + musicRankingList);
    }
}

实现一个新闻推荐机制

我们可以把一个sorted set里的数据倒序排序,选择其中我们指定的分数区间范围内的数据,对这块数据还可以进行分页查询,我们可以维护一个新闻数据集合,里面的分数都是新闻的时间戳。 zadd,把当日最新的新闻加入到一个集合里,zrem是删除某个新闻,zcard是统计当日最新新闻,,zrevrangebyscore max_time min_time start_index count withscores,是说按照他的时间分数进行倒序排序,然后获取指定的分页,zcount 可以获取指定分数范围的数量。

/**
 * 实现一个新闻推荐机制
 * @author: wangyj
 * @create: 2022-05-06
 * @version: 1.0.0
 **/
public class NewsDemo {

    private static Jedis jedis = null;
    static {
        jedis = new Jedis("127.0.0.1");
        jedis.auth("jing62661206");
    }

    /**
     * 加入一篇新闻
     * @param newsId
     */
    public void addNews(long newsId, long timestamp) {
        jedis.zadd("news", timestamp, String.valueOf(newsId));
    }

    /**
     * 搜索新闻
     * @param maxTimestamp
     * @param minTimestamp
     * @param index
     * @param count
     * @return
     */
    public Set<Tuple> searchNews(long maxTimestamp, long minTimestamp, int index , int count) {
        return jedis.zrevrangeByScoreWithScores("news", maxTimestamp, minTimestamp, index, count);
    }

    public static void main(String[] args) throws Exception {
        NewsDemo demo = new NewsDemo();

        for(int i = 0; i < 20; i++) {
            demo.addNews(i + 1, i + 1);
        }

        long maxTimestamp = 18;
        long minTimestamp = 2;

        int pageNo = 1;
        int pageSize = 10;
        int startIndex = (pageNo - 1) * 10;

        Set<Tuple> searchResult = demo.searchNews(
                maxTimestamp, minTimestamp, startIndex, pageSize);

        System.out.println("搜索指定时间范围内的新闻的第一页:" + searchResult);
    }
}

大数据商品关系推荐机制

zremrangebyscore,zunionstore,zinterstore 买了一个商品,他会给你推荐一个列表,购买过此商品的顾客也同时购买了其他XX商品,这个也是可以用这个sorted set来实现的。 对每个顾客,每次购买了之后,都会遍历他之前购买过的所有的商品,对每个商品都维护一个同时购买的其他商品的数量,可以用zincrby set 1 其他商品,然后后续如果要看到某个商品购买的人还购买了其他哪些商品,就可以用zrevrange set start_index end_index withscores。

/**
 * 推荐其他商品案例
 * @author: wangyj
 * @create: 2022-05-06
 * @version: 1.0.0
 **/
public class RecommendProductDemo {

    private static Jedis jedis = null;
    static {
        jedis = new Jedis("127.0.0.1");
        jedis.auth("jing62661206");
    }

    /**
     * 继续购买商品
     * @param productId
     * @param otherProductId
     */
    public void continuePurchase(long productId, long otherProductId) {
        jedis.zincrby("continue_purchase_products:" + productId, 1, String.valueOf(otherProductId));
    }

    /**
     * 推荐其他人购买过的其他商品
     * @param productId
     * @return
     */
    public Set<Tuple> getRecommendProducts(long productId) {
        return jedis.zrevrangeWithScores("continue_purchase_products:" + productId, 0, 2);
    }

    public static void main(String[] args) throws Exception {
        RecommendProductDemo demo = new RecommendProductDemo();

        int productId = 1;

        for(int i = 0; i < 20; i++) {
            demo.continuePurchase(productId, i + 2);
        }
        for(int i = 0; i < 3; i++) {
            demo.continuePurchase(productId, i + 2);
        }

        Set<Tuple> recommendProducts = demo.getRecommendProducts(productId);
        System.out.println("推荐其他人购买过的商品:" + recommendProducts);
    }
}

网站搜索框的自动补全功能

zrangebylex、zrevrangebylex、zlexcount、zremrangebylex、zpopmax、zpopmin、bzpopmax、bzpopmin 对于输入的每个搜索词,都会遍历一下,拼接出来,然后zincrby auto_complete:潜在搜索词 权重 完整搜索词,这样的话,就是每个潜在搜索词都会有一个集合,集合里是各种可能对应的搜索词和权重分数。 然后真正你搜的时候,把你输入的潜在搜索词去执行,zrevrange set 0 9,就是对这个潜在搜索词按照权重分数倒序拿出最近的10个搜索词,做一个自动提示和补全。 string和number list、hash、set、sorted set(自己定制他的排序规则,定制搜索浏览的规则)

/**
 * 自动补全案例
 * @author: wangyj
 * @create: 2022-05-06
 * @version: 1.0.0
 **/
public class AutoCompleteDemo {

    private static Jedis jedis = null;
    static {
        jedis = new Jedis("127.0.0.1");
        jedis.auth("jing62661206");
    }

    /**
     * 搜索某个关键词
     * @param keyword
     */
    public void search(String keyword) {
        char[] keywordCharArray = keyword.toCharArray();

        StringBuffer potentialKeyword = new StringBuffer("");

        // 我喜欢学习

        // 我:时间+我喜欢学习
        // 我喜:时间+我喜欢学习

        // 我爱大家
        // 我:时间+我爱大家

        for(char keywordChar : keywordCharArray) {
            potentialKeyword.append(keywordChar);
            System.out.println("keywordChar: " + potentialKeyword.toString());
            jedis.zincrby(
                    "potential_Keyword:" + potentialKeyword.toString() + ":keywords",
                    new Date().getTime(),
                    keyword);
        }
    }

    /**
     * 获取自动补全列表
     * @param potentialKeyword
     * @return
     */
    public Set<String> getAutoCompleteList(String potentialKeyword) {
        return jedis.zrevrange("potential_Keyword:" + potentialKeyword + ":keywords",
                0, 2);
    }

    public static void main(String[] args) throws Exception {
        AutoCompleteDemo demo = new AutoCompleteDemo();

        demo.search("我爱大家");
        demo.search("我喜欢学习Redis");
        demo.search("我很喜欢一个城市");
        demo.search("我不太喜欢玩儿");
        demo.search("我喜欢学习Spark");

        Set<String> autoCompleteList = demo.getAutoCompleteList("我");
        System.out.println("第一次自动补全推荐:" + autoCompleteList);

        autoCompleteList = demo.getAutoCompleteList("我喜");
        System.out.println("第二次自动补全推荐:" + autoCompleteList);
    }
}

基于HyperLogLog的网站UV统计程序

hyperloglog,数据结构+概率算法,组合而成的,去重统计,近似数 1023468个用户来访问过你 1011325,算出来这样的一个近似值,对于很多大数据统计类的程序,精准也没太大的必要,CEO层面,100万的日活,如果基于set来计数,太耗费内存了,基于HyperLogLog算法来计数,是近似计数,有0.8%的误差,但是误差不会太大,可以给出一个相对准确的近似计数,而且就占用12kb的内存,不需要存一个set来统计uv。 pfadd key 一大堆item,可以对数据进行计数,如果计算过这个元素,就返回0,没计算过就返回1,他也是可以去重的,pfcount key,可以获取计数结果。 这个其实是很有用的,其实一些网站数据分析,什么pv、uv之类的,没必要过于精准,直接就是用redis的hyperloglog 计数就挺好的。

网站重复垃圾数据的快速去重和过滤

对于你的一些网站,垃圾数据,多的是一些不良广告,在一些论坛或者别的,有的时候你会看到一些在帖子下面的评论里,是一些广告,都有,可能会遇到有人频繁的提交相同的垃圾信息到你的站点里。 pfadd key content,如果返回的是1,那么说明之前没见过这条数据,如果返回的是0,说明之前见过这条数据了

周活跃用户数、月活跃用户数、年活跃用户数的统计

pfmerge 多个hyperloglog,可以算出来周、月和年的活跃用户数

基于位图的网站用户行为记录程序

如果说你要记录一下,在系统 里执行一些特殊的操作,每天执行过某个操作的用户有多少个人,操作日志,审计日志,记录下来每个用户每天做了哪些操作,每个用户每天搞一个set,里面放他做的一些操作日志,也可以对每种操作都搞一个set,里面放执行过这些操作的用户。 bitmap,位图,二进制里的一位一位的,字符串,int,long,double,二进制,都是对应多少多少位的,一个字节是8位的二进制数,int就是4个字节就是32位,你直接可以在redis里操作二进制的位数据。 可以把网站里每一种操作每天执行过的用户都放在一个位图里,一个用户仅仅对应了一位而已。

setbit key user_id 1
getbit key user_id
bitcount key

一个位图统计100万用户的行为,也不过一百多kb的内存。

基于GeoHash的你与商铺距离计算的程序

edis里还有一种特殊的数据结构,叫做Geo,可以让你在里面添加很多的地理位置,每个位置对应一个名称和一个经纬度,然后可以利用这个数据结构计算各种距离的位置,获取方圆半径多少1公里内的店铺

geoadd key longitude latitude user	116.49428833935545 39.86700462665782
geoadd key longitude latitude shop	116.45961274121092 39.87517301328063
geodist key user shop unit=’km’

陌生人社交APP里的查找附近的人功能实现

# 记录每个用户的地理位置
geoadd key longitude latitude user

# radius设置为1,就是附近1公里的用户都找出来,再从返回的set里删除user自己,就可以了
georadiusbymember key user radius unit=’km’

带有自动过期时间的分布式缓存实现

redis,我们之前已经讲解了他的一些数据结构,过期时间、pipeline、事务,过期时间,就是说你可以对一个key指定一个过期时间,到了时间之后,自动就让这个数据就过期了,不再生效了。

set key value
expire key timeout

支持超时自动释放的分布式锁实现

set key value ex=timeout nx=true

支持自动过期的用户登录会话实现


set key token ex=timeout

支持冷数据自动淘汰的自动补全程序

expire key timeout

支持身份验证功能的分布式锁释放机制

pipeline = pipeline() // 流水线里的命令,都是一次性打包发送执行的
    
    try {
        pipeline.watch(key)	
            // key如果有变化,后续提交事务直接失败,unwatch可以取消监控
            
            lock_value = pipeline.get(key)
            
            if(lock_value == null) {
                // 锁已经被释放了
            } else if(lock_value == user_id) {
                // 是自己加的锁,可以释放
                // 先用multi()打开一个事务,事务里的命令一起成功或者一起失败
                pipeline.multi()
                    pipeline.delete(key)
                    pipeline.execute() // 提交事务,discard可以放弃事务
                } else {
                // 不是自己加的锁,不能让你去释放
            }
    } catch() {
        // 如果有异常说明你提交事务的时候有人更新了key
    } finally {
        // 每个流水线都绑定了一个连接	
        // 执行完一个流水线,必须执行reset把连接还给连接池
        pipeline.reset()
        }

stream,发布/订阅,lua