Redis practice - 02. Redis simple practice - article voting

Man Fu Zhu Ji 2021-01-21 23:25:25
redis practice redis simple practice


demand

function : P15
  • Publish articles
  • Access to the article
  • Group articles
  • Vote for it
Numerical values and constraints P15
  1. If an article gets at least 200 Support tickets , So this is an interesting article
  2. If this website has 50 An interesting article , So the website should put this 50 Put an article in front of the article list page 100 At least one day
  3. Support article rating ( A vote for support will add to the score ), And the score decreases with time

Realization

Vote for it P15

If you want to realize the real-time score decreasing with time , And support ranking by score , So it's a lot of work and it's not accurate . You can imagine that only the timestamp will change in real time over time , If we take the time stamp of the post as the initial rating , Then the initial score of the post will be higher , On another level, the score decreases with time . According to every interesting article every day 200 One vote of support , On average, one day (86400 second ) in , Each ticket can raise the score 432 branch .

In order to get articles by rating and chronological order , Need articles id And the corresponding information exists in two ordered sets , Respectively :postTime and score .

In order to prevent unified users from voting for unified articles many times , You need to record who voted for each article id, Store in a collection , by :votedUser:{articleId} .

At the same time, it is stipulated that an article cannot be voted one week after its publication period , The score will be fixed , At the same time, the user list collection that records the votes of articles will also be deleted .

// redis key
type RedisKey string
const (
// Release time Ordered set
POST_TIME RedisKey = "postTime"
// Article rating Ordered set
SCORE RedisKey = "score"
// Article voting user set prefix
VOTED_USER_PREFIX RedisKey = "votedUser:"
// Number of published articles character string
ARTICLE_COUNT RedisKey = "articleCount"
// Publish article hash table prefix
ARTICLE_PREFIX RedisKey = "article:"
// Group prefix
GROUP_PREFIX RedisKey = "group:"
)
const ONE_WEEK_SECONDS = int64(7 * 24 * 60 * 60)
const UPVOTE_SCORE = 432
// user userId To the article articleId Vote for ( No transaction control , The first 4 Chapter will introduce Redis Business )
func UpvoteArticle(conn redis.Conn, userId int, articleId int) {
// Calculate the earliest publishing time of articles that can be voted at the current time
earliestPostTime := time.Now().Unix() - ONE_WEEK_SECONDS
// obtain Current article The release time of
postTime, err := redis.Int64(conn.Do("ZSCORE", POST_TIME, articleId))
// Get error or article articleId The deadline for voting has passed , Then return to
if err != nil || postTime < earliestPostTime {
return
}
// The current article can vote , Then vote
votedUserKey := VOTED_USER_PREFIX + RedisKey(strconv.Itoa(articleId))
addedNum, err := redis.Int(conn.Do("SADD", votedUserKey, userId))
// Add error or At present, we have voted , Then return to
if err != nil || addedNum == 0 {
return
}
// The user has been successfully added to the voting set of the current article , Increase Current article score
_, err = conn.Do("ZINCRBY", SCORE, UPVOTE_SCORE, articleId)
// Self increasing error , Then return to
if err != nil {
return
}
// increase Current article Support votes
articleKey := ARTICLE_PREFIX + RedisKey(strconv.Itoa(articleId))
_, err = conn.Do("HINCRBY", articleKey, 1)
// Self increasing error , Then return to
if err != nil {
return
}
}
Publish articles P17

have access to INCR The command generates a self incrementing unique for each article id .

The publisher's userId Recorded in the voting user set of this article ( That is, the publisher votes for himself by default ), At the same time, set the expiration time to one week .

Store information about articles , And record the initial score and release time .

