Location>code7788 >text

[Solution] Design and Implementation of Message Notification System in Java Internet Project (Next)

Popularity:80 ℃/2024-08-05 08:18:00

catalogs
  • preamble
  • IV. Technology selection
  • V. Back-end interface design
    • 5.1 Business system interfaces
    • 5.2 App Side Interface
  • VI. Key logic implementation
    • 6.1 Redis Storage Structure
    • 6.2 Read Message Handling
    • 6.3 Cache Timing Clear
  • Summary of this article

preamble

As an independent microservice, the notification-system is responsible for all the notification-related back-end functions in the App. The system needs to be associated with the article system, order system, membership system, etc., but also needs to be associated with other business systems, is a general service system on the bottom.

There are several common types of message notifications in the App: comment notification, like notification, collection notification, order notification, activity notification, personal center related notifications, etc. The ultimate goal of this system is to enhance user stickiness, strengthen interaction between the App and users, and support the development of the core business. The system has high requirements in terms of scalability, high performance, high availability, data consistency, etc. The ultimate goal is to enhance user stickiness, strengthen the interaction between the App and users, and support the development of core business.

The (upper) part of the article will illustrate from the 3 parts of requirements analysis, data model design, and key process design, and the (lower) part will illustrate from the 3 parts of technology selection, back-end interface design, and key logic implementation.


IV. Technology selection

I've made a table of the key technology options that need to be used in this system to make it easy to sort through:

Description:
  • You can use Spirng Cloud or Spirng Cloud Alibaba, whichever you're used to, as long as it's packaged into a runnable microservice;
  • It is also possible to replace MySQL with a non-relational database such as MongoDB, which will perform better if the tables are not closely related to each other;
  • Redis is very suitable to be used as a caching middleware to store unstructured data, and in many scenarios, its outstanding performance and convenient API are its advantages;
  • MQ is actually selected for more complex projects to asynchronous/decoupled, both kafka and RabbitMQ, RocketMQ is Ali's own, the console is also easy to use;
  • Other open source dependencies are best used apache's top projects or Spring's official, like hutool this third-party package is not really recommended, the security risk may be higher.

V. Back-end interface design

As a low-level public service, it is basically invoked by the upstream business system before serving the user (i.e., App side). The following design of two Controllers for the business side and App side, you can refer to the interface specification, but also wrote a general idea of the notes, the key logic will be in the next section to start talking about.

5.1 Business system interfaces

There are 3 interfaces exposed to the business system:

  1. Getting Notification Configuration
  2. Send notification
  3. Withdrawal of notification
@RestController
@RequestMapping("notice/api")
public class NoticeApiController {

    @Resource
    private NotificationService notificationService.

    /**
     * Add new notification, used by the business system
     * @param dto
     * @return MessageService unique id
     */
    @PostMapping("/add")
    public Response<Long> addNotice(@Valid @RequestBody AddNoticeDTO dto){
        // The business side needs to confirm the source according to sourceId before calling this interface, the implementation is to enter the database first, and then into Redis.
        return ((dto)); }
    }

    /**
     * Withdrawal notification (same as batch withdrawal), used by the business system.
     * @param idList, the set of primary key ids of the messages to be withdrawn.
     * @return whether success: true-success, false-failure
     */
    @PostMapping("/recall")
    public Response<Boolean> recallNotice(@RequestBody List<Long> idList){
        // Withdraw just consider updating the database first and Redis second.
        return ((idList));
    }

    /**
     * Get the notification configuration
     * @param sourceId business system identifier
     * @return Configuration details information
     */
    @GetMapping("/getNoticeConfig")
    public Response<NotificationConfig> getNoticeConfig(@RequestParam(value = "noticeId") String sourceId){
        //Notification configuration needs to be verified before each business system call to prevent illegal calls.
        return ((sourceId));
    }

}

5.2 App Side Interface

There are 2 interfaces open to the App side:

  1. Get the total number of unread messages from users
  2. Get the list of user messages
@RestController
@RequestMapping("notice/app")
public class NoticeAppController {

    @Resource
    private NotificationService notificationService;

    /**
     * Get the total number of unread messages from users
     */
    @Auth
    @GetMapping("/num")
    public Response<NoticeNumVO> getMsgNum() {
        //App End-user uniqueness uuid
        String userUuid = "";
        return ((userUuid));
    }

    /**
     * Get the list of user messages
     *
     * @param queryDate:Inquiry Time queryDate
     * @param pageIndex:pagination,1commencement
     * @param pageSize:page size
     * @param superType:Message parent type,1-commentaries、kudos、system message,2-notifications,3-private letter,4-Customer Service Message
     */
    @Auth
    @GetMapping("/list/{queryDate}/{pageIndex}/{pageSize}/{superType}")
    public Response<List<Notification>> getNoticeList(@PathVariable String queryDate, @PathVariable Integer pageIndex,
                                                      @PathVariable Integer pageSize, @PathVariable Integer superType) throws ParseException {
        //App End-user uniqueness uuid
        String userUuid = "";
        Date dateStr = (queryDate, new String[]{"yyyyMMddHHmmss"});
        return ((userUuid, dateStr, pageIndex, pageSize, superType));
    }

}

VI. Key logic implementation

This subsection will explain the two interfaces on the APP side in detail. The implementation of the unread message count and the message list requires the close cooperation of Redis + MySQL.

