首页 文章 精选 留言 我的

精选列表

搜索[整合],共10003篇文章
优秀的个人博客,低调大师

SpringBoot2 整合ElasticJob框架,定制化管理流程

一、ElasticJob简介 1、定时任务 在前面的文章中,说过QuartJob这个定时任务,被广泛应用的定时任务标准。但Quartz核心点在于执行定时任务并不是在于关注的业务模式和场景,缺少高度自定义的功能。Quartz能够基于数据库实现任务的高可用,但是不具备分布式并行调度的功能。 2、ElasticJob说明 基础简介 Elastic-Job 是一个开源的分布式调度中间件,由两个相互独立的子项目 Elastic-Job-Lite 和 Elastic-Job-Cloud 组成。Elastic-Job-Lite 为轻量级无中心化解决方案,使用 jar 包提供分布式任务的调度和治理。 Elastic-Job-Cloud 是一个 Mesos Framework,依托于Mesos额外提供资源治理、应用分发以及进程隔离等服务。 功能特点 分布式调度协调 弹性扩容缩容 失效转移 错过执行作业重触发 作业分片一致性,保证同一分片在分布式环境中仅一个执行实例 补刀:人家官网这样描述的,这里赘述一下,充实一下文章。 基础框架结构 该图片来自ElasticJob官网。 由图可知如下内容: 需要Zookeeper组件支持,作为分布式的调度任务,有良好的监听机制,和控制台,下面的案例也就冲这个图解来。 3、分片管理 这个概念在ElasticJob中是最具有特点的,实用性极好。 分片概念 任务的分布式执行,需要将一个任务拆分为多个独立的任务项,然后由分布式的服务器分别执行某一个或几个分片项。 场景描述:假设有服务3台,分3片管理,要处理数据表100条,那就可以100%3,按照余数0,1,2分散到三台服务上执行,看到这里分库分表的基本逻辑涌上心头,这就是为何很多大牛讲说,编程思维很重要。 个性化参数 个性化参数即shardingItemParameter,可以和分片项匹配对应关系,用于将分片项的数字转换为更加可读的业务代码。 场景描述:这里猛一读好像很飘逸,其实就是这个意思,如果分3片,取名[0,1,2]不好看,或者不好标识,可以分别给个别名标识一下,[0=A,1=B,2=C]。 二、定时任务加载 1、核心依赖包 这里使用2.0+的版本。 <dependency> <groupId>com.dangdang</groupId> <artifactId>elastic-job-lite-core</artifactId> <version>2.1.5</version> </dependency> <dependency> <groupId>com.dangdang</groupId> <artifactId>elastic-job-lite-spring</artifactId> <version>2.1.5</version> </dependency> 2、核心配置文件 这里主要配置一下Zookeeper中间件,分片和分片参数。 zookeeper: server: 127.0.0.1:2181 namespace: es-job job-config: cron: 0/10 * * * * ? shardCount: 1 shardItem: 0=A,1=B,2=C,3=D 3、自定义注解 看了官方的案例,没看到好用的注解,这里只能自己编写一个,基于案例的加载过程和核心API作为参考。 核心配置类: com.dangdang.ddframe.job.lite.config.LiteJobConfiguration 根据自己想如何使用注解的思路,比如我只想注解定时任务名称和Cron表达式这两个功能,其他参数直接统一配置(这里可能是受QuartJob影响太深,可能根本就是想省事...) @Inherited @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface TaskJobSign { @AliasFor("cron") String value() default ""; @AliasFor("value") String cron() default ""; String jobName() default ""; } 4、作业案例 这里打印一些基本参数,对照配置和注解,一目了然。 @Component @TaskJobSign(cron = "0/5 * * * * ?",jobName = "Hello-Job") public class HelloJob implements SimpleJob { private static final Logger LOG = LoggerFactory.getLogger(HelloJob.class.getName()) ; @Override public void execute(ShardingContext shardingContext) { LOG.info("当前线程: "+Thread.currentThread().getId()); LOG.info("任务分片:"+shardingContext.getShardingTotalCount()); LOG.info("当前分片:"+shardingContext.getShardingItem()); LOG.info("分片参数:"+shardingContext.getShardingParameter()); LOG.info("任务参数:"+shardingContext.getJobParameter()); } } 5、加载定时任务 既然自定义注解,那加载过程自然也要自定义一下,读取自定义的注解,配置化,加入容器,然后初始化,等着任务执行就好。 @Configuration public class ElasticJobConfig { @Resource private ApplicationContext applicationContext ; @Resource private ZookeeperRegistryCenter zookeeperRegistryCenter; @Value("${job-config.cron}") private String cron ; @Value("${job-config.shardCount}") private int shardCount ; @Value("${job-config.shardItem}") private String shardItem ; /** * 配置任务监听器 */ @Bean public ElasticJobListener elasticJobListener() { return new TaskJobListener(); } /** * 初始化配置任务 */ @PostConstruct public void initTaskJob() { Map<String, SimpleJob> jobMap = this.applicationContext.getBeansOfType(SimpleJob.class); Iterator iterator = jobMap.entrySet().iterator(); while (iterator.hasNext()) { // 自定义注解管理 Map.Entry<String, SimpleJob> entry = (Map.Entry)iterator.next(); SimpleJob simpleJob = entry.getValue(); TaskJobSign taskJobSign = simpleJob.getClass().getAnnotation(TaskJobSign.class); if (taskJobSign != null){ String cron = taskJobSign.cron() ; String jobName = taskJobSign.jobName() ; // 生成配置 SimpleJobConfiguration simpleJobConfiguration = new SimpleJobConfiguration( JobCoreConfiguration.newBuilder(jobName, cron, shardCount) .shardingItemParameters(shardItem).jobParameter(jobName).build(), simpleJob.getClass().getCanonicalName()); LiteJobConfiguration liteJobConfiguration = LiteJobConfiguration.newBuilder( simpleJobConfiguration).overwrite(true).build(); TaskJobListener taskJobListener = new TaskJobListener(); // 初始化任务 SpringJobScheduler jobScheduler = new SpringJobScheduler( simpleJob, zookeeperRegistryCenter, liteJobConfiguration, taskJobListener); jobScheduler.init(); } } } } 絮叨一句:不要疑问这些API是怎么知道,看下官方文档的案例,他们怎么使用这些核心API,这里就是照着写过来,就是多一步自定义注解类的加载过程。当然官方文档大致读一遍还是很有必要的。 补刀一句:如何快速学习一些组件的用法,首先找到官方文档,或者开源库Wiki,再不济ReadMe文档(如果都没有,酌情放弃,另寻其他),熟悉基本功能是否符合自己的需求,如果符合,就看下基本用法案例,熟悉API,最后就是研究自己需要的功能模块,个人经验来看,该过程是弯路最少,坑最少的。 6、任务监听 用法非常简单,实现ElasticJobListener接口。 @Component public class TaskJobListener implements ElasticJobListener { private static final Logger LOG = LoggerFactory.getLogger(TaskJobListener.class); private long beginTime = 0; @Override public void beforeJobExecuted(ShardingContexts shardingContexts) { beginTime = System.currentTimeMillis(); LOG.info(shardingContexts.getJobName()+"===>开始..."); } @Override public void afterJobExecuted(ShardingContexts shardingContexts) { long endTime = System.currentTimeMillis(); LOG.info(shardingContexts.getJobName()+ "===>结束...[耗时:"+(endTime - beginTime)+"]"); } } 絮叨一句:before和after执行前后,中间执行目标方法,标准的AOP切面思想,所以底层水平决定了对上层框架的理解速度,那本《Java编程思想》上的灰尘是不是该擦擦? 三、动态添加 1、作业任务 有部分场景需要动态添加和管理定时任务,基于上面的加载流程,在自定义一些步骤就可以。 @Component public class GetTimeJob implements SimpleJob { private static final Logger LOG = LoggerFactory.getLogger(GetTimeJob.class.getName()) ; private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") ; @Override public void execute(ShardingContext shardingContext) { LOG.info("Job Name:"+shardingContext.getJobName()); LOG.info("Local Time:"+format.format(new Date())); } } 2、添加任务服务 这里就动态添加上面的任务。 @Service public class TaskJobService { @Resource private ZookeeperRegistryCenter zookeeperRegistryCenter; public void addTaskJob(final String jobName,final SimpleJob simpleJob, final String cron,final int shardCount,final String shardItem) { // 配置过程 JobCoreConfiguration jobCoreConfiguration = JobCoreConfiguration.newBuilder( jobName, cron, shardCount) .shardingItemParameters(shardItem).build(); JobTypeConfiguration jobTypeConfiguration = new SimpleJobConfiguration(jobCoreConfiguration, simpleJob.getClass().getCanonicalName()); LiteJobConfiguration liteJobConfiguration = LiteJobConfiguration.newBuilder( jobTypeConfiguration).overwrite(true).build(); TaskJobListener taskJobListener = new TaskJobListener(); // 加载执行 SpringJobScheduler jobScheduler = new SpringJobScheduler( simpleJob, zookeeperRegistryCenter, liteJobConfiguration, taskJobListener); jobScheduler.init(); } } 补刀一句:这里添加之后,任务就会定时执行,如何停止任务又是一个问题,可以在任务名上做一些配置,比如在数据库生成一条记录[1,job1,state],如果调度到state为停止状态的任务,直接截胡即可。 3、测试接口 @RestController public class TaskJobController { @Resource private TaskJobService taskJobService ; @RequestMapping("/addJob") public String addJob(@RequestParam("cron") String cron,@RequestParam("jobName") String jobName, @RequestParam("shardCount") Integer shardCount, @RequestParam("shardItem") String shardItem) { taskJobService.addTaskJob(jobName, new GetTimeJob(), cron, shardCount, shardItem); return "success"; } } 四、源代码地址 GitHub·地址 https://github.com/cicadasmile/middle-ware-parent GitEE·地址 https://gitee.com/cicadasmile/middle-ware-parent

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

笔记:C#和C++常见容器整合

前言 有一段时间未使用c#/c++,因此写个合集用来回忆常用容器,也方便以后复习记忆 C#常见容器 Array 【数组】 简单的数组集合特点:内存中连续存储,索引快,赋值修改也快。缺点:需要事先声明数组长度,插值复杂。 String[] array = new String[5]; array[0] = '1'; ArrayList 【数组列表】 特点: 无需像Array需要初始长度,可以动态增加。 底层是Array,但继承了List接口 可以存储不同类型数据,所有元素视为object,也因此会出现类型不匹配,即不是类型安全。在拆箱操作时,性能消耗大。 ArrayList Alist = new Arraylist(); Alist.add('there') Alist.add(2333) List< T > 泛型 可以自定义list中元素的结构 public class TeamMate { public string name; public int age; public int groupNum; public override string ToString() { return "name=" + name + ",age=" + age + ",groupNum=" + groupNum; } } static void Main(string[] args) { List<TeamMate> list = new List<TeamMate>(); list.Add(new TeamMate { name = "Tom", age = 20, groupNum = 12 }); list.Add(new TeamMate { name = "Jerry", age = 22, groupNum = 13 }); foreach(Person p in list) { Console.WriteLine(p.ToString()); } } LinkedList< T > 【双向链表】 LinkedList< T >集合类没有非泛型类的版本,相当于C++中的list,内部实现是双链表 优点:如果要插入一个元素到链表的中间位置,会非常的快,原因:如果插入,只需要修改上一个元素的Next与下一个元素的Previous的引用则可。而像ArrayList数组列表中,如果插入,需要移动其后的所有元素。缺点:链表只能是一个接着一个的访问,这样就要用较长的时间来查找定位位于链表中间的元素。 因此从时间复杂度上,要在第K个位置插入一个元素,无论是ArrayList还是LinkedList的时间复杂度都为O(n)+O(1)=O(n)其中数组列表查找时间为O(1),但由于要将后续元素向后移动,则插入时间为O(n);而链表查找时间为O(n),但插入时间为O(1)。 将 List.Add方法和LinkedList的Add方法相比较,真正的性能差别不在于Add操作,而在LinkedList在给GC(垃圾回收机制)的压力上。一个List本质上是将其数据保存在一个堆栈的数组上,而LinkedList是将其所有节点保存在一系列堆栈上。这就使得GC需要更多地管理堆栈上LinkedList的节点对象。 LinkedListNode被LinkedList类包含,用LinkedListNode类,可以获得元素的上一个与下一个元素的引用。 LinkedList<int> list = new LinkedList<int>(); list.AddFirst(2); list.AddFirst(1); list.AddLast(4); list.AddLast(5); 此时链表结构为:1 2 4 5 LinkedListNode<int> NewList = list.Find(4); //4节点的位置 list.AddBefore(NewList, 3); //在4前增加3 此时链表结构为:1 2 3 4 5 HashTable【哈希表】 哈希表是通过key-value形式来存取数据的,相同存储形式的还有字典Dictionary,由于存储对象都是object,因而可以满足任意形式的键值对,但也因此有装箱和拆箱的性能消耗。 特点:当前HashTable中的被占用空间达到一个百分比的时候就将该空间自动扩容,在.net中这个百分比是72%,也叫.net中HashTable的填充因子为0.72。 构造哈希表 Hashtable table = new Hashtable(); table.Add("Name", "Tom"); table.Add("Age", 20); 遍历哈希表 Hashtable table = new Hashtable(); table.Add("Name", "Tom"); table.Add("Age", 20); foreach(DictionaryEntry entry in table) { Console.Write(entry.Key + ":"); Console.WriteLine(entry.Value); } Dictionary< T >【字典】(HashTable的泛型) 相当于C++中的map。只不过map的内部实现是红黑树,Dictionary的内部实现是哈希表,是类型安全的容器。在单线程的时候使用Dictionary更好,多线程的时候使用HashTable更好。 class Person { public string name; public int age; public override string ToString() { return "name=" + name + ",age=" + age; } } class Program { static void Main(string[] args) { Dictionary<string, Person> dictionary = new Dictionary<string, Person>(); Person person1 = new Person() { name = "Tom", age = 20 }; Person person2 = new Person() { name = "Jerry", age = 21}; dictionary.Add("学生1", person1); dictionary.Add("学生2", person2); foreach(KeyValuePair<string, Person> pair in dictionary) { Console.Write(pair.Key + ":"); Console.WriteLine(pair.Value.ToString()); } } } //只取Key值 foreach (string key in dictionary.Keys) { Console.WriteLine(key); } //只取value foreach (Person person in dictionary.Values) { Console.WriteLine(person); } HashSet< T > 特点: 一组不包含重复元素的集合 用O(n)的space来换取O(n)的时间,也就是查找元素的时间是O(1) HashSet<string> set = new HashSet<string>(); set.Add("Tom"); set.Add("Jerry"); set.Add("Tom"); foreach(var n in set) { Console.WriteLine(n); } 上述集合元素中Tom有2个,是重复值,那么输出HashSet结果就只显示一个,这就是不显示重复值的功能。 Queue【队列】 特点:先进先出的对象集合当需要对各项进行先进先出的访问时,则使用队列。当在列表中添加一项,称为入队,当从列表中移除一项时,称为出队。 Queue q = new Queue(); //入队操作 q.Enqueue("A"); q.Enqueue("B"); q.Enqueue("C"); //出队操作 q.Dequeue() Queue< T >【泛型队列】 class Person { public string name; public int age; public override string ToString() { return "name=" + name + ",age=" + age; } } class Program { static void Main(string[] args) { Person person1 = new Person() { name = "Tom", age = 20}; Person person2 = new Person() { name = "Jerry", age = 20 }; Queue<Person> q = new Queue<Person>(); q.Enqueue(person1); q.Enqueue(person2); Console.WriteLine(q.Dequeue()); } } Stack【栈】 特点:后进先出的对象集合。当需要对各项进行后进先出的访问时,则使用栈。当在列表中添加一项,称为推入元素,当从列表中移除一项时,称为弹出元素。 Stack st = new Stack(); //入栈 st.Push("A"); st.Push("B"); //出栈 st.Pop() C++常见容器 转载 C++ STL的实现:1.vector 底层数据结构为数组,支持快速随机访问 2.list 底层数据结构为双向链表,支持快速增删 3.deque 底层数据结构为一个中央控制器和多个缓冲区,支持首尾(中间不能)快速增删,也支持随机访问注:deque是一个双端队列(double-ended queue),也是在堆中保存内容的.它的保存形式如下:[堆1] --> [堆2] -->[堆3] --> ...每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品4.stack 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时 5.queue 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装) 6.priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现 7.set 底层数据结构为红黑树,有序,不重复 8.multiset 底层数据结构为红黑树,有序,可重复 9.map 底层数据结构为红黑树,有序,不重复 10.multimap 底层数据结构为红黑树,有序,可重复 11.hash_set 底层数据结构为hash表,无序,不重复 12.hash_multiset 底层数据结构为hash表,无序,可重复 13.hash_map 底层数据结构为hash表,无序,不重复 14.hash_multimap 底层数据结构为hash表,无序,可重复 stl容器区别: vector list deque set map-底层实现 在STL中基本容器有: vector、list、deque、set、map set 和map都是无序的保存元素,只能通过它提供的接口对里面的元素进行访问 set:集合, 用来判断某一个元素是不是在一个组里面,使用的比较少map:映射,相当于字典,把一个值映射成另一个值,如果想创建字典的话使用它好了底层采用的是树型结构,多数使用平衡二叉树实现,查找某一值是常数时间,遍历起来效果也不错, 只是每次插入值的时候,会重新构成底层的平衡二叉树,效率有一定影响. vector、list、deque是有序容器 1.vector vector就是动态数组.它也是在堆中分配内存,元素连续存放,有保留内存,如果减少大小后,内存也不会释放.如果新值>当前大小时才会再分配内存. 它拥有一段连续的内存空间,并且起始地址不变,因此它能非常好的支持随即存取,即[]操作符,但由于它的内存空间是连续的,所以在中间进行插入和删除会造成内存块的拷贝,另外,当该数组后的内存空间不够时,需要重新申请一块足够大的内存并进行内存的拷贝。这些都大大影响了vector的效率。 对最后元素操作最快(在后面添加删除最快), 此时一般不需要移动内存,只有保留内存不够时才需要 对中间和开始处进行添加删除元素操作需要移动内存,如果你的元素是结构或是类,那么移动的同时还会进行构造和析构操作,所以性能不高 (最好将结构或类的指针放入vector中,而不是结构或类本身,这样可以避免移动时的构造与析构)。访问方面,对任何元素的访问都是O(1),也就是是常数的,所以vector常用来保存需要经常进行随机访问的内容,并且不需要经常对中间元素进行添加删除操作. 相比较可以看到vector的属性与string差不多,同样可以使用capacity看当前保留的内存,使用swap来减少它使用的内存. capacity()返回vector所能容纳的元素数量(在不重新分配内存的情况下) 总结需要经常随机访问请用vector 2.list list就是双向链表,元素也是在堆中存放,每个元素都是放在一块内存中,它的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点使得它的随机存取变的非常没有效率,因此它没有提供[]操作符的重载。但由于链表的特点,它可以以很好的效率支持任意地方的删除和插入。 list没有空间预留习惯,所以每分配一个元素都会从内存中分配,每删除一个元素都会释放它占用的内存. list在哪里添加删除元素性能都很高,不需要移动内存,当然也不需要对每个元素都进行构造与析构了,所以常用来做随机操作容器.但是访问list里面的元素时就开始和最后访问最快访问其它元素都是O(n) ,所以如果需要经常随机访问的话,还是使用其它的好 总结如果你喜欢经常添加删除大对象的话,那么请使用list要保存的对象不大,构造与析构操作不复杂,那么可以使用vector代替list<指针>完全是性能最低的做法,这种情况下还是使用vector<指针>好,因为指针没有构造与析构,也不占用很大内存 3.deque deque是一个双端队列(double-ended queue),也是在堆中保存内容的.它的保存形式如下:[堆1]...[堆2]...[堆3]每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品. 它支持[]操作符,也就是支持随即存取,可以让你在前面快速地添加删除元素,或是在后面快速地添加删除元素,然后还可以有比较高的随机访问速度,和vector的效率相差无几,它支持在两端的操作:push_back,push_front,pop_back,pop_front等,并且在两端操作上与list的效率也差不多。在标准库中vector和deque提供几乎相同的接口,在结构上它们的区别主要在于这两种容器在组织内存上不一样,deque是按页或块来分配存储器的,每页包含固定数目的元素.相反vector分配一段连续的内存,vector只是在序列的尾段插入元素时才有效率,而deque的分页组织方式即使在容器的前端也可以提供常数时间的insert和erase操作,而且在体积增长方面也比vector更具有效率 总结:vector是可以快速地在最后添加删除元素,并可以快速地访问任意元素list是可以快速地在所有地方添加删除元素,但是只能快速地访问最开始与最后的元素deque在开始和最后添加元素都一样快,并提供了随机访问方法,像vector一样使用[]访问任意元素,但是随机访问速度比不上vector快,因为它要内部处理堆跳转deque也有保留空间.另外,由于deque不要求连续空间,所以可以保存的元素比vector更大,这点也要注意一下.还有就是在前面和后面添加元素时都不需要移动其它块的元素,所以性能也很高。 因此在实际使用时,如何选择这三个容器中哪一个,应根据你的需要而定,一般应遵循下面的原则:1、如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector2、如果你需要大量的插入和删除,而不关心随即存取,则应使用list3、如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque。 c++部分的原文出处

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

FEBS Cloud v1.1 发布,整合 Spring Cloud Alibaba

FEBS Cloud是一款使用Spring Cloud Greenwich.SR3、Spring Cloud OAuth2 & Spring Cloud Alibaba构建的低耦合权限管理系统,前端(FEBS Cloud Web)采用vue element admin构建。FEBS意指:Fast,Easy use,Beautiful和Safe。该系统具有如下特点: 前后端分离架构,客户端和服务端纯Token交互; 认证服务器与资源服务器分离,方便接入自己的微服务系统; 微服务防护,客户端请求资源只能通过微服务网关获取; 集成Spring Boot Admin,多维度监控微服务; 集成Spring Cloud Alibaba Nacos服务治理和集中配置管理; 网关集成Sentinel流控; 集成Zipkin,方便跟踪Feign调用链; 集成ELK,集中管理日志,便于问题分析; 微服务Docker化,使用Docker Compose一键部署; 提供详细的使用文档和搭建教程; 前后端请求参数校验,Excel导入导出,代码生成等。 文档与教程 项目文档及手摸手搭建教程地址:https://www.kancloud.cn/mrbird/spring-cloud/1263679 更新日志 网关使用Spring Cloud Gateway重新构建 使用Spring Cloud Alibaba Nacos服务治理 使用Spring Cloud Alibaba Nacos集中管理微服务配置 自定义@ControllerEndpoint注解优化Controller层代码 Spring Cloud升级到Greenwich.SR3 Spring Boot升级到2.1.8.RELEASE 代码优化及BUG修复 系统架构 系统架构如下图所示(右键在新标签页中打开图片): 项目地址 平台 FEBS Cloud(后端) FEBS Cloud Web(前端) GitHub https://github.com/wuyouzhuguli/FEBS-Cloud https://github.com/wuyouzhuguli/FEBS-Cloud-Web Gitee https://gitee.com/mrbirdd/FEBS-Cloud https://gitee.com/mrbirdd/FEBS-Cloud-Web 演示地址 演示地址(服务器资源有限,没有搭建ELK):http://49.234.20.223:9527 演示环境账号密码: 账号 密码 权限 scott 1234qwer 注册账户,拥有查看权限

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

SpringBoot2.0 整合 RocketMQ ,实现请求异步处理

本文源码:GitHub·点这里 || GitEE·点这里 一、RocketMQ简介 1、架构图片 2、角色分类 (1)、Broker RocketMQ 的核心,接收 Producer 发过来的消息、处理 Consumer 的消费消息请求、消息的持 久化存储、服务端过滤功能等 。 (2)、NameServer 消息队列中的状态服务器,集群的各个组件通过它来了解全局的信息 。类似微服务中注册中心的服务注册,发现,下线,上线的概念。 热备份:NamServer可以部署多个,相互之间独立,其他角色同时向多个NameServer 机器上报状态信息。 心跳机制:NameServer 中的 Broker、 Topic等状态信息不会持久存储,都是由各个角色定时上报并存储到内存中,超时不上报的话, NameServer会认为某个机器出故障不可用。 (3)、Producer 消息的生成者,最常用的producer类就是DefaultMQProducer。 (4)、Consumer 消息的消费者,常用Consumer类DefaultMQPushConsumer收到消息后自动调用传入的处理方法来处理,实时性高DefaultMQPullConsumer用户自主控制 ,灵活性更高。 3、通信机制 (1)、Broker启动后需要完成一次将自己注册至NameServer的操作;随后每隔30s时间定时向NameServer更新Topic路由信息。 (2)、Producer发送消息时候,需要根据消息的Topic从本地缓存的获取路由信息。如果没有则更新路由信息会从NameServer重新拉取,同时Producer会默认每隔30s向NameServer拉取一次路由信息。 (3)、Consumer消费消息时候,从NameServer获取的路由信息,并再完成客户端的负载均衡后,监听指定消息队列获取消息并进行消费。 二、代码实现案例 1、项目结构图 版本描述 <spring-boot.version>2.1.3.RELEASE</spring-boot.version> <rocketmq.version>4.3.0</rocketmq.version> 2、配置文件 rocketmq: # 生产者配置 producer: isOnOff: on # 发送同一类消息的设置为同一个group,保证唯一 groupName: CicadaGroup # 服务地址 namesrvAddr: 127.0.0.1:9876 # 消息最大长度 默认1024*4(4M) maxMessageSize: 4096 # 发送消息超时时间,默认3000 sendMsgTimeout: 3000 # 发送消息失败重试次数,默认2 retryTimesWhenSendFailed: 2 # 消费者配置 consumer: isOnOff: on # 官方建议:确保同一组中的每个消费者订阅相同的主题。 groupName: CicadaGroup # 服务地址 namesrvAddr: 127.0.0.1:9876 # 接收该 Topic 下所有 Tag topics: CicadaTopic~*; consumeThreadMin: 20 consumeThreadMax: 64 # 设置一次消费消息的条数,默认为1条 consumeMessageBatchMaxSize: 1 # 配置 Group Topic Tag rocket: group: rocketGroup topic: rocketTopic tag: rocketTag 3、生产者配置 /** * RocketMQ 生产者配置 */ @Configuration public class ProducerConfig { private static final Logger LOG = LoggerFactory.getLogger(ProducerConfig.class) ; @Value("${rocketmq.producer.groupName}") private String groupName; @Value("${rocketmq.producer.namesrvAddr}") private String namesrvAddr; @Value("${rocketmq.producer.maxMessageSize}") private Integer maxMessageSize ; @Value("${rocketmq.producer.sendMsgTimeout}") private Integer sendMsgTimeout; @Value("${rocketmq.producer.retryTimesWhenSendFailed}") private Integer retryTimesWhenSendFailed; @Bean public DefaultMQProducer getRocketMQProducer() { DefaultMQProducer producer; producer = new DefaultMQProducer(this.groupName); producer.setNamesrvAddr(this.namesrvAddr); //如果需要同一个jvm中不同的producer往不同的mq集群发送消息,需要设置不同的instanceName if(this.maxMessageSize!=null){ producer.setMaxMessageSize(this.maxMessageSize); } if(this.sendMsgTimeout!=null){ producer.setSendMsgTimeout(this.sendMsgTimeout); } //如果发送消息失败,设置重试次数,默认为2次 if(this.retryTimesWhenSendFailed!=null){ producer.setRetryTimesWhenSendFailed(this.retryTimesWhenSendFailed); } try { producer.start(); } catch (MQClientException e) { e.printStackTrace(); } return producer; } } 4、消费者配置 /** * RocketMQ 消费者配置 */ @Configuration public class ConsumerConfig { private static final Logger LOG = LoggerFactory.getLogger(ConsumerConfig.class) ; @Value("${rocketmq.consumer.namesrvAddr}") private String namesrvAddr; @Value("${rocketmq.consumer.groupName}") private String groupName; @Value("${rocketmq.consumer.consumeThreadMin}") private int consumeThreadMin; @Value("${rocketmq.consumer.consumeThreadMax}") private int consumeThreadMax; @Value("${rocketmq.consumer.topics}") private String topics; @Value("${rocketmq.consumer.consumeMessageBatchMaxSize}") private int consumeMessageBatchMaxSize; @Resource private RocketMsgListener msgListener; @Bean public DefaultMQPushConsumer getRocketMQConsumer(){ DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName); consumer.setNamesrvAddr(namesrvAddr); consumer.setConsumeThreadMin(consumeThreadMin); consumer.setConsumeThreadMax(consumeThreadMax); consumer.registerMessageListener(msgListener); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); consumer.setConsumeMessageBatchMaxSize(consumeMessageBatchMaxSize); try { String[] topicTagsArr = topics.split(";"); for (String topicTags : topicTagsArr) { String[] topicTag = topicTags.split("~"); consumer.subscribe(topicTag[0],topicTag[1]); } consumer.start(); }catch (MQClientException e){ e.printStackTrace(); } return consumer; } } 5、消息监听配置 /** * 消息消费监听 */ @Component public class RocketMsgListener implements MessageListenerConcurrently { private static final Logger LOG = LoggerFactory.getLogger(RocketMsgListener.class) ; @Resource private ParamConfigService paramConfigService ; @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext context) { if (CollectionUtils.isEmpty(list)){ return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } MessageExt messageExt = list.get(0); LOG.info("接受到的消息为:"+new String(messageExt.getBody())); int reConsume = messageExt.getReconsumeTimes(); // 消息已经重试了3次,如果不需要再次消费,则返回成功 if(reConsume ==3){ return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } if(messageExt.getTopic().equals(paramConfigService.rocketTopic)){ String tags = messageExt.getTags() ; switch (tags){ case "rocketTag": LOG.info("开户 tag == >>"+tags); break ; default: LOG.info("未匹配到Tag == >>"+tags); break; } } // 消息消费成功 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } } 6、配置参数绑定 @Service public class ParamConfigService { @Value("${rocket.group}") public String rocketGroup ; @Value("${rocket.topic}") public String rocketTopic ; @Value("${rocket.tag}") public String rocketTag ; } 7、消息发送测试 @Service public class RocketMqServiceImpl implements RocketMqService { @Resource private DefaultMQProducer defaultMQProducer; @Resource private ParamConfigService paramConfigService ; @Override public SendResult openAccountMsg(String msgInfo) { // 可以不使用Config中的Group defaultMQProducer.setProducerGroup(paramConfigService.rocketGroup); SendResult sendResult = null; try { Message sendMsg = new Message(paramConfigService.rocketTopic, paramConfigService.rocketTag, "open_account_key", msgInfo.getBytes()); sendResult = defaultMQProducer.send(sendMsg); } catch (Exception e) { e.printStackTrace(); } return sendResult ; } } 三、项目源码 GitHub·地址 https://github.com/cicadasmile/middle-ware-parent GitEE·地址 https://gitee.com/cicadasmile/middle-ware-parent

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

SpringBoot整合RabbitMQ之典型应用场景实战三

实战前言 RabbitMQ 作为目前应用相当广泛的消息中间件,在企业级应用、微服务应用中充当着重要的角色。特别是在一些典型的应用场景以及业务模块中具有重要的作用,比如业务服务模块解耦、异步通信、高并发限流、超时业务、数据延迟处理等。前两篇博文我介绍分享了RabbitMQ在业务服务模块异步解耦以及通信的实战业务场景,感兴趣童鞋可以前往观看:1.https://www.roncoo.com/article/detail/134309 2.https://www.roncoo.com/article/detail/134312 这篇博文我们继续介绍分享RabbitMQ死信队列实战以及在支付系统中支付过程超时则自动失效其下单记录 这样的业务场景! RabbitMQ 实战:死信队列认识与场景实战 死信队列认识 死信队列,又可以称之为“延迟/延时队列”,也是队列的一种,只不过与普通的队列最大的不同之处在于创建时的组成成分不同,创建死信队列的“成分”将不仅仅只是:名称、持久化、自动删除等基本属性,还包含了死信交换机、死信路由甚至还有TTL(Time-To-Live)即队列中消息可生存的时间。 死信队列其实最大的作用是可以实现消息或者数据延迟/延时处理,而且还可以动态的设定延迟的时间,即动态设定 TTL。典型的业务场景很多,在这里就不一一列举了,总之,凡是业务中需要延迟一定时间再处理的数据均可以将其压入死信队列中,等待一定的时间后再执行真正的处理逻辑! 下面是死信队列在创建、绑定、生产消息、消费消息过程的结构流程图,在这里其实已经很明确的指出死信队列的创建跟绑定逻辑 以及 真正监听消费处理消息的队列的绑定逻辑。图中问题的答案为:当入死信队列的消息TTL一到,它自然而然的将被路由到 死信交换机绑定的队列 中被真正消费处理!!! 死信队列场景实战 有了上面的流程图做指导,接下来,我们将用死信队列实战这样的一个业务场景:用户在商城下单成功并点击去支付后在指定时间未支付时自动失效!于是乎,我们需要创建两个消息模型,在 RabbitmqConfig 实施: 死信队列:用于设定指定的待支付的交易订单号在指定的 TTL(单位为 ms)后何去何从! 真正队列:用于监听消费处理指定的交易订单号,即判断该交易订单号是否已完成,如果否,则失效之! 接下来是我们的生产端的逻辑:用户商城下单的处理! 接下来是等待固定的 TTL:在这里设定的是 10s,当消息入死信队列 10s 后,将自然而然的将消息路由到下一个中转站,即真正的消费监听处理队列进行处理:判断该笔交易订单号是否已经付款,如果否,则失效之! 可以将该服务跑起来,然后发起 controller 的用户下单请求,会发现消息入死信队列后不会立马被消费,等待 10s 会,消息会被路由到真正的消费队列中进行处理,这一现象可以在 MQ 的后端控制台应用中看到! 总结:到此我们的死信队列已经实战完毕,回顾我们所介绍的历程,其实核心重点在于上面的那张 “死信队列的结构流程图”,理解了这个结构流程图中的相关组件及其流程,则在实战各种需要延时处理的业务场景将得心应手,包括如何创建死信队列,如何面向生产端绑定死信队列,如何面向消费端绑定真正的队列等等!而对于死信队列的实战场景,前面也介绍过了:凡是需要等待一定时间或者需要缓一缓特定时间的业务、数据均可以通过死信队列来实现! 可以将该服务跑起来,然后发起 controller 的用户下单请求,会发现消息入死信队列后不会立马被消费,等待 10s 会,消息会被路由到真正的消费队列中进行处理,这一现象可以在 MQ 的后端控制台应用中看到! 回顾与总结 RabbitMQ 的认识与实际业务场景的实战到此我都已经介绍完毕,总体而言,RabbitMQ 作为目前应用相当广泛的消息中间件,在我们实际系统的业务模块中具有重要的作用,特别是刚开始介绍的几种消息模型以及死信队列模型在微服务系统、分布式系统中均可充当重要的角色,其中我们实战的业务场景包括业务服务模块解耦异步通信(异步发送日志、异步发送邮件);另外,我们还介绍了消息确认机制,这是一种 MQ 确保消息能被消费者消费的机制,对于一些业务模块也是有广泛的应用;除此之外,我们还模拟实战了秒杀系统、抢单系统这样的业务场景下 RabbitMQ 的作用:限流、排队缓压、减少数据库读写锁的发生等等! 彩蛋:本博文介绍了RabbitMQ死信队列及其业务场景的实战,此业务场景其实适用于任何需要“延迟一定时间处理”的业务,包括本博文介绍的 “支付系统-用户下单点击去支付后超时仍未支付成功时则需要暂时失效其下单记录”!另外,博主已将RabbitMQ相关技术以及场景实战的相关要点录制成了视频教程,感兴趣小伙伴可以前往学习观看:https://www.roncoo.com/course/view/95ffac8bd3aa4f2d8a0d83c32f46c69d

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

Mybatis3.x 遇到的问题整合[持续更新]

在映射文件中写了这样的一条sql <select id="selectUserById" parameter="int" resultType="User"> select * from user <if test="id != 0"> id = #{id} </if> </select> 这条sql运行后会抛出一个There is no getter for property named "id" in "class java.lang.Integer"这样的错误 原因: mybatis默认使用ongl解析参数,在这里就变成了使用 "java.lang.Integer.id"进行取值,而这里的id是User类所以就抛出了异常 解决方案: 1.将#{id}替换为#{_paramter} <select id="selectUserById" parameter="int" resultType="User"> select * from user <if test="id != 0"> id = #{_parameter} </if> </select> 在方法中提前声明 public void methodName(@Param(value ="id") int id,@Param(value="username") String username); 将parameterType="int"转为parameterType="User" 将id的取值顺序重新转到User.id中即可

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

HDwiki+discuz在启用https下的整合问题

这几天在折腾HDwiki+discuz,系统环境是FreeBSD 10.4, PHP 5.6(HDwiki要求),PHP-PDO_MYSQL,PHP-GD,PHP-XML等,MYSQL 5.7 HDwiki 5.1(6.0安装成功后打开前台,提示http 500错误,找不到原因) discuz X3.4 因为宽带运营商关闭了80端口,443端口有开,所以就直接用上https,结果问题一大堆。 首先,discuz和ucenter在https下通信失败,后来找到修改方法: DISCUZ 开启https后ucenter通信失败解决方法,一般是做完301重定向https后通信失败的,下面是具体解决方法: 打开目录 uc_server/model/misc.php 文件;找到69行,插入下面代码: ...... $port = !empty($matches['port']) ? $matches['port'] : ($matches['scheme'] == 'https' ? 443 : 80); //以下为插入代码 if(substr($url,0,5)=='https'){ $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); if($post){ curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $post); } if($cookie){ curl_setopt($ch, CURLOPT_COOKIE, $cookie); } curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); return curl_exec($ch); } //插入代码结束 if($post) { $out = "POST $path HTTP/1.0\r\n"; ...... 接下来设置HDwiki和ucenter,问题比较多了。 一开始,只要设置https,就提示“您输入的URL地址不正确!” 网上也没有任何资料,经过调试,发现要修改HDwiki目录下的control/admin_setting.php的659行 原来长这样: if(empty($ucapi) || !preg_match("/^(http:\/\/)/i", $ucapi)) { 修改成这样: if(empty($ucapi) || !preg_match("/^((http:|https:)\/\/)/i", $ucapi)) { 这个问题解决后,出现的是“uc_url_unreachable”错误。走了不少弯路后,发现还是https的问题。 admin_setting.php调用了api/uc_client/client.php里的uc_fopen(),而这个函数使用fsockopen模拟浏览器进行访问。 并且只有拼接了http协议的情况。对这个接口文件的分析,见这篇文章http://blog.csdn.net/yanhui_wei/article/details/17919645 综合这些分析,比较一下discuz的uc_server/model/misc.php和HDwiki下的api/uc_client/client.php,会发现: discuz的uc_server/model/misc.php里的function dfopen2()和 function dfopen()与HDwiki下的api/uc_client/client.php的uc_fopen2()和uc_fopen()基本上是类似的。 所以解决方法如下: 1、拷贝discuz下的uc_client目录,替换HDwiki下的api/uc_client目录 2、修改api/uc_client/client.php文件,重点在于用function dfopen2()和 function dfopen()的内容替换uc_fopen2()和uc_fopen(),修改完成的结果如下: function uc_fopen2($url, $limit = 0, $post = '', $cookie = '', $bysocket = FALSE, $ip = '', $timeout = 15, $block = TRUE, $encodetype = 'URLENCODE') { $__times__ = isset($_GET['__times__']) ? intval($_GET['__times__']) + 1 : 1; if($__times__ > 2) { return ''; } $url .= (strpos($url, '?') === FALSE ? '?' : '&')."__times__=$__times__"; return uc_fopen($url, $limit, $post, $cookie, $bysocket, $ip, $timeout, $block, $encodetype); } function uc_fopen($url, $limit = 0, $post = '', $cookie = '', $bysocket = FALSE , $ip = '', $timeout = 15, $block = TRUE, $encodetype = 'URLENCODE') { $return = ''; $matches = parse_url($url); $scheme = $matches['scheme']; $host = $matches['host']; $path = $matches['path'] ? $matches['path'].($matches['query'] ? '?'.$matches['query'] : '') : '/'; $port = !empty($matches['port']) ? $matches['port'] : ($matches['scheme'] == 'https' ? 443 : 80); /* //以下内容可以不需要 if($scheme=='https'){ $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); if($post) { curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_POSTFIELDS, $post); } if($cookie) { curl_setopt($curl, CURLOPT_COOKIE, $cookie); } curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); curl_setopt($curl, CURLOPT_HEADER, 0); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $return = curl_exec($curl); if (curl_errno($curl)) { echo '<pre><b>错误:</b><br />'.curl_error($curl); } curl_close($curl); return $return; } // 结束 */ if($post) { $out = "POST $path HTTP/1.0\r\n"; $header = "Accept: */*\r\n"; $header .= "Accept-Language: zh-cn\r\n"; $boundary = $encodetype == 'URLENCODE' ? '' : ';'.substr($post, 0, trim(strpos($post, "\n"))); $header .= $encodetype == 'URLENCODE' ? "Content-Type: application/x-www-form-urlencoded\r\n" : "Content-Type: multipart/form-data$boundary\r\n"; $header .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n"; $header .= "Host: $host:$port\r\n"; $header .= 'Content-Length: '.strlen($post)."\r\n"; $header .= "Connection: Close\r\n"; $header .= "Cache-Control: no-cache\r\n"; $header .= "Cookie: $cookie\r\n\r\n"; $out .= $header.$post; } else { $out = "GET $path HTTP/1.0\r\n"; $header = "Accept: */*\r\n"; $header .= "Accept-Language: zh-cn\r\n"; $header .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n"; $header .= "Host: $host:$port\r\n"; $header .= "Connection: Close\r\n"; $header .= "Cookie: $cookie\r\n\r\n"; $out .= $header; } $fpflag = 0; if(!$fp = @fsocketopen(($scheme == 'https' ? 'ssl' : $scheme).'://'.($scheme == 'https' ? $host : ($ip ? $ip : $host)), $port, $errno, $errstr, $timeout)) { $context = array( 'http' => array( 'method' => $post ? 'POST' : 'GET', 'header' => $header, 'content' => $post, 'timeout' => $timeout, ), ); $context = stream_context_create($context); $fp = @fopen($scheme.'://'.($scheme == 'https' ? $host : ($ip ? $ip : $host)).':'.$port.$path, 'b', false, $context); $fpflag = 1; } if(!$fp) { return ''; } else { stream_set_blocking($fp, $block); stream_set_timeout($fp, $timeout); @fwrite($fp, $out); $status = stream_get_meta_data($fp); if(!$status['timed_out']) { while (!feof($fp) && !$fpflag) { if(($header = @fgets($fp)) && ($header == "\r\n" || $header == "\n")) { break; } } $stop = false; while(!feof($fp) && !$stop) { $data = fread($fp, ($limit == 0 || $limit > 8192 ? 8192 : $limit)); $return .= $data; if($limit) { $limit -= strlen($data); $stop = $limit <= 0; } } } @fclose($fp); return $return; } } .... //找到同步登录登出代码,修改成以下内容 /** * 进入同步登录代码 * * @param int $uid 用户ID * @return string HTML代码 */ function uc_user_synlogin($uid) { $uid = intval($uid); $return = uc_api_post('user', 'synlogin', array('uid'=>$uid)); return $return; } /** * 进入同步登出代码 * * @return string HTML代码 */ function uc_user_synlogout() { $return = uc_api_post('user', 'synlogout', array()); return $return; } .... 然后就可以看到一切都正常了。 另外需要注意的是,必须保证ssl证书在有效期内,因为这个,浪费了2天的时间。

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

第二篇:SpringBoot高级-整合redis作为缓存

Redis简介 Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 搭建Redis环境 1. 安装redis:使用Docker (使用docker中国加速 [root@localhost ~]# docker pull registry.docker-cn.com/library/redis Using default tag: latest latest: Pulling from library/redis 683abbb4ea60: Already exists 259238e792d8: Pull complete 78399601c709: Pull complete f397da474601: Pull complete c57de4edc390: Pull complete b2ea05c9d9a1: Pull complete Digest: sha256:5534b92530acc653f0721ebfa14f31bc718f68bf9070cbba25bb00bc7aacfabb Status: Downloaded newer image for registry.docker-cn.com/library/redis:latest 使用docker images查询 [root@localhost ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE registry.docker-cn.com/library/redis latest 71a81cb279e3 7 days ago 83.4MB mysql 5.7 66bc0f66b7af 7 days ago 372MB 使用docker启动redis [root@localhost ~]# docker run -d -p 6379:6379 --name myredis registry.docker-cn.com/library/redis ba65a5f3fc5c996ea23582a0cfeb0275be759c1a19fc920dd6f513127b5c9738 使用docker ps -a 查询运行情况 [root@localhost ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ba65a5f3fc5c registry.docker-cn.com/library/redis "docker-entrypoint..." About a minute ago Up About a minute 0.0.0.0:6379->6379/tcp myredis 2. 引入redis依赖包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 3. 配置redis # redis配置项 # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=192.168.43.53 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password= 引入redis依赖包后系统就会自动引入RedisAutoConfiguration完成redis相关配置。其中就包括自动配置好了,RedisTemplate 、StringRedisTemplage。我们就可以在项目中直接使用。4. 编写redis测试方法 Redis 常见的五大数据类型: String(字符串)、List(列表)、Set(集合)、Hash(散列)/ ZSet(有序集合) stringRedisTemplate.opsForValue():【String(字符串)】 stringRedisTemplate.opsForList():【List(列表)】 stringRedisTemplate.opsForSet():【Set(集合)】 stringRedisTemplate.opsForHash():【Hash(散列)】 stringRedisTemplate.opsForZSet():【有序集合】 /** * Redis 常见的五大数据类型 * String(字符串)、List(列表)、Set(集合)、Hash(散列)/ ZSet(有序集合) */ @Autowired StringRedisTemplate stringRedisTemplate; @Autowired RedisTemplate redisTemplate; @Autowired RedisTemplate empRedisTemplate; // 自定义RedisTemplate @Test public void test01() { // 给redis存字符串数据 stringRedisTemplate.opsForValue().append("msg", "hello"); stringRedisTemplate.opsForList().leftPush("list", "v1"); stringRedisTemplate.opsForList().leftPush("list", "v2"); stringRedisTemplate.opsForList().leftPush("list", "v3"); stringRedisTemplate.opsForList().leftPush("list", "v4"); String msg = stringRedisTemplate.opsForValue().get("msg"); System.out.println(msg); // 给redis存对象数据 Employee employee = employeeMapper.getEmpById(1); // 这里使用默认的JDK序列化器:JdkSerializationRedisSerializer 将序列化后的数据保存到redis中 // 我们也可以自定义序列化器完成序列化存储 // 1. 自动手动将对象序列化为JSON字符串 // 2. 指定redisTemplate默认的序列化器 redisTemplate.opsForValue().set("emp", employee); empRedisTemplate.opsForValue().set("emp_json", employee); } 自定义序列化器: /** * Created by Administrator on 2018/7/4 0004. */ @Configuration public class MyRedisConfig { @Bean public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Employee> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Employee.class); template.setDefaultSerializer(jackson2JsonRedisSerializer); return template; } } 使用Redis测试SpringBoot缓存 原理:系统使用CacheManager(ConcurrentMapCacheManager默认)来创建Cache组件,来完成缓存的CRUD操作。 默认情况下系统使用SimpleCacheConfiguration来引入ConcurrentMapCacheManager缓存管理器--》ConcurrentMapCache作为缓存组件 引入了redis的starter后容器中保存的是RedisCacheManager--》RedisCache作为缓存组件(通过操作redis缓存数据), 默认保存数据 k-v 都是Object 默认利用jdk序列化保存 所以我们需要自定义CacheManager(使用我们自己的序列化器): 重写RedisCacheConfiguration类中的方法: @Bean public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); cacheManager.setUsePrefix(true); List cacheNames = this.cacheProperties.getCacheNames(); if(!cacheNames.isEmpty()) { cacheManager.setCacheNames(cacheNames); } return (RedisCacheManager)this.customizerInvoker.customize(cacheManager); } @Configuration public class MyRedisConfig { @Bean public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Employee> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Employee> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Employee.class); template.setDefaultSerializer(jackson2JsonRedisSerializer); return template; } @Bean public RedisTemplate<Object, Department> deptRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Department> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Department> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Department.class); template.setDefaultSerializer(jackson2JsonRedisSerializer); return template; } @Bean public RedisCacheManager empCacheManager(RedisTemplate<Object, Employee> empRedisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate); // 使用前綴,默认使用cacheNames作为前缀 cacheManager.setUsePrefix(true); return cacheManager; } @Bean public RedisCacheManager deptCacheManager(RedisTemplate<Object, Department> deptRedisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(deptRedisTemplate); // 使用前綴,默认使用cacheNames作为前缀 cacheManager.setUsePrefix(true); return cacheManager; } @Primary @Bean public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); cacheManager.setUsePrefix(true); return cacheManager; } } 在使用RedisCache时使用注解指定CacheManager: @Cacheable(cacheNames = {"emp"}, cacheManager = "empCacheManager") @Service public class EmployeeService { 手动编码操作缓存: @Autowired @Qualifier("empCacheManager") CacheManager empCacheManager; public Employee getEmp(Integer id){ Employee emp = employeeMapper.getEmpById(id); // 获取某个缓存组件 Cache empCache = empCacheManager.getCache("emp"); empCache.put("emp:1", emp); return emp; }

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Apache Tomcat

Apache Tomcat

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。

JDK

JDK

JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。