Online business optimization case practice

This article is a summary of my experience in handling online businesses during my many years of development career. I believe that everyone has encountered these businesses more or less, so I share them with you here, and you can also see if you have encountered similar businesses. Scenes. The outline of this article is as follows,

image

Upload files in the background

The online backend project has a message push function. When the operator creates a new notification message, he or she needs to upload a list of files containing user IDs to push system messages to the specified users included in the file.

The above function description seems very simple, but in fact, the step of processing uploaded files is very particular. If the uploaded file in the background is too large, causing memory overflow, or reading the file is too slow, these are actually some hidden problems.

For the technical side, if you want to do this function well and ensure that the functions of uploading files and sending messages are normal under a large number of users (for example, reaching one million levels), you need to think carefully. I will give my optimization ideas here.

Upload file type selection

Usually, most users use Excel files as the background upload file type, but compared to Excel files, there is a more recommended file format, which is CSV files.

CSV is a plain text format where data is stored as text, with each row of data separated by commas, without any formatting.

CSV is therefore suitable for simple, readable, import and export scenarios, and since CSV files contain only plain text, the file size is usually much smaller than Excel files.

However, the CSV file's support for complex spreadsheet operations is not as powerful as Excel's, but it is enough for this file upload business with only one column.

If the uploaded file contains 1 million user IDs, then there are obvious advantages to using CSV file upload here, which takes up less memory and processes the uploaded file faster.

Save message push status

Since large batch data insertion is a time-consuming operation (it may take seconds or minutes), it is necessary to save the status of whether the batch insertion is successful, and this message needs to be displayed in the background< a i=1>Push statusWhether it is successful or failed, it is convenient for operators to trace back the message push status.

batch write

Regarding the batch writing scenario when uploading large files here, here are a few points that everyone should pay attention to.

rewriteBatchedStatements=true

The rewriteBatchedStatements parameter must be added to the URL of MySQL's JDBC connection, and the driver must be version 5.1.13 or above to achieve high-performance batch insertion.

By default, the MySQL JDBC driver will ignore the executeBatch() statement, break up a group of SQL statements that we expect to be executed in batches, and send them to the MySQL database one by one. The batch insertion is actually a single insertion, which directly results in lower performance. Only when the rewriteBatchedStatements parameter is set to true will the driver help you execute SQL in batches. In addition, this option is valid for INSERT/UPDATE/DELETE.

Whether to enable transaction functions

In fact, many people have their own opinions on whether to enable things in batch writing scenarios. Here I will give the pros and cons of enabling or not enabling them.

  • Enabling transactions: The advantage is that, for example, during batch insertion, atomicity can be ensured in abnormal situations, but the performance is lower than that without transactions, and will be significantly lower when the amount of data is extremely large.

  • Not enabling transactions: The advantage is high writing performance, and the writing performance of extremely large data volumes is significantly improved, but atomicity cannot be guaranteed.

In the scenario of large file upload and batch writing mentioned in this article, if you pursue the ultimate performance, I recommend not enabling transactions.

If network fluctuations or database downtime occur during the batch writing process, we actually only need to create a new notification message and re-upload the file containing the user ID.

Because the previous notification message was not completed due to batch insertion steps,the push status is failed. You can wait for the developer to process the dirty data later.

Big business

@Transactional is a transaction annotation provided by the Spring framework. I believe many people know this. However, in some high-performance scenarios, it is not recommended to use it. It is recommended to use programmatic transactions. Manually control transaction commit or rollback to reduce the scope of transaction impact and thus improve performance.

Use transaction annotations

The following is a code to roll back business data if the order times out and is not paid, using @Transactional transaction annotation

@Transactional(rollbackFor = Exception.class)
public void doUnPaidTask(Long orderId) {
    // 1. 查询订单是否存在
    Order order = orderService.getById(orderId);
    ,,,

    // 2. 更新订单为已取消状态
    order.setOrderStatus((byte) OrderStatusEnum.ORDER_CLOSED_BY_EXPIRED.getOrderStatus());
    orderService.updateById(order);
    ...
    // 3. 订单商品数量增加
    LambdaQueryWrapper<OrderItem> queryWrapper = Wrappers.lambdaQuery();
    queryWrapper.eq(OrderItem::getOrderId, orderId);
    List<OrderItem> orderItems = orderItemService.list(queryWrapper);
    for (OrderItem orderItem : orderItems) {
        Long goodsId = orderItem.getGoodsId();
        Integer goodsCount = orderItem.getGoodsCount();
        if (!goodsDao.addStock(goodsId, goodsCount)) {
            throw new BusinessException("秒杀商品货品库存增加失败");
        }
    }

    // 4. 返还用户优惠券
    couponService.releaseCoupon(orderId);
    log.info("---------------订单orderId:{},未支付超时取消成功", orderId);
}

You can see that the code logic for order rollback above has four steps, as follows:

  1. Check if the order exists

  1. Update order to canceled status

  1. Increase in order quantity

  1. Return user coupons

There is a problem here. In the order rollback method, only steps 2, 3, and 4 need to be executed in one thing. Step 1 can actually be executed outside the thing to reduce the scope of the thing.

Use programmatic transactions

After optimizing it using programmatic transactions, the code is as follows,

@Resource
private PlatformTransactionManager platformTransactionManager;
@Resource
private TransactionDefinition transactionDefinition;

