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