Single-threaded and multi-threaded Redis

After 6.0, the whole redis speaking, it is multi-threaded, but redis the worker threads (operations of the ten major data types) are single-threaded, such as RDB,AOF,asynchronous deletion, cluster data synchronization are all executed by extra threads

Why Redis initially chose single-threaded

assets/Redis高级篇/image-20240224112815163.png assets/Redis高级篇/image-20240224112823238.png assets/Redis高级篇/image-20240224113953213.png

Reasons why single-threaded was still fast initially

Multi-threading is for better utilization CPU, and for redis the main bottleneck is memory or network bandwidth rather than CPU CPU, and even single-threaded can through multiplexing and non-blocking IO concurrently handle client requests, initially single-threaded maintenance and development are simpler

assets/Redis高级篇/image-20240224114035636.png assets/Redis高级篇/image-20240224114047435.png

Why multi-threading was added later

For example, when a hash set contains tens of thousands of elements, then the del command will cause the Redis main thread to stall

The thread will not be released for a long wait, similar to adding a synchronized lock, you can imagine under high concurrency, the program will be super blocked

assets/Redis高级篇/image-20240225225924253.png

Network IO is multi-threaded, the thread that actually does the work is still single-threaded

Multiple servers connect to one redis server to operate, multiple network IO, but only redis the main thread is responsible for writing

Introduction to IO multiplexing, the real main thread appears

assets/Redis高级篇/image-20240225220655469.png

Generally CPU and memory are not a problem, network IO is the main problem

Although some command operations can be executed by background threads or child processes (such as data deletion, snapshot generation, AOF rewrite). However, from network IO handling to the actual read and write command handling, all are completed by a single thread. The performance bottleneck of Redis sometimes appears in network IO handling, that is, the speed at which a single main thread handles network requests cannot keep up with the speed of the underlying network hardware. To deal with this problem:

adopt multiple IO threads to handle network requests, improving the parallelism of network request handling,Redis6/7 is the method adopted

assets/Redis高级篇/image-20240225221100871.png That is, network IO is multi-threaded, the thread that actually does the work is still single-threaded

  1. connections, establishedSocketconnection assets/Redis高级篇/image-20240225221336857.png

assets/Redis高级篇/image-20240225222023779.png assets/Redis高级篇/image-20240225221841448.png assets/Redis高级篇/image-20240225222015206.png

IO threads read and parse, such as whether the network is connected, whether the IP is allowed, whether the firewall is allowed

redis The reason it is fast is IO multiplexingand epoll functionusage

Five IO models in Unix network programming

IO Introduction to multiplexing : Only one service process is needed to simultaneously handle multiple socket descriptor connections assets/Redis高级篇/image-20240225224837407.png

IO Multiplexing is a synchronous IO model, implementing one thread to monitor multiple file handles, once a certain file handle is ready it can notify the corresponding program to perform the corresponding read and write operations, if no file handle is ready it will block the application, thereby releasing computer resources IO Multiplexing : Using a single thread can handle multiple client connections, without needing to maintain too many processes

Introduction to IO multiplexing

File descriptor FD handle

assets/Redis高级篇/image-20240225223213381.png

What is IO multiplexing

assets/Redis高级篇/image-20240225223415524.png

"文件句柄就绪"Usually refers to the file descriptor (file descriptor) or file handle (file handle) in the operating system being in a state whereI/Ooperations can be performed.

assets/Redis高级篇/image-20240225223956014.png

assets/Redis高级篇/image-20240225224426317.png

assets/Redis高级篇/image-20240225224444894.png

assets/Redis高级篇/image-20240225224837407.png

redis and multiplexing

redis Thread safety issues will not occur because the main command execution is still completed by the main thread

redis and multiplexing

assets/Redis高级篇/image-20240225225231125.png assets/Redis高级篇/image-20240225225309844.png

assets/Redis高级篇/image-20240225225615879.png

Since multi-threading is disabled by default, in actual applications, if you find that the CPU overhead of the redis instance is not large but the throughput has not improved, you can consider enabling it, for 80 percent of companies single-threaded is enough

Enable multi-threading

assets/Redis高级篇/image-20240225230058937.png assets/Redis高级篇/image-20240225230115940.png

  1. Setio-thread-do-readsThe configuration item is yes, which means multithreading is enabled.

  2. Set the number of threads. Regarding the setting of the thread count, the official recommendation is that if it is a 4 core CPU, it is recommended to set the thread count to 2 or 3, and if it is an 8 core CPU, it is recommended to set the thread count to 6. The thread count must be less than the number of machine cores. A larger thread count is not always better.

A larger thread count is not always better assets/Redis高级篇/image-20240225230310891.png

Redis multiplexing refers to simultaneously monitoring the readable and writable status of multiple sockets in a single thread through epoll or kqueue and other mechanisms, thereby managing and processing multiple connections. This mechanism enables Redis a single thread to handle a large number of concurrent connections without blocking.

Even if Redis multithreading is not used, the multiplexing mechanism can still be used to manage connections. This is because multiplexing does not depend on multithreading, but instead uses the asynchronous IO mechanism provided by the operating system to monitor and process status changes of multiple connections through a single thread.

BigKey

Disable keys in redis *flushDB flushALL

Directly configure it as an empty string in the configuration file assets/Redis高级篇/image-20240226155417140.png

scan

Redis of SCAN The command is used to iterate over the elements in a collection. It can be used to traverse all elements in a collection without blocking the server.

Compared with using KEYS the command to obtain all elements,SCAN the command is more efficient because it can evenly distribute the workload among different iterators without placing too much load on the server

SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]

SCAN Return an array containing two elements

The first element is the new cursor used for the next iteration

The second element is a array, and this array contains all the elements that were iterated. If the new cursor returns zero, it means the iteration has ended.

The number returned at one time is uncontrollable, and it can only roughly match the count parameter

Usage

  1. After creating scanParams
  2. scan fill inscanParams
Define a scan
ScanParams scanParams = new ScanParams().match(pattern).count(count); ScanResult<String> scanResult = jedis.scan(cursor, scanParams); 

//First element
cursor = scanResult.getCursor();
//Second element
List<String> keys = scanResult.getResult(); 
Basic usage

assets/Redis高级篇/image-20240226160234341.png assets/Redis高级篇/image-20240226160637907.png

assets/Redis高级篇/image-20240506182731690.png

Big key

How large counts as a big key

Alibaba Cloud Specification assets/Redis高级篇/image-20240226184857377.png assets/Redis高级篇/image-20240226184916178.png assets/Redis高级篇/image-20240226191328590.png

How to discover big keys
  1. redis-cli xxxx Add at the end --bigkeys assets/Redis高级篇/image-20240226193545644.png Although--bigkeysthe command can help you find some big keys, when the size of the key exceeds a certain limit, it may not provide efficient performance and may even have a negative impact on the stability and overall performance of the Redis instance. assets/Redis高级篇/image-20240226193904401.png

  2. memory usage key

assets/Redis高级篇/image-20240226194600235.png

Delete

Incremental deletion, slowly delete part of the data, and finally del

hash

assets/Redis高级篇/image-20240226195122291.png assets/Redis高级篇/image-20240226195129942.png assets/Redis高级篇/image-20240226195144210.png

list

assets/Redis高级篇/image-20240226195334732.png assets/Redis高级篇/image-20240226195343981.png assets/Redis高级篇/image-20240226195406077.png

