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

Redis actual combat —— 02. Redis Simple practice - More related articles about article voting

  1. Redis The actual combat Redis + Jedis

    use Memcached, There are requirements for cache object size , A single object cannot be greater than 1MB, And does not support complex data types , for example SET etc. . Based on these limitations , It is necessary to consider Redis! Related links : Redis actual combat Redis The actual combat Redi ...

  2. Redis The actual combat Redis + Jedis[ turn ]

    http://blog.csdn.net/it_man/article/details/9730605 2013-08-03 11:01 1786 Human reading   Comment on (0)  Collection   report   Catalog (?)[-] ...

  3. Distributed cache technology redis Learning Series ( 5、 ... and )——redis actual combat (redis And spring Integrate , Distributed lock implementation )

    This article is about redis Learn the fifth part of the series , Click on the link below to go back to the series <redis Introduction and linux The installation of the > < Explain in detail redis data structure ( Memory model ) And common commands > <redi ...

  4. Distributed cache technology redis series ( 5、 ... and )——redis actual combat (redis And spring Integrate , Distributed lock implementation )

    This article is about redis Learn the fifth part of the series , Click on the link below to go back to the series <redis Introduction and linux The installation of the > < Explain in detail redis data structure ( Memory model ) And common commands > <redi ...

  5. Redis Practical summary -Redis High availability

    In previous blogs <Redis Practical summary - To configure . Persistence . Copy > Give a kind of Redis Master slave replication mechanism , Simply implemented Redis High availability . then , If Master Server down , Will lead to the whole Redis paralysis , In this way ...

  6. Redis The actual combat Redis command

    Read the directory 1. String command 2. List command 3. Assemble orders 4. Hash command 5. To assemble orders in order 6. Publish and subscribe commands 7. A profound Redis Can store keys and 5 Mapping between different types of data structures , this 5 It's a data node ...

  7. redis Practical notes (1)- The first 1 Chapter First time to know Redis

    The first 1 Chapter First time to know Redis notes : This book is in redis3.0 Version of , such as redis3.0 Support server cluster in the future .3.0 Before, only the client can be partitioned .    The main content of this chapter 1.Redis Similarities and differences with other software 2.Re ...

  8. Use Redis Building an article voting website

    It involves key: 1. article_time, Record the release time of the article ,zset structure 2. article_score, Record the score of the article , zset structure score = Release time + Number of voting users X 432 ...

  9. redis actual combat --- Reading notes

    Chapter one First time to know redis redis Is a remote memory database , Strong performance , Unique data model with replication features and for problem solving .   1. redis brief introduction redis It's a non relational database (NOSQL) r ...

  10. Redis actual combat

    About a year ago , Company colleagues began to use Redis, I don't know. It's configuration , It's still about the version , At that time Redis Often after a period of use , The connection is full and does not release . In my impression ,Redis 2.4.8 The following version is due to the synchronization problem of master-slave library , will ...

Random recommendation

  1. About strcpy_s

    #include"stdafx.h" #include<iostream> #include<cstring> int main() { using nam ...

  2. Multithreaded call HttpWebRequest Concurrent connection restrictions

    .net Of HttpWebRequest perhaps WebClient There are concurrent connection restrictions in the case of multithreading , This limitation applies to desktop operating systems such as windows xp , windows  7 The default is 2, Operating on the server ...

  3. DELPHI Get process handle through window handle or window title

    DELPHI Get process handle through window handle or window title 2009 year 05 month 08 Japan Friday 10:15procedure TForm1.Button1Click(Sender: TObject);varhWind ...

  4. MySql Learning notes ( One ) —— Use of keywords

    1.distinct keyword effect : Retrieve columns with different values , For example, there are suppliers in a commodity table vend_id, One supplier will correspond to many goods , How many suppliers are we looking for , You can use the keyword to duplicate . select distinc ...

  5. GAN_ Li Hongyi's explanation

    GAN_ Li Hongyi's explanation : In the above formula ,xi from data in sample Part of , Now the goal is to maximize the likelihood function , bring Generator Most likely to produce data These in sample: The reason for this design V function , In order to ...

  6. MATLAB To Python The way 1_ Data structure and simple operation

    stay numpy in , use array Instead of matrix, differ MATLAB Everything in the world matrix, The data here is first represented by array There is , And then through the operation can be and matrix form array operation 1,array In the form of 1.1 A one-dimensional ...

  7. PAT Answer to question a -1103. Integer Factorization (30)-(dfs)

    The question is not bad ~. The question : Given N.K.P, So that it can be decomposed into N = n1^P + … nk^P In the form of , If possible , Output sum(ni) The biggest Division , If sum equally , The one with the larger output sequence . Otherwise output Impossible. ...

  8. natural language processing --- Using hidden Markov model (HMM) Part of speech tagging ---1998 year 1 Corpus of people's daily ---learn---test---evaluation---Demo---java Realization

    Put one first Demo Test chart for The sentence and part of speech of each participle are marked as :    at present /t this /rzv strip /q Expressway /n Between /f Of /ude1 Section /n has /d emergency /a closed /v ./w Need basic knowledge HM ...

  9. Laravel Alipay payment asynchronous notification

    Alipay payment notice has front-end notification. (GET) Asynchronous notification with server (POST) When configuring Alipay payment , The problem that needs attention is the callback operation of Alipay. : 1. stay laravel Alipay should notify the path organization. csrf verification , Otherwise, it will lead to 419 wrong ...

  10. Java Web -- Servlet(5) Development Servlet Three ways 、 To configure Servlet Specific explanation 、Servlet Life cycle of (2)

    3、 ... and .Servlet Life cycle of One Java servlet It has a life cycle , This life cycle defines a Servlet How to be loaded and initialized , How to receive and respond to requests , How is it removed from the service .Servlet ...