首页 文章 精选 留言 我的

精选列表

搜索[Python版本],共10000篇文章
优秀的个人博客,低调大师

基于2PC和延迟更新完成分布式消息队列多条事务Golang版本

背景 分布式多消息事务问题 在消息队列使用场景中,有时需要同时下发多条消息,但现在的消息队列比如kafka只支持单条消息的事务保证,不能保证多条消息,今天说的这个方案就时kafka内部的一个子项目中基于2PC和延迟更新来实现分布式事务 2PC 2PC俗称两阶段提交,通过将一个操作分为两个阶段:准备阶段和提交阶段来尽可能保证操作的原子执行(实际上不可能,大家有个概念先) 延迟更新 延迟更新其实是一个很常用的技术手段,简单来说,当某个操作条件不满足时,通过一定手段将数据暂存,等条件满足时在进行执行 基于2PC和延迟队列的分布式事务实现 系统架构 实现也蛮简单的, 在原来的业务消息之后再添加一条事务消息(事务消息可以通过类似唯一ID来关联到之前提交的消息), worker未消费到事物提交的消息,就会一直将消息放在本地延迟存储中,只有当接收到事物提交消息,才会进行业务逻辑处理 业务流程 生产者 逐条发送业务消息组 发送事务提交消息 消费者 消费消息队列,将业务消息存放本地延迟存储 接收提交事务消息,从本地延迟存储获取所有数据,然后从延迟存储中删除该消息 代码实现 核心组件 MemoryQuue: 用于模拟消息队列,接收事件分发事件 Worker: 模拟具体业务服务,接收消息,存入本地延迟更新存储,或者提交事务触发业务回调 Event与EventListener Event: 用于标识事件,用户将业务数据封装成事件存入到MemoryQueue中 EventListener: 事件回调接口,用于MemoryQueue接收到数据后的回调 事件在发送的时候,需要通过一个前缀来进行事件类型标识,这里有三种TaskPrefix、CommitTaskPrefix、ClearTaskPrefix const ( // TaskPrefix 任务key前缀 TaskPrefix string = "task-" // CommitTaskPrefix 提交任务key前缀 CommitTaskPrefix string = "commit-" // ClearTaskPrefix 清除任务 ClearTaskPrefix string = "clear-" ) // Event 事件类型 type Event struct { Key string Name string Value interface{} } // EventListener 用于接收消息回调 type EventListener interface { onEvent(event *Event) } MemoryQueue MemoryQueue内存消息队列,通过Push接口接收用户数据,通过AddListener来注册EventListener, 同时内部通过poll来从chan event取出数据分发给所有的Listener // MemoryQueue 内存消息队列 type MemoryQueue struct { done chan struct{} queue chan Event listeners []EventListener wg sync.WaitGroup } // Push 添加数据 func (mq *MemoryQueue) Push(eventType, name string, value interface{}) { mq.queue <- Event{Key: eventType + name, Name: name, Value: value} mq.wg.Add(1) } // AddListener 添加监听器 func (mq *MemoryQueue) AddListener(listener EventListener) bool { for _, item := range mq.listeners { if item == listener { return false } } mq.listeners = append(mq.listeners, listener) return true } // Notify 分发消息 func (mq *MemoryQueue) Notify(event *Event) { defer mq.wg.Done() for _, listener := range mq.listeners { listener.onEvent(event) } } func (mq *MemoryQueue) poll() { for { select { case <-mq.done: break case event := <-mq.queue: mq.Notify(&event) } } } // Start 启动内存队列 func (mq *MemoryQueue) Start() { go mq.poll() } // Stop 停止内存队列 func (mq *MemoryQueue) Stop() { mq.wg.Wait() close(mq.done) } Worker Worker接收MemoryQueue里面的数据,然后在本地根据不同类型来进行对应事件事件类型处理, 主要是通过事件的前缀来进行对应事件回调函数的选择 // Worker 工作进程 type Worker struct { name string deferredTaskUpdates map[string][]Task onCommit ConfigUpdateCallback } func (w *Worker) onEvent(event *Event) { switch { // 获取任务事件 case strings.Contains(event.Key, TaskPrefix): w.onTaskEvent(event) // 清除本地延迟队列里面的任务 case strings.Contains(event.Key, ClearTaskPrefix): w.onTaskClear(event) // 获取commit事件 case strings.Contains(event.Key, CommitTaskPrefix): w.onTaskCommit(event) } } 事件处理任务 事件处理任务主要分为:onTaskClear(从本地清楚该数据)、onTaskEvent(数据存储本地延迟存储进行暂存)、onTaskCommit(事务提交) func (w *Worker) onTaskClear(event *Event) { task, err := event.Value.(Task) if !err { // log return } _, found := w.deferredTaskUpdates[task.Group] if !found { return } delete(w.deferredTaskUpdates, task.Group) // 还可以继续停止本地已经启动的任务 } // onTaskCommit 接收任务提交, 从延迟队列中取出数据然后进行业务逻辑处理 func (w *Worker) onTaskCommit(event *Event) { // 获取之前本地接收的所有任务 tasks, found := w.deferredTaskUpdates[event.Name] if !found { return } // 获取配置 config := w.getTasksConfig(tasks) if w.onCommit != nil { w.onCommit(config) } delete(w.deferredTaskUpdates, event.Name) } // onTaskEvent 接收任务数据,此时需要丢到本地暂存不能进行应用 func (w *Worker) onTaskEvent(event *Event) { task, err := event.Value.(Task) if !err { // log return } // 保存任务到延迟更新map configs, found := w.deferredTaskUpdates[task.Group] if !found { configs = make([]Task, 0) } configs = append(configs, task) w.deferredTaskUpdates[task.Group] = configs } // getTasksConfig 获取task任务列表 func (w *Worker) getTasksConfig(tasks []Task) map[string]string { config := make(map[string]string) for _, t := range tasks { config = t.updateConfig(config) } return config } 主流程 unc main() { // 生成一个内存队列启动 queue := NewMemoryQueue(10) queue.Start() // 生成一个worker name := "test" worker := NewWorker(name, func(data map[string]string) { for key, value := range data { println("worker get task key: " + key + " value: " + value) } }) // 注册到队列中 queue.AddListener(worker) taskName := "test" // events 发送的任务事件 configs := []map[string]string{ map[string]string{"task1": "SendEmail", "params1": "Hello world"}, map[string]string{"task2": "SendMQ", "params2": "Hello world"}, } // 分发任务 queue.Push(ClearTaskPrefix, taskName, nil) for _, conf := range configs { queue.Push(TaskPrefix, taskName, Task{Name: taskName, Group: taskName, Config: conf}) } queue.Push(CommitTaskPrefix, taskName, nil) // 停止队列 queue.Stop() } 输出 # go run main.go worker get task key: params1 value: Hello world worker get task key: task1 value: SendEmail worker get task key: params2 value: Hello world worker get task key: task2 value: SendMQ 总结 在分布式环境中,很多时候并不需要使用CP模型,更多时候是满足最终一致性即可 基于2PC和延迟队列的这种设计,主要是依赖于事件驱动的架构 在kafka connect中, 每次节点变化都会触发一次任务的重分配,所以延迟存储直接用的就是内存中的HashMap, 因为即使分配消息的主节点挂了,那就再触发一次事件,直接将HashMap里面的数据清掉,进行下一次事务即可,并不需要保证延迟存储里面的数据不丢, 所以方案因环境、需求不同,可以做一些取舍,没必要什么东西都去加一个CP模型的中间件进来,当然其实那样更简单 未完待续! 更多文章可以访问http://www.sreguide.com/

优秀的个人博客,低调大师

查询用户提交的会员卡表单信息(alipay.marketing.card.activateform.query)JAVA版本demo

说明: 该接口是会员卡开卡场景下,用户确认领卡后,跳转到商户开卡处理页面,商户通过该接口查询用户表单信息 开卡表单模板配置以及获取领卡投放链接参考该贴:[url]https://openclub.alipay.com/read.php?tid=1590&fid=71[/url]调用代码示例:第一步:需要获取AccessToken这个值,因为该接口需要用到。 参考该贴【获取会员信息】:[url]https://openclub.alipay.com/read.php?tid=1817&fid=67[/url] 第二步,核心代码示例: public static void CardActivate_formQuery(){ AlipayClient alipayClien

优秀的个人博客,低调大师

会员卡开卡表单模板配置(alipay.marketing.card.formtemplate.set)JAVA版本demo

官方接口文档:[url]https://docs.open.alipay.com/251/105668/[/url],我这里写的是开卡组件前两步的操作流程,仅供参考1.调用接口前的准备工作(创建应用等)参考该文档:[url]https://docs.open.alipay.com/251/105731/[/url]2.核心代码,这里要说明一下,目前测试发现这个接口的model原型有问题,推荐使用拼接字符串或者使用我下面的这个代码来编写项目,后面这个会将其优化model 示例代码 //会员卡开卡表单模板配置(alipay.marketing.card.formtemplate.set) public static void CardForm_templateSet(){ AlipayClient al

优秀的个人博客,低调大师

【工作效率】使用docker搭建gitlab以及ci平台,完整版本(使用springboot项目演示)

整体步骤概括 准备两台虚拟机,最小安装,配置ip能ping通外网即可,安装docker 一台虚拟机跑gitlab,一台跑gitlab runner和springboot项目 将springboot项目打成systemctl系统服务 编写gitlab.ci自动构建你的springboot项目 自动构建的基本思路是当dev分支合并到master分支时,触发构建,自动发布,下面是详细步骤 1.通过docker、docker-compose搭建gitlab 使用的是一个开源库 https://github.com/sameersbn/docker-gitlab 这一步基本参考这个库就能搭建成功了 确定你安装好了docker和docker-compose 下载官方docker-compose文件wget https://raw.githubusercontent.com/sameersbn/docker-gitlab/master/docker-compose.yml 修改两个地方gitlab_port就是你访问的端口地址,gitlab_host则是你配置的虚拟机ip这时你的目录结构应该是这样的cd至gitlab目录下执行命令docker-compose up等待docker镜像安装完成完成后等待几分钟gitlab初始化,访问http://yourdomain:10080,重置密码这里我已经建好项目了,到此 第一步搭建gitlab服务已经完成接下来是第二步 2.注册gitlab-runner完成项目自动构建 新建dev分支 checkout你的dev分支,开发只对dev分支操作,master分支则用于ci构建 编写gitlab-ci.yml pages: stage: deploy script: - cd /gitlab-project/demo - git pull - mvn package - systemctl restart demo only: - master 可以看到ci只针对master分支,即dev分支merge至master分支时 触发构建 搭建runner服务器前面说到需要准备两台服务器,一台搭建gitlab,一台搭建runner gitlab-runner比较简单,查一查如何安装即可,安装完毕输入 gitlab-ci-multi-runner register Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/): #这里输入你的gitlab域名 # executor输入shell 域名和token在Specific Runners这里注册完毕后可以看到这里有显示,你应该只有一个接下来是将springboot项目做成系统服务systemctl这部分内容自己百度,贴一下自己的demo.service Description=demo [Service] WorkingDirectory=/gitlab-project/demo PrivateTmp=true Type=simple ExecStart=/usr/java/jdk1.8.0_181-amd64/bin/java -jar /gitlab-project/demo/target/demo-0.0.1-SNAPSHOT.jar -java.io.tmpdir=/gitlab-project/tmp --spring.config.location=/gitlab-project/demo/target/classes/application.yml ExecStop=kill -9 `ps -ef|grep java|grep -v grep|grep demo|awk '{print $2}'` [Install] WantedBy=multi-user.target gitlab-project/demo是这个demo项目的路径,在进行ci前 你应该git clone项目至对应路径 3. 最后一步啦 现在项目的某个接口返回数据是这样的 我们修改一下提交至dev分支可以看到刚刚我们提交的请求已经出现在项目主页了,我们合并他 选择对应的job查看,已经在跑了 curl一下 小伙伴们还不赶紧试试?

优秀的个人博客,低调大师

(2018年版本)

对于做工程项目和搞科研的人来说,有现成的模块或工具使用是一件多么美妙的事情啊,无需访问源码或理解内部工作机制的细节即可完成相应的任务。常用的方法是调用一些API,即一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力。本文总结对于机器学习行业者有用的50多个API,主要涉及的领域如下: 人脸和图像识别(Face and Image Recognition) 文本分析、自然语言处理、情感分析(Text Analysis, NLP, Sentiment Analysis) 语言翻译(Language Translation) 机器学习和预测(Machine Learning and prediction) 本文对每个研究领域都列出了一些主流的API,其排列顺序是根据字母顺序而来。 人脸

优秀的个人博客,低调大师

windows下安装Linux双系统共存不需要改引导(U盘安装版本)

1.下载CentOS-6.7-x86_64-bin-DVD1.iso放在任意磁盘 https://www.centoschina.cn/download/9805.html 2.下载PartAssist.exe 分区助手软件。 3.打开分区助手软件在C盘,D盘等等的盘切割分区(可以切割多个分区,也可以切割单个分区,只要你切割出来够装Linux就行),(注意:同一块硬盘内最多4个主分区,或3个主分区外加1个扩展分区,也就是说当你目前已经有4个主分区的情况下,可能会出现“安装系统时空闲分区不可用”状况,必须删除一个主分区,否则无法完成Linux系统的安装) 4.合并切出来的若干个分区 在切割出来的新分区选择合并分区(合并多个刚切割出来的空白分区) 5.格式化新分区并修改文件系统为Ext3,簇大小默认即可 6.下载老毛桃装机版软件(用来做CentOS的) 打开老毛桃装机版(下载地址:http://www.laomaotao.org/)如下图界面,点击ISO模式, 当弹出如下界面时,选择 否 而后弹出下图对话框,在硬盘驱动器一栏选择你的U盘,选择 便捷启动-->写入新的硬盘主引导记录(MBR)-->USB-HDD+(本文选择的是这个)或USB-ZIP+,其他选项依照图示选择,,点击格式化然后点击写入,等待写入完成即可。。 至此就U盘启动制作完成了(需要注意的是如果U盘内有重要文件请提前备份) 7.安装CentOS系统 插入U盘重启电脑,进入BIOS界面选择你的U盘作为第一启动项(该过程不详细描述,大家可以自行百度, 不同品牌电脑进入BIOS的方式有所不同,常见方式有,出现开机logo后快速多次敲击F2、F12、DEL、enter或ESC建,有时具体按键会在开机logo的底部文字内显示),进入BIOS选择U盘作为第一启动项后按F10保存并退出,重启系统,进入如下界面。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册