set

assets/Redis高级篇/image-20240226195427067.png assets/Redis高级篇/image-20240226195446417.png assets/Redis高级篇/image-20240226195457819.png

zset

assets/Redis高级篇/image-20240226195527418.png assets/Redis高级篇/image-20240226195540329.png assets/Redis高级篇/image-20240226195551971.png

Setting tuning

redis has two deletion modes, blocking and non blocking

LAZY FREEING

assets/Redis高级篇/image-20240226195752876.png

assets/Redis高级篇/image-20240226202308740.png

Cache dual write consistency

redis Serving as a cache, it is divided into two types

  1. Read only cache (data only exists in redis exists in, does not exist inmysql)
  2. Read write cache (has writes and reads)
    1. Synchronous write through strategy assets/Redis Advanced section/image-20240226220618130.png For business requirements that take effect immediately, adopt updating the database first, then deleting the cache
    2. Asynchronous delayed write strategy assets/Redis Advanced section/image-20240226220544159.png logistics and so on, no rush from mysql Update to redis

Cache acquisition strategy: double check locking strategy

With the double-check locking mechanism, only one thread is guaranteed to read from mysql the latest data, then write to mysql, completing data consistency. When other threads acquire the lock and check again, the cache already exists

First attempts to get from redis, if not present then attempts to acquire the lock,

Acquisition mechanism: double-check locking strategy

Get from redis, if redis is empty then get from mysql

assets/Redis高级篇/image-20240226214858699.png assets/Redis高级篇/image-20240226220902417.png assets/Redis高级篇/image-20240226220925126.png


@Service
@Slf4j
public class UserService {
    public static final String CACHE_KEY_USER = "user:";
    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * The business logic is not written wrong, for small and medium companies(QPS《=1000)can be used, but big companies cannot
     * @param id
     * @return
     */
    public User findUserById(Integer id)
    {
        User user = null;
        String key = CACHE_KEY_USER+id;

        //1 First from redis query inside, if there is a direct return result, if not then query mysql
        user = (User) redisTemplate.opsForValue().get(key);

        if(user == null)
        {
            //2 redis no data inside, continue query mysql
            user = userMapper.selectByPrimaryKey(id);
            if(user == null)
            {
                //3.1 redis+mysql no data at all
                //You refine it specifically, to prevent multiple penetration. Our business rule stipulates, record the one causing penetration key write back redis
                return user;
            }else{
                //3.2 mysql yes, need to write data back to redis,ensure the next cache hit rate
                redisTemplate.opsForValue().set(key,user);
            }
        }
        return user;
    }


    /**
     * strengthen and supplement, to avoid sudden key expiration, overwhelming the mysql,Do some prevention, try to avoid breakdown situations.
     * @param id
     * @return
     */
    public User findUserById2(Integer id)
    {
        User user = null;
        String key = CACHE_KEY_USER+id;

        //1 First from redis query inside, if there is a direct return result, if not then query mysql,
        // Number 1 query redis,before locking
        user = (User) redisTemplate.opsForValue().get(key);
        if(user == null) {
            //2 Used by big companies, for high QPS optimization, lock first upon entry, ensure one request operation, let the outside redis wait a bit, avoid breakdown mysql
            synchronized (UserService.class){
                //Number 2 query redis,after locking
                user = (User) redisTemplate.opsForValue().get(key);
                //3 second check redis still null,can query mysql completed(mysql by default has data)
                if (user == null) {
                    //4 query mysql get data(mysql by default has data)
                    user = userMapper.selectByPrimaryKey(id);
                    if (user == null) {
                        return null;
                    }else{
                        //5 mysql has data inside, need to write back redis,complete the data consistency synchronization work
                        redisTemplate.opsForValue().setIfAbsent(key,user,7L,TimeUnit.DAYS);
                    }
                }
            }
        }
        return user;
    }
}

Update strategy

Setting an expiration time for the cache, regularly cleaning the cache and writing back, is a solution to ensure eventual consistency

assets/Redis高级篇/image-20240226222119529.png

Update cache first, then update database

Not recommended, in business mysql is used as the base data, ensure final interpretation

Likewise multi-threaded assets/Redis高级篇/image-20240226223903566.png

Delete cache first, then update database (delayed double delete)

First deleted thereidsdata, then letmysqlperformed the update,mysqlaccessed the data before the update finished, the access got the old value, and would write the old value back toredis, the earlier deletion was wasted, causing the newly updated data not synced toredis,redisthe data has always been dirty, use delayed double delete to solve, that is atmysqldelete redis again after update, the delayed deletion time needs to be estimated

assets/Redis高级篇/image-20240226224102966.png assets/Redis高级篇/image-20240226224244107.png assets/Redis高级篇/image-20240226224307905.png assets/Redis高级篇/image-20240226225114399.png assets/Redis高级篇/image-20240226225139329.png

Solution: delayed double delete strategy

Delete twice before and after updating mysql. The sleep time of thread A responsible for deletion must be greater than the time for thread B to write to redis cache, to prevent other threads from reading old data and writing to redis without it being deleted (second deletion: make deletion asynchronous to avoid throughput reduction)

assets/Redis高级篇/image-20240226225927741.png

  • How long should the deletion sleep

assets/Redis高级篇/image-20240226230156341.png

  • Synchronous elimination strategy, how to handle throughput reduction

Make deletion asynchronous to avoid throughput reduction

The previous solutions are not perfect, even if solved they still require introducing new ones such as delayed double delete

Update database first, then update cache
  1. Exception situation: under multithreading, order gets scrambled assets/Redis高级篇/image-20240226222030956.png
  2. Exception situation: Redis update fails, Redis reads dirty data assets/Redis高级篇/image-20240226221900997.png assets/Redis高级篇/image-20240227113511287.png
Update the database first, then delete the cache (recommended)

The problem is still reading the old value, but after MySQL finishes updating, the old value will be replaced, and the next request will get the latest value, which will not cause other new problems

assets/Redis高级篇/image-20240226230625978.png

But if the deletion fails, there is a problem

Interview

Interview

assets/Redis高级篇/image-20240226203415318.png assets/Redis高级篇/image-20240227113857061.png assets/Redis高级篇/image-20240227114009151.png

Fact consistency is very hard to achieve, generally it is eventual consistency

Summary

assets/Redis高级篇/image-20240227114057438.png assets/Redis高级篇/image-20240227114108573.png

Redis and MySQL Data dual write consistency engineering implementation case

cannel

alibaba/canal: Alibaba MySQL binlog incremental subscription and consumption component (github.com)

How to knowmysqla change occurred? And be able to notifyredis? assets/Redis高级篇/image-20240227121343144.png assets/Redis高级篇/image-20240227122420881.png

Working principle of traditional MySQL master slave replication

assets/Redis高级篇/image-20240227122640454.png

Working principle of canal

assets/Redis高级篇/image-20240227123015632.png

MySQL modifications before using canal
  • MySQL configuration modification assets/Redis高级篇/image-20240227123715932.png

Enable the binlog writing function of MySQL

assets/Redis高级篇/image-20240227123753697.png Afterwards it will become ON assets/Redis高级篇/image-20240227123930027.png

Create a new canal account and grant authorization select * from mysql.user

