Home  >  Article  >  Database  >  Let's talk about using redis to implement distributed caching

Let's talk about using redis to implement distributed caching

WBOY
WBOYforward
2022-07-14 17:01:172764browse

This article brings you relevant knowledge about Redis, which mainly organizes issues related to distributed cache. Distributed means that it consists of multiple applications, which may be distributed on different servers. Above, we are ultimately providing services for the web side. Let’s take a look at them together. I hope it will be helpful to everyone.

Let's talk about using redis to implement distributed caching

Recommended learning: Redis video tutorial

Distributed cache description:

The focus of distributed caching is on distribution. I believe you have come into contact with many distributed ones, such as distributed development, distributed deployment, distributed locks, things, systems, etc. There are many. This gives us a clear understanding of distribution itself. Distribution is composed of multiple applications, which may be distributed on different servers, and ultimately provide services to the web side.
Distributed caching has the following advantages:

  1. The cached data on all Web servers is the same, and the cached data will not be different due to different applications and different servers. .
  2. The cache is independent and not affected by the restart of the Web server or deletion and addition, which means that these changes to the Web will not cause changes to the cached data.

In the traditional single application architecture, because the number of user visits is not high, most of the cache exists to store user information and some pages, and most operations are performed directly with the DB. Reading and writing interaction, this kind of architecture is simple, also called simple architecture,
Traditional OA projects such as ERP, SCM, CRM and other systems have a small number of users and are also due to the business reasons of most companies. The single application architecture is still very complicated. It is a commonly used architecture, but in some systems, as the number of users increases and the business expands, DB bottlenecks appear.

There are two ways to deal with this situation that I have learned below

(1): When the number of user visits is not large, but the amount of data read and written is large, What we generally adopt is to separate the reading and writing of the DB, use one master and multiple slaves, and upgrade the hardware to solve the DB bottleneck problem.
Such shortcomings are also pure:

1. What to do when the number of users is large? ,
2. The performance improvement is limited,
3. The price/performance ratio is not high. Improving performance requires a lot of money (for example, the current I/O throughput is 0.9 and needs to be increased to 1.0. This price is indeed considerable when we increase the machine configuration)

(2): When the number of user visits also increases, we need to introduce caching to solve the problem. A picture describes the general function of caching.

The cache is mainly aimed at data that does not change frequently and has a large number of visits. The DB database can be understood as only for data solidification or only for reading frequently. For the changed data, I did not draw the SET operation in the picture above. I just want to explain that the cache can be used as a temporary database. We can synchronize the data in the cache and the database through scheduled tasks. In this way The advantage is that it can transfer the pressure of the database to the cache.

The emergence of cache solves the problem of database pressure, but when the following situations occur, the cache will no longer play a role, cache penetration, cache breakdown, cache avalanche, these three situations.

Cache penetration: When using cache in our program, we usually first go to the cache to query the cache data we want. If the data we want does not exist in the cache, If so, the cache will lose its function (cache failure). We just need to reach out to the DB library to ask for data. At this time, if there are too many such actions, the database will crash. This situation needs to be prevented by us. For example: we obtain user information from the cache, but deliberately enter user information that does not exist in the cache, thus avoiding the cache and shifting the pressure back to the data. To deal with this problem, we can cache the data accessed for the first time, because the cache cannot find the user information, and the database cannot query the user information. At this time, to avoid repeated access, we cache the request and put the pressure back on Turning to the cache, some people may have questions. What to do when there are tens of thousands of accessed parameters that are unique and can avoid cache. We also store the data and set a shorter expiration time to clear the cache.

Cache breakdown: The thing is like this. For some cache KEYs with expiration times set, when they expire, the program is accessed with high concurrent access (cache invalidation). At this time Use a mutex lock to solve the problem.

Mutex lock principle:The popular description is that 10,000 users access the database, but only one user can get access to the database. When After the user obtains this permission, he re-creates the cache. At this time, the remaining visitors are waiting to access the cache because they have not obtained the permission.

