1.Options
Garnet中所有可配置的选项
2.RedisOptions
Garnet兼容redis配置文件,以及部分配置项包括:“bind”,“port”,“maxmemory” 等等。
3.RedisConfigSerializer Redis配置序列化器
while ((line = reader.ReadLine()) != null)
{
lineCount++;
// Ignore whitespaces and comments
if (string.IsNullOrWhiteSpace(line) || line.TrimStart().StartsWith('#'))
continue;
// Expected line format: keyword argument1 argument2 ... argumentN
var sepIdx = line.IndexOf(' ');
if (sepIdx == -1)
throw new RedisSerializationException(
$"Unable to deserialize {nameof(RedisOptions)} object. Line {lineCount} not in expected format (keyword argument1 argument2 ... argumentN).");
// Ignore key when no matching property found
var key = line.Substring(0, sepIdx);
if (!KeyToProperty.Value.ContainsKey(key))
{
logger?.LogWarning($"Redis configuration option not supported: {key}.");
continue;
}
var value = line.Substring(sepIdx + 1);
// Get matching property & the underlying option type (T in Option<T>)
var prop = KeyToProperty.Value[key];
var optType = prop.PropertyType.GenericTypeArguments.First();
// Try to deserialize the value
if (!TryChangeType(value, typeof(string), optType, out var newVal))
{
// If unsuccessful and if underlying option type is an array, try to deserialize array by elements
if (optType.IsArray)
{
// Split the values in the serialized array
var values = value.Split(' ');
// Instantiate a new array
var elemType = optType.GetElementType();
newVal = Array.CreateInstance(elemType, values.Length);
// Try deserializing and setting array elements
for (var i = 0; i < values.Length; i++)
{
if (!TryChangeType(values[i], typeof(string), elemType, out var elem))
throw new RedisSerializationException(
$"Unable to deserialize {nameof(RedisOptions)} object. Unable to convert object of type {typeof(string)} to object of type {elemType}. (Line: {lineCount}; Key: {key}; Property: {prop.Name}).");
((Array)newVal).SetValue(elem, i);
}
}
else
{
throw new RedisSerializationException(
$"Unable to deserialize {nameof(RedisOptions)} object. Unable to convert object of type {typeof(string)} to object of type {optType}. (Line: {lineCount}; Key: {key}; Property: {prop.Name}).");
}
}
// Create a new Option<T> object
var newOpt = Activator.CreateInstance(prop.PropertyType);
// Set the underlying option value
var valueProp = prop.PropertyType.GetProperty(nameof(Option<object>.Value));
valueProp.SetValue(newOpt, newVal);
// Set the options property to the new option object
prop.SetValue(options, newOpt);
// Append usage warning, if defined
var redisOptionAttr = (RedisOptionAttribute)prop.GetCustomAttributes(typeof(RedisOptionAttribute), false).First();
if (!string.IsNullOrEmpty(redisOptionAttr.UsageWarning))
{
logger?.LogWarning($"Redis configuration option usage warning ({key}): {redisOptionAttr.UsageWarning}");
}
}
使用while循环逐行读取文件流,并且转换为RedisOptions实体,转换失败的字段也会报出RedisSerializationException异常。这里面有一个比较又意思设计:
[RedisOption("bind", nameof(Options.Address), BindWarning, typeof(ArrayToFirstItemTransformer<string>))]
public Option<string[]> Bind { get; set; }
RedisOptions中字段类型使用了Option进行包裹,因此在序列化的时候稍微麻烦一点。
4.IConfigProvider 配置提供器
/// <summary>
/// Interface for importing / exporting options from different configuration file types
/// </summary>
internal interface IConfigProvider
{
/// <summary>
/// Import an Options object from path using a stream provider
/// </summary>
/// <param name="path">Path to the config file containing the serialized object</param>
/// <param name="streamProvider">Stream provider to use when reading from the path</param>
/// <param name="options">Options object to populate with deserialized options</param>
/// <param name="logger">Logger</param>
/// <returns>True if import succeeded</returns>
bool TryImportOptions(string path, IStreamProvider streamProvider, Options options, ILogger logger);
/// <summary>
/// Export an Options object to path using a stream provider
/// </summary>
/// <param name="path">Path to the config file to write into</param>
/// <param name="streamProvider">Stream provider to use when writing to the path</param>
/// <param name="options">Options object to serialize</param>
/// <param name="logger">Logger</param>
/// <returns>True if export succeeded</returns>
bool TryExportOptions(string path, IStreamProvider streamProvider, Options options, ILogger logger);
}
RedisConfigProvider与GarnetConfigProvider共同继承IConfigProvider用来导出或者导入配置项,GarnetConfigProvider用于提供Options,导出方法中使用了JsonConvert用来序列化Options;RedisConfigProvider则用于提供RedisOptions,导出方法中使用了RedisConfigSerializer 用来序列化RedisOptions。
5.IGarnetCustomTransformer 自定义转换器
/// <summary>
/// Defines an interface for custom transformers that transform a RedisOptions property value to an Options property value and vice versa
/// The custom transformer is specified by the RedisOptionAttribute decorating the RedisOptions property
/// </summary>
/// <typeparam name="TIn">RedisOptions property value type</typeparam>
/// <typeparam name="TOut">Options property value type</typeparam>
internal interface IGarnetCustomTransformer<TIn, TOut>
{
/// <summary>
/// Transforms a RedisOptions property value to an Options property value
/// </summary>
/// <param name="input">RedisOptions property value</param>
/// <param name="output">Options property value</param>
/// <param name="errorMessage">Error message, empty if no error occurred</param>
/// <returns>True if transform successful</returns>
bool Transform(TIn input, out TOut output, out string errorMessage);
/// <summary>
/// Transforms an Options property value back to a RedisOptions property value
/// </summary>
/// <param name="input">Options property value</param>
/// <param name="output">RedisOptions property value</param>
/// <param name="errorMessage">Error message, empty if no error occurred</param>
/// <returns>True if transform successful</returns>
bool TransformBack(TOut input, out TIn output, out string errorMessage);
}
用于将RedisOptions属性值转换为Options属性值,反之亦然