Niuke.com back-end project combat (44): Optimize website performance

Optimize website performance

  • Local cache
  • Cache data on the application server for best performance
  • To consider the size of the cache, cache expiration time
  • Common caching tools: Ehcache, Guava, Caffeine, etc.
  • Distributed cache
  • Cache data on NoSQL databases, across servers.
  • Common caching tools: MemCache, Redis, etc.
  • Multi-level cache
  • > Level 1 cache (local cache)> Level 2 cache (distributed cache)> DB
  • Avoid cache avalanche (cache invalidation, a large number of requests go directly to the DB), and improve the availability of the system

Local caching is suitable for information that is not strongly associated with users. Redis can cache information strongly associated with users (such as login credentials), which can be cross-server, but is slightly slower than local caching

Insert picture description here


The two-level cache process:

Insert picture description here

Local cache

Data with relatively low frequency of data change is used for caching. Cache the list of popular posts in the local cache. Use Caffeine
1, guide the package

		<dependency>
			<groupId>com.github.ben-manes.caffeine</groupId>
			<artifactId>caffeine</artifactId>
			<version>2.7.0</version>
		</dependency>

2. Custom configuration

# caffeine
caffeine.posts.max-size=15  // 设置缓存空间里缓存多少对象,缓存的列表的对象是page
caffeine.posts.expire-seconds=180  // 3min

There are generally two ways to update the cached data: 1. The data is updated 2. The cache expires.

3. Optimize business methods, generally optimizing Service.
Use Caffeine to cache the list of posts and the total number of lines

The core interface of Caffeine is Cache, which has two sub-interfaces LoadingCache and AsyncLoadingCache. LoadingCache is a synchronous cache, AsyncLoadingCache can achieve asynchronous and concurrent

    @Value("${caffeine.posts.max-size}")
    private int maxSize;

    @Value("${caffeine.posts.expire-seconds}")
    private int expireSeconds;

    // Caffeine核心接口: Cache, LoadingCache, AsyncLoadingCache

    // 帖子列表缓存
    private LoadingCache<String, List<DiscussPost>> postListCache;

    // 帖子总数缓存
    private LoadingCache<Integer, Integer> postRowsCache;

    @PostConstruct
    public void init() {
        // 初始化帖子列表缓存
        postListCache = Caffeine.newBuilder()
                .maximumSize(maxSize)
                .expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
                .build(new CacheLoader<String, List<DiscussPost>>() {  
                    @Nullable
                    @Override
                    public List<DiscussPost> load(@NonNull String key) throws Exception {  // 该方法即是 当本地缓存不存在 所需的数据是,从数据库查找,并存入缓存中
                        if (key == null || key.length() == 0) {
                            throw new IllegalArgumentException("参数错误!");
                        }

                        String[] params = key.split(":");
                        if (params == null || params.length != 2) {
                            throw new IllegalArgumentException("参数错误!");
                        }

                        int offset = Integer.valueOf(params[0]);
                        int limit = Integer.valueOf(params[1]);

                        // 可以在这里添加二级缓存: Redis -> mysql

                        logger.debug("load post list from DB.");
                        return discussPostMapper.selectDiscussPosts(0, offset, limit, 1);
                    }
                });
        // 初始化帖子总数缓存
        postRowsCache = Caffeine.newBuilder()
                .maximumSize(maxSize)
                .expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
                .build(new CacheLoader<Integer, Integer>() {
                    @Nullable
                    @Override
                    public Integer load(@NonNull Integer key) throws Exception {
                        logger.debug("load post rows from DB.");
                        return discussPostMapper.selectDiscussPostRows(key);
                    }
                });
    }

    public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit, int orderMode) {
        if (userId == 0 && orderMode == 1) {
        	// 只有当访问首页热门帖子时才从缓存中取数据
            return postListCache.get(offset + ":" + limit);
        }

        logger.debug("load post list from DB.");
        return discussPostMapper.selectDiscussPosts(userId, offset, limit, orderMode);
    }

    public int findDiscussPostRows(int userId) {
        if (userId == 0) {
            return postRowsCache.get(userId);
        }

        logger.debug("load post rows from DB.");
        return discussPostMapper.selectDiscussPostRows(userId);
    }

pressure test

Stress testing is best to make the data in the database more, and the performance of accessing the database is worse, so that it is convenient to compare with the cache.
A. Create data

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class CaffeineTests {

    @Autowired
    private DiscussPostService postService;

    @Test
    public void initDataForTest() {
        for (int i = 0; i < 300000; i++) {
            DiscussPost post = new DiscussPost();
            post.setUserId(111);
            post.setTitle("互联网求职暖春计划");
            post.setContent("今年的就业形势,确实不容乐观。过了个年,仿佛跳水一般,整个讨论区哀鸿遍野!19届真的没人要了吗?!18届被优化真的没有出路了吗?!大家的“哀嚎”与“悲惨遭遇”牵动了每日潜伏于讨论区的牛客小哥哥小姐姐们的心,于是牛客决定:是时候为大家做点什么了!为了帮助大家度过“寒冬”,牛客网特别联合60+家企业,开启互联网求职暖春计划,面向18届&19届,拯救0 offer!");
            post.setCreateTime(new Date());
            post.setScore(Math.random() * 2000);
            postService.addDiscussPost(post);
        }
    }

    @Test
    public void testCache() {
        System.out.println(postService.findDiscussPosts(0, 0, 10, 1));
        System.out.println(postService.findDiscussPosts(0, 0, 10, 1));
        System.out.println(postService.findDiscussPosts(0, 0, 10, 1));
        System.out.println(postService.findDiscussPosts(0, 0, 10, 0));
    }

B. JMeter
stress test is actually the use of tools to simulate the client to access the server.

1) Add thread group

Insert picture description here


2) Set http request

Insert picture description here


3) Add a random interval between requests

Insert picture description here


4) Hybrid report: mainly depends on throughput, that is, how many requests the server can handle per second

Insert picture description here

Further: Level 2 cache

Thoughts on synchronization between secondary caches