Never expires: Some people may think, can’t it be enough if I don’t set the expiration time? Yes, but this also has disadvantages. We need to update the cache regularly. At this time, the data in the cache is relatively delayed.

Cache avalanche: means that multiple caches are set to expire at the same time. At this time, a large number of data accesses come, (Cache invalidation) The pressure on the database DB Up again. The solution is to add a random number to the expiration time when setting the expiration time to ensure that the cache does not fail in large areas.

Project preparation

1. First install Redis, you can refer to here
2. Then download and install: client tool: RedisDesktopManager (convenient management)
3. Reference Microsoft.Extensions.Caching.Redis in our project Nuget

For this we create a new ASP.NET Core MVC project and first register the Redis service in the ConfigureServices method of the project Startup class:

public void ConfigureServices(IServiceCollection services)
{
    //将Redis分布式缓存服务添加到服务中
    services.AddDistributedRedisCache(options =>
    {
        //用于连接Redis的配置  Configuration.GetConnectionString("RedisConnectionString")读取配置信息的串
        options.Configuration = "localhost";// Configuration.GetConnectionString("RedisConnectionString");
        //Redis实例名DemoInstance
        options.InstanceName = "DemoInstance";
    });
    services.AddMvc();
}

You can also specify the IP address, port number and login password of the Redis server when registering the Redis service above:

public void ConfigureServices(IServiceCollection services)
{
    //将Redis分布式缓存服务添加到服务中
    services.AddDistributedRedisCache(options =>
    {
        //用于连接Redis的配置  Configuration.GetConnectionString("RedisConnectionString")读取配置信息的串
        options.Configuration = "192.168.1.105:6380,password=1qaz@WSX3edc$RFV";//指定Redis服务器的IP地址、端口号和登录密码
        //Redis实例名DemoInstance
        options.InstanceName = "DemoInstance";
    });
    services.AddMvc();
}

Later we will explain how the Redis instance name DemoInstance set by options.InstanceName above is used What to do

In addition, you can also specify the timeout period of the Redis server in the services.AddDistributedRedisCache method. If you call the method in the IDistributedCache interface introduced later and the operation on the Redis server times out, a RedisConnectionException will be thrown. and RedisTimeoutException, so below we specify three timeouts when registering the Redis service:

public void ConfigureServices(IServiceCollection services)
{
    //将Redis分布式缓存服务添加到服务中
    services.AddDistributedRedisCache(options =>
    {
        options.ConfigurationOptions = new StackExchange.Redis.ConfigurationOptions()
        {
            Password = "1qaz@WSX3edc$RFV",
            ConnectTimeout = 5000,//设置建立连接到Redis服务器的超时时间为5000毫秒
            SyncTimeout = 5000,//设置对Redis服务器进行同步操作的超时时间为5000毫秒
            ResponseTimeout = 5000//设置对Redis服务器进行操作的响应超时时间为5000毫秒
        };

        options.ConfigurationOptions.EndPoints.Add("192.168.1.105:6380");
        options.InstanceName = "DemoInstance";
    });
    services.AddMvc();
}

Among them, ConnectTimeout is the timeout to establish a connection to the Redis server, and SyncTimeout and ResponseTimeout are to perform data processing on the Redis server. The timeout period for the operation. Note that above we used the options.ConfigurationOptions attribute to set the IP address, port number and login password of the Redis server

IDistributedCache interface

Referenced in the project: using Microsoft. Extensions.Caching.Distributed; Using IDistributedCache

The IDistributedCache interface contains synchronous and asynchronous methods. Interface allows adding, retrieving, and deleting items in a distributed cache implementation. The IDistributedCache interface contains the following methods:
Get, GetAsync
takes a string key and retrieves the cache item as a byte[] if found in the cache.
Set, SetAsync
Use string keys to add or change items to the cache (byte[] form).
Refresh, RefreshAsync
Refreshes an item in the cache based on a key and resets its adjustable expiration timeout value (if any).
Remove, RemoveAsync
Remove cache items based on key. If the key passed into the Remove method does not exist in Redis, the Remove method will not report an error, but nothing will happen. However, if the parameter passed into the Remove method is null, an exception will be thrown.