// Publish articles ( No transaction control , The first 4 Chapter will introduce Redis Business )
func PostArticle(conn redis.Conn, userId int, title string, link string) {
// Get the current article from add id
articleId, err := redis.Int(conn.Do("INCR", ARTICLE_COUNT))
if err != nil {
return
}
// Add authors to the voting user set
votedUserKey := VOTED_USER_PREFIX + RedisKey(strconv.Itoa(articleId))
_, err = conn.Do("SADD", votedUserKey, userId)
if err != nil {
return
}
// Set up Voting user set The expiration time is one week
_, err = conn.Do("EXPIRE", votedUserKey, ONE_WEEK_SECONDS)
if err != nil {
return
}
postTime := time.Now().Unix()
articleKey := ARTICLE_PREFIX + RedisKey(strconv.Itoa(articleId))
// Set up information about the article
_, err = conn.Do("HMSET", articleKey,
"title", title,
"link", link,
"userId", userId,
"postTime", postTime,
"upvoteNum", 1,
)
if err != nil {
return
}
// Set up Release time
_, err = conn.Do("ZADD", POST_TIME, postTime, articleId)
if err != nil {
return
}
// Set up Article rating
score := postTime + UPVOTE_SCORE
_, err = conn.Do("ZADD", SCORE, score, articleId)
if err != nil {
return
}
}
Page to get articles P18

Paging access supports four sorts , Get error returns an empty array .

Be careful :ZRANGE and ZREVRANGE It's a closed interval from the beginning to the end .

type ArticleOrder int
const (
TIME_ASC ArticleOrder = iota
TIME_DESC
SCORE_ASC
SCORE_DESC
)
// according to ArticleOrder Get the corresponding command and RedisKey
func getCommandAndRedisKey(articleOrder ArticleOrder) (string, RedisKey) {
switch articleOrder {
case TIME_ASC:
return "ZRANGE", POST_TIME
case TIME_DESC:
return "ZREVRANGE", POST_TIME
case SCORE_ASC:
return "ZRANGE", SCORE
case SCORE_DESC:
return "ZREVRANGE", SCORE
default:
return "", ""
}
}
// Perform paging to get article logic ( Ignore some simple logic such as parameter verification )
func doListArticles(conn redis.Conn, page int, pageSize int, command string, redisKey RedisKey) []map[string]string {
var articles []map[string]string
// ArticleOrder incorrect , Return to empty list
if command == "" || redisKey == ""{
return nil
}
// obtain Start stop subscript ( They're all closed intervals )
start := (page - 1) * pageSize
end := start + pageSize - 1
// obtain article id list
ids, err := redis.Ints(conn.Do(command, redisKey, start, end))
if err != nil {
return articles
}
// Get information about each article
for _, id := range ids {
articleKey := ARTICLE_PREFIX + RedisKey(strconv.Itoa(id))
article, err := redis.StringMap(conn.Do("HGETALL", articleKey))
if err == nil {
articles = append(articles, article)
}
}
return articles
}
// Page to get articles
func ListArticles(conn redis.Conn, page int, pageSize int, articleOrder ArticleOrder) []map[string]string {
// obtain ArticleOrder Corresponding command and RedisKey
command, redisKey := getCommandAndRedisKey(articleOrder)
// Perform paging to get article logic , And return the result
return doListArticles(conn, page, pageSize, command, redisKey)
}
Group articles P19

Support to add articles to group collection , It also supports deleting articles from the grouping set .

// Set up groups
func AddToGroup(conn redis.Conn, groupId int, articleIds ...int) {
groupKey := GROUP_PREFIX + RedisKey(strconv.Itoa(groupId))
args := make([]interface{}, 1 + len(articleIds))
args[0] = groupKey
// []int convert to []interface{}
for i, articleId := range articleIds {
args[i + 1] = articleId
}
// I won't support it []int Go straight to []interface{}
// Nor does it support groupKey, articleIds... Pass the reference in this way ( So the matching parameter is interface{}, ...interface{})
_, _ = conn.Do("SADD", args...)
}
// Ungroup
func RemoveFromGroup(conn redis.Conn, groupId int, articleIds ...int) {
groupKey := GROUP_PREFIX + RedisKey(strconv.Itoa(groupId))
args := make([]interface{}, 1 + len(articleIds))
args[0] = groupKey
// []int convert to []interface{}
for i, articleId := range articleIds {
args[i + 1] = articleId
}
// I won't support it []int Go straight to []interface{}
// Nor does it support groupKey, articleIds... Pass the reference in this way ( So the matching parameter is interface{}, ...interface{})
_, _ = conn.Do("SREM", args...)
}
Get articles in groups by page P20

