Redis应用—9.简单应用汇总

大纲

1.基于Redis实现的简单缓存机制(String数据结构)

2.实现一个最简单的分布式锁(String数据结构)

3.博客网站的文章发布与查看(String数据结构)

4.博客字数统计与文章预览(String数据结构)

5.用户操作日志审计功能(String数据结构)

6.实现一个简单的唯一ID生成器(incr命令)

7.实现博客点赞次数计数器(incr命令 + decr命令)

8.社交网站的网址点击追踪机制(长网址转短网址)(Hash数据结构)

9.基于Hash实现博客基本功能(Hash数据结构)

10.基于令牌的用户登录会话机制(Hash数据结构)

11.秒杀活动下的公平队列抢购机制(List数据结构)

12.基于List实现博客的分页浏览(List数据结构)

13.实现OA系统中的待办事项管理(List数据结构)

14.网站用户注册时的邮件验证机制(List数据结构)

15.网站每日UV数据指标去重统计(Set数据结构)

16.博客网站的文章标签管理(Set数据结构)

17.朋友圈点赞功能的实现(Set数据结构)

18.实现一个网站投票统计程序(Set数据结构)

19.实现微博的社交关系(Set数据结构)

20.实现网站上的抽奖程序(Set数据结构)

21.为商品搜索构建反向索引(Set数据结构)

22.实现音乐网站的排行榜(Sorted Set数据结构)

23.实现获取指定时间的新闻(Sorted Set数据结构)

24.实现购买某商品时也会购买其他商品的推荐功能(Sorted Set数据结构)

25.实现搜索框的自动补全(Sorted Set数据结构)

26.基于HyperLogLog的网站UV统计程序(HyperLogLog数据结构)

27.网站重复垃圾数据的快速去重和过滤(HyperLogLog数据结构)

28.周活月活年活的统计(HyperLogLog数据结构)

29.基于位图的用户行为记录(BitMap数据结构)

30.基于Geo的距离计算程序(Geo数据结构)

31.陌生人社交里的查找附近的人(Geo数据结构)

32.带有自动过期时间的分布式缓存(expire命令)

33.支持超时自动释放的简单分布式锁(expire命令)

34.支持自动过期的用户登录会话实现(expire命令)

35.支持冷数据自动淘汰的自动补全(expire命令)

36.支持身份验证的分布式锁释放(pipeline命令)

1.基于Redis实现的简单缓存机制(String数据结构)

Redis里存放了大量的key-value对,可以先通过"set key value"命令往Redis里存放一些数据,然后再通过"get key"命令从Redis里获取这些数据。

基于Redis的缓存机制可以实现高并发和高性能。比如可以把复杂查询后的结果放入Redis里作为缓存,下次查询时就直接从Redis缓存里取出,无需再到数据库中查询复杂SQL。比如可以将HTML页面存放在Redis缓存里,下次查询时就直接从Redis缓存里获取,无需先基于JSP动态渲染页面,再从磁盘上读取出来返回给浏览器。