DROP USER IF EXISTS 'canal'@'%'; CREATE USER 'canal'@'%' IDENTIFIED BY 'canal';   GRANT ALL PRIVILEGES ON . TO 'canal'@'%' IDENTIFIED BY 'canal';   FLUSH PRIVILEGES; SELECT * FROM mysql.user;

Install and configure canal
  1. Download the deployer version and extract it to the/mycanalpath assets/Redis高级篇/image-20240227124349998.png
  2. Configurationassets/Redis高级篇/image-20240227124713097.png assets/Redis高级篇/image-20240227124727411.png Fill in master with your own MySQL address assets/Redis高级篇/image-20240227124737618.png Replace the account with the one you created earlier assets/Redis高级篇/image-20240227124750228.png

Start

/opt/mycanal/binExecute under the path ./startup.sh

Log

mycanal/logs/canal incat canal.log

Writing canal in Java business logic

pom
<dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.client</artifactId>
    <version>1.1.0</version>
</dependency>


<dependency>
		<groupId>redis.clients</groupId>
		<artifactId>jedis</artifactId>
		<version>3.8.0</version>
</dependency>

RedisUtils


public class RedisUtils
{
    public static final String  REDIS_IP_ADDR = "192.168.111.185";
    public static final String  REDIS_pwd = "111111";
    public static JedisPool jedisPool;

    static {
        JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(20);
        jedisPoolConfig.setMaxIdle(10);
        jedisPool=new JedisPool(jedisPoolConfig,REDIS_IP_ADDR,6379,10000,REDIS_pwd);
    }

    public static Jedis getJedis() throws Exception {
        if(null!=jedisPool){
            return jedisPool.getResource();
        }
        throw new Exception("Jedispool is not ok");
    }

}


canal configuration RedisCanalClientExample


public class RedisCanalClientExample
{
    public static final Integer _60SECONDS = 60;
    public static final String  REDIS_IP_ADDR = "192.168.111.185";