Grouping information and sorting information in different ( Orderly ) Collection , So we need to take two ( Orderly ) Intersection of sets , And then get it by pagination .

It takes time to get the intersection , So cache 60s, Not in real time .

// Cache expiration time 60s
const EXPIRE_SECONDS = 60
// Pagination to get articles under a group ( Ignore simple logic such as parameter verification ; The expiration setting is not in the transaction )
func ListArticlesFromGroup(conn redis.Conn, groupId int, page int, pageSize int, articleOrder ArticleOrder) []map[string]string {
// obtain ArticleOrder Corresponding command and RedisKey
command, redisKey := getCommandAndRedisKey(articleOrder)
// ArticleOrder incorrect , Return to empty list , Avoid doing more intersection operations
if command == "" || redisKey == ""{
return nil
}
groupKey := GROUP_PREFIX + RedisKey(strconv.Itoa(groupId))
targetRedisKey := redisKey + RedisKey("-inter-") + groupKey
exists, err := redis.Int(conn.Do("EXISTS", targetRedisKey))
// The intersection does not exist or has expired , Then we take the intersection
if err == nil || exists != 1 {
_, err := conn.Do("ZINTERSTORE", targetRedisKey, 2, redisKey, groupKey)
if err != nil {
return nil
}
}
// Set expiration time ( Expiration failed , Does not affect the query )
_, _ = conn.Do("EXPIRE", targetRedisKey, EXPIRE_SECONDS)
// Perform paging to get article logic , And return the result
return doListArticles(conn, page, pageSize, command, targetRedisKey)
}
Exercises : Vote against P21

Add the function of voting against , And support the vote for and vote against each other .

  • After seeing this exercise and the corresponding tips , And the scenes of voting on weekdays , I don't think the way in the title is reasonable . In support of / It is in line with the user's habit to deal with the corresponding conversion logic in case of negative vote , It also has good scalability .
  • Change

    • article HASH, Add one more downvoteNum Field , Used to record the number of votes cast against
    • Article voting user collection SET Change it to HASH, The type used to store user votes
    • UpvoteArticle Replace the function with VoteArticle, At the same time, add a type of VoteType Input . Function functions not only support input support / no , And support the cancellation of the vote