6.1 Redis Storage Structure

The following highlights the design of the Redis cache structure for this system, which globally uses only the hash structure, +1 when adding a new message, -1 when withdrawing a message, and an arithmetic update when a message has been read:

Redis-Hash Structure

Description:

  • Redis-key is the fixed String constant "";

  • Hash-key is the unique userUuid of the user on the App side;

  • Hash-value is the total number of messages received by this user, +1 for new, -1 for withdrawn.

If you don't know much about the basic structure of Redis, refer to this blog post of mine:/CodeBlogMan/p/

Below is a code example of the key implementation steps:

  1. New Message

        //first into MySQL
        Notification notification = (dto).
        //then into Redis
        ().increment(RedisKey, (), 1); // then into
    
  2. Withdrawal of messages

        // Update MySQL first
        (notification).
        //then update Redis
        ().increment(RedisKey, userUuid, -1);
    

Attention:

Both write operations and update operations operate on the database first and then synchronize into Redis. reason:The data in the database is the source, and the data stored is structured and persistent data; Redis is only used as a cache, taking advantage of Redis's fast read speed, and storing some hot data of small size.

6.2 Read Message Handling

Read and unread is actually two states, Redis in the beginning of the storage are unread number, when the user clicks to view the list, the front-end will call the back-end message list interface, the message list directly to check the database (recorded read and unread state), this time synchronization to update the number of unread messages in the Redis, then at this time: the number of unread messages = the total number of Redis - MySQL has read the number of messages.

The code below says it better:

  1. Query the number of unread messages

        Integer num.
        // read redis first, then read the database if it's not there, and finally put the database read back into redis
        num = (Integer) ().get(RedisKey, userUuid); //To prevent the notification from being put into redis at the beginning, null means nothing.
        //prevents the notifications from not being put into redis when they're added in the first place; null means nothing, not 0.
        if ((num)) {
            (num); }else {
        }else {
            num = (userUuid, queryDate);
            ("Total number of unread messages is not in the cache, check database: {}", num);
            (num);
            //put into cache, take out what to put in
            ().put(RedisKey, userUuid, num);
        }
    return num;
    
  2. Query Message List

     (Notification::getTargetUserUuid, userUuid)
            .eq(Notification::getSuperType, superType)
            .eq(Notification::getMsgStatus, ())
            .le(Notification::getCreateTime, dateTime)
            .orderByDesc(Notification::getCreateTime);
        List<Notification> queryList = ();
        // After querying, synchronize to update the database with messages of this type as read.
        (wrapper); long isReadNum; //The query is synchronized to update the database as read.
        long isReadNum.
        isReadNum = ().filter(val -> NumberUtils.INTEGER_ZERO.equals(())).count(); //The key step is to synchronize the update of the database with the messages of this type as read.
        // Critical step, synchronize the number of unread messages in redis with updates
        Integer redisNum = (Integer) ().get(RedisKey.INITIAL_NOTICE_NUM_PERFIX, userUuid); //To determine the number of unread messages in redis, we need to first determine the number of unread messages in redis.
        //To determine whether the redis is null, and 0 is not the same as
        int hv = (redisNum) ? 0 : (int) (redisNum - isReadNum);
        ().put(RedisKey, userUuid, (hv, 0)); return queryList; return queryList; hv = (redisNum) ?
    return queryList;
    

6.3 Cache Timing Clear

Since there is no expire expiration time added to the above redis-hash structure, it is obvious that this structure will get bigger and bigger as time goes by, eventually leading to a large key, which will affect the read/write performance of redis.
So here is a program that needs to be given to solve this problem, and my core idea is:

  • Whenever a redis count is written, another key is used at the same time to memorize the operation time, and a timed task is executed every 10 minutes;
  • One by one, the value of the time key (i.e. operation time) is taken out according to the uuid, if the current system time - the operation time of the uuid >3600ms (i.e. one hour) then the data of the uuid will be deleted;
  • The next time you call the interface, read the database first, and then write into redis, depending on the code.
@Component
@Slf4j
public class HandleNoticeCache {
    private static final Long FLAG_TIME = 3600L;
    @Resource
    private RedisTemplate redisTemplate;
    @Scheduled(cron = " * 0/10 * * * ? ")
    public void deleteNoticeCache(){
        HashOperations<String, String, Integer> hashOperations = ();
        //All of the notification operations uuid,A large volume of data may result in OOM
        Set<String> uuidList = (RedisKey.NOTICE_NUM_TIME);
        if ((uuidList)){
            (val -> {
                Integer operateTime = (RedisKey.NOTICE_NUM_TIME, val);
                if ((operateTime)){
                    //Current system time-Recording time of the operation
                   long resultTime = () - operateTime;
                   if (resultTime > FLAG_TIME){
                       (RedisKey.NOTICE_NUM_PERFIX, val);
                       ("Deletion of the notification of uuid because of:{}", val);
                       (RedisKey.COMMENT_NUM_PERFIX, val);
                       ("Delete the comment notification for uuid because of:{}", val);
               }
                }
            });
        }
    }

}

Summary of this article

Here on the Internet message notification system design and implementation of the sharing is complete, as for the source code I look at the weekend or vacation time there is no time to send out, after their own personal git open source repository should have been built.

Please correct any errors and shortcomings in the article, and feel free to speak your mind in the comments section!