    private static void redisInsert(List<Column> columns)
    {
        JSONObject jsonObject = new JSONObject();
        for (Column column : columns)
        {
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
            jsonObject.put(column.getName(),column.getValue());
        }
        if(columns.size() > 0)
        {
            try(Jedis jedis = RedisUtils.getJedis())
            {
                jedis.set(columns.get(0).getValue(),jsonObject.toJSONString());
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }


    private static void redisDelete(List<Column> columns)
    {
        JSONObject jsonObject = new JSONObject();
        for (Column column : columns)
        {
            jsonObject.put(column.getName(),column.getValue());
        }
        if(columns.size() > 0)
        {
            try(Jedis jedis = RedisUtils.getJedis())
            {
                jedis.del(columns.get(0).getValue());
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    private static void redisUpdate(List<Column> columns)
    {
        JSONObject jsonObject = new JSONObject();
        for (Column column : columns)
        {
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
            jsonObject.put(column.getName(),column.getValue());
        }
        if(columns.size() > 0)
        {
            try(Jedis jedis = RedisUtils.getJedis())
            {
                jedis.set(columns.get(0).getValue(),jsonObject.toJSONString());
                System.out.println("---------update after: "+jedis.get(columns.get(0).getValue()));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    public static void printEntry(List<Entry> entrys) {
        for (Entry entry : entrys) {
            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                continue;
            }

            RowChange rowChage = null;
            try {
                //Get the changed row data
                rowChage = RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error,data:" + entry.toString(),e);
            }
            //Get the change type
            EventType eventType = rowChage.getEventType();
            System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",
                    entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                    entry.getHeader().getSchemaName(), entry.getHeader().getTableName(), eventType));

            for (RowData rowData : rowChage.getRowDatasList()) {
                if (eventType == EventType.INSERT) {
                    redisInsert(rowData.getAfterColumnsList());
                } else if (eventType == EventType.DELETE) {
                    redisDelete(rowData.getBeforeColumnsList());
                } else {//EventType.UPDATE
                    redisUpdate(rowData.getAfterColumnsList());
                }
            }
        }
    }


    public static void main(String[] args)
    {
        System.out.println("---------O(∩_∩)O哈哈~ initCanal() main方法-----------");

        //=================================
        // Create the link canal Server side
        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(REDIS_IP_ADDR,
                11111), "example", "", "");
        int batchSize = 1000;
        //Idle no load counter
        int emptyCount = 0;
        System.out.println("---------------------canal init OK,开始监听mysql变化------");
        try {
            connector.connect();
            //connector.subscribe(".*\\..*");
            //The more monitoring,the slower the write back,Only guarantee dual write consistency for important data
            connector.subscribe("bigdata.t_user");
            connector.rollback();
            int totalEmptyCount = 10 * _60SECONDS;
            while (emptyCount < totalEmptyCount) {
                System.out.println("我是canal,每秒一次正在监听:"+ UUID.randomUUID().toString());
                Message message = connector.getWithoutAck(batchSize); // Get a specified amount of data
                long batchId = message.getId();
                int size = message.getEntries().size();
                if (batchId == -1 || size == 0) {
                    emptyCount++;
                    try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                } else {
                    //Reset the counter to zero
                    emptyCount = 0;
                    printEntry(message.getEntries());
                }
                connector.ack(batchId); // Submit confirmation
                // connector.rollback(batchId); // Processing failed, Roll back data
            }
            System.out.println("已经监听了"+totalEmptyCount+"秒,无任何消息,请重启重试......");
        } finally {
            connector.disconnect();
        }
    }
}
Configuration details

Filter regular expression configured for connector.subscribe in the Java program

assets/Redis高级篇/image-20240227133108859.png

try-with-resourcesNo need to manually close anymore assets/Redis高级篇/image-20240227133159757.png assets/Redis高级篇/image-20240227144136857.png

jedisIt closes automatically, so manual closing is not neededfinally

Practical case implementation bitmap/hyperloglog/GEO

hyperloglog

UV/PV/DAU/MAU : unique visitors divided by page views divided by daily active users divided by monthly active users

Cardinality statistics : count the distinct elements in a setcount, it is the calculation of the remaining elements after deduplicating the set, using a probabilistic algorithm, it does not store the data itself, it estimates the cardinality value, while ensuring the error stays within a certain range

0.81 percent error

JAVA microservices ecosystem divided by Redis7 divided by basics divided by Redis introduction > redis cardinality statistics ( HyperLogLog )

assets/Redis高级篇/image-20240227200949823.png

how hyperloglog evolved

Because bitmap used too much memory when the data volume was too large, hyperloglog evolved from it, and the memory reduction effect is significant

assets/Redis高级篇/image-20240227201745289.png assets/Redis高级篇/image-20240227201956254.png assets/Redis高级篇/image-20240227201949379.png assets/Redis高级篇/image-20240227202115350.png

http://antirez.com/news/75

1.04/16384/16384 = 0.0081
assets/Redis高级篇/image-20240227202151936.png

GEO

JAVA Microservices Ecosystem / Redis7 / Basics / Redis Introduction > Geospatial GEO

assets/Redis高级篇/image-20240228115525757.png

JAVA Microservices Ecosystem / Redis7 / Basics / Redis Introduction > Geospatial GEO

assets/Redis高级篇/image-20240228120757914.png

RedisTemplate operations related to GEO

public static final String CITY ="city";  

@Autowired  
private RedisTemplate redisTemplate;
public String geoAdd()  
{  
    Map<String, Point> map = new HashMap<>();  
    map.put("天安门",new Point(116.403963,39.915119));  
    map.put("故宫",new Point(116.403414,39.924091));  
    map.put("长城",new Point(116.024067,40.362639));  
  
    redisTemplate.opsForGeo().add(CITY,map);  
  
    return map.toString();  
}  
  
public Point position(String member) {  
    //Get longitude and latitude coordinates  
    List<Point> list = redisTemplate.opsForGeo().position(CITY, member);  
    return list.get(0);  
}  
  
public String hash(String member) {  
    //geohash Generated by algorithm base32 Encoded value  
    List<String> list = redisTemplate.opsForGeo().hash(CITY, member);  
    return list.get(0);  
}  
  
public Distance distance(String member1, String member2) {  
    //Get the distance between two given locations  
    Distance distance = redisTemplate.opsForGeo().distance(CITY, member1, member2,  
            RedisGeoCommands.DistanceUnit.KILOMETERS);  
    return distance;  
}  
public GeoResults radiusByxy() {  
    //Find nearby places by longitude and latitude,Wangfujing location in Beijing 116.418017,39.914402  
    //Metrics.KILOMETERS.getMultiplier()Radius of the circle in kilometers
    Circle circle = new Circle(116.418017, 39.914402, Metrics.KILOMETERS.getMultiplier());  
    // return 50 items  
    RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortDescending().limit(50);  
  
    GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = redisTemplate.opsForGeo().radius(CITY, circle, args);  
  
    return geoResults;  
}  
public GeoResults radiusByMember() {  
    //find nearby by location  
    return null;  
}

assets/Redis入门/image-20231122210524494.png

assets/Redis高级篇/image-20240228130413769.png

assets/Redis高级篇/image-20240228125751045.png

bitmap

assets/Redis高级篇/image-20240228142123227.png

JAVA Microservices Ecosystem/Redis7/Basics/Redis Introduction > redis bitmap

Bloom filter BloomFilter

assets/Redis高级篇/image-20240228142157788.png

A Bloom filter is a long binary array+hash algorithm constructed from it, it does not store data but determines whether data exists

A Bloom filter uses the property that if it exists it may exist (hash values of different data may be the same), and if it does not exist it definitely does not exist, to quickly determine definitely definitely data that does not exist in the sample / Data existing in the sample(has errors) , filter according to requirements

Efficient insertion and query, uses little space

The returned results are uncertain and not perfect enough

Composed of a bit array initialized to 0 and multiple hash functions, used to quickly determine set whether an element exists in it, and determine whether an interval has data

  • Based on existence or not (the existence judgment has errors)

Whitelist verification: access is allowed only if it exists, and access is not allowed if it does not exist (solves redis cache penetration) Blacklist verification (spam): if it exists, access is not allowed; if it does not exist, access is allowed

assets/Redis高级篇/image-20240430001456570.png assets/Redis高级篇/image-20240228151943075.png

assets/Redis高级篇/image-20240228143534196.png assets/Redis高级篇/image-20240228193741352.png

Principle

We can take redis one of the bitmap data items and make it into a Bloom filter

Principles and operations

assets/Redis高级篇/image-20240228153535104.png

Multiple slots can be combined to indicate whether a data item exists; it exists only when multiple slots are all present assets/Redis高级篇/image-20240228153804694.png assets/Redis高级篇/image-20240228153814155.png

Therefore, after adding data to a Bloom filter, try to minimize deletions

assets/Redis高级篇/image-20240228154706723.png assets/Redis高级篇/image-20240228154713011.png With only one hash function, hash collisions easily occur; multiple hash functions can reduce hash collisions

Usage steps
  • Initialize the bitmap assets/Redis高级篇/image-20240228155539538.png

  • Add and occupy slots assets/Redis高级篇/image-20240228155558184.png

  • Determine whether it exists assets/Redis高级篇/image-20240228155609616.png assets/Redis高级篇/image-20240228155617894.png assets/Redis高级篇/image-20240228160420295.png assets/Redis高级篇/image-20240228160619282.png

Use cases

Solve the cache penetration problem

assets/Redis高级篇/image-20240228160902622.png assets/Redis高级篇/image-20240228161254384.png

Handwritten Bloom filter

Generally no need to hand write, use Google's guava that is enough

Math.abs Compute the absolute value, avoid negative numbers

Handwritten Bloom filter

assets/Redis高级篇/image-20240228162951167.png

After computing the hash, throughhashValueand modulo 2 to the 32nd power, obtain the corresponding index slot

  1. ThroughBloomFlterInitAdd existing data as a whitelist into the Bloom filter

  2. ThroughCheckUtilsCheck whether new data is in the Bloom filter

  3. Obtain the hash value int hashvalues = Math.abs(key.hashCode());

  4. Obtain the slot number long v = (long) (hashvalues % Math.pow(2, 32));

  5. Set the bit redisTemplate.opsForValue().setBit("whitelistCustomer",v, true);

Bloom filter, add @component, @PostConstruct

Customize one key value For example whitelistCustomer, the data type is bitmap, this data type bitmap is used as the Bloom filter

Compute the values of other keys, obtain the corresponding slots, and set them totrue

BloomFilterInit

* Bloom filter whitelist initialization utility class, at the start set part of the data as whitelist,   
* Whitelist business default rule: if the Bloom filter says yes, redis it is very likely yes. 


@Component
public class BloomFilterInit  
{  
    @Resource  
    private RedisTemplate redisTemplate;  
  
    @PostConstruct//Initialize whitelist data,Temporarily commented out to save background printing  
    public void init()  
    {  
        //1 Load whitelist customers into the Bloom filter  
        String key = "customer:12";  
        //2 Compute hashValue,Since there is a possibility of computing a negative number, we take the absolute value  
        int hashValue = Math.abs(key.hashCode());  
        //3 Through hashValue and 2 of the 32 after power, take the remainder to obtain the corresponding index slot  
        long index = (long)(hashValue % Math.pow(2,32));  
        log.info(key+" 对应的坑位index:{}",index);  
        //4 Set redis inside bitmap corresponding type whitelist: whitelistCustomer slot, set this value to 1  
        redisTemplate.opsForValue().setBit("whitelistCustomer",index,true);  
  
    }  
}

CheckUtils Similarly compute the slot value, check whether it is in the Bloom filter

checkItem represents the Bloom filter

CheckUtils
public class CheckUtils  
{  
    @Resource  
    private RedisTemplate redisTemplate;  
  
    public boolean checkWithBloomFilter(String checkItem,String key)  
    {  
        int hashValue = Math.abs(key.hashCode());  
        long index = (long)(hashValue % Math.pow(2,32)); 
        ////checkItem is key value 
        boolean existOK = redisTemplate.opsForValue().getBit(checkItem,index);  
        log.info("--->key:"+key+" 对应坑位下标index: "+index+" 是否存在:"+existOK);  
  
        return existOK;  
    }  
}
java
public Customer findCustomerByIdWithBloomFilter (Integer customerId)  
{  
    Customer customer = null;  
    //cache key name  
    String key = CACHA_KEY_CUSTOMER + customerId;  
  
    //Bloom filter check,No is absolutely no, yes is possibly yes  
    //===============================================  
    if(!checkUtils.checkWithBloomFilter("whitelistCustomer",key))  
    {  
        log.info("白名单无此顾客,不可以访问: "+key);  
        return null;    }  
    //===============================================  
  
    //1 Query redis  
    customer = (Customer) redisTemplate.opsForValue().get(key);  
    //redis No, further query mysql  
    if (customer == null) {  
        //2 From mysql found customer  
        customer = customerMapper.selectByPrimaryKey(customerId);  
        // mysql Yes, redis No  
        if (customer != null) {  
            //3 Write the mysql retrieved data into redis,to make the next query able to redis hit.   
            redisTemplate.opsForValue().set(key, customer);  
        }  
    }  
    return customer;  
}

Besides Bloom, there is also the Cuckoo filter

mapper

abel533/Mapper: Mybatis Common Mapper - Easy to use (github.com)

https://www.bilibili.com/video/BV13R4y1v7sP?p=121&vd_source=17535a06cd4587318e9b67ac64afeab7

Cache warmup + cache avalanche + cache breakdown + cache penetration

assets/Redis高级篇/image-20240228194654216.png

Cache warmup

Proactively place data into redis first

  1. @PostConstruct Initialize whitelist data (also used to initialize the Bloom filter)
  2. From the configuration file application.yml Inject
@Value("${whitelist.ips}") // Inject IP whitelist
private String ips;
@Value("${whitelist.users}") // Inject user whitelist
private String users;

Cache avalanche

Cache avalanche

redis went down, or redis simultaneously hasa large amount key large-scale failure

Solution:

  1. staggered expiration
  2. redis in the hotspot key set to never expire, stagger expiration times
  3. redis Cache cluster achieves high availability: master slave plus sentinel, cluster,AOF/RDB
  4. circuit breaking and degradation service circuit breaking/service degradation

Cache penetration

Every query redis is not in it, from beginning to end it is never there, directly reaching mysql causing database pressure to surge

assets/Redis高级篇/image-20240229114927835.png

assets/Redis高级篇/image-20240430000312389.png assets/Redis高级篇/image-20240430000419379.png

Solution

  1. Null object caching (use default value) the penetrating ones are further cached into redis in it, hackers will use different key to update, will write many irrelevant key,grows larger and larger

  2. Google Bloom filter Guava(authoritative and recommended) Guava good decoupling small misjudgment probability is acceptable all legal key put into Guava version Bloom filter +redis inside, otherwise data returns null


<!--guava Google open source Guava built in Bloom filter-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>

Guava whitelist example

The smaller the misjudgment rate, the more slots generated and the more hash functions used

However, increasing the number of hash functions also increases computational overhead. Usually, a reasonable number of hash functions can reduce computational cost while maintaining a low misjudgment rate

assets/Redis高级篇/image-20240228230943050.png assets/Redis高级篇/image-20240228231126403.png

This 10 ten thousand pieces of data are not in the Bloom filter,should all not exist,but due to filter misjudgment may be judged as existing

@Service
@Slf4j
public class GuavaBloomFilterService
{
    //1 define a constant
    public static final int _1W = 10000;
    //2 define our guava Bloom filter, initial capacity
    public static final int SIZE = 100 * _1W;
    //3 misjudgment rate, the smaller it is the fewer misjudgments there are(Think, can it be infinitely small? Would it not be better without false positives?)
    public static double fpp = 0.01;//0.01 0.000000000000001
    //4 Create guava Bloom filter
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), SIZE,fpp);


    public void guavaBloomFilter()
    {
        //1 First let bloomFilter add 100W whitelist data
        for (int i = 1; i <= SIZE  ; i++) {
            bloomFilter.put(i);
        }
        //2 deliberately take 10W pieces of data not in the legal range,to demonstrate the false positive rate
        ArrayList<Integer> list = new ArrayList<>(10 * _1W);

        //3 Verify
        //the filter only added SIZE data,deliberately from SIZE+1 data outside to start judging,false positives appear
        for (int i = SIZE+1; i <= SIZE+(10 * _1W) ; i++)
        {
            if(bloomFilter.mightContain(i))
            {
                log.info("被误判了:{}",i);
                list.add(i);
            }
        }
        log.info("误判总数量:{}",list.size());
    }
}

Guava blacklist example

assets/Redis高级篇/image-20240228231604404.png

Cache breakdown

a certain key is heavily requested, this key happens to expire, causing a large number of requests to hit the database, resulting in cache breakdown assets/Redis高级篇/image-20240229115521197.png

Some hackers do this at the instant an event ends, at this instant a batch of redis is deleted, new ones imported redis, hackers exploit this instant to carry out cache breakdown

Solutions: double check locking, mutual exclusion update, random backoff, staggered expiration times

Mutual exclusion update: similar to double check locking, but more focused on ensuring only one thread updates the cache, while other threads wait for the update to complete and then directly read the cache.

Random backoff : when the cache expires, different requests apply a random delay before accessing the database, this avoids a large number of requests accessing the database at the same time. Affects user experience.

Reason

For example, Double Eleven business only runs for a few days, after the time passes it is cleared but then accessed again assets/Redis高级篇/image-20240229123022599.png assets/Redis高级篇/image-20240229131954946.png Right after deletion, other threads may access it again

  • Solution
  1. Use the earlier double check locking on read
  2. Staggered expiration times, or for frequently accessed hot keys, simply do not set an expiration time
  3. Use two copies of data A expired B as backup, A not yet finished updating when deleting, at this time there is B holding up

Summary

assets/Redis高级篇/image-20240229190353764.png

Hand write a Redis distributed lock

(lock name|UUID|counter)``redisLock:uuid:valueNumber

Lock name : all are the same, ensuring uniqueness, there is only one lock UUID = factory UUID + thread UUID : determine whether the lock was created by oneself, so as to judge whether it can be deleted (ensuring only the creator of the lock has permission to operate on it), and whether reentrant operations are possible, but the UUID in the lock is bound to the thread

Use Hset to build a distributed lock

assets/Redis高级篇/image-20240229191750770.png

Since distribution involves multiple virtual machines, while Java a lock can only manage one virtual machine, single threaded thread lock mechanism no longer works, at this point a lock that all threads can access and acquire is needed (can be through redis or zookeeper to build), to work across processes and services

assets/Redis高级篇/image-20240229195626005.png

What a qualified redis lock requires

assets/Redis高级篇/image-20240229211008632.png

Use the string data type in redis (can be improved to hash when reentrant locks are needed), create a key with a fixed and unique value, as a distributed lock, other threads must compete for this unique redis lock before they can operate, the standalone version of the lock (synchronized/lock) can only manage the current thread itself

Prevent multithreaded spurious wakeup

  • Question 1 : prevent multithreaded spurious wakeup, do not use if judgment, but instead use while,whileinwaitafter waking up, perform anotherwhilejudgment, whileifwill not, similarly recursion is prone to errors under high concurrency, so at this time usespinto replace the method retry call

if you cannot grab it you need to retry, if you use if, then you need recursion to retry, too many requests may explode StackOverflowError assets/Redis高级篇/image-20240301131741828.png in order to prevent multithreading spurious wakeup, do not useifjudgment, but instead usewhile

while it can keep running when the condition is met, while if wants to keep running it needs to recurse this method

assets/Redis高级篇/image-20240725113135093.png

  • improvement, avoiding StackOverflowError assets/Redis高级篇/image-20240301132338693.png it only ends when the lock is obtained

ensure expiration and creation are atomic

  • Problem 2: The problem caused by downtime, need to ensure expiration and creation are atomic assets/Redis高级篇/image-20240301164214540.png after obtaining the lock, one of the Java services A suddenly goes down, and the created distributed lock without a set time still exists, and without a set expiration time it will exist forever, need to add an expiration time assets/Redis高级篇/image-20240301164351655.png this piece of code does not have atomicity, it may happen that the lock is grabbed, but then it crashes without setting the expiration time (program crash, server downtime, etc.), ensuring that the expiration time is set at the same time the lock is grabbed means it is atomic

how to prevent mistakenly deleting the key upon expiration

  • Problem 3: How to prevent mistakenly deleting the key upon expiration, the expiration time is reached and the lock is already invalid but the task is not yet complete, another thread obtains the lock, and afterwards when it completes it deletes, but it deletes another thread lock

the lock, that is the key, by setting the same, value set to uuid, then by judging the value it is judged whether the key was created by oneselfof

because the keys are all the same, if the key exists it cannot be created, so after expiration another thread creates a new lock, and once its own lock has expired, it will not delete the lock created by others

assets/Redis高级篇/image-20240301193550454.png

before assets/Redis高级篇/image-20240301193518166.png

now: judge whether it is one own,

assets/Redis高级篇/image-20240301193529989.png but this delete operation still does not have atomicity

lua script: makes the delete atomic

Distributed Locks with Redis | Redis

how can the two operations of judgment and deletion be atomic?

  • Problem four: deletion does not have atomicity, need lua script, if it crashes right after the judgment then the deletion will not happen

assets/Redis高级篇/image-20240302122044509.png

purpose

redis call lua the script through eval command guarantees the atomicity of code execution, directly return returns the result value after the script execution

assets/Redis高级篇/image-20240301210203079.png

lua the purpose of the script is to combine multiple commands into one execution, such as the above three becoming a one time execution assets/Redis高级篇/image-20240301212033905.png

syntax format

assets/Redis高级篇/image-20240301214338095.png

nil represents returning empty, equivalent to false in java

nil=false 1=true 0=false

  • lua syntax format

assets/Redis高级篇/image-20240302115152920.png

assets/Redis高级篇/image-20240302115734714.png

end" the number of parameters after firstin orderdistributed to KEYS, then distributed to ARGV EVAL in the command,KEYS After the number of parameters, all subsequent parameters are considered as ARGV parameters

Usage of Lua scripts in Java

assets/Redis高级篇/image-20240302122256572.png

Execute lua For scripts, it is recommended to use DefaultRedisScript()

stringRedisTemplate.execute(new DefaultRedisScript(luaScript,Boolean.class), Arrays.asList(key),uuidValue);

Arrays.asList(key)==KEYS[]
uuidValue==ARGV[]

assets/Redis高级篇/image-20240302122440182.png

nil = null 1 = true 0 = false The return value has three sets of values

So use Boolean.class to accept, or you can use Long.class to accept But Boolean.class The type cannot directly accept the value returned by Redis nil value

Redis Lua script return value to Java Boolean conversion rules: when trying to convert the return value of a Redis Lua script to Boolean it follows certain rules. Usually, Redis integer return values are converted like this: 0 will be converted to false, any non-zero integer (usually 1 or other positive/negative numbers) will be converted to true

Lua language truth and false value rules: In Lua logical judgment (for example if condition),lua considers 0 and empty string are all true, except false and nil are false, all other values are true.

Note

If the lua script already has double quotes outside, change them to single quotes inside

assets/Redis高级篇/image-20240302115013449.png

Boolean represents the return value

Reentrant lock

assets/Redis高级篇/image-20240302134425020.png

After a method in the same thread acquires the lock, if the method it calls also needs to acquire the lock, how should it be designed?

After a business in redis acquires the lock, if next time it is still acquired by itself, how should it be allocated?

assets/Redis高级篇/image-20240302134210350.png

After a method in a thread acquires the lock, if a method inside the method needs the lock again, the previously acquired lock can be reused

Each lock object has alock counterand apointer to the thread holding the lock。

Implicit locks such as synchronized are all reentrant locks

assets/Redis高级篇/image-20240302135802993.png assets/Redis高级篇/image-20240302135813958.png

Principle

assets/Redis高级篇/image-20240302135829392.png

Synchronized block

assets/Redis高级篇/image-20240302135235376.png

Synchronized method

assets/Redis高级篇/image-20240302135252976.png

Explicit lock

assets/Redis高级篇/image-20240302140213199.png

You only need the redis lock you write to have lock and unlock functions, as well as a lock counter and it will do

Only in redis hash satisfies kkv structure

(lock name|UUID|counter)``redisLock:uuid:valueNumber

The lock name is the same, the UUID is only used for judgment when deleting, and for judgment during reentrant locking

Therefore use hash version redis lock instead of the previous string version

LOCK

In order to lock hope to extract the following section

hope to extract this section into a method and turn it into lock

assets/Redis高级篇/image-20240302172820416.png

  • Lock : Lua script for locking and counting

Check if the lock exists. If it exists, create it, set the count to 1 by default, and set the expiration time If it is determined to exist and belongs to itself, it means reuse. Set the counter to increment by one and set the expiration time

Return 0 if not present, return 1 if present

assets/Redis高级篇/image-20240302174642457.png Optimize to assets/Redis高级篇/image-20240302175013029.png Then rewrite as a Lua script

hincrbyIf it does not exist, it will be created automatically assets/Redis高级篇/image-20240302181352573.png

lua In the script, return to java the data, that is, ruturn in:0 will be converted to false, any non-zero integer (usually 1 or other positive or negative numbers) will be converted to true

in the Lua script: call The method returns 0 (does not exist) or 1 (exists)

The truth and falsehood rules of the Lua language itself: in Lua logical judgments (for example if condition),lua considers 0 and empty string are both true, except false and nil are false, all other values are true.

unLOCK

unlock : Extract this section into a method to become unlock

Check whether the lock exists and belongs to itself, then decrement the counter by one Check if the counter is zero and delete it directly If it does not exist, return directly

call method return 0 , it usually indicates successful execution and returns an integer value, which is equivalent tojavainreturn

assets/Redis高级篇/image-20240302173502065.png

The counter decrements to 0, indicating that everything has been unlocked and it can be deleted

assets/Redis高级篇/image-20240302182656392.png

Currently UUID is generated in the lock. Reusing the lock will cause the UUID to differ. Later, a factory will be used to create locks to solve this

lua Integrate into java program

Implement Lock lock, designed according to the Lock lock specification

Introduce the factory pattern: it means creating various types for you in advance so you can choose which to use

assets/Redis高级篇/image-20240303122911227.png

  • Problem

The uuid is created in the lock, but when locks are created through the factory, the factory creates a new lock on reuse, causing the uuid to differ, and the same thread ends up with two uuids To ensure that the same thread shares one lock, it needs to be set to the same UUID

Therefore, the lock factory introduces a fixed uuid, so that different threads generate the same uuid, while in the Redis lock the uuid is the thread factory uuid plus the corresponding thread

Solution: create the UUID in the factory, not in the lock

  • Problem

Sometimes business execution takes a long time, how to automatically renew the duration

Through java timer timer

Write a Lua script for automatic renewal, for example, if it has not completed after 10s, automatically extend the time

Extension script assets/Redis高级篇/image-20240303183102388.png

DistributedLockFactory factory
@Component  
public class DistributedLockFactory  
{  
    @Autowired  
    private StringRedisTemplate stringRedisTemplate;  
    private String lockName;  
    private String uuid;  
  
    public DistributedLockFactory()  
    {
        this.uuid = IdUtil.simpleUUID();
    }  
  
    public Lock getDistributedLock(String lockType)  
    {
        if(lockType == null) return null;  
  
        if(lockType.equalsIgnoreCase("REDIS")){  
            this.lockName = "zzyyRedisLock";  
            return new RedisDistributedLock(stringRedisTemplate,lockName,uuid);  
        }else if(lockType.equalsIgnoreCase("ZOOKEEPER")){  
            this.lockName = "zzyyZookeeperLockNode";  
            //TODO zookeeper version distributed lock  
            return null;  
        }else if(lockType.equalsIgnoreCase("MYSQL")){  
            //TODO MYSQL version distributed lock  
            return null;  
        }  
  
        return null;  
    }  
}

timer is java timer, use timer to implement renewal

RedisDistributedLock

lock()will call tryLock() tryLock()CalltryLock(long time, TimeUnit unit)


import cn.hutool.core.util.IdUtil;  
import org.springframework.data.redis.core.StringRedisTemplate;  
import org.springframework.data.redis.core.script.DefaultRedisScript;  
import org.springframework.stereotype.Component;  
  
import java.util.Arrays;  
import java.util.Timer;  
import java.util.TimerTask;  
import java.util.concurrent.TimeUnit;  
import java.util.concurrent.locks.Condition;  
import java.util.concurrent.locks.Lock;  


/**  
 * @auther zzyy  
 * @create 2023-01-09 16:41  
 * our self developed redis distributed lock, implemented Lock interface  
 */  
//@Component imported DistributedLockFactory factory pattern, just get it from the factory  
public class RedisDistributedLock implements Lock  
{  
    private StringRedisTemplate stringRedisTemplate;  
  
    private String lockName;//KEYS[1]  
    private String uuidValue;//ARGV[1]  
    private long   expireTime;//ARGV[2]  
  
  
    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuid)  
    {  
        this.stringRedisTemplate = stringRedisTemplate;  
        this.lockName = lockName;  
        this.uuidValue = uuid+":"+Thread.currentThread().getId();  
        this.expireTime = 30L;  
    }  
  
  
    @Override  
    public void lock()  
    {  
        tryLock();  
    }  
    @Override  
    public boolean tryLock()  
    {  
        try {tryLock(-1L,TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}  
        return false;  
    }  
  
    @Override  
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException  
    {  
        if(time == -1L)  
        {  
            String script =  
                    "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then    " +  
                            "redis.call('hincrby',KEYS[1],ARGV[1],1)    " +  
                            "redis.call('expire',KEYS[1],ARGV[2])    " +  
                            "return 1  " +  
                    "else   " +  
                            "return 0 " +  
                    "end";  
            System.out.println("lockName:"+lockName+"\t"+"uuidValue:"+uuidValue);  
            //the execution script is true success, but while in it is false will jump out of the loop, that is, negation is needed  
            while(!stringRedisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList(lockName), uuidValue,String.valueOf(expireTime)))  
            {  
                //pause 60 milliseconds  
                try { TimeUnit.MILLISECONDS.sleep(60); } catch (InterruptedException e) { e.printStackTrace(); }  
            }  
            //create a new background scanning program to persist key the current ttl,whether it reaches our specified 1/2 1/3 to implement renewal  
            renewExpire();  
            return true;        }  
        return false;  
    }  
  
  
    //execute the unlock script  
    @Override  
    public void unlock()  
    {  
        System.out.println("unlock(): lockName:"+lockName+"\t"+"uuidValue:"+uuidValue);  
        String script =  
                "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then    " +  
                        "return nil  " +  
                "elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then    " +  
                        "return redis.call('del',KEYS[1])  " +  
                "else    " +  
                        "return 0 " +  
                "end";  
        // nil = null 1 = true 0 = false the return value has three sets of values  
        Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));  
  