As mentioned above, since the Set and Get methods of the IDistributedCache interface access data from Redis through byte[] byte arrays, it is not very convenient in a sense. I encapsulate it below. A RedisCache class can access any type of data from Redis.

The Json.NET Nuget package is used to serialize and deserialize Json format:

using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
using System.Text;

namespace AspNetCoreRedis.Assembly
{
    /// <summary>
    /// RedisCache缓存操作类
    /// </summary>
    public class RedisCache
    {
        protected IDistributedCache cache;

        /// <summary>
        /// 通过IDistributedCache来构造RedisCache缓存操作类
        /// </summary>
        /// <param name="cache">IDistributedCache对象</param>
        public RedisCache(IDistributedCache cache)
        {
            this.cache = cache;
        }

        /// <summary>
        /// 添加或更改Redis的键值,并设置缓存的过期策略
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="value">缓存值</param>
        /// <param name="distributedCacheEntryOptions">设置Redis缓存的过期策略,可以用其设置缓存的绝对过期时间(AbsoluteExpiration或AbsoluteExpirationRelativeToNow),也可以设置缓存的滑动过期时间(SlidingExpiration)</param>
        public void Set(string key, object value, DistributedCacheEntryOptions distributedCacheEntryOptions)
        {
            //通过Json.NET序列化缓存对象为Json字符串
            //调用JsonConvert.SerializeObject方法时,设置ReferenceLoopHandling属性为ReferenceLoopHandling.Ignore,来避免Json.NET序列化对象时,因为对象的循环引用而抛出异常
            //设置TypeNameHandling属性为TypeNameHandling.All,这样Json.NET序列化对象后的Json字符串中,会包含序列化的类型,这样可以保证Json.NET在反序列化对象时,去读取Json字符串中的序列化类型,从而得到和序列化时相同的对象类型
            var stringObject = JsonConvert.SerializeObject(value, new JsonSerializerSettings()
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                TypeNameHandling = TypeNameHandling.All
            });

            var bytesObject = Encoding.UTF8.GetBytes(stringObject);//将Json字符串通过UTF-8编码,序列化为字节数组

            cache.Set(key, bytesObject, distributedCacheEntryOptions);//将字节数组存入Redis
            Refresh(key);//刷新Redis
        }

        /// <summary>
        /// 查询键值是否在Redis中存在
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <returns>true:存在,false:不存在</returns>
        public bool Exist(string key)
        {
            var bytesObject = cache.Get(key);//从Redis中获取键值key的字节数组,如果没获取到,那么会返回null

            if (bytesObject == null)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// 从Redis中获取键值
        /// </summary>
        /// <typeparam name="T">缓存的类型</typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="isExisted">是否获取到键值,true:获取到了,false:键值不存在</param>
        /// <returns>缓存的对象</returns>
        public T Get<T>(string key, out bool isExisted)
        {
            var bytesObject = cache.Get(key);//从Redis中获取键值key的字节数组,如果没获取到,那么会返回null

            if (bytesObject == null)
            {
                isExisted = false;
                return default(T);
            }

            var stringObject = Encoding.UTF8.GetString(bytesObject);//通过UTF-8编码,将字节数组反序列化为Json字符串

            isExisted = true;

            //通过Json.NET反序列化Json字符串为对象
            //调用JsonConvert.DeserializeObject方法时,也设置TypeNameHandling属性为TypeNameHandling.All,这样可以保证Json.NET在反序列化对象时,去读取Json字符串中的序列化类型,从而得到和序列化时相同的对象类型
            return JsonConvert.DeserializeObject<T>(stringObject, new JsonSerializerSettings()
            {
                TypeNameHandling = TypeNameHandling.All
            });
        }

        /// <summary>
        /// 从Redis中删除键值,如果键值在Redis中不存在,该方法不会报错,只是什么都不会发生
        /// </summary>
        /// <param name="key">缓存键</param>
        public void Remove(string key)
        {
            cache.Remove(key);//如果键值在Redis中不存在,IDistributedCache.Remove方法不会报错,但是如果传入的参数key为null,则会抛出异常
        }

        /// <summary>
        /// 从Redis中刷新键值
        /// </summary>
        /// <param name="key">缓存键</param>
        public void Refresh(string key)
        {
            cache.Refresh(key);
        }
    }
}

