помещение
пол года назад(2020-06), эпидемия достигла дна, а объем бизнеса компании продолжал расти.Чтобы облегчить доставку коротких сообщений, отправку шаблонных сообщений и другие каналы, операционный отдел предложил функциональное требование по сжатию длинных ссылок в короткие ссылки. В то время для быстрого продвижения использовались некоторые известные сторонние платформы сжатия коротких цепей, и были некоторые проблемы:
- Дорого
- В некоторых случаях короткие доменные имена будут заблокированы на некоторых сторонних платформах, таких как WeChat.
- Нет возможности настроить план обработки данных обратно к источнику, и невозможно открыть всю бизнес-ссылку для анализа и отслеживания данных.
Исходя из таких проблем, я решил разработать сервис коротких ссылок (длинная ссылка, сжатая в) самостоятельно. В то время микросервисы были разделены синхронно. Многие внутренние микросервисы нужно было переименовать. Девушка в группе сказала, что лучше использовать его.Githubталисман, чтобы назватьoctopus cat(Кошка Осьминог), чтобы назвать его, но из-за проблем с авторскими правами она удалила своего любимого кота, оставив Осьминога сoctopusназвание:
(Описание проекта тоже опечатка, должно быть "короткая ссылка") Поскольку реализованная функция не сложная, первое издание было опубликовано в2020-06Он будет выпущен в конце месяца.octopusРеализация относится к нескольким статьям в Интернете о «Реализации службы короткой цепочки» с относительно высоким количеством просмотров страниц.Давайте поговорим о принципе реализации, реализации службы и архитектуре развертывания.
Фундаментальный
Суть службы коротких цепей заключается в построении уникальной связи сопоставления между короткими и длинными ссылками, которая основана на алгоритме генерации идентификации сопоставления с высокой производительностью, большим количеством перестановок и трудностью взлома.
Создайте уникальное сопоставление отношений
Изображение выше представляет собой текстовое сообщение с напоминанием, полученное автором о результате погашения рассрочки Jingdong Baitiao.Содержание текстового сообщения также содержит короткую цепочку.https://3.cn/j/xxxxxxx, скопируйте его в браузер, чтобы открыть, и обнаружите, что клиент будет перенаправлен на длинную цепочкуhttps://jrmkt.jd.com/ptp/wl/vouchers.html?activityId=${activityId}&uep_p=${uep_p}&uep_template_id=${uep_template_id}&uep_timestamp=${uep_timestamp}, затем прыгайте вH5После входа в систему вы перейдете на страницу стратегии с белым баром. На самом деле длинную цепочку можно сжать в несколько коротких цепочек, которые могут использовать одно и то же доменное имя или разные доменные имена:
доступhttps://3.cn/j/xxxxxxxКонкретный процесс взаимодействия коротких ссылок угадывается следующим образом:
jrmkt.jd.com и 3.cn являются доменными именами Doge East.
Построение уникальных отношений сопоставления фактически основано на фиксированной длинной ссылке, которая сопоставляется с одной или несколькими короткими ссылками, которые могут быть созданы динамически.Это уникальное отношение сопоставления требует, чтобы сгенерированные короткие ссылки удовлетворяли:
- Его нелегко взломать (используя числа, такие как самоувеличивающийся первичный ключ базы данных, в качестве уникального идентификатора сопоставления, люди могут легко использовать его для совершения злонамеренных вызовов).
- Нельзя повторять (короткая ссылка может соответствовать только одной длинной ссылке, конечно, длинная ссылка может соответствовать нескольким коротким ссылкам)
- Длина сообщения должна быть как можно короче. Это связано с тем, что содержание сообщения, передаваемого третьей стороной, обычно имеет ограничение по длине. Если короткая цепочка слишком длинная, ее будет нелегко передать, и число количество слов в отправляемом контенте будет ограничено.
30символы длинные, короткая цепочка уже учтена20символы длинные, только10Длина символов явно неразумна для игры коллег по эксплуатации.) - Если ссылка слишком длинная, "кодовые точки" в сгенерированном QR-коде будут очень плотными, что не способствует идентификации и передаче клиента. Бывает, что в компании автора работает сцена, где используются QR-коды, поэтому длина ссылки должны быть максимально сокращены.
В общем, идентификатор сопоставления в этом уникальном отношении сопоставления должен быть похож наHashалгоритмически сгенерированныйHashКод имеет высокую уникальность и низкую частоту коллизий, а также обладает характеристиками короткой и легкой передачи.Подробнее о том, как сгенерировать уникальный идентификатор отображения, см. в следующем разделе «Алгоритм генерации кода сжатия».
Алгоритм генерации кода сжатия
Здесь "код сжатия" (compression_code) — термин, придуманный автором, в данной статье его значение — короткая ссылкаURLчасть пути (для экономии длины, за исключением части протокола и имени домена,URLтолько первый сегмент пути):
Среди них протокольная часть в основном фиксируется какhttps://(Не рекомендуется с точки зрения безопасностиhttp://), доменные имена с короткой цепью можно приобрести с максимально короткими доменными именами, такими какt.cn, но дальновидные капиталисты вообще скупят все качественные короткие доменные имена и поднимут цену до высокой, так что длина доменного имени в основном фактор, который трудно контролировать, а оставшаяся контролируемая часть - часть кода сжатия . Часть кода сжатия управляема, но поскольку онаURLчастьURLКодирование побега, тогда длина искусственно контролируется. Предположим, мы используем26прописные и строчные буквы плюс10число, то дляNМаксимальное количество комбинаций, которые может представлять код с битовым сжатием, составляет:
-
N = 4, количество комбинаций62 ^ 4 = 14_776_336,14710 000 близко148Десять тысяч -
N = 5, количество комбинаций62 ^ 5 = 916_132_832,9.16около 100 миллионов -
N = 6, количество комбинаций62 ^ 6 = 56_800_235_584,568около 100 миллионов
Вообще говоря, чем меньше количество комбинаций, тем легче его взломать.Чем больше количество комбинаций, тем больше длина сжатого кода, поэтому обычно используется длина4,5а также6, а недопустимая длинная цепочка может быть восстановлена или отключена на более позднем этапе.Эти три длины могут быть удовлетворены для сценариев приложений, которые создают короткие цепочки в большинстве логарифмов.octopusВ реализации выбор6Код сжатия битовой длины, больше ничего, потому что есть готовые зрелые эталонные схемы:62Базовое число точно состоит из символов0-9 a-z A-Zсостав, при генерации сжатого кода нужно только сгенерировать уникальный10базовое число, а затем на основе этого10Преобразовать базовое число в62Двоичные числа могут быть. Сказав это, схема выглядит следующим образом:
Пунктирная часть обычно основана на эффективном алгоритме дайджеста с низким уровнем коллизий, таком какMurmurHash, в то время как первый(1)Сплошная линия на этом шаге предназначена для создания глобально уникального10Обычно используются следующие методы:
- Последовательность автоинкремента базы данных (например, первичный ключ автоинкремента)
-
Snowflakeалгоритм - Похож на саморазвитие
UUIDАлгоритмы генерируют глобально уникальные значения последовательности
Учитывая, что автор ранее изучалSnowflakeЗдесь просто используется принцип алгоритмаSnowflakeАлгоритм генерирует последовательность с автоматическим приращением и использует следующий процесс для генерации и выделения кода сжатия:
Поскольку операционный отдел не генерирует большую партию коротких цепочек и существует только одно доменное имя с короткими цепями,Следовательно, для простоты операция сжатия напрямую использует один код сжатия, независимо от совместного использования одного и того же кода сжатия разными доменными именами с короткой цепочкой или восстановления кода сжатия..
реализация услуги
Главный вход службы короткой цепи, как правило,QPSЧрезвычайно высокая, поэтому нам нужно сделать все возможное, чтобы сократить время, отнимающее эту запись, и рассмотреть возможность использованияRedisДля кеширования входящего трафика выбор инфраструктуры следующий:
-
JDK1.8+: использование производственного развертыванияJDK11 -
MVCРамы и контейнеры:spring-boot-starter-webfluxилиspring-cloud-gateway, в основном должны использоватьNettyкак базовый коммуникационный контейнер - внутренний
RPCРамка:Dubbo - Регистрация и обнаружение службы:
Nacos - необязательный
APMинструмент:Pinpoint
Зависимости промежуточного ПО (поскольку весь сервисный кластер раньше находился в облаке, а низконагруженные сервисы совместно используют некоторое промежуточное ПО):
MySQL8.x-
Redis5.xОбычный ведущий-ведомый или дозорный кластер -
RabbitMQ3.8.xКластер с использованием зеркальных очередей
Схема конструкции сервиса выглядит следующим образом:
В последней версии рассматривается удаление блокировщика из черного и белого списка,Заменен перехватчиком реальности, основанным на фильтре Блума.. Сервис использует два перехватчика (хотяFilterТрансляция является фильтром, но по привычке, далее именуемой цепочкой перехватчиков, цепочка перехватчиков, состоящая из перехватчиков, предоставляемых контейнером, в основном отвечает за функции безопасности сервиса и отслеживания цепочки вызовов, в то время как цепочка перехватчиков, настроенная внутри служба в основном отвечает за реализацию синтаксического анализа параметров запроса,URLТакие функции, как преобразования, перенаправления и асинхронное ведение журнала событий.
Модульное деление:
- (ROOT) octopus
- octopus-contract
- octopus-server
octopus-contractМодуль должен отсоединиться от родителяPOMуправление для облегчения отдельных итерационных обновлений.
Дизайн базы данных
всего использовано5Таблица:
конкретная инициализацияDDLследующим образом:
CREATE DATABASE `db_octopus` CHARSET 'utf8mb4' COLLATE 'utf8mb4_unicode_520_ci';
USE `db_octopus`;
CREATE TABLE `url_map`
(
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`short_url` VARCHAR(32) NOT NULL COMMENT '短链URL',
`long_url` VARCHAR(768) NOT NULL COMMENT '长链URL',
`short_url_digest` VARCHAR(128) NOT NULL COMMENT '短链摘要',
`long_url_digest` VARCHAR(128) NOT NULL COMMENT '长链摘要',
`compression_code` VARCHAR(16) NOT NULL COMMENT '压缩码',
`description` VARCHAR(256) COMMENT '描述',
`url_status` TINYINT NOT NULL DEFAULT 1 COMMENT 'URL状态,1:正常,2:已失效',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`edit_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`creator` VARCHAR(32) NOT NULL DEFAULT 'admin' COMMENT '创建者',
`editor` VARCHAR(32) NOT NULL DEFAULT 'admin' COMMENT '更新者',
`deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除标识',
`version` BIGINT NOT NULL DEFAULT 1 COMMENT '版本号',
UNIQUE uniq_compression_code (`compression_code`),
INDEX idx_short_url (`short_url`),
INDEX idx_short_url_digest (`short_url_digest`),
INDEX idx_long_url_digest (`long_url_digest`)
) COMMENT 'URL映射表';
CREATE TABLE `domain_conf`
(
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`domain_value` VARCHAR(16) NOT NULL COMMENT '域名',
`protocol` VARCHAR(8) NOT NULL DEFAULT 'https' COMMENT '协议,https或者http',
`domain_status` TINYINT NOT NULL DEFAULT 1 COMMENT '域名状态,1:正常,2:已失效',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`edit_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`creator` VARCHAR(32) NOT NULL DEFAULT 'admin' COMMENT '创建者',
`editor` VARCHAR(32) NOT NULL DEFAULT 'admin' COMMENT '更新者',
`deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除标识',
`version` BIGINT NOT NULL DEFAULT 1 COMMENT '版本号',
UNIQUE uniq_domain (`domain_value`)
) COMMENT '域名配置';
CREATE TABLE `compression_code`
(
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`compression_code` VARCHAR(16) NOT NULL COMMENT '压缩码',
`code_status` TINYINT NOT NULL DEFAULT 1 COMMENT '压缩码状态,1:未使用,2:已使用,3:已失效',
`sequence_value` VARCHAR(64) NOT NULL COMMENT '序列(盐)',
`strategy` VARCHAR(8) NOT NULL DEFAULT 'sequence' COMMENT '策略,sequence或者hash',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`edit_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`creator` VARCHAR(32) NOT NULL DEFAULT 'admin' COMMENT '创建者',
`editor` VARCHAR(32) NOT NULL DEFAULT 'admin' COMMENT '更新者',
`deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除标识',
`version` BIGINT NOT NULL DEFAULT 1 COMMENT '版本号',
UNIQUE uniq_compression_code (`compression_code`)
) COMMENT '压缩码';
CREATE TABLE `visit_statistics`
(
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`edit_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`creator` VARCHAR(32) NOT NULL DEFAULT 'admin' COMMENT '创建者',
`editor` VARCHAR(32) NOT NULL DEFAULT 'admin' COMMENT '更新者',
`deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除标识',
`version` BIGINT NOT NULL DEFAULT 1 COMMENT '版本号',
`statistics_date` DATE NOT NULL DEFAULT '1970-01-01' COMMENT '统计日期',
`pv_count` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '页面流量数',
`uv_count` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '独立访客数',
`ip_count` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '独立IP数',
`effective_redirection_count` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '有效跳转数',
`ineffective_redirection_count` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '无效跳转数',
`compression_code` VARCHAR(16) NOT NULL COMMENT '压缩码',
`short_url_digest` VARCHAR(128) NOT NULL COMMENT '短链摘要',
`long_url_digest` VARCHAR(128) NOT NULL COMMENT '长链摘要',
UNIQUE uniq_date_code_digest (`statistics_date`, `compression_code`)
) COMMENT '访问数据统计';
CREATE TABLE `transform_event_record`
(
`id` BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`edit_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`creator` VARCHAR(32) NOT NULL DEFAULT 'admin' COMMENT '创建者',
`editor` VARCHAR(32) NOT NULL DEFAULT 'admin' COMMENT '更新者',
`deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除标识',
`version` BIGINT NOT NULL DEFAULT 1 COMMENT '版本号',
`unique_identity` VARCHAR(128) NOT NULL COMMENT '唯一身份标识,SHA-1(客户端IP-UA)',
`client_ip` VARCHAR(64) NOT NULL COMMENT '客户端IP',
`short_url` VARCHAR(32) NOT NULL COMMENT '短链URL',
`long_url` VARCHAR(768) NOT NULL COMMENT '长链URL',
`short_url_digest` VARCHAR(128) NOT NULL COMMENT '短链摘要',
`long_url_digest` VARCHAR(128) NOT NULL COMMENT '长链摘要',
`compression_code` VARCHAR(16) NOT NULL COMMENT '压缩码',
`record_time` DATETIME NOT NULL COMMENT '记录时间戳',
`user_agent` VARCHAR(2048) COMMENT 'UA',
`cookie_value` VARCHAR(2048) COMMENT 'cookie',
`query_param` VARCHAR(2048) COMMENT 'URL参数',
`province` VARCHAR(32) COMMENT '省份',
`city` VARCHAR(32) COMMENT '城市',
`phone_type` VARCHAR(64) COMMENT '手机型号',
`browser_type` VARCHAR(64) COMMENT '浏览器类型',
`browser_version` VARCHAR(128) COMMENT '浏览器版本号',
`os_type` VARCHAR(32) COMMENT '操作系统型号',
`device_type` VARCHAR(32) COMMENT '设备型号',
`os_version` VARCHAR(32) COMMENT '操作系统版本号',
`transform_status` TINYINT NOT NULL DEFAULT 0 COMMENT '转换状态,1:转换成功,2:转换失败,3:重定向成功,4:重定向失败',
INDEX idx_record_time (`record_time`),
INDEX idx_compression_code (`compression_code`),
INDEX idx_short_url_digest (`short_url_digest`),
INDEX idx_long_url_digest (`long_url_digest`),
INDEX idx_unique_identity (`unique_identity`)
) COMMENT '转换事件记录';
Реализация модуля генерации кода сжатия
Метод генерации кода сжатия относительно прост:
private final SequenceGenerator sequenceGenerator; # <------------- 雪花算法序列生成器
@Value("${compress.code.batch:100}")
private Integer compressCodeBatch;
......
private void generateBatchCompressionCodes() {
for (int i = 0; i < compressCodeBatch; i++) {
long sequence = sequenceGenerator.generate();
CompressionCode compressionCode = new CompressionCode();
compressionCode.setSequenceValue(String.valueOf(sequence));
String code = ConversionUtils.X.encode62(sequence); # <-------------- 10进制转62进制
code = code.substring(code.length() - 6);
compressionCode.setCompressionCode(code);
compressionCodeDao.insertSelective(compressionCode);
}
}
Всегда генерируйте доступные коды сжатия в пакетах, и вам нужно только узнать первый код сжатия, который в настоящее время не используется при запросе.
Реализация цепочки перехватчиков контейнеров
Перехватчик контейнера должен быть реализованorg.springframework.web.server.WebFilter(WebFluxизFilterинтерфейс), существует четыре основных реализации (порядок следующий):
-
MappedDiagnosticContextFilter: импортtransmittable-thread-localпройти черезMDCДелатьTraceIdпривязка контекста запроса,WebFluxмодель потоков и общиеServletПоточная модель контейнера отличается и не может использоваться здесь напрямую.ThreadLocalилиSlf4jЦентральные равниныMDCвыполнить -
BlockIpFilter: определить запрос клиентаIPПопадать ли в черный список -
AccessDomainFilter: определить, попадает ли доменное имя в белый список коротких доменных имен (необязательно, поскольку внешнийNGINXДелайте перехват, эта реализация необязательна) -
ExcludeUriFilter: определить текущий запросURIон попалURIчерный список
Вот простая демонстрацияMappedDiagnosticContextFilterРеализация:
@Order(value = Integer.MIN_VALUE)
@Component
public class MappedDiagnosticContextFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String uuid = UUID.randomUUID().toString();
MDC.put("TRACE_ID", uuid);
return chain.filter(exchange).then(Mono.fromRunnable(() -> MDC.remove("TRACE_ID")));
}
}
надTRACE_IDсовместим с проектомlogback.xmlсерединаpatternиспользовать. Также нужна ссылкаhttps://github.com/alibaba/transmittable-thread-local/blob/master/docs/requirement-scenario.mdсерединаlogbackа такжеtransmittable-thread-localВыполните сценарии интеграции:
Здесь для облегчения управления и обновления версии автор напрямуюlogback-mdc-ttlРеализация исходного кода трансформируется и внедряется в проект.
Реализация внутренней цепочки перехватчиков службы
Цепочка перехватчиков внутри службы в основном отвечает за разбор параметров запроса,URLСопоставление конверсии, перенаправления и доступа к записям результатов конверсии, интерфейс верхнего уровня разработан следующим образом:
public interface TransformFilter {
default int order() {
return 1;
}
default void init(TransformContext context) {
}
void doFilter(TransformFilterChain chain,
TransformContext context);
}
TransformContextЯвляется имущественным классом, по существу обычнымJavaBean, оформленный следующим образом:
В настоящее время встроенный4реализации перехватчиков, в том числе:
-
ExtractRequestHeaderTransformFilter: Разбор заголовка запроса -
UrlTransformFilter:URLконвертировать -
RedirectionTransformFilter: обработка перенаправления -
TransformEventProcessTransformFilter: преобразование записей событий
кUrlTransformFilterВ качестве примера исходный код выглядит следующим образом:
@Slf4j
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class UrlTransformFilter implements TransformFilter {
@Autowired
private UrlMapCacheManager urlMapCacheManager;
@Override
public int order() {
return 2;
}
@Override
public void init(TransformContext context) {
}
@Override
public void doFilter(TransformFilterChain chain,
TransformContext context) {
String compressionCode = context.getCompressionCode();
UrlMap urlMap = urlMapCacheManager.loadUrlMapCacheByCompressCode(compressionCode);
context.setTransformStatus(TransformStatus.TRANSFORM_FAIL);
if (Objects.nonNull(urlMap)) {
context.setTransformStatus(TransformStatus.TRANSFORM_SUCCESS);
context.setParam(TransformContext.PARAM_LONG_URL_KEY, urlMap.getLongUrl());
context.setParam(TransformContext.PARAM_SHORT_URL_KEY, urlMap.getShortUrl());
chain.doFilter(context);
} else {
log.warn("压缩码[{}]不存在或异常,终止TransformFilterChain执行,并且重定向到404页面......", compressionCode);
throw new RedirectToErrorPageException(String.format("[c:%s]", compressionCode));
}
}
}
Все находящиеся в эксплуатации перехватчикиscopeобеprototype, что означает, что каждый раз, когда инициализируется цепочка перехватчиков, соответствующийBean.
реализация главного контроллера
потому чтоoctopusОн служит только входом для доступа по короткой цепочке, а функция фонового управления передается другому сервису, который имеет только один контроллер, и в контроллере есть только один метод:
@RequiredArgsConstructor
@RestController
public class OctopusController {
private final UrlMapService urlMapService;
@GetMapping(path = "/{compressionCode}")
@ResponseStatus(HttpStatus.FOUND)
public Mono<Void> dispatch(@PathVariable(name = "compressionCode") String compressionCode, ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
TransformContext context = new TransformContext();
context.setCompressionCode(compressionCode);
context.setParam(TransformContext.PARAM_SERVER_WEB_EXCHANGE_KEY, exchange);
if (Objects.nonNull(request.getRemoteAddress())) {
context.setParam(TransformContext.PARAM_REMOTE_HOST_NAME_KEY, request.getRemoteAddress().getHostName());
}
HttpHeaders httpHeaders = request.getHeaders();
Set<String> headerNames = httpHeaders.keySet();
if (!CollectionUtils.isEmpty(headerNames)) {
headerNames.forEach(headerName -> {
String headerValue = httpHeaders.getFirst(headerName);
context.setHeader(headerName, headerValue);
});
}
// 处理转换
urlMapService.processTransform(context);
// 这里有一个技巧,flush用到的线程和内部逻辑处理的线程不是同一个线程,所以要用到TTL -- 和Servlet容器不一样,所以目前写的比较别扭
return Mono.fromRunnable(context.getRedirectAction());
}
}
Этот управляемый мастером метод почтовых индексов распространения отвечает только за инкапсуляцию параметров для вызова внутренней цепочки перехватчиков службы для последующей обработки. Затем добавьте глобальный обработчик исключений, чтобы направлять все исключения или недопустимые операции в пользовательский404Страница (можно даже повесить на нее небольшую рекламу):
Реализация контракта Даббо
octopus-contractЭто полностью самостоятельный модуль, можно даже сказать, что это полностью самостоятельный проект, основная роль которого заключается в предоставлении контрактов.API, пусть вводятся другие сервисы, пустьoctopus-serverмодуль для реализации. Интерфейс контракта определяется следующим образом:
public interface OctopusApi {
Response<CreateUrlMapResponse> createUrlMap(CreateUrlMapRequest request);
}
на основеDubboРеализация выглядит следующим образом:
@DubboService(retries = -1)
public class DefaultOctopusApi implements OctopusApi {
@Autowired
private UrlMapService urlMapService;
@Value("${default.octopus.domain}")
private String domain;
@Override
public Response<CreateUrlMapResponse> createUrlMap(CreateUrlMapRequest request) {
UrlMap urlMap = new UrlMap();
urlMap.setUrlStatus(UrlMapStatus.AVAILABLE.getValue());
urlMap.setLongUrl(request.getLongUrl());
urlMap.setDescription(request.getDescription());
String shortUrl = urlMapService.createUrlMap(domain, urlMap);
return Response.succeed(new CreateUrlMapResponse(request.getRequestId(), shortUrl));
}
}
Модуль контракта в производстве сделал много настроек функций, вот только простой пример реализации.
Архитектура развертывания
octopusСервисный кластер развертывается отдельно и поддерживает неограниченное добавление узлов Ключ к архитектуре развертывания лежит в сетевой архитектуре Внутренняя балансировка нагрузки используетNginx, самая внешняя балансировка нагрузки использует облачную балансировку нагрузки, такую как Alibaba CloudSLBилиUCloudизULB. Добавляйте или удаляйте доменные имена с короткой цепью, главное изменитьNginxКонфигурация. Базовая структура выглядит следующим образом:
Просто убедитесь, что пул балансировки нагрузки указывает наoctopusКластеризации достаточно. Короткие доменные имена могут быть динамически добавлены или удалены. После операции толькоnginx -s -reloadобновитьNginxможно настроить.
Пользуйтесь сервисами с короткой цепью
доdomain_confВ таблицу записывается имя локального домена и данные порта:
Напишите класс интеграционного теста, который создает карту короткой цепочки:
@Slf4j
@SpringBootTest(classes = OctopusServerApplication.class, properties = "spring.profiles.active=local")
@RunWith(SpringRunner.class)
public class UrlMapServiceTest {
@Autowired
private UrlMapService urlMapService;
@Test
public void createUrlMap() {
String domain = "localhost:9099";
UrlMap urlMap = new UrlMap();
urlMap.setUrlStatus(UrlMapStatus.AVAILABLE.getValue());
urlMap.setLongUrl("https://throwx.cn/2020/08/24/canal-ha-cluster-guide");
urlMap.setDescription("测试短链");
String url = urlMapService.createUrlMap(domain, urlMap);
log.info("生成的短链:{}", url);
}
}
// 某次执行的结果如下:生成的短链:http://localhost:9099/Myt8qW
Запустите проект на основе локальной конфигурации, затем получите доступhttp://localhost:9099/Myt8qW, эффект следующий:
Журнал выглядит следующим образом:
[2020-12-27 19:29:22,285] [INFO] cn.throwx.octopus.server.application.consumer.TransformEventConsumer [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] [1c603903-e8d8-4072-aa97-6abf614b9411] - 接收到URL转换事件,内容:{"clientIp":"192.168.211.113","compressionCode":"Myt8qW","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36","cookieValue":"Webstorm-734c3b68=9b8b3560-41f5-478a-93d0-b02128b1022f; __gads=ID=28121bd829638f67-2286c86e7fc400d3:T=1604132165:RT=1604132165:S=ALNI_MbsMQROv6swaC8kf4ux2suZm_GZXA; Hm_lvt_4df6907aebab752244c3ca1432b4ff57=1605930058,1607228133","timestamp":1609068562262,"shortUrlString":"http://localhost:9099/Myt8qW","longUrlString":"https://throwx.cn/2020/08/24/canal-ha-cluster-guide","transformStatusValue":3}......
[2020-12-27 19:29:22,353] [INFO] cn.throwx.octopus.server.application.consumer.TransformEventConsumer [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] [1c603903-e8d8-4072-aa97-6abf614b9411] - 记录URL转换事件完成......
Просмотрите данные таблицы журнала событий конверсии:
Последующие итерации функции
В предыдущей схеме есть риск безопасности: нет белого списка для кодов сжатия, который легко атаковать методом подделки кодов сжатия и сращивания коротких ссылок на основе коротких доменных имен. Решение состоит в том, чтобы добавить или заменить перехватчик белого списка сжатого кода (короткая ссылка) на основе фильтра Блума в цепочке перехватчиков контейнера, чтобы большинство злонамеренно подделанных кодов сжатия могли быть перехвачены на ранней стадии, позволяя очень небольшое количество Поток вредоносного сжатого кода, попадающий в часть частоты ошибок, оценивается в последующей логике обработки. Кроме того, можно ввестиCaffeineСотрудничатьRedisДелайте двухуровневый кеш, все-таки локальный кеш быстрее.
резюме
octopusПервое издание представляет собой4Микропроект, который срочно итерировался в течение нескольких часов, до сих пор обновлялся много раз, и производство в основном стабильно. Версия, описанная в этой статье, представляет собой портированную версию производственной версии компании, которая упрощает большую часть кода и удаляет некоторые схемы бизнес-связей.Здесь исходный код открыт, чтобы предоставить справку для некоторых сценариев, которые могут использовать короткую цепочку. услуги Не копируйте решения. Репозиторий исходного кода:
-
Gitee:https://gitee.com/throwableDoge/octopus -
Github:https://github.com/zjcscut/octopus
код находится вmainветвь.
пасхальные яйца
В последнее время давно голубю, причина в том, что в конце года было много итераций бизнес-функций, и реконструкция внутреннего сервиса этикеток заняла много времени. Автор исследовал такие идеи, как «шардинг», «асинхронность» и т. д., в предпосылке контролируемого времени, в предпосылке небольшого объема данных (миллионы и десятки миллионов), с помощью широко используемых реляционных баз данных, кэшей, -архитектуры платформы больших данных, такие как очереди сообщений, могут заменить решения, упомянутые в разделе «Методология портрета пользователя и инженерные решения».
Внутреннее кодовое название службы лейбла — «Тихиро», которое взято из «Цяньсюнь в толпе» в «Случае Циньюй Юаньси» Синь Циджи, а название проекта происходит от героини мультфильма Хаяо Миядзаки «Унесенные призраками». (Римский звук Qianxunchihiro):
После того, как последующие проекты будут запущены в течение определенного периода времени и стабилизированы, им нужно время, чтобы написать серию статей о том, как предоставить инженерные решения для портретов пользователей без использования системы больших данных.
(Конец этой статьи c-10-d e-a-20201227)