        //if there is no lock it will return null,indicating timeout, the own lock will automatically disappear, and it will not affect other threads creating their own locks (key:value,number)  
        if(null == flag)  
        {  
            throw new RuntimeException("this lock doesn't exists,o(╥﹏╥)o");  
        }  
    }  
	  //automatic renewal
    private void renewExpire()  
  
    {  
        String script =  
                "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then     " +  
                        "return redis.call('expire',KEYS[1],ARGV[2]) " +  
                "else     " +  
                        "return 0 " +  
                "end";  
        //timer is java the timer of  
        new Timer().schedule(new TimerTask()  
        {  
            @Override  
            public void run()  
            {  
                if (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime)))  
                {  
                    renewExpire();  
                }  
            }  
        },(this.expireTime * 1000)/3);  
    }  
  
  
    //====the following two are temporarily unused, no longer rewritten  
    //no need to consider interruption,just delete it when done  
    @Override  
    public void lockInterruptibly() throws InterruptedException  
    {  
  
    }  
    @Override  
    public Condition newCondition()  
    {  
        return null;  
    }  
}
//V8.0, the completion of the automatic renewal function, a custom background scanning program, if the business logic is not completed within the specified time, it will call the script for adding time and automatic renewal
//V8.0,the completion of the automatic renewal function, a custom background scanning program, if the business logic is not completed within the specified time, it will call the script for adding time and automatic renewal  
public String sale()  
{  
    String retMessage = "";  
  
    Lock redisLock = distributedLockFactory.getDistributedLock("redis");  
    redisLock.lock();  
    try    {  
        //1 query inventory information  
        String result = stringRedisTemplate.opsForValue().get("inventory001");  
        //2 determine whether the inventory is sufficient  
        Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);  
        //3 deduct inventory, reduce by one each time  
        if(inventoryNumber > 0)  
        {  
            stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));  
            retMessage = "成功卖出一个商品,库存剩余:"+inventoryNumber;  
            System.out.println(retMessage+"\t"+"服务端口号"+port);  
            //pause 120 seconds thread,intentionally, to demonstrate the automatic renewal function......  
            try { TimeUnit.SECONDS.sleep(120); } catch (InterruptedException e) { e.printStackTrace(); }  
  
        }else{  
            retMessage = "商品卖完了,o(╥﹏╥)o";  
        }  
    }finally {  
        redisLock.unlock();  
    }  
    return retMessage+"\t"+"服务端口号"+port;  
}