Use test

Then we In the ASP.NET Core MVC project, create a new CacheController, and then test the related methods of the RedisCache class in its Index method:

public class CacheController : Controller
{
    protected RedisCache redisCache;

    //由于我们前面在Startup类的ConfigureServices方法中调用了services.AddDistributedRedisCache来注册Redis服务,所以ASP.NET Core MVC会自动依赖注入下面的IDistributedCache cache参数
    public CacheController(IDistributedCache cache)
    {
        redisCache = new RedisCache(cache);
    }

    public IActionResult Index()
    {
        bool isExisted;
        isExisted = redisCache.Exist("abc");//查询键值"abc"是否存在
        redisCache.Remove("abc");//删除不存在的键值"abc",不会报错

        string key = "Key01";//定义缓存键"Key01"
        string value = "This is a demo key !";//定义缓存值

        redisCache.Set(key, value, new DistributedCacheEntryOptions()
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
        });//设置键值"Key01"到Redis,使用绝对过期时间,AbsoluteExpirationRelativeToNow设置为当前系统时间10分钟后过期

        //也可以通过AbsoluteExpiration属性来设置绝对过期时间为一个具体的DateTimeOffset时间点
        //redisCache.Set(key, value, new DistributedCacheEntryOptions()
        //{
        //    AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(10)
        //});//设置键值"Key01"到Redis,使用绝对过期时间,AbsoluteExpiration设置为当前系统时间10分钟后过期

        var getVaue = redisCache.Get<string>(key, out isExisted);//从Redis获取键值"Key01",可以看到getVaue的值为"This is a demo key !"

        value = "This is a demo key again !";//更改缓存值

        redisCache.Set(key, value, new DistributedCacheEntryOptions()
        {
            SlidingExpiration = TimeSpan.FromMinutes(10)
        });//将更改后的键值"Key01"再次缓存到Redis,这次使用滑动过期时间,SlidingExpiration设置为10分钟

        getVaue = redisCache.Get<string>(key, out isExisted);//再次从Redis获取键值"Key01",可以看到getVaue的值为"This is a demo key again !"

        redisCache.Remove(key);//从Redis中删除键值"Key01"

        return View();
    }
}

Previously we called services.AddDistributedRedisCache registration in the ConfigureServices method of the Startup class of the project When serving Redis, options.InstanceName = "DemoInstance" is set. So what is the use of this InstanceName?

When we call the following code of the Index method in the above CacheController:

string key = "Key01";//定义缓存键"Key01"
string value = "This is a demo key !";//定义缓存值

redisCache.Set(key, value, new DistributedCacheEntryOptions()
{
    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
});//设置键值"Key01"到Redis,使用绝对过期时间,AbsoluteExpirationRelativeToNow设置为当前系统时间10分钟后过期

We use redis-cli to log in to the Redis server, and use the Keys * command to view the information stored in the current Redis service When all keys are displayed, you can see the result as follows:

You can see that although the key stored in Redis in our code is "Key01", it is actually in the Redis service The stored key is "DemoInstanceKey01", so the key actually stored in the Redis service is the "InstanceName key" combination key, so we can do data isolation in Redis for different Applications by setting different InstanceNames. This That’s the role of InstanceName

Recommended learning: Redis video tutorial

The above is the detailed content of Let's talk about using redis to implement distributed caching. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:csdn.net. If there is any infringement, please contact admin@php.cn delete