// redis key
type RedisKey string
const (
// Release time Ordered set
POST_TIME RedisKey = "postTime"
// Article rating Ordered set
SCORE RedisKey = "score"
// Article voting user set prefix
VOTED_USER_PREFIX RedisKey = "votedUser:"
// Number of published articles character string
ARTICLE_COUNT RedisKey = "articleCount"
// Publish article hash table prefix
ARTICLE_PREFIX RedisKey = "article:"
// Group prefix
GROUP_PREFIX RedisKey = "group:"
)
type VoteType string
const (
// No vote
NONVOTE VoteType = ""
// Vote for it
UPVOTE VoteType = "1"
// Vote against
DOWNVOTE VoteType = "2"
)
const ONE_WEEK_SECONDS = int64(7 * 24 * 60 * 60)
const UPVOTE_SCORE = 432
// according to The original voting type and New voting types , obtain fraction 、 Support votes 、 No The incremental ( Not processed yet “ enumeration ” Wrong situation , Direct full return 0)
func getDelta(oldVoteType VoteType, newVoteType VoteType) (scoreDelta, upvoteNumDelta, downvoteNumDelta int) {
// The type remains the same , The relevant values do not need to be changed
if oldVoteType == newVoteType {
return 0, 0, 0
}
switch oldVoteType {
case NONVOTE:
if newVoteType == UPVOTE {
return UPVOTE_SCORE, 1, 0
}
if newVoteType == DOWNVOTE {
return -UPVOTE_SCORE, 0, 1
}
case UPVOTE:
if newVoteType == NONVOTE {
return -UPVOTE_SCORE, -1, 0
}
if newVoteType == DOWNVOTE {
return -(UPVOTE_SCORE << 1), -1, 1
}
case DOWNVOTE:
if newVoteType == NONVOTE {
return UPVOTE_SCORE, 0, -1
}
if newVoteType == UPVOTE {
return UPVOTE_SCORE << 1, 1, -1
}
default:
return 0, 0, 0
}
return 0, 0, 0
}
// by vote Update data ( Ignore part of the parameter validation ; No transaction control , The first 4 Chapter will introduce Redis Business )
func doVoteArticle(conn redis.Conn, userId int, articleId int, oldVoteType VoteType, voteType VoteType) {
// obtain fraction 、 Support votes 、 No The incremental
scoreDelta, upvoteNumDelta, downvoteNumDelta := getDelta(oldVoteType, voteType)
// Update the current user voting type
votedUserKey := VOTED_USER_PREFIX + RedisKey(strconv.Itoa(articleId))
_, err := conn.Do("HSET", votedUserKey, userId, voteType)
// Setting error , Then return to
if err != nil {
return
}
// to update Current article score
_, err = conn.Do("ZINCRBY", SCORE, scoreDelta, articleId)
// Self increasing error , Then return to
if err != nil {
return
}
// to update Current article Support votes
articleKey := ARTICLE_PREFIX + RedisKey(strconv.Itoa(articleId))
_, err = conn.Do("HINCRBY", articleKey, "upvoteNum", upvoteNumDelta)
// Self increasing error , Then return to
if err != nil {
return
}
// to update Current article No
_, err = conn.Do("HINCRBY", articleKey, "downvoteNum", downvoteNumDelta)
// Self increasing error , Then return to
if err != nil {
return
}
}
// Execute the voting logic ( Ignore part of the parameter validation ; No transaction control , The first 4 Chapter will introduce Redis Business )
func VoteArticle(conn redis.Conn, userId int, articleId int, voteType VoteType) {
// Calculate the earliest publishing time of articles that can be voted at the current time
earliestPostTime := time.Now().Unix() - ONE_WEEK_SECONDS
// obtain Current article The release time of
postTime, err := redis.Int64(conn.Do("ZSCORE", POST_TIME, articleId))
// Get error or article articleId The deadline for voting has passed , Then return to
if err != nil || postTime < earliestPostTime {
return
}
// Gets the voting type in the collection
votedUserKey := VOTED_USER_PREFIX + RedisKey(strconv.Itoa(articleId))
result, err := conn.Do("HGET", votedUserKey, userId)
// Query error , Then return to
if err != nil {
return
}
// After the transformation oldVoteType Must be "", "1", "2" One of them
oldVoteType, err := redis.String(result, err)
// If the type of vote does not change , Do not deal with
if VoteType(oldVoteType) == voteType {
return
}
// Execute voting to modify data logic
doVoteArticle(conn, userId, articleId, VoteType(oldVoteType), voteType)
}

Summary

  • Redis characteristic

    • Memory storage :Redis Very fast
    • long-range :Redis Can connect with multiple clients and servers
    • Persistence : After the server restarts, it still keeps the data before it restarts
    • Scalable : Master slave replication and fragmentation

Thinking and thinking

  • Code doesn't take shape all at once , Will continue to improve the previous logic in the process of writing new functions , And extract common methods to achieve high maintainability and scalability .
  • I feel like I haven't changed my mind ( I don't know. It's still this one Redis The problem with open source libraries ), Always use Java Thought , It's not convenient in many places .
  • Although I write some private methods to ensure that there will not be some abnormal data , But some will be dealt with accordingly , In case you don't pay attention to the call later and make an error .
This article was first published on the official account : Man Fu Zhu Ji ( Click to view the original ) Open source in GitHub : reading-notes/redis-in-action
版权声明
本文为[Man Fu Zhu Ji]所创,转载请带上原文链接,感谢
https://javamana.com/2021/01/20210121221811289R.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云