CAP

assets/Redis高级篇/image-20240303180011939.png

interview

how to write?

First, according to Java in juc the in LOCK standard implementation method,

locking logic : Use Hset , KKV structure to perform locking, through while retry at a certain interval, continuously spin to determine whether the lock can be obtained, through lua script to ensure atomicity, and through renewal to ensure that long duration businesses can automatically renew and smoothly complete the business assets/Redis高级篇/image-20240303202214402.png

unlock logic : ensure that it is your own lock, only you can delete your own lock, through the lock counter, it can only be deleted when it is 0

assets/Redis高级篇/image-20240303202226298.png

Use Redis to implement distributed locks | Redis --- Distributed Locks with Redis | Redis

Redlock algorithm and underlying source code analysis

使用Redis实现分布式锁 _ Redis --- Distributed Locks with Redis _ Redis (03_03_2024 21_53_05) ↗

redisson/redisson

single point of failure

Single point of failure: because replication in Redis was previously asynchronous, a sudden downtime can cause the lock to disappear. After thread one obtains the lock, the master goes down before writing the lock to the slave, and then the slave is elected as the master. After thread two sees that the new master has no lock, it creates its own lock. At this point there appeartwo or more threads obtained the lock, this situation is strictly forbidden, the solution is to abandon the slave node, and instead create a lock node, and the lock can only be obtained from the lock node