public void doUnPaidTask(Long orderId) {
    // 启用编程式事务
    // 1. 在开启事务钱查询订单是否存在
    Order order = orderService.getById(orderId);
    ...
    // 2. 开启事务
    TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
    try {
        // 3. 设置订单为已取消状态
        order.setOrderStatus((byte) OrderStatusEnum.ORDER_CLOSED_BY_EXPIRED.getOrderStatus());
        orderService.updateById(order);
        ...
        // 4. 商品货品数量增加
        LambdaQueryWrapper<OrderItem> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(OrderItem::getOrderId, orderId);
        List<OrderItem> orderItems = orderItemService.list(queryWrapper);
        for (OrderItem orderItem : orderItems) {
            Long goodsId = orderItem.getGoodsId();
            Integer goodsCount = orderItem.getGoodsCount();
            if (!goodsDao.addStock(goodsId, goodsCount)) {
                throw new BusinessException("秒杀商品货品库存增加失败");
            }
        }

        // 5. 返还优惠券
        couponService.releaseCoupon(orderId);
        // 6. 所有更新操作完成后,提交事务
        platformTransactionManager.commit(transaction);
        log.info("---------------订单orderId:{},未支付超时取消成功", orderId);
    } catch (Exception e) {
        log.info("---------------订单orderId:{},未支付超时取消失败", orderId, e);
        // 7. 发生异常,回滚事务
        platformTransactionManager.rollback(transaction);
    }
}

It can be seen that after using programmatic transactions, we exclude the query logic from the transaction, which also reduces the scope of things.

In scenarios where extremely high performance is a priority, we can even consider not using transactions and just use local message tables + message queues to achieve eventual consistency.

Massive log collection

The company has a project client online, which uses the TCP protocol to establish a connection with a back-end log collection service to report client log data.

During peak business periods, thousands of clients will establish connections at the same time and report log data in real time.

In the above peak period scenario, the log collection service will be under considerable pressure. If the program code logic processing is slightly improper, it will cause service lags, excessive CPU usage, memory overflow and other problems.

In order to solve the above scenario of a large number of connections reporting data, the log collection service decided to use the Netty framework for development.

Here are some optimization points after using Netty in the log collection program.

Collecting logs asynchronously

There are three options for asynchronous processing of the collection process of client connection report logs. Let me introduce them to you:

  • Normal version: Using blocking queue ArrayBlockingQueue to achieve producer-consumer mode, asynchronous batch processing of reported log data, in this scenario, the data is cached into memory through the producer Queue, and then obtain the log data of the memory queue in batches from the consumer and save it into the database. The advantage is that it is simple and easy to use, but the disadvantage is that there is a risk of memory overflow.

  • Advanced version: uses Disruptor queue, which is also a memory-based high-performance producer-consumer queue. The consumption speed comparison ArrayBlockingQueue is more than an order of magnitude Performance improvement, attached brief introduction:https://www.jianshu.com/p/bad7b4b44e48.

  • Ultimate version: It is also the final solution adopted by the company's log collection program. Using kfaka message queue middleware, the persistent log reports data first, and then consumes it slowly. Although introducing third-party dependencies will increase the complexity of the system, kfaka performs so well in big data scenarios that it is worth it.

Collection log compression

If the reported logs are to be sent to other services, they need to be compressed before processing. This step is to avoid consuming too much network bandwidth.

In Java, it usually refers to the serialization method. Compared with Protobuf, fst, Hession, etc., the serialization method that comes with Jdk has no advantage in serialization speed and size, and can even be described as garbage.

Commonly used serialization frameworks in Java include the following:

  • The serialization that comes with JDK: has poor performance, takes up a lot of space, and cannot cross languages. The advantage is that it is easy to use and has strong versatility.

  • JSON: Commonly used JSON libraries include Jackson, Gson, Fastjson, etc. It has better performance, takes up less space, and has extensive cross-language support, but it cannot serialize complex objects.

  • Protocol Buffers: Open sourced by Google, based on the IDL language definition format, the compiler generates object access code. High performance and small footprint, but the Schema needs to be defined in advance.

  • Thrift: Facebook open source, similar to Protocol Buffers. The customization ecosystem is not as complete as PB, but it supports multi-language interaction.

  • Avro: Hadoop ecosystem serialization framework, supports data isolation and evolution, dynamic reading and writing, good performance and reliability, and takes up less space. However, it is complex to use and has poor versatility.

  • Hessian: An open source binary remote communication protocol that provides RMI functionality using a simple method and is mainly used for object-oriented message communication. It supports cross-platform, multi-language support, and is simple to use. The disadvantage is that the performance of transferring complex objects will decrease, and it is not suitable for applications with high security.

If the compatibility requirements are not high, you can choose JSON. If the efficiency and the smaller the amount of transmitted data are required, PB/Thrift/Avro/Hessian is more suitable.

Data storage selection

It is recommended to use Clickhouse for storage in scenarios where a large amount of data such as logs is added to the database and has not been modified. The advantage is that compared with MySQL for the same amount of data, it takes up less storage and the query speed is faster. The disadvantage is that the concurrent query performance is relatively low. , it is not so mature compared to MySQL.

A few final words

This is the end of the three practical online business optimization practices introduced in this article. In fact, there are many more practical cases of this kind, but due to the length of this article, I will not go into that much. I will continue to update this type of article when I have the opportunity. I hope you all To be able to like.

Article reprinted from:waynaqua

Introduction:https://www.cnblogs.com/waynaqua/p/17893173.html

おすすめ

転載: blog.csdn.net/sdgfafg_25/article/details/134940969