Spring boot + redis realizes the record of article views
Previously, the article visits in personal blog websites were stored in MySQL. Every time you visit, you need to check the database through the article id and operate + 1 after obtaining the number of views. After updating the database, in order to display the article views in real time, you have to read the database again. A large number of accesses brings thread safety problems: two threads obtain the number at the same time and update after + 1 respectively. This access is problematic. In this case, we used synchronized to lock the operation roughly. This brings a problem. Multiple threads can only have one thread to obtain article information, and other threads are blocked,. Later, I came up with another solution. The increase of browsing volume is independent and not bound with reading article information. After each article information is read, an interface is called to increase the number. One drawback is that the number of article visits displayed is not real-time.
Finally, it is decided to use redis to store the visits of articles. The key statement of redis is incr key. The function is value+1 of key; The key is bound to the article id. Because redis is single threaded, the execution of a single statement is thread safe.
The ideal is very plump, but there are still many problems in operation.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
Set redis configuration
redis: database: 0 # Write your ip address in the Redis server address host: XXX.XX.XX.XXX # Redis server connection port port: Your port number # Redis server connection password (blank by default) password: Your password timeout: 6000 lettcue: pool: # The maximum free connection in the connection pool. The default value is 8. max-idle: 100 # The minimum free connection in the connection pool. The default value is 0. min-idle: 10 # If the value is - 1, it means there is no limit; If the pool has allocated maxActive jedis instances, the status of the pool is exhausted max-active: 8 # The maximum time to wait for an available connection, in milliseconds. The default value is - 1, which means never timeout. If the waiting time is exceeded, the JedisConnectionException will be thrown directly max-wait: 2000
If the connection pool attribute of redis is not set, there is only one connection by default. If multiple threads use redisTemplate, it will report "redis connection exception".
Set redisTemplate class template
@Bean public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) { // Configure redisTemplate RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer());//key serialization redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));//value serialization return redisTemplate; }
JUnit test class:
@Resource RedisUtils redisUtils; @Test public void redisTest(){ try { Runnable runnable = new Runnable(){ @SneakyThrows @Override public void run() { for(int i=0;i<500;i++){ Thread.sleep(1); System.out.println(Thread.currentThread().getName()+": "+addReadNums()); } } }; ExecutorService executorService = Executors.newFixedThreadPool(5); executorService.execute(runnable);//Thread 1 executorService.execute(runnable);//Thread 2 System.out.println("@Test Thread execution completed"); } catch (Exception e) { System.out.println(e); } } public Long addReadNums(){ return redisUtils.incrBy("test", 1); }
An error was reported during the multithreading simulation test. There was a problem with the lettuce thread pool under multithreading. After searching the data, they all said that Jedis was used as the redis thread pool.
After modification
Modified dependency: remove lettuce thread pool in spring boot starter data redis and import jedis
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.3.0</version> </dependency>
Note that the version of Jedis is different from that of spring boot starter data redis. If the version is inconsistent, the creation of redisTemplate will fail.
reference resources: [exception] nested exception is Java lang.NoClassDefFoundError: redis/clients/jedis/util/SafeEncoder
You should also modify the creation method of redisTemplate class
@Bean public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory connectionFactory) { // Configure redisTemplate RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer());//key serialization redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));//value serialization return redisTemplate; }
At this time, the test found that the thread will print after running for a while: Shutting down ExecutorService 'applicationtask executor'. Then the thread stops. Discovery is the pit of the JUnit unit test: it will call the relevant System. after the main thread ends. The exit () method closes the JVM, so the child thread hangs passively.
reference resources: Multithreading in JUnit unit testing
Modified test class:
@Test public void redisTest(){ try { Runnable runnable = new Runnable(){ @SneakyThrows @Override public void run() { for(int i=0;i<500;i++){ Thread.sleep(1); System.out.println(Thread.currentThread().getName()+": "+addReadNums()); } } }; ExecutorService executorService = Executors.newFixedThreadPool(5); executorService.execute(runnable);//Thread 1 executorService.execute(runnable);//Thread 2 try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("@Test Thread execution completed"); } catch (Exception e) { System.out.println(e); } } public Long addReadNums(){ return redisUtils.incrBy("test", 1); }
It is found that there is no problem adding 500 under the two threads.
Look, redis is really 1000.
Finally, for article views, as long as the key passes in the article id. If you are worried that the key is repeated with other contents, you can add an identifier to distinguish it.