assets/Redis高级篇/image-20240303214357337.png

assets/Redis高级篇/image-20240303214717045.png assets/Redis高级篇/image-20240303180011939.png

Redlock

8. Distributed locks and synchronizers · redisson/redisson Wiki (github.com)

redisson/redisson: Redisson uses multiple reids, each one is master, without using replication or any other implicit coordination system

Redlock

The second step is to try to acquire the lock on each Redis instance in turn, and if the timeout is exceeded, move directly to the next Redis acquisition. The time consumed by finally trying all Redis instances, minus the time consumed by the first step, is the time taken to acquire the lock

assets/Redis高级篇/image-20240303220712637.png assets/Redis高级篇/image-20240303220724870.png

Fault tolerance formula

2x+1

x is the number of failed nodes

assets/Redis高级篇/image-20240303220943011.png

Configuration

1. change pom

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.19.1</version>
        </dependency>


2. in configuration in redisConfig the configuration in (or the configuration yaml)

@Bean  
public Redisson redisson()  
{  
    Config config = new Config();  
    //add multiple,this is only a demonstration
    config.useSingleServer().setAddress("redis://192.168.111.185:6379").setDatabase(0).setPassword("111111");  
    
  // or read config from file
//config = Config.fromYAML(new File("config-file.yaml")); 

    return (Redisson) Redisson.create(config);  
}

  • Problem

