Single-threaded and multi-threaded Redis
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
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
Why multi-threading was added later
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
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
That is, network IO is multi-threaded, the thread that actually does the work is still single-threaded
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

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
redisand multiplexing
redis Thread safety issues will not occur because the main command execution is still completed by the main thread
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
Set
io-thread-do-readsThe configuration item is yes, which means multithreading is enabled.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.
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
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
- After creating
scanParams scanfill 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();
Big key
How to discover big keys
redis-cli xxxx Add at the end --bigkeys
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.
memory usage key
Delete
Incremental deletion, slowly delete part of the data, and finally del
Setting tuning
redis has two deletion modes, blocking and non blocking
Cache dual write consistency
redis Serving as a cache, it is divided into two types
- Read only cache (data only exists in
redisexists in, does not exist inmysql) - Read write cache (has writes and reads)
- 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
- 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
@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
Update cache first, then update database
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
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)
- How long should the deletion sleep
- 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
Update the database first, then delete the cache (recommended)
Interview
Fact consistency is very hard to achieve, generally it is eventual consistency
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?

MySQL modifications before using canal
Enable the binlog writing function of MySQL
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
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("================> 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
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 )
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
GEO
JAVA Microservices Ecosystem / Redis7 / Basics / Redis Introduction > Geospatial GEO
JAVA Microservices Ecosystem / Redis7 / Basics / Redis Introduction > Geospatial GEO
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;
}
bitmap
JAVA Microservices Ecosystem/Redis7/Basics/Redis Introduction > redis bitmap
Bloom filter BloomFilter
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
Principle
We can take redis one of the bitmap data items and make it into a Bloom filter
Principles and operations
Multiple slots can be combined to indicate whether a data item exists; it exists only when multiple slots are all present

Therefore, after adding data to a Bloom filter, try to minimize deletions
With only one hash function, hash collisions easily occur; multiple hash functions can reduce hash collisions
Use cases
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
After computing the hash, throughhashValueand modulo 2 to the 32nd power, obtain the corresponding index slot
Through
BloomFlterInitAdd existing data as a whitelist into the Bloom filterThrough
CheckUtilsCheck whether new data is in the Bloom filterObtain the hash value
int hashvalues = Math.abs(key.hashCode());Obtain the slot number
long v = (long) (hashvalues % Math.pow(2, 32));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
Cache warmup
Proactively place data into redis first
@PostConstructInitialize whitelist data (also used to initialize the Bloom filter)- From the configuration file
application.ymlInject
@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:
- staggered expiration
redisin thehotspot keyset tonever expire, stagger expiration timesredisCache cluster achieves high availability: master slave plus sentinel, cluster,AOF/RDB- 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
Solution
Null object caching (use default value) the penetrating ones are further cached into
redisin it, hackers will use differentkeyto update, will write many irrelevantkey,grows larger and largerGoogleBloom filterGuava(authoritative and recommended) Guava good decoupling small misjudgment probability is acceptable all legalkeyput intoGuavaversion Bloom filter+redisinside, otherwise data returnsnull
<!--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
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());
}
}
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

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
- Solution
- Use the earlier double check locking on read
- Staggered expiration times, or for frequently accessed hot keys, simply do not set an expiration time
- 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
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
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
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 useifjudgment, but instead usewhile,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
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
ensure expiration and creation are atomic
- Problem 2: The problem caused by downtime, need to ensure expiration and creation are atomic
after obtaining the lock, one of the Java services Asuddenly 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
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
now: judge whether it is one own,
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
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
lua the purpose of the script is to combine multiple commands into one execution, such as the above three becoming a one time execution

syntax format
nil represents returning empty, equivalent to false in java
nil=false
1=true
0=false
- lua syntax format
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
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[]
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
Boolean represents the return value
Reentrant lock
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?
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
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
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
Optimize to
Then rewrite as a Lua script
hincrbyIf it does not exist, it will be created automatically

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
The counter decrements to 0, indicating that everything has been unlocked and it can be deleted
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
- 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
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
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

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
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) ↗
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
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
Fault tolerance formula
2x+1
x is the number of failed nodes
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
Multi machine case
Redis master slave based on failover cannot achieve true redlock
java
<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
Memory settings
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)
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

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
Cache eviction policy
lruandlfuWhat 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
Eight types in total
Two dimensions: filter among expired keys/filter among all keys
Four aspects: LRU/LFU/random/ttl
Configuration
Cache configuration suggestions
Redis classic five major types source code and underlying implementation
This chapter is entirely for interviews, there is not much meaning for learning
High performance design, in depth analysis of epoll and IO multiplexing
Summary
Materials
- Starting mindset: official website
- Troubleshooting mindset: logs
^u0ikb3
2020 Shang Silicon Valley Java big factory interview questions




































































































































