public class JedisTest {public static void main(String[] args) throws Exception {Jedis jedis = new Jedis("127.0.0.1");//最简单的缓存读写示例jedis.set("key1", "value1");System.out.println(jedis.get("key1"));}
}

2.实现一个最简单的分布式锁(String数据结构)

通过Redis的"set key value nx"命令可实现一个最简单的分布式锁。

当key不存在时,才能设置成功,返回true。当key存在时,会设置失败,返回false。

public class JedisTest {public static void main(String[] args) throws Exception {Jedis jedis = new Jedis("127.0.0.1");//最简单的基于nx选项实现的分布式锁jedis.del("lock_test");String result = jedis.set("lock_test", "value_test", SetParams.setParams().nx());System.out.println("第一次加锁的结果:" + result);result = jedis.set("lock_test", "value_test", SetParams.setParams().nx());System.out.println("第二次加锁的结果:" + result);jedis.del("lock_test");result = jedis.set("lock_test", "value_test", SetParams.setParams().nx());System.out.println("第二次加锁的结果:" + result);}
}

3.博客网站的文章发布与查看(String数据结构)

Redis有mset、mget、msetnx等命令。其中mset可一次性设置多个key-value对,mget可获取多个key的value,msetnx就是在多个key都不存在的情况下一次性设置多个key的value。mset和mget,相当于batch批量设置和查询,可节省网络通讯时间。

public class JedisTest {public static void main(String[] args) throws Exception {Jedis jedis = new Jedis("127.0.0.1");//博客的发布、修改与查看Long publishBlogResult = jedis.msetnx("article:1:title", "学习Redis","article:1:content", "如何学好Redis的使用","article:1:author", "Test","article:1:time", "2020-01-01 00:00:00");System.out.println("发布博客的结果:" + publishBlogResult);List<String> blog = jedis.mget("article:1:title", "article:1:content","article:1:author", "article:1:time");System.out.println("查看博客:" + blog);String updateBlogResult = jedis.mset("article:1:title", "修改后的学习redis","article:1:content", "修改后的如何学好redis的使用");System.out.println("修改博客的结果:" + updateBlogResult);blog = jedis.mget("article:1:title", "article:1:content","article:1:author", "article:1:time");System.out.println("再次查看博客:" + blog);}
}

4.博客字数统计与文章预览(String数据结构)

使用Redis的strlen命令可以统计value值的字数,getrange命令可以截取value值的内容。

public class JedisTest {public static void main(String[] args) throws Exception {Jedis jedis = new Jedis("127.0.0.1");Long blogLength = jedis.strlen("article:1:content");System.out.println("博客的长度统计:" + blogLength);String blogContentPreview = jedis.getrange("article:1:content", 0, 5);System.out.println("博客内容预览:" + blogContentPreview);}
}

5.用户操作日志审计功能(String数据结构)

使用Redis的append命令可以向value值追加内容,比如需要记录用户每天的核心操作日志,就可以使用Redis的append命令。

public class JedisTest {public static void main(String[] args) throws Exception {Jedis jedis = new Jedis("127.0.0.1");//操作日志的审计功能jedis.del("operation_log_2020_01_01");jedis.setnx("operation_log_2020_01_01", "");for (int i = 0; i < 10; i++) {jedis.append("operation_log_2020_01_01", "今天的第" + (i + 1) + "条操作日志\n");}String operationLog = jedis.get("operation_log_2020_01_01");System.out.println("今天所有的操作日志:\n" + operationLog);}
}

6.实现一个简单的唯一ID生成器(incr命令)

在单库单表中,唯一ID可以通过自增主键来生成。在分库分表中,唯一ID可以通过SnowFlake来生成。当然,Redis的incr命令也可以生成唯一ID。

public class JedisTest {public static void main(String[] args) throws Exception {Jedis jedis = new Jedis("127.0.0.1");//唯一ID生成器jedis.del("order_id_counter");for (int i = 0; i < 10; i++) {Long orderId = jedis.incr("order_id_counter");System.out.println("生成的第" + (i + 1) + "个唯一ID:" + orderId);}}
}

7.实现博客点赞次数计数器(incr命令 + decr命令)

使用Redis的incr命令和decr命令可维护点赞次数。

public class JedisTest {public static void main(String[] args) throws Exception {Jedis jedis = new Jedis("127.0.0.1");//博客的点赞计数器jedis.del("article:1:dianzan");for (int i = 0; i < 10; i++) {jedis.incr("article:1:dianzan");}Long dianzanCounter = Long.valueOf(jedis.get("article:1:dianzan"));System.out.println("博客的点赞次数为:" + dianzanCounter);jedis.decr("article:1:dianzan");dianzanCounter = Long.valueOf(jedis.get("article:1:dianzan"));System.out.println("再次查看博客的点赞次数为:" + dianzanCounter);}
}

8.社交网站的网址点击追踪机制(长网址转短网址)(Hash数据结构)

Redis的Hash数据结构可以实现网址点击追踪机制。需要对某个原地址进行追踪时:首先通过Redis的incr命令获取一个自增的10进制数。然后将10进制数转换为36进制数,并将转换后的36进制数作为短网址。接着通过hset命令设置短网址的点击次数为0,以及通过hset命令设置短网址和原地址的映射关系。当有用户访问该短网址时,就可以通过hincrBy命令对点击次数进行自增。

//短网址追踪案例
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 = new Jedis("127.0.0.1");public ShortUrlDemo() {jedis.set("short_url_seed", "51167890045");}//获取短网址public String getShortUrl(String url) {//通过Redis的incr命令获取一个自增的10进制数long shortUrlSeed = jedis.incr("short_url_seed");//将10进制数转换为36进制数,并将转换后的36进制数作为短网址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;}//增加短网址的访问次数public void incrementShortUrlAccessCount(String shortUrl) {jedis.hincrBy("short_url_access_count", shortUrl, 1);}//获取短网址的访问次数public long getShortUrlAccessCount(String shortUrl) {return Long.valueOf(jedis.hget("short_url_access_count", shortUrl));}public static void main(String[] args) throws Exception {ShortUrlDemo shortUrlDemo = new ShortUrlDemo();String shortUrl = shortUrlDemo.getShortUrl("http://redis.com/index.html");System.out.println("页面上展示的短网址为:" + shortUrl);//假设访问152次for (int i = 0; i < 152; i++) {shortUrlDemo.incrementShortUrlAccessCount(shortUrl);}long accessCount = shortUrlDemo.getShortUrlAccessCount(shortUrl);System.out.println("短网址被访问的次数为:" + accessCount);}
}

9.基于Hash实现博客基本功能(Hash数据结构)

Java对象特别适合使用Hash数据结构来存放到Redis中。如果先将Java对象序列化成字符串,再以字符串形式存放到Redis里,那么在Redis中操作Java对象就不太方便了。其中使用Redis的命令有:hexists、hset、hgetAll、hincrBy。

//博客网站案例
public class BlogDemo {private Jedis jedis = new Jedis("127.0.0.1");//获取博客idpublic 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.hset("article::" + id, blog);return true;}//查看一篇博客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.hset("article::" + id, updatedBlog);}//对博客进行点赞public void incrementBlogLikeCount(long id) {jedis.hincrBy("article::" + id, "like_count", 1);}//增加博客浏览次数public void incrementBlogViewCount(long id) {jedis.hincrBy("article::" + id, "view_count", 1);}public static void main(String[] args) {BlogDemo demo = new BlogDemo();//发表一篇博客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", "test");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);}
}

10.基于令牌的用户登录会话机制(Hash数据结构)

用户中心在处理用户请求时,会先检查请求是否有令牌及令牌是否合法,此时可以使用Redis的hset和hget命令来实现令牌对应的session检查。

//用户会话管理案例
public class SessionDemo {private Jedis jedis = new Jedis("127.0.0.1");//检查session是否有效public boolean isSessionValid(String token) throws Exception {//校验token是否为空if (token == null || "".equals(token)) {return false;}//假设session是一个value为user_id的json字符串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;}//模拟的登录方法public String login(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去初始化用户的sessioninitSession(userId, token);//返回这个令牌给用户return token;}//用户登录成功之后,初始化一个sessionpublic void initSession(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();//通过hset来设置sessionjedis.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();//第一次访问系统,token都是空的boolean isSessionValid = demo.isSessionValid(null);System.out.println("第一次访问系统的session校验结果:" + (isSessionValid == true ? "通过" : "不通过"));//强制性进行登录,获取到tokenString token = demo.login("test","123456");System.out.println("登录过后拿到令牌:" + token);//第二次再次访问系统,此时是可以访问的isSessionValid = demo.isSessionValid(token);System.out.println("第二次访问系统的session校验结果:" + (isSessionValid == true ? "通过" : "不通过"));}
}

11.秒杀活动下的公平队列抢购机制(List数据结构)

秒杀系统有很多实现方案,其中一种方案就是公平队列方案。对所有涌入系统的秒杀抢购请求,都放入Redis的一个List数据结构里排队,然后请求入队后就让请求等待秒杀结果。接着通过一个消费者从List中按顺序获取抢购请求,按顺序进行库存扣减。扣减成功才响应请求抢购成功,可以通过Redis的rpush和lpop命令实现一个公平队列。

//秒杀活动案例
public class SecKillDemo {private Jedis jedis = new Jedis("127.0.0.1");//秒杀抢购请求入队public void enqueueSecKillRequest(String secKillRequest) {jedis.rpush("sec_kill_request_queue", secKillRequest);}//秒杀抢购请求出队public String dequeueSecKillRequest() {return jedis.lpop("sec_kill_request_queue");}public static void main(String[] args) throws Exception {SecKillDemo demo = new SecKillDemo();for (int i = 0; i < 10; i++) {demo.enqueueSecKillRequest("第" + (i + 1) + "个秒杀请求");}while (true) {String secKillRequest = demo.dequeueSecKillRequest();if (secKillRequest == null || "null".equals(secKillRequest) || "".equals(secKillRequest)) {break;}System.out.println(secKillRequest);}}
}

12.基于List实现博客的分页浏览(List数据结构)

发表博客时就把博客数据lpush到一个List里,分页查询时就通过"lrange list start_index end_index"命令查询某一页的数据,获取所有数据量时就通过"llen list"命令来获取。

//博客网站案例
public class BlogDemo {private Jedis jedis = new Jedis("127.0.0.1");//获取博客idpublic 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.hset("article::" + id, blog);jedis.lpush("blog_list", String.valueOf(id));return true;}//查看一篇博客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.hset("article::" + id, updatedBlog);}//对博客进行点赞public void incrementBlogLikeCount(long id) {jedis.hincrBy("article::" + id, "like_count", 1);}//增加博客浏览次数public void incrementBlogViewCount(long id) {jedis.hincrBy("article::" + id, "view_count", 1);}//分页查询博客public List<String> findBlogByPage(int pageNo, int pageSize) {int startIndex = (pageNo - 1) * pageSize;int endIndex = pageNo * pageSize - 1;return jedis.lrange("blog_list", startIndex, endIndex);}public static void main(String[] args) {BlogDemo demo = new BlogDemo();//发表一篇博客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", "test");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);//构造20篇博客数据for (int i = 0; i < 20; i++) {id = demo.getBlogId();blog = new HashMap<String, String>();blog.put("id", String.valueOf(id));blog.put("title", "第" + (i + 1) + "篇博客");blog.put("content", "学习第" + (i + 1) + "篇博客,是一件很有意思的事情");blog.put("author", "test");blog.put("time", "2020-01-01 10:00:00");demo.publishBlog(id, blog);}//分页浏览所有的博客,先浏览第一页int pageNo = 1;int pageSize = 10;List<String> blogPage = demo.findBlogByPage(pageNo, pageSize);System.out.println("展示第一页的博客......");for (String blogId : blogPage) {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);}//点击查看博客的详细内容,并进行点赞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);}
}

13.实现OA系统中的待办事项管理(List数据结构)

使用Redis的lindex、lset、linsert、lrange、lrem可实现待办事项管理。

新增待办事项:lpush list event

插入待办事项:linsert list index event

查询待办事项列表:lrange list 0 -1

完成待办事项:lrem list 0 event

添加已办事项:lpush done_list event

修改待办事项:lindex和lset

查询已办事项列表:lrange done_list 0 -01

//OA系统的待办事项的管理案例
public class TodoEventDemo {private Jedis jedis = new Jedis("127.0.0.1");//添加待办事项public void addTodoEvent(long userId, String todoEvent) {jedis.lpush("todo_event::" + userId, todoEvent);}//分页查询待办事项列表public 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);}//插入待办事项public void insertTodoEvent(long userId, ListPosition position, String targetTodoEvent, String todoEvent) {jedis.linsert("todo_event::" + userId, position, targetTodoEvent, todoEvent);}//修改一个待办事项public void updateTodoEvent(long userId, int index, String updatedTodoEvent) {jedis.lset("todo_event::" + userId, index, updatedTodoEvent);}//完成(移除)一个待办事项public void finishTodoEvent(long userId, String todoEvent) {jedis.lrem("todo_event::" + userId, 0, todoEvent);}public static void main(String[] args) throws Exception {TodoEventDemo demo = new TodoEventDemo();//添加20个待办事项long userId = 2;for (int i = 0; i < 20; i++) {demo.addTodoEvent(userId, "第" + (i + 1) + "个待办事项");}//查询第一页待办事项int pageNo = 1;int pageSize = 10;List<String> todoEventPage = demo.findTodoEventByPage(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(userId, ListPosition.BEFORE, targetTodoEvent, "插入的待办事项");System.out.println("在" + targetTodoEvent + "前面插入了一个待办事项");//重新分页查询第一页待办事项todoEventPage = demo.findTodoEventByPage(userId, pageNo, pageSize);System.out.println("第二次查询第一页待办事项......");for (String todoEvent :todoEventPage) {System.out.println(todoEvent);}//修改一个待办事项index = random.nextInt(todoEventPage.size());demo.updateTodoEvent(userId, index, "修改后的待办事项");//完成一个待办事项demo.finishTodoEvent(userId, todoEventPage.get(0));//最后查询一次待办事项todoEventPage = demo.findTodoEventByPage(userId, pageNo, pageSize);System.out.println("第三次查询第一页待办事项......");for (String todoEvent :todoEventPage) {System.out.println(todoEvent);}}
}

14.网站用户注册时的邮件验证机制(List数据结构)

用户注册时使用Redis的lpush命令将发送邮件任务放入List,发送邮件时就用Redis的brpop命令阻塞式地从List队列里获取任务。

//注册之后发送邮件的案例
public class SendMailDemo {private Jedis jedis = new Jedis("127.0.0.1");//将发送邮件的任务进入队列public void enqueueSendMailTask(String sendMailTask) {jedis.lpush("send_mail_task_queue", sendMailTask);}//阻塞式获取发送邮件任务//即从List中获取不到元素会阻塞5秒public List<String> takeSendMailTask() {return jedis.brpop(5, "send_mail_task_queue");}public static void main(String[] args) {SendMailDemo demo = new SendMailDemo();System.out.println("尝试阻塞式地获取发送邮件任务......");//此时队列中没有任务,会进行阻塞List<String> sendMailTasks = demo.takeSendMailTask();demo.enqueueSendMailTask("第一个邮件发送任务");sendMailTasks = demo.takeSendMailTask();System.out.println(sendMailTasks);}
}

15.网站每日UV数据指标去重统计(Set数据结构)

每日UV就是每日的独立访客,可以使用Redis的Set数据结构来实现,具体可以使用Redis的sadd命令添加访客和scard命令统计访客。

//网站UV统计案例
public class UVDemo {private Jedis jedis = new Jedis("127.0.0.1");//添加一次用户访问记录public 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的值public long getUV() {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();for (int i = 0; i < 100; i++) {long userId = i + 1;for (int j = 0; j < 10; j++) {demo.addUserAccess(userId);}}long uv = demo.getUV();System.out.println("当日uv为:" + uv);}
}

16.博客网站的文章标签管理(Set数据结构)

使用Redis的sadd和smembers命令可管理标签。

//博客网站案例
public class BlogDemo {private Jedis jedis = new Jedis("127.0.0.1");//获取博客idpublic long getBlogId() {return jedis.incr("blog_id_counter");}//发表一篇博客public boolean publishBlog(long id, Map<String, String> blog, String[] tags) {if (jedis.hexists("article::" + id, "title")) {return false;}blog.put("content_length", String.valueOf(blog.get("content").length()));jedis.hmset("article::" + id, blog);jedis.lpush("blog_list", String.valueOf(id));jedis.sadd("article::" + id + "::tags", tags);return true;}//查看一篇博客public Map<String, String> findBlogById(long id) {Map<String, String> blog = jedis.hgetAll("article::" + id);Set<String> tags = jedis.smembers("article::" + id + "::tags");blog.put("tags", tags.toString());incrementBlogViewCount(id);return blog;}...
}

17.朋友圈点赞功能的实现(Set数据结构)

用户对某条朋友圈进行点赞,可以使用sadd命令。用户对某条朋友圈取消点赞,可以使用srem命令。查看某个用户是否对某条朋友圈进行过点赞,可以使用sismember命令。查看某条朋友圈具体被哪些人点赞,可以使用smembers命令。查看某条朋友圈的点赞次数,可以使用scard命令。

//朋友圈点赞案例
public class MomentsDemo {private Jedis jedis = new Jedis("127.0.0.1");//对某条朋友圈进行点赞public void likeMoment(long userId, long momentId) {jedis.sadd("moment_like_users::" + momentId, String.valueOf(userId));}//对某条朋友圈取消点赞public void dislikeMoment(long userId, long momentId) {jedis.srem("moment_like_users::" + momentId, String.valueOf(userId));}//查看自己是否对某条朋友圈点赞过public boolean hasLikedMoment(long userId, long momentId) {return jedis.sismember("moment_like_users::" + momentId, String.valueOf(userId));}//获取某条朋友圈有哪些人点赞了public Set<String> getMomentLikeUsers(long momentId) {return jedis.smembers("moment_like_users::" + momentId);}//获取某条朋友圈被几个人点赞了public long getMomentLikeUsersCount(long momentId) {return jedis.scard("moment_like_users::" + momentId);}public static void main(String[] args) throws Exception {MomentsDemo demo = new MomentsDemo();//用户idlong userId = 11;//朋友圈idlong momentId = 151;//朋友1的用户idlong friendId = 12;//朋友2的用户idlong otherFriendId = 13;//朋友1对你的朋友圈进行点赞,再取消点赞demo.likeMoment(friendId, momentId);demo.dislikeMoment(friendId, momentId);boolean hasLikedMoment = demo.hasLikedMoment(friendId, momentId);System.out.println("朋友1刷朋友圈,看到是否对自己的朋友圈点赞过:" + (hasLikedMoment ? "是" : "否"));//朋友2对你的朋友圈进行点赞demo.likeMoment(otherFriendId, momentId);hasLikedMoment = demo.hasLikedMoment(otherFriendId, momentId);System.out.println("朋友2刷朋友圈,看到是否对自己的朋友圈点赞过:" + (hasLikedMoment ? "是" : "否"));//查看自己的朋友圈的点赞情况Set<String> momentLikeUsers = demo.getMomentLikeUsers(momentId);long momentLikeUsersCount = demo.getMomentLikeUsersCount(momentId);System.out.println("自己刷朋友圈,看到自己发的朋友圈被" + momentLikeUsersCount + "个人点赞了,点赞的用户为:" + momentLikeUsers);}
}

18.实现一个网站投票统计程序(Set数据结构)

使用Redis的sadd和scard命令可实现投票功能。

//投票统计案例
public class VoteDemo {private Jedis jedis = new Jedis("127.0.0.1");//投票public void vote(long userId, long voteItemId) {jedis.sadd("vote_item_users::" + voteItemId, String.valueOf(userId));}//检查用户对投票项是否投过票public boolean hasVoted(long userId, long voteItemId) {return jedis.sismember("vote_item_users::" + voteItemId, String.valueOf(userId));}//获取一个投票项被哪些人投票public Set<String> getVoteItemUsers(long voteItemId) {return jedis.smembers("vote_item_users::" + voteItemId);}//获取一个投票项被多少人投票public long getVoteItemUsersCount(long voteItemId) {return jedis.scard("vote_item_users::" + voteItemId);}public static void main(String[] args) throws Exception {VoteDemo demo = new VoteDemo();//定义用户idlong userId = 1;//定义投票项idlong voteItemId = 110;//进行投票demo.vote(userId, voteItemId);//检查我是否投票过boolean hasVoted = demo.hasVoted(userId, voteItemId);System.out.println("用户查看自己是否投票过:" +(hasVoted ? "是" : "否"));//归票统计Set<String> voteItemUsers = demo.getVoteItemUsers(voteItemId);long voteItemUsersCount = demo.getVoteItemUsersCount(voteItemId);System.out.println("投票项有哪些人投票:" + voteItemUsers + ",有几个人投票:" + voteItemUsersCount);}
}

19.实现微博的社交关系(Set数据结构)

使用Redis的sadd命令和srem命令可以实现类似微博的关注和取关功能,使用Redis的sinter命令可以获取共同关注的人(两个Set取交集),使用Redis的sdiff命令可以获取没有关注的人(两个Set取差集)。

//微博案例
public class MicroBlogDemo {private Jedis jedis = new Jedis("127.0.0.1");//关注别人,userId关注followUserIdpublic void follow(long userId, long followUserId) {jedis.sadd("user::" + followUserId + "::followers", String.valueOf(userId));//关注者都有谁jedis.sadd("user::" + userId + "::follow_users", String.valueOf(followUserId));//都关注了谁}//取消关注别人public void unfollow(long userId, long followUserId) {jedis.srem("user::" + followUserId + "::followers", String.valueOf(userId));//关注者都有谁jedis.srem("user::" + userId + "::follow_users", String.valueOf(followUserId));//都关注了谁}//查看有哪些人关注了自己public Set<String> getFollowers(long userId) {return jedis.smembers("user::" + userId + "::followers");//关注者都有谁}//查看关注了自己的人数public long getFollowersCount(long userId) {return jedis.scard("user::" + userId + "::followers");//关注者都有谁}//查看自己关注了哪些人public Set<String> getFollowUsers(long userId) {return jedis.smembers("user::" + userId + "::follow_users");//都关注了谁}//查看自己关注的人数public long getFollowUsersCount(long userId) {return jedis.scard("user::" + userId + "::follow_users");//都关注了谁}//获取用户跟其他用户之间共同关注的人有哪些public Set<String> getSameFollowUsers(long userId, long otherUserId) {return jedis.sinter("user::" + userId + "::follow_users", "user::" + otherUserId + "::follow_users");}//获取otherUserId关注的,但userId没有关注的人,这些人可以推荐给userIdpublic Set<String> getRecommendFollowUsers(long userId, long otherUserId) {return jedis.sdiff("user::" + otherUserId + "::follow_users", "user::" + userId + "::follow_users");}public static void main(String[] args) throws Exception {MicroBlogDemo demo = new MicroBlogDemo();//定义用户idlong userId = 31;long friendId = 32;long superstarId = 33;long classmateId = 34;long motherId = 35;//定义关注的关系链demo.follow(userId, friendId);demo.follow(userId, motherId);demo.follow(userId, superstarId);demo.follow(friendId, superstarId);demo.follow(friendId, classmateId);//明星看看自己被哪些人关注了Set<String> superstarFollowers = demo.getFollowers(superstarId);long superstarFollowersCount = demo.getFollowersCount(superstarId);System.out.println("明星被哪些人关注了:" + superstarFollowers + ",被关注的人数为:" + superstarFollowersCount);//朋友看看自己被哪些人关注了,自己又关注了哪些人Set<String> friendFollowers = demo.getFollowers(friendId);long friendFollowersCount = demo.getFollowersCount(friendId);System.out.println("朋友被哪些人关注了:" + friendFollowers + ",被多少人关注了:" + friendFollowersCount);Set<String> friendFollowUsers = demo.getFollowUsers(friendId);long friendFollowUsersCount = demo.getFollowUsersCount(friendId);System.out.println("朋友关注了哪些人:" + friendFollowUsers + ",关注了多少人:" + friendFollowUsersCount);//查看我关注了哪些人Set<String> myFollowUsers = demo.getFollowUsers(userId);long myFollowUsersCount = demo.getFollowUsersCount(userId);System.out.println("我关注了哪些人:" + myFollowUsers + ", 我关注的人数:" + myFollowUsersCount);//获取我和朋友共同关注的用户Set<String> sameFollowUsers = demo.getSameFollowUsers(userId, friendId);System.out.println("我和朋友共同关注的人有哪些:" + sameFollowUsers);//获取可以推荐给我关注的人Set<String> recommendFollowUsers = demo.getRecommendFollowUsers(userId, friendId);System.out.println("推荐给我关注的人有哪些:" + recommendFollowUsers);}
}

20.实现网站上的抽奖程序(Set数据结构)

使用Redis的sadd命令和srandmember命令可以实现抽奖功能。

//抽奖案例
public class LotteryDrawDemo {private Jedis jedis = new Jedis("127.0.0.1");//添加抽奖候选人public void addLotteryDrawCandidate(long userId, long lotteryDrawEventId) {jedis.sadd("lottery_draw_event::" + lotteryDrawEventId +"::candidates", String.valueOf(userId));}//实际进行抽奖public List<String> doLotteryDraw(long lotteryDrawEventId, int count) {return jedis.srandmember("lottery_draw_event::" + lotteryDrawEventId +"::candidates", count);}public static void main(String[] args) throws Exception {LotteryDrawDemo demo = new LotteryDrawDemo();int lotteryDrawEventId = 120;for (int i = 0; i < 20; i++) {demo.addLotteryDrawCandidate(i + 1, lotteryDrawEventId);}List<String> lotteryDrawUsers = demo.doLotteryDraw(lotteryDrawEventId, 3);System.out.println("获奖人选为:" + lotteryDrawUsers);}
}

21.为商品搜索构建反向索引(Set数据结构)

使用Redis的sadd命令和sinter命令可以为商品搜索构建反向索引。

//商品搜索案例
public class ProductSearchDemo {private Jedis jedis = new Jedis("127.0.0.1");//添加商品的时候附带一些关键词public void addProduct(long productId, String[] keywords) {for (String keyword : keywords) {jedis.sadd("keyword::" + keyword + "::products", String.valueOf(productId));}}//根据多个关键词搜索商品public Set<String> searchProduct(String[] keywords) {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 {ProductSearchDemo demo = new ProductSearchDemo();//添加一批商品demo.addProduct(11, new String[]{"手机", "iphone", "潮流"});demo.addProduct(12, new String[]{"iphone", "潮流", "炫酷"});demo.addProduct(13, new String[]{"iphone", "天蓝色"});//根据关键词搜索商品Set<String> searchResult = demo.searchProduct(new String[]{"iphone", "潮流"});System.out.println("商品搜索结果为:" + searchResult);}
}

22.实现音乐网站的排行榜(Sorted Set数据结构)

Redis的Sorted Set里的元素会按照分数进行排序。使用zadd命令可以把音乐加入排行榜中(刚开始分数可能就是0),使用zscore命令可以获取音乐的分数,使用zrem命令可以删除某首音乐,使用zincrby命令可以给某首音乐增加分数(比如播放、分享、点赞时),使用zrevrank命令可以获取音乐在排行榜里的排名,使用zrevrange set 0 100 withscores(可以获取排名前100首热门歌曲)。

//音乐排行榜案例
public class MusicRankingListDemo {private Jedis jedis = new Jedis("127.0.0.1");//把新的音乐加入到排行榜里去public void addSong(long songId) {jedis.zadd("music_ranking_list", 0, String.valueOf(songId));}//增加歌曲的分数public void incrementSongScore(long songId, double score) {jedis.zincrby("music_ranking_list", score, String.valueOf(songId));}//获取歌曲在排行榜里的排名public long getSongRank(long songId) {return jedis.zrevrank("music_ranking_list", String.valueOf(songId));}//获取音乐排行榜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);}
}

23.实现获取指定时间的新闻(Sorted Set数据结构)

可以对Redis里的Sorted Set的数据进行倒序排序,然后可以选择其中指定的分数区间范围内的数据,并进行分页查询。

可以维护一个新闻数据集合,里面的分数都是新闻的时间戳。使用zadd命令可以把当日最新的新闻加入到一个集合里,使用zrem命令可以删除某个新闻,使用zcard命令可以统计当日最新新闻,使用zrevrangeByScoreWithScores命令可以按时间分数进行倒序排序,使用zcount命令可以获取指定分数范围的数量。

//新闻浏览案例
public class NewsDemo {private Jedis jedis = new Jedis("127.0.0.1");//加入一篇新闻public void addNews(long newsId, long timestamp) {jedis.zadd("news", timestamp, String.valueOf(newsId));}//搜索新闻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);}
}

24.实现购买某商品时也会购买其他商品的推荐功能(Sorted Set数据结构)

用户购买两个商品时,其中一个商品可以作为key,另一个商品作为value,商品的购买数量作为score,设置到Redis的Sorted Set数据结构中,这样后续就可以根据score分数来获取购买某商品时应该推荐那些商品,对应的Redis命令是zincrby命令和zrevrangeWithScores命令。

//推荐其他商品案例
public class RecommendProductDemo {private Jedis jedis = new Jedis("127.0.0.1");//购买商品productId的同时,也购买了商品otherProductIdpublic void continuePurchase(long productId, long otherProductId) {jedis.zincrby("continue_purchase_products::" + productId, 1, String.valueOf(otherProductId));}//推荐其他人购买productId时也会购买的其他商品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);}
}

25.实现搜索框的自动补全(Sorted Set数据结构)

使用zincrby命令和zrevrange命令可以实现搜索框的自动补全功能。每次搜索时,key为潜在搜索词,value为完整搜索词,score为当前时间。

//自动补全案例
public class AutoCompleteDemo {private Jedis jedis = new Jedis("127.0.0.1");//搜索某个关键词public void search(String keyword) {char[] keywordCharArray = keyword.toCharArray();StringBuffer potentialKeyword = new StringBuffer("");for (char keywordChar : keywordCharArray) {potentialKeyword.append(keywordChar);//key为潜在搜索词,value为完整搜索词,score为当前时间jedis.zincrby("potential_Keyword::" + potentialKeyword.toString() + "::keywords", new Date().getTime(), keyword);}}//获取自动补全列表//按照score排序,就可以获取最新搜索的搜索词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);}
}

26.基于HyperLogLog的网站UV统计程序(HyperLogLog数据结构)

Redis的HyperLogLog可以得到去重统计的近似数。如果基于Redis的set来统计UV,则太耗费内存了,而且也没必要太精准。如果基于HyperLogLog来统计UV,则只占12KB内存,且误差只有0.8%。

使用Redis的HyperLogLog的具体方式是:先通过pfadd命令对数据进行计数,再通过pfcount命令获取计数结果。

//基于HyperLogLog统计UV的案例
public class HyperLogLogUVDemo {private Jedis jedis = new Jedis("127.0.0.1");//初始化uv数据public void initUVData() {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");String today = dateFormat.format(new Date());for (int i = 0; i < 1358; i++) {for (int j = 0; j < 10; j++) {jedis.pfadd("hyperloglog_uv_" + today, String.valueOf((i + 1)));}}}//获取uv值public long getUV() {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");String today = dateFormat.format(new Date());return jedis.pfcount("hyperloglog_uv_" + today);}public static void main(String[] args) throws Exception {HyperLogLogUVDemo demo = new HyperLogLogUVDemo();demo.initUVData();long uv = demo.getUV();System.out.println("今天uv的值是:" + uv);}
}

27.网站重复垃圾数据的快速去重和过滤(HyperLogLog数据结构)

可以使用Redis的HyperLogLog对垃圾数据进行快速过滤,比如通过执行"pfadd key content"命令。如果返回的是1,那么说明之前没见过这条数据。如果返回的是0,说明之前见过这条数据了。

//垃圾内容过滤案例
public class GarbageContentFilterDemo {private Jedis jedis = new Jedis("127.0.0.1");//判断当前内容是否是垃圾内容public Boolean isGarbageContent(String content) {return jedis.pfadd("hyperloglog_contennt", content) == 0;}public static void main(String[] args) {GarbageContentFilterDemo demo = new GarbageContentFilterDemo();String content = "正常的内容";System.out.println("是否为垃圾内容:" + (demo.isGarbageContent(content) ? "是" : "否"));content = "垃圾内容";System.out.println("是否为垃圾内容:" + (demo.isGarbageContent(content) ? "是" : "否"));content = "垃圾内容";System.out.println("是否为垃圾内容:" + (demo.isGarbageContent(content) ? "是" : "否"));}
}

28.周活月活年活的统计(HyperLogLog数据结构)

首先使用pfadd命令对每天日活进行计数,然后再通过pfcount命令获取每天日活结果,周活跃用户数和年活跃用户数都可以基于日活跃用户数来进行统计。

//网站日常指标统计案例
public class WebsiteStatisticsDemo {private Jedis jedis = new Jedis("127.0.0.1");//初始化某一天的uv数据public void initUVData(String date) {Random random = new Random();int startIndex = random.nextInt(1000);System.out.println("今日访问uv起始id为:" + startIndex);for (int i = startIndex; i < startIndex + 1358; i++) {for (int j = 0; j < 10; j++) {jedis.pfadd("hyperloglog_uv_" + date, String.valueOf((i + 1)));}}}//获取某日的uv值public long getUV(String date) {return jedis.pfcount("hyperloglog_uv_" + date);}//获取周活跃用户数public long getWeeklyUV() {List<String> keys = new ArrayList<String>();SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");Calendar calendar = Calendar.getInstance();calendar.setTime(new Date());for (int i = 0; i < 7; i++) {calendar.add(Calendar.DAY_OF_YEAR, 1);String date = dateFormat.format(calendar.getTime());keys.add("hyperloglog_uv_" + date);}String[] keyArray = keys.toArray(new String[keys.size()]);jedis.pfmerge("weekly_uv", keyArray);return jedis.pfcount("weekly_uv");}public static void main(String[] args) throws Exception {WebsiteStatisticsDemo demo = new WebsiteStatisticsDemo();SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");Calendar calendar = Calendar.getInstance();calendar.setTime(new Date());long duplicateUv = 0;for (int i = 0; i < 7; i++) {calendar.add(Calendar.DAY_OF_YEAR, 1);String date = dateFormat.format(calendar.getTime());demo.initUVData(date);long uv = demo.getUV(date);System.out.println("日期为" + date + "的uv值为:" + uv);duplicateUv += uv;}long weeklyUV = demo.getWeeklyUV();System.out.println("实际的周活跃用户数为:" + weeklyUV);}
}

29.基于位图的用户行为记录(BitMap数据结构)

Redis的位图命令setbit:一个字节(byte)8个位(bit),每个位是0或者1。"setbit key offset value",指将左起向右偏移offset的位置设置value。

如果用户系统需要统计某用户登录天数,且统计窗口随机。那么这时可这样设计:以用户ID为位图的key,登录日期天数作为偏移量。

所以这个key是有365个位的。用户在某一天登录了,就在这个key上对应的偏移量设1。用户user1在第2天登录了,可以设置:"setbit user1 1 1"。用户user1在第8天登录了,可以设置:"setbit user1 7 1"。用户user1在第365天登录了,可以设置:"setbit user1 364 1"。这样一个用户总共就只需要46个字节而已,1亿用户 * 46byte = 4G。查看用户最后两周是否登录,可以执行:"bitcount user1 -2 -1"。

如果要把活跃用户统计出来,比如1号~3号连续登录要去重。那么可以这样设计:以日期为位图的key,用户ID作为偏移量。固定好对应于二进制位的那一个位,id和二进制位做好映射。

比如1号用户在2020年1月1号登录了,那么"setbit 20200101 1 1"。比如1号用户在2020年1月2号登录了,那么"setbit 20200102 1 1"。比如7号用户在2020年1月2号登录了,那么"setbit 20200102 7 1"。因此可以通过位或算出2020年1月2号到2020年1月2号的活跃用户数是:"bitop or destkey 20200101 20200102"。然后获取destkey有多少个1即可:"bitcount destkey 0 -1"。

如果要记录用户是否执行过某些操作,也可以使用位图来实现高效记录。此时可这样设计:以操作类型为位图的key,用户ID作为位图的偏移量。

//网站用户操作日志案例
public class UserOperationLogDemo {private Jedis jedis = new Jedis("127.0.0.1");//记录用户的操作日志public void recordUserOperationLog(String operation, long userId) {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");String today = dateFormat.format(new Date());jedis.setbit("operation::" + operation + "::" + today + "::log", userId, String.valueOf(1));}//判断用户今天是否执行过某个操作public Boolean hasOperated(String operation, long userId) {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");String today = dateFormat.format(new Date());return jedis.getbit("operation::" + operation + "::" + today + "::log", userId);}public static void main(String[] args) {UserOperationLogDemo demo = new UserOperationLogDemo();demo.recordUserOperationLog("操作1", 110);System.out.println("用户110是否执行过操作:" + (demo.hasOperated("操作1", 110) ? "是" : "否"));System.out.println("用户111是否执行过操作:" + (demo.hasOperated("操作1", 111) ? "是" : "否"));}
}

30.基于Geo的距离计算程序(Geo数据结构)

使用Redis的geoadd命令和geodist命令可以计算两个经纬度的距离。

//用户与商家的距离计算案例
public class UserShopDistanceDemo {private Jedis jedis = new Jedis("127.0.0.1");//添加一个地理位置public void addLocation(String name, double longitude, double latitude) {jedis.geoadd("location_data", longitude, latitude, name);}//获取用户到商家的位置public double getDistance(String user, String shop) {return jedis.geodist("location_data", user, shop, GeoUnit.KM);}public static void main(String[] args) {UserShopDistanceDemo demo = new UserShopDistanceDemo();demo.addLocation("张三", 116.49428833935545, 39.86700462665782);demo.addLocation("丫丫小吃店", 116.45961274121092, 39.87517301328063);System.out.println("用户到商家的距离为:" + demo.getDistance("张三", "丫丫小吃店"));}
}

31.陌生人社交里的查找附近的人(Geo数据结构)

使用Redis的georadiusByMember命令可以查找某位置附近的位置。

//查找附近的人案例
public class NearbyShopsDemo {private Jedis jedis = new Jedis("127.0.0.1");//添加一个地理位置public void addLocation(String name, double longitude, double latitude) {jedis.geoadd("location_data", longitude, latitude, name);}//查找附近5公里内的店铺public List<GeoRadiusResponse> getNearbyShops() {return jedis.georadiusByMember("location_data", "张三", 5.0, GeoUnit.KM);}public static void main(String[] args) {NearbyShopsDemo demo = new NearbyShopsDemo();List<String> nearbyShops = new ArrayList<String>();List<GeoRadiusResponse> results = demo.getNearbyShops();for (GeoRadiusResponse result : results) {String name = result.getMemberByString();if (!name.equals("张三")) {nearbyShops.add(name);}}System.out.println("附近5公里内的商家:" + nearbyShops);}
}

32.带有自动过期时间的分布式缓存(expire命令)

其实就是使用Redis的expire命令设置过期时间。

//数据自动过期的案例
public class ExpireDemo {private static Jedis jedis = new Jedis("127.0.0.1");public static void main(String[] args) throws Exception {jedis.set("test_key", "test_value");jedis.expire("test_key", 10);Thread.sleep(12 * 1000);String testValue = jedis.get("test_key");System.out.println("数据是否过期:" + (testValue == null || "null".equals(testValue) ? "是" : "否"));}
}

33.支持超时自动释放的简单分布式锁(expire命令)

其实就是使用Redis的setnx命令和expire命令设置锁及其过期时间。

//支持超时自动释放的简单分布式锁案例
public class TimeoutDistributedLockDemo {private static Jedis jedis = new Jedis("127.0.0.1");//加锁public Boolean lock(String key, String value, int timeout) {long result = jedis.setnx(key, value);jedis.expire(key, timeout);return result > 0;}public static void main(String[] args) throws Exception {TimeoutDistributedLockDemo demo = new TimeoutDistributedLockDemo();demo.lock("test_lock", "test_value", 10);Thread.sleep(12 * 1000);Boolean result = demo.lock("test_lock", "test_value", 10);System.out.println("第二次加锁结果:" + (result ? "成功" : "失败"));}
}

34.支持自动过期的用户登录会话实现(expire命令)

通过expire命令设置过期时间让用户登录自动过期。

//用户会话管理案例
public class SessionDemo {private Jedis jedis = new Jedis("127.0.0.1");//检查session是否有效public boolean isSessionValid(String token) throws Exception {//校验token是否为空if (token == null || "".equals(token)) {return false;}String session = jedis.get( "session::" + token);if (session == null || "".equals(session) || "null".equals(session)) {return false;}//如果token不为空,且获取到的Session不为空,且Session没过期,此时可以认为Session在有效期内return true;}//模拟的登录方法public String login(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去初始化用户的Sessionjedis.set("session::" + token, String.valueOf(userId));jedis.expire("session::" + token, 10);//返回这个令牌给用户return token;}public static void main(String[] args) throws Exception {SessionDemo demo = new SessionDemo();//第一次访问系统,token都是空的boolean isSessionValid = demo.isSessionValid(null);System.out.println("第一次访问系统的Session校验结果:" + (isSessionValid == true ? "通过" : "不通过"));//强制性进行登录,获取到tokenString token = demo.login("test","123456");System.out.println("登陆过后拿到令牌:" + token);//第二次再次访问系统,此时是可以访问的isSessionValid = demo.isSessionValid(token);System.out.println("第二次访问系统的Session校验结果:" + (isSessionValid == true ? "通过" : "不通过"));Thread.sleep(12 * 1000);//第三次再次访问系统,Session已过期,此时是不可以访问的isSessionValid = demo.isSessionValid(token);System.out.println("第三次访问系统的Session校验结果:" + (isSessionValid == true ? "通过" : "不通过"));}
}

35.支持冷数据自动淘汰的自动补全(expire命令)

通过expire命令设置过期时间让冷数据自动淘汰实现冷热分离。

//自动补全案例
public class AutoCompleteDemo {private Jedis jedis = new Jedis("127.0.0.1");//搜索某个关键词public void search(String keyword) {char[] keywordCharArray = keyword.toCharArray();StringBuffer potentialKeyword = new StringBuffer("");for (char keywordChar : keywordCharArray) {potentialKeyword.append(keywordChar);jedis.zincrby("potential_Keyword::" + potentialKeyword.toString() + "::keywords", new Date().getTime(), keyword);//通过设置过期时间,冷数据就会自动淘汰jedis.expire("potential_Keyword::" + potentialKeyword.toString() + "::keywords", 10);}}//获取自动补全列表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);Thread.sleep(12 * 1000);autoCompleteList = demo.getAutoCompleteList("我");System.out.println("第三次自动补全推荐:" + autoCompleteList);}
}

36.支持身份验证的分布式锁释放(pipeline命令)

通过Redis的pipeline命令和事务操作可以保证多个命令在一个事务内全部完成,从而减少多次网络请求带来的开销。

pipeline的watch命令可以在事务开始执行前对所要操作的key执行监测,从而保证了事务的完整性和一致性。因此,为了防止锁篡改,可以在加锁完成之后对锁进行watch操作,一旦锁发生变化,则终止事务,回滚操作。

//支持超时自动释放的分布式锁案例
public class TimeoutDistributedLockDemo {private static Jedis jedis = new Jedis("127.0.0.1");//加锁public Boolean lock(String key, String value, int timeout) {long result = jedis.setnx(key, value);jedis.expire(key, timeout);return result > 0;}//释放锁,需要判断是否是当前线程加的锁,才可以释放//key是锁的名称,value是线程名称public Boolean unlock(String key, String value) {String currentValue = jedis.get(key);Pipeline pipeline = jedis.pipelined();try {pipeline.watch(key);if (currentValue == null || currentValue.equals("") || currentValue.equals("null")) {return true;}if (currentValue.equals(value)) {//通过pipeline.multi()命令开启事务pipeline.multi();pipeline.del(key);//通过pipeline.exec()提交事务pipeline.exec();return true;} else {return false;}} catch (Exception e) {e.printStackTrace();return false;} finally {pipeline.unwatch();pipeline.close();}}public static void main(String[] args) throws Exception {TimeoutDistributedLockDemo demo = new TimeoutDistributedLockDemo();demo.lock("test_lock", "test_value", 10);Thread.sleep(12 * 1000);Boolean result = demo.lock("test_lock", "test_value", 10);System.out.println("第二次加锁结果:" + (result ? "成功" : "失败"));System.out.println("不是加锁线程能否释放锁:" + (demo.unlock("test_lock", "非加锁线程的名称") ? "能" : "否"));System.out.println("加锁线程能否释放锁:" + (demo.unlock("test_lock", "加锁线程的名称") ? "能" : "否"));}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/493479.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络之多路转接epoll

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络之多路转接epoll 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目…

多个Echart遍历生成 / 词图云

echart官网 安装 如果版本报错推荐安装以下版本 npm install echarts4.8.0 --savenpm uninstall echarts//这个是卸载命令以下安装成功后是局部引入:多个Echart遍历生成 vue3echart单个页面多个图表循环渲染展示:<template><div class"main"><div …

Windows server 服务器网络安全管理之防火墙出站规则设置

Windows server 服务器网络安全管理之防火墙出站规则设置 创建一条出站规则 这条出站规则针对IE浏览器设置&#xff0c;指定路径 TCP协议和指定端口&#xff08;多个端口的写法要注意&#xff09; 所有IP&#xff0c;所有应用&#xff0c;都采用阻止 给这条规则进行命名…

jmeter 接口性能测试 学习笔记

目录 说明工具准备工具配置jmeter 界面汉化配置汉化步骤汉化结果图 案例1&#xff1a;测试接口接口准备线程组添加线程组配置线程组值线程数&#xff08;Number of Threads&#xff09;Ramp-Up 时间&#xff08;Ramp-Up Period&#xff09;循环次数&#xff08;Loop Count&…

Pytorch | 从零构建ResNet对CIFAR10进行分类

Pytorch | 从零构建ResNet对CIFAR10进行分类 CIFAR10数据集ResNet核心思想网络结构创新点优点应用 ResNet结构代码详解结构代码代码详解BasicBlock 类ResNet 类ResNet18、ResNet34、ResNet50、ResNet101、ResNet152函数 训练过程和测试结果代码汇总resnet.pytrain.pytest.py 前…

gpu硬件架构

1.简介 NVIDIA在视觉计算和人工智能&#xff08;AI&#xff09;领域处于领先地位&#xff1b;其旗舰GPU已成为解决包括高性能计算和人工智能在内的各个领域复杂计算挑战所不可或缺的。虽然它们的规格经常被讨论&#xff0c;但很难掌握各种组件的清晰完整的图景。 这些GPU的高性…

Java中的方法重写:深入解析与最佳实践

在Java编程中&#xff0c;方法重写&#xff08;Method Overriding&#xff09;是面向对象编程&#xff08;OOP&#xff09;的核心概念之一。它允许子类提供一个与父类中同名方法的具体实现&#xff0c;从而实现多态性&#xff08;Polymorphism&#xff09;。本文将深入探讨Java…

使用vcpkg安装opencv>=4.9后#include<opencv2/opencv.hpp>#include<opencv2/core.hpp>无效

使用vcpkg安装opencv>4.9后#include<opencv2/opencv.hpp>#include<opencv2/core.hpp>无效\无法查找或打开 至少从2024年开始&#xff0c;发布的vcpkg默认安装的opencv版本都是4.x版。4.8版本及以前&#xff0c;vcpkg编译后的opencv头文件目录是*/vcpkg/x64-win…

基于java web在线商城购物系统源码+论文

一、环境信息 开发语言&#xff1a;JAVA JDK版本&#xff1a;JDK8及以上 数据库&#xff1a;MySql5.6及以上 Maven版本&#xff1a;任意版本 操作系统&#xff1a;Windows、macOS 开发工具&#xff1a;Idea、Eclipse、MyEclipse 开发框架&#xff1a;SpringbootHTMLjQueryMysq…

基于字节大模型的论文翻译(含免费源码)

基于字节大模型的论文翻译 源代码&#xff1a; &#x1f44f; star ✨ https://github.com/boots-coder/LLM-application 展示 项目简介 本项目是一个基于大语言模型&#xff08;Large Language Model, LLM&#xff09;的论文阅读与翻译辅助工具。它通过用户界面&#xff08…

centos7下docker 容器实现redis主从同步

1.下载redis 镜像 docker pull bitnami/redis2. 文件夹授权 此文件夹是 你自己映射到宿主机上的挂载目录 chmod 777 /app/rd13.创建docker网络 docker network create mynet4.运行docker 镜像 安装redis的master -e 是设置环境变量值 docker run -d -p 6379:6379 \ -v /a…

实现 WebSocket 接入文心一言

目录 什么是 WebSocket&#xff1f; 为什么需要 WebSocket&#xff1f; HTTP 的局限性 WebSocket 的优势 总结&#xff1a;HTTP 和 WebSocket 的区别 WebSocket 的劣势 WebSocket 常见应用场景 WebSocket 握手过程 WebSocket 事件处理和生命周期 WebSocket 心跳机制 …

机动车油耗计算API集成指南

机动车油耗计算API集成指南 引言 在当今社会&#xff0c;随着机动车数量的持续增长和环保意识的不断增强&#xff0c;如何有效管理和降低车辆油耗成为了车主、车队管理者以及交通政策制定者共同关注的问题。为了帮助这些群体更好地理解和优化燃油消耗情况&#xff0c;本接口能…

前端yarn工具打包时网络连接问题排查与解决

最近线上前端打包时提示 “There appears to be trouble with your network connection”&#xff0c;以此文档记录下排查过程。 前端打包方式 docker启动临时容器打包&#xff0c;命令如下 docker run --rm -w /app -v pwd:/app alpine-node-common:v16.20-pro sh -c "…

IIC I2C子协议 SMBus协议 通信协议原理 时序 SMBus深度剖析

引言&#xff1a;系统管理总线&#xff08;SMBus&#xff09;是一种双线接口&#xff0c;通过该接口&#xff0c;各种系统组件芯片和设备可以相互以及与系统其他部分通信。它基于IC总线的操作原理。附录B提供了一些SMBus特性与IC总线不同的方式的描述。 SMBus为系统和电源管理相…

【Lua热更新】上篇

Lua 热更新 - 上篇 下篇链接&#xff1a;【Lua热更新】下篇 文章目录 Lua 热更新 - 上篇一、AssetBundle1.理论2. AB包资源加载 二、Lua 语法1. 简单数据类型2.字符串操作3.运算符4.条件分支语句5.循环语句6.函数7. table数组8.迭代器遍历9.复杂数据类型 - 表9.1字典9.2类9.3…

React图标库: 使用React Icons实现定制化图标效果

React图标库: 使用React Icons实现定制化图标效果 图标库介绍 是一个专门为React应用设计的图标库&#xff0c;它包含了丰富的图标集合&#xff0c;覆盖了常用的图标类型&#xff0c;如FontAwesome、Material Design等。React Icons可以让开发者在React应用中轻松地添加、定制各…

如何使用 WebAssembly 扩展后端应用

1. WebAssembly 简介 随着互联网的发展&#xff0c;越来越多的应用借助 Javascript 转到了 Web 端&#xff0c;但人们也发现&#xff0c;随着移动互联网的兴起&#xff0c;需要把大量的应用迁移到手机端&#xff0c;随着手端的应用逻辑越来越复杂&#xff0c;Javascript 的解析…

Fastdfs V6.12.1集群部署(arm/x86均可用)

文章目录 一、 Fastdfs 介绍二、部署 信息三、步骤tracker/storage 机器的 compose 内容storage 机器的 composetracker 与 storage 启动目录层级与配置文件测试测试集群扩容与缩减注意事项 一、 Fastdfs 介绍 FastDFS 是一款高性能的分布式文件系统&#xff0c;特别适合用于存…

maven-resources-production:ratel-fast: java.lang.IndexOutOfBoundsException

Maven生产环境中遇到java.lang.IndexOutOfBoundsException的问题&#xff0c;尝试了重启电脑、重启IDEA等常规方法无效&#xff0c;最终通过直接重建工程解决了问题。 Rebuild Project 再启动OK