redlock also need to determine whether it is your own lock

//V9.1, introduce the RedLock algorithm implementation class recommended by the official website corresponding to Redisson

@Autowired  
private StringRedisTemplate stringRedisTemplate;

@Value("${server.port}")  
private String port;

@Autowired
private DistributedLockFactory distributedLockFactory;  

//V9.1,introduce Redisson the recommendation from the official website corresponding to RedLock algorithm implementation class
@Autowired  
private Redisson redisson;  
public String saleByRedisson()  
{  
    String retMessage = "";  
  
    RLock redissonLock = redisson.getLock("zzyyRedisLock");  
    redissonLock.lock();  
  
    try{
        //1 query inventory information
        String result = stringRedisTemplate.opsForValue().get("inventory001");  
        //2 determine whether the inventory is sufficient
        Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);  
        //3 deduct inventory, decreasing by one each time
        if(inventoryNumber > 0)  
        {  
            stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));  
            retMessage = "成功卖出一个商品,库存剩余:"+inventoryNumber;  
            System.out.println(retMessage+"\t"+"服务端口号"+port);  
        }else{  
            retMessage = "商品卖完了,o(╥﹏╥)o";  
        }  
    }finally {  
        //improvement point, can only delete your own key,cannot delete others  
        if(redissonLock.isLocked() && redissonLock.isHeldByCurrentThread())  
        {  
            redissonLock.unlock();  
        }  
    }  
    return retMessage+"\t"+"服务端口号"+port;  
}

Source code analysis

still islocking, reentrancy, renewal, unlocking

redissonLock default expiration time 30 seconds,

java

the newly created default 30 seconds assets/Redis高级篇/image-20240305160545983.png assets/Redis高级篇/image-20240305160552204.png

Locking process

scheduleExpriationRenewalRenew the cache

assets/Redis高级篇/image-20240305160911218.png assets/Redis高级篇/image-20240305161528860.png

Renewal

assets/Redis高级篇/image-20240305161545972.png assets/Redis高级篇/image-20240324192852271.png

assets/Redis高级篇/image-20240324192832406.png

Unlock

assets/Redis高级篇/image-20240324192923778.png

Multi machine case

Redis master slave based on failover cannot achieve true redlock

java

assets/Redis高级篇/image-20240324204344371.png assets/Redis高级篇/image-20240324204356828.png


        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.19.1</version>
        </dependency>


server.port=9090
spring.application.name=redlock


spring.swagger2.enabled=true


spring.redis.database=0
spring.redis.password=
spring.redis.timeout=3000
spring.redis.mode=single
//pooling technology
spring.redis.pool.conn-timeout=3000
spring.redis.pool.so-timeout=3000
spring.redis.pool.size=10

spring.redis.single.address1=192.168.111.185:6381
spring.redis.single.address2=192.168.111.185:6382
spring.redis.single.address3=192.168.111.185:6383

Redis cache expiration eviction policy

assets/Redis高级篇/image-20240324221518475.png

Memory settings

assets/Redis高级篇/image-20240324223009410.png

If it is not set or is set to 0, on the 64 bit operating system the memory size is not limited, on the 32 bit operating system at most uses 3GB , generally it is recommended to set Redis memory tothree quarters

maxmemory maxmemory 128mb exceeding the memory will explode OOM

Modify : configuration file/command config set maxmemory xxx

View : info memory / config get maxmemory

Expiration key deletion policy

If a key is expired, is it immediately deleted from memory after the expiration time is reached?

Immediate deletion

Bad for CPU, exchange processor performance for storage space (time for space)

assets/Redis高级篇/image-20240324223716667.png

Lazy deletion

Enable : lazyfree-lazy-eviction=yes

Not memory friendly, trade storage space for processor performance (space for time)

Only scans accessed keys; keys that are not accessed are not known even if expired, and will not be deleted assets/Redis高级篇/image-20240324223731390.png

Periodic deletion

Periodic deletion is a compromise between the first two inspection strategies

Sample at intervals a portion key Check whether there are expired keys; some will slip through the net

assets/Redis高级篇/image-20240324230500854.png

Cache eviction policy

lru and lfu What is the difference from the algorithm

LRU: most recently least used page replacement algorithm, in terms of time LFU: most recently least frequently used page replacement algorithm, in terms of frequency

assets/Redis高级篇/image-20240325191116616.png

Eight types in total Two dimensions: filter among expired keys/filter among all keys Four aspects: LRU/LFU/random/ttl

assets/Redis高级篇/image-20240325192158807.png assets/Redis高级篇/image-20240325192218356.png

Configuration

assets/Redis高级篇/image-20240325192428595.png

Cache configuration suggestions

assets/Redis高级篇/image-20240325192547187.png

Redis classic five major types source code and underlying implementation

This chapter is entirely for interviews, there is not much meaning for learning

Interview

assets/Redis高级篇/image-20240325195618815.png assets/Redis高级篇/image-20240325195628669.png assets/Redis高级篇/image-20240325195638618.png

assets/Redis高级篇/image-20240325195646954.png

High performance design, in depth analysis of epoll and IO multiplexing

Summary

Materials

  1. Starting mindset: official website
  2. Troubleshooting mindset: logs

Redis脑图(基础篇+高级篇) ↗

^u0ikb3

2020 Shang Silicon Valley Java big factory interview questions

Shang Silicon Valley 